/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 | | |