Project

General

Profile

Bug #6774 » WTreeView.C

Thomas Frank, 11/29/2018 03:06 PM

 
1
/*
2
 * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
3
 *
4
 * See the LICENSE file for terms of use.
5
 */
6

    
7
#include <math.h>
8
#include <cmath>
9
#include <iostream>
10
#include <boost/algorithm/string.hpp>
11

    
12
#include "Wt/WAbstractItemModel.h"
13
#include "Wt/WApplication.h"
14
#include "Wt/WContainerWidget.h"
15
#include "Wt/WEnvironment.h"
16
#include "Wt/WException.h"
17
#include "Wt/WItemDelegate.h"
18
#include "Wt/WItemSelectionModel.h"
19
#include "Wt/WLogger.h"
20
#include "Wt/WStringStream.h"
21
#include "Wt/WTemplate.h"
22
#include "Wt/WText.h"
23
#include "Wt/WTheme.h"
24
#include "Wt/WTreeView.h"
25
#include "Wt/WVBoxLayout.h"
26
#include "Wt/WWebWidget.h"
27

    
28
#include "WebUtils.h"
29

    
30
#ifndef WT_DEBUG_JS
31
#include "js/WTreeView.min.js"
32
#endif
33

    
34
#include <limits>
35

    
36
#if defined(_MSC_VER) && (_MSC_VER < 1800)
37
namespace {
38
  double round(double x)
39
  {
40
    return floor(x + 0.5);
41
  }
42
}
43
#endif
44

    
45
/*
46
  TODO:
47

    
48
  nice to have:
49
   - stateless slot implementations
50
   - keyboard navigation ?
51
*/
52

    
53
#ifndef DOXYGEN_ONLY
54

    
55
// Widest scrollbar found ? My Gnome Firefox has this
56
#define SCROLLBAR_WIDTH         22
57
#define UNKNOWN_VIEWPORT_HEIGHT 30
58

    
59
namespace Wt {
60

    
61
LOGGER("WTreeView");
62

    
63
class ContentsContainer final : public WContainerWidget
64
{
65
public:
66
  ContentsContainer(WTreeView *treeView)
67
    : treeView_(treeView)
68
  { 
69
    setLayoutSizeAware(true);
70
  }
71

    
72
protected:
73
  virtual void layoutSizeChanged(int width, int height) override
74
  {
75
    treeView_->contentsSizeChanged(width, height);
76
  }
77

    
78
private:
79
  WTreeView *treeView_;
80
};
81

    
82
class ToggleButtonConfig
83
{
84
public:
85
  ToggleButtonConfig(WWidget *parent, const std::string& styleClass)
86
    : styleClass_(styleClass)
87
  {
88
    toggleJS_.reset(new JSlot(parent));
89
  }
90

    
91
  void addState(const std::string& className) {
92
    states_.push_back(className);
93
  }
94

    
95
  void generate() {
96
    WApplication *app = WApplication::instance();
97

    
98
    std::stringstream js;
99
    js << 
100
      "function(s, e) {"
101
      """var states = new Array(";
102

    
103
    for (unsigned i = 0; i < states_.size(); ++i) {
104
      if (i != 0)
105
	js << ',';
106
      js << '\'' << states_[i] << '\'';
107
    }
108

    
109
    js <<
110
      """), i, il;"
111
      """for (i=0; i<" << states_.size() << "; ++i) {"
112
      ""  "if ($(s).hasClass(states[i])) {"
113
      "" << app->javaScriptClass() << ".emit(s, 't-'+states[i]);"
114
      ""    "$(s).removeClass(states[i])"
115
      ""        ".addClass(states[(i+1) % " << states_.size() << "]);"
116
      ""    "break;"
117
      ""  "}"
118
      """}"
119
      "}";
120
    
121
    toggleJS_->setJavaScript(js.str());
122
  }
123

    
124
  const std::vector<std::string>& states() const { return states_; }
125
  const std::string& styleClass() const { return styleClass_; }
126

    
127
private:
128
  std::vector<std::string> states_;
129
  std::unique_ptr<JSlot> toggleJS_;
130
  std::string styleClass_;
131

    
132
  friend class ToggleButton;
133
};
134

    
135
class ToggleButton : public Wt::WText
136
{
137
public:
138
  ToggleButton(ToggleButtonConfig *config) 
139
    : config_(config)
140
  {
141
    setStyleClass(config_->styleClass());
142

    
143
    setInline(false);
144

    
145
    if (WApplication::instance()->environment().ajax()) {
146
      clicked().connect(*config_->toggleJS_);
147
      clicked().preventPropagation();
148

    
149
      for (unsigned i = 0; i < config_->states().size(); ++i)
150
	signals_.push_back(std::unique_ptr<JSignal<> >(new JSignal<>(this, "t-" +  config_->states()[i])));
151
    } else {
152
      clicked().connect(this, &ToggleButton::handleClick);
153
      for (unsigned i = 0; i < config_->states().size(); ++i)
154
	signals_.push_back(std::unique_ptr<Signal<> >(new Signal<>()));
155
    }
156
  }
157

    
158
  SignalBase& signal(int i) { return *signals_[i]; }
159

    
160
  void setState(int i)
161
  {
162
    setStyleClass(config_->styleClass() + config_->states()[i]);
163
  }
164

    
165
private:
166
  std::vector<std::unique_ptr<SignalBase> > signals_;
167
  ToggleButtonConfig       *config_;
168

    
169
  void handleClick() {
170
    for (unsigned i = 0; i < config_->states().size(); ++i)
171
      if (boost::ends_with(styleClass().toUTF8(), config_->states()[i])) {
172
	(dynamic_cast<Signal<> *>(signals_[i].get()))->emit();
173
	break;
174
      }
175
  }
176
};
177

    
178
class RowSpacer final : public Wt::WWebWidget
179
{
180
public:
181
  RowSpacer(Wt::WTreeViewNode *node, int height)
182
    : node_(node),
183
      height_(0)
184
  {
185
    setHeight(0);
186
    setInline(false);
187
    setStyleClass("Wt-spacer");
188
  }
189

    
190
  void setRows(int height, bool force = false);
191

    
192
  int rows() const { return height_; }
193
  Wt::WTreeViewNode *node() const { return node_; }
194

    
195
  int renderedRow(int lowerBound = 0,
196
		  int upperBound = std::numeric_limits<int>::max());
197

    
198
protected:
199
  virtual Wt::DomElementType domElementType() const override
200
  {
201
    return Wt::DomElementType::DIV;
202
  }
203

    
204
private:
205
  Wt::WTreeViewNode *node_;
206
  int height_;
207
};
208

    
209
class WTreeViewNode : public WContainerWidget
210
{
211
public:
212
  WTreeViewNode(WTreeView *view, const WModelIndex& index,
213
		int childrenHeight, bool isLast, WTreeViewNode *parent);
214
  ~WTreeViewNode();
215

    
216
  void update(int firstColumn, int lastColumn);
217
  void updateGraphics(bool isLast, bool isEmpty);
218
  void insertColumns(int column, int count);
219
  void removeColumns(int column, int count);
220
  bool isLast() const;
221

    
222
  void rerenderSpacers();
223

    
224
  const WModelIndex& modelIndex() const { return index_; }
225
  int childrenHeight() const { return childrenHeight_; }
226
  int renderedHeight();
227
  bool childrenLoaded() const { return childrenLoaded_; }
228

    
229
  WWidget *widgetForModelRow(int row);
230
  WTreeViewNode *nextChildNode(WTreeViewNode *n);
231

    
232
  bool isAllSpacer();
233

    
234
  void setTopSpacerHeight(int rows);
235
  void addTopSpacerHeight(int rows);
236
  int topSpacerHeight();
237

    
238
  void setBottomSpacerHeight(int rows);
239
  void addBottomSpacerHeight(int rows);
240
  int bottomSpacerHeight();
241

    
242
  RowSpacer *topSpacer(bool create = false);
243
  RowSpacer *bottomSpacer(bool create = false);
244

    
245
  WContainerWidget *childContainer();
246

    
247
  void shiftModelIndexes(int start, int count);
248

    
249
  WTreeViewNode *parentNode() const { return parentNode_; }
250

    
251
  bool isExpanded();
252

    
253
  void adjustChildrenHeight(int diff);
254
  void normalizeSpacers();
255

    
256
  void selfCheck();
257

    
258
  WTreeView *view() const { return view_; }
259

    
260
  int renderedRow(int lowerBound = 0,
261
		  int upperBound = std::numeric_limits<int>::max());
262
  int renderedRow(WTreeViewNode *node,
263
		  int lowerBound = 0,
264
		  int upperBound = std::numeric_limits<int>::max());
265

    
266
  void renderSelected(bool selected, int column);
267

    
268
  void doExpand();
269
  void doCollapse();
270

    
271
  WWidget *cellWidget(int column);
272

    
273
private:
274
  WTreeView *view_;
275
  WTemplate *nodeWidget_;
276
  WContainerWidget *childContainer_;
277
  WModelIndex index_;
278
  int childrenHeight_;
279
  WTreeViewNode *parentNode_;
280
  bool childrenLoaded_;
281
  void loadChildren();
282

    
283
  WModelIndex childIndex(int column);
284

    
285
  void setCellWidget(int column, std::unique_ptr<WWidget> w);
286
  void addColumnStyleClass(int column, WWidget *w);
287
};
288

    
289
void RowSpacer::setRows(int height, bool force)
290
{
291
  if (height < 0) {
292
    LOG_ERROR("RowSpacer::setRows() with heigth " << height);
293
    height = 0;
294
  }
295

    
296
  if (height == 0)
297
    removeFromParent();
298
  else
299
    if (force || height != height_) {
300
      height_ = height;
301
      setHeight(node_->view()->rowHeight() * height);
302
    }
303
}
304

    
305
int RowSpacer::renderedRow(int lowerBound, int upperBound)
306
{
307
  Wt::WTreeViewNode *n = node();
308

    
309
  int result = 0;
310
  if (this == n->bottomSpacer())
311
    result = n->childrenHeight() - n->bottomSpacerHeight();
312

    
313
  if (result > upperBound)
314
    return result;
315
  else
316
    return result
317
      + n->renderedRow(lowerBound - result, upperBound - result);
318
}
319

    
320
WTreeViewNode::WTreeViewNode(WTreeView *view, const WModelIndex& index,
321
			     int childrenHeight, bool isLast,
322
			     WTreeViewNode *parent)
323
  : view_(view),
324
    nodeWidget_(nullptr),
325
    childContainer_(nullptr),
326
    index_(index),
327
    childrenHeight_(childrenHeight),
328
    parentNode_(parent),
329
    childrenLoaded_(false)
330
{
331
  nodeWidget_ = addWidget(std::unique_ptr<WTemplate>(new WTemplate
332
			  (tr("Wt.WTreeViewNode.template"))));
333
  nodeWidget_->setStyleClass("Wt-item");
334
  nodeWidget_->bindEmpty("cols-row");
335
  nodeWidget_->bindEmpty("expand");
336
  nodeWidget_->bindEmpty("no-expand");
337
  nodeWidget_->bindEmpty("col0");
338

    
339
// TF: Although it seems, that this ctor never gets called with childrenheight == -1
340
//     for non-root nodes, the variable 'selfheight' does not make any sense here,
341
//     if not properly initialized *before* its one and only usage below...
342
  int selfHeight = index == view_->rootIndex() ? 0 : 1;
343
//  int selfHeight = 0;
344
  bool needLoad = view_->isExpanded(index_);
345

    
346
  if (index_ != view_->rootIndex() && !needLoad)
347
    childContainer()->hide();
348

    
349
  if (needLoad) {
350
    childrenLoaded_ = true;
351
    if (childrenHeight_ == -1)
352
      childrenHeight_ = view_->subTreeHeight(index_) - selfHeight;
353

    
354
    if (childrenHeight_ > 0)
355
      setTopSpacerHeight(childrenHeight_);
356
  } else
357
    childrenHeight_ = 0;
358

    
359
  if (index_ != view_->rootIndex()) {
360
    updateGraphics(isLast, !view_->model()->hasChildren(index_));
361
    insertColumns(0, view_->columnCount());
362

    
363
// TF: Too late for initialization. Variable never referenced again...
364
//    selfHeight = 1;
365

    
366
    if (view_->selectionBehavior() == SelectionBehavior::Rows &&
367
	view_->isSelected(index_))
368
      renderSelected(true, 0);
369
  }
370

    
371
  view_->addRenderedNode(this);
372
}
373

    
374
WTreeViewNode::~WTreeViewNode()
375
{
376
  view_->removeRenderedNode(this);
377

    
378
  if (view_->isEditing()) {
379
    WModelIndex parent = index_.parent();
380

    
381
    int thisNodeCount = view_->model()->columnCount(parent);
382

    
383
    for (int i = 0; i < thisNodeCount; ++i) {
384
      WModelIndex child = childIndex(i);
385
      view_->persistEditor(child);
386
    }
387
  }
388
}
389

    
390
void WTreeViewNode::update(int firstColumn, int lastColumn)
391
{
392
  WModelIndex parent = index_.parent();
393

    
394
  int thisNodeCount = view_->model()->columnCount(parent);
395

    
396
  for (int i = firstColumn; i <= lastColumn; ++i) {
397
    WModelIndex child = i < thisNodeCount ? childIndex(i) : WModelIndex();
398

    
399
    WWidget *w = cellWidget(i);
400

    
401
    WFlags<ViewItemRenderFlag> renderFlags = None;
402
    if (view_->selectionBehavior() == SelectionBehavior::Items &&
403
	view_->isSelected(child))
404
      renderFlags |= ViewItemRenderFlag::Selected;
405

    
406
    if (view_->isEditing(child)) {
407
      renderFlags |= ViewItemRenderFlag::Editing;
408
      if (view_->hasEditFocus(child))
409
	renderFlags |= ViewItemRenderFlag::Focused;
410
    }
411

    
412
    if (!view_->isValid(child)) {
413
      renderFlags |= ViewItemRenderFlag::Invalid;
414
    }
415
    
416
    std::unique_ptr<WWidget> wAfter = view_->itemDelegate(i)->update(w, child, renderFlags);
417
    if (wAfter)
418
      w = wAfter.get();
419

    
420
    if (renderFlags.test(ViewItemRenderFlag::Editing))
421
      view_->setEditorWidget(child, w);
422

    
423
    if (wAfter) {
424
      setCellWidget(i, std::move(wAfter));
425

    
426
      /*
427
       * If we are creating a new editor, then reset its current edit
428
       * state.
429
       */
430
      if (renderFlags.test(ViewItemRenderFlag::Editing)) {
431
	cpp17::any state = view_->editState(child);
432
	if (cpp17::any_has_value(state))
433
	  view_->itemDelegate(i)->setEditState(w, child, state);
434
      }
435
    } else
436
      addColumnStyleClass(i, w);
437
  }
438
}
439

    
440
void WTreeViewNode::updateGraphics(bool isLast, bool isEmpty)
441
{
442
  if (index_ == view_->rootIndex())
443
    return;
444

    
445
  if (index_.parent() == view_->rootIndex() && !view_->rootIsDecorated()) {
446
    nodeWidget_->bindEmpty("expand");
447
    nodeWidget_->bindEmpty("no-expand");
448
    return;
449
  }
450

    
451
  if (!isEmpty) {
452
    ToggleButton *expandButton = nodeWidget_->resolve<ToggleButton *>("expand");
453
    if (!expandButton) {
454
      nodeWidget_->bindEmpty("no-expand");
455
      expandButton = 
456
	nodeWidget_->bindWidget
457
	("expand", std::unique_ptr<ToggleButton>(new ToggleButton(view_->expandConfig_.get())));
458

    
459
      if (WApplication::instance()->environment().agentIsIE())
460
	expandButton->setWidth(19);
461

    
462
      expandButton->signal(0).connect(this, &WTreeViewNode::doExpand);
463
      expandButton->signal(1).connect(this, &WTreeViewNode::doCollapse);
464

    
465
      expandButton->setState(isExpanded() ? 1 : 0);
466
    }
467
  } else {
468
    WText *noExpandIcon = nodeWidget_->resolve<WText *>("no-expand");
469
    if (!noExpandIcon) {
470
      noExpandIcon
471
	= nodeWidget_->bindWidget("no-expand", std::unique_ptr<WText>(new WText()));
472
      noExpandIcon->setInline(false);
473
      noExpandIcon->setStyleClass("Wt-ctrl rh noexpand");
474
      if (WApplication::instance()->environment().agentIsIE())
475
	noExpandIcon->setWidth(19);
476
    }
477
  }
478

    
479
  toggleStyleClass("Wt-trunk", !isLast);
480
  nodeWidget_->toggleStyleClass("Wt-end", isLast);
481
  nodeWidget_->toggleStyleClass("Wt-trunk", !isLast);
482
}
483

    
484
void WTreeViewNode::insertColumns(int column, int count)
485
{
486
  WContainerWidget *row = nodeWidget_->resolve<WContainerWidget *>("cols-row");
487

    
488
  if (view_->columnCount() > 1) {
489
    if (!row) {
490
      std::unique_ptr<WContainerWidget> newRow(new WContainerWidget());
491

    
492
      if (view_->rowHeaderCount()) {
493
	newRow->setStyleClass("Wt-tv-rowc rh");
494
	std::unique_ptr<WContainerWidget> rowWrap(new WContainerWidget());
495
	rowWrap->addWidget(std::move(newRow));
496
	newRow = std::move(rowWrap);
497
      }
498

    
499
      newRow->setStyleClass("Wt-tv-row rh");
500

    
501
      nodeWidget_->bindWidget("cols-row", std::move(newRow));
502
    }
503
  } else
504
    if (row)
505
      row->removeFromParent();
506

    
507
  update(0, view_->columnCount() - 1);
508
}
509

    
510
void WTreeViewNode::removeColumns(int column, int count)
511
{
512
  insertColumns(0, 0);
513
}
514

    
515
bool WTreeViewNode::isLast() const
516
{
517
  return index_ == view_->rootIndex()
518
    || (index_.row() == view_->model()->rowCount(index_.parent()) - 1);
519
}
520

    
521
WModelIndex WTreeViewNode::childIndex(int column)
522
{
523
  return view_->model()->index(index_.row(), column, index_.parent());
524
}
525

    
526
void WTreeViewNode::addColumnStyleClass(int column, WWidget *w)
527
{
528
  WStringStream s;
529

    
530
  s << view_->columnStyleClass(column) << " Wt-tv-c rh "
531
    << w->styleClass().toUTF8();
532

    
533
  w->setStyleClass(WString::fromUTF8(s.c_str()));
534
}
535

    
536
void WTreeViewNode::setCellWidget(int column, std::unique_ptr<WWidget> newW)
537
{
538
  WWidget *current = cellWidget(column);
539

    
540
  addColumnStyleClass(column, newW.get());
541

    
542
  if (current)
543
    current->setStyleClass(WString::Empty);
544

    
545
  if (!WApplication::instance()->environment().ajax()) {
546
    WInteractWidget *wi = dynamic_cast<WInteractWidget *>(newW.get());
547
    const WModelIndex ci = childIndex(column);
548
    if (wi) {
549
      wi->clicked().connect
550
	(view_, std::bind(&WTreeView::handleClick, view_, ci,
551
			  std::placeholders::_1));
552
    }
553
  }
554

    
555
  if (column == 0) {
556
    newW->setInline(false);
557
    nodeWidget_->bindWidget("col0", std::move(newW));
558
  } else {
559
    WContainerWidget *row 
560
      = nodeWidget_->resolve<WContainerWidget *>("cols-row");
561
    if (view_->rowHeaderCount())
562
      row = dynamic_cast<WContainerWidget *>(row->widget(0));
563

    
564
    if (current)
565
      current->removeFromParent();
566

    
567
    row->insertWidget(column - 1, std::move(newW));
568
  }
569
}
570

    
571
WWidget *WTreeViewNode::cellWidget(int column)
572
{
573
  if (column == 0)
574
    return nodeWidget_->resolveWidget("col0");
575
  else {
576
    WContainerWidget *row 
577
      = nodeWidget_->resolve<WContainerWidget *>("cols-row");
578

    
579
    if (view_->rowHeaderCount())
580
      row = dynamic_cast<WContainerWidget *>(row->widget(0));
581

    
582
    return row->count() >= column ? row->widget(column - 1) : nullptr;
583
  }
584
}
585

    
586
void WTreeViewNode::doExpand()
587
{
588
  if (isExpanded())
589
    return;
590

    
591
  loadChildren();
592

    
593
  ToggleButton *expandButton = nodeWidget_->resolve<ToggleButton *>("expand");
594
  if (expandButton)
595
    expandButton->setState(1);
596

    
597
  view_->expandedSet_.insert(index_);
598

    
599
  childContainer()->show();
600

    
601
  if (parentNode())
602
    parentNode()->adjustChildrenHeight(childrenHeight_);
603

    
604
  view_->adjustRenderedNode(this, renderedRow());
605
  view_->scheduleRerender(WTreeView::RenderState::NeedAdjustViewPort);
606

    
607
  view_->expanded_.emit(index_);
608
}
609

    
610
void WTreeViewNode::doCollapse()
611
{
612
  if (!isExpanded())
613
    return;
614

    
615
  ToggleButton *expandButton = nodeWidget_->resolve<ToggleButton *>("expand");
616
  if (expandButton)
617
    expandButton->setState(0);
618

    
619
  view_->setCollapsed(index_);
620

    
621
  childContainer()->hide();
622

    
623
  if (parentNode())
624
    parentNode()->adjustChildrenHeight(-childrenHeight_);
625

    
626
  view_->renderedRowsChanged(renderedRow(), -childrenHeight_);
627

    
628
  view_->collapsed_.emit(index_);
629
}
630

    
631
bool WTreeViewNode::isExpanded()
632
{
633
  return index_ == view_->rootIndex() || !childContainer()->isHidden();
634
}
635

    
636
void WTreeViewNode::normalizeSpacers()
637
{
638
  if (childrenLoaded_ && childContainer()->count() == 2) {
639
    RowSpacer *top = topSpacer();
640
    RowSpacer *bottom = bottomSpacer();
641

    
642
    if (top && bottom && top != bottom) {
643
      top->setRows(top->rows() + bottom->rows());
644
      if (bottom)
645
	bottom->removeFromParent();
646
    }
647
  }
648
}
649

    
650
void WTreeViewNode::rerenderSpacers()
651
{
652
  RowSpacer *s = topSpacer();
653
  if (s)
654
    s->setRows(topSpacerHeight(), true);
655

    
656
  s = bottomSpacer();
657
  if (s)
658
    s->setRows(bottomSpacerHeight(), true);
659
}
660

    
661
bool WTreeViewNode::isAllSpacer()
662
{
663
  return childrenLoaded_ && topSpacer() && (topSpacer() == bottomSpacer());
664
}
665

    
666
void WTreeViewNode::loadChildren()
667
{
668
  if (!childrenLoaded_) {
669
    childrenLoaded_ = true;
670

    
671
    view_->expandedSet_.insert(index_);
672
    childrenHeight_ = view_->subTreeHeight(index_) - 1;
673
    view_->expandedSet_.erase(index_);
674

    
675
    if (childrenHeight_ > 0)
676
      setTopSpacerHeight(childrenHeight_);
677
  }
678
}
679

    
680
void WTreeViewNode::adjustChildrenHeight(int diff)
681
{
682
  childrenHeight_ += diff;
683

    
684
  if (isExpanded()) {
685
    WTreeViewNode *parent = parentNode();
686

    
687
    if (parent)
688
      parent->adjustChildrenHeight(diff);
689
    else
690
      view_->pageChanged().emit();
691
  }
692
}
693

    
694
WContainerWidget *WTreeViewNode::childContainer()
695
{
696
  if (!childContainer_) {
697
    childContainer_ = addWidget(std::unique_ptr<WContainerWidget>(new WContainerWidget()));
698
    childContainer_->setList(true);
699

    
700
    if (index_ == view_->rootIndex())
701
      childContainer_->addStyleClass("Wt-tv-root");
702
  }
703

    
704
  return childContainer_;
705
}
706

    
707
WWidget *WTreeViewNode::widgetForModelRow(int modelRow)
708
{
709
  if (!childrenLoaded_)
710
    return nullptr;
711

    
712
  WContainerWidget *c = childContainer();
713

    
714
  int first = topSpacer() ? 1 : 0;
715

    
716
  if (first < c->count()) {
717
    WTreeViewNode *n = dynamic_cast<WTreeViewNode *>(c->widget(first));
718
    if (n) {
719
      int row = topSpacerHeight();
720
      int index = first + (modelRow - row);
721

    
722
      if (index < first)
723
	return topSpacer();
724
      else if (index < c->count())
725
	return c->widget(index);
726
      else
727
	return bottomSpacer();
728
    } else
729
      return bottomSpacer();
730
  } else // isAllSpacer()
731
    return topSpacer();
732
}
733

    
734
void WTreeViewNode::shiftModelIndexes(int start, int offset)
735
{
736
  if (!childrenLoaded_)
737
    return;
738

    
739
  WContainerWidget *c = childContainer();
740

    
741
  int first, end, inc;
742

    
743
  if (offset > 0) {
744
    first = c->count() - 1;
745
    end = -1;
746
    inc = -1;
747
  } else {
748
    first = 0;
749
    end = c->count();
750
    inc = 1;
751
  }
752

    
753
  for (int i = first; i != end; i += inc) {
754
    WTreeViewNode *n = dynamic_cast<WTreeViewNode *>(c->widget(i));
755

    
756
    if (n && n->modelIndex().row() >= start) {
757
      view_->removeRenderedNode(n);
758

    
759
      n->index_ = view_->model()->index(n->modelIndex().row() + offset,
760
					n->modelIndex().column(), index_);
761

    
762
      // update items through delegate
763
      int lastColumn = view_->columnCount() - 1;
764
      int thisNodeCount = view_->model()->columnCount(index_);
765

    
766
      for (int j = 0; j <= lastColumn; ++j) {
767
	WModelIndex child = j < thisNodeCount
768
	  ? n->childIndex(j) : WModelIndex();
769
	view_->itemDelegate(j)->updateModelIndex(n->cellWidget(j), child);
770
      }
771

    
772
      view_->addRenderedNode(n);
773
    }
774
  }
775
}
776

    
777
int WTreeViewNode::renderedHeight()
778
{
779
  return index_ == view_->rootIndex() ? childrenHeight_ :
780
    (1 + (isExpanded() ? childrenHeight_ : 0));
781
}
782

    
783
int WTreeViewNode::topSpacerHeight()
784
{
785
  RowSpacer *s = topSpacer();
786

    
787
  return s ? s->rows() : 0;
788
}
789

    
790
void WTreeViewNode::setTopSpacerHeight(int rows)
791
{
792
  if (rows == 0){
793
    childContainer()->removeWidget(topSpacer());
794
  }
795
  else
796
    topSpacer(true)->setRows(rows);
797
}
798

    
799
void WTreeViewNode::addTopSpacerHeight(int rows)
800
{
801
  setTopSpacerHeight(topSpacerHeight() + rows);
802
}
803

    
804
RowSpacer *WTreeViewNode::topSpacer(bool create)
805
{
806
  WContainerWidget *c = childContainer();
807

    
808
  RowSpacer *result = nullptr;
809
  if (c->count() == 0 || !(result = dynamic_cast<RowSpacer *>(c->widget(0)))) {
810
    if (!create)
811
      return nullptr;
812
    else {
813
      result = new RowSpacer(this, 0);
814
      c->insertWidget(0, std::unique_ptr<WWidget>(result));
815
    }
816
  }
817

    
818
  return result;
819
}
820

    
821
int WTreeViewNode::bottomSpacerHeight()
822
{
823
  RowSpacer *s = bottomSpacer();
824

    
825
  return s ? s->rows() : 0;
826
}
827

    
828
void WTreeViewNode::setBottomSpacerHeight(int rows)
829
{
830
  if (!rows) {
831
    auto bottom = bottomSpacer();
832
    if (bottom)
833
      bottom->removeFromParent();
834
  } else
835
    bottomSpacer(true)->setRows(rows);
836
}
837

    
838
void WTreeViewNode::addBottomSpacerHeight(int rows)
839
{
840
  setBottomSpacerHeight(bottomSpacerHeight() + rows);
841
}
842

    
843
RowSpacer *WTreeViewNode::bottomSpacer(bool create)
844
{
845
  WContainerWidget *c = childContainer();
846

    
847
  RowSpacer *result = nullptr;
848
  if (c->count() == 0
849
      || !(result = dynamic_cast<RowSpacer *>(c->widget(c->count() - 1)))) {
850
    if (!create)
851
      return nullptr;
852
    else {
853
      result = c->addWidget(std::unique_ptr<RowSpacer>(new RowSpacer(this, 0)));
854
    }
855
  }
856

    
857
  return result;
858
}
859

    
860
WTreeViewNode *WTreeViewNode::nextChildNode(WTreeViewNode *prev)
861
{
862
  if (!childrenLoaded_)
863
    return nullptr;
864

    
865
  WContainerWidget *c = childContainer();
866

    
867
  int nextI = prev ? c->indexOf(prev) + 1 : topSpacer() ? 1 : 0;
868

    
869
  if (nextI < c->count())
870
    return dynamic_cast<WTreeViewNode *>(c->widget(nextI));
871
  else
872
    return nullptr;
873
}
874

    
875
int WTreeViewNode::renderedRow(int lowerBound, int upperBound)
876
{
877
  if (!parentNode_)
878
    return 0;
879
  else {
880
    int result = parentNode_->renderedRow(0, upperBound);
881

    
882
    if (result > upperBound)
883
      return result;
884

    
885
    return result
886
      + parentNode_->renderedRow(this, lowerBound - result,
887
				 upperBound - result);
888
  }
889
}
890

    
891
int WTreeViewNode::renderedRow(WTreeViewNode *node,
892
			       int lowerBound, int upperBound)
893
{
894
  if (renderedHeight() < lowerBound)
895
    return renderedHeight();
896

    
897
  int result = topSpacerHeight();
898

    
899
  if (result > upperBound)
900
    return result;
901

    
902
  for (WTreeViewNode *c = nextChildNode(nullptr); c; c = nextChildNode(c)) {
903
    if (c == node)
904
      return result;
905
    else {
906
      result += c->renderedHeight();
907
      if (result > upperBound)
908
	return result;
909
    }
910
  }
911

    
912
  assert(false);
913
  return 0;
914
}
915

    
916
void WTreeViewNode::renderSelected(bool selected, int column)
917
{
918
  std::string cl = WApplication::instance()->theme()->activeClass();
919

    
920
  if (view_->selectionBehavior() == SelectionBehavior::Rows) {
921
    nodeWidget_->toggleStyleClass(cl, selected);
922
  } else {
923
    WWidget *w = cellWidget(column);
924
    w->toggleStyleClass(cl, selected);
925
  }
926
}
927

    
928
void WTreeViewNode::selfCheck()
929
{
930
  assert(renderedHeight() == view_->subTreeHeight(index_));
931

    
932
  int childNodesHeight = 0;
933
  for (WTreeViewNode *c = nextChildNode(nullptr); c; c = nextChildNode(c)) {
934
    c->selfCheck();
935
    childNodesHeight += c->renderedHeight();
936
  }
937

    
938
  if (childNodesHeight == 0) {
939
    assert(topSpacer() == bottomSpacer());
940
    assert(topSpacerHeight() == childrenHeight());
941
  } else {
942
    assert(topSpacerHeight() + childNodesHeight + bottomSpacerHeight()
943
	   == childrenHeight());
944
  }
945
}
946

    
947
WTreeView::WTreeView()
948
  : skipNextMouseEvent_(false),
949
    renderedNodesAdded_(false),
950
    rootNode_(nullptr),
951
    rowHeightRule_(nullptr),
952
    rowWidthRule_(nullptr),
953
    rowContentsWidthRule_(nullptr),
954
    c0StyleRule_(nullptr),
955
    rootIsDecorated_(true),
956
    viewportTop_(0),
957
    viewportHeight_(UNKNOWN_VIEWPORT_HEIGHT),
958
    firstRenderedRow_(0),
959
    validRowCount_(0),
960
    nodeLoad_(0),
961
    headerContainer_(nullptr),
962
    contentsContainer_(nullptr),
963
    scrollBarC_(nullptr),
964
    firstRemovedRow_(0),
965
    removedHeight_(0),
966
    itemEvent_(impl_, "itemEvent"),
967
    itemTouchEvent_(impl_, "itemTouchEvent")
968
{
969
  setSelectable(false);
970

    
971
  expandConfig_.reset(new ToggleButtonConfig(this, "Wt-ctrl rh "));
972
  expandConfig_->addState("expand");
973
  expandConfig_->addState("collapse");
974
  expandConfig_->generate();
975

    
976
  setStyleClass("Wt-itemview Wt-treeview");
977

    
978
  const char *CSS_RULES_NAME = "Wt::WTreeView";
979

    
980
  WApplication *app = WApplication::instance();
981

    
982
  if (app->environment().agentIsWebKit() || app->environment().agentIsOpera())
983
    if (!app->styleSheet().isDefined(CSS_RULES_NAME))
984
      /* bottom scrollbar */
985
      addCssRule
986
	(".Wt-treeview .Wt-tv-rowc", "position: relative;", CSS_RULES_NAME);
987

    
988
  setup();
989
}
990

    
991
void WTreeView::setup()
992
{
993
  WApplication *app = WApplication::instance();
994

    
995
  impl_->clear();
996

    
997
  rootNode_ = nullptr;
998

    
999
  /*
1000
   * Setup main layout
1001
   */
1002
  headers_ = new WContainerWidget();
1003
  headers_->setStyleClass("Wt-headerdiv headerrh");
1004

    
1005
  contents_ = new WContainerWidget();
1006
  WContainerWidget *wrapRoot 
1007
    = contents_->addWidget(std::unique_ptr<WContainerWidget>(new WContainerWidget()));
1008

    
1009
  if (app->environment().agentIsIE()) {
1010
    wrapRoot->setAttributeValue("style", "zoom: 1");
1011
    contents_->setAttributeValue("style", "zoom: 1");
1012
  }
1013

    
1014
  if (app->environment().ajax()) {
1015
    impl_->setPositionScheme(PositionScheme::Relative);
1016

    
1017
    std::unique_ptr<WVBoxLayout> layout(new WVBoxLayout());
1018
    layout->setSpacing(0);
1019
    layout->setContentsMargins(0, 0, 0, 0);
1020

    
1021
    headerContainer_ = new WContainerWidget();
1022
    headerContainer_->setOverflow(Overflow::Hidden);
1023
    headerContainer_->setStyleClass("Wt-header headerrh cwidth");
1024
    headerContainer_->addWidget(std::unique_ptr<WWidget>(headers_));
1025

    
1026
    contentsContainer_ = new ContentsContainer(this);
1027
    contentsContainer_->setStyleClass("cwidth");
1028
    contentsContainer_->setOverflow(Overflow::Auto);
1029
    contentsContainer_->scrolled().connect(this, &WTreeView::onViewportChange);
1030
    contentsContainer_->addWidget(std::unique_ptr<WWidget>(contents_));
1031

    
1032
    layout->addWidget(std::unique_ptr<WWidget>(headerContainer_));
1033
    layout->addWidget(std::unique_ptr<WWidget>(contentsContainer_), 1);
1034

    
1035
    impl_->setLayout(std::move(layout));
1036
  } else {
1037
    contentsContainer_ = new WContainerWidget();
1038
    contentsContainer_->addWidget(std::unique_ptr<WWidget>(contents_));
1039
    contentsContainer_->setOverflow(Overflow::Hidden);
1040

    
1041
    impl_->setPositionScheme(PositionScheme::Relative);
1042
    contentsContainer_->setPositionScheme(PositionScheme::Relative);
1043
    contents_->setPositionScheme(PositionScheme::Relative);
1044

    
1045
    impl_->addWidget(std::unique_ptr<WWidget>(headers_));
1046
    impl_->addWidget(std::unique_ptr<WWidget>(contentsContainer_));
1047

    
1048
    viewportHeight_ = 1000;
1049

    
1050
    resize(width(), height());
1051
  }
1052

    
1053
  setRowHeight(rowHeight());
1054
}
1055

    
1056
void WTreeView::defineJavaScript()
1057
{
1058
  WApplication *app = WApplication::instance();
1059

    
1060
  if (!app->environment().ajax())
1061
    return;
1062

    
1063
  LOAD_JAVASCRIPT(app, "js/WTreeView.js", "WTreeView", wtjs1);
1064

    
1065
  setJavaScriptMember(" WTreeView", "new " WT_CLASS ".WTreeView("
1066
		      + app->javaScriptClass() + "," + jsRef() + ","
1067
		      + contentsContainer_->jsRef() + ","
1068
		      + headerContainer_->jsRef() + ","
1069
		      + std::to_string(rowHeaderCount())+ ",'"
1070
		      + WApplication::instance()->theme()->activeClass()
1071
		      + "');");
1072

    
1073
  setJavaScriptMember(WT_RESIZE_JS,
1074
		      "function(self,w,h,s) {"
1075
		      """$(self).data('obj').wtResize();"
1076
		      "}");
1077
}
1078

    
1079
void WTreeView::setRowHeaderCount(int count)
1080
{
1081
  WApplication *app = WApplication::instance();
1082

    
1083
  // This kills progressive enhancement too
1084
  if (!app->environment().ajax())
1085
    return;
1086

    
1087
  int oldCount = rowHeaderCount();
1088

    
1089
  if (count != 0 && count != 1)
1090
    throw WException("WTreeView::setRowHeaderCount: count must be 0 or 1");
1091

    
1092
  WAbstractItemView::setRowHeaderCount(count);
1093

    
1094
  if (count && !oldCount) {
1095
    addStyleClass("column1");
1096
    WContainerWidget *rootWrap
1097
      = dynamic_cast<WContainerWidget *>(contents_->widget(0));
1098
    rootWrap->setWidth(WLength(100, LengthUnit::Percentage));
1099
    rootWrap->setOverflow(Overflow::Hidden);
1100
    contents_->setPositionScheme(PositionScheme::Relative);
1101
    rootWrap->setPositionScheme(PositionScheme::Absolute);
1102

    
1103
    bool useStyleLeft
1104
      = app->environment().agentIsWebKit()
1105
      || app->environment().agentIsOpera();
1106

    
1107
    if (useStyleLeft) {
1108
      bool rtl = app->layoutDirection() == LayoutDirection::RightToLeft;
1109

    
1110
      tieRowsScrollJS_.setJavaScript
1111
	("function(obj, event) {"
1112
	 "" WT_CLASS ".getCssRule('#" + id() + " .Wt-tv-rowc').style.left"
1113
	 ""  "= -obj.scrollLeft "
1114
	 + (rtl ? "+ (obj.firstChild.offsetWidth - obj.offsetWidth)" : "")
1115
	 + "+ 'px';"
1116
	 "}");
1117
    } else {
1118
      /*
1119
       * this is for some reason very very slow in webkit, and with
1120
       * application/xml on Firefox (jQuery suffers)
1121
       */
1122
      tieRowsScrollJS_.setJavaScript
1123
	("function(obj, event) {"
1124
	 "$('#" + id() + " .Wt-tv-rowc').parent().scrollLeft(obj.scrollLeft);"
1125
	 "}");
1126
    }
1127

    
1128
    WContainerWidget *scrollBarContainer = new WContainerWidget();
1129
    scrollBarContainer->setStyleClass("cwidth");
1130
    scrollBarContainer->setHeight(SCROLLBAR_WIDTH);
1131
    scrollBarC_ 
1132
      = scrollBarContainer->addWidget(std::unique_ptr<WContainerWidget>(new WContainerWidget()));
1133
    scrollBarC_->setStyleClass("Wt-tv-row Wt-scroll");
1134
    scrollBarC_->scrolled().connect(tieRowsScrollJS_);
1135

    
1136
    if (app->environment().agentIsIE()) {
1137
      scrollBarContainer->setPositionScheme(PositionScheme::Relative);
1138
      bool rtl = app->layoutDirection() == LayoutDirection::RightToLeft;
1139
      scrollBarC_->setAttributeValue("style",
1140
				     std::string(rtl ? "left:" : "right:")
1141
				     + "0px");
1142
      // and still it doesn't work properly...
1143
    }
1144

    
1145
    WContainerWidget *scrollBar 
1146
      = scrollBarC_->addWidget(std::unique_ptr<WContainerWidget>(new WContainerWidget()));
1147
    scrollBar->setStyleClass("Wt-tv-rowc");
1148
    if (useStyleLeft)
1149
      scrollBar->setAttributeValue("style", "left: 0px;");
1150
    impl_->layout()->addWidget(std::unique_ptr<WWidget>(scrollBarContainer));
1151
  }
1152
}
1153

    
1154
WTreeView::~WTreeView()
1155
{
1156
  wApp->styleSheet().removeRule(rowHeightRule_);
1157
  wApp->styleSheet().removeRule(rowWidthRule_);
1158
  wApp->styleSheet().removeRule(rowContentsWidthRule_);
1159
  wApp->styleSheet().removeRule(c0StyleRule_);
1160

    
1161
  impl_->clear();
1162
}
1163

    
1164
std::string WTreeView::columnStyleClass(int column) const
1165
{
1166
  return columnInfo(column).styleClass();
1167
}
1168

    
1169
void WTreeView::setColumnWidth(int column, const WLength& width)
1170
{
1171
  if (!width.isAuto())
1172
    columnInfo(column).width = WLength(round(width.value()), width.unit());
1173
  else
1174
    columnInfo(column).width = WLength::Auto;
1175

    
1176
  WWidget *toResize = columnInfo(column).styleRule->templateWidget();
1177
  toResize->setWidth(0);
1178
  toResize->setWidth(columnInfo(column).width.toPixels());
1179

    
1180
  WApplication *app = WApplication::instance();
1181

    
1182
  if (app->environment().ajax() && 
1183
      static_cast<unsigned int>(renderState_) < 
1184
      static_cast<unsigned int>(RenderState::NeedRerenderHeader))
1185
    doJavaScript("$('#" + id() + "').data('obj').adjustColumns();");
1186

    
1187
  if (!app->environment().ajax() && column == 0 && !width.isAuto()) {
1188
    double total = 0;
1189
    for (int i = 0; i < columnCount(); ++i)
1190
      if (!columnInfo(i).hidden)
1191
	total += columnWidth(i).toPixels();
1192

    
1193
    resize(total, height());
1194
  }
1195
}
1196

    
1197
void WTreeView::setColumnHidden(int column, bool hidden)
1198
{
1199
  if (columnInfo(column).hidden != hidden) {
1200
    WAbstractItemView::setColumnHidden(column, hidden);
1201

    
1202
    WWidget *toHide = columnInfo(column).styleRule->templateWidget();
1203
    toHide->setHidden(hidden);
1204

    
1205
    setColumnWidth(column, columnWidth(column));
1206
  }
1207
}
1208

    
1209
void WTreeView::setHeaderHeight(const WLength& height)
1210
{
1211
  WAbstractItemView::setHeaderHeight(height);
1212
}
1213

    
1214
void WTreeView::setRootIsDecorated(bool show)
1215
{
1216
  rootIsDecorated_ = show;
1217
}
1218

    
1219
void WTreeView::setAlternatingRowColors(bool enable)
1220
{
1221
  WAbstractItemView::setAlternatingRowColors(enable);
1222
  setRootNodeStyle();
1223
}
1224

    
1225
void WTreeView::setRootNodeStyle()
1226
{
1227
  if (!rootNode_)
1228
    return;
1229

    
1230
  if (alternatingRowColors())
1231
    rootNode_->decorationStyle().setBackgroundImage
1232
      (WLink(WApplication::instance()->theme()->resourcesUrl()
1233
	     + "stripes/stripe-" + std::to_string
1234
	     (static_cast<int>(rowHeight().toPixels())) + "px.gif"));
1235
   else
1236
     rootNode_->decorationStyle().setBackgroundImage(WLink(""));
1237
 }
1238

    
1239
void WTreeView::setRowHeight(const WLength& rowHeight)
1240
{
1241
  WAbstractItemView::setRowHeight(rowHeight);
1242

    
1243
  if (rowHeightRule_) {
1244
    rowHeightRule_->templateWidget()->setHeight(rowHeight);
1245
    rowHeightRule_->templateWidget()->setLineHeight(rowHeight);
1246
  }
1247

    
1248
  if (!WApplication::instance()->environment().ajax() && !height().isAuto())
1249
    viewportHeight_ = static_cast<int>(contentsContainer_->height().toPixels()
1250
				       / rowHeight.toPixels());
1251

    
1252
  setRootNodeStyle();
1253

    
1254
  for (NodeMap::const_iterator i = renderedNodes_.begin();
1255
       i != renderedNodes_.end(); ++i)
1256
    i->second->rerenderSpacers();
1257

    
1258
  if (rootNode_)
1259
    scheduleRerender(RenderState::NeedAdjustViewPort);
1260
}
1261

    
1262
void WTreeView::resize(const WLength& width, const WLength& height)
1263
{
1264
  WApplication *app = WApplication::instance();
1265
  WLength w = app->environment().ajax() ? WLength::Auto : width;
1266

    
1267
  if (app->environment().ajax())
1268
    contentsContainer_->setWidth(w);
1269
  
1270
  if (headerContainer_)
1271
    headerContainer_->setWidth(w);
1272

    
1273
  if (!height.isAuto()) {
1274
    if (!app->environment().ajax()) {
1275
      if (impl_->count() < 3)
1276
	impl_->addWidget(createPageNavigationBar());
1277

    
1278
      double navigationBarHeight = 35;
1279
      double headerHeight = this->headerHeight().toPixels();
1280

    
1281
      int h = (int)(height.toPixels() - navigationBarHeight - headerHeight);
1282
      contentsContainer_->setHeight(std::max(h, (int)rowHeight().value()));
1283

    
1284
      viewportHeight_
1285
	= static_cast<int>(contentsContainer_->height().toPixels()
1286
			   / rowHeight().toPixels());
1287
    } else
1288
      viewportHeight_ = static_cast<int>
1289
	(std::ceil(height.toPixels() / rowHeight().toPixels()));
1290
  } else {
1291
    if (app->environment().ajax())
1292
      viewportHeight_ = UNKNOWN_VIEWPORT_HEIGHT;
1293

    
1294
    scheduleRerender(RenderState::NeedAdjustViewPort);
1295
  }
1296

    
1297
  WCompositeWidget::resize(width, height);
1298
}
1299

    
1300
int WTreeView::calcOptimalFirstRenderedRow() const
1301
{
1302
  if (WApplication::instance()->environment().ajax())
1303
    return std::max(0, viewportTop_ - viewportHeight_ - viewportHeight_ / 2);
1304
  else
1305
    return viewportTop_;
1306
}
1307

    
1308
int WTreeView::calcOptimalRenderedRowCount() const
1309
{
1310
  if (WApplication::instance()->environment().ajax())
1311
    return 4 * viewportHeight_;
1312
  else
1313
    return viewportHeight_ + 5; // some margin... something inaccurate going on?
1314
}
1315

    
1316
void WTreeView::setModel(const std::shared_ptr<WAbstractItemModel>& model)
1317
{
1318
  WAbstractItemView::setModel(model);
1319

    
1320
  typedef WTreeView Self;
1321

    
1322
  /* connect slots to new model */
1323
  modelConnections_.push_back(model->columnsInserted().connect
1324
			      (this, &Self::modelColumnsInserted));
1325
  modelConnections_.push_back(model->columnsAboutToBeRemoved().connect
1326
			      (this, &Self::modelColumnsAboutToBeRemoved));
1327
  modelConnections_.push_back(model->columnsRemoved().connect
1328
			      (this, &Self::modelColumnsRemoved));
1329
  modelConnections_.push_back(model->rowsInserted().connect
1330
			      (this, &Self::modelRowsInserted));
1331
  modelConnections_.push_back(model->rowsAboutToBeRemoved().connect
1332
			      (this, &Self::modelRowsAboutToBeRemoved));
1333
  modelConnections_.push_back(model->rowsRemoved().connect
1334
			      (this, &Self::modelRowsRemoved));
1335
  modelConnections_.push_back(model->dataChanged().connect
1336
			      (this, &Self::modelDataChanged));
1337
  modelConnections_.push_back(model->headerDataChanged().connect
1338
			      (this, &Self::modelHeaderDataChanged));
1339
  modelConnections_.push_back(model->layoutAboutToBeChanged().connect
1340
			      (this, &Self::modelLayoutAboutToBeChanged));
1341
  modelConnections_.push_back(model->layoutChanged().connect
1342
			      (this, &Self::modelLayoutChanged));
1343
  modelConnections_.push_back(model->modelReset().connect
1344
			      (this, &Self::modelReset));
1345

    
1346
  expandedSet_.clear();
1347

    
1348
  WApplication *app = WApplication::instance();
1349
  while (static_cast<int>(columns_.size()) > model->columnCount()) {
1350
    app->styleSheet().removeRule(columns_.back().styleRule.get());
1351
    columns_.erase(columns_.begin() + columns_.size() - 1);
1352
  }
1353

    
1354
  pageChanged().emit();
1355
}
1356

    
1357
void WTreeView::scheduleRerender(RenderState what)
1358
{
1359
  if (what == RenderState::NeedRerender || what == RenderState::NeedRerenderData) {
1360
    if(rootNode_){
1361
      rootNode_->removeFromParent();
1362
      rootNode_ = nullptr;
1363
    }
1364
  }
1365

    
1366
  WAbstractItemView::scheduleRerender(what);
1367
}
1368

    
1369
void WTreeView::render(WFlags<RenderFlag> flags)
1370
{
1371
  WApplication *app = WApplication::instance();
1372

    
1373
  if (flags.test(RenderFlag::Full)) {
1374
    defineJavaScript();
1375

    
1376
    if (!itemTouchEvent_.isConnected())
1377
      itemTouchEvent_.connect(this, &WTreeView::onItemTouchEvent);
1378

    
1379
    if (!itemEvent_.isConnected()) {
1380
      itemEvent_.connect(this, &WTreeView::onItemEvent);
1381

    
1382
      addCssRule("#" + id() + " .cwidth", "");
1383

    
1384
      rowHeightRule_ = app->styleSheet().addRule
1385
	(std::unique_ptr<WCssTemplateRule>(new WCssTemplateRule("#" + id() + " .rh")));
1386
      rowHeightRule_->templateWidget()->setHeight(rowHeight());
1387
      rowHeightRule_->templateWidget()->setLineHeight(rowHeight());
1388

    
1389
      rowWidthRule_ = app->styleSheet().addRule
1390
	(std::unique_ptr<WCssTemplateRule>(new WCssTemplateRule("#" + id() + " .Wt-tv-row")));
1391
      rowContentsWidthRule_ = app->styleSheet().addRule
1392
	(std::unique_ptr<WCssTemplateRule>(new WCssTemplateRule("#" + id() + " .Wt-tv-rowc")));
1393

    
1394
      if (app->environment().ajax()) {
1395
	contentsContainer_->scrolled().connect
1396
	  ("function(obj, event) {"
1397
	   /*
1398
	    * obj.sb: workaround for Konqueror to prevent recursive
1399
	    * invocation because reading scrollLeft triggers onscroll()
1400
	    */
1401
	   """if (obj.sb) return;"
1402
	   """obj.sb = true;"
1403
	   "" + headerContainer_->jsRef() + ".scrollLeft=obj.scrollLeft;"
1404
	   /* the following is a workaround for IE7 */
1405
	   """var t = " + contents_->jsRef() + ".firstChild;"
1406
	   """var h = " + headers_->jsRef() + ";"
1407
	   """h.style.width = (t.offsetWidth - 1) + 'px';"
1408
	   """h.style.width = t.offsetWidth + 'px';"
1409
	   """obj.sb = false;"
1410
	   "}");	
1411
      }
1412

    
1413
      c0StyleRule_ = addCssRule("#" + id() + " li .none",
1414
				"width: auto;"
1415
				"text-overflow: ellipsis;"
1416
				"overflow: hidden");
1417
   
1418
      if (columns_.size() > 0) {
1419
	ColumnInfo& ci = columnInfo(0);
1420
	c0StyleRule_->setSelector("#" + id() + " li ." + ci.styleClass());
1421
      }
1422
    }
1423
  }
1424

    
1425
  while (renderState_ != RenderState::RenderOk) {
1426
    RenderState s = renderState_;
1427
    renderState_ = RenderState::RenderOk;
1428

    
1429
    switch (s) {
1430
    case RenderState::NeedRerender:
1431
      rerenderHeader();
1432
      rerenderTree();
1433
      break;
1434
    case RenderState::NeedRerenderHeader:
1435
      rerenderHeader();
1436
      break;
1437
    case RenderState::NeedRerenderData:
1438
      rerenderTree();
1439
      break;
1440
    case RenderState::NeedAdjustViewPort:
1441
      adjustToViewport();
1442
      break;
1443
    default:
1444
      break;
1445
    }
1446
  }
1447

    
1448
  if (app->environment().ajax() && rowHeaderCount() && renderedNodesAdded_) {
1449
    doJavaScript("{var s=" + scrollBarC_->jsRef() + ";"
1450
		 """if (s) {" + tieRowsScrollJS_.execJs("s") + "}"
1451
		 "}");
1452
    renderedNodesAdded_ = false;
1453
  }
1454

    
1455
  // update the rowHeight (needed for scrolling fix)
1456
  WStringStream s;
1457
  s << "jQuery.data(" << jsRef()
1458
    << ", 'obj').setRowHeight("
1459
    <<  static_cast<int>(this->rowHeight().toPixels())
1460
    << ");";
1461

    
1462
  if (app->environment().ajax()) 
1463
    doJavaScript(s.str());
1464

    
1465
  WAbstractItemView::render(flags);
1466
}
1467

    
1468
void WTreeView::rerenderHeader()
1469
{
1470
  WApplication *app = WApplication::instance();
1471

    
1472
  saveExtraHeaderWidgets();
1473
  headers_->clear();
1474

    
1475
  WContainerWidget *row
1476
    = headers_->addWidget(std::unique_ptr<WContainerWidget>(new WContainerWidget()));
1477
  row->setFloatSide(Side::Right);
1478

    
1479
  if (rowHeaderCount()) {
1480
    row->setStyleClass("Wt-tv-row headerrh background");
1481
    row = row->addWidget(std::unique_ptr<WContainerWidget>(new WContainerWidget()));
1482
    row->setStyleClass("Wt-tv-rowc headerrh");
1483
  } else
1484
    row->setStyleClass("Wt-tv-row");
1485

    
1486
  for (int i = 0; i < columnCount(); ++i) {
1487
    std::unique_ptr<WWidget> w = createHeaderWidget(i);
1488

    
1489
    if (i != 0) {
1490
      w->setFloatSide(Side::Left);
1491
      row->addWidget(std::move(w));
1492
    } else
1493
      headers_->addWidget(std::move(w));
1494
  }
1495

    
1496
  if (app->environment().ajax())
1497
    doJavaScript("$('#" + id() + "').data('obj').adjustColumns();");
1498
}
1499

    
1500
void WTreeView::enableAjax()
1501
{
1502
  saveExtraHeaderWidgets();
1503

    
1504
  setup();
1505
  defineJavaScript();
1506

    
1507
  scheduleRerender(RenderState::NeedRerender);
1508

    
1509
  WAbstractItemView::enableAjax();
1510
}
1511

    
1512
void WTreeView::rerenderTree()
1513
{
1514
  WContainerWidget *wrapRoot
1515
    = dynamic_cast<WContainerWidget *>(contents_->widget(0));
1516

    
1517
  bool firstTime = rootNode_ == nullptr;
1518
  wrapRoot->clear();
1519

    
1520
  firstRenderedRow_ = calcOptimalFirstRenderedRow();
1521
  validRowCount_ = 0;
1522

    
1523
  rootNode_ = wrapRoot->addWidget
1524
    (std::unique_ptr<WTreeViewNode>(new WTreeViewNode(this, rootIndex(), -1, true, nullptr)));
1525

    
1526
  if (WApplication::instance()->environment().ajax()) {
1527

    
1528
    if (editTriggers().test(EditTrigger::SingleClicked) || 
1529
	clicked().isConnected()) {
1530
      connectObjJS(rootNode_->clicked(), "click");
1531
      if (firstTime)
1532
	connectObjJS(contentsContainer_->clicked(), "rootClick");
1533
    }
1534

    
1535
    if (editTriggers().test(EditTrigger::DoubleClicked) || 
1536
	doubleClicked().isConnected()) {
1537
      connectObjJS(rootNode_->doubleClicked(), "dblClick");
1538
      if (firstTime)
1539
	connectObjJS(contentsContainer_->doubleClicked(), "rootDblClick");
1540
    }
1541

    
1542
    connectObjJS(rootNode_->mouseWentDown(), "mouseDown");
1543
    if (firstTime)
1544
      connectObjJS(contentsContainer_->mouseWentDown(), "rootMouseDown");
1545

    
1546
    if (mouseWentUp().isConnected()) { 
1547
	  // Do not stop propagation to avoid mouseDrag event being emitted 
1548
      connectObjJS(rootNode_->mouseWentUp(), "mouseUp");
1549
      if (firstTime)
1550
	connectObjJS(contentsContainer_->mouseWentUp(), "rootMouseUp");
1551
    }
1552

    
1553
#ifdef WT_CNOR
1554
    // workaround because cnor is a bit dumb and does not understand that it
1555
    // can convert EventSignal<TouchEvent>& to EventSignalBase&
1556
    EventSignalBase& a = rootNode_->touchStarted();
1557
#endif
1558
   
1559
    connectObjJS(rootNode_->touchStarted(), "touchStart");
1560
    connectObjJS(rootNode_->touchMoved(), "touchMove");
1561
    connectObjJS(rootNode_->touchEnded(), "touchEnd");
1562
  }
1563

    
1564
  setRootNodeStyle();
1565

    
1566
  pageChanged().emit();
1567

    
1568
  adjustToViewport();
1569
}
1570

    
1571
void WTreeView::onViewportChange(WScrollEvent e)
1572
{
1573
  viewportTop_ = static_cast<int>
1574
    (std::floor(e.scrollY() / rowHeight().toPixels()));
1575

    
1576
  contentsSizeChanged(0, e.viewportHeight());
1577
}
1578

    
1579
void WTreeView::contentsSizeChanged(int width, int height)
1580
{
1581
  viewportHeight_
1582
    = static_cast<int>(std::ceil(height / rowHeight().toPixels()));
1583

    
1584
  scheduleRerender(RenderState::NeedAdjustViewPort);
1585
}
1586

    
1587
void WTreeView::onItemEvent(std::string nodeAndColumnId, std::string type,
1588
			    std::string extra1, std::string extra2,
1589
			    WMouseEvent event)
1590
{
1591
  // nodeId and columnId are combined because WSignal needed to be changed
1592
  // since MSVS 2012 does not support >5 arguments in std::bind()
1593
  WModelIndex index = calculateModelIndex(nodeAndColumnId);
1594

    
1595
  /*
1596
   * Every mouse event is emitted twice (because we don't prevent
1597
   * the propagation because it will block the mouseWentUp event
1598
   * and therefore result in mouseDragged being emitted (See #3879)
1599
   */
1600
  if (nodeAndColumnId.empty() && skipNextMouseEvent_) {
1601
    skipNextMouseEvent_ = false; 
1602
    return;
1603
  } else if (!nodeAndColumnId.empty()) {
1604
    skipNextMouseEvent_ = true;
1605
  }
1606

    
1607
  if (type == "clicked") {
1608
    handleClick(index, event);
1609
  } else if (type == "dblclicked") {
1610
    handleDoubleClick(index, event);
1611
  } else if (type == "mousedown") {
1612
    handleMouseDown(index, event);
1613
  } else if (type == "mouseup") {
1614
    handleMouseUp(index, event);
1615
  } else if (type == "drop") {
1616
    WDropEvent e(WApplication::instance()->decodeObject(extra1), extra2, event);
1617
    dropEvent(e, index);
1618
  }
1619
}
1620

    
1621
void WTreeView::onItemTouchEvent(std::string nodeAndColumnId, std::string type, WTouchEvent event)
1622
{
1623
  // nodeId and columnId are combined because WSignal needed to be changed
1624
  // since MSVS 2012 does not support >5 arguments in std::bind()
1625
  std::vector<WModelIndex> index;
1626
  index.push_back(calculateModelIndex(nodeAndColumnId));
1627

    
1628
  if (type == "touchselect")
1629
    handleTouchSelect(index, event);
1630
  else if (type == "touchstart")
1631
    handleTouchStart(index, event);
1632
  else if (type == "touchend")
1633
    handleTouchEnd(index, event);
1634
}
1635

    
1636
WModelIndex WTreeView::calculateModelIndex(std::string nodeAndColumnId)
1637
{
1638
  std::vector<std::string> nodeAndColumnSplit;
1639
  boost::split(nodeAndColumnSplit, nodeAndColumnId, boost::is_any_of(":"));
1640

    
1641
  WModelIndex index;
1642

    
1643
  if (nodeAndColumnSplit.size() == 2) {
1644
    std::string nodeId = nodeAndColumnSplit[0];
1645
    int columnId = -1;
1646
    try {
1647
      columnId = Utils::stoi(std::string(nodeAndColumnSplit[1]));
1648
    } catch (std::exception& e) {
1649
      LOG_ERROR("WTreeview::calculateModelIndex: bad value for format 1: "
1650
		<< nodeAndColumnSplit[1]);
1651
    }
1652

    
1653
    int column = (columnId == 0 ? 0 : -1);
1654
    for (unsigned i = 0; i < columns_.size(); ++i)
1655
      if (columns_[i].id == columnId) {
1656
	column = i;
1657
	break;
1658
      }
1659

    
1660
    if (column != -1) {
1661
      WModelIndex c0index;
1662
      for (NodeMap::const_iterator i = renderedNodes_.begin();
1663
	    i != renderedNodes_.end(); ++i) {
1664
	if (i->second->id() == nodeId) {
1665
          c0index = i->second->modelIndex();
1666
 	  break;
1667
	}
1668
      }
1669

    
1670
      if (c0index.isValid())
1671
	index = model()->index(c0index.row(), column, c0index.parent());
1672
      else
1673
        LOG_ERROR("WTreeView::calculateModelIndex: illegal node id: " << nodeId);
1674
    }
1675
  }
1676
  return index;
1677
}
1678

    
1679
int WTreeView::subTreeHeight(const WModelIndex& index,
1680
			     int lowerBound, int upperBound) const
1681
{
1682
  int result = 0;
1683

    
1684
  if (index != rootIndex())
1685
    ++result;
1686

    
1687
  if (result >= upperBound)
1688
    return result;
1689

    
1690
  if (model() && isExpanded(index)) {
1691
    int childCount = model()->rowCount(index);
1692

    
1693
    for (int i = 0; i < childCount; ++i) {
1694
      WModelIndex childIndex = model()->index(i, 0, index);
1695

    
1696
      result += subTreeHeight(childIndex, upperBound - result);
1697

    
1698
      if (result >= upperBound)
1699
	return result;
1700
    }
1701
  }
1702

    
1703
  return result;
1704
}
1705

    
1706
bool WTreeView::isExpanded(const WModelIndex& index) const
1707
{
1708
  return index == rootIndex()
1709
    || expandedSet_.find(index) != expandedSet_.end();
1710
}
1711

    
1712
bool WTreeView::isExpandedRecursive(const WModelIndex& index) const
1713
{
1714
  if (isExpanded(index)) {
1715
    if (index != rootIndex())
1716
      return isExpanded(index.parent());
1717
    else
1718
      return false;
1719
  } else
1720
    return false;
1721
}
1722

    
1723
void WTreeView::setCollapsed(const WModelIndex& index)
1724
{
1725
  expandedSet_.erase(index);
1726

    
1727
  /*
1728
   * Deselecting everything that is collapsed is not consistent with
1729
   * the allowed initial state. If the user wants this, he can implement
1730
   * this himself.
1731
   */
1732
#if 0
1733
  bool selectionHasChanged = false;
1734
  WModelIndexSet& selection = selectionModel()->selection_;
1735

    
1736
  WModelIndexSet toDeselect;
1737
  for (WModelIndexSet::iterator it = selection.lower_bound(index);
1738
       it != selection.end(); ++it) {    
1739
    WModelIndex i = *it;
1740
    if (i == index) {
1741
    } else if (WModelIndex::isAncestor(i, index)) {
1742
      toDeselect.insert(i);
1743
    } else
1744
      break;
1745
  }
1746

    
1747
  for (WModelIndexSet::iterator it = toDeselect.begin();
1748
       it != toDeselect.end(); ++it)
1749
    if (internalSelect(*it, SelectionFlag::Deselect))
1750
      selectionHasChanged = true;
1751

    
1752
  if (selectionHasChanged)
1753
    selectionChanged().emit();
1754
#endif
1755
}
1756

    
1757
void WTreeView::setExpanded(const WModelIndex& index, bool expanded)
1758
{
1759
  if (isExpanded(index) != expanded) {
1760
    WWidget *w = widgetForIndex(index);
1761

    
1762
    WTreeViewNode *node = w ? dynamic_cast<WTreeViewNode *>(w) : nullptr;
1763

    
1764
    if (node) {
1765
      if (expanded)
1766
	node->doExpand();
1767
      else
1768
	node->doCollapse();
1769
    } else {
1770
      int height = subTreeHeight(index);
1771

    
1772
      if (expanded)
1773
	expandedSet_.insert(index);
1774
      else
1775
	setCollapsed(index);
1776

    
1777
      if (w) {
1778
	RowSpacer *spacer = dynamic_cast<RowSpacer *>(w);
1779

    
1780
	int diff = subTreeHeight(index) - height;
1781

    
1782
	spacer->setRows(spacer->rows() + diff);
1783
	spacer->node()->adjustChildrenHeight(diff);
1784

    
1785
	renderedRowsChanged(renderedRow(index, spacer,
1786
					renderLowerBound(), renderUpperBound()),
1787
			    diff);
1788
      }
1789
    }
1790
  }
1791
}
1792

    
1793
void WTreeView::expand(const WModelIndex& index)
1794
{
1795
  setExpanded(index, true);
1796
}
1797

    
1798
void WTreeView::collapse(const WModelIndex& index)
1799
{
1800
  setExpanded(index, false);
1801
}
1802

    
1803
void WTreeView::expandToDepth(int depth)
1804
{
1805
  if (depth > 0)
1806
    expandChildrenToDepth(rootIndex(), depth);
1807
}
1808

    
1809
void WTreeView::expandChildrenToDepth(const WModelIndex& index, int depth)
1810
{
1811
  for (int i = 0; i < model()->rowCount(index); ++i) {
1812
    WModelIndex c = model()->index(i, 0, index);
1813

    
1814
    expand(c);
1815

    
1816
    if (depth > 1)
1817
      expandChildrenToDepth(c, depth - 1);
1818
  }
1819
}
1820

    
1821
WWidget *WTreeView::itemWidget(const WModelIndex& index) const
1822
{
1823
  if (!index.isValid())
1824
    return nullptr;
1825

    
1826
  WTreeViewNode *n = nodeForIndex(index);
1827
  if (n)
1828
    return n->cellWidget(index.column());
1829
  else
1830
    return nullptr;
1831
}
1832

    
1833
/*
1834
 * Returns the widget that renders the node indicated by index.
1835
 *
1836
 * It may be:
1837
 *  - a tree node (node->modelIndex() == index)
1838
 *  - 0 if index is not somewhere in column 0
1839
 *  - a spacer which includes the 'index', when all intermediate
1840
 *    nodes are expanded, or
1841
 *  - 0 otherwise
1842
 */
1843
WWidget *WTreeView::widgetForIndex(const WModelIndex& index) const
1844
{
1845
  if (!index.isValid())
1846
    return rootNode_;
1847

    
1848
  if (index.column() != 0)
1849
    return nullptr;
1850

    
1851
  NodeMap::const_iterator i = renderedNodes_.find(index);
1852

    
1853
  if (i != renderedNodes_.end())
1854
    return i->second;
1855
  else {
1856
    if (!isExpanded(index.parent()))
1857
      return nullptr;
1858

    
1859
    WWidget *parent = widgetForIndex(index.parent());
1860
    WTreeViewNode *parentNode = dynamic_cast<WTreeViewNode *>(parent);
1861

    
1862
    if (parentNode) {
1863
      int row = getIndexRow(index, parentNode->modelIndex(), 0,
1864
			    std::numeric_limits<int>::max());
1865
      return parentNode->widgetForModelRow(row);
1866
    } else
1867
      return parent;
1868
  }
1869
}
1870

    
1871
void WTreeView::modelColumnsInserted(const WModelIndex& parent,
1872
				     int start, int end)
1873
{
1874
  int count = end - start + 1;
1875
  if (!parent.isValid()) {
1876

    
1877
    WApplication *app = WApplication::instance();
1878
    for (int i = start; i < start + count; ++i)
1879
      columns_.insert(columns_.begin() + i, createColumnInfo(i));
1880

    
1881
    if (static_cast<unsigned int>(renderState_) < 
1882
	static_cast<unsigned int>(RenderState::NeedRerenderHeader)) {
1883
      if (start == 0)
1884
	scheduleRerender(RenderState::NeedRerenderHeader);
1885
      else {
1886
	if (app->environment().ajax())
1887
	  doJavaScript("$('#" + id() + "').data('obj').adjustColumns();");
1888

    
1889
	WContainerWidget *row = headerRow();
1890

    
1891
	for (int i = start; i < start + count; ++i) {
1892
	  std::unique_ptr<WWidget> w = createHeaderWidget(i);
1893
	  w->setFloatSide(Side::Left);
1894
	  row->insertWidget(i - 1, std::move(w));
1895
	}
1896
      }
1897
    }
1898
  }
1899

    
1900
  if (renderState_ == RenderState::NeedRerender ||
1901
      renderState_ == RenderState::NeedRerenderData)
1902
    return;
1903

    
1904
  if (start == 0)
1905
    scheduleRerender(RenderState::NeedRerenderData);
1906
  else {
1907
    WTreeViewNode *node = nodeForIndex(parent);
1908
    if (node)
1909
      for (WTreeViewNode *c = node->nextChildNode(nullptr); c;
1910
	   c = node->nextChildNode(c))
1911
	c->insertColumns(start, count);
1912
  }
1913
}
1914

    
1915
void WTreeView::modelColumnsAboutToBeRemoved(const WModelIndex& parent,
1916
					     int start, int end)
1917
{
1918
  int count = end - start + 1;
1919
  if (!parent.isValid()) {
1920
    WApplication *app = wApp;
1921

    
1922
    if (static_cast<unsigned int>(renderState_) < 
1923
	static_cast<unsigned int>(RenderState::NeedRerenderHeader)) {
1924
      if (app->environment().ajax())
1925
	doJavaScript("$('#" + id() + "').data('obj').adjustColumns();");
1926
    }
1927

    
1928
    for (int i=start; i<start+count; i++)
1929
      app->styleSheet().removeRule(columns_[i].styleRule.get());
1930
    columns_.erase(columns_.begin() + start, columns_.begin() + start + count);
1931

    
1932
    if (static_cast<unsigned int>(renderState_) < 
1933
	static_cast<unsigned int>(RenderState::NeedRerenderHeader)) {
1934
      if (start == 0)
1935
	scheduleRerender(RenderState::NeedRerenderHeader);
1936
      else {
1937
	for (int i = start; i < start + count; ++i) {
1938
	  auto w = headerWidget(start, false);
1939
	  if (w)
1940
	    w->removeFromParent();
1941
	}
1942
      }
1943
    }
1944
  }
1945

    
1946
  if (start == 0)
1947
    scheduleRerender(RenderState::NeedRerenderData);
1948
}
1949

    
1950
void WTreeView::modelColumnsRemoved(const WModelIndex& parent,
1951
				    int start, int end)
1952
{
1953
  if (renderState_ == RenderState::NeedRerender ||
1954
      renderState_ == RenderState::NeedRerenderData)
1955
    return;
1956

    
1957
  int count = end - start + 1;
1958

    
1959
  if (start != 0) {
1960
    WTreeViewNode *node = nodeForIndex(parent);
1961
    if (node)
1962
      for (WTreeViewNode *c = node->nextChildNode(nullptr); c;
1963
	   c = node->nextChildNode(c))
1964
	c->removeColumns(start, count);
1965
  }
1966

    
1967
  if (start <= currentSortColumn_ && currentSortColumn_ <= end)
1968
    currentSortColumn_ = -1;
1969
}
1970

    
1971
void WTreeView::modelRowsInserted(const WModelIndex& parent,
1972
				  int start, int end)
1973
{
1974
  int count = end - start + 1;
1975
  shiftModelIndexes(parent, start, count);
1976

    
1977
  if (renderState_ == RenderState::NeedRerender || 
1978
      renderState_ == RenderState::NeedRerenderData)
1979
    return;
1980

    
1981
  WWidget *parentWidget = widgetForIndex(parent);
1982

    
1983
  bool renderedRowsChange = isExpandedRecursive(parent);
1984

    
1985
  if (parentWidget) {
1986
    WTreeViewNode *parentNode = dynamic_cast<WTreeViewNode *>(parentWidget);
1987

    
1988
    if (parentNode) {
1989
      if (parentNode->childrenLoaded()) {
1990
	WWidget *startWidget = nullptr;
1991

    
1992
	/*
1993
	 * First we decide between inserting in the top spacer, bottom spacer
1994
	 * and in actually rendered nodes.
1995
	 */
1996
	if (end < model()->rowCount(parent) - 1)
1997
	  startWidget = parentNode->widgetForModelRow(start);
1998
	else if (parentNode->bottomSpacerHeight() != 0)
1999
	  startWidget = parentNode->bottomSpacer();
2000

    
2001
	parentNode->adjustChildrenHeight(count);
2002
	parentNode->shiftModelIndexes(start, count);
2003

    
2004
	if (startWidget && startWidget == parentNode->topSpacer()) {
2005
	  parentNode->addTopSpacerHeight(count);
2006
	  if (renderedRowsChange)
2007
	    renderedRowsChanged(renderedRow(model()->index(start, 0, parent),
2008
					    parentNode->topSpacer(),
2009
					    renderLowerBound(),
2010
					    renderUpperBound()),
2011
				count);
2012

    
2013
	} else if (startWidget && startWidget == parentNode->bottomSpacer()) {
2014
	  parentNode->addBottomSpacerHeight(count);
2015
	  if (renderedRowsChange)
2016
	    renderedRowsChanged(renderedRow(model()->index(start, 0, parent),
2017
					    parentNode->bottomSpacer(),
2018
					    renderLowerBound(),
2019
					    renderUpperBound()),
2020
				count);
2021
	} else {
2022
	  int maxRenderHeight
2023
	    = firstRenderedRow_ + std::max(validRowCount_, viewportHeight_)
2024
	    - parentNode->renderedRow() - parentNode->topSpacerHeight();
2025

    
2026
	  int containerIndex = startWidget ? parentNode->childContainer()
2027
	    ->indexOf(startWidget) : parentNode->childContainer()->count();
2028

    
2029
	  int parentRowCount = model()->rowCount(parent);
2030

    
2031
	  int nodesToAdd = std::max(0, std::min(count, maxRenderHeight));
2032

    
2033
	  WTreeViewNode *first = nullptr;
2034
	  for (int i = 0; i < nodesToAdd; ++i) {
2035
	    std::unique_ptr<WTreeViewNode> n
2036
	      (new WTreeViewNode(this, model()->index(start + i, 0, parent),
2037
				 -1, start + i == parentRowCount - 1,
2038
				 parentNode));
2039
	    if (!first)
2040
	      first = n.get();
2041

    
2042
	    parentNode->childContainer()->insertWidget(containerIndex + i,
2043
						       std::move(n));
2044

    
2045
	    if (renderedRowsChange)
2046
	      ++validRowCount_;
2047

    
2048
	  }
2049

    
2050
	  if (nodesToAdd < count) {
2051
	    parentNode->addBottomSpacerHeight(count - nodesToAdd);
2052

    
2053
	    // +1 for bottom spacer
2054
	    int targetSize = containerIndex + nodesToAdd + 1;
2055

    
2056
	    int extraBottomSpacer = 0;
2057
	    while (parentNode->childContainer()->count() > targetSize) {
2058
	      WTreeViewNode *n
2059
		= dynamic_cast<WTreeViewNode *>(parentNode->childContainer()
2060
						->widget(targetSize - 1));
2061
	      assert(n);
2062
	      extraBottomSpacer += n->renderedHeight();
2063

    
2064
	      if (renderedRowsChange)
2065
		validRowCount_ -= n->renderedHeight();
2066

    
2067
	      n->removeFromParent();
2068
	    }
2069

    
2070
	    if (extraBottomSpacer)
2071
	      parentNode->addBottomSpacerHeight(extraBottomSpacer);
2072

    
2073
	    parentNode->normalizeSpacers();
2074
	  }
2075

    
2076
	  if (first && renderedRowsChange)
2077
	    renderedRowsChanged(first->renderedRow(renderLowerBound(),
2078
						   renderUpperBound()),
2079
				nodesToAdd);
2080

    
2081
	  // Update graphics if the last node has changed, i.e. if we are
2082
	  // adding rows at the back
2083
	  if (end == model()->rowCount(parent) - 1 && start >= 1) {
2084
	    WTreeViewNode *n = dynamic_cast<WTreeViewNode *>
2085
	      (parentNode->widgetForModelRow(start - 1));
2086

    
2087
	    if (n)
2088
	      n->updateGraphics(false, !model()->hasChildren(n->modelIndex()));
2089
	  }
2090
	}
2091
      } /* else:
2092
	   children not loaded -- so we do not need to bother
2093
	 */
2094

    
2095
      // Update graphics for parent when first children have een added
2096
      if (model()->rowCount(parent) == count)
2097
	parentNode->updateGraphics(parentNode->isLast(), false);
2098
    } else {
2099
      if (isExpanded(parent)) {
2100
	RowSpacer *s = dynamic_cast<RowSpacer *>(parentWidget);
2101

    
2102
	s->setRows(s->rows() + count);
2103
	s->node()->adjustChildrenHeight(count);
2104

    
2105
	if (renderedRowsChange)
2106
	  renderedRowsChanged
2107
	    (renderedRow(model()->index(start, 0, parent), s,
2108
			 renderLowerBound(), renderUpperBound()),
2109
	     count);
2110
      }
2111
    }
2112
  } else {
2113
    /* 
2114
     * parentWidget is 0: it means it is not even part of any spacer.
2115
     */
2116
  }
2117
}
2118

    
2119
void WTreeView::modelRowsAboutToBeRemoved(const WModelIndex& parent,
2120
					  int start, int end)
2121
{
2122
  int count = end - start + 1;
2123

    
2124
  bool renderedRowsChange = isExpandedRecursive(parent);
2125

    
2126
  if (renderState_ != RenderState::NeedRerender && 
2127
      renderState_ != RenderState::NeedRerenderData) {
2128
    firstRemovedRow_ = -1;
2129
    removedHeight_ = 0;
2130

    
2131
    WWidget *parentWidget = widgetForIndex(parent);
2132

    
2133
    if (parentWidget) {
2134
      WTreeViewNode *parentNode = dynamic_cast<WTreeViewNode *>(parentWidget);
2135

    
2136
      if (parentNode) {
2137
	if (parentNode->childrenLoaded()) {
2138
	  for (int i = end; i >= start; --i) {
2139
	    WWidget *w = parentNode->widgetForModelRow(i);
2140
	    assert(w);
2141

    
2142
	    RowSpacer *s = dynamic_cast<RowSpacer *>(w);
2143
	    if (s) {
2144
	      WModelIndex childIndex = model()->index(i, 0, parent);
2145

    
2146
	      if (i == start && renderedRowsChange)
2147
		firstRemovedRow_ = renderedRow(childIndex, w);
2148

    
2149
	      int childHeight = subTreeHeight(childIndex);
2150

    
2151
	      if (renderedRowsChange)
2152
		removedHeight_ += childHeight;
2153

    
2154
	      s->setRows(s->rows() - childHeight);
2155
	    } else {
2156
	      WTreeViewNode *node = dynamic_cast<WTreeViewNode *>(w);
2157

    
2158
	      if (renderedRowsChange) {
2159
		if (i == start)
2160
		  firstRemovedRow_ = node->renderedRow();
2161

    
2162
		removedHeight_ += node->renderedHeight();
2163
	      }
2164

    
2165
	      node->removeFromParent();
2166
	    }
2167
	  }
2168
	} /* else:
2169
	     children not loaded -- so we do not need to bother
2170
	  */
2171
      } else {
2172
	/*
2173
	 * Only if the parent is in fact expanded, the spacer reduces
2174
	 * in size
2175
	 */
2176
	if (isExpanded(parent)) {
2177
	  RowSpacer *s = dynamic_cast<RowSpacer *>(parentWidget);
2178

    
2179
	  for (int i = start; i <= end; ++i) {
2180
	    WModelIndex childIndex = model()->index(i, 0, parent);
2181
	    int childHeight = subTreeHeight(childIndex);
2182

    
2183
	    if (renderedRowsChange) {
2184
	      removedHeight_ += childHeight;
2185

    
2186
	      if (i == start)
2187
		firstRemovedRow_ = renderedRow(childIndex, s);
2188
	    }
2189
	  }
2190
        }
2191
      }
2192
    } else {
2193
      /*
2194
	parentWidget is 0: it means it is not even part of any spacer.
2195
      */
2196
    }
2197
  }
2198

    
2199
  shiftModelIndexes(parent, start, -count);
2200
}
2201

    
2202
void WTreeView::modelRowsRemoved(const WModelIndex& parent,
2203
				 int start, int end)
2204
{
2205
  int count = end - start + 1;
2206

    
2207
  if (renderState_ != RenderState::NeedRerender &&
2208
      renderState_ != RenderState::NeedRerenderData) {
2209
    WWidget *parentWidget = widgetForIndex(parent);
2210

    
2211
    if (parentWidget) {
2212
      WTreeViewNode *parentNode = dynamic_cast<WTreeViewNode *>(parentWidget);
2213

    
2214
      if (parentNode) {
2215
	if (parentNode->childrenLoaded()) {
2216
	  parentNode->normalizeSpacers();
2217
	  parentNode->adjustChildrenHeight(-removedHeight_);
2218
	  parentNode->shiftModelIndexes(start, -count);
2219

    
2220
	  // Update graphics for last node in parent, if we are removing rows
2221
	  // at the back. This is not affected by widgetForModelRow() returning
2222
	  // accurate information of rows just deleted and indexes not yet
2223
	  // shifted
2224
	  if (end >= model()->rowCount(parent) && start >= 1) {
2225
	    WTreeViewNode *n = dynamic_cast<WTreeViewNode *>
2226
	      (parentNode->widgetForModelRow(start - 1));
2227

    
2228
	    if (n)
2229
	      n->updateGraphics(true, !model()->hasChildren(n->modelIndex()));
2230
	  }
2231
	} /* else:
2232
	     children not loaded -- so we do not need to bother
2233
	  */
2234

    
2235
	// Update graphics for parent when all rows have been removed
2236
        if (model()->rowCount(parent) == 0 && count != 0)
2237
	  parentNode->updateGraphics(parentNode->isLast(), true);
2238
      } else {
2239
	/*
2240
	 * Only if the parent is in fact expanded, the spacer reduces
2241
	 * in size
2242
	 */
2243
	if (isExpanded(parent)) {
2244
	  RowSpacer *s = dynamic_cast<RowSpacer *>(parentWidget);
2245
	  WTreeViewNode *node = s->node();
2246
	  s->setRows(s->rows() - removedHeight_); // could delete s!
2247
	  node->adjustChildrenHeight(-removedHeight_);
2248
        }
2249
      }
2250
    } else {
2251
      /*
2252
	parentWidget is 0: it means it is not even part of any spacer.
2253
      */
2254
    }
2255
  }
2256

    
2257
  if (renderState_ != RenderState::NeedRerender && 
2258
      renderState_ != RenderState::NeedRerenderData)
2259
    renderedRowsChanged(firstRemovedRow_, -removedHeight_);
2260
}
2261

    
2262
void WTreeView::modelDataChanged(const WModelIndex& topLeft,
2263
				 const WModelIndex& bottomRight)
2264
{
2265
  if (renderState_ == RenderState::NeedRerender || 
2266
      renderState_ == RenderState::NeedRerenderData)
2267
    return;
2268
  
2269
  WModelIndex parent = topLeft.parent();  
2270
  WTreeViewNode *parentNode = nodeForIndex(parent);
2271

    
2272
  if (parentNode && parentNode->childrenLoaded()) {
2273
    for (int r = topLeft.row(); r <= bottomRight.row(); ++r) {
2274
      WModelIndex index = model()->index(r, 0, parent);
2275

    
2276
      WTreeViewNode *n = nodeForIndex(index);
2277

    
2278
      if (n)
2279
	n->update(topLeft.column(), bottomRight.column());
2280
    }
2281
  }
2282
}
2283

    
2284
WWidget *WTreeView::headerWidget(int column, bool contentsOnly)
2285
{
2286
  WWidget *result = nullptr;
2287

    
2288
  if (headers_ && headers_->count() > 0) {
2289
    if (column == 0)
2290
      result = headers_->widget(headers_->count() - 1);
2291
    else
2292
      result = headerRow()->widget(column - 1);
2293
  }
2294

    
2295
  if (result && contentsOnly)
2296
    return result->find("contents");
2297
  else
2298
    return result;
2299
}
2300

    
2301
WContainerWidget *WTreeView::headerRow()
2302
{
2303
  WContainerWidget *row
2304
    = dynamic_cast<WContainerWidget *>(headers_->widget(0));
2305
  if (rowHeaderCount())
2306
    row = dynamic_cast<WContainerWidget *>(row->widget(0));
2307
  return row;
2308
}
2309

    
2310
int WTreeView::renderedRow(const WModelIndex& index, WWidget *w,
2311
			   int lowerBound, int upperBound)
2312
{
2313
  WTreeViewNode *node = dynamic_cast<WTreeViewNode *>(w);
2314

    
2315
  if (node)
2316
    return node->renderedRow(lowerBound, upperBound);
2317
  else {
2318
    RowSpacer *s = dynamic_cast<RowSpacer *>(w);
2319

    
2320
    int result = s->renderedRow(0, upperBound);
2321

    
2322
    if (result > upperBound)
2323
      return result;
2324
    else if (result + s->node()->renderedHeight() < lowerBound)
2325
      return result;
2326
    else
2327
      return result + getIndexRow(index, s->node()->modelIndex(),
2328
				  lowerBound - result, upperBound - result);
2329
  }
2330
}
2331

    
2332
// TF: This function returns the zero-based numerical index of the table row
2333
//     for 'child' under 'ancestor', given that the parent of 'child' is expanded and
2334
//     using 'ancestor' as the (invisible) root.
2335
int WTreeView::getIndexRow( const WModelIndex& child,
2336
                            const WModelIndex& ancestor,
2337
                            int lowerBound, int upperBound ) const
2338
{
2339
  if ( !child.isValid() || child == ancestor )
2340
    return 0;
2341
  else {
2342
    WModelIndex parent = child.parent();
2343

    
2344
    int result = 0;
2345
    for ( int r = 0; r < child.row(); ++r ) {
2346
      result += subTreeHeight( model()->index( r, 0, parent ), 0,
2347
                               upperBound - result );
2348
      if ( result >= upperBound )
2349
        return result;
2350
    }
2351

    
2352
    // TF: Adjustment with the position of parent within its siblings,
2353
    //     only if parent is not the (invisible) root...
2354
    //     getIndexRow() will deliver strange results anyway, if the
2355
    //     parent of the initial child was not expanded!
2356
    if ( parent != ancestor )
2357
      result += 1 + getIndexRow( parent, ancestor,
2358
                                 lowerBound - result, upperBound - result );
2359
    return result;
2360
    //    return result + getIndexRow(parent, ancestor,
2361
    //				lowerBound - result, upperBound - result);
2362
  }
2363
}
2364

    
2365
int WTreeView::renderLowerBound() const
2366
{
2367
  return firstRenderedRow_;
2368
}
2369

    
2370
int WTreeView::renderUpperBound() const
2371
{
2372
  return firstRenderedRow_ + validRowCount_;
2373
}
2374

    
2375
void WTreeView::renderedRowsChanged(int row, int count)
2376
{
2377
  if (count < 0
2378
      && row - count >= firstRenderedRow_
2379
      && row < firstRenderedRow_ + validRowCount_)
2380
    validRowCount_ += std::max(firstRenderedRow_ - row + count, count);
2381

    
2382
  if (row < firstRenderedRow_)
2383
    firstRenderedRow_ += count;
2384

    
2385
  scheduleRerender(RenderState::NeedAdjustViewPort);
2386
}
2387

    
2388
void WTreeView::adjustToViewport(WTreeViewNode *changed)
2389
{
2390
  //assert(rootNode_->rowCount() == 1);
2391

    
2392
  firstRenderedRow_ = std::max(0, firstRenderedRow_);
2393
  validRowCount_
2394
    = std::max(0, std::min(validRowCount_,
2395
			   rootNode_->renderedHeight() - firstRenderedRow_));
2396

    
2397
  int viewportBottom = std::min(rootNode_->renderedHeight(),
2398
				viewportTop_ + viewportHeight_);
2399
  int lastValidRow = firstRenderedRow_ + validRowCount_;
2400

    
2401
  bool renderMore =
2402
    (std::max(0,
2403
	      viewportTop_ - viewportHeight_) < firstRenderedRow_)
2404
    || (std::min(rootNode_->renderedHeight(),
2405
		 viewportBottom + viewportHeight_) > lastValidRow);
2406

    
2407
  bool pruneFirst = false;
2408

    
2409
  //assert(rootNode_->rowCount() == 1);
2410

    
2411
  if (renderMore) {
2412
    int newFirstRenderedRow = std::min(firstRenderedRow_,
2413
				       calcOptimalFirstRenderedRow());
2414
    int newLastValidRow = std::max(lastValidRow,
2415
				   std::min(rootNode_->renderedHeight(),
2416
					    calcOptimalFirstRenderedRow()
2417
					    + calcOptimalRenderedRowCount()));
2418
    //assert(rootNode_->rowCount() == 1);
2419

    
2420
    int newValidRowCount = newLastValidRow - newFirstRenderedRow;
2421

    
2422
    int newRows = std::max(0, firstRenderedRow_ - newFirstRenderedRow)
2423
      + std::max(0, newLastValidRow - lastValidRow);
2424

    
2425
    const int pruneFactor
2426
      = WApplication::instance()->environment().ajax() ? 9 : 1;
2427

    
2428
    if (nodeLoad_ + newRows > pruneFactor * viewportHeight_) {
2429
      pruneFirst = true;
2430
    } else
2431
      if (newFirstRenderedRow < firstRenderedRow_
2432
	  || newLastValidRow > lastValidRow) {
2433
	firstRenderedRow_ = newFirstRenderedRow;
2434
	validRowCount_ = newValidRowCount;
2435
	adjustRenderedNode(rootNode_, 0);
2436
      }
2437
  }
2438

    
2439
  const int pruneFactor
2440
    = WApplication::instance()->environment().ajax() ? 5 : 1;
2441

    
2442
  if (pruneFirst || nodeLoad_ > pruneFactor * viewportHeight_) {
2443
    firstRenderedRow_ = calcOptimalFirstRenderedRow();
2444
    validRowCount_ = calcOptimalRenderedRowCount();
2445

    
2446
    pruneNodes(rootNode_, 0);
2447

    
2448
    if (pruneFirst && nodeLoad_ < calcOptimalRenderedRowCount()) {
2449
      adjustRenderedNode(rootNode_, 0);
2450
    } 
2451
  }
2452

    
2453
  //assert(rootNode_->rowCount() == 1);
2454
}
2455

    
2456
int WTreeView::adjustRenderedNode(WTreeViewNode *node, int theNodeRow)
2457
{
2458
  //assert(rootNode_->rowCount() == 1);
2459

    
2460
  WModelIndex index = node->modelIndex();
2461

    
2462
  if (index != rootIndex())
2463
    ++theNodeRow;
2464

    
2465
  if (!isExpanded(index) && !node->childrenLoaded())
2466
    return theNodeRow;
2467

    
2468
  int nodeRow = theNodeRow;
2469

    
2470
  if (node->isAllSpacer()) {
2471
    if (nodeRow + node->childrenHeight() > firstRenderedRow_
2472
	&& nodeRow < firstRenderedRow_ + validRowCount_) {
2473
      // replace spacer by some nodes
2474
      int childCount = model()->rowCount(index);
2475

    
2476
      bool firstNode = true;
2477
      int rowStubs = 0;
2478

    
2479
      for (int i = 0; i < childCount; ++i) {
2480
	WModelIndex childIndex = model()->index(i, 0, index);
2481

    
2482
	int childHeight = subTreeHeight(childIndex);
2483

    
2484
	if (nodeRow <= firstRenderedRow_ + validRowCount_
2485
	    && nodeRow + childHeight > firstRenderedRow_) {
2486
	  if (firstNode) {
2487
	    firstNode = false;
2488
	    node->setTopSpacerHeight(rowStubs);
2489
	    rowStubs = 0;
2490
	  }
2491

    
2492
	  // assert(rootNode_->rowCount() == 1);
2493

    
2494
	  WTreeViewNode *n = node->childContainer()->addWidget
2495
	    (std::unique_ptr<WTreeViewNode>(new WTreeViewNode
2496
	     (this, childIndex, childHeight - 1, i == childCount - 1, node)));
2497

    
2498
	  int nestedNodeRow = nodeRow;
2499
	  nestedNodeRow = adjustRenderedNode(n, nestedNodeRow);
2500
	  assert(nestedNodeRow == nodeRow + childHeight);
2501
	} else {
2502
	  rowStubs += childHeight;
2503
	}
2504
	nodeRow += childHeight;
2505
      }
2506
      node->setBottomSpacerHeight(rowStubs);
2507
    } else
2508
      nodeRow += node->childrenHeight();
2509
  } else {
2510
    // get a reference to the first existing child, which we'll recursively
2511
    // adjust later
2512
    int topSpacerHeight = node->topSpacerHeight();
2513
    int nestedNodeRow = nodeRow + topSpacerHeight;
2514
    WTreeViewNode *child = node->nextChildNode(nullptr);
2515

    
2516
    int childCount = model()->rowCount(index);
2517
    while (topSpacerHeight != 0
2518
	   && nodeRow + topSpacerHeight > firstRenderedRow_) {
2519
      // eat from top spacer and replace with actual nodes
2520
      WTreeViewNode *n
2521
	= dynamic_cast<WTreeViewNode *>(node->childContainer()->widget(1));
2522

    
2523
      assert(n);
2524

    
2525
      WModelIndex childIndex
2526
	= model()->index(n->modelIndex().row() - 1, 0, index);
2527

    
2528
      assert(childIndex.isValid());
2529

    
2530
      int childHeight = subTreeHeight(childIndex);
2531

    
2532
      {
2533
	std::unique_ptr<WTreeViewNode> nn
2534
	  (n = new WTreeViewNode(this, childIndex, childHeight - 1,
2535
			     childIndex.row() == childCount - 1, node));
2536
	node->childContainer()->insertWidget(1, std::move(nn));
2537
      }
2538

    
2539
      nestedNodeRow = nodeRow + topSpacerHeight - childHeight;
2540
      nestedNodeRow = adjustRenderedNode(n, nestedNodeRow);
2541
      assert(nestedNodeRow == nodeRow + topSpacerHeight);
2542

    
2543
      topSpacerHeight -= childHeight;
2544
      node->addTopSpacerHeight(-childHeight);
2545
    }
2546

    
2547
    for (; child; child=node->nextChildNode(child))
2548
      nestedNodeRow = adjustRenderedNode(child, nestedNodeRow);
2549

    
2550
    int nch = node->childrenHeight();
2551
    int bottomSpacerStart = nch - node->bottomSpacerHeight();
2552

    
2553
    while (node->bottomSpacerHeight() != 0
2554
	   && nodeRow + bottomSpacerStart
2555
	      <= firstRenderedRow_ + validRowCount_){
2556
      // eat from bottom spacer and replace with actual nodes
2557
      int lastNodeIndex = node->childContainer()->count() - 2;
2558
      WTreeViewNode *n = dynamic_cast<WTreeViewNode *>
2559
	(node->childContainer()->widget(lastNodeIndex));
2560

    
2561
      assert(n);
2562

    
2563
      WModelIndex childIndex
2564
	= model()->index(n->modelIndex().row() + 1, 0, index);
2565

    
2566
      assert (childIndex.isValid());
2567

    
2568
      int childHeight = subTreeHeight(childIndex);
2569

    
2570
      {
2571
	std::unique_ptr<WTreeViewNode> nn
2572
	  (n = new WTreeViewNode(this, childIndex, childHeight - 1,
2573
				 childIndex.row() == childCount - 1, node));
2574
	node->childContainer()->insertWidget(lastNodeIndex + 1, std::move(nn));
2575
      }
2576

    
2577
      nestedNodeRow = nodeRow + bottomSpacerStart;
2578
      nestedNodeRow = adjustRenderedNode(n, nestedNodeRow);
2579
      assert(nestedNodeRow == nodeRow + bottomSpacerStart + childHeight);
2580

    
2581
      node->addBottomSpacerHeight(-childHeight);
2582
      bottomSpacerStart += childHeight;
2583
    }
2584

    
2585
    nodeRow += nch;
2586
  }
2587

    
2588
  // assert(rootNode_->rowCount() == 1);
2589

    
2590
  // if a node has children loaded but is not currently expanded, then still
2591
  // adjust it, but do not return the calculated nodeRow for it.
2592
  return isExpanded(index) ? nodeRow : theNodeRow;
2593
}
2594

    
2595
int WTreeView::pruneNodes(WTreeViewNode *node, int nodeRow)
2596
{
2597
  // remove unneeded nodes: nodes within collapsed tree nodes, and nodes
2598
  // beyond the optimal bounds
2599
  WModelIndex index = node->modelIndex();
2600

    
2601
  ++nodeRow;
2602

    
2603
  if (isExpanded(index)) {
2604
    // prune away nodes until we are within the rendered region
2605
    nodeRow += node->topSpacerHeight();
2606

    
2607
    bool done = false;
2608
    WTreeViewNode *c = nullptr;
2609

    
2610
    for (; nodeRow < firstRenderedRow_; ) {
2611
      c = node->nextChildNode(nullptr);
2612
      if (!c) {
2613
	done = true;
2614
	break;
2615
      }
2616

    
2617
      if (nodeRow + c->renderedHeight() < firstRenderedRow_) {
2618
	node->addTopSpacerHeight(c->renderedHeight());
2619
	nodeRow += c->renderedHeight();
2620
	c->removeFromParent();
2621
      } else {
2622
	nodeRow = pruneNodes(c, nodeRow);
2623
	break;
2624
      }
2625
    }
2626

    
2627
    if (!done) {
2628
      for (; nodeRow <= firstRenderedRow_ + validRowCount_; ) {
2629
	c = node->nextChildNode(c);
2630

    
2631
	if (!c) {
2632
	  done = true;
2633
	  break;
2634
	}
2635

    
2636
	nodeRow = pruneNodes(c, nodeRow);
2637
      }
2638
    }
2639

    
2640
    if (!done) {
2641
      c = node->nextChildNode(c);
2642

    
2643
      if (c != nullptr) {
2644
	int i = node->childContainer()->indexOf(c);
2645
	int prunedHeight = 0;
2646

    
2647
	while (c && i < node->childContainer()->count()) {
2648
	  c = dynamic_cast<WTreeViewNode *> (node->childContainer()->widget(i));
2649
	  if (c) {
2650
	    prunedHeight += c->renderedHeight();
2651
	    c->removeFromParent();
2652
	  }
2653
	}
2654

    
2655
	node->addBottomSpacerHeight(prunedHeight);
2656
      }
2657
    }
2658

    
2659
    nodeRow += node->bottomSpacerHeight();
2660

    
2661
    node->normalizeSpacers();
2662

    
2663
  } else
2664
    if (node->childrenLoaded()) {
2665
      int prunedHeight = 0;
2666
      for (;;) {
2667
	WTreeViewNode *c = node->nextChildNode(nullptr);
2668
	if (!c)
2669
	  break;
2670

    
2671
	prunedHeight += c->renderedHeight();
2672
	c->removeFromParent();
2673
	c = nullptr;
2674
      }
2675

    
2676
      node->addBottomSpacerHeight(prunedHeight);
2677
      node->normalizeSpacers();
2678
    }
2679

    
2680
  return nodeRow;
2681
}
2682

    
2683
int WTreeView::
2684
shiftModelIndexes(const WModelIndex& parent, int start, int count,
2685
		  const std::shared_ptr<WAbstractItemModel>& model,
2686
		  WModelIndexSet& set)
2687
{
2688
  /*
2689
   * handle the set of exanded model indexes:
2690
   *  - collect indexes in the same parent at lower rows that need to
2691
   *    be shifted
2692
   *  - if deleting, delete indexes that are within the range of deleted
2693
   *    rows
2694
   */
2695
  std::vector<WModelIndex> toShift;
2696
  std::vector<WModelIndex> toErase;
2697

    
2698
  for (WModelIndexSet::iterator it
2699
	 = set.lower_bound(model->index(start, 0, parent)); it != set.end();) {
2700
#ifndef WT_TARGET_JAVA
2701
    WModelIndexSet::iterator n = it;
2702
    ++n;
2703
#endif
2704

    
2705
    WModelIndex i = *it;
2706

    
2707
    WModelIndex p = i.parent();
2708
    if (p != parent && !WModelIndex::isAncestor(p, parent))
2709
      break;
2710

    
2711
    if (p == parent) {
2712
      toShift.push_back(i);
2713
      toErase.push_back(i);
2714
    } else if (count < 0) {
2715
      // delete indexes that are about to be deleted, if they are within
2716
      // the range of deleted indexes
2717
      do {
2718
	if (p.parent() == parent
2719
	    && p.row() >= start
2720
	    && p.row() < start - count) {
2721
	  toErase.push_back(i);
2722
	  break;
2723
	} else
2724
	  p = p.parent();
2725
      } while (p != parent);
2726
    }
2727

    
2728
#ifndef WT_TARGET_JAVA
2729
    it = n;
2730
#endif
2731
  }
2732

    
2733
  for (unsigned i = 0; i < toErase.size(); ++i)
2734
    set.erase(toErase[i]);
2735

    
2736
  int removed = 0;
2737
  for (unsigned i = 0; i < toShift.size(); ++i) {
2738
    // for negative count: only reinsert model indexes that need
2739
    // not be removed (they are currently all removed)
2740
    if (toShift[i].row() + count >= start) {
2741
      WModelIndex newIndex = model->index(toShift[i].row() + count,
2742
					  toShift[i].column(), parent);
2743
      set.insert(newIndex);
2744
    } else
2745
      ++removed;
2746
  }
2747

    
2748
  return removed;
2749
}
2750

    
2751
void WTreeView::shiftModelIndexes(const WModelIndex& parent,
2752
				  int start, int count)
2753
{
2754
  shiftModelIndexes(parent, start, count, model(), expandedSet_);
2755

    
2756
  int removed = shiftModelIndexes(parent, start, count, model(),
2757
				  selectionModel()->selection_);
2758

    
2759
  shiftEditorRows(parent, start, count, false);
2760

    
2761
  if (removed)
2762
    selectionChanged().emit();
2763
}
2764

    
2765
void WTreeView::modelLayoutAboutToBeChanged()
2766
{
2767
  WModelIndex::encodeAsRawIndexes(expandedSet_);
2768

    
2769
  WAbstractItemView::modelLayoutAboutToBeChanged();
2770
}
2771

    
2772
void WTreeView::modelLayoutChanged()
2773
{
2774
  WAbstractItemView::modelLayoutChanged();
2775

    
2776
  expandedSet_ = WModelIndex::decodeFromRawIndexes(expandedSet_);
2777

    
2778
  renderedNodes_.clear();
2779

    
2780
  pageChanged().emit();
2781
}
2782

    
2783
void WTreeView::addRenderedNode(WTreeViewNode *node)
2784
{
2785
  renderedNodes_[node->modelIndex()] = node;
2786
  ++nodeLoad_;
2787
  renderedNodesAdded_ = true;
2788
}
2789

    
2790
void WTreeView::removeRenderedNode(WTreeViewNode *node)
2791
{
2792
  renderedNodes_.erase(node->modelIndex());
2793
  --nodeLoad_;
2794
}
2795

    
2796
bool WTreeView::internalSelect(const WModelIndex& index, SelectionFlag option)
2797
{
2798
  if (selectionBehavior() == SelectionBehavior::Rows && 
2799
      index.column() != 0)
2800
    return
2801
      internalSelect(model()->index(index.row(), 0, index.parent()), option);
2802

    
2803
  if (WAbstractItemView::internalSelect(index, option)) {
2804
    WTreeViewNode *node = nodeForIndex(index);
2805
    if (node)
2806
      node->renderSelected(isSelected(index), index.column());
2807

    
2808
    return true;
2809
  } else
2810
    return false;
2811
}
2812

    
2813
WTreeViewNode *WTreeView::nodeForIndex(const WModelIndex& index) const
2814
{
2815
  if (index == rootIndex())
2816
    return rootNode_;
2817
  else {
2818
    WModelIndex column0Index = index.column() == 0 ? index : model()->index(index.row(), 0, index.parent());
2819
    NodeMap::const_iterator i = renderedNodes_.find(column0Index);
2820
    return i != renderedNodes_.end() ? i->second : nullptr;
2821
  }
2822
}
2823

    
2824
void WTreeView::selectRange(const WModelIndex& first, const WModelIndex& last)
2825
{
2826
  WModelIndex index = first;
2827
  for (;;) {
2828
    for (int c = first.column(); c <= last.column(); ++c) {
2829
      WModelIndex ic = model()->index(index.row(), c, index.parent());
2830
      internalSelect(ic, SelectionFlag::Select);
2831

    
2832
      if (ic == last)
2833
	return;
2834
    }
2835

    
2836
    WModelIndex indexc0
2837
      = index.column() == 0 ? index
2838
      : model()->index(index.row(), 0, index.parent());
2839

    
2840
    if (isExpanded(indexc0) && model()->hasChildren(indexc0))
2841
      index = model()->index(0, first.column(), indexc0);
2842
    else {
2843
      for (;;) {
2844
	// next row in parent, if one is available
2845
	WModelIndex parent = index.parent();
2846
	if (index.row() + 1 < model()->rowCount(parent)) {
2847
	  index = model()->index(index.row() + 1, first.column(), parent);
2848
	  break;
2849
	} else
2850
	  // otherwise go up one level
2851
	  index = index.parent();
2852
      }
2853
    }
2854
  }
2855
}
2856

    
2857
WAbstractItemView::ColumnInfo WTreeView::createColumnInfo(int column) const
2858
{
2859
  ColumnInfo ci = WAbstractItemView::createColumnInfo(column);
2860

    
2861
  if (column == 0) {
2862
    // column 0 needs width auto, so we override the ci.styleRule with
2863
    // a more specific rule. We also set the correct overflow attributes
2864

    
2865
    ci.width = WLength::Auto;
2866
    ci.styleRule->templateWidget()->resize(WLength::Auto, WLength::Auto);
2867

    
2868
    if (c0StyleRule_) {
2869
      WCssStyleSheet& styleSheet = wApp->styleSheet();
2870
      c0StyleRule_->setSelector("#" + id() + " li ." + ci.styleClass());
2871
      styleSheet.addRule(styleSheet.removeRule(c0StyleRule_)); // needed on rerender
2872
    }
2873
  }
2874

    
2875
  return ci;
2876
}
2877

    
2878
void WTreeView::setCurrentPage(int page)
2879
{
2880
  viewportTop_ = page * viewportHeight_;
2881

    
2882
  contents_->setOffsets(-viewportTop_ * rowHeight().toPixels(), Side::Top);
2883

    
2884
  pageChanged().emit();
2885

    
2886
  scheduleRerender(RenderState::NeedAdjustViewPort);
2887
}
2888

    
2889
int WTreeView::currentPage() const
2890
{
2891
  return viewportTop_ / viewportHeight_;
2892
}
2893

    
2894
int WTreeView::pageCount() const
2895
{
2896
  if (rootNode_) {
2897
    return (rootNode_->renderedHeight() - 1) / viewportHeight_ + 1;
2898
  } else
2899
    return 1;
2900
}
2901

    
2902
int WTreeView::pageSize() const
2903
{
2904
  return viewportHeight_;
2905
}
2906

    
2907
void WTreeView::scrollTo( const WModelIndex& index, ScrollHint hint )
2908
{
2909
  // TF: Numeric index of table row for 'index', given that 'index' is visible,
2910
  //     i.e. all of its parents are expanded.
2911
  //     (This should not be increased by 1! s. getIndexRow())
2912
  int row = getIndexRow( index, rootIndex(), 0,
2913
                         std::numeric_limits<int>::max() );
2914
  //  int row = getIndexRow(index, rootIndex(), 0,
2915
  //                        std::numeric_limits<int>::max()) + 1;
2916

    
2917
  WApplication *app = WApplication::instance();
2918

    
2919
  if ( app->environment().ajax() ) {
2920
    if ( viewportHeight_ != UNKNOWN_VIEWPORT_HEIGHT ) {
2921
      // TF: If 'EnsureVisible' then scroll to bottom, if 'row' is below
2922
      //     the bottom visible entry, and scroll to top, if 'row' is above
2923
      //     the first visible entry (minimal scrolling).
2924
      //     'viewportTop_' and 'viewportHeight_' may include partially visible rows!
2925
      if ( hint == ScrollHint::EnsureVisible ) {
2926
        if ( viewportTop_ + viewportHeight_ <= row )
2927
          hint = ScrollHint::PositionAtBottom;
2928
        else if ( row <= viewportTop_ )
2929
          hint = ScrollHint::PositionAtTop;
2930
      }
2931
      /*
2932
            if (hint == ScrollHint::EnsureVisible) {
2933
              if (viewportTop_ + viewportHeight_ < row)
2934
                hint = ScrollHint::PositionAtTop;
2935
              else if (row < viewportTop_)
2936
                hint = ScrollHint::PositionAtBottom;
2937
            }
2938
      */
2939

    
2940
      // TF: Adjust viewport, if still required...
2941
      if ( hint != ScrollHint::EnsureVisible ) {
2942
        switch ( hint ) {
2943
        case ScrollHint::PositionAtTop:
2944
          viewportTop_ = row; break;
2945
        case ScrollHint::PositionAtBottom:
2946
          viewportTop_ = row - viewportHeight_ + 1; break;
2947
        case ScrollHint::PositionAtCenter:
2948
          viewportTop_ = row - viewportHeight_ / 2 + 1; break;
2949
        default:
2950
          break;
2951
        }
2952

    
2953
        scheduleRerender( RenderState::NeedAdjustViewPort );
2954
      }
2955
    }
2956

    
2957
    WStringStream s;
2958

    
2959
    s << "setTimeout(function() { jQuery.data(" << jsRef()
2960
      << ", 'obj').scrollTo(-1, "
2961
      << row << "," << static_cast<int>(rowHeight().toPixels())
2962
      << "," << (int)hint << ");});";
2963

    
2964
    doJavaScript( s.str() );
2965
  }
2966
  else
2967
    setCurrentPage( row / pageSize() );
2968
}
2969

    
2970
EventSignal<WScrollEvent>& WTreeView::scrolled(){
2971
  if (wApp->environment().ajax() && contentsContainer_ != nullptr)
2972
    return contentsContainer_->scrolled();
2973

    
2974
  throw WException("Scrolled signal existes only with ajax.");
2975
}
2976
}
2977

    
2978
#endif // DOXYGEN_ONLY
(3-3/4)