1 /*
2  * Copyright (C) 2009 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 
7 #include "Wt/WTableView.h"
8 
9 #include "Wt/WAbstractItemDelegate.h"
10 #include "Wt/WApplication.h"
11 #include "Wt/WAbstractItemModel.h"
12 #include "Wt/WContainerWidget.h"
13 #include "Wt/WEnvironment.h"
14 #include "Wt/WException.h"
15 #include "Wt/WGridLayout.h"
16 #include "Wt/WLogger.h"
17 #include "Wt/WModelIndex.h"
18 #include "Wt/WStringStream.h"
19 #include "Wt/WTable.h"
20 #include "Wt/WTheme.h"
21 
22 #include "WebUtils.h"
23 
24 #ifndef WT_DEBUG_JS
25 
26 #include "js/WTableView.min.js"
27 #endif
28 
29 #define UNKNOWN_VIEWPORT_HEIGHT 800
30 #define CONTENTS_VIEWPORT_HEIGHT -1
31 
32 #include <algorithm>
33 #include <cmath>
34 #include <math.h>
35 
36 #if defined(_MSC_VER) && (_MSC_VER < 1800)
37 namespace {
round(double x)38   double round(double x)
39   {
40     return floor(x + 0.5);
41   }
42 }
43 #endif
44 
45 namespace Wt {
46 
47 LOGGER("WTableView");
48 
WTableView()49 WTableView::WTableView()
50   : headers_(nullptr),
51     canvas_(nullptr),
52     table_(nullptr),
53     headerContainer_(nullptr),
54     contentsContainer_(nullptr),
55     headerColumnsCanvas_(nullptr),
56     headerColumnsTable_(nullptr),
57     headerColumnsHeaderContainer_(nullptr),
58     headerColumnsContainer_(nullptr),
59     plainTable_(nullptr),
60     dropEvent_(impl_, "dropEvent"),
61     rowDropEvent_(impl_, "rowDropEvent"),
62     scrolled_(impl_, "scrolled"),
63     itemTouchSelectEvent_(impl_, "itemTouchSelectEvent"),
64     firstColumn_(-1),
65     lastColumn_(-1),
66     viewportLeft_(0),
67     viewportWidth_(1000),
68     viewportTop_(0),
69     viewportHeight_(UNKNOWN_VIEWPORT_HEIGHT),
70     scrollToRow_(-1),
71     scrollToHint_(ScrollHint::EnsureVisible),
72     columnResizeConnected_(false)
73 {
74   preloadMargin_[0] = preloadMargin_[1] = preloadMargin_[2] = preloadMargin_[3] = WLength();
75 
76   setSelectable(false);
77 
78   setStyleClass("Wt-itemview Wt-tableview");
79 
80   setup();
81 }
82 
setup()83 void WTableView::setup()
84 {
85   impl_->clear();
86 
87   WApplication *app = WApplication::instance();
88 
89   if (app->environment().ajax()) {
90     impl_->setPositionScheme(PositionScheme::Relative);
91 
92     headers_ = new WContainerWidget();
93     headers_->setStyleClass("Wt-headerdiv headerrh");
94 
95     table_ = new WContainerWidget();
96     table_->setStyleClass("Wt-tv-contents");
97     table_->setPositionScheme(PositionScheme::Absolute);
98     table_->setWidth(WLength(100, LengthUnit::Percentage));
99 
100     std::unique_ptr<WGridLayout> layout(new WGridLayout());
101     layout->setHorizontalSpacing(0);
102     layout->setVerticalSpacing(0);
103     layout->setContentsMargins(0, 0, 0, 0);
104 
105     headerContainer_ = new WContainerWidget();
106     headerContainer_->setStyleClass("Wt-header headerrh");
107     headerContainer_->setOverflow(Overflow::Hidden);
108     headerContainer_->addWidget(std::unique_ptr<WWidget>(headers_));
109 
110     canvas_ = new WContainerWidget();
111     canvas_->setStyleClass("Wt-spacer");
112     canvas_->setPositionScheme(PositionScheme::Relative);
113     canvas_->clicked().connect
114       (this, std::bind(&WTableView::handleSingleClick, this, false,
115 		       std::placeholders::_1));
116 
117     canvas_->clicked().connect
118       ("function(o, e) { "
119        """$(document).trigger($.event.fix(e));"
120        "}");
121 
122     canvas_->clicked().preventPropagation();
123     canvas_->mouseWentDown().connect
124       (this, std::bind(&WTableView::handleMouseWentDown, this, false,
125 		       std::placeholders::_1));
126     canvas_->mouseWentDown().preventPropagation();
127     canvas_->mouseWentDown().connect("function(o, e) { "
128                                      "$(document).trigger($.event.fix(e));"
129                                      "}");
130     canvas_->mouseWentUp().connect
131       (this, std::bind(&WTableView::handleMouseWentUp, this, false,
132 		       std::placeholders::_1));
133     canvas_->mouseWentUp().preventPropagation();
134     canvas_->mouseWentUp().connect("function(o, e) { "
135                                      "$(document).trigger($.event.fix(e));"
136                                      "}");
137     canvas_->addWidget(std::unique_ptr<WWidget>(table_));
138 
139     contentsContainer_ = new WContainerWidget();
140     contentsContainer_->setOverflow(Overflow::Auto);
141     contentsContainer_->setPositionScheme(PositionScheme::Absolute);
142     contentsContainer_->addWidget(std::unique_ptr<WWidget>(canvas_));
143 
144     contentsContainer_->clicked().connect
145       (this, std::bind(&WTableView::handleRootSingleClick, this, 0,
146 		       std::placeholders::_1));
147     contentsContainer_->mouseWentUp().connect
148       (this, std::bind(&WTableView::handleRootMouseWentUp, this, 0,
149 		       std::placeholders::_1));
150 
151     headerColumnsHeaderContainer_ = new WContainerWidget();
152     headerColumnsHeaderContainer_->setStyleClass("Wt-header Wt-headerdiv "
153 						 "headerrh");
154     headerColumnsHeaderContainer_->hide();
155 
156     headerColumnsTable_ = new WContainerWidget();
157     headerColumnsTable_->setStyleClass("Wt-tv-contents");
158     headerColumnsTable_->setPositionScheme(PositionScheme::Absolute);
159     headerColumnsTable_->setWidth(WLength(100, LengthUnit::Percentage));
160 
161     headerColumnsCanvas_ = new WContainerWidget();
162     headerColumnsCanvas_->setPositionScheme(PositionScheme::Relative);
163     headerColumnsCanvas_->clicked().preventPropagation();
164     headerColumnsCanvas_->clicked().connect
165       (this, std::bind(&WTableView::handleSingleClick, this, true,
166 		       std::placeholders::_1));
167     headerColumnsCanvas_->clicked().connect("function(o, e) { "
168                                "$(document).trigger($.event.fix(e));"
169                                "}");
170     headerColumnsCanvas_->mouseWentDown().preventPropagation();
171     headerColumnsCanvas_->mouseWentDown().connect
172       (this, std::bind(&WTableView::handleMouseWentDown, this, true,
173 		       std::placeholders::_1));
174     headerColumnsCanvas_->mouseWentDown().connect("function(o, e) { "
175                                      "$(document).trigger($.event.fix(e));"
176                                      "}");
177     headerColumnsCanvas_->mouseWentUp().preventPropagation();
178     headerColumnsCanvas_->mouseWentUp().connect
179       (this, std::bind(&WTableView::handleMouseWentUp, this, true,
180 		       std::placeholders::_1));
181     headerColumnsCanvas_->mouseWentUp().connect("function(o, e) { "
182                                      "$(document).trigger($.event.fix(e));"
183                                      "}");
184     headerColumnsCanvas_->addWidget
185       (std::unique_ptr<WWidget>(headerColumnsTable_));
186 
187     headerColumnsContainer_ = new WContainerWidget();
188     headerColumnsContainer_->setPositionScheme(PositionScheme::Absolute);
189     headerColumnsContainer_->setOverflow(Overflow::Hidden);
190     headerColumnsContainer_->addWidget
191       (std::unique_ptr<WWidget>(headerColumnsCanvas_));
192     headerColumnsContainer_->hide();
193 
194     headerColumnsContainer_->clicked().connect
195       (this, std::bind(&WTableView::handleRootSingleClick, this, 0,
196 		       std::placeholders::_1));
197     headerColumnsContainer_->mouseWentUp().connect
198       (this, std::bind(&WTableView::handleRootMouseWentUp, this, 0,
199 		       std::placeholders::_1));
200 
201     layout->addWidget(std::unique_ptr<WWidget>(headerColumnsHeaderContainer_),
202 		      0, 0);
203     layout->addWidget(std::unique_ptr<WWidget>(headerContainer_), 0, 1);
204     layout->addWidget(std::unique_ptr<WWidget>(headerColumnsContainer_), 1, 0);
205     layout->addWidget(std::unique_ptr<WWidget>(contentsContainer_), 1, 1);
206 
207     for (int i = 0; i < layout->count(); ++i)
208       layout->itemAt(i)->widget()->addStyleClass("tcontainer");
209 
210     layout->setRowStretch(1, 1);
211     layout->setColumnStretch(1, 1);
212 
213     impl_->setLayout(std::move(layout));
214   } else {
215     plainTable_ = new WTable();
216     plainTable_->setStyleClass("Wt-plaintable");
217     plainTable_->setAttributeValue("style", "table-layout: fixed;");
218     plainTable_->setHeaderCount(1);
219 
220     impl_->addWidget(std::unique_ptr<WWidget>(plainTable_));
221 
222     resize(width(), height());
223   }
224 
225   setRowHeight(rowHeight());
226 
227   updateTableBackground();
228 }
229 
enableAjax()230 void WTableView::enableAjax()
231 {
232   plainTable_ = 0;
233   setup();
234   defineJavaScript();
235   scheduleRerender(RenderState::NeedRerenderHeader);
236   WAbstractItemView::enableAjax();
237 }
238 
resize(const WLength & width,const WLength & height)239 void WTableView::resize(const WLength& width, const WLength& height)
240 {
241   if (ajaxMode()) {
242     if (height.unit() == LengthUnit::Percentage) {
243       LOG_ERROR("resize(): height cannot be a Percentage");
244       return;
245     }
246 
247     if (!height.isAuto()) {
248       viewportHeight_
249 	= static_cast<int>(std::ceil((height.toPixels()
250 				      - headerHeight().toPixels())));
251       if (scrollToRow_ != -1) {
252 	WModelIndex index = model()->index(scrollToRow_, 0, rootIndex());
253 	scrollToRow_ = -1;
254 	scrollTo(index, scrollToHint_);
255       }
256     } else
257       viewportHeight_ = UNKNOWN_VIEWPORT_HEIGHT;
258   } else { // Plain HTML mode
259     if (!plainTable_) // Not yet rendered
260       return;
261 
262     plainTable_->setWidth(width);
263 
264     if (!height.isAuto()) {
265       if (impl_->count() < 2)
266 	impl_->addWidget(createPageNavigationBar());
267     }
268   }
269 
270   computeRenderedArea();
271 
272   WCompositeWidget::resize(width, height);
273 
274   scheduleRerender(RenderState::NeedAdjustViewPort);
275 }
276 
~WTableView()277 WTableView::~WTableView()
278 {
279   impl_->clear();
280 }
281 
updateTableBackground()282 void WTableView::updateTableBackground()
283 {
284   if (ajaxMode()) {
285     WApplication::instance()->theme()->apply
286       (this, table_, TableViewRowContainer);
287     WApplication::instance()->theme()->apply
288       (this, headerColumnsTable_, TableViewRowContainer);
289   } else
290     // FIXME avoid background on header row ?
291     WApplication::instance()->theme()->apply
292       (this, plainTable_, TableViewRowContainer);
293 }
294 
setModel(const std::shared_ptr<WAbstractItemModel> & model)295 void WTableView::setModel(const std::shared_ptr<WAbstractItemModel>& model)
296 {
297   WAbstractItemView::setModel(model);
298 
299   typedef WTableView Self;
300 
301   /* connect slots to new model */
302   modelConnections_.push_back(model->columnsInserted().connect
303 			      (this, &Self::modelColumnsInserted));
304   modelConnections_.push_back(model->columnsAboutToBeRemoved().connect
305 			      (this, &Self::modelColumnsAboutToBeRemoved));
306   modelConnections_.push_back(model->rowsInserted().connect
307 			      (this, &Self::modelRowsInserted));
308   modelConnections_.push_back(model->rowsAboutToBeRemoved().connect
309 			      (this, &Self::modelRowsAboutToBeRemoved));
310   modelConnections_.push_back(model->rowsRemoved().connect
311 			      (this, &Self::modelRowsRemoved));
312   modelConnections_.push_back(model->dataChanged().connect
313 			      (this, &Self::modelDataChanged));
314   modelConnections_.push_back(model->headerDataChanged().connect
315 			      (this, &Self::modelHeaderDataChanged));
316   modelConnections_.push_back(model->layoutAboutToBeChanged().connect
317 			      (this, &Self::modelLayoutAboutToBeChanged));
318   modelConnections_.push_back(model->layoutChanged().connect
319 			      (this, &Self::modelLayoutChanged));
320   modelConnections_.push_back(model->modelReset().connect
321 			      (this, &Self::modelReset));
322 
323   firstColumn_ = lastColumn_ = -1;
324   adjustSize();
325 }
326 
renderWidget(WWidget * widget,const WModelIndex & index)327 std::unique_ptr<WWidget> WTableView::renderWidget(WWidget* widget, const WModelIndex& index)
328 {
329   auto itemDelegate = this->itemDelegate(index.column());
330 
331   WFlags<ViewItemRenderFlag> renderFlags = None;
332 
333   if (ajaxMode()) {
334     if (isSelected(index))
335       renderFlags |= ViewItemRenderFlag::Selected;
336   }
337 
338   if (isEditing(index)) {
339     renderFlags |= ViewItemRenderFlag::Editing;
340     if (hasEditFocus(index))
341       renderFlags |= ViewItemRenderFlag::Focused;
342   }
343 
344   if (!isValid(index)) {
345     renderFlags |= ViewItemRenderFlag::Invalid;
346   }
347 
348   bool initial = !widget;
349 
350   std::unique_ptr<WWidget> wAfter = itemDelegate->update(widget, index, renderFlags);
351   if (wAfter)
352     widget = wAfter.get();
353   widget->setInline(false);
354   widget->addStyleClass("Wt-tv-c");
355   widget->setHeight(rowHeight());
356 
357   if (renderFlags.test(ViewItemRenderFlag::Editing)) {
358     widget->setTabIndex(-1);
359     setEditorWidget(index, widget);
360   }
361 
362   if (initial) {
363     /*
364      * If we are re-creating an old editor, then reset its current edit
365      * state (we do not actually check if it is an old editor, but we could
366      * now with stateSaved)
367      */
368     if (renderFlags.test(ViewItemRenderFlag::Editing)) {
369       cpp17::any state = editState(index);
370       if (cpp17::any_has_value(state))
371 	itemDelegate->setEditState(widget, index, state);
372     }
373   }
374 
375   return wAfter;
376 }
377 
spannerCount(const Side side)378 int WTableView::spannerCount(const Side side) const
379 {
380   assert(ajaxMode());
381 
382   switch (side) {
383   case Side::Top: {
384     return (int)(table_->offset(Side::Top).toPixels() / rowHeight().toPixels());
385   }
386   case Side::Bottom: {
387     return (int)(model()->rowCount(rootIndex())
388       - (table_->offset(Side::Top).toPixels() + table_->height().toPixels())
389 		 / rowHeight().toPixels());
390   }
391   case Side::Left:
392     return firstColumn_; // headers are included
393   case Side::Right:
394     return columnCount() - (lastColumn_ + 1);
395   default:
396     assert(false);
397     return -1;
398   }
399 }
400 
setRenderedHeight(double th)401 void WTableView::setRenderedHeight(double th)
402 {
403   table_->setHeight(th);
404   headerColumnsTable_->setHeight(th);
405   for (int i = 0; i < renderedColumnsCount(); ++i) {
406     ColumnWidget *w = columnContainer(i);
407     w->setHeight(th);
408   }
409 }
410 
setSpannerCount(const Side side,const int count)411 void WTableView::setSpannerCount(const Side side, const int count)
412 {
413   assert(ajaxMode());
414 
415   switch (side) {
416   case Side::Top: {
417     int size = model()->rowCount(rootIndex()) - count - spannerCount(Side::Bottom);
418 
419     double to = count * rowHeight().toPixels();
420     table_->setOffsets(to, Side::Top);
421     headerColumnsTable_->setOffsets(to, Side::Top);
422 
423     double th = size * rowHeight().toPixels();
424     setRenderedHeight(th);
425     break;
426   }
427   case Side::Bottom: {
428     int size = model()->rowCount(rootIndex()) - spannerCount(Side::Top) - count;
429     double th = size * rowHeight().toPixels();
430     setRenderedHeight(th);
431     break;
432   }
433   case Side::Left: {
434     int total = 0;
435     for (int i = rowHeaderCount(); i < count; i++)
436       if (!columnInfo(i).hidden)
437 	total += (int)columnInfo(i).width.toPixels() + 7;
438     table_->setOffsets(total, Side::Left);
439     firstColumn_ = count;
440     break;
441   }
442   case Side::Right:
443     lastColumn_ = columnCount() - count - 1;
444     break;
445   default:
446     assert(false);
447   }
448 }
449 
firstRow()450 int WTableView::firstRow() const
451 {
452   if (ajaxMode())
453     return spannerCount(Side::Top);
454   else
455     return renderedFirstRow_;
456 }
457 
lastRow()458 int WTableView::lastRow() const
459 {
460   if (ajaxMode())
461     return model()->rowCount(rootIndex()) - spannerCount(Side::Bottom) - 1;
462   else
463     return renderedLastRow_;
464 }
465 
firstColumn()466 int WTableView::firstColumn() const
467 {
468   if (ajaxMode())
469     return firstColumn_;
470   else
471     return 0;
472 }
473 
lastColumn()474 int WTableView::lastColumn() const
475 {
476   if (ajaxMode())
477     return lastColumn_;
478   else
479     return columnCount() - 1;
480 }
481 
addSection(const Side side)482 void WTableView::addSection(const Side side)
483 {
484   assert(ajaxMode());
485 
486   switch (side) {
487   case Side::Top:
488     setSpannerCount(side, spannerCount(side) - 1);
489     break;
490   case Side::Bottom:
491     setSpannerCount(side, spannerCount(side) - 1);
492     break;
493   case Side::Left: {
494     ColumnWidget *w = createColumnWidget(firstColumn() - 1);
495 
496     if (!columnInfo(w->column()).hidden)
497       table_->setOffsets(table_->offset(Side::Left).toPixels()
498 			 - columnWidth(w->column()).toPixels() - 7, Side::Left);
499     else
500       w->hide();
501 
502     --firstColumn_;
503     break;
504   }
505   case Side::Right: {
506     ColumnWidget *w = createColumnWidget(lastColumn() + 1);
507 
508     if (columnInfo(w->column()).hidden)
509       w->hide();
510 
511     ++lastColumn_;
512     break;
513   }
514   default:
515     assert(false);
516   }
517 }
518 
deleteItem(int row,int col,WWidget * w)519 void WTableView::deleteItem(int row, int col, WWidget *w)
520 {
521   persistEditor(model()->index(row, col, rootIndex()));
522   w->removeFromParent();
523 }
524 
removeSection(const Side side)525 void WTableView::removeSection(const Side side)
526 {
527   assert(ajaxMode());
528 
529   int row = firstRow(), col = firstColumn();
530 
531   switch (side) {
532   case Side::Top:
533     setSpannerCount(side, spannerCount(side) + 1);
534 
535     for (int i = 0; i < renderedColumnsCount(); ++i) {
536       ColumnWidget *w = columnContainer(i);
537       deleteItem(row, col + i, w->widget(0));
538     }
539     break;
540   case Side::Bottom:
541     row = lastRow();
542     setSpannerCount(side, spannerCount(side) + 1);
543 
544     for (int i = 0; i < renderedColumnsCount(); ++i) {
545       ColumnWidget *w = columnContainer(i);
546       deleteItem(row, col + i, w->widget(w->count() - 1));
547     }
548     break;
549   case Side::Left: {
550     ColumnWidget *w = columnContainer(rowHeaderCount());
551 
552     if (!columnInfo(w->column()).hidden)
553       table_->setOffsets(table_->offset(Side::Left).toPixels()
554 			 + columnWidth(w->column()).toPixels() + 7, Side::Left);
555     ++firstColumn_;
556 
557     for (int i = w->count() - 1; i >= 0; --i)
558       deleteItem(row + i, col, w->widget(i));
559 
560     w->removeFromParent();
561 
562     break;
563   }
564   case Side::Right: {
565     ColumnWidget *w = columnContainer(-1);
566     col = w->column();
567 
568     --lastColumn_;
569 
570     for (int i = w->count() - 1; i >= 0; --i)
571       deleteItem(row + i, col, w->widget(i));
572 
573     w->removeFromParent();
574 
575     break;
576   }
577   default:
578     break;
579   }
580 }
581 
renderTable(const int fr,const int lr,const int fc,const int lc)582 void WTableView::renderTable(const int fr, const int lr,
583 			     const int fc, const int lc)
584 {
585   assert(ajaxMode());
586 
587   if (fr > lastRow() || firstRow() > lr ||
588       fc > lastColumn() || firstColumn() > lc)
589     reset();
590 
591   int oldFirstRow = firstRow();
592   int oldLastRow = lastRow();
593 
594   int topRowsToAdd = 0;
595   int bottomRowsToAdd = 0;
596 
597   if (oldLastRow - oldFirstRow < 0) {
598     topRowsToAdd = 0;
599     setSpannerCount(Side::Top, fr);
600     setSpannerCount(Side::Bottom, model()->rowCount(rootIndex()) - fr);
601     bottomRowsToAdd = lr - fr + 1;
602   } else {
603     topRowsToAdd = firstRow() - fr;
604     bottomRowsToAdd = lr - lastRow();
605   }
606 
607   int oldFirstCol = firstColumn();
608   int oldLastCol = lastColumn();
609 
610   int leftColsToAdd = 0;
611   int rightColsToAdd = 0;
612 
613   if (oldLastCol - oldFirstCol < 0) {
614     leftColsToAdd = 0;
615     setSpannerCount(Side::Left, fc);
616     setSpannerCount(Side::Right, columnCount() - fc);
617     rightColsToAdd = lc - fc + 1;
618   } else {
619     leftColsToAdd = firstColumn() - fc;
620     rightColsToAdd = lc - lastColumn();
621   }
622 
623   // Remove columns
624   for (int i = 0; i < -leftColsToAdd; ++i)
625     removeSection(Side::Left);
626   for (int i = 0; i < -rightColsToAdd; ++i)
627     removeSection(Side::Right);
628 
629   // Remove rows
630   for (int i = 0; i < -topRowsToAdd; ++i)
631     removeSection(Side::Top);
632   for (int i = 0; i < -bottomRowsToAdd; ++i)
633     removeSection(Side::Bottom);
634 
635   // Add (empty) columns
636   for (int i = 0; i < leftColsToAdd; ++i)
637     addSection(Side::Left);
638   for (int i = 0; i < rightColsToAdd; ++i)
639     addSection(Side::Right);
640 
641   // Add new top rows
642   for (int i = 0; i < topRowsToAdd; ++i) {
643     int row = fr + i;
644     for (int col = 0; col < rowHeaderCount(); ++col) {
645       ColumnWidget *w = columnContainer(col);
646       w->insertWidget(i, renderWidget(nullptr, model()->index(row, col, rootIndex())));
647     }
648     for (int col = fc; col <= lc; ++col) {
649       ColumnWidget *w = columnContainer(col - fc + rowHeaderCount());
650       w->insertWidget(i, renderWidget(nullptr, model()->index(row, col, rootIndex())));
651     }
652     addSection(Side::Top);
653   }
654   // Populate new columns of existing rows
655   if (oldLastRow != -1 &&
656       (leftColsToAdd > 0 ||
657        rightColsToAdd > 0)) {
658     for (int row = std::max(oldFirstRow, fr); row <= std::min(oldLastRow, lr); ++row) {
659       // Populate left columns
660       for (int j = 0; j < leftColsToAdd; ++j) {
661         int col = fc + j;
662         int renderCol = rowHeaderCount() + j;
663         ColumnWidget *w = columnContainer(renderCol);
664         w->addWidget(renderWidget(nullptr, model()->index(row, col, rootIndex())));
665       }
666       // Populate right columns
667       for (int j = 0; j < rightColsToAdd; ++j) {
668         int col = lc - rightColsToAdd + 1 + j;
669         ColumnWidget *w = columnContainer(col - fc + rowHeaderCount());
670         w->addWidget(renderWidget(nullptr, model()->index(row, col, rootIndex())));
671       }
672     }
673   }
674   // Add new bottom rows
675   for (int i = 0; i < bottomRowsToAdd; ++i) {
676     int row = oldLastRow == -1 ? fr + i : oldLastRow + 1 + i;
677     for (int col = 0; col < rowHeaderCount(); ++col) {
678       ColumnWidget *w = columnContainer(col);
679       w->addWidget(renderWidget(nullptr, model()->index(row, col, rootIndex())));
680     }
681     for (int col = fc; col <= lc; ++col) {
682       ColumnWidget *w = columnContainer(col - fc + rowHeaderCount());
683       w->addWidget(renderWidget(nullptr, model()->index(row, col, rootIndex())));
684     }
685     addSection(Side::Bottom);
686   }
687 
688   updateColumnOffsets();
689 
690   assert(lastRow() == lr && firstRow() == fr);
691   assert(lastColumn() == lc && firstColumn() == fc);
692 
693   const double marginTop = (preloadMargin(Side::Top).isAuto() ? viewportHeight_ : preloadMargin(Side::Top).toPixels()) / 2;
694   const double marginBottom = (preloadMargin(Side::Bottom).isAuto() ? viewportHeight_ : preloadMargin(Side::Bottom).toPixels()) / 2;
695   const double marginLeft = (preloadMargin(Side::Left).isAuto() ? viewportWidth_ : preloadMargin(Side::Left).toPixels()) / 2;
696   const double marginRight = (preloadMargin(Side::Right).isAuto() ? viewportWidth_ : preloadMargin(Side::Right).toPixels()) / 2;
697 
698   const double scrollX1 = round(std::max(0.0, viewportLeft_ - marginLeft));
699   const double scrollX2 = round(viewportLeft_ + marginRight);
700   const double scrollY1 = round(std::max(0.0, viewportTop_ - marginTop));
701   const double scrollY2 = round(viewportTop_ + marginBottom);
702 
703   WStringStream s;
704 
705   char buf[30];
706 
707   s << jsRef() << ".wtObj.scrolled(";
708   s << Utils::round_js_str(scrollX1, 3, buf) << ", ";
709   s << Utils::round_js_str(scrollX2, 3, buf) << ", ";
710   s << Utils::round_js_str(scrollY1, 3, buf) << ", ";
711   s << Utils::round_js_str(scrollY2, 3, buf) << ");";
712 
713   doJavaScript(s.str());
714 }
715 
setHidden(bool hidden,const WAnimation & animation)716 void WTableView::setHidden(bool hidden, const WAnimation& animation)
717 {
718   bool change = isHidden() != hidden;
719 
720   WAbstractItemView::setHidden(hidden, animation);
721 
722   if (change && !hidden) {
723     /*
724      * IE9 reset the scroll position to (0, 0) when display changes from
725      * 'none' to ''
726      */
727     WApplication *app = WApplication::instance();
728     if (app->environment().ajax() && isRendered()
729 	&& app->environment().agentIsIE()
730 	&& !app->environment().agentIsIElt(9)) {
731       WStringStream s;
732       s << jsRef() << ".wtObj.resetScroll();";
733       doJavaScript(s.str());
734     }
735   }
736 }
737 
resetGeometry()738 void WTableView::resetGeometry()
739 {
740   if (ajaxMode()) {
741     reset();
742   } else { // plain HTML
743     renderedLastRow_
744       = std::min(model()->rowCount(rootIndex()) - 1,
745 		 renderedFirstRow_ + pageSize() - 1);
746     renderedLastColumn_ = columnCount() - 1;
747   }
748 }
749 
canvasHeight()750 double WTableView::canvasHeight() const
751 {
752   return std::max(1.0,
753 		  model()->rowCount(rootIndex()) * rowHeight().toPixels());
754 }
755 
reset()756 void WTableView::reset()
757 {
758   assert(ajaxMode());
759 
760   int total = 0;
761   for (int i = 0; i < columnCount(); ++i)
762     if (!columnInfo(i).hidden)
763       total += (int)columnInfo(i).width.toPixels() + 7;
764 
765   headers_->setWidth(total);
766   canvas_->resize(total, canvasHeight());
767   headerColumnsCanvas_->setHeight(canvasHeight());
768 
769   computeRenderedArea();
770 
771   int renderedRows = lastRow() - firstRow() + 1;
772   for (int i = 0; i < renderedRows; ++i)
773     removeSection(Side::Top);
774 
775   setSpannerCount(Side::Top, 0);
776   setSpannerCount(Side::Left, rowHeaderCount());
777 
778   table_->clear();
779 
780   setSpannerCount(Side::Bottom, model()->rowCount(rootIndex()));
781   setSpannerCount(Side::Right, columnCount() - rowHeaderCount());
782 
783   headerColumnsTable_->clear();
784 
785   for (int i = 0; i < rowHeaderCount(); ++i)
786     createColumnWidget(i);
787 }
788 
defineJavaScript()789 void WTableView::defineJavaScript()
790 {
791   WApplication *app = WApplication::instance();
792 
793   LOAD_JAVASCRIPT(app, "js/WTableView.js", "WTableView", wtjs1);
794 
795   WStringStream s;
796   s << "new " WT_CLASS ".WTableView("
797     << app->javaScriptClass() << ',' << jsRef() << ','
798     << contentsContainer_->jsRef() << ','
799     << viewportTop_ << ','
800     << headerContainer_->jsRef() << ','
801     << headerColumnsContainer_->jsRef() << ",'"
802     << WApplication::instance()->theme()->activeClass()
803     << "');";
804   setJavaScriptMember(" WTableView", s.str());
805 
806   if (!dropEvent_.isConnected())
807     dropEvent_.connect(this, &WTableView::onDropEvent);
808 
809   if (!rowDropEvent_.isConnected())
810     rowDropEvent_.connect(this, &WTableView::onRowDropEvent);
811 
812   if (!scrolled_.isConnected())
813     scrolled_.connect(this, &WTableView::onViewportChange);
814 
815   if (!itemTouchSelectEvent_.isConnected())
816     itemTouchSelectEvent_.connect(this, &WTableView::handleTouchSelected);
817 
818   if (!columnResizeConnected_) {
819     columnResized().connect(this, &WTableView::onColumnResize);
820     columnResizeConnected_ = true;
821   }
822 
823   if (canvas_) {
824     app->addAutoJavaScript
825       ("{var obj = " + jsRef() + ";"
826        "if (obj && obj.wtObj) obj.wtObj.autoJavaScript();}");
827 
828     connectObjJS(canvas_->mouseWentDown(), "mouseDown");
829     connectObjJS(canvas_->mouseWentUp(), "mouseUp");
830 
831 #ifdef WT_CNOR
832     // workaround because cnor is a bit dumb and does not understand that it
833     // can convert EventSignal<TouchEvent>& to EventSignalBase&
834     EventSignalBase& a = canvas_->touchStarted();
835 #endif
836 
837     connectObjJS(canvas_->touchStarted(), "touchStart");
838     connectObjJS(canvas_->touchMoved(), "touchMove");
839     connectObjJS(canvas_->touchEnded(), "touchEnd");
840 
841     /* Two-lines needed for WT_PORT */
842     EventSignalBase& ccScrolled = contentsContainer_->scrolled();
843     connectObjJS(ccScrolled, "onContentsContainerScroll");
844 
845     EventSignalBase& cKeyDown = canvas_->keyWentDown();
846     connectObjJS(cKeyDown, "onkeydown");
847   }
848 }
849 
render(WFlags<RenderFlag> flags)850 void WTableView::render(WFlags<RenderFlag> flags)
851 {
852   if (flags.test(RenderFlag::Full) && !ajaxMode() &&
853       Wt::WApplication::instance()->environment().ajax()) {
854     // Was not rendered when Ajax was enabled, issue #5470
855     plainTable_ = 0;
856     setup();
857   }
858 
859   if (ajaxMode()) {
860     if (flags.test(RenderFlag::Full))
861       defineJavaScript();
862 
863     if (!canvas_->doubleClicked().isConnected()
864 	&& (editTriggers().test(EditTrigger::DoubleClicked) ||
865 	    doubleClicked().isConnected())) {
866       canvas_->doubleClicked().connect
867 	(this, std::bind(&WTableView::handleDblClick, this, false,
868 			 std::placeholders::_1));
869       canvas_->doubleClicked().preventPropagation();
870       headerColumnsCanvas_->doubleClicked().connect
871 	(this, std::bind(&WTableView::handleDblClick, this, true,
872 			 std::placeholders::_1));
873       headerColumnsCanvas_->doubleClicked().preventPropagation();
874 
875       contentsContainer_->doubleClicked().connect
876 	(this, std::bind(&WTableView::handleRootDoubleClick, this, 0,
877 			 std::placeholders::_1));
878       headerColumnsContainer_->doubleClicked().connect
879 	(this, std::bind(&WTableView::handleRootDoubleClick, this, 0,
880 			 std::placeholders::_1));
881     }
882 
883     if (!touchStartConnection_.isConnected()
884         && touchStarted().isConnected()) {
885       touchStartConnection_ = canvas_->touchStarted()
886 	.connect(this, &WTableView::handleTouchStarted);
887     }
888 
889     if (!touchMoveConnection_.isConnected()
890         && touchMoved().isConnected()) {
891       touchMoveConnection_ = canvas_->touchMoved()
892         .connect(this, &WTableView::handleTouchMoved);
893     }
894 
895     if (!touchEndConnection_.isConnected()
896         && touchEnded().isConnected()) {
897       touchEndConnection_ = canvas_->touchEnded()
898 	.connect(this, &WTableView::handleTouchEnded);
899     }
900 
901     WStringStream s;
902     s << jsRef() << ".wtObj.setItemDropsEnabled("
903        << enabledDropLocations_.test(DropLocation::OnItem) << ");";
904     s << jsRef() << ".wtObj.setRowDropsEnabled("
905        << enabledDropLocations_.test(DropLocation::BetweenRows) << ");";
906     doJavaScript(s.str());
907   }
908 
909   if (model())
910     while (renderState_ != RenderState::RenderOk) {
911       RenderState s = renderState_;
912       renderState_ = RenderState::RenderOk;
913 
914       switch (s) {
915       case RenderState::NeedRerender:
916 	resetGeometry();
917 	rerenderHeader();
918 	rerenderData();
919 	break;
920       case RenderState::NeedRerenderHeader:
921 	rerenderHeader();
922 	break;
923       case RenderState::NeedRerenderData:
924 	rerenderData();
925 	break;
926       case RenderState::NeedUpdateModelIndexes:
927 	updateModelIndexes();
928         /* fallthrough */
929       case RenderState::NeedAdjustViewPort:
930 	adjustToViewport();
931 	break;
932       default:
933 	break;
934       }
935     }
936 
937   WAbstractItemView::render(flags);
938 }
939 
updateModelIndexes()940 void WTableView::updateModelIndexes()
941 {
942   int row1 = firstRow();
943   int row2 = lastRow();
944   int col1 = firstColumn();
945   int col2 = lastColumn();
946 
947   for (int i = row1; i <= row2; ++i) {
948     int renderedRow = i - firstRow();
949 
950     int rhc = ajaxMode() ? rowHeaderCount() : 0;
951 
952     for (int j = 0; j < rhc; ++j) {
953       int renderedColumn = j;
954 
955       WModelIndex index = model()->index(i, j, rootIndex());
956       updateModelIndex(index, renderedRow, renderedColumn);
957     }
958 
959     for (int j = col1; j <= col2; ++j) {
960       int renderedColumn = rhc + j - firstColumn();
961 
962       WModelIndex index = model()->index(i, j, rootIndex());
963       updateModelIndex(index, renderedRow, renderedColumn);
964     }
965   }
966 }
967 
updateModelIndex(const WModelIndex & index,int renderedRow,int renderedColumn)968 void WTableView::updateModelIndex(const WModelIndex& index,
969 				  int renderedRow, int renderedColumn)
970 {
971   WContainerWidget *parentWidget;
972   int wIndex;
973 
974   if (ajaxMode()) {
975     parentWidget = columnContainer(renderedColumn);
976     wIndex = renderedRow;
977   } else {
978     parentWidget = plainTable_->elementAt(renderedRow + 1, renderedColumn);
979     wIndex = 0;
980   }
981 
982   auto itemDelegate = this->itemDelegate(index.column());
983   WWidget *widget = parentWidget->widget(wIndex);
984   itemDelegate->updateModelIndex(widget, index);
985 }
986 
rerenderData()987 void WTableView::rerenderData()
988 {
989   if (ajaxMode()) {
990     reset();
991 
992     renderTable(renderedFirstRow_,
993 		renderedLastRow_,
994 		renderedFirstColumn_,
995 		renderedLastColumn_);
996   } else {
997     pageChanged().emit();
998 
999     while (plainTable_->rowCount() > 1)
1000       plainTable_->removeRow(plainTable_->rowCount() - 1);
1001 
1002     for (int i = firstRow(); i <= lastRow(); ++i) {
1003       int renderedRow = i - firstRow();
1004 
1005       std::string cl = WApplication::instance()->theme()->activeClass();
1006 
1007       if (selectionBehavior() == SelectionBehavior::Rows
1008 	  && isSelected(model()->index(i, 0, rootIndex()))) {
1009 	WTableRow *row = plainTable_->rowAt(renderedRow + 1);
1010 	row->setStyleClass(cl);
1011       }
1012 
1013       for (int j = firstColumn(); j <= lastColumn(); ++j) {
1014 	int renderedCol = j - firstColumn();
1015 
1016 	const WModelIndex index = model()->index(i, j, rootIndex());
1017 	std::unique_ptr<WWidget> w = renderWidget(nullptr, index);
1018 	WTableCell *cell = plainTable_->elementAt
1019 	  (renderedRow + 1, renderedCol);
1020 	if (columnInfo(j).hidden)
1021 	  cell->hide();
1022 
1023 	WInteractWidget *wi = dynamic_cast<WInteractWidget *>(w.get());
1024 	if (wi && !isEditing(index))
1025 	  wi->clicked().connect
1026 	    (this, std::bind(&WTableView::handleClick, this, index,
1027 			     std::placeholders::_1));
1028 
1029 	if (selectionBehavior() == SelectionBehavior::Items &&
1030 	    isSelected(index))
1031 	  cell->setStyleClass(cl);
1032 
1033 	cell->addWidget(std::move(w));
1034       }
1035     }
1036   }
1037 }
1038 
rerenderHeader()1039 void WTableView::rerenderHeader()
1040 {
1041   saveExtraHeaderWidgets();
1042 
1043   if (ajaxMode()) {
1044     headers_->clear();
1045     headerColumnsHeaderContainer_->clear();
1046 
1047     for (int i = 0; i < columnCount(); ++i) {
1048       std::unique_ptr<WWidget> w = createHeaderWidget(i);
1049       w->setFloatSide(Side::Left);
1050       w->setWidth(columnInfo(i).width.toPixels() + 1);
1051       if (columnInfo(i).hidden)
1052 	w->hide();
1053       if (i < rowHeaderCount())
1054 	headerColumnsHeaderContainer_->addWidget(std::move(w));
1055       else
1056 	headers_->addWidget(std::move(w));
1057     }
1058   } else { // Plain HTML mode
1059     for (int i = 0; i < columnCount(); ++i) {
1060       std::unique_ptr<WWidget> w = createHeaderWidget(i);
1061       WTableCell *cell = plainTable_->elementAt(0, i);
1062       cell->clear();
1063       cell->setStyleClass("headerrh");
1064       w->setWidth(columnInfo(i).width.toPixels() + 1);
1065       cell->resize(columnInfo(i).width.toPixels() + 1, w->height());
1066       if (columnInfo(i).hidden)
1067 	cell->hide();
1068       cell->addWidget(std::move(w));
1069     }
1070   }
1071 }
1072 
setColumnHidden(int column,bool hidden)1073 void WTableView::setColumnHidden(int column, bool hidden)
1074 {
1075   if (columnInfo(column).hidden != hidden) {
1076     WAbstractItemView::setColumnHidden(column, hidden);
1077 
1078     int delta = static_cast<int>(columnInfo(column).width.toPixels()) + 7;
1079     if (hidden)
1080       delta = -delta;
1081 
1082     if (ajaxMode()) {
1083       headers_->setWidth(headers_->width().toPixels() + delta);
1084       canvas_->setWidth(canvas_->width().toPixels() + delta);
1085 
1086       if (isColumnRendered(column))
1087 	updateColumnOffsets();
1088       else
1089 	if (column < firstColumn())
1090 	  setSpannerCount(Side::Left, spannerCount(Side::Left));
1091 
1092       if (static_cast<unsigned int>(renderState_) >=
1093 	  static_cast<unsigned int>(RenderState::NeedRerenderHeader))
1094 	return;
1095 
1096       WWidget *hc = headerWidget(column, false);
1097       hc->setHidden(hidden);
1098     } else {
1099       if (static_cast<unsigned int>(renderState_) <
1100 	  static_cast<unsigned int>(RenderState::NeedRerenderData)) {
1101 	for (int i = 0; i < plainTable_->rowCount(); ++i)
1102 	  plainTable_->elementAt(i, column)->setHidden(hidden);
1103       }
1104     }
1105   }
1106 }
1107 
setColumnWidth(int column,const WLength & width)1108 void WTableView::setColumnWidth(int column, const WLength& width)
1109 {
1110   WLength rWidth = WLength(round(width.value()), width.unit());
1111   double delta = rWidth.toPixels() - columnInfo(column).width.toPixels();
1112   columnInfo(column).width = rWidth;
1113 
1114   if (columnInfo(column).hidden)
1115     delta = 0;
1116 
1117   if (ajaxMode()) {
1118     headers_->setWidth(headers_->width().toPixels() + delta);
1119     canvas_->setWidth(canvas_->width().toPixels() + delta);
1120 
1121     if (static_cast<unsigned int>(renderState_) >=
1122 	static_cast<unsigned int>(RenderState::NeedRerenderHeader))
1123       return;
1124 
1125     if (isColumnRendered(column))
1126       updateColumnOffsets();
1127     else
1128       if (column < firstColumn())
1129 	setSpannerCount(Side::Left, spannerCount(Side::Left));
1130   }
1131 
1132   if (static_cast<unsigned int>(renderState_) >=
1133       static_cast<unsigned int>(RenderState::NeedRerenderHeader))
1134     return;
1135 
1136   WWidget *hc;
1137   if (column < rowHeaderCount())
1138     hc = headerColumnsHeaderContainer_->widget(column);
1139   else
1140     hc = headers_->widget(column - rowHeaderCount());
1141 
1142   hc->setWidth(0);
1143   hc->setWidth(rWidth.toPixels() + 1);
1144   if (!ajaxMode())
1145     hc->parent()->resize(rWidth.toPixels() + 1, hc->height());
1146 }
1147 
isRowRendered(const int row)1148 bool WTableView::isRowRendered(const int row) const
1149 {
1150   return row >= firstRow() && row <= lastRow();
1151 }
1152 
isColumnRendered(const int column)1153 bool WTableView::isColumnRendered(const int column) const
1154 {
1155   return column >= firstColumn() && column <= lastColumn();
1156 }
1157 
createColumnWidget(int column)1158 WTableView::ColumnWidget *WTableView::createColumnWidget(int column)
1159 {
1160   assert(ajaxMode());
1161 
1162   ColumnWidget *result = new ColumnWidget(column);
1163   std::unique_ptr<ColumnWidget> columnWidget(result);
1164 
1165   WTableView::ColumnInfo& ci = columnInfo(column);
1166   columnWidget->setStyleClass(ci.styleClass());
1167   columnWidget->setPositionScheme(PositionScheme::Absolute);
1168   columnWidget->setOffsets(0, Side::Top | Side::Left);
1169   columnWidget->setOverflow(Overflow::Hidden);
1170   columnWidget->setHeight(table_->height());
1171 
1172   if (column >= rowHeaderCount()) {
1173     if (table_->count() == 0
1174         || column > columnContainer(-1)->column())
1175       table_->addWidget(std::move(columnWidget));
1176     else
1177       table_->insertWidget(0, std::move(columnWidget));
1178   } else
1179     headerColumnsTable_->insertWidget(column, std::move(columnWidget));
1180 
1181   return result;
1182 }
1183 
ColumnWidget(int column)1184 WTableView::ColumnWidget::ColumnWidget(int column)
1185   : column_(column)
1186 { }
1187 
columnContainer(int renderedColumn)1188 WTableView::ColumnWidget *WTableView::columnContainer(int renderedColumn) const
1189 {
1190   assert(ajaxMode());
1191 
1192   if (renderedColumn < headerColumnsTable_->count() && renderedColumn >= 0)
1193     return dynamic_cast<ColumnWidget *>
1194       (headerColumnsTable_->widget(renderedColumn));
1195   else if (table_->count() > 0) {
1196     // -1 is last column
1197     if (renderedColumn < 0)
1198       return dynamic_cast<ColumnWidget *>(table_->widget(table_->count() - 1));
1199     else
1200       return dynamic_cast<ColumnWidget *>
1201         (table_->widget(renderedColumn - headerColumnsTable_->count()));
1202   } else
1203     return nullptr;
1204 }
1205 
updateColumnOffsets()1206 void WTableView::updateColumnOffsets()
1207 {
1208   assert(ajaxMode());
1209 
1210   int totalRendered = 0;
1211   for (int i = 0; i < rowHeaderCount(); ++i) {
1212     ColumnInfo ci = columnInfo(i);
1213 
1214     ColumnWidget *w = columnContainer(i);
1215     w->setOffsets(0, Side::Left);
1216     w->setOffsets(totalRendered, Side::Left);
1217     w->setWidth(0);
1218     w->setWidth(ci.width.toPixels() + 7);
1219 
1220     if (!columnInfo(i).hidden)
1221       totalRendered += (int)ci.width.toPixels() + 7;
1222 
1223     w->setHidden(ci.hidden);
1224   }
1225 
1226   headerColumnsContainer_->setWidth(totalRendered);
1227   headerColumnsCanvas_->setWidth(totalRendered);
1228   headerColumnsTable_->setWidth(totalRendered);
1229   headerColumnsHeaderContainer_->setWidth(totalRendered);
1230 
1231   headerColumnsContainer_->setHidden(totalRendered == 0);
1232   headerColumnsHeaderContainer_->setHidden(totalRendered == 0);
1233 
1234   int fc = firstColumn();
1235   int lc = lastColumn();
1236 
1237   totalRendered = 0;
1238   int total = 0;
1239   for (int i = rowHeaderCount(); i < columnCount(); ++i) {
1240     ColumnInfo ci = columnInfo(i);
1241 
1242     if (i >= fc && i <= lc) {
1243       ColumnWidget *w = columnContainer(rowHeaderCount() + i - fc);
1244 
1245       w->setOffsets(0, Side::Left);
1246       w->setOffsets(totalRendered, Side::Left);
1247       w->setWidth(0);
1248       w->setWidth(ci.width.toPixels() + 7);
1249 
1250       if (!columnInfo(i).hidden)
1251 	totalRendered += (int)ci.width.toPixels() + 7;
1252 
1253       w->setHidden(ci.hidden);
1254     }
1255 
1256     if (!columnInfo(i).hidden)
1257       total += (int)columnInfo(i).width.toPixels() + 7;
1258   }
1259 
1260   double ch = canvasHeight();
1261   canvas_->resize(total, ch);
1262   headerColumnsCanvas_->setHeight(ch);
1263   headers_->setWidth(total);
1264   table_->setWidth(totalRendered);
1265 }
1266 
setRowHeight(const WLength & rowHeight)1267 void WTableView::setRowHeight(const WLength& rowHeight)
1268 {
1269   int renderedRowCount = model() ? lastRow() - firstRow() + 1 : 0;
1270 
1271   // Avoid floating point error which might lead to incorrect viewport calculation
1272   WLength len = WLength((int)rowHeight.toPixels());
1273 
1274   WAbstractItemView::setRowHeight(len);
1275 
1276   if (ajaxMode()) {
1277     canvas_->setLineHeight(len);
1278     headerColumnsCanvas_->setLineHeight(len);
1279 
1280     if (model()) {
1281       double ch = canvasHeight();
1282       canvas_->resize(canvas_->width(), ch);
1283       headerColumnsCanvas_->setHeight(ch);
1284       double th = renderedRowCount * len.toPixels();
1285       setRenderedHeight(th);
1286     }
1287   } else { // Plain HTML mode
1288     plainTable_->setLineHeight(len);
1289     resize(width(), height());
1290   }
1291 
1292   updateTableBackground();
1293 
1294   scheduleRerender(RenderState::NeedRerenderData);
1295 }
1296 
setHeaderHeight(const WLength & height)1297 void WTableView::setHeaderHeight(const WLength& height)
1298 {
1299   WAbstractItemView::setHeaderHeight(height);
1300 
1301   if (!ajaxMode())
1302     resize(this->width(), this->height());
1303 }
1304 
headerWidget(int column,bool contentsOnly)1305 WWidget* WTableView::headerWidget(int column, bool contentsOnly)
1306 {
1307   WWidget *result = nullptr;
1308 
1309   if (ajaxMode()) {
1310     if (headers_) {
1311       if (column < headerColumnsTable_->count()) {
1312 	if (column < headerColumnsHeaderContainer_->count())
1313 	  result = headerColumnsHeaderContainer_->widget(column);
1314       } else if (column - headerColumnsTable_->count() < headers_->count())
1315 	result = headers_->widget(column - headerColumnsTable_->count());
1316     }
1317   } else
1318     if (plainTable_ && column < plainTable_->columnCount())
1319       result = plainTable_->elementAt(0, column)->widget(0);
1320 
1321   if (result && contentsOnly)
1322     return result->find("contents");
1323   else
1324     return result;
1325 }
1326 
shiftModelIndexRows(int start,int count)1327 void WTableView::shiftModelIndexRows(int start, int count)
1328 {
1329   WModelIndexSet& set = selectionModel()->selection_;
1330 
1331   std::vector<WModelIndex> toShift;
1332   std::vector<WModelIndex> toErase;
1333 
1334   for (WModelIndexSet::iterator it
1335 	 = set.lower_bound(model()->index(start, 0, rootIndex()));
1336        it != set.end(); ++it) {
1337 
1338     if (count < 0) {
1339       if ((*it).row() < start - count) {
1340 	toErase.push_back(*it);
1341 	continue;
1342       }
1343     }
1344 
1345     toShift.push_back(*it);
1346     toErase.push_back(*it);
1347   }
1348 
1349   for (unsigned i = 0; i < toErase.size(); ++i)
1350     set.erase(toErase[i]);
1351 
1352   for (unsigned i = 0; i < toShift.size(); ++i) {
1353     WModelIndex newIndex = model()->index(toShift[i].row() + count,
1354 					 toShift[i].column(),
1355 					 toShift[i].parent());
1356     set.insert(newIndex);
1357   }
1358 
1359   shiftEditorRows(rootIndex(), start, count, true);
1360 
1361   if (!toErase.empty())
1362     selectionChanged().emit();
1363 }
1364 
shiftModelIndexColumns(int start,int count)1365 void WTableView::shiftModelIndexColumns(int start, int count)
1366 {
1367   WModelIndexSet& set = selectionModel()->selection_;
1368 
1369   std::vector<WModelIndex> toShift;
1370   std::vector<WModelIndex> toErase;
1371 
1372   for (WModelIndexSet::iterator it = set.begin(); it != set.end(); ++it) {
1373     if (count < 0) {
1374       if ((*it).column() < start - count) {
1375 	toErase.push_back(*it);
1376 	continue;
1377       }
1378     }
1379 
1380     if ((*it).column() >= start) {
1381       toShift.push_back(*it);
1382       toErase.push_back(*it);
1383     }
1384   }
1385 
1386   for (unsigned i = 0; i < toErase.size(); ++i)
1387     set.erase(toErase[i]);
1388 
1389   for (unsigned i = 0; i < toShift.size(); ++i) {
1390     WModelIndex newIndex = model()->index(toShift[i].row(),
1391 					  toShift[i].column() + count,
1392 					  toShift[i].parent());
1393     set.insert(newIndex);
1394   }
1395 
1396   shiftEditorColumns(rootIndex(), start, count, true);
1397 
1398   if (!toShift.empty() || !toErase.empty())
1399     selectionChanged().emit();
1400 }
1401 
modelColumnsInserted(const WModelIndex & parent,int start,int end)1402 void WTableView::modelColumnsInserted(const WModelIndex& parent,
1403 				      int start, int end)
1404 {
1405   if (parent != rootIndex())
1406     return;
1407 
1408   int count = end - start + 1;
1409   int width = 0;
1410 
1411   for (int i = start; i < start + count; ++i) {
1412     columns_.insert(columns_.begin() + i, createColumnInfo(i));
1413     width += (int)columnInfo(i).width.toPixels() + 7;
1414   }
1415 
1416   shiftModelIndexColumns(start, end - start + 1);
1417 
1418   if (ajaxMode())
1419     canvas_->setWidth(canvas_->width().toPixels() + width);
1420 
1421   if (static_cast<unsigned int>(renderState_) <
1422       static_cast<unsigned int>(RenderState::NeedRerenderHeader))
1423     scheduleRerender(RenderState::NeedRerenderHeader);
1424 
1425   if (start > (lastColumn() + 1) ||
1426       renderState_ == RenderState::NeedRerender ||
1427       renderState_ == RenderState::NeedRerenderData)
1428     return;
1429 
1430   scheduleRerender(RenderState::NeedRerenderData);
1431   adjustSize();
1432 }
1433 
modelColumnsAboutToBeRemoved(const WModelIndex & parent,int start,int end)1434 void WTableView::modelColumnsAboutToBeRemoved(const WModelIndex& parent,
1435 					      int start, int end)
1436 {
1437   if (parent != rootIndex())
1438     return;
1439 
1440   for (int r = 0; r < model()->rowCount(); r++) {
1441     for (int c = start; c <= end; c++) {
1442       closeEditor(model()->index(r, c), false);
1443     }
1444   }
1445 
1446   shiftModelIndexColumns(start, -(end - start + 1));
1447 
1448   int count = end - start + 1;
1449   int width = 0;
1450 
1451   for (int i = start; i < start + count; ++i)
1452     if (!columnInfo(i).hidden)
1453       width += (int)columnInfo(i).width.toPixels() + 7;
1454 
1455   WApplication *app = WApplication::instance();
1456   for (int i = start; i< start + count; ++i)
1457     app->styleSheet().removeRule(columns_[i].styleRule.get());
1458   columns_.erase(columns_.begin() + start, columns_.begin() + start + count);
1459 
1460   if (ajaxMode())
1461     canvas_->setWidth(canvas_->width().toPixels() - width);
1462 
1463   if (start <= currentSortColumn_ && currentSortColumn_ <= end)
1464     currentSortColumn_ = -1;
1465 
1466   if (static_cast<unsigned int>(renderState_) <
1467       static_cast<unsigned int>(RenderState::NeedRerenderHeader))
1468     scheduleRerender(RenderState::NeedRerenderHeader);
1469 
1470   if (start > lastColumn() ||
1471       renderState_ == RenderState::NeedRerender ||
1472       renderState_ == RenderState::NeedRerenderData)
1473     return;
1474 
1475   resetGeometry();
1476 
1477   scheduleRerender(RenderState::NeedRerenderData);
1478   adjustSize();
1479 }
1480 
modelRowsInserted(const WModelIndex & parent,int start,int end)1481 void WTableView::modelRowsInserted(const WModelIndex& parent,
1482 				   int start, int end)
1483 {
1484   if (parent != rootIndex())
1485     return;
1486 
1487   int count = end - start + 1;
1488   shiftModelIndexRows(start, count);
1489 
1490   computeRenderedArea();
1491 
1492   if (ajaxMode()) {
1493     canvas_->setHeight(canvasHeight());
1494     headerColumnsCanvas_->setHeight(canvasHeight());
1495     scheduleRerender(RenderState::NeedAdjustViewPort);
1496 
1497     if (start < firstRow())
1498       setSpannerCount(Side::Top, spannerCount(Side::Top) + count);
1499     else if (start <= lastRow())
1500       scheduleRerender(RenderState::NeedRerenderData);
1501   } else if (start <= lastRow())
1502     scheduleRerender(RenderState::NeedRerenderData);
1503 
1504   adjustSize();
1505 }
1506 
1507 namespace {
1508 
calcOverlap(int start1,int end1,int start2,int end2)1509 int calcOverlap(int start1, int end1,
1510 		int start2, int end2)
1511 {
1512   int s = std::max(start1, start2);
1513   int e = std::min(end1, end2);
1514   return std::max(0, e - s);
1515 }
1516 
1517 }
1518 
modelRowsAboutToBeRemoved(const WModelIndex & parent,int start,int end)1519 void WTableView::modelRowsAboutToBeRemoved(const WModelIndex& parent,
1520 					   int start, int end)
1521 {
1522   if (parent != rootIndex())
1523     return;
1524 
1525   for (int c = 0; c < columnCount(); c++) {
1526     for (int r = start; r <= end; r++) {
1527       closeEditor(model()->index(r, c), false);
1528     }
1529   }
1530 
1531   shiftModelIndexRows(start, -(end - start + 1));
1532 
1533   int overlapTop = calcOverlap(0, spannerCount(Side::Top),
1534 			       start, end + 1);
1535   int overlapMiddle = calcOverlap(firstRow(), lastRow() + 1,
1536 				  start, end + 1);
1537 
1538   if (overlapMiddle > 0) {
1539     int first = std::max(0, start - firstRow());
1540 
1541     for (int i = 0; i < renderedColumnsCount(); ++i) {
1542       ColumnWidget *column = columnContainer(i);
1543       for (int j = 0; j < overlapMiddle; ++j)
1544         column->widget(first)->removeFromParent();
1545     }
1546 
1547     setSpannerCount(Side::Bottom, spannerCount(Side::Bottom) + overlapMiddle);
1548   }
1549 
1550   if (overlapTop > 0) {
1551     setSpannerCount(Side::Top, spannerCount(Side::Top) - overlapTop);
1552     setSpannerCount(Side::Bottom, spannerCount(Side::Bottom) + overlapTop);
1553   }
1554 }
1555 
modelRowsRemoved(const WModelIndex & parent,int start,int end)1556 void WTableView::modelRowsRemoved(const WModelIndex& parent,
1557 				  int start, int end)
1558 {
1559   if (parent != rootIndex())
1560     return;
1561 
1562   if (ajaxMode()) {
1563     canvas_->setHeight(canvasHeight());
1564     headerColumnsCanvas_->setHeight(canvasHeight());
1565     scheduleRerender(RenderState::NeedAdjustViewPort);
1566   }
1567 
1568   scheduleRerender(RenderState::NeedUpdateModelIndexes);
1569 
1570   computeRenderedArea();
1571   adjustSize();
1572 }
1573 
modelDataChanged(const WModelIndex & topLeft,const WModelIndex & bottomRight)1574 void WTableView::modelDataChanged(const WModelIndex& topLeft,
1575 				  const WModelIndex& bottomRight)
1576 {
1577   if (topLeft.parent() != rootIndex())
1578     return;
1579 
1580   if (static_cast<unsigned int>(renderState_) <
1581       static_cast<unsigned int>(RenderState::NeedRerenderData)) {
1582     int row1 = std::max(topLeft.row(), firstRow());
1583     int row2 = std::min(bottomRight.row(), lastRow());
1584     int col1 = std::max(topLeft.column(), firstColumn());
1585     int col2 = std::min(bottomRight.column(), lastColumn());
1586 
1587     for (int i = row1; i <= row2; ++i) {
1588       int renderedRow = i - firstRow();
1589 
1590       int rhc = ajaxMode() ? rowHeaderCount() : 0;
1591 
1592       for (int j = topLeft.column(); j < rhc; ++j) {
1593 	int renderedColumn = j;
1594 
1595 	WModelIndex index = model()->index(i, j, rootIndex());
1596 	updateItem(index, renderedRow, renderedColumn);
1597       }
1598 
1599       for (int j = col1; j <= col2; ++j) {
1600 	int renderedColumn = rhc + j - firstColumn();
1601 
1602 	WModelIndex index = model()->index(i, j, rootIndex());
1603 	updateItem(index, renderedRow, renderedColumn);
1604       }
1605     }
1606   }
1607 }
1608 
updateItem(const WModelIndex & index,int renderedRow,int renderedColumn)1609 void WTableView::updateItem(const WModelIndex& index,
1610 			    int renderedRow, int renderedColumn)
1611 {
1612   WContainerWidget *parentWidget;
1613   int wIndex;
1614 
1615   if (ajaxMode()) {
1616     parentWidget = columnContainer(renderedColumn);
1617     wIndex = renderedRow;
1618   } else {
1619     parentWidget = plainTable_->elementAt(renderedRow + 1, renderedColumn);
1620     wIndex = 0;
1621   }
1622 
1623   WWidget *current = parentWidget->widget(wIndex);
1624 
1625   std::unique_ptr<WWidget> wAfter = renderWidget(current, index);
1626   WWidget *w = nullptr;
1627   if (wAfter)
1628     w = wAfter.get();
1629   else
1630     w = current;
1631 
1632   if (wAfter) {
1633     parentWidget->removeWidget(current); // current may or may not be dangling at this point
1634     parentWidget->insertWidget(wIndex, std::move(wAfter));
1635 
1636     if (!ajaxMode() && !isEditing(index)) {
1637       WInteractWidget *wi = dynamic_cast<WInteractWidget *>(w);
1638       if (wi)
1639 	wi->clicked().connect
1640 	  (this, std::bind(&WTableView::handleClick, this, index,
1641 			   std::placeholders::_1));
1642     }
1643   }
1644 }
1645 
onViewportChange(int left,int top,int width,int height)1646 void WTableView::onViewportChange(int left, int top, int width, int height)
1647 {
1648   assert(ajaxMode());
1649 
1650   viewportLeft_ = left;
1651   viewportWidth_ = width;
1652   viewportTop_ = top;
1653   viewportHeight_ = height;
1654 
1655   if (scrollToRow_ != -1) {
1656     WModelIndex index = model()->index(scrollToRow_, 0, rootIndex());
1657     scrollToRow_ = -1;
1658     scrollTo(index, scrollToHint_);
1659   }
1660 
1661   computeRenderedArea();
1662 
1663   scheduleRerender(RenderState::NeedAdjustViewPort);
1664 }
1665 
onColumnResize()1666 void WTableView::onColumnResize()
1667 {
1668   computeRenderedArea();
1669 
1670   scheduleRerender(RenderState::NeedAdjustViewPort);
1671 }
1672 
computeRenderedArea()1673 void WTableView::computeRenderedArea()
1674 {
1675   if (ajaxMode()) {
1676     const int borderRows = 5;
1677 
1678     int modelHeight = 0;
1679     if (model())
1680       modelHeight = model()->rowCount(rootIndex());
1681 
1682     if (viewportHeight_ != -1) {
1683       /* row range */
1684       const int top = std::min(viewportTop_,
1685 			 static_cast<int>(canvas_->height().toPixels()));
1686 
1687       const int height = std::min(viewportHeight_,
1688 			    static_cast<int>(canvas_->height().toPixels()));
1689 
1690       const double renderedRows = height / rowHeight().toPixels();
1691 
1692       const double renderedRowsAbove = preloadMargin(Side::Top).isAuto() ? renderedRows + borderRows :
1693                                                                            preloadMargin(Side::Top).toPixels() / rowHeight().toPixels();
1694 
1695       const double renderedRowsBelow = preloadMargin(Side::Bottom).isAuto() ? renderedRows + borderRows :
1696                                                                               preloadMargin(Side::Bottom).toPixels() / rowHeight().toPixels();
1697 
1698       renderedFirstRow_ = static_cast<int>(std::floor(top / rowHeight().toPixels()));
1699 
1700       renderedLastRow_
1701         = static_cast<int>(std::ceil(std::min(renderedFirstRow_ + renderedRows + renderedRowsBelow, modelHeight - 1.0)));
1702       renderedFirstRow_
1703         = static_cast<int>(std::floor(std::max(renderedFirstRow_ - renderedRowsAbove, 0.0)));
1704     } else {
1705       renderedFirstRow_ = 0;
1706       renderedLastRow_ = modelHeight - 1;
1707     }
1708 
1709     if (renderedFirstRow_ % 2 == 1)
1710       --renderedFirstRow_;
1711 
1712     const int borderColumnPixels = 200;
1713     const double marginLeft = preloadMargin(Side::Left).isAuto() ? viewportWidth_ + borderColumnPixels : preloadMargin(Side::Left).toPixels();
1714     const double marginRight = preloadMargin(Side::Right).isAuto() ? viewportWidth_ + borderColumnPixels : preloadMargin(Side::Right).toPixels();
1715 
1716     /* column range */
1717     int left
1718       = static_cast<int>(std::floor(std::max(0.0, viewportLeft_ - marginLeft)));
1719     int right
1720       = static_cast<int>(std::ceil(std::min(std::max(canvas_->width().toPixels(),
1721                                             viewportWidth_ * 1.0), // When a column was made wider, and the
1722                                                                    // canvas is narrower than the viewport,
1723                                                                    // the size of the canvas will not have
1724                                                                    // been updated yet, so we use the viewport
1725                                                                    // width instead.
1726                                    viewportLeft_ + viewportWidth_ + marginRight)));
1727 
1728     int total = 0;
1729     renderedFirstColumn_ = rowHeaderCount();
1730     renderedLastColumn_ = columnCount() - 1;
1731     for (int i = rowHeaderCount(); i < columnCount(); i++) {
1732       if (columnInfo(i).hidden)
1733 	continue;
1734 
1735       int w = static_cast<int>(columnInfo(i).width.toPixels());
1736 
1737       if (total <= left && left < total + w)
1738 	renderedFirstColumn_ = i;
1739 
1740       if (total <= right && right < total + w) {
1741 	renderedLastColumn_ = i;
1742 	break;
1743       }
1744 
1745       total += w + 7;
1746     }
1747 
1748     assert(renderedLastColumn_ == -1
1749 	   || renderedFirstColumn_ <= renderedLastColumn_);
1750 
1751   } else { // Plain HTML
1752     renderedFirstColumn_ = 0;
1753     if (model()) {
1754       renderedLastColumn_ = columnCount() - 1;
1755 
1756       int cp = std::max(0, std::min(currentPage(), pageCount() - 1));
1757       setCurrentPage(cp);
1758     } else
1759       renderedFirstRow_ = renderedLastRow_ = 0;
1760   }
1761 }
1762 
adjustToViewport()1763 void WTableView::adjustToViewport()
1764 {
1765   assert(ajaxMode());
1766 
1767   computeRenderedArea();
1768 
1769   if (renderedFirstRow_ != firstRow() ||
1770       renderedLastRow_ != lastRow() ||
1771       renderedFirstColumn_ != firstColumn()||
1772       renderedLastColumn_ != lastColumn()) {
1773     renderTable(renderedFirstRow_,
1774 		renderedLastRow_,
1775 		renderedFirstColumn_,
1776 		renderedLastColumn_);
1777   }
1778 }
1779 
setAlternatingRowColors(bool enable)1780 void WTableView::setAlternatingRowColors(bool enable)
1781 {
1782   WAbstractItemView::setAlternatingRowColors(enable);
1783   updateTableBackground();
1784 }
1785 
handleSingleClick(bool headerColumns,const WMouseEvent & event)1786 void WTableView::handleSingleClick(bool headerColumns, const WMouseEvent& event)
1787 {
1788   WModelIndex index = translateModelIndex(headerColumns, event);
1789   handleClick(index, event);
1790 }
1791 
handleDblClick(bool headerColumns,const WMouseEvent & event)1792 void WTableView::handleDblClick(bool headerColumns, const WMouseEvent& event)
1793 {
1794   WModelIndex index = translateModelIndex(headerColumns, event);
1795   handleDoubleClick(index, event);
1796 }
1797 
handleMouseWentDown(bool headerColumns,const WMouseEvent & event)1798 void WTableView::handleMouseWentDown(bool headerColumns,
1799 				     const WMouseEvent& event)
1800 {
1801   WModelIndex index = translateModelIndex(headerColumns, event);
1802   handleMouseDown(index, event);
1803 }
1804 
handleMouseWentUp(bool headerColumns,const WMouseEvent & event)1805 void WTableView::handleMouseWentUp(bool headerColumns, const WMouseEvent& event)
1806 {
1807   WModelIndex index = translateModelIndex(headerColumns, event);
1808   handleMouseUp(index, event);
1809 }
1810 
handleTouchSelected(const WTouchEvent & event)1811 void WTableView::handleTouchSelected(const WTouchEvent& event)
1812 {
1813   std::vector<WModelIndex> indices;
1814   for(std::size_t i = 0; i < event.touches().size(); i++){
1815     indices.push_back(translateModelIndex(event.touches()[i]));
1816   }
1817   handleTouchSelect(indices, event);
1818 }
1819 
handleTouchStarted(const WTouchEvent & event)1820 void WTableView::handleTouchStarted(const WTouchEvent& event)
1821 {
1822   std::vector<WModelIndex> indices;
1823   const std::vector<Touch> &touches = event.changedTouches();
1824   for(std::size_t i = 0; i < touches.size(); i++){
1825     indices.push_back(translateModelIndex(touches[i]));
1826   }
1827   handleTouchStart(indices, event);
1828 }
1829 
handleTouchMoved(const WTouchEvent & event)1830 void WTableView::handleTouchMoved(const WTouchEvent& event)
1831 {
1832   std::vector<WModelIndex> indices;
1833   const std::vector<Touch> &touches = event.changedTouches();
1834   for(std::size_t i = 0; i < touches.size(); i++){
1835     indices.push_back(translateModelIndex(touches[i]));
1836   }
1837   handleTouchMove(indices, event);
1838 }
1839 
handleTouchEnded(const WTouchEvent & event)1840 void WTableView::handleTouchEnded(const WTouchEvent& event)
1841 {
1842   std::vector<WModelIndex> indices;
1843   const std::vector<Touch> &touches = event.changedTouches();
1844   for (std::size_t i = 0; i < touches.size(); i++) {
1845     indices.push_back(translateModelIndex(touches[i]));
1846   }
1847   handleTouchEnd(indices, event);
1848 }
1849 
handleRootSingleClick(int u,const WMouseEvent & event)1850 void WTableView::handleRootSingleClick(int u, const WMouseEvent& event)
1851 {
1852   handleClick(WModelIndex(), event);
1853 }
1854 
handleRootDoubleClick(int u,const WMouseEvent & event)1855 void WTableView::handleRootDoubleClick(int u, const WMouseEvent& event)
1856 {
1857   handleDoubleClick(WModelIndex(), event);
1858 }
1859 
handleRootMouseWentDown(int u,const WMouseEvent & event)1860 void WTableView::handleRootMouseWentDown(int u, const WMouseEvent& event)
1861 {
1862   handleMouseDown(WModelIndex(), event);
1863 }
1864 
handleRootMouseWentUp(int u,const WMouseEvent & event)1865 void WTableView::handleRootMouseWentUp(int u, const WMouseEvent& event)
1866 {
1867   handleMouseUp(WModelIndex(), event);
1868 }
1869 
modelLayoutChanged()1870 void WTableView::modelLayoutChanged()
1871 {
1872   WAbstractItemView::modelLayoutChanged();
1873 
1874   resetGeometry();
1875 }
1876 
modelIndexAt(WWidget * widget)1877 WModelIndex WTableView::modelIndexAt(WWidget *widget) const
1878 {
1879   for (WWidget *w = widget; w; w = w->parent()) {
1880     if (w->hasStyleClass("Wt-tv-c")) {
1881       ColumnWidget *column = dynamic_cast<ColumnWidget *>(w->parent());
1882 
1883       if (!column)
1884 	return WModelIndex();
1885 
1886       int row = firstRow() + column->indexOf(w);
1887       int col = column->column();
1888 
1889       return model()->index(row, col, rootIndex());
1890     }
1891   }
1892 
1893   return WModelIndex();
1894 }
1895 
translateModelIndex(bool headerColumns,const WMouseEvent & event)1896 WModelIndex WTableView::translateModelIndex(bool headerColumns,
1897 					    const WMouseEvent& event)
1898 {
1899   int row = (int)(event.widget().y / rowHeight().toPixels());
1900   int column = -1;
1901 
1902   int total = 0;
1903 
1904   if (headerColumns) {
1905     for (int i = 0; i < rowHeaderCount(); ++i) {
1906       if (!columnInfo(i).hidden)
1907 	total += static_cast<int>(columnInfo(i).width.toPixels()) + 7;
1908 
1909       if (event.widget().x < total) {
1910 	column = i;
1911 	break;
1912       }
1913     }
1914   } else {
1915     for (int i = rowHeaderCount(); i < columnCount(); i++) {
1916       if (!columnInfo(i).hidden)
1917 	total += static_cast<int>(columnInfo(i).width.toPixels()) + 7;
1918 
1919       if (event.widget().x < total) {
1920 	column = i;
1921 	break;
1922       }
1923     }
1924   }
1925 
1926   if (column >= 0 && row >= 0 && row < model()->rowCount(rootIndex()))
1927     return model()->index(row, column, rootIndex());
1928   else
1929     return WModelIndex();
1930 }
1931 
translateModelIndex(const Touch & touch)1932 WModelIndex WTableView::translateModelIndex(const Touch& touch)
1933 {
1934   int row = (int)(touch.widget().y / rowHeight().toPixels());
1935   int column = -1;
1936 
1937   int total = 0;
1938 
1939   for (int i = rowHeaderCount(); i < columnCount(); i++) {
1940     if (!columnInfo(i).hidden)
1941       total += static_cast<int>(columnInfo(i).width.toPixels()) + 7;
1942 
1943     if (touch.widget().x < total) {
1944       column = i;
1945       break;
1946     }
1947   }
1948 
1949   if (column >= 0 && row >= 0 && row < model()->rowCount(rootIndex()))
1950     return model()->index(row, column, rootIndex());
1951   else
1952     return WModelIndex();
1953 }
1954 
internalSelect(const WModelIndex & index,SelectionFlag option)1955 bool WTableView::internalSelect(const WModelIndex& index, SelectionFlag option)
1956 {
1957   if (selectionBehavior() == SelectionBehavior::Rows &&
1958       index.column() != 0)
1959     return internalSelect(model()->index(index.row(), 0, index.parent()),
1960 			  option);
1961 
1962   if (WAbstractItemView::internalSelect(index, option)) {
1963     renderSelected(isSelected(index), index);
1964     return true;
1965   } else
1966     return false;
1967 }
1968 
renderedColumnsCount()1969 int WTableView::renderedColumnsCount() const
1970 {
1971   assert(ajaxMode());
1972 
1973   return headerColumnsTable_->count() + table_->count();
1974 }
1975 
itemWidget(const WModelIndex & index)1976 WWidget *WTableView::itemWidget(const WModelIndex& index) const
1977 {
1978   if ((index.column() < headerColumnsTable_->count() || isColumnRendered(index.column())) &&
1979       isRowRendered(index.row()))
1980   {
1981     int renderedRow = index.row() - firstRow();
1982     int renderedCol;
1983 
1984     if (index.column() < headerColumnsTable_->count())
1985       renderedCol = index.column();
1986     else
1987       renderedCol = headerColumnsTable_->count() + index.column() - firstColumn();
1988 
1989     if (ajaxMode()) {
1990       ColumnWidget *column = columnContainer(renderedCol);
1991       return column->widget(renderedRow);
1992     } else {
1993       return plainTable_->elementAt(renderedRow + 1, renderedCol);
1994     }
1995   } else
1996     return nullptr;
1997 }
1998 
renderSelected(bool selected,const WModelIndex & index)1999 void WTableView::renderSelected(bool selected, const WModelIndex& index)
2000 {
2001   std::string cl = WApplication::instance()->theme()->activeClass();
2002 
2003   if (selectionBehavior() == SelectionBehavior::Rows) {
2004     if (isRowRendered(index.row())) {
2005       int renderedRow = index.row() - firstRow();
2006 
2007       if (ajaxMode()) {
2008 	for (int i = 0; i < renderedColumnsCount(); ++i) {
2009 	  ColumnWidget *column = columnContainer(i);
2010 	  WWidget *w = column->widget(renderedRow);
2011 	  w->toggleStyleClass(cl, selected);
2012 	}
2013       } else {
2014 	WTableRow *row = plainTable_->rowAt(renderedRow + 1);
2015 	row->toggleStyleClass(cl, selected);
2016       }
2017     }
2018   } else {
2019     WWidget *w = itemWidget(index);
2020     if (w)
2021       w->toggleStyleClass(cl, selected);
2022   }
2023 }
2024 
selectRange(const WModelIndex & first,const WModelIndex & last)2025 void WTableView::selectRange(const WModelIndex& first, const WModelIndex& last)
2026 {
2027   for (int c = first.column(); c <= last.column(); ++c)
2028     for (int r = first.row(); r <= last.row(); ++r)
2029       internalSelect(model()->index(r, c, rootIndex()),
2030 		     SelectionFlag::Select);
2031 }
2032 
onDropEvent(int renderedRow,int columnId,std::string sourceId,std::string mimeType,WMouseEvent event)2033 void WTableView::onDropEvent(int renderedRow, int columnId,
2034 			     std::string sourceId, std::string mimeType,
2035 			     WMouseEvent event)
2036 {
2037   WDropEvent e(WApplication::instance()->decodeObject(sourceId), mimeType,
2038 	       event);
2039 
2040   WModelIndex index = model()->index(firstRow() + renderedRow,
2041 				     columnById(columnId), rootIndex());
2042 
2043   dropEvent(e, index);
2044 }
onRowDropEvent(int renderedRow,int columnId,std::string sourceId,std::string mimeType,std::string side,WMouseEvent event)2045 void WTableView::onRowDropEvent(int renderedRow, int columnId,
2046                                 std::string sourceId, std::string mimeType,
2047                                 std::string side, WMouseEvent event)
2048 {
2049   WDropEvent e(WApplication::instance()->decodeObject(sourceId), mimeType,
2050 	       event);
2051 
2052   WModelIndex index;
2053   if (renderedRow >= 0)
2054     index = model()->index(firstRow() + renderedRow,
2055                            columnById(columnId), rootIndex());
2056 
2057   dropEvent(e, index, side == "top" ? Side::Top : Side::Bottom);
2058 }
2059 
setCurrentPage(int page)2060 void WTableView::setCurrentPage(int page)
2061 {
2062   renderedFirstRow_ = page * pageSize();
2063 
2064   if (model())
2065     renderedLastRow_= std::min(renderedFirstRow_ + pageSize() - 1,
2066 			       model()->rowCount(rootIndex()) - 1);
2067   else
2068     renderedLastRow_ = renderedFirstRow_;
2069 
2070   scheduleRerender(RenderState::NeedRerenderData);
2071 }
2072 
currentPage()2073 int WTableView::currentPage() const
2074 {
2075   return renderedFirstRow_ / pageSize();
2076 }
2077 
pageCount()2078 int WTableView::pageCount() const
2079 {
2080   if (model()) {
2081     return (model()->rowCount(rootIndex()) - 1) / pageSize() + 1;
2082   } else
2083     return 1;
2084 }
2085 
pageSize()2086 int WTableView::pageSize() const
2087 {
2088   if (height().isAuto())
2089     return 10000;
2090   else {
2091     const int navigationBarHeight = 25; // set in wt.css
2092 
2093     int pageSize = static_cast<int>
2094       ((height().toPixels() - headerHeight().toPixels() - navigationBarHeight)
2095        / rowHeight().toPixels());
2096     if (pageSize <= 0)
2097       pageSize = 1;
2098 
2099     return pageSize;
2100   }
2101 }
2102 
scrollTo(const WModelIndex & index,ScrollHint hint)2103 void WTableView::scrollTo(const WModelIndex& index, ScrollHint hint)
2104 {
2105   if (index.parent() == rootIndex()) {
2106     if (ajaxMode()) {
2107       int rh = static_cast<int>(rowHeight().toPixels());
2108       int rowY = index.row() * rh;
2109 
2110       if (viewportHeight_ != UNKNOWN_VIEWPORT_HEIGHT) {
2111 	if (hint == ScrollHint::EnsureVisible) {
2112 	  if (viewportTop_ + viewportHeight_ < rowY)
2113 	    hint = ScrollHint::PositionAtTop;
2114 	  else if (rowY < viewportTop_)
2115 	   hint = ScrollHint::PositionAtBottom;
2116 	}
2117 
2118 	switch (hint) {
2119 	case ScrollHint::PositionAtTop:
2120 	  viewportTop_ = rowY; break;
2121 	case ScrollHint::PositionAtBottom:
2122 	  viewportTop_ = rowY - viewportHeight_ + rh; break;
2123 	case ScrollHint::PositionAtCenter:
2124 	  viewportTop_ = rowY - (viewportHeight_ - rh)/2; break;
2125 	default:
2126 	  break;
2127 	}
2128 
2129 	viewportTop_ = std::max(0, viewportTop_);
2130 
2131 	if (hint != ScrollHint::EnsureVisible) {
2132 	  computeRenderedArea();
2133 
2134 	  scheduleRerender(RenderState::NeedAdjustViewPort);
2135 	}
2136       } else {
2137 	scrollToRow_ = index.row();
2138 	scrollToHint_ = hint;
2139       }
2140 
2141       if (isRendered()) {
2142 	WStringStream s;
2143 
2144 	s << jsRef() << ".wtObj.setScrollToPending();"
2145 	  << "setTimeout(function() {"
2146 	  << jsRef() << ".wtObj.scrollTo(-1, "
2147 	  << rowY << "," << (int)hint << "); }, 0);";
2148 
2149 	doJavaScript(s.str());
2150       }
2151     } else
2152       setCurrentPage(index.row() / pageSize());
2153   }
2154 }
2155 
scrollTo(int x,int y)2156 void WTableView::scrollTo(int x, int y)
2157 {
2158   if (ajaxMode()) {
2159     if (isRendered()) {
2160       WStringStream s;
2161 
2162       s << jsRef() << ".wtObj.scrollToPx(" << x << ", "
2163         << y << ");";
2164 
2165       doJavaScript(s.str());
2166     }
2167   }
2168 }
2169 
setOverflow(Overflow overflow,WFlags<Orientation> orientation)2170 void WTableView::setOverflow(Overflow overflow,
2171 			     WFlags< Orientation > orientation)
2172 {
2173   if (contentsContainer_)
2174     contentsContainer_->setOverflow(overflow, orientation);
2175 }
2176 
setPreloadMargin(const WLength & margin,WFlags<Side> side)2177 void WTableView::setPreloadMargin(const WLength &margin, WFlags<Side> side)
2178 {
2179   if (side.test(Side::Top)) {
2180     preloadMargin_[0] = margin;
2181   }
2182   if (side.test(Side::Right)) {
2183     preloadMargin_[1] = margin;
2184   }
2185   if (side.test(Side::Bottom)) {
2186     preloadMargin_[2] = margin;
2187   }
2188   if (side.test(Side::Left)) {
2189     preloadMargin_[3] = margin;
2190   }
2191 
2192   computeRenderedArea();
2193 
2194   scheduleRerender(RenderState::NeedAdjustViewPort);
2195 }
2196 
preloadMargin(Side side)2197 WLength WTableView::preloadMargin(Side side) const
2198 {
2199   switch (side) {
2200   case Side::Top:
2201     return preloadMargin_[0];
2202   case Side::Right:
2203     return preloadMargin_[1];
2204   case Side::Bottom:
2205     return preloadMargin_[2];
2206   case Side::Left:
2207     return preloadMargin_[3];
2208   default:
2209     return WLength();
2210   }
2211 }
2212 
setRowHeaderCount(int count)2213 void WTableView::setRowHeaderCount(int count)
2214 {
2215   WAbstractItemView::setRowHeaderCount(count);
2216 
2217   scheduleRerender(RenderState::NeedRerender);
2218 }
2219 
scrolled()2220 EventSignal<WScrollEvent>& WTableView::scrolled(){
2221   if (wApp->environment().ajax() && contentsContainer_ != nullptr)
2222     return contentsContainer_->scrolled();
2223 
2224   throw WException("Scrolled signal existes only with ajax.");
2225 }
2226 }
2227