Coverage Report

Created: 2025-11-11 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wt/src/Wt/WAbstractItemView.C
Line
Count
Source
1
/*
2
 * Copyright (C) 2010 Emweb bv, Herent, Belgium.
3
 *
4
 * See the LICENSE file for terms of use.
5
 */
6
7
#include "Wt/WAbstractItemModel.h"
8
#include "Wt/WAbstractItemView.h"
9
#include "Wt/WAbstractTableModel.h"
10
#include "Wt/WApplication.h"
11
#include "Wt/WContainerWidget.h"
12
#include "Wt/WEnvironment.h"
13
#include "Wt/WEvent.h"
14
#include "Wt/WImage.h"
15
#include "Wt/WItemSelectionModel.h"
16
#include "Wt/WItemDelegate.h"
17
#include "Wt/WLogger.h"
18
#include "Wt/WPushButton.h"
19
#include "Wt/WText.h"
20
#include "Wt/WTheme.h"
21
22
#include "SizeHandle.h"
23
#include "WebUtils.h"
24
25
namespace Wt {
26
27
WT_MAYBE_UNUSED LOGGER("WAbstractItemView");
28
29
class DefaultPagingBar : public WContainerWidget
30
{
31
public:
32
  DefaultPagingBar(WAbstractItemView *view)
33
0
    : view_(view)
34
0
  {
35
0
    view_->addStyleClass("Wt-itemview-paged");
36
0
    setStyleClass("Wt-pagingbar");
37
38
0
    firstButton_ = addWidget(
39
0
          std::make_unique<WPushButton>(
40
0
            tr("Wt.WAbstractItemView.PageBar.First")));
41
0
    firstButton_->clicked().connect(this, &DefaultPagingBar::showFirstPage);
42
43
0
    prevButton_ = addWidget(
44
0
          std::make_unique<WPushButton>(
45
0
            tr("Wt.WAbstractItemView.PageBar.Previous")));
46
0
    prevButton_->clicked().connect(this, &DefaultPagingBar::showPreviousPage);
47
48
0
    current_ = addWidget(std::make_unique<WText>());
49
50
0
    nextButton_ = addWidget(
51
0
          std::make_unique<WPushButton>(
52
0
            tr("Wt.WAbstractItemView.PageBar.Next")));
53
0
    nextButton_->clicked().connect(this, &DefaultPagingBar::showNextPage);
54
55
0
    lastButton_ = addWidget(
56
0
          std::make_unique<WPushButton>(
57
0
            tr("Wt.WAbstractItemView.PageBar.Last")));
58
0
    lastButton_->clicked().connect(this, &DefaultPagingBar::showLastPage);
59
60
0
    view_->pageChanged().connect(this, &DefaultPagingBar::update);
61
62
0
    update();
63
0
  }
64
65
private:
66
  WAbstractItemView *view_;
67
  observing_ptr<WPushButton> prevButton_, nextButton_,
68
    firstButton_, lastButton_;
69
  observing_ptr<WText> current_;
70
71
0
  void update() {
72
0
    firstButton_->setDisabled(view_->currentPage() == 0);
73
0
    prevButton_->setDisabled(view_->currentPage() == 0);
74
75
0
    nextButton_->setDisabled(view_->currentPage() == view_->pageCount() - 1);
76
0
    lastButton_->setDisabled(view_->currentPage() == view_->pageCount() - 1);
77
78
0
    current_->setText(WString::tr("Wt.WAbstractItemView.PageIOfN").
79
0
                      arg(view_->currentPage() + 1).arg(view_->pageCount()));
80
0
  }
81
82
  void showFirstPage()
83
0
  {
84
0
    view_->setCurrentPage(0);
85
0
  }
86
87
  void showLastPage()
88
0
  {
89
0
    view_->setCurrentPage(view_->pageCount() - 1);
90
0
  }
91
92
  void showPreviousPage()
93
0
  {
94
0
    if (view_->currentPage() > 0)
95
0
      view_->setCurrentPage(view_->currentPage() - 1);
96
0
  }
97
98
  void showNextPage()
99
0
  {
100
0
    if (view_->currentPage() < view_->pageCount() - 1)
101
0
      view_->setCurrentPage(view_->currentPage() + 1);
102
0
  }
103
};
104
105
class HeaderProxyModel : public WAbstractTableModel
106
{
107
public:
108
  HeaderProxyModel(const std::shared_ptr<WAbstractItemModel>& model)
109
0
    : model_(model)
110
0
  { }
111
112
  virtual int columnCount(WT_MAYBE_UNUSED const WModelIndex& parent = WModelIndex()) const
113
    override
114
0
  {
115
0
    return model_->columnCount();
116
0
  }
117
118
  virtual int rowCount(WT_MAYBE_UNUSED const WModelIndex& parent = WModelIndex()) const
119
    override
120
0
  {
121
0
    return 1;
122
0
  }
123
124
  virtual cpp17::any data(const WModelIndex& index,
125
                       ItemDataRole role = ItemDataRole::Display) const override
126
0
  {
127
0
    return model_->headerData(index.column(), Orientation::Horizontal, role);
128
0
  }
129
130
  virtual bool setData(const WModelIndex& index, const cpp17::any& value,
131
                       ItemDataRole role = ItemDataRole::Edit) override
132
0
  {
133
0
    return model_->setHeaderData(index.column(), Orientation::Horizontal,
134
0
                                 value, role);
135
0
  }
136
137
  virtual WFlags<ItemFlag> flags(const WModelIndex& index) const override
138
0
  {
139
0
    WFlags<HeaderFlag> headerFlags
140
0
      = model_->headerFlags(index.column(), Orientation::Horizontal);
141
142
0
    WFlags<ItemFlag> result;
143
144
0
    if (headerFlags.test(HeaderFlag::UserCheckable))
145
0
      result |= ItemFlag::UserCheckable;
146
0
    if (headerFlags.test(HeaderFlag::Tristate))
147
0
      result |= ItemFlag::Tristate;
148
0
    if (headerFlags.test(HeaderFlag::XHTMLText))
149
0
      result |= ItemFlag::XHTMLText;
150
151
0
    return result;
152
0
  }
153
154
private:
155
  std::shared_ptr<WAbstractItemModel> model_;
156
};
157
158
WAbstractItemView::ColumnInfo::ColumnInfo(const WAbstractItemView *view,
159
                                          int anId)
160
0
  : id(anId),
161
0
    sortOrder(SortOrder::Ascending),
162
0
    alignment(AlignmentFlag::Left),
163
0
    headerHAlignment(AlignmentFlag::Left),
164
0
    headerVAlignment(view->defaultHeaderVAlignment_),
165
0
    headerWordWrap(view->defaultHeaderWordWrap_),
166
0
    extraHeaderWidget(nullptr),
167
0
    sorting(view->sorting_),
168
0
    hidden(false),
169
0
    resizable(view->columnResize_),
170
0
    itemDelegate_(nullptr)
171
0
{
172
0
  width = WLength(150);
173
174
0
  std::unique_ptr<WCssTemplateRule> r
175
0
    (new WCssTemplateRule("#" + view->id() + " ." + styleClass()));
176
0
  r->templateWidget()->resize(width.toPixels(), WLength::Auto);
177
0
  styleRule = r.get();
178
179
0
  WApplication::instance()->styleSheet().addRule(std::move(r));
180
0
}
181
182
int WAbstractItemView::visibleColumnIndex(int modelColumn) const
183
0
{
184
0
  if (columns_[modelColumn].hidden)
185
0
    return -1;
186
187
0
  int j = 0;
188
189
0
  for (int i = 0; i < modelColumn; ++i)
190
0
    if (!columns_[i].hidden)
191
0
      ++j;
192
193
0
  return j;
194
0
}
195
196
int WAbstractItemView::modelColumnIndex(int visibleColumn) const
197
0
{
198
0
  int j = -1;
199
200
0
  for (int i = 0; i <= visibleColumn; ++i) {
201
0
    ++j;
202
0
    while (static_cast<unsigned>(j) < columns_.size() && columns_[j].hidden)
203
0
      ++j;
204
205
0
    if (static_cast<unsigned>(j) >= columns_.size())
206
0
      return -1;
207
0
  }
208
209
0
  return j;
210
0
}
211
212
std::string WAbstractItemView::ColumnInfo::styleClass() const
213
0
{
214
0
#ifndef WT_TARGET_JAVA
215
0
  char buf[40];
216
0
  buf[0] = 0;
217
0
  std::strcat(buf, "Wt-tv-c");
218
0
  Utils::itoa(id, buf + 7, 10);
219
0
  return buf;
220
#else
221
  return "Wt-tv-c" + std::to_string(id);
222
#endif
223
0
}
224
225
WAbstractItemView::WAbstractItemView()
226
0
  : WCompositeWidget(std::unique_ptr<WWidget>(new WContainerWidget())),
227
0
    renderState_(RenderState::NeedRerender),
228
0
    currentSortColumn_(-1),
229
0
    dragEnabled_(false),
230
0
    selectionModel_(new WItemSelectionModel()),
231
0
    rowHeight_(20),
232
0
    headerLineHeight_(20),
233
0
    selectionMode_(SelectionMode::None),
234
0
    sorting_(true),
235
0
    columnResize_(true),
236
0
    defaultHeaderVAlignment_(AlignmentFlag::Middle),
237
0
    defaultHeaderWordWrap_(true),
238
0
    rowHeaderCount_(0),
239
0
    sortEnabled_(true),
240
0
    columnWidthChanged_(implementation(), "columnResized"),
241
0
    nextColumnId_(1),
242
0
    alternatingRowColors_(false),
243
0
    editTriggers_(EditTrigger::DoubleClicked),
244
0
    editOptions_(EditOption::SingleEditor),
245
0
    touchRegistered_(false)
246
0
{
247
0
  impl_ = dynamic_cast<WContainerWidget *>(implementation());
248
249
0
  auto d = std::make_shared<WItemDelegate>();
250
0
  setItemDelegate(d);
251
0
  setHeaderItemDelegate(d);
252
253
0
  WApplication *app = WApplication::instance();
254
255
0
  SizeHandle::loadJavaScript(app);
256
257
0
  if (!app->environment().ajax())
258
0
    columnResize_ = false;
259
260
0
  bindObjJS(resizeHandleMDownJS_, "resizeHandleMDown");
261
262
0
  headerHeightRule_ = new WCssTemplateRule("#" + id() + " .headerrh");
263
0
  app->styleSheet().addRule(std::unique_ptr<WCssRule>(headerHeightRule_.get()));
264
0
  setHeaderHeight(headerLineHeight_);
265
0
}
266
267
WAbstractItemView::~WAbstractItemView()
268
0
{
269
0
  WApplication *app = WApplication::instance();
270
271
0
  app->styleSheet().removeRule(headerHeightRule_.get());
272
273
0
  for (unsigned i = 0; i < columns_.size(); ++i)
274
0
    app->styleSheet().removeRule(columns_[i].styleRule.get());
275
0
}
276
277
void WAbstractItemView
278
::setModel(const std::shared_ptr<WAbstractItemModel>& model)
279
0
{
280
0
  if (!columnWidthChanged_.isConnected())
281
0
    columnWidthChanged_.connect(this, &WAbstractItemView::updateColumnWidth);
282
283
0
  bool isReset = model_.get();
284
285
  /* disconnect slots from previous model */
286
0
  for (unsigned i = 0; i < modelConnections_.size(); ++i)
287
0
    modelConnections_[i].disconnect();
288
0
  modelConnections_.clear();
289
290
0
  model_ = model;
291
0
  headerModel_.reset(new HeaderProxyModel(model_));
292
293
0
  auto oldSelectionModel = std::move(selectionModel_);
294
0
  selectionModel_.reset(new WItemSelectionModel(model));
295
0
  selectionModel_->setSelectionBehavior(oldSelectionModel->selectionBehavior());
296
297
0
  delayedClearAndSelectIndex_ = WModelIndex();
298
299
0
  editedItems_.clear();
300
301
0
  if (!isReset)
302
0
    initDragDrop();
303
304
0
  configureModelDragDrop();
305
306
0
  setRootIndex(WModelIndex());
307
308
0
  setHeaderHeight(headerLineHeight_);
309
0
}
310
311
void WAbstractItemView::setRootIndex(const WModelIndex& rootIndex)
312
0
{
313
0
  rootIndex_ = rootIndex;
314
315
0
  scheduleRerender(RenderState::NeedRerender);
316
317
0
  unsigned modelColumnCount = model_->columnCount(rootIndex_);
318
319
0
  WApplication *app = WApplication::instance();
320
321
0
  while (columns_.size() > modelColumnCount) {
322
0
    int i = columns_.size() - 1;
323
0
    app->styleSheet().removeRule(columns_[i].styleRule.get());
324
0
    columns_.erase(columns_.begin() + i);
325
0
  }
326
327
0
  while (columns_.size() < modelColumnCount)
328
0
    columns_.push_back(createColumnInfo(columns_.size()));
329
0
}
330
331
void WAbstractItemView::setRowHeight(const WLength& rowHeight)
332
0
{
333
0
  rowHeight_ = rowHeight;
334
0
}
335
336
void WAbstractItemView::setRowHeaderCount(int count)
337
0
{
338
0
  rowHeaderCount_ = count;
339
0
}
340
341
WLength WAbstractItemView::columnWidth(int column) const
342
0
{
343
0
  return columnInfo(column).width;
344
0
}
345
346
void WAbstractItemView::updateColumnWidth(int columnId, int width)
347
0
{
348
0
  int column = columnById(columnId);
349
350
0
  if (column >= 0) {
351
0
    columnInfo(column).width = width;
352
0
    columnResized_.emit(column, columnInfo(column).width);
353
354
0
    WWidget *w = headerWidget(column, 0);
355
0
    if (w)
356
0
      w->scheduleRender(RepaintFlag::SizeAffected); // for layout
357
0
  }
358
0
}
359
360
void WAbstractItemView::setColumnHidden(int column, bool hidden)
361
0
{
362
0
  columnInfo(column).hidden = hidden;
363
0
}
364
365
bool WAbstractItemView::isColumnHidden(int column) const
366
0
{
367
0
  return columnInfo(column).hidden;
368
0
}
369
370
void WAbstractItemView::hideColumn(int column)
371
0
{
372
0
  setColumnHidden(column, true);
373
0
}
374
375
void WAbstractItemView::showColumn(int column)
376
0
{
377
0
  setColumnHidden(column, false);
378
0
}
379
380
void WAbstractItemView::initDragDrop()
381
0
{
382
  /* item drag & drop */
383
0
  addCssRule
384
0
    ("#" + id() + "dw",
385
0
     "width: 32px; height: 32px;"
386
0
     "background: url(" + WApplication::resourcesUrl() + "items-not-ok.gif);");
387
388
0
  addCssRule
389
0
    ("#" + id() + "dw.Wt-valid-drop",
390
0
     "width: 32px; height: 32px;"
391
0
     "background: url(" + WApplication::resourcesUrl() + "items-ok.gif);");
392
393
0
  selectionChanged_.connect(this, &WAbstractItemView::checkDragSelection);
394
0
}
395
396
void WAbstractItemView::setColumnResizeEnabled(bool enabled)
397
0
{
398
 
399
0
  columnResize_ = enabled;
400
0
  for (unsigned int i = 0; i < static_cast<unsigned int>(columns_.size()); i++){
401
0
    columnInfo(i).resizable = enabled;
402
0
  }
403
0
  scheduleRerender(RenderState::NeedRerenderHeader);
404
0
}
405
406
void WAbstractItemView::setColumnResizeEnabled(bool enabled, int column)
407
0
{
408
0
  if (enabled != columnInfo(column).resizable) {
409
0
    columnInfo(column).resizable = enabled;
410
0
    scheduleRerender(RenderState::NeedRerenderHeader);
411
0
  }
412
0
}
413
414
void WAbstractItemView::setColumnAlignment(int column, AlignmentFlag alignment)
415
0
{
416
0
  columnInfo(column).alignment = alignment;
417
418
0
  WApplication *app = WApplication::instance();
419
420
0
  const char *align = nullptr;
421
0
  switch (alignment) {
422
0
  case AlignmentFlag::Left:
423
0
    align = app->layoutDirection() == LayoutDirection::LeftToRight
424
0
      ? "left" : "right";
425
0
    break;
426
0
  case AlignmentFlag::Center: align = "center"; break;
427
0
  case AlignmentFlag::Right:
428
0
    align = app->layoutDirection() == LayoutDirection::LeftToRight
429
0
      ? "right" : "left";
430
0
    break;
431
0
  case AlignmentFlag::Justify: align = "justify"; break;
432
0
  default:
433
0
    break;
434
0
  }
435
436
0
  if (align) {
437
0
    WWidget *w = columnInfo(column).styleRule->templateWidget();
438
0
    w->setAttributeValue("style", std::string("text-align: ") + align);
439
0
  }
440
0
}
441
442
AlignmentFlag WAbstractItemView::columnAlignment(int column) const
443
0
{
444
0
  return columnInfo(column).alignment;
445
0
}
446
447
void WAbstractItemView::setHeaderAlignment(int column,
448
                                           WFlags<AlignmentFlag> alignment)
449
0
{
450
0
  columnInfo(column).headerHAlignment = alignment & AlignHorizontalMask;
451
452
0
  if (!(alignment & AlignVerticalMask).empty())
453
0
    columnInfo(column).headerVAlignment = alignment & AlignVerticalMask;
454
455
0
  if (columnInfo(column).hidden ||
456
0
      static_cast<unsigned int>(renderState_) >=
457
0
      static_cast<unsigned int>(RenderState::NeedRerenderHeader))
458
0
    return;
459
460
0
  WContainerWidget *wc = dynamic_cast<WContainerWidget *>(headerWidget(column));
461
462
0
  wc->setContentAlignment(alignment);
463
464
0
  if (columnInfo(column).headerVAlignment == AlignmentFlag::Middle)
465
0
    wc->setLineHeight(headerLineHeight_);
466
0
  else
467
0
    wc->setLineHeight(WLength::Auto);
468
0
}
469
470
void WAbstractItemView::setHeaderWordWrap(int column, bool enabled)
471
0
{
472
0
  columnInfo(column).headerWordWrap = enabled;
473
474
0
  if (columnInfo(column).hidden ||
475
0
      static_cast<unsigned int>(renderState_) >=
476
0
      static_cast<unsigned int>(RenderState::NeedRerenderHeader))
477
0
    return;
478
479
0
  if (columnInfo(column).headerVAlignment == AlignmentFlag::Top) {
480
0
    WContainerWidget *wc
481
0
      = dynamic_cast<WContainerWidget *>(headerWidget(column));
482
0
    wc->toggleStyleClass("Wt-wwrap", enabled);
483
0
  }
484
0
}
485
486
AlignmentFlag WAbstractItemView::horizontalHeaderAlignment(int column) const
487
0
{
488
0
  return columnInfo(column).headerHAlignment;
489
0
}
490
491
AlignmentFlag WAbstractItemView::verticalHeaderAlignment(int column) const
492
0
{
493
0
  return columnInfo(column).headerVAlignment;
494
0
}
495
496
void WAbstractItemView::setAlternatingRowColors(bool enable)
497
0
{
498
0
  alternatingRowColors_ = enable;
499
0
}
500
501
void WAbstractItemView::setSelectionMode(SelectionMode mode)
502
0
{
503
0
  if (mode != selectionMode_) {
504
0
    clearSelection();
505
0
    selectionMode_ = mode;
506
0
  }
507
0
}
508
509
void WAbstractItemView::setSelectionBehavior(SelectionBehavior behavior)
510
0
{
511
0
  if (behavior != selectionBehavior()) {
512
0
    clearSelection();
513
0
    selectionModel_->setSelectionBehavior(behavior);
514
0
  }
515
0
}
516
517
SelectionBehavior WAbstractItemView::selectionBehavior() const
518
0
{
519
0
  return selectionModel_->selectionBehavior();
520
0
}
521
522
void WAbstractItemView
523
::setItemDelegate(const std::shared_ptr<WAbstractItemDelegate>& delegate)
524
0
{
525
0
  itemDelegate_ = delegate;
526
0
  itemDelegate_->closeEditor()
527
0
    .connect(this, &WAbstractItemView::closeEditorWidget);
528
0
}
529
530
void WAbstractItemView
531
::setItemDelegateForColumn(int column,
532
                        const std::shared_ptr<WAbstractItemDelegate>& delegate)
533
0
{
534
0
  columnInfo(column).itemDelegate_ = delegate;
535
0
  delegate->closeEditor()
536
0
    .connect(this, &WAbstractItemView::closeEditorWidget);
537
0
}
538
539
std::shared_ptr<WAbstractItemDelegate> WAbstractItemView
540
::itemDelegateForColumn(int column)
541
  const
542
0
{
543
0
  return columnInfo(column).itemDelegate_;
544
0
}
545
546
std::shared_ptr<WAbstractItemDelegate> WAbstractItemView
547
::itemDelegate(const WModelIndex& index)
548
  const
549
0
{
550
0
  return itemDelegate(index.column());
551
0
}
552
553
std::shared_ptr<WAbstractItemDelegate> WAbstractItemView
554
::itemDelegate(int column) const
555
0
{
556
0
  auto result = itemDelegateForColumn(column);
557
558
0
  return result ? result : itemDelegate_;
559
0
}
560
561
void WAbstractItemView::
562
setHeaderItemDelegate(const std::shared_ptr<WAbstractItemDelegate>& delegate)
563
0
{
564
0
  headerItemDelegate_ = delegate;
565
0
}
566
567
std::shared_ptr<WAbstractItemDelegate> WAbstractItemView::headerItemDelegate()
568
  const
569
0
{
570
0
  return headerItemDelegate_;
571
0
}
572
573
std::string repeat(const std::string& s, int times)
574
0
{
575
0
  std::string result;
576
0
  for (int i = 0; i < times; ++i) {
577
0
    result += s;
578
0
  }
579
0
  return result;
580
0
}
581
582
void WAbstractItemView::dropEvent(const WDropEvent& e, const WModelIndex& index)
583
0
{
584
  /*
585
   * Here, we only handle standard drag&drop actions between abstract
586
   * item models.
587
   */
588
0
  if (enabledDropLocations_.test(DropLocation::OnItem)) {
589
0
    std::vector<std::string> acceptMimeTypes = model_->acceptDropMimeTypes();
590
591
0
    for (std::size_t i = 0; i < acceptMimeTypes.size(); ++i) {
592
0
      if (acceptMimeTypes[i] == e.mimeType()) {
593
        // we define internal by sharing the same selection model...
594
        // currently selection models cannot be shared
595
0
        bool internal = e.source() == selectionModel_.get();
596
597
0
        DropAction action = internal ?
598
0
          DropAction::Move : DropAction::Copy;
599
600
0
        model_->dropEvent(e, action,
601
0
                          index.row(), index.column(), index.parent());
602
603
0
        setSelectedIndexes(WModelIndexSet());
604
0
        return;
605
0
      }
606
0
    }
607
0
  }
608
609
0
  WCompositeWidget::dropEvent(e);
610
0
}
611
612
void WAbstractItemView::dropEvent(const WDropEvent& e, const WModelIndex& index, Side side)
613
0
{
614
0
  if (enabledDropLocations_.test(DropLocation::BetweenRows)) {
615
0
    std::vector<std::string> acceptMimeTypes = model_->acceptDropMimeTypes();
616
617
0
    for (std::size_t i = 0; i < acceptMimeTypes.size(); ++i) {
618
0
      if (acceptMimeTypes[i] == e.mimeType()) {
619
0
        model_->dropEvent(e, DropAction::Move, index, side);
620
621
0
        setSelectedIndexes(WModelIndexSet());
622
0
        return;
623
0
      }
624
0
    }
625
0
  }
626
627
0
  WCompositeWidget::dropEvent(e);
628
0
}
629
630
void WAbstractItemView::configureModelDragDrop()
631
0
{
632
0
  if (!model_)
633
0
    return;
634
635
0
  if (dragEnabled_) {
636
0
    setAttributeValue
637
0
      ("dsid",
638
0
       WApplication::instance()->encodeObject(selectionModel_.get()));
639
640
0
    checkDragSelection();
641
0
  }
642
643
0
  std::vector<std::string> acceptMimeTypes = model_->acceptDropMimeTypes();
644
645
0
  for (unsigned i = 0; i < acceptMimeTypes.size(); ++i)
646
0
    if (!enabledDropLocations_.empty())
647
0
      acceptDrops(acceptMimeTypes[i], "Wt-drop-site");
648
0
    else
649
0
      stopAcceptDrops(acceptMimeTypes[i]);
650
0
}
651
652
653
void WAbstractItemView::setDropsEnabled(bool enable)
654
0
{
655
0
  if (enable)
656
0
    setEnabledDropLocations(DropLocation::OnItem);
657
0
  else
658
0
    setEnabledDropLocations(None);
659
0
}
660
661
0
void WAbstractItemView::setEnabledDropLocations(WFlags<DropLocation> dropLocations) {
662
0
  if (enabledDropLocations_ == dropLocations)
663
0
    return;
664
665
0
  enabledDropLocations_ = dropLocations;
666
0
  configureModelDragDrop();
667
668
0
  scheduleRender();
669
0
}
670
671
void WAbstractItemView::checkDragSelection()
672
0
{
673
  /*
674
   * Check whether the current selection can be drag and dropped
675
   */
676
0
  computedDragMimeType_ = selectionModel_->mimeType();
677
678
0
  setAttributeValue("dmt", computedDragMimeType_);
679
680
0
  if (!computedDragMimeType_.empty())
681
0
    setAttributeValue("drag", "true");
682
0
  else
683
0
    setAttributeValue("drag", "false");
684
0
}
685
686
WText *WAbstractItemView::headerSortIconWidget(int column)
687
0
{
688
0
  if (!columnInfo(column).sorting)
689
0
    return nullptr;
690
691
0
  WWidget *hw = headerWidget(column);
692
0
  if (hw)
693
0
    return dynamic_cast<WText *>(hw->find("sort"));
694
0
  else
695
0
    return nullptr;
696
0
}
697
698
std::unique_ptr<WWidget> WAbstractItemView::createExtraHeaderWidget(WT_MAYBE_UNUSED int column)
699
0
{
700
0
  return std::unique_ptr<WWidget>();
701
0
}
702
703
WWidget *WAbstractItemView::extraHeaderWidget(int column)
704
0
{
705
0
  return columnInfo(column).extraHeaderWidget.get();
706
0
}
707
708
void WAbstractItemView::handleHeaderClicked(int columnid, WMouseEvent event)
709
0
{
710
0
  int column = columnById(columnid);
711
0
  ColumnInfo& info = columnInfo(column);
712
713
0
  if (sortEnabled_ && info.sorting)
714
0
    toggleSortColumn(columnid);
715
716
0
  headerClicked_.emit(column, event);
717
0
}
718
719
void WAbstractItemView::handleHeaderDblClicked(int columnid, WMouseEvent event)
720
0
{
721
0
  headerDblClicked_.emit(columnById(columnid), event);
722
0
}
723
724
void WAbstractItemView::handleHeaderMouseDown(int columnid, WMouseEvent event)
725
0
{
726
0
  headerMouseWentDown_.emit(columnById(columnid), event);
727
0
}
728
729
void WAbstractItemView::handleHeaderMouseUp(int columnid, WMouseEvent event)
730
0
{
731
0
  headerMouseWentUp_.emit(columnById(columnid), event);
732
0
}
733
734
void WAbstractItemView::toggleSortColumn(int columnid)
735
0
{
736
0
  int column = columnById(columnid);
737
738
0
  if (column != currentSortColumn_)
739
0
    sortByColumn(column, columnInfo(column).sortOrder);
740
0
  else
741
0
    sortByColumn(column,
742
0
                 columnInfo(column).sortOrder == SortOrder::Ascending
743
0
                 ? SortOrder::Descending : SortOrder::Ascending);
744
0
}
745
746
int WAbstractItemView::sortColumn() const
747
0
{
748
0
  return currentSortColumn_;
749
0
}
750
751
SortOrder WAbstractItemView::sortOrder() const
752
0
{
753
0
  if (currentSortColumn_ >= 0
754
0
      && currentSortColumn_ < static_cast<int>(columns_.size()))
755
0
    return columns_[currentSortColumn_].sortOrder;
756
0
  else
757
0
    return SortOrder::Ascending;
758
0
}
759
760
int WAbstractItemView::columnById(int columnid) const
761
0
{
762
0
  for (unsigned i = 0; i < columns_.size(); ++i)
763
0
    if (columnInfo(i).id == columnid)
764
0
      return i;
765
766
0
  return 0;
767
0
}
768
769
int WAbstractItemView::columnCount() const
770
0
{
771
0
  return columns_.size();
772
0
}
773
774
int WAbstractItemView::visibleColumnCount() const
775
0
{
776
0
  int result = 0;
777
778
0
  for (unsigned i = 0; i < columns_.size(); ++i)
779
0
    if (!columns_[i].hidden)
780
0
      ++result;
781
782
0
  return result;
783
0
}
784
785
WAbstractItemView::ColumnInfo WAbstractItemView::createColumnInfo(WT_MAYBE_UNUSED int column) const
786
0
{
787
0
  return ColumnInfo(this, nextColumnId_++);
788
0
}
789
790
WAbstractItemView::ColumnInfo& WAbstractItemView::columnInfo(int column) const
791
0
{
792
0
  while (column >= (int)columns_.size())
793
0
    columns_.push_back(createColumnInfo(columns_.size()));
794
795
0
  return columns_[column];
796
0
}
797
798
void WAbstractItemView::sortByColumn(int column, SortOrder order)
799
0
{
800
0
  if (currentSortColumn_ != -1) {
801
0
    WText* t = headerSortIconWidget(currentSortColumn_);
802
0
    if (t)
803
0
      t->setStyleClass("Wt-tv-sh Wt-tv-sh-none");
804
0
  }
805
806
0
  currentSortColumn_ = column;
807
0
  columnInfo(column).sortOrder = order;
808
809
0
  if (renderState_ != RenderState::NeedRerender) {
810
0
    WText* t = headerSortIconWidget(currentSortColumn_);
811
0
    if (t)
812
0
      t->setStyleClass(order == SortOrder::Ascending
813
0
                       ? "Wt-tv-sh Wt-tv-sh-up" : "Wt-tv-sh Wt-tv-sh-down");
814
0
  }
815
816
0
  model_->sort(column, order);
817
0
}
818
819
void WAbstractItemView::setSortingEnabled(bool enabled)
820
0
{
821
0
  sorting_ = enabled;
822
0
  for (unsigned i = 0; i < columns_.size(); ++i)
823
0
    columnInfo(i).sorting = enabled;
824
825
0
  scheduleRerender(RenderState::NeedRerenderHeader);
826
0
}
827
828
void WAbstractItemView::setSortingEnabled(int column, bool enabled)
829
0
{
830
0
  columnInfo(column).sorting = enabled;
831
832
0
  scheduleRerender(RenderState::NeedRerenderHeader);
833
0
}
834
835
bool WAbstractItemView::isSortingEnabled(int column) const
836
0
{
837
0
  return columnInfo(column).sorting;
838
0
}
839
840
void WAbstractItemView::modelReset()
841
0
{
842
0
  setModel(model_);
843
0
}
844
845
bool WAbstractItemView::internalSelect(const WModelIndex& index,
846
                                       SelectionFlag option)
847
0
{
848
0
  if (!(index.flags() & ItemFlag::Selectable) ||
849
0
      selectionMode() == SelectionMode::None)
850
0
    return false;
851
852
0
  if (option == SelectionFlag::ToggleSelect)
853
0
    option = isSelected(index)
854
0
      ? SelectionFlag::Deselect : SelectionFlag::Select;
855
856
0
  if (selectionMode() == SelectionMode::Single &&
857
0
      option == SelectionFlag::Select)
858
0
    option = SelectionFlag::ClearAndSelect;
859
860
0
  if ((option == SelectionFlag::ClearAndSelect ||
861
0
       option == SelectionFlag::Select) &&
862
0
      selectionModel()->selection_.size() == 1 &&
863
0
      isSelected(index))
864
0
    return false;
865
0
  else if (option == SelectionFlag::Deselect && !isSelected(index))
866
0
    return false;
867
868
0
  if (option == SelectionFlag::ClearAndSelect) {
869
0
    clearSelection();
870
0
    option = SelectionFlag::Select;
871
0
  }
872
873
  /*
874
   * now option is either SelectionFlag::Select or SelectionFlag::Deselect and we only need to do
875
   * exactly that one thing
876
   */
877
0
  if (option == SelectionFlag::Select)
878
0
    selectionModel()->selection_.insert(index);
879
0
  else
880
0
    selectionModel()->selection_.erase(index);
881
882
0
  return true;
883
0
}
884
885
void WAbstractItemView::clearSelection()
886
0
{
887
0
  WModelIndexSet& nodes = selectionModel_->selection_;
888
889
0
  while (!nodes.empty()) {
890
0
    WModelIndex i = *nodes.begin();
891
0
    internalSelect(i, SelectionFlag::Deselect);
892
0
  }
893
0
}
894
895
void WAbstractItemView::setSelectedIndexes(const WModelIndexSet& indexes)
896
0
{
897
0
  if (indexes.empty() && selectionModel_->selection_.empty())
898
0
    return;
899
900
0
  clearSelection();
901
902
0
  for (WModelIndexSet::const_iterator i = indexes.begin();
903
0
       i != indexes.end(); ++i)
904
0
    internalSelect(*i, SelectionFlag::Select);
905
906
0
  selectionChanged_.emit();
907
0
}
908
909
void WAbstractItemView::extendSelection(const WModelIndex& index)
910
0
{
911
0
  if (selectionModel_->selection_.empty())
912
0
    internalSelect(index, SelectionFlag::Select);
913
0
  else {
914
0
    if (selectionBehavior() == SelectionBehavior::Rows &&
915
0
        index.column() != 0) {
916
0
      extendSelection(model_->index(index.row(), 0, index.parent()));
917
0
      return;
918
0
    }
919
0
  }
920
921
  /*
922
     * Expand current selection. If index is within or below the
923
     * current selection, we select from the top item to index. If index
924
     * is above the current selection, select everything from the
925
     * bottom item to index.
926
     *
927
     * For a WTreeView, only indexes with expanded ancestors can be
928
     * part of the selection: this is asserted when collapsing a index.
929
     */
930
0
  WModelIndex top = Utils::first(selectionModel_->selection_);
931
0
  if (top < index) {
932
0
    clearSelection();
933
0
    selectRange(top, index);
934
0
  } else {
935
0
    WModelIndex bottom = Utils::last(selectionModel_->selection_);
936
0
    clearSelection();
937
0
    selectRange(index, bottom);
938
0
  }
939
0
  selectionChanged_.emit();
940
0
}
941
942
943
void WAbstractItemView::extendSelection(const std::vector<WModelIndex>& indices)
944
0
{
945
0
  const WModelIndex &firstIndex = indices[0];
946
0
  const WModelIndex &secondIndex = indices[indices.size()-1];
947
0
  if (indices.size() > 1) {
948
0
    if(firstIndex.row() > secondIndex.row())
949
0
      selectRange(secondIndex, firstIndex);
950
0
    else
951
0
      selectRange(firstIndex, secondIndex);
952
0
  }
953
954
0
  selectionChanged_.emit();
955
0
}
956
957
bool WAbstractItemView::isSelected(const WModelIndex& index) const
958
0
{
959
0
  return selectionModel_->isSelected(index);
960
0
}
961
962
void WAbstractItemView::select(const WModelIndex& index, SelectionFlag option)
963
0
{
964
0
  if (internalSelect(index, option))
965
0
    selectionChanged_.emit();
966
0
}
967
968
void WAbstractItemView::selectionHandleClick(const WModelIndex& index,
969
                                             WFlags<KeyboardModifier> modifiers)
970
0
{
971
0
  if (selectionMode_ == SelectionMode::None)
972
0
    return;
973
974
0
  if (selectionMode_ == SelectionMode::Extended) {
975
0
    if (modifiers.test(KeyboardModifier::Shift))
976
0
      extendSelection(index);
977
0
    else {
978
0
      if (!(modifiers & (KeyboardModifier::Control |
979
0
                         KeyboardModifier::Meta))) {
980
0
        if (!dragEnabled_)
981
0
          select(index, SelectionFlag::ClearAndSelect);
982
0
        else {
983
0
          if (!isSelected(index))
984
0
            select(index, SelectionFlag::ClearAndSelect);
985
0
          else
986
0
            delayedClearAndSelectIndex_ = index;
987
0
        }
988
0
      } else
989
0
        select(index, SelectionFlag::ToggleSelect);
990
0
    }
991
0
  } else {
992
0
    if (!(modifiers & (KeyboardModifier::Control |
993
0
                      KeyboardModifier::Meta)).empty() &&
994
0
        isSelected(index)) {
995
0
      clearSelection();
996
0
      selectionChanged_.emit();
997
0
    } else
998
0
      select(index, SelectionFlag::Select);
999
0
  }
1000
0
}
1001
1002
void WAbstractItemView::selectionHandleTouch(const std::vector<WModelIndex>& indices,
1003
                                             const WTouchEvent& event)
1004
0
{
1005
0
  if (selectionMode_ == SelectionMode::None)
1006
0
    return;
1007
1008
0
  const WModelIndex &index = indices[0];
1009
1010
0
  if (selectionMode_ == SelectionMode::Extended) {
1011
0
    if (event.touches().size() > 1)
1012
0
      extendSelection(indices);
1013
0
    else {
1014
0
      select(index, SelectionFlag::ToggleSelect);
1015
0
    }
1016
0
  } else {
1017
0
    if (isSelected(index)) {
1018
0
      clearSelection();
1019
0
      selectionChanged_.emit();
1020
0
    } else
1021
0
      select(index, SelectionFlag::ClearAndSelect);
1022
0
  }
1023
0
}
1024
1025
WModelIndexSet WAbstractItemView::selectedIndexes() const
1026
0
{
1027
0
  return selectionModel_->selection_;
1028
0
}
1029
1030
void WAbstractItemView::scheduleRerender(RenderState what)
1031
0
{
1032
0
  if ((what == RenderState::NeedRerenderHeader &&
1033
0
       renderState_ == RenderState::NeedRerenderData)
1034
0
      || (what == RenderState::NeedRerenderData &&
1035
0
          renderState_ == RenderState::NeedRerenderHeader))
1036
0
    renderState_ = RenderState::NeedRerender;
1037
0
  else
1038
0
    renderState_ = std::max(what, renderState_);
1039
1040
0
  if (!isRendered())
1041
0
    return;
1042
1043
0
  scheduleRender();
1044
0
}
1045
1046
void WAbstractItemView::modelHeaderDataChanged(Orientation orientation,
1047
                                               int start, int end)
1048
0
{
1049
0
  if (static_cast<unsigned int>(renderState_) <
1050
0
      static_cast<unsigned int>(RenderState::NeedRerenderHeader)) {
1051
0
    if (orientation == Orientation::Horizontal) {
1052
0
      for (int i = start; i <= end; ++i) {
1053
0
        WContainerWidget *hw
1054
0
          = dynamic_cast<WContainerWidget *>(headerWidget(i, true));
1055
0
        WWidget *tw = hw->widget(hw->count() - 1);
1056
0
        headerItemDelegate_->update(tw, headerModel_->index(0, i), None);
1057
0
        tw->setInline(false);
1058
0
        tw->addStyleClass("Wt-label");
1059
1060
0
        WWidget *h = headerWidget(i, false);
1061
0
        ColumnInfo& info = columnInfo(i);
1062
0
        h->setStyleClass(info.styleClass() + " Wt-tv-c headerrh");
1063
0
        WT_USTRING sc = asString(headerModel_->index(0, i)
1064
0
                                 .data(ItemDataRole::StyleClass));
1065
0
        if (!sc.empty())
1066
0
          h->addStyleClass(sc);
1067
0
      }
1068
0
    }
1069
0
  }
1070
0
}
1071
1072
int WAbstractItemView::headerLevel(int column) const
1073
0
{
1074
0
  cpp17::any d = model_->headerData(column, Orientation::Horizontal,
1075
0
                                 ItemDataRole::Level);
1076
1077
0
  if (cpp17::any_has_value(d))
1078
0
    return static_cast<int>(asNumber(d));
1079
0
  else
1080
0
    return 0;
1081
0
}
1082
1083
void WAbstractItemView::saveExtraHeaderWidgets()
1084
0
{
1085
0
  for (int i = 0; i < columnCount(); ++i) {
1086
0
    WWidget *w = columnInfo(i).extraHeaderWidget.get();
1087
0
    if (w && w->parent()) {
1088
0
      w->parent()->removeWidget(w).release();
1089
0
    }
1090
0
  }
1091
0
}
1092
1093
std::unique_ptr<WWidget> WAbstractItemView::createHeaderWidget(int column)
1094
0
{
1095
  /****
1096
   * result:
1097
   * +------------------------------+
1098
   * | +----------------------+     +
1099
   * | | +------------------+ |     |
1100
   * | | | contents         | |     |
1101
   * | | +------------------+ | +-+ |
1102
   * | | +------------------+ | | | |
1103
   * | | | extra widget     | | | <------ resize handle with border right and
1104
   * | | | ....             | | | | |     margin-top for right-border-level
1105
   * | | +------------------+ | | | |
1106
   * | +----------------------+ +-+ |
1107
   * +------------------------------+
1108
   */
1109
1110
0
  ColumnInfo& info = columnInfo(column);
1111
1112
  /* Contents */
1113
1114
0
  std::unique_ptr<WContainerWidget> contents(new WContainerWidget());
1115
0
  contents->setObjectName("contents");
1116
1117
0
  if (info.sorting) {
1118
0
    std::unique_ptr<WText> sortIcon(new WText());
1119
0
    sortIcon->setObjectName("sort");
1120
0
    sortIcon->setInline(false);
1121
0
    sortIcon->setStyleClass("Wt-tv-sh Wt-tv-sh-none");
1122
0
    if (currentSortColumn_ == column)
1123
0
      sortIcon->setStyleClass(info.sortOrder == SortOrder::Ascending
1124
0
                              ? "Wt-tv-sh Wt-tv-sh-up"
1125
0
                              : "Wt-tv-sh Wt-tv-sh-down");
1126
0
    contents->addWidget(std::move(sortIcon));
1127
0
  }
1128
1129
0
  if (!(model_->headerFlags(column) & (HeaderFlag::ColumnIsExpandedLeft |
1130
0
                                     HeaderFlag::ColumnIsExpandedRight)).empty()) {
1131
0
    std::unique_ptr<WImage> collapseIcon(new WImage());
1132
0
    collapseIcon->setFloatSide(Side::Left);
1133
0
    collapseIcon
1134
0
      ->setImageLink(WLink(WApplication::relativeResourcesUrl() + "minus.gif"));
1135
0
    collapseIcon->clicked().connect
1136
0
      (this, std::bind(&WAbstractItemView::collapseColumn, this, info.id));
1137
0
    contents->addWidget(std::move(collapseIcon));
1138
0
  } else if (model_->headerFlags(column).test(HeaderFlag::ColumnIsCollapsed)) {
1139
0
    std::unique_ptr<WImage> expandIcon(new WImage());
1140
0
    expandIcon->setFloatSide(Side::Left);
1141
0
    expandIcon
1142
0
      ->setImageLink(WLink(WApplication::relativeResourcesUrl() + "plus.gif"));
1143
0
    expandIcon->clicked().connect
1144
0
      (this, std::bind(&WAbstractItemView::expandColumn, this, info.id));
1145
0
    contents->addWidget(std::move(expandIcon));
1146
0
  }
1147
1148
0
  WModelIndex index = headerModel_->index(0, column);
1149
0
  std::unique_ptr<WWidget> i(headerItemDelegate_->update(nullptr, index, None));
1150
0
  i->setInline(false);
1151
0
  i->addStyleClass("Wt-label");
1152
0
  contents->addWidget(std::move(i));
1153
1154
  // Fix #10512: Disable the header if the view is disabled
1155
0
  if (isDisabled()) {
1156
0
    auto app = Wt::WApplication::instance();
1157
0
    std::string themedDisabledClass = app ? app->theme()->disabledClass() : "";
1158
0
    contents->addStyleClass(themedDisabledClass);
1159
0
    for (WWidget* child : contents->children()) {
1160
0
      child->addStyleClass(themedDisabledClass);
1161
0
    }
1162
0
  }
1163
1164
0
  int headerLevel = model_ ? this->headerLevel(column) : 0;
1165
1166
0
  contents->setMargin(headerLevel * headerLineHeight_.toPixels(), Side::Top);
1167
1168
  /* Resize handle (or border-right 1 stub) */
1169
1170
0
  int rightBorderLevel = headerLevel;
1171
0
  if (model_) {
1172
0
    int rightColumn = modelColumnIndex(visibleColumnIndex(column) + 1);
1173
0
    if (rightColumn != -1) {
1174
0
      WFlags<HeaderFlag> flagsLeft = model_->headerFlags(column);
1175
0
      WFlags<HeaderFlag> flagsRight = model_->headerFlags(rightColumn);
1176
1177
0
      int rightHeaderLevel = this->headerLevel(rightColumn);
1178
1179
0
      if (flagsLeft.test(HeaderFlag::ColumnIsExpandedRight))
1180
0
        rightBorderLevel = headerLevel + 1;
1181
0
      else if (flagsRight.test(HeaderFlag::ColumnIsExpandedLeft))
1182
0
        rightBorderLevel = rightHeaderLevel + 1;
1183
0
      else
1184
0
        rightBorderLevel = std::min(headerLevel, rightHeaderLevel);
1185
0
    }
1186
0
  }
1187
1188
0
  bool hasResizeHandle = info.resizable;
1189
1190
0
  std::unique_ptr<WContainerWidget> resizeHandle(new WContainerWidget());
1191
0
  resizeHandle->setStyleClass(std::string("Wt-tv-rh")
1192
0
                              + (hasResizeHandle ? "" : " Wt-tv-no-rh" )
1193
0
                              + " Wt-tv-br headerrh");
1194
1195
0
  if (hasResizeHandle)
1196
0
    resizeHandle->mouseWentDown().connect(resizeHandleMDownJS_);
1197
1198
0
  resizeHandle->setMargin(rightBorderLevel * headerLineHeight_.toPixels(),
1199
0
                          Side::Top);
1200
1201
  /*
1202
   * Extra widget
1203
   */
1204
1205
0
  if (!columnInfo(column).extraHeaderWidget)
1206
0
    columnInfo(column).extraHeaderWidget
1207
0
      = createExtraHeaderWidget(column).release();
1208
1209
0
  std::unique_ptr<WWidget> extraW(columnInfo(column).extraHeaderWidget.get());
1210
1211
  /*
1212
   * Assemble into result
1213
   */
1214
0
  std::unique_ptr<WContainerWidget> result(new WContainerWidget());
1215
0
  result->setStyleClass(info.styleClass() + " Wt-tv-c headerrh");
1216
0
  result->addWidget(std::move(resizeHandle));
1217
1218
0
  std::unique_ptr<WContainerWidget> main(new WContainerWidget());
1219
0
  main->setOverflow(Overflow::Hidden);
1220
0
  main->setContentAlignment(info.headerHAlignment);
1221
0
  main->addWidget(std::move(contents));
1222
1223
0
  if (info.headerVAlignment == AlignmentFlag::Middle)
1224
0
    main->setLineHeight(headerLineHeight_);
1225
0
  else {
1226
0
    main->setLineHeight(WLength::Auto);
1227
0
    if (info.headerWordWrap)
1228
0
      main->addStyleClass("Wt-wwrap");
1229
0
  }
1230
1231
0
  if (extraW)
1232
0
    main->addWidget(std::move(extraW));
1233
1234
0
  main->clicked().connect(this,
1235
0
          std::bind(&WAbstractItemView::handleHeaderClicked, this,
1236
0
                    info.id, std::placeholders::_1));
1237
0
  main->mouseWentDown().connect(this,
1238
0
          std::bind(&WAbstractItemView::handleHeaderMouseDown, this,
1239
0
                    info.id, std::placeholders::_1));
1240
0
  main->mouseWentUp().connect(this,
1241
0
          std::bind(&WAbstractItemView::handleHeaderMouseUp, this,
1242
0
                    info.id, std::placeholders::_1));
1243
0
  main->doubleClicked().connect(this,
1244
0
          std::bind(&WAbstractItemView::handleHeaderDblClicked, this,
1245
0
                    info.id, std::placeholders::_1));
1246
1247
0
  result->addWidget(std::move(main));
1248
1249
  // Fix #10512: Disable the header if the view is disabled
1250
0
  if (isDisabled()) {
1251
0
    auto app = Wt::WApplication::instance();
1252
0
    std::string themedDisabledClass = app ? app->theme()->disabledClass() : "";
1253
0
    result->addStyleClass(themedDisabledClass);
1254
0
    for (WWidget* child : result->children()) {
1255
0
      child->addStyleClass(themedDisabledClass);
1256
0
    }
1257
0
  }
1258
1259
0
  WT_USTRING sc = asString(index.data(ItemDataRole::StyleClass));
1260
0
  if (!sc.empty())
1261
0
    result->addStyleClass(sc);
1262
1263
0
  return result;
1264
0
}
1265
1266
void WAbstractItemView::enableAjax()
1267
0
{
1268
0
  WCompositeWidget::enableAjax();
1269
0
  if (uDragWidget_) {
1270
0
    headerContainer()->addWidget(std::move(uDragWidget_));
1271
0
    configureModelDragDrop();
1272
0
  }
1273
0
}
1274
1275
void WAbstractItemView::setDragEnabled(bool enable)
1276
0
{
1277
0
  if (dragEnabled_ != enable) {
1278
0
    dragEnabled_ = enable;
1279
1280
0
    if (enable) {
1281
0
      uDragWidget_ = std::unique_ptr<WText>(new WText());
1282
0
      dragWidget_ = uDragWidget_.get();
1283
0
      dragWidget_->setId(id() + "dw");
1284
0
      dragWidget_->setInline(false);
1285
0
      dragWidget_->hide();
1286
0
      setAttributeValue("dwid", dragWidget_->id());
1287
0
      if (headerContainer())
1288
0
        headerContainer()->addWidget(std::move(uDragWidget_));
1289
1290
0
      configureModelDragDrop();
1291
0
    } else {
1292
      // TODO disable
1293
0
    }
1294
0
  }
1295
0
}
1296
1297
int WAbstractItemView::headerLevelCount() const
1298
0
{
1299
0
  int result = 0;
1300
1301
0
  if (model_)
1302
0
    for (unsigned int i = 0; i < columns_.size(); ++i)
1303
0
      if (!columns_[i].hidden)
1304
0
        result = std::max(result, headerLevel(i));
1305
1306
0
  return result + 1;
1307
0
}
1308
1309
void WAbstractItemView::setHeaderHeight(const WLength& height)
1310
0
{
1311
0
  headerLineHeight_ = height;
1312
1313
0
  int lineCount = headerLevelCount();
1314
0
  WLength headerHeight = headerLineHeight_ * lineCount;
1315
1316
0
  if (columns_.size() > 0) {
1317
0
    WWidget *w = headerWidget(0, false);
1318
0
    if (w)
1319
0
      w->scheduleRender(RepaintFlag::SizeAffected); // for layout
1320
0
  }
1321
1322
0
  headerHeightRule_->templateWidget()->resize(WLength::Auto, headerHeight);
1323
0
}
1324
1325
void WAbstractItemView::bindObjJS(JSlot& slot, const std::string& jsMethod)
1326
0
{
1327
0
  slot.setJavaScript
1328
0
    ("function(obj, event) {"
1329
0
     """" + jsRef() + ".wtObj." + jsMethod + "(obj, event);"
1330
0
     "}");
1331
0
}
1332
1333
void WAbstractItemView::connectObjJS(EventSignalBase& s,
1334
                                     const std::string& jsMethod)
1335
0
{
1336
0
  s.connect
1337
0
    ("function(obj, event) {"
1338
0
     """" + jsRef() + ".wtObj." + jsMethod + "(obj, event);"
1339
0
     "}");
1340
0
}
1341
1342
void WAbstractItemView::modelLayoutAboutToBeChanged()
1343
0
{
1344
0
  if (rootIndex_.isValid())
1345
0
    rootIndex_.encodeAsRawIndex();
1346
1347
0
  for (EditorMap::iterator i = editedItems_.begin(); i != editedItems_.end();
1348
0
       ++i) {
1349
0
    persistEditor(i->first, i->second);
1350
0
    (const_cast<WModelIndex &>(i->first)).encodeAsRawIndex();
1351
0
  }
1352
1353
0
  selectionModel_->modelLayoutAboutToBeChanged();
1354
0
}
1355
1356
void WAbstractItemView::modelLayoutChanged()
1357
0
{
1358
0
  if (rootIndex_.isValid())
1359
0
    rootIndex_ = rootIndex_.decodeFromRawIndex();
1360
1361
0
  EditorMap newEditorMap;
1362
0
  for (EditorMap::iterator i = editedItems_.begin(); i != editedItems_.end();
1363
0
       ++i) {
1364
0
    WModelIndex m = i->first.decodeFromRawIndex();
1365
0
    if (m.isValid())
1366
0
      newEditorMap[m] = i->second;
1367
0
  }
1368
0
  editedItems_.swap(newEditorMap);
1369
1370
0
  selectionModel_->modelLayoutChanged();
1371
1372
0
  scheduleRerender(RenderState::NeedRerenderData);
1373
1374
0
  selectionChanged().emit();
1375
0
}
1376
1377
std::unique_ptr<WWidget> WAbstractItemView::createPageNavigationBar()
1378
0
{
1379
0
  return std::unique_ptr<WWidget>(new DefaultPagingBar(this));
1380
0
}
1381
1382
void WAbstractItemView::collapseColumn(int columnid)
1383
0
{
1384
0
  model_->collapseColumn(columnById(columnid));
1385
0
  scheduleRerender(RenderState::NeedRerenderHeader);
1386
0
  setHeaderHeight(headerLineHeight_);
1387
0
}
1388
1389
void WAbstractItemView::expandColumn(int columnid)
1390
0
{
1391
0
  model_->expandColumn(columnById(columnid));
1392
0
  scheduleRerender(RenderState::NeedRerenderHeader);
1393
0
  setHeaderHeight(headerLineHeight_);
1394
0
}
1395
1396
void WAbstractItemView::handleClick(const WModelIndex& index,
1397
                                    const WMouseEvent& event)
1398
0
{
1399
0
  if (dragEnabled_ && delayedClearAndSelectIndex_.isValid()) {
1400
0
    Coordinates delta = event.dragDelta();
1401
0
    if ((delta.x < 0 ? -delta.x : delta.x) < 4 && (delta.y < 0 ? -delta.y : delta.y) < 4)
1402
0
      select(delayedClearAndSelectIndex_, SelectionFlag::ClearAndSelect);
1403
0
  }
1404
1405
0
  bool doEdit = index.isValid() && editTriggers().test(EditTrigger::SingleClicked);
1406
1407
0
  if (doEdit)
1408
0
    edit(index);
1409
1410
0
  clicked_.emit(index, event);
1411
0
}
1412
1413
void WAbstractItemView::handleDoubleClick(const WModelIndex& index,
1414
                                          const WMouseEvent& event)
1415
0
{
1416
0
  bool doEdit = index.isValid() && editTriggers().test(EditTrigger::DoubleClicked);
1417
0
  if (doEdit)
1418
0
    edit(index);
1419
1420
0
  doubleClicked_.emit(index, event);
1421
0
}
1422
1423
void WAbstractItemView::handleMouseDown(const WModelIndex& index,
1424
                                        const WMouseEvent& event)
1425
0
{
1426
  // Needed because mousedown signal is emitted after a touchstart signal
1427
0
  if (touchRegistered_)
1428
0
    return;
1429
1430
0
  bool doEdit = index.isValid() &&
1431
0
    editTriggers().test(EditTrigger::SelectedClicked) && isSelected(index);
1432
1433
0
  delayedClearAndSelectIndex_ = WModelIndex();
1434
1435
0
  if (index.isValid() && event.button() == MouseButton::Left)
1436
0
    selectionHandleClick(index, event.modifiers());
1437
1438
0
  if (doEdit)
1439
0
    edit(index);
1440
1441
0
  mouseWentDown_.emit(index, event);
1442
0
  touchRegistered_ = false;
1443
0
}
1444
1445
void WAbstractItemView::handleMouseUp(const WModelIndex& index,
1446
                                      const WMouseEvent& event)
1447
0
{
1448
0
  mouseWentUp_.emit(index, event);
1449
0
}
1450
1451
void WAbstractItemView::handleTouchSelect(const std::vector<WModelIndex>& indices,
1452
                                          const WTouchEvent& event)
1453
0
{
1454
0
  if (indices.empty())
1455
0
    return; // no indices, likely due to faulty input
1456
1457
0
  const WModelIndex& index = indices[0];
1458
0
  touchRegistered_ = true;
1459
0
  delayedClearAndSelectIndex_ = WModelIndex();
1460
0
  if (indices.size() == 1) {
1461
1462
0
    bool doEdit = index.isValid() &&
1463
0
        (editTriggers().test(EditTrigger::SelectedClicked)) && isSelected(index);
1464
1465
0
    if (doEdit)
1466
0
      edit(index);
1467
0
  }
1468
0
  if (indices[0].isValid() && indices[indices.size()-1].isValid()){
1469
0
    selectionHandleTouch(indices, event);
1470
0
  }
1471
1472
0
  touchStart_.emit(index, event);
1473
0
}
1474
1475
void WAbstractItemView::handleTouchStart(const std::vector<WModelIndex>& indices,
1476
                                           const WTouchEvent& event)
1477
0
{
1478
0
  touchStarted_.emit(indices, event);
1479
0
}
1480
1481
void WAbstractItemView::handleTouchMove(const std::vector<WModelIndex>& indices,
1482
                                        const WTouchEvent& event)
1483
0
{
1484
0
  touchMoved_.emit(indices, event);
1485
0
}
1486
1487
void WAbstractItemView::handleTouchEnd(const std::vector<WModelIndex>& indices,
1488
                                       const WTouchEvent& event)
1489
0
{
1490
0
  touchEnded_.emit(indices, event);
1491
0
}
1492
1493
void WAbstractItemView::setEditTriggers(WFlags<EditTrigger> editTriggers)
1494
0
{
1495
0
  editTriggers_ = editTriggers;
1496
0
}
1497
1498
void WAbstractItemView::setEditOptions(WFlags<EditOption> editOptions)
1499
0
{
1500
0
  editOptions_ = editOptions;
1501
0
}
1502
1503
void WAbstractItemView::edit(const WModelIndex& index)
1504
0
{
1505
0
  if (index.flags().test(ItemFlag::Editable) && !isEditing(index)) {
1506
0
    if (editOptions_.test(EditOption::SingleEditor)) {
1507
0
      while (!editedItems_.empty())
1508
0
        closeEditor(editedItems_.begin()->first, false);
1509
0
    }
1510
1511
0
    Utils::insert(editedItems_, index, Editor());
1512
0
    editedItems_[index].widget = nullptr;
1513
0
    editedItems_[index].stateSaved = false;
1514
1515
0
    modelDataChanged(index, index);
1516
0
  }
1517
0
}
1518
1519
void WAbstractItemView::closeEditorWidget(WWidget *editor, bool saveData)
1520
0
{
1521
0
  for (EditorMap::iterator i = editedItems_.begin();
1522
0
       i != editedItems_.end(); ++i)
1523
0
    if (i->second.widget.get() == editor) {
1524
0
      if (editOptions_.test(EditOption::LeaveEditorsOpen)) {
1525
        // Save data, but keep editor open
1526
0
        if (saveData)
1527
0
          saveEditedValue(i->first, i->second);
1528
0
      } else
1529
0
        closeEditor(i->first, saveData);
1530
1531
0
      return;
1532
0
    }
1533
0
}
1534
1535
void WAbstractItemView::closeEditor(const WModelIndex& index, bool saveData)
1536
0
{
1537
0
  EditorMap::iterator i = editedItems_.find(index);
1538
1539
0
  if (i != editedItems_.end()) {
1540
0
    Editor editor = i->second;
1541
1542
0
    WModelIndex closed = index;
1543
0
#ifndef WT_TARGET_JAVA
1544
0
    editedItems_.erase(i);
1545
#else
1546
    editedItems_.erase(index);
1547
#endif
1548
1549
0
    if (saveData || editOptions_.test(EditOption::SaveWhenClosed))
1550
0
      saveEditedValue(closed, editor);
1551
1552
0
    modelDataChanged(closed, closed);
1553
0
  }
1554
0
}
1555
1556
void WAbstractItemView::closeEditors(bool saveData)
1557
0
{
1558
0
  while (!editedItems_.empty()) {
1559
0
    closeEditor(editedItems_.begin()->first, saveData);
1560
0
  }
1561
0
}
1562
1563
ValidationState WAbstractItemView::validateEditor(const WModelIndex& index)
1564
0
{
1565
0
  EditorMap::iterator i = editedItems_.find(index);
1566
1567
0
  if (i != editedItems_.end()) {
1568
0
    auto delegate = itemDelegate(index);
1569
1570
0
    cpp17::any editState;
1571
1572
0
    Editor& editor = i->second;
1573
1574
0
    if (editor.widget)
1575
0
      editState = delegate->editState(editor.widget.get(), index);
1576
0
    else
1577
0
      editState = editor.editState;
1578
1579
0
    ValidationState state = delegate->validate(index, editState);
1580
0
    editor.valid = (state == ValidationState::Valid);
1581
1582
0
    return state;
1583
0
  }
1584
1585
0
  return ValidationState::Invalid;
1586
0
}
1587
1588
ValidationState WAbstractItemView::validateEditors()
1589
0
{
1590
0
  ValidationState state = ValidationState::Valid;
1591
1592
0
  for (EditorMap::const_iterator i = editedItems_.begin();
1593
0
       i != editedItems_.end(); ++i) {
1594
0
    ValidationState s = validateEditor(i->first);
1595
0
    if (s < state)
1596
0
      state = s;
1597
0
  }
1598
1599
0
  return state;
1600
0
}
1601
1602
bool WAbstractItemView::isEditing(const WModelIndex& index) const
1603
0
{
1604
0
  return editedItems_.find(index) != editedItems_.end();
1605
0
}
1606
1607
bool WAbstractItemView::shiftEditorRows(const WModelIndex& parent,
1608
                                        int start, int count,
1609
                                        bool persistWhenShifted)
1610
0
{
1611
  /* Returns whether an editor with a widget shifted */
1612
0
  bool result = false;
1613
1614
0
  if (!editedItems_.empty()) {
1615
0
    std::vector<WModelIndex> toClose;
1616
1617
0
    EditorMap newMap;
1618
1619
0
    for (EditorMap::iterator i = editedItems_.begin(); i != editedItems_.end();
1620
0
         ++i) {
1621
0
      WModelIndex c = i->first;
1622
1623
0
      WModelIndex p = c.parent();
1624
1625
0
      if (p != parent && !WModelIndex::isAncestor(p, parent))
1626
0
        newMap[c] = i->second;
1627
0
      else {
1628
0
        if (p == parent) {
1629
0
          if (c.row() >= start) {
1630
0
            if (c.row() < start - count) {
1631
0
              toClose.push_back(c);
1632
0
            } else {
1633
0
              WModelIndex shifted
1634
0
                = model_->index(c.row() + count, c.column(), p);
1635
0
              newMap[shifted] = i->second;
1636
0
              if (i->second.widget) {
1637
0
                if (persistWhenShifted)
1638
0
                   persistEditor(shifted, i->second);
1639
0
                result = true;
1640
0
              }
1641
0
            }
1642
0
          } else
1643
0
            newMap[c] = i->second;
1644
0
        } else if (count < 0) {
1645
0
          do {
1646
0
            if (p.parent() == parent
1647
0
                && p.row() >= start
1648
0
                && p.row() < start - count) {
1649
0
              toClose.push_back(c);
1650
0
              break;
1651
0
            } else
1652
0
              p = p.parent();
1653
0
          } while (p != parent);
1654
0
        }
1655
0
      }
1656
0
    }
1657
1658
0
    for (unsigned i = 0; i < toClose.size(); ++i)
1659
0
      closeEditor(toClose[i]);
1660
1661
0
    editedItems_ = newMap;
1662
0
  }
1663
1664
0
  return result;
1665
0
}
1666
1667
bool WAbstractItemView::shiftEditorColumns(const WModelIndex& parent,
1668
                                           int start, int count,
1669
                                           bool persistWhenShifted)
1670
0
{
1671
  /* Returns whether an editor with a widget shifted */
1672
0
  bool result = false;
1673
1674
0
  if (!editedItems_.empty()) {
1675
0
    std::vector<WModelIndex> toClose;
1676
1677
0
    EditorMap newMap;
1678
1679
0
    for (EditorMap::iterator i = editedItems_.begin(); i != editedItems_.end();
1680
0
         ++i) {
1681
0
      WModelIndex c = i->first;
1682
1683
0
      WModelIndex p = c.parent();
1684
1685
0
      if (p != parent && !WModelIndex::isAncestor(p, parent))
1686
0
        newMap[c] = i->second;
1687
0
      else {
1688
0
        if (p == parent) {
1689
0
          if (c.column() >= start) {
1690
0
            if (c.column() < start - count) {
1691
0
              toClose.push_back(c);
1692
0
            } else {
1693
0
              WModelIndex shifted
1694
0
                = model_->index(c.row(), c.column() + count, p);
1695
0
              newMap[shifted] = i->second;
1696
0
              if (i->second.widget) {
1697
0
                if (persistWhenShifted)
1698
0
                   persistEditor(shifted, i->second);
1699
0
                result = true;
1700
0
              }
1701
0
            }
1702
0
          } else
1703
0
            newMap[c] = i->second;
1704
0
        } else if (count < 0) {
1705
0
          do {
1706
0
            if (p.parent() == parent
1707
0
                && p.column() >= start
1708
0
                && p.column() < start - count) {
1709
0
              toClose.push_back(c);
1710
0
              break;
1711
0
            } else
1712
0
              p = p.parent();
1713
0
          } while (p != parent);
1714
0
        }
1715
0
      }
1716
0
    }
1717
1718
0
    for (unsigned i = 0; i < toClose.size(); ++i)
1719
0
      closeEditor(toClose[i]);
1720
1721
0
    editedItems_ = newMap;
1722
0
  }
1723
1724
0
  return result;
1725
0
}
1726
1727
bool WAbstractItemView::isValid(const WModelIndex& index) const
1728
0
{
1729
0
  EditorMap::const_iterator i = editedItems_.find(index);
1730
1731
0
  if (i != editedItems_.end()) {
1732
0
    const Editor& editor = i->second;
1733
0
    return editor.valid;
1734
0
  } else
1735
0
    return false;
1736
0
}
1737
1738
bool WAbstractItemView::hasEditFocus(const WModelIndex& index) const
1739
0
{
1740
  /*
1741
   * Later, we may want to only return true for the 'smallest' index
1742
   * satisfying all these conditions
1743
   */
1744
0
  EditorMap::const_iterator i = editedItems_.find(index);
1745
1746
0
  if (i != editedItems_.end()) {
1747
0
    const Editor& editor = i->second;
1748
0
    return !editor.widget && !editor.stateSaved;
1749
0
  } else
1750
0
    return false;
1751
0
}
1752
1753
bool WAbstractItemView::isEditing() const
1754
0
{
1755
0
  return !editedItems_.empty();
1756
0
}
1757
1758
void WAbstractItemView::saveEditedValue(const WModelIndex& index,
1759
                                        Editor& editor)
1760
0
{
1761
0
  cpp17::any editState;
1762
0
  auto delegate = itemDelegate(index);
1763
1764
0
  if (editor.widget)
1765
0
    editState = delegate->editState(editor.widget.get(), index);
1766
0
  else
1767
0
    editState = editor.editState;
1768
1769
0
  delegate->setModelData(editState, model_.get(), index);
1770
0
}
1771
1772
void WAbstractItemView::persistEditor(const WModelIndex& index)
1773
0
{
1774
0
  EditorMap::iterator i = editedItems_.find(index);
1775
1776
0
  if (i != editedItems_.end())
1777
0
    persistEditor(index, i->second);
1778
0
}
1779
1780
void WAbstractItemView::persistEditor(const WModelIndex& index, Editor& editor)
1781
0
{
1782
0
  if (editor.widget) {
1783
0
    editor.editState
1784
0
      = itemDelegate(index)->editState(editor.widget.get(), index);
1785
0
    editor.stateSaved = true;
1786
0
    editor.widget = nullptr;
1787
0
  }
1788
0
}
1789
1790
void WAbstractItemView::setEditState(const WModelIndex& index,
1791
                                     const cpp17::any& editState)
1792
0
{
1793
0
  editedItems_[index].editState = editState;
1794
0
}
1795
1796
void WAbstractItemView::setEditorWidget(const WModelIndex& index,
1797
                                        WWidget *editor)
1798
0
{
1799
0
  editedItems_[index].widget = editor;
1800
0
  editedItems_[index].stateSaved = !editor;
1801
0
}
1802
1803
cpp17::any WAbstractItemView::editState(const WModelIndex& index) const
1804
0
{
1805
0
  EditorMap::const_iterator i = editedItems_.find(index);
1806
1807
0
  if (i != editedItems_.end())
1808
0
    return i->second.editState;
1809
0
  else
1810
0
    return cpp17::any();
1811
0
}
1812
1813
EventSignal<WKeyEvent>& WAbstractItemView::keyWentDown()
1814
0
{
1815
0
  impl_->setCanReceiveFocus(true);
1816
0
  return impl_->keyWentDown();
1817
0
}
1818
1819
EventSignal<WKeyEvent>& WAbstractItemView::keyPressed()
1820
0
{
1821
0
  impl_->setCanReceiveFocus(true);
1822
0
  return impl_->keyPressed();
1823
0
}
1824
1825
EventSignal<WKeyEvent>& WAbstractItemView::keyWentUp()
1826
0
{
1827
0
  impl_->setCanReceiveFocus(true);
1828
0
  return impl_->keyWentUp();
1829
0
}
1830
1831
}
1832