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