Project

General

Profile

Bug #6774 » WTreeView.C

Thomas Frank, 11/29/2018 01:55 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/WItemDelegate.h"
17
#include "Wt/WItemSelectionModel.h"
18
#include "Wt/WStringStream.h"
19
#include "Wt/WTemplate.h"
20
#include "Wt/WText.h"
21
#include "Wt/WTheme.h"
22
#include "Wt/WTreeView.h"
23
#include "Wt/WVBoxLayout.h"
24
#include "Wt/WWebWidget.h"
25

    
26
#include "WebUtils.h"
27

    
28
#ifndef WT_DEBUG_JS
29
#include "js/WTreeView.min.js"
30
#endif
31

    
32
#include <limits>
33

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

    
43
/*
44
  TODO:
45

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

    
51
#ifndef DOXYGEN_ONLY
52

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

    
57
namespace Wt {
58

    
59
LOGGER("WTreeView");
60

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

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

    
76
private:
77
  WTreeView *treeView_;
78
};
79

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

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

    
93
  void generate() {
94
    WApplication *app = WApplication::instance();
95

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

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

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

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

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

    
130
  friend class ToggleButton;
131
};
132

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

    
141
    setInline(false);
142

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

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

    
156
  SignalBase& signal(int i) { return *signals_[i]; }
157

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

    
163
private:
164
  std::vector<std::unique_ptr<SignalBase>> signals_;
165
  ToggleButtonConfig       *config_;
166

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

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

    
188
  void setRows(int height, bool force = false);
189

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

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

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

    
202
private:
203
  Wt::WTreeViewNode *node_;
204
  int height_;
205
};
206

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

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

    
220
  void rerenderSpacers();
221

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

    
227
  WWidget *widgetForModelRow(int row);
228
  WTreeViewNode *nextChildNode(WTreeViewNode *n);
229

    
230
  bool isAllSpacer();
231

    
232
  void setTopSpacerHeight(int rows);
233
  void addTopSpacerHeight(int rows);
234
  int topSpacerHeight();
235

    
236
  void setBottomSpacerHeight(int rows);
237
  void addBottomSpacerHeight(int rows);
238
  int bottomSpacerHeight();
239

    
240
  RowSpacer *topSpacer(bool create = false);
241
  RowSpacer *bottomSpacer(bool create = false);
242

    
243
  WContainerWidget *childContainer();
244

    
245
  void shiftModelIndexes(int start, int count);
246

    
247
  WTreeViewNode *parentNode() const { return parentNode_; }
248

    
249
  bool isExpanded();
250

    
251
  void adjustChildrenHeight(int diff);
252
  void normalizeSpacers();
253

    
254
  void selfCheck();
255

    
256
  WTreeView *view() const { return view_; }
257

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

    
264
  void renderSelected(bool selected, int column);
265

    
266
  void doExpand();
267
  void doCollapse();
268

    
269
  WWidget *cellWidget(int column);
270

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

    
281
  WModelIndex childIndex(int column);
282

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

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

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

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

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

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

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

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

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

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

    
352
    if (childrenHeight_ > 0)
353
      setTopSpacerHeight(childrenHeight_);
354
  } else
355
    childrenHeight_ = 0;
356

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

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

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

    
369
  view_->addRenderedNode(this);
370
}
371

    
372
WTreeViewNode::~WTreeViewNode()
373
{
374
  view_->removeRenderedNode(this);
375

    
376
  if (view_->isEditing()) {
377
    WModelIndex parent = index_.parent();
378

    
379
    int thisNodeCount = view_->model()->columnCount(parent);
380

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

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

    
392
  int thisNodeCount = view_->model()->columnCount(parent);
393

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

    
397
    WWidget *w = cellWidget(i);
398

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

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

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

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

    
421
    if (wAfter) {
422
      setCellWidget(i, std::move(wAfter));
423

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

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

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

    
449
  if (!isEmpty) {
450
    ToggleButton *expandButton = nodeWidget_->resolve<ToggleButton *>("expand");
451
    if (!expandButton) {
452
      nodeWidget_->bindEmpty("no-expand");
453
      expandButton = 
454
	nodeWidget_->bindWidget
455
	("expand", cpp14::make_unique<ToggleButton>(view_->expandConfig_.get()));
456

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

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

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

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

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

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

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

    
497
      newRow->setStyleClass("Wt-tv-row rh");
498

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

    
505
  update(0, view_->columnCount() - 1);
506
}
507

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

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

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

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

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

    
531
  w->setStyleClass(WString::fromUTF8(s.c_str()));
532
}
533

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

    
538
  addColumnStyleClass(column, newW.get());
539

    
540
  if (current)
541
    current->setStyleClass(WString::Empty);
542

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

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

    
560
    if (current)
561
      current->removeFromParent();
562

    
563
    row->insertWidget(column - 1, std::move(newW));
564
  }
565
}
566

    
567
WWidget *WTreeViewNode::cellWidget(int column)
568
{
569
  if (column == 0)
570
    return nodeWidget_->resolveWidget("col0");
571
  else {
572
    WContainerWidget *row 
573
      = nodeWidget_->resolve<WContainerWidget *>("cols-row");
574

    
575
    if (view_->rowHeaderCount())
576
      row = dynamic_cast<WContainerWidget *>(row->widget(0));
577

    
578
    return row->count() >= column ? row->widget(column - 1) : nullptr;
579
  }
580
}
581

    
582
void WTreeViewNode::doExpand()
583
{
584
  if (isExpanded())
585
    return;
586

    
587
  loadChildren();
588

    
589
  ToggleButton *expandButton = nodeWidget_->resolve<ToggleButton *>("expand");
590
  if (expandButton)
591
    expandButton->setState(1);
592

    
593
  view_->expandedSet_.insert(index_);
594

    
595
  childContainer()->show();
596

    
597
  if (parentNode())
598
    parentNode()->adjustChildrenHeight(childrenHeight_);
599

    
600
  view_->adjustRenderedNode(this, renderedRow());
601
  view_->scheduleRerender(WTreeView::RenderState::NeedAdjustViewPort);
602

    
603
  view_->expanded_.emit(index_);
604
}
605

    
606
void WTreeViewNode::doCollapse()
607
{
608
  if (!isExpanded())
609
    return;
610

    
611
  ToggleButton *expandButton = nodeWidget_->resolve<ToggleButton *>("expand");
612
  if (expandButton)
613
    expandButton->setState(0);
614

    
615
  view_->setCollapsed(index_);
616

    
617
  childContainer()->hide();
618

    
619
  if (parentNode())
620
    parentNode()->adjustChildrenHeight(-childrenHeight_);
621

    
622
  view_->renderedRowsChanged(renderedRow(), -childrenHeight_);
623

    
624
  view_->collapsed_.emit(index_);
625
}
626

    
627
bool WTreeViewNode::isExpanded()
628
{
629
  return index_ == view_->rootIndex() || !childContainer()->isHidden();
630
}
631

    
632
void WTreeViewNode::normalizeSpacers()
633
{
634
  if (childrenLoaded_ && childContainer()->count() == 2) {
635
    RowSpacer *top = topSpacer();
636
    RowSpacer *bottom = bottomSpacer();
637

    
638
    if (top && bottom && top != bottom) {
639
      top->setRows(top->rows() + bottom->rows());
640
      if (bottom)
641
	bottom->removeFromParent();
642
    }
643
  }
644
}
645

    
646
void WTreeViewNode::rerenderSpacers()
647
{
648
  RowSpacer *s = topSpacer();
649
  if (s)
650
    s->setRows(topSpacerHeight(), true);
651

    
652
  s = bottomSpacer();
653
  if (s)
654
    s->setRows(bottomSpacerHeight(), true);
655
}
656

    
657
bool WTreeViewNode::isAllSpacer()
658
{
659
  return childrenLoaded_ && topSpacer() && (topSpacer() == bottomSpacer());
660
}
661

    
662
void WTreeViewNode::loadChildren()
663
{
664
  if (!childrenLoaded_) {
665
    childrenLoaded_ = true;
666

    
667
    view_->expandedSet_.insert(index_);
668
    childrenHeight_ = view_->subTreeHeight(index_) - 1;
669
    view_->expandedSet_.erase(index_);
670

    
671
    if (childrenHeight_ > 0)
672
      setTopSpacerHeight(childrenHeight_);
673
  }
674
}
675

    
676
void WTreeViewNode::adjustChildrenHeight(int diff)
677
{
678
  childrenHeight_ += diff;
679

    
680
  if (isExpanded()) {
681
    WTreeViewNode *parent = parentNode();
682

    
683
    if (parent)
684
      parent->adjustChildrenHeight(diff);
685
    else
686
      view_->pageChanged().emit();
687
  }
688
}
689

    
690
WContainerWidget *WTreeViewNode::childContainer()
691
{
692
  if (!childContainer_) {
693
    childContainer_ = addWidget(cpp14::make_unique<WContainerWidget>());
694
    childContainer_->setList(true);
695

    
696
    if (index_ == view_->rootIndex())
697
      childContainer_->addStyleClass("Wt-tv-root");
698
  }
699

    
700
  return childContainer_;
701
}
702

    
703
WWidget *WTreeViewNode::widgetForModelRow(int modelRow)
704
{
705
  if (!childrenLoaded_)
706
    return nullptr;
707

    
708
  WContainerWidget *c = childContainer();
709

    
710
  int first = topSpacer() ? 1 : 0;
711

    
712
  if (first < c->count()) {
713
    WTreeViewNode *n = dynamic_cast<WTreeViewNode *>(c->widget(first));
714
    if (n) {
715
      int row = topSpacerHeight();
716
      int index = first + (modelRow - row);
717

    
718
      if (index < first)
719
	return topSpacer();
720
      else if (index < c->count())
721
	return c->widget(index);
722
      else
723
	return bottomSpacer();
724
    } else
725
      return bottomSpacer();
726
  } else // isAllSpacer()
727
    return topSpacer();
728
}
729

    
730
void WTreeViewNode::shiftModelIndexes(int start, int offset)
731
{
732
  if (!childrenLoaded_)
733
    return;
734

    
735
  WContainerWidget *c = childContainer();
736

    
737
  int first, end, inc;
738

    
739
  if (offset > 0) {
740
    first = c->count() - 1;
741
    end = -1;
742
    inc = -1;
743
  } else {
744
    first = 0;
745
    end = c->count();
746
    inc = 1;
747
  }
748

    
749
  for (int i = first; i != end; i += inc) {
750
    WTreeViewNode *n = dynamic_cast<WTreeViewNode *>(c->widget(i));
751

    
752
    if (n && n->modelIndex().row() >= start) {
753
      view_->removeRenderedNode(n);
754

    
755
      n->index_ = view_->model()->index(n->modelIndex().row() + offset,
756
					n->modelIndex().column(), index_);
757

    
758
      // update items through delegate
759
      int lastColumn = view_->columnCount() - 1;
760
      int thisNodeCount = view_->model()->columnCount(index_);
761

    
762
      for (int j = 0; j <= lastColumn; ++j) {
763
	WModelIndex child = j < thisNodeCount
764
	  ? n->childIndex(j) : WModelIndex();
765
	view_->itemDelegate(j)->updateModelIndex(n->cellWidget(j), child);
766
      }
767

    
768
      view_->addRenderedNode(n);
769
    }
770
  }
771
}
772

    
773
int WTreeViewNode::renderedHeight()
774
{
775
  return index_ == view_->rootIndex() ? childrenHeight_ :
776
    (1 + (isExpanded() ? childrenHeight_ : 0));
777
}
778

    
779
int WTreeViewNode::topSpacerHeight()
780
{
781
  RowSpacer *s = topSpacer();
782

    
783
  return s ? s->rows() : 0;
784
}
785

    
786
void WTreeViewNode::setTopSpacerHeight(int rows)
787
{
788
  if (rows == 0){
789
    childContainer()->removeWidget(topSpacer());
790
  }
791
  else
792
    topSpacer(true)->setRows(rows);
793
}
794

    
795
void WTreeViewNode::addTopSpacerHeight(int rows)
796
{
797
  setTopSpacerHeight(topSpacerHeight() + rows);
798
}
799

    
800
RowSpacer *WTreeViewNode::topSpacer(bool create)
801
{
802
  WContainerWidget *c = childContainer();
803

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

    
814
  return result;
815
}
816

    
817
int WTreeViewNode::bottomSpacerHeight()
818
{
819
  RowSpacer *s = bottomSpacer();
820

    
821
  return s ? s->rows() : 0;
822
}
823

    
824
void WTreeViewNode::setBottomSpacerHeight(int rows)
825
{
826
  if (!rows) {
827
    auto bottom = bottomSpacer();
828
    if (bottom)
829
      bottom->removeFromParent();
830
  } else
831
    bottomSpacer(true)->setRows(rows);
832
}
833

    
834
void WTreeViewNode::addBottomSpacerHeight(int rows)
835
{
836
  setBottomSpacerHeight(bottomSpacerHeight() + rows);
837
}
838

    
839
RowSpacer *WTreeViewNode::bottomSpacer(bool create)
840
{
841
  WContainerWidget *c = childContainer();
842

    
843
  RowSpacer *result = nullptr;
844
  if (c->count() == 0
845
      || !(result = dynamic_cast<RowSpacer *>(c->widget(c->count() - 1)))) {
846
    if (!create)
847
      return nullptr;
848
    else {
849
      result = c->addWidget(cpp14::make_unique<RowSpacer>(this, 0));
850
    }
851
  }
852

    
853
  return result;
854
}
855

    
856
WTreeViewNode *WTreeViewNode::nextChildNode(WTreeViewNode *prev)
857
{
858
  if (!childrenLoaded_)
859
    return nullptr;
860

    
861
  WContainerWidget *c = childContainer();
862

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

    
865
  if (nextI < c->count())
866
    return dynamic_cast<WTreeViewNode *>(c->widget(nextI));
867
  else
868
    return nullptr;
869
}
870

    
871
int WTreeViewNode::renderedRow(int lowerBound, int upperBound)
872
{
873
  if (!parentNode_)
874
    return 0;
875
  else {
876
    int result = parentNode_->renderedRow(0, upperBound);
877

    
878
    if (result > upperBound)
879
      return result;
880

    
881
    return result
882
      + parentNode_->renderedRow(this, lowerBound - result,
883
				 upperBound - result);
884
  }
885
}
886

    
887
int WTreeViewNode::renderedRow(WTreeViewNode *node,
888
			       int lowerBound, int upperBound)
889
{
890
  if (renderedHeight() < lowerBound)
891
    return renderedHeight();
892

    
893
  int result = topSpacerHeight();
894

    
895
  if (result > upperBound)
896
    return result;
897

    
898
  for (WTreeViewNode *c = nextChildNode(nullptr); c; c = nextChildNode(c)) {
899
    if (c == node)
900
      return result;
901
    else {
902
      result += c->renderedHeight();
903
      if (result > upperBound)
904
	return result;
905
    }
906
  }
907

    
908
  assert(false);
909
  return 0;
910
}
911

    
912
void WTreeViewNode::renderSelected(bool selected, int column)
913
{
914
  std::string cl = WApplication::instance()->theme()->activeClass();
915

    
916
  if (view_->selectionBehavior() == SelectionBehavior::Rows) {
917
    nodeWidget_->toggleStyleClass(cl, selected);
918
  } else {
919
    WWidget *w = cellWidget(column);
920
    w->toggleStyleClass(cl, selected);
921
  }
922
}
923

    
924
void WTreeViewNode::selfCheck()
925
{
926
  assert(renderedHeight() == view_->subTreeHeight(index_));
927

    
928
  int childNodesHeight = 0;
929
  for (WTreeViewNode *c = nextChildNode(nullptr); c; c = nextChildNode(c)) {
930
    c->selfCheck();
931
    childNodesHeight += c->renderedHeight();
932
  }
933

    
934
  if (childNodesHeight == 0) {
935
    assert(topSpacer() == bottomSpacer());
936
    assert(topSpacerHeight() == childrenHeight());
937
  } else {
938
    assert(topSpacerHeight() + childNodesHeight + bottomSpacerHeight()
939
	   == childrenHeight());
940
  }
941
}
942

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

    
967
  expandConfig_.reset(new ToggleButtonConfig(this, "Wt-ctrl rh "));
968
  expandConfig_->addState("expand");
969
  expandConfig_->addState("collapse");
970
  expandConfig_->generate();
971

    
972
  setStyleClass("Wt-itemview Wt-treeview");
973

    
974
  const char *CSS_RULES_NAME = "Wt::WTreeView";
975

    
976
  WApplication *app = WApplication::instance();
977

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

    
984
  setup();
985
}
986

    
987
void WTreeView::setup()
988
{
989
  WApplication *app = WApplication::instance();
990

    
991
  impl_->clear();
992

    
993
  rootNode_ = nullptr;
994

    
995
  /*
996
   * Setup main layout
997
   */
998
  headers_ = new WContainerWidget();
999
  headers_->setStyleClass("Wt-headerdiv headerrh");
1000

    
1001
  contents_ = new WContainerWidget();
1002
  WContainerWidget *wrapRoot 
1003
    = contents_->addWidget(cpp14::make_unique<WContainerWidget>());
1004

    
1005
  if (app->environment().agentIsIE()) {
1006
    wrapRoot->setAttributeValue("style", "zoom: 1");
1007
    contents_->setAttributeValue("style", "zoom: 1");
1008
  }
1009

    
1010
  if (app->environment().ajax()) {
1011
    impl_->setPositionScheme(PositionScheme::Relative);
1012

    
1013
    std::unique_ptr<WVBoxLayout> layout(new WVBoxLayout());
1014
    layout->setSpacing(0);
1015
    layout->setContentsMargins(0, 0, 0, 0);
1016

    
1017
    headerContainer_ = new WContainerWidget();
1018
    headerContainer_->setOverflow(Overflow::Hidden);
1019
    headerContainer_->setStyleClass("Wt-header headerrh cwidth");
1020
    headerContainer_->addWidget(std::unique_ptr<WWidget>(headers_));
1021

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

    
1028
    layout->addWidget(std::unique_ptr<WWidget>(headerContainer_));
1029
    layout->addWidget(std::unique_ptr<WWidget>(contentsContainer_), 1);
1030

    
1031
    impl_->setLayout(std::move(layout));
1032
  } else {
1033
    contentsContainer_ = new WContainerWidget();
1034
    contentsContainer_->addWidget(std::unique_ptr<WWidget>(contents_));
1035
    contentsContainer_->setOverflow(Overflow::Hidden);
1036

    
1037
    impl_->setPositionScheme(PositionScheme::Relative);
1038
    contentsContainer_->setPositionScheme(PositionScheme::Relative);
1039
    contents_->setPositionScheme(PositionScheme::Relative);
1040

    
1041
    impl_->addWidget(std::unique_ptr<WWidget>(headers_));
1042
    impl_->addWidget(std::unique_ptr<WWidget>(contentsContainer_));
1043

    
1044
    viewportHeight_ = 1000;
1045

    
1046
    resize(width(), height());
1047
  }
1048

    
1049
  setRowHeight(rowHeight());
1050
}
1051

    
1052
void WTreeView::defineJavaScript()
1053
{
1054
  WApplication *app = WApplication::instance();
1055

    
1056
  if (!app->environment().ajax())
1057
    return;
1058

    
1059
  LOAD_JAVASCRIPT(app, "js/WTreeView.js", "WTreeView", wtjs1);
1060

    
1061
  setJavaScriptMember(" WTreeView", "new " WT_CLASS ".WTreeView("
1062
		      + app->javaScriptClass() + "," + jsRef() + ","
1063
		      + contentsContainer_->jsRef() + ","
1064
		      + headerContainer_->jsRef() + ","
1065
		      + std::to_string(rowHeaderCount())+ ",'"
1066
		      + WApplication::instance()->theme()->activeClass()
1067
		      + "');");
1068

    
1069
  setJavaScriptMember(WT_RESIZE_JS,
1070
		      "function(self,w,h) {"
1071
		      "$(self).data('obj').wtResize();"
1072
		      "}");
1073
}
1074

    
1075
void WTreeView::setRowHeaderCount(int count)
1076
{
1077
  WApplication *app = WApplication::instance();
1078

    
1079
  // This kills progressive enhancement too
1080
  if (!app->environment().ajax())
1081
    return;
1082

    
1083
  int oldCount = rowHeaderCount();
1084

    
1085
  if (count != 0 && count != 1)
1086
    throw WException("WTreeView::setRowHeaderCount: count must be 0 or 1");
1087

    
1088
  WAbstractItemView::setRowHeaderCount(count);
1089

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

    
1099
    bool useStyleLeft
1100
      = app->environment().agentIsWebKit()
1101
      || app->environment().agentIsOpera();
1102

    
1103
    if (useStyleLeft) {
1104
      bool rtl = app->layoutDirection() == LayoutDirection::RightToLeft;
1105

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

    
1124
    WContainerWidget *scrollBarContainer = new WContainerWidget();
1125
    scrollBarContainer->setStyleClass("cwidth");
1126
    scrollBarContainer->setHeight(SCROLLBAR_WIDTH);
1127
    scrollBarC_ 
1128
      = scrollBarContainer->addWidget(cpp14::make_unique<WContainerWidget>());
1129
    scrollBarC_->setStyleClass("Wt-tv-row Wt-scroll");
1130
    scrollBarC_->scrolled().connect(tieRowsScrollJS_);
1131

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

    
1141
    WContainerWidget *scrollBar 
1142
      = scrollBarC_->addWidget(cpp14::make_unique<WContainerWidget>());
1143
    scrollBar->setStyleClass("Wt-tv-rowc");
1144
    if (useStyleLeft)
1145
      scrollBar->setAttributeValue("style", "left: 0px;");
1146
    impl_->layout()->addWidget(std::unique_ptr<WWidget>(scrollBarContainer));
1147
  }
1148
}
1149

    
1150
WTreeView::~WTreeView()
1151
{
1152
  wApp->styleSheet().removeRule(rowHeightRule_);
1153
  wApp->styleSheet().removeRule(rowWidthRule_);
1154
  wApp->styleSheet().removeRule(rowContentsWidthRule_);
1155
  wApp->styleSheet().removeRule(c0StyleRule_);
1156

    
1157
  impl_->clear();
1158
}
1159

    
1160
std::string WTreeView::columnStyleClass(int column) const
1161
{
1162
  return columnInfo(column).styleClass();
1163
}
1164

    
1165
void WTreeView::setColumnWidth(int column, const WLength& width)
1166
{
1167
  if (!width.isAuto())
1168
    columnInfo(column).width = WLength(round(width.value()), width.unit());
1169
  else
1170
    columnInfo(column).width = WLength::Auto;
1171

    
1172
  WWidget *toResize = columnInfo(column).styleRule->templateWidget();
1173
  toResize->setWidth(0);
1174
  toResize->setWidth(columnInfo(column).width.toPixels());
1175

    
1176
  WApplication *app = WApplication::instance();
1177

    
1178
  if (app->environment().ajax() && 
1179
      static_cast<unsigned int>(renderState_) < 
1180
      static_cast<unsigned int>(RenderState::NeedRerenderHeader))
1181
    doJavaScript("$('#" + id() + "').data('obj').adjustColumns();");
1182

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

    
1189
    resize(total, height());
1190
  }
1191
}
1192

    
1193
void WTreeView::setColumnHidden(int column, bool hidden)
1194
{
1195
  if (columnInfo(column).hidden != hidden) {
1196
    WAbstractItemView::setColumnHidden(column, hidden);
1197

    
1198
    WWidget *toHide = columnInfo(column).styleRule->templateWidget();
1199
    toHide->setHidden(hidden);
1200

    
1201
    setColumnWidth(column, columnWidth(column));
1202
  }
1203
}
1204

    
1205
void WTreeView::setHeaderHeight(const WLength& height)
1206
{
1207
  WAbstractItemView::setHeaderHeight(height);
1208
}
1209

    
1210
void WTreeView::setRootIsDecorated(bool show)
1211
{
1212
  rootIsDecorated_ = show;
1213
}
1214

    
1215
void WTreeView::setAlternatingRowColors(bool enable)
1216
{
1217
  WAbstractItemView::setAlternatingRowColors(enable);
1218
  setRootNodeStyle();
1219
}
1220

    
1221
void WTreeView::setRootNodeStyle()
1222
{
1223
  if (!rootNode_)
1224
    return;
1225

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

    
1235
void WTreeView::setRowHeight(const WLength& rowHeight)
1236
{
1237
  WAbstractItemView::setRowHeight(rowHeight);
1238

    
1239
  if (rowHeightRule_) {
1240
    rowHeightRule_->templateWidget()->setHeight(rowHeight);
1241
    rowHeightRule_->templateWidget()->setLineHeight(rowHeight);
1242
  }
1243

    
1244
  if (!WApplication::instance()->environment().ajax() && !height().isAuto())
1245
    viewportHeight_ = static_cast<int>(contentsContainer_->height().toPixels()
1246
				       / rowHeight.toPixels());
1247

    
1248
  setRootNodeStyle();
1249

    
1250
  for (NodeMap::const_iterator i = renderedNodes_.begin();
1251
       i != renderedNodes_.end(); ++i)
1252
    i->second->rerenderSpacers();
1253

    
1254
  if (rootNode_)
1255
    scheduleRerender(RenderState::NeedAdjustViewPort);
1256
}
1257

    
1258
void WTreeView::resize(const WLength& width, const WLength& height)
1259
{
1260
  WApplication *app = WApplication::instance();
1261
  WLength w = app->environment().ajax() ? WLength::Auto : width;
1262

    
1263
  if (app->environment().ajax())
1264
    contentsContainer_->setWidth(w);
1265
  
1266
  if (headerContainer_)
1267
    headerContainer_->setWidth(w);
1268

    
1269
  if (!height.isAuto()) {
1270
    if (!app->environment().ajax()) {
1271
      if (impl_->count() < 3)
1272
	impl_->addWidget(createPageNavigationBar());
1273

    
1274
      double navigationBarHeight = 35;
1275
      double headerHeight = this->headerHeight().toPixels();
1276

    
1277
      int h = (int)(height.toPixels() - navigationBarHeight - headerHeight);
1278
      contentsContainer_->setHeight(std::max(h, (int)rowHeight().value()));
1279

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

    
1290
    scheduleRerender(RenderState::NeedAdjustViewPort);
1291
  }
1292

    
1293
  WCompositeWidget::resize(width, height);
1294
}
1295

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

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

    
1312
void WTreeView::setModel(const std::shared_ptr<WAbstractItemModel>& model)
1313
{
1314
  WAbstractItemView::setModel(model);
1315

    
1316
  typedef WTreeView Self;
1317

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

    
1342
  expandedSet_.clear();
1343

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

    
1350
  pageChanged().emit();
1351
}
1352

    
1353
void WTreeView::scheduleRerender(RenderState what)
1354
{
1355
  if (what == RenderState::NeedRerender || what == RenderState::NeedRerenderData) {
1356
    if(rootNode_){
1357
      rootNode_->removeFromParent();
1358
      rootNode_ = nullptr;
1359
    }
1360
  }
1361

    
1362
  WAbstractItemView::scheduleRerender(what);
1363
}
1364

    
1365
void WTreeView::render(WFlags<RenderFlag> flags)
1366
{
1367
  WApplication *app = WApplication::instance();
1368

    
1369
  if (flags.test(RenderFlag::Full)) {
1370
    defineJavaScript();
1371

    
1372
    if (!itemTouchEvent_.isConnected())
1373
      itemTouchEvent_.connect(this, &WTreeView::onItemTouchEvent);
1374

    
1375
    if (!itemEvent_.isConnected()) {
1376
      itemEvent_.connect(this, &WTreeView::onItemEvent);
1377

    
1378
      addCssRule("#" + id() + " .cwidth", "");
1379

    
1380
      rowHeightRule_ = app->styleSheet().addRule
1381
	(cpp14::make_unique<WCssTemplateRule>("#" + id() + " .rh"));
1382
      rowHeightRule_->templateWidget()->setHeight(rowHeight());
1383
      rowHeightRule_->templateWidget()->setLineHeight(rowHeight());
1384

    
1385
      rowWidthRule_ = app->styleSheet().addRule
1386
	(cpp14::make_unique<WCssTemplateRule>("#" + id() + " .Wt-tv-row"));
1387
      rowContentsWidthRule_ = app->styleSheet().addRule
1388
	(cpp14::make_unique<WCssTemplateRule>("#" + id() + " .Wt-tv-rowc"));
1389

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

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

    
1421
  while (renderState_ != RenderState::RenderOk) {
1422
    RenderState s = renderState_;
1423
    renderState_ = RenderState::RenderOk;
1424

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

    
1444
  if (app->environment().ajax() && rowHeaderCount() && renderedNodesAdded_) {
1445
    doJavaScript("{var s=" + scrollBarC_->jsRef() + ";"
1446
		 """if (s) {" + tieRowsScrollJS_.execJs("s") + "}"
1447
		 "}");
1448
    renderedNodesAdded_ = false;
1449
  }
1450

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

    
1458
  if (app->environment().ajax()) 
1459
    doJavaScript(s.str());
1460

    
1461
  WAbstractItemView::render(flags);
1462
}
1463

    
1464
void WTreeView::rerenderHeader()
1465
{
1466
  WApplication *app = WApplication::instance();
1467

    
1468
  saveExtraHeaderWidgets();
1469
  headers_->clear();
1470

    
1471
  WContainerWidget *row
1472
    = headers_->addWidget(cpp14::make_unique<WContainerWidget>());
1473
  row->setFloatSide(Side::Right);
1474

    
1475
  if (rowHeaderCount()) {
1476
    row->setStyleClass("Wt-tv-row headerrh background");
1477
    row = row->addWidget(cpp14::make_unique<WContainerWidget>());
1478
    row->setStyleClass("Wt-tv-rowc headerrh");
1479
  } else
1480
    row->setStyleClass("Wt-tv-row");
1481

    
1482
  for (int i = 0; i < columnCount(); ++i) {
1483
    std::unique_ptr<WWidget> w = createHeaderWidget(i);
1484

    
1485
    if (i != 0) {
1486
      w->setFloatSide(Side::Left);
1487
      row->addWidget(std::move(w));
1488
    } else
1489
      headers_->addWidget(std::move(w));
1490
  }
1491

    
1492
  if (app->environment().ajax())
1493
    doJavaScript("$('#" + id() + "').data('obj').adjustColumns();");
1494
}
1495

    
1496
void WTreeView::enableAjax()
1497
{
1498
  saveExtraHeaderWidgets();
1499

    
1500
  setup();
1501
  defineJavaScript();
1502

    
1503
  scheduleRerender(RenderState::NeedRerender);
1504

    
1505
  WAbstractItemView::enableAjax();
1506
}
1507

    
1508
void WTreeView::rerenderTree()
1509
{
1510
  WContainerWidget *wrapRoot
1511
    = dynamic_cast<WContainerWidget *>(contents_->widget(0));
1512

    
1513
  bool firstTime = rootNode_ == nullptr;
1514
  wrapRoot->clear();
1515

    
1516
  firstRenderedRow_ = calcOptimalFirstRenderedRow();
1517
  validRowCount_ = 0;
1518

    
1519
  rootNode_ = wrapRoot->addWidget
1520
    (cpp14::make_unique<WTreeViewNode>(this, rootIndex(), -1, true, nullptr));
1521

    
1522
  if (WApplication::instance()->environment().ajax()) {
1523

    
1524
    if (editTriggers().test(EditTrigger::SingleClicked) || 
1525
	clicked().isConnected()) {
1526
      connectObjJS(rootNode_->clicked(), "click");
1527
      if (firstTime)
1528
	connectObjJS(contentsContainer_->clicked(), "rootClick");
1529
    }
1530

    
1531
    if (editTriggers().test(EditTrigger::DoubleClicked) || 
1532
	doubleClicked().isConnected()) {
1533
      connectObjJS(rootNode_->doubleClicked(), "dblClick");
1534
      if (firstTime)
1535
	connectObjJS(contentsContainer_->doubleClicked(), "rootDblClick");
1536
    }
1537

    
1538
    connectObjJS(rootNode_->mouseWentDown(), "mouseDown");
1539
    if (firstTime)
1540
      connectObjJS(contentsContainer_->mouseWentDown(), "rootMouseDown");
1541

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

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

    
1560
  setRootNodeStyle();
1561

    
1562
  pageChanged().emit();
1563

    
1564
  adjustToViewport();
1565
}
1566

    
1567
void WTreeView::onViewportChange(WScrollEvent e)
1568
{
1569
  viewportTop_ = static_cast<int>
1570
    (std::floor(e.scrollY() / rowHeight().toPixels()));
1571

    
1572
  contentsSizeChanged(0, e.viewportHeight());
1573
}
1574

    
1575
void WTreeView::contentsSizeChanged(int width, int height)
1576
{
1577
  viewportHeight_
1578
    = static_cast<int>(std::ceil(height / rowHeight().toPixels()));
1579

    
1580
  scheduleRerender(RenderState::NeedAdjustViewPort);
1581
}
1582

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

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

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

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

    
1624
  if (type == "touchselect")
1625
    handleTouchSelect(index, event);
1626
  else if (type == "touchstart")
1627
    handleTouchStart(index, event);
1628
  else if (type == "touchend")
1629
    handleTouchEnd(index, event);
1630
}
1631

    
1632
WModelIndex WTreeView::calculateModelIndex(std::string nodeAndColumnId)
1633
{
1634
  std::vector<std::string> nodeAndColumnSplit;
1635
  boost::split(nodeAndColumnSplit, nodeAndColumnId, boost::is_any_of(":"));
1636

    
1637
  WModelIndex index;
1638

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

    
1649
    int column = (columnId == 0 ? 0 : -1);
1650
    for (unsigned i = 0; i < columns_.size(); ++i)
1651
      if (columns_[i].id == columnId) {
1652
	column = i;
1653
	break;
1654
      }
1655

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

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

    
1675
int WTreeView::subTreeHeight(const WModelIndex& index,
1676
			     int lowerBound, int upperBound) const
1677
{
1678
  int result = 0;
1679

    
1680
  if (index != rootIndex())
1681
    ++result;
1682

    
1683
  if (result >= upperBound)
1684
    return result;
1685

    
1686
  if (model() && isExpanded(index)) {
1687
    int childCount = model()->rowCount(index);
1688

    
1689
    for (int i = 0; i < childCount; ++i) {
1690
      WModelIndex childIndex = model()->index(i, 0, index);
1691

    
1692
      result += subTreeHeight(childIndex, upperBound - result);
1693

    
1694
      if (result >= upperBound)
1695
	return result;
1696
    }
1697
  }
1698

    
1699
  return result;
1700
}
1701

    
1702
bool WTreeView::isExpanded(const WModelIndex& index) const
1703
{
1704
  return index == rootIndex()
1705
    || expandedSet_.find(index) != expandedSet_.end();
1706
}
1707

    
1708
bool WTreeView::isExpandedRecursive(const WModelIndex& index) const
1709
{
1710
  if (isExpanded(index)) {
1711
    if (index != rootIndex())
1712
      return isExpanded(index.parent());
1713
    else
1714
      return false;
1715
  } else
1716
    return false;
1717
}
1718

    
1719
void WTreeView::setCollapsed(const WModelIndex& index)
1720
{
1721
  expandedSet_.erase(index);
1722

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

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

    
1743
  for (WModelIndexSet::iterator it = toDeselect.begin();
1744
       it != toDeselect.end(); ++it)
1745
    if (internalSelect(*it, SelectionFlag::Deselect))
1746
      selectionHasChanged = true;
1747

    
1748
  if (selectionHasChanged)
1749
    selectionChanged().emit();
1750
#endif
1751
}
1752

    
1753
void WTreeView::setExpanded(const WModelIndex& index, bool expanded)
1754
{
1755
  if (isExpanded(index) != expanded) {
1756
    WWidget *w = widgetForIndex(index);
1757

    
1758
    WTreeViewNode *node = w ? dynamic_cast<WTreeViewNode *>(w) : nullptr;
1759

    
1760
    if (node) {
1761
      if (expanded)
1762
	node->doExpand();
1763
      else
1764
	node->doCollapse();
1765
    } else {
1766
      int height = subTreeHeight(index);
1767

    
1768
      if (expanded)
1769
	expandedSet_.insert(index);
1770
      else
1771
	setCollapsed(index);
1772

    
1773
      if (w) {
1774
	RowSpacer *spacer = dynamic_cast<RowSpacer *>(w);
1775

    
1776
	int diff = subTreeHeight(index) - height;
1777

    
1778
	spacer->setRows(spacer->rows() + diff);
1779
	spacer->node()->adjustChildrenHeight(diff);
1780

    
1781
	renderedRowsChanged(renderedRow(index, spacer,
1782
					renderLowerBound(), renderUpperBound()),
1783
			    diff);
1784
      }
1785
    }
1786
  }
1787
}
1788

    
1789
void WTreeView::expand(const WModelIndex& index)
1790
{
1791
  setExpanded(index, true);
1792
}
1793

    
1794
void WTreeView::collapse(const WModelIndex& index)
1795
{
1796
  setExpanded(index, false);
1797
}
1798

    
1799
void WTreeView::expandToDepth(int depth)
1800
{
1801
  if (depth > 0)
1802
    expandChildrenToDepth(rootIndex(), depth);
1803
}
1804

    
1805
void WTreeView::expandChildrenToDepth(const WModelIndex& index, int depth)
1806
{
1807
  for (int i = 0; i < model()->rowCount(index); ++i) {
1808
    WModelIndex c = model()->index(i, 0, index);
1809

    
1810
    expand(c);
1811

    
1812
    if (depth > 1)
1813
      expandChildrenToDepth(c, depth - 1);
1814
  }
1815
}
1816

    
1817
WWidget *WTreeView::itemWidget(const WModelIndex& index) const
1818
{
1819
  if (!index.isValid())
1820
    return nullptr;
1821

    
1822
  WTreeViewNode *n = nodeForIndex(index);
1823
  if (n)
1824
    return n->cellWidget(index.column());
1825
  else
1826
    return nullptr;
1827
}
1828

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

    
1844
  if (index.column() != 0)
1845
    return nullptr;
1846

    
1847
  NodeMap::const_iterator i = renderedNodes_.find(index);
1848

    
1849
  if (i != renderedNodes_.end())
1850
    return i->second;
1851
  else {
1852
    if (!isExpanded(index.parent()))
1853
      return nullptr;
1854

    
1855
    WWidget *parent = widgetForIndex(index.parent());
1856
    WTreeViewNode *parentNode = dynamic_cast<WTreeViewNode *>(parent);
1857

    
1858
    if (parentNode) {
1859
      int row = getIndexRow(index, parentNode->modelIndex(), 0,
1860
			    std::numeric_limits<int>::max());
1861
      return parentNode->widgetForModelRow(row);
1862
    } else
1863
      return parent;
1864
  }
1865
}
1866

    
1867
void WTreeView::modelColumnsInserted(const WModelIndex& parent,
1868
				     int start, int end)
1869
{
1870
  int count = end - start + 1;
1871
  if (!parent.isValid()) {
1872

    
1873
    WApplication *app = WApplication::instance();
1874
    for (int i = start; i < start + count; ++i)
1875
      columns_.insert(columns_.begin() + i, createColumnInfo(i));
1876

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

    
1885
	WContainerWidget *row = headerRow();
1886

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

    
1896
  if (renderState_ == RenderState::NeedRerender ||
1897
      renderState_ == RenderState::NeedRerenderData)
1898
    return;
1899

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

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

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

    
1924
    for (int i=start; i<start+count; i++)
1925
      app->styleSheet().removeRule(columns_[i].styleRule.get());
1926
    columns_.erase(columns_.begin() + start, columns_.begin() + start + count);
1927

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

    
1942
  if (start == 0)
1943
    scheduleRerender(RenderState::NeedRerenderData);
1944
}
1945

    
1946
void WTreeView::modelColumnsRemoved(const WModelIndex& parent,
1947
				    int start, int end)
1948
{
1949
  if (renderState_ == RenderState::NeedRerender ||
1950
      renderState_ == RenderState::NeedRerenderData)
1951
    return;
1952

    
1953
  int count = end - start + 1;
1954

    
1955
  if (start != 0) {
1956
    WTreeViewNode *node = nodeForIndex(parent);
1957
    if (node)
1958
      for (WTreeViewNode *c = node->nextChildNode(nullptr); c;
1959
	   c = node->nextChildNode(c))
1960
	c->removeColumns(start, count);
1961
  }
1962

    
1963
  if (start <= currentSortColumn_ && currentSortColumn_ <= end)
1964
    currentSortColumn_ = -1;
1965
}
1966

    
1967
void WTreeView::modelRowsInserted(const WModelIndex& parent,
1968
				  int start, int end)
1969
{
1970
  int count = end - start + 1;
1971
  shiftModelIndexes(parent, start, count);
1972

    
1973
  if (renderState_ == RenderState::NeedRerender || 
1974
      renderState_ == RenderState::NeedRerenderData)
1975
    return;
1976

    
1977
  WWidget *parentWidget = widgetForIndex(parent);
1978

    
1979
  bool renderedRowsChange = isExpandedRecursive(parent);
1980

    
1981
  if (parentWidget) {
1982
    WTreeViewNode *parentNode = dynamic_cast<WTreeViewNode *>(parentWidget);
1983

    
1984
    if (parentNode) {
1985
      if (parentNode->childrenLoaded()) {
1986
	WWidget *startWidget = nullptr;
1987

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

    
1997
	parentNode->adjustChildrenHeight(count);
1998
	parentNode->shiftModelIndexes(start, count);
1999

    
2000
	if (startWidget && startWidget == parentNode->topSpacer()) {
2001
	  parentNode->addTopSpacerHeight(count);
2002
	  if (renderedRowsChange)
2003
	    renderedRowsChanged(renderedRow(model()->index(start, 0, parent),
2004
					    parentNode->topSpacer(),
2005
					    renderLowerBound(),
2006
					    renderUpperBound()),
2007
				count);
2008

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

    
2022
	  int containerIndex = startWidget ? parentNode->childContainer()
2023
	    ->indexOf(startWidget) : parentNode->childContainer()->count();
2024

    
2025
	  int parentRowCount = model()->rowCount(parent);
2026

    
2027
	  int nodesToAdd = std::max(0, std::min(count, maxRenderHeight));
2028

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

    
2038
	    parentNode->childContainer()->insertWidget(containerIndex + i,
2039
						       std::move(n));
2040

    
2041
	    if (renderedRowsChange)
2042
	      ++validRowCount_;
2043

    
2044
	  }
2045

    
2046
	  if (nodesToAdd < count) {
2047
	    parentNode->addBottomSpacerHeight(count - nodesToAdd);
2048

    
2049
	    // +1 for bottom spacer
2050
	    int targetSize = containerIndex + nodesToAdd + 1;
2051

    
2052
	    int extraBottomSpacer = 0;
2053
	    while (parentNode->childContainer()->count() > targetSize) {
2054
	      WTreeViewNode *n
2055
		= dynamic_cast<WTreeViewNode *>(parentNode->childContainer()
2056
						->widget(targetSize - 1));
2057
	      assert(n);
2058
	      extraBottomSpacer += n->renderedHeight();
2059

    
2060
	      if (renderedRowsChange)
2061
		validRowCount_ -= n->renderedHeight();
2062

    
2063
	      n->removeFromParent();
2064
	    }
2065

    
2066
	    if (extraBottomSpacer)
2067
	      parentNode->addBottomSpacerHeight(extraBottomSpacer);
2068

    
2069
	    parentNode->normalizeSpacers();
2070
	  }
2071

    
2072
	  if (first && renderedRowsChange)
2073
	    renderedRowsChanged(first->renderedRow(renderLowerBound(),
2074
						   renderUpperBound()),
2075
				nodesToAdd);
2076

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

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

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

    
2098
	s->setRows(s->rows() + count);
2099
	s->node()->adjustChildrenHeight(count);
2100

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

    
2115
void WTreeView::modelRowsAboutToBeRemoved(const WModelIndex& parent,
2116
					  int start, int end)
2117
{
2118
  int count = end - start + 1;
2119

    
2120
  bool renderedRowsChange = isExpandedRecursive(parent);
2121

    
2122
  if (renderState_ != RenderState::NeedRerender && 
2123
      renderState_ != RenderState::NeedRerenderData) {
2124
    firstRemovedRow_ = -1;
2125
    removedHeight_ = 0;
2126

    
2127
    WWidget *parentWidget = widgetForIndex(parent);
2128

    
2129
    if (parentWidget) {
2130
      WTreeViewNode *parentNode = dynamic_cast<WTreeViewNode *>(parentWidget);
2131

    
2132
      if (parentNode) {
2133
	if (parentNode->childrenLoaded()) {
2134
	  for (int i = end; i >= start; --i) {
2135
	    WWidget *w = parentNode->widgetForModelRow(i);
2136
	    assert(w);
2137

    
2138
	    RowSpacer *s = dynamic_cast<RowSpacer *>(w);
2139
	    if (s) {
2140
	      WModelIndex childIndex = model()->index(i, 0, parent);
2141

    
2142
	      if (i == start && renderedRowsChange)
2143
		firstRemovedRow_ = renderedRow(childIndex, w);
2144

    
2145
	      int childHeight = subTreeHeight(childIndex);
2146

    
2147
	      if (renderedRowsChange)
2148
		removedHeight_ += childHeight;
2149

    
2150
	      s->setRows(s->rows() - childHeight);
2151
	    } else {
2152
	      WTreeViewNode *node = dynamic_cast<WTreeViewNode *>(w);
2153

    
2154
	      if (renderedRowsChange) {
2155
		if (i == start)
2156
		  firstRemovedRow_ = node->renderedRow();
2157

    
2158
		removedHeight_ += node->renderedHeight();
2159
	      }
2160

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

    
2175
	  for (int i = start; i <= end; ++i) {
2176
	    WModelIndex childIndex = model()->index(i, 0, parent);
2177
	    int childHeight = subTreeHeight(childIndex);
2178

    
2179
	    if (renderedRowsChange) {
2180
	      removedHeight_ += childHeight;
2181

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

    
2195
  shiftModelIndexes(parent, start, -count);
2196
}
2197

    
2198
void WTreeView::modelRowsRemoved(const WModelIndex& parent,
2199
				 int start, int end)
2200
{
2201
  int count = end - start + 1;
2202

    
2203
  if (renderState_ != RenderState::NeedRerender &&
2204
      renderState_ != RenderState::NeedRerenderData) {
2205
    WWidget *parentWidget = widgetForIndex(parent);
2206

    
2207
    if (parentWidget) {
2208
      WTreeViewNode *parentNode = dynamic_cast<WTreeViewNode *>(parentWidget);
2209

    
2210
      if (parentNode) {
2211
	if (parentNode->childrenLoaded()) {
2212
	  parentNode->normalizeSpacers();
2213
	  parentNode->adjustChildrenHeight(-removedHeight_);
2214
	  parentNode->shiftModelIndexes(start, -count);
2215

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

    
2224
	    if (n)
2225
	      n->updateGraphics(true, !model()->hasChildren(n->modelIndex()));
2226
	  }
2227
	} /* else:
2228
	     children not loaded -- so we do not need to bother
2229
	  */
2230

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

    
2253
  if (renderState_ != RenderState::NeedRerender && 
2254
      renderState_ != RenderState::NeedRerenderData)
2255
    renderedRowsChanged(firstRemovedRow_, -removedHeight_);
2256
}
2257

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

    
2268
  if (parentNode && parentNode->childrenLoaded()) {
2269
    for (int r = topLeft.row(); r <= bottomRight.row(); ++r) {
2270
      WModelIndex index = model()->index(r, 0, parent);
2271

    
2272
      WTreeViewNode *n = nodeForIndex(index);
2273

    
2274
      if (n)
2275
	n->update(topLeft.column(), bottomRight.column());
2276
    }
2277
  }
2278
}
2279

    
2280
WWidget *WTreeView::headerWidget(int column, bool contentsOnly)
2281
{
2282
  WWidget *result = nullptr;
2283

    
2284
  if (headers_ && headers_->count() > 0) {
2285
    if (column == 0)
2286
      result = headers_->widget(headers_->count() - 1);
2287
    else
2288
      result = headerRow()->widget(column - 1);
2289
  }
2290

    
2291
  if (result && contentsOnly)
2292
    return result->find("contents");
2293
  else
2294
    return result;
2295
}
2296

    
2297
WContainerWidget *WTreeView::headerRow()
2298
{
2299
  WContainerWidget *row
2300
    = dynamic_cast<WContainerWidget *>(headers_->widget(0));
2301
  if (rowHeaderCount())
2302
    row = dynamic_cast<WContainerWidget *>(row->widget(0));
2303
  return row;
2304
}
2305

    
2306
int WTreeView::renderedRow(const WModelIndex& index, WWidget *w,
2307
			   int lowerBound, int upperBound)
2308
{
2309
  WTreeViewNode *node = dynamic_cast<WTreeViewNode *>(w);
2310

    
2311
  if (node)
2312
    return node->renderedRow(lowerBound, upperBound);
2313
  else {
2314
    RowSpacer *s = dynamic_cast<RowSpacer *>(w);
2315

    
2316
    int result = s->renderedRow(0, upperBound);
2317

    
2318
    if (result > upperBound)
2319
      return result;
2320
    else if (result + s->node()->renderedHeight() < lowerBound)
2321
      return result;
2322
    else
2323
      return result + getIndexRow(index, s->node()->modelIndex(),
2324
				  lowerBound - result, upperBound - result);
2325
  }
2326
}
2327

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

    
2340
    int result = 0;
2341
    for (int r = 0; r < child.row(); ++r) {
2342
      result += subTreeHeight(model()->index(r, 0, parent), 0,
2343
			      upperBound - result);
2344
      if (result >= upperBound)
2345
	return result;
2346
    }
2347

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

    
2361
int WTreeView::renderLowerBound() const
2362
{
2363
  return firstRenderedRow_;
2364
}
2365

    
2366
int WTreeView::renderUpperBound() const
2367
{
2368
  return firstRenderedRow_ + validRowCount_;
2369
}
2370

    
2371
void WTreeView::renderedRowsChanged(int row, int count)
2372
{
2373
  if (count < 0
2374
      && row - count >= firstRenderedRow_
2375
      && row < firstRenderedRow_ + validRowCount_)
2376
    validRowCount_ += std::max(firstRenderedRow_ - row + count, count);
2377

    
2378
  if (row < firstRenderedRow_)
2379
    firstRenderedRow_ += count;
2380

    
2381
  scheduleRerender(RenderState::NeedAdjustViewPort);
2382
}
2383

    
2384
void WTreeView::adjustToViewport(WTreeViewNode *changed)
2385
{
2386
  //assert(rootNode_->rowCount() == 1);
2387

    
2388
  firstRenderedRow_ = std::max(0, firstRenderedRow_);
2389
  validRowCount_
2390
    = std::max(0, std::min(validRowCount_,
2391
			   rootNode_->renderedHeight() - firstRenderedRow_));
2392

    
2393
  int viewportBottom = std::min(rootNode_->renderedHeight(),
2394
				viewportTop_ + viewportHeight_);
2395
  int lastValidRow = firstRenderedRow_ + validRowCount_;
2396

    
2397
  bool renderMore =
2398
    (std::max(0,
2399
	      viewportTop_ - viewportHeight_) < firstRenderedRow_)
2400
    || (std::min(rootNode_->renderedHeight(),
2401
		 viewportBottom + viewportHeight_) > lastValidRow);
2402

    
2403
  bool pruneFirst = false;
2404

    
2405
  //assert(rootNode_->rowCount() == 1);
2406

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

    
2416
    int newValidRowCount = newLastValidRow - newFirstRenderedRow;
2417

    
2418
    int newRows = std::max(0, firstRenderedRow_ - newFirstRenderedRow)
2419
      + std::max(0, newLastValidRow - lastValidRow);
2420

    
2421
    const int pruneFactor
2422
      = WApplication::instance()->environment().ajax() ? 9 : 1;
2423

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

    
2435
  const int pruneFactor
2436
    = WApplication::instance()->environment().ajax() ? 5 : 1;
2437

    
2438
  if (pruneFirst || nodeLoad_ > pruneFactor * viewportHeight_) {
2439
    firstRenderedRow_ = calcOptimalFirstRenderedRow();
2440
    validRowCount_ = calcOptimalRenderedRowCount();
2441

    
2442
    pruneNodes(rootNode_, 0);
2443

    
2444
    if (pruneFirst && nodeLoad_ < calcOptimalRenderedRowCount()) {
2445
      adjustRenderedNode(rootNode_, 0);
2446
    } 
2447
  }
2448

    
2449
  //assert(rootNode_->rowCount() == 1);
2450
}
2451

    
2452
int WTreeView::adjustRenderedNode(WTreeViewNode *node, int theNodeRow)
2453
{
2454
  //assert(rootNode_->rowCount() == 1);
2455

    
2456
  WModelIndex index = node->modelIndex();
2457

    
2458
  if (index != rootIndex())
2459
    ++theNodeRow;
2460

    
2461
  if (!isExpanded(index) && !node->childrenLoaded())
2462
    return theNodeRow;
2463

    
2464
  int nodeRow = theNodeRow;
2465

    
2466
  if (node->isAllSpacer()) {
2467
    if (nodeRow + node->childrenHeight() > firstRenderedRow_
2468
	&& nodeRow < firstRenderedRow_ + validRowCount_) {
2469
      // replace spacer by some nodes
2470
      int childCount = model()->rowCount(index);
2471

    
2472
      bool firstNode = true;
2473
      int rowStubs = 0;
2474

    
2475
      for (int i = 0; i < childCount; ++i) {
2476
	WModelIndex childIndex = model()->index(i, 0, index);
2477

    
2478
	int childHeight = subTreeHeight(childIndex);
2479

    
2480
	if (nodeRow <= firstRenderedRow_ + validRowCount_
2481
	    && nodeRow + childHeight > firstRenderedRow_) {
2482
	  if (firstNode) {
2483
	    firstNode = false;
2484
	    node->setTopSpacerHeight(rowStubs);
2485
	    rowStubs = 0;
2486
	  }
2487

    
2488
	  // assert(rootNode_->rowCount() == 1);
2489

    
2490
	  WTreeViewNode *n = node->childContainer()->addWidget
2491
	    (cpp14::make_unique<WTreeViewNode>
2492
	     (this, childIndex, childHeight - 1, i == childCount - 1, node));
2493

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

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

    
2519
      assert(n);
2520

    
2521
      WModelIndex childIndex
2522
	= model()->index(n->modelIndex().row() - 1, 0, index);
2523

    
2524
      assert(childIndex.isValid());
2525

    
2526
      int childHeight = subTreeHeight(childIndex);
2527

    
2528
      {
2529
	std::unique_ptr<WTreeViewNode> nn
2530
	  (n = new WTreeViewNode(this, childIndex, childHeight - 1,
2531
			     childIndex.row() == childCount - 1, node));
2532
	node->childContainer()->insertWidget(1, std::move(nn));
2533
      }
2534

    
2535
      nestedNodeRow = nodeRow + topSpacerHeight - childHeight;
2536
      nestedNodeRow = adjustRenderedNode(n, nestedNodeRow);
2537
      assert(nestedNodeRow == nodeRow + topSpacerHeight);
2538

    
2539
      topSpacerHeight -= childHeight;
2540
      node->addTopSpacerHeight(-childHeight);
2541
    }
2542

    
2543
    for (; child; child=node->nextChildNode(child))
2544
      nestedNodeRow = adjustRenderedNode(child, nestedNodeRow);
2545

    
2546
    int nch = node->childrenHeight();
2547
    int bottomSpacerStart = nch - node->bottomSpacerHeight();
2548

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

    
2557
      assert(n);
2558

    
2559
      WModelIndex childIndex
2560
	= model()->index(n->modelIndex().row() + 1, 0, index);
2561

    
2562
      assert (childIndex.isValid());
2563

    
2564
      int childHeight = subTreeHeight(childIndex);
2565

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

    
2573
      nestedNodeRow = nodeRow + bottomSpacerStart;
2574
      nestedNodeRow = adjustRenderedNode(n, nestedNodeRow);
2575
      assert(nestedNodeRow == nodeRow + bottomSpacerStart + childHeight);
2576

    
2577
      node->addBottomSpacerHeight(-childHeight);
2578
      bottomSpacerStart += childHeight;
2579
    }
2580

    
2581
    nodeRow += nch;
2582
  }
2583

    
2584
  // assert(rootNode_->rowCount() == 1);
2585

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

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

    
2597
  ++nodeRow;
2598

    
2599
  if (isExpanded(index)) {
2600
    // prune away nodes until we are within the rendered region
2601
    nodeRow += node->topSpacerHeight();
2602

    
2603
    bool done = false;
2604
    WTreeViewNode *c = nullptr;
2605

    
2606
    for (; nodeRow < firstRenderedRow_; ) {
2607
      c = node->nextChildNode(nullptr);
2608
      if (!c) {
2609
	done = true;
2610
	break;
2611
      }
2612

    
2613
      if (nodeRow + c->renderedHeight() < firstRenderedRow_) {
2614
	node->addTopSpacerHeight(c->renderedHeight());
2615
	nodeRow += c->renderedHeight();
2616
	c->removeFromParent();
2617
	c = nullptr;
2618
      } else {
2619
	nodeRow = pruneNodes(c, nodeRow);
2620
	break;
2621
      }
2622
    }
2623

    
2624
    if (!done) {
2625
      for (; nodeRow <= firstRenderedRow_ + validRowCount_; ) {
2626
	c = node->nextChildNode(c);
2627

    
2628
	if (!c) {
2629
	  done = true;
2630
	  break;
2631
	}
2632

    
2633
	nodeRow = pruneNodes(c, nodeRow);
2634
      }
2635
    }
2636

    
2637
    if (!done) {
2638
      c = node->nextChildNode(c);
2639

    
2640
      if (c != nullptr) {
2641
	int i = node->childContainer()->indexOf(c);
2642
	int prunedHeight = 0;
2643

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

    
2653
	node->addBottomSpacerHeight(prunedHeight);
2654
      }
2655
    }
2656

    
2657
    nodeRow += node->bottomSpacerHeight();
2658

    
2659
    node->normalizeSpacers();
2660

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

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

    
2674
      node->addBottomSpacerHeight(prunedHeight);
2675
      node->normalizeSpacers();
2676
    }
2677

    
2678
  return nodeRow;
2679
}
2680

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

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

    
2703
    WModelIndex i = *it;
2704

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

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

    
2726
#ifndef WT_TARGET_JAVA
2727
    it = n;
2728
#endif
2729
  }
2730

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

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

    
2746
  return removed;
2747
}
2748

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

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

    
2757
  shiftEditorRows(parent, start, count, false);
2758

    
2759
  if (removed)
2760
    selectionChanged().emit();
2761
}
2762

    
2763
void WTreeView::modelLayoutAboutToBeChanged()
2764
{
2765
  WModelIndex::encodeAsRawIndexes(expandedSet_);
2766

    
2767
  WAbstractItemView::modelLayoutAboutToBeChanged();
2768
}
2769

    
2770
void WTreeView::modelLayoutChanged()
2771
{
2772
  WAbstractItemView::modelLayoutChanged();
2773

    
2774
  expandedSet_ = WModelIndex::decodeFromRawIndexes(expandedSet_);
2775

    
2776
  renderedNodes_.clear();
2777

    
2778
  pageChanged().emit();
2779
}
2780

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

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

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

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

    
2806
    return true;
2807
  } else
2808
    return false;
2809
}
2810

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

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

    
2830
      if (ic == last)
2831
	return;
2832
    }
2833

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

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

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

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

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

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

    
2873
  return ci;
2874
}
2875

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

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

    
2882
  pageChanged().emit();
2883

    
2884
  scheduleRerender(RenderState::NeedAdjustViewPort);
2885
}
2886

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

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

    
2900
int WTreeView::pageSize() const
2901
{
2902
  return viewportHeight_;
2903
}
2904

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

    
2915
  WApplication *app = WApplication::instance();
2916

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

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

    
2951
        scheduleRerender( RenderState::NeedAdjustViewPort );
2952
      }
2953
    }
2954

    
2955
    WStringStream s;
2956

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

    
2962
    doJavaScript(s.str());
2963
  } else
2964
    setCurrentPage(row / pageSize());
2965
}
2966

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

    
2971
  throw WException("Scrolled signal existes only with ajax.");
2972
}
2973
}
2974

    
2975
#endif // DOXYGEN_ONLY
(2-2/4)