1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2013 - 2018 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
4 * Copyright (C) 2015 - 2016 Jan Bajer aka bajasoft <jbajer@gmail.com>
5 * Copyright (C) 2015 - 2016 Piotr Wójcik <chocimier@tlen.pl>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 **************************************************************************/
21
22 #include "ItemViewWidget.h"
23 #include "ItemDelegate.h"
24 #include "../core/IniSettings.h"
25 #include "../core/SessionsManager.h"
26
27 #include <QtCore/QTimer>
28 #include <QtGui/QDropEvent>
29 #include <QtWidgets/QMenu>
30 #include <QtWidgets/QToolTip>
31
32 namespace Otter
33 {
34
HeaderViewWidget(Qt::Orientation orientation,QWidget * parent)35 HeaderViewWidget::HeaderViewWidget(Qt::Orientation orientation, QWidget *parent) : QHeaderView(orientation, parent)
36 {
37 setMinimumSectionSize(0);
38 setTextElideMode(Qt::ElideRight);
39 setSectionsMovable(true);
40
41 connect(this, &HeaderViewWidget::sectionClicked, this, &HeaderViewWidget::handleSectionClicked);
42 }
43
showEvent(QShowEvent * event)44 void HeaderViewWidget::showEvent(QShowEvent *event)
45 {
46 setSectionsClickable(true);
47 setSortIndicatorShown(true);
48 setSortIndicator(-1, Qt::AscendingOrder);
49
50 QHeaderView::showEvent(event);
51 }
52
contextMenuEvent(QContextMenuEvent * event)53 void HeaderViewWidget::contextMenuEvent(QContextMenuEvent *event)
54 {
55 const ItemViewWidget *view(qobject_cast<ItemViewWidget*>(parent()));
56
57 if (!view)
58 {
59 return;
60 }
61
62 const int sortColumn(view->getSortColumn());
63 const int sortOrder(view->getSortOrder());
64 QMenu menu(this);
65 QMenu *sortMenu(menu.addMenu(tr("Sorting")));
66 QAction *sortAscendingAction(sortMenu->addAction(tr("Sort Ascending")));
67 sortAscendingAction->setData(AscendingOrder);
68 sortAscendingAction->setCheckable(true);
69 sortAscendingAction->setChecked(sortColumn >= 0 && sortOrder == Qt::AscendingOrder);
70
71 QAction *sortDescendingAction(sortMenu->addAction(tr("Sort Descending")));
72 sortDescendingAction->setData(DescendingOrder);
73 sortDescendingAction->setCheckable(true);
74 sortDescendingAction->setChecked(sortColumn >= 0 && sortOrder == Qt::DescendingOrder);
75
76 sortMenu->addSeparator();
77
78 QAction *noSortAction(sortMenu->addAction(tr("No Sorting")));
79 noSortAction->setData(NoOrder);
80 noSortAction->setCheckable(true);
81 noSortAction->setChecked(sortColumn < 0);
82
83 QActionGroup orderActionGroup(sortMenu);
84 orderActionGroup.setExclusive(true);
85 orderActionGroup.addAction(sortAscendingAction);
86 orderActionGroup.addAction(sortDescendingAction);
87 orderActionGroup.addAction(noSortAction);
88
89 sortMenu->addSeparator();
90
91 QMenu *visibilityMenu(menu.addMenu(tr("Visible Columns")));
92 visibilityMenu->setEnabled(model()->columnCount() > 1);
93
94 QAction *showAllColumnsAction(nullptr);
95 bool areAllColumnsVisible(true);
96
97 if (visibilityMenu->isEnabled())
98 {
99 showAllColumnsAction = visibilityMenu->addAction(tr("Show All"));
100 showAllColumnsAction->setData(-1);
101 showAllColumnsAction->setCheckable(true);
102
103 visibilityMenu->addSeparator();
104 }
105
106 QActionGroup sortActionGroup(sortMenu);
107 sortActionGroup.setExclusive(true);
108
109 for (int i = 0; i < model()->columnCount(); ++i)
110 {
111 const QString title(model()->headerData(i, orientation()).toString().isEmpty() ? tr("(Untitled)") : model()->headerData(i, orientation()).toString());
112 QAction *sortAction(sortMenu->addAction(title));
113 sortAction->setData(i);
114 sortAction->setCheckable(true);
115 sortAction->setChecked(i == sortColumn);
116
117 sortActionGroup.addAction(sortAction);
118
119 if (visibilityMenu->isEnabled())
120 {
121 QAction *visibilityAction(visibilityMenu->addAction(title));
122 visibilityAction->setData(i);
123 visibilityAction->setCheckable(true);
124 visibilityAction->setChecked(!view->isColumnHidden(i));
125
126 if (!visibilityAction->isChecked())
127 {
128 areAllColumnsVisible = false;
129 }
130 }
131 }
132
133 if (showAllColumnsAction)
134 {
135 showAllColumnsAction->setChecked(areAllColumnsVisible);
136 showAllColumnsAction->setEnabled(!areAllColumnsVisible);
137 }
138
139 connect(sortMenu, &QMenu::triggered, this, &HeaderViewWidget::toggleSort);
140 connect(visibilityMenu, &QMenu::triggered, this, &HeaderViewWidget::toggleColumnVisibility);
141
142 menu.exec(event->globalPos());
143 }
144
toggleColumnVisibility(QAction * action)145 void HeaderViewWidget::toggleColumnVisibility(QAction *action)
146 {
147 if (action)
148 {
149 emit columnVisibilityChanged(action->data().toInt(), !action->isChecked());
150 }
151 }
152
toggleSort(QAction * action)153 void HeaderViewWidget::toggleSort(QAction *action)
154 {
155 const ItemViewWidget *view(qobject_cast<ItemViewWidget*>(parent()));
156
157 if (action && view)
158 {
159 const int value(action->data().toInt());
160 int column(view->getSortColumn());
161
162 if (column < 0)
163 {
164 for (int i = 0; i < count(); ++i)
165 {
166 column = logicalIndex(i);
167
168 if (!isSectionHidden(column))
169 {
170 break;
171 }
172 }
173 }
174
175 if (value == AscendingOrder)
176 {
177 setSort(column, Qt::AscendingOrder);
178 }
179 else if (value == DescendingOrder)
180 {
181 setSort(column, Qt::DescendingOrder);
182 }
183 else
184 {
185 handleSectionClicked(value);
186 }
187 }
188 }
189
handleSectionClicked(int column)190 void HeaderViewWidget::handleSectionClicked(int column)
191 {
192 const ItemViewWidget *view(qobject_cast<ItemViewWidget*>(parent()));
193
194 if (!view)
195 {
196 return;
197 }
198
199 if (column >= 0 && view->getSortColumn() != column)
200 {
201 setSort(column, Qt::AscendingOrder);
202 }
203 else if (column >= 0 && view->getSortOrder() == Qt::AscendingOrder)
204 {
205 setSort(column, Qt::DescendingOrder);
206 }
207 else
208 {
209 setSort(-1, Qt::AscendingOrder);
210 }
211 }
212
setSort(int column,Qt::SortOrder order)213 void HeaderViewWidget::setSort(int column, Qt::SortOrder order)
214 {
215 setSortIndicator(column, order);
216
217 emit sortChanged(column, order);
218 }
219
viewportEvent(QEvent * event)220 bool HeaderViewWidget::viewportEvent(QEvent *event)
221 {
222 if (event->type() == QEvent::ToolTip && model())
223 {
224 const QHelpEvent *helpEvent(static_cast<QHelpEvent*>(event));
225 const int column(logicalIndexAt(helpEvent->pos()));
226
227 if (column >= 0)
228 {
229 const QString text(model()->headerData(column, orientation(), Qt::DisplayRole).toString());
230
231 if (!text.isEmpty())
232 {
233 QToolTip::showText(helpEvent->globalPos(), text, this);
234
235 return true;
236 }
237 }
238 }
239
240 return QHeaderView::viewportEvent(event);
241 }
242
ItemViewWidget(QWidget * parent)243 ItemViewWidget::ItemViewWidget(QWidget *parent) : QTreeView(parent),
244 m_headerWidget(new HeaderViewWidget(Qt::Horizontal, this)),
245 m_sourceModel(nullptr),
246 m_proxyModel(nullptr),
247 m_viewMode(ListView),
248 m_sortOrder(Qt::AscendingOrder),
249 m_sortColumn(-1),
250 m_dragRow(-1),
251 m_canGatherExpanded(false),
252 m_isExclusive(false),
253 m_isModified(false),
254 m_isInitialized(false)
255 {
256 handleOptionChanged(SettingsManager::Interface_ShowScrollBarsOption, SettingsManager::getOption(SettingsManager::Interface_ShowScrollBarsOption));
257 setHeader(m_headerWidget);
258 setItemDelegate(new ItemDelegate(this));
259 setIndentation(0);
260 setAllColumnsShowFocus(true);
261
262 m_filterRoles.insert(Qt::DisplayRole);
263
264 viewport()->setAcceptDrops(true);
265
266 connect(SettingsManager::getInstance(), &SettingsManager::optionChanged, this, &ItemViewWidget::handleOptionChanged);
267 connect(m_headerWidget, &HeaderViewWidget::sortChanged, this, &ItemViewWidget::setSort);
268 connect(m_headerWidget, &HeaderViewWidget::columnVisibilityChanged, this, &ItemViewWidget::setColumnVisibility);
269 connect(m_headerWidget, &HeaderViewWidget::sectionMoved, this, &ItemViewWidget::saveState);
270 }
271
showEvent(QShowEvent * event)272 void ItemViewWidget::showEvent(QShowEvent *event)
273 {
274 ensureInitialized();
275
276 QTreeView::showEvent(event);
277 }
278
resizeEvent(QResizeEvent * event)279 void ItemViewWidget::resizeEvent(QResizeEvent *event)
280 {
281 QTreeView::resizeEvent(event);
282
283 updateSize();
284 }
285
keyPressEvent(QKeyEvent * event)286 void ItemViewWidget::keyPressEvent(QKeyEvent *event)
287 {
288 const int rowCount(getRowCount());
289
290 if ((event->key() == Qt::Key_Down || event->key() == Qt::Key_Up) && rowCount > 1 && moveCursor(((event->key() == Qt::Key_Up) ? MoveUp : MoveDown), event->modifiers()) == currentIndex())
291 {
292 QModelIndex newIndex;
293
294 if (event->key() == Qt::Key_Down)
295 {
296 for (int i = 0; i < rowCount; ++i)
297 {
298 const QModelIndex index(getIndex(i, 0));
299
300 if (index.flags().testFlag(Qt::ItemIsSelectable))
301 {
302 newIndex = index;
303
304 break;
305 }
306 }
307 }
308 else
309 {
310 for (int i = (rowCount - 1); i >= 0; --i)
311 {
312 const QModelIndex index(getIndex(i, 0));
313
314 if (index.flags().testFlag(Qt::ItemIsSelectable))
315 {
316 newIndex = index;
317
318 break;
319 }
320 }
321 }
322
323 if (newIndex.isValid())
324 {
325 const QItemSelectionModel::SelectionFlags command(selectionCommand(newIndex, event));
326
327 if (command != QItemSelectionModel::NoUpdate || style()->styleHint(QStyle::SH_ItemView_MovementWithoutUpdatingSelection, nullptr, this))
328 {
329 if (event->key() == Qt::Key_Down)
330 {
331 scrollTo(getIndex(0, 0));
332 }
333
334 selectionModel()->setCurrentIndex(newIndex, command);
335 }
336
337 event->accept();
338
339 return;
340 }
341 }
342
343 QTreeView::keyPressEvent(event);
344 }
345
dropEvent(QDropEvent * event)346 void ItemViewWidget::dropEvent(QDropEvent *event)
347 {
348 if (m_viewMode == TreeView)
349 {
350 QTreeView::dropEvent(event);
351
352 return;
353 }
354
355 QDropEvent mutableEvent(QPointF((visualRect(getIndex(0, 0)).x() + 1), event->posF().y()), Qt::MoveAction, event->mimeData(), event->mouseButtons(), event->keyboardModifiers(), event->type());
356
357 QTreeView::dropEvent(&mutableEvent);
358
359 if (!mutableEvent.isAccepted())
360 {
361 return;
362 }
363
364 event->accept();
365
366 int dropRow(indexAt(event->pos()).row());
367
368 if (dropRow > m_dragRow)
369 {
370 --dropRow;
371 }
372
373 if (dropIndicatorPosition() == QAbstractItemView::BelowItem)
374 {
375 ++dropRow;
376 }
377
378 markAsModified();
379
380 QTimer::singleShot(0, this, [=]()
381 {
382 setCurrentIndex(getIndex(qBound(0, dropRow, getRowCount()), 0));
383 });
384 }
385
startDrag(Qt::DropActions supportedActions)386 void ItemViewWidget::startDrag(Qt::DropActions supportedActions)
387 {
388 m_dragRow = currentIndex().row();
389
390 QTreeView::startDrag(supportedActions);
391 }
392
ensureInitialized()393 void ItemViewWidget::ensureInitialized()
394 {
395 if (m_isInitialized || !model())
396 {
397 return;
398 }
399
400 const QString name(Utils::normalizeObjectName(objectName(), QLatin1String("ViewWidget")));
401
402 if (name.isEmpty())
403 {
404 return;
405 }
406
407 updateSize();
408
409 IniSettings settings(SessionsManager::getReadableDataPath(QLatin1String("views.ini")));
410 settings.beginGroup(name);
411
412 setSort(settings.getValue(QLatin1String("sortColumn"), -1).toInt(), ((settings.getValue(QLatin1String("sortOrder"), QLatin1String("ascending")).toString() == QLatin1String("ascending")) ? Qt::AscendingOrder : Qt::DescendingOrder));
413
414 const QStringList columns(settings.getValue(QLatin1String("columns")).toString().split(QLatin1Char(','), QString::SkipEmptyParts));
415 bool shouldStretchLastSection(true);
416
417 if (!columns.isEmpty())
418 {
419 for (int i = 0; i < model()->columnCount(); ++i)
420 {
421 setColumnHidden(i, true);
422 }
423
424 disconnect(m_headerWidget, &HeaderViewWidget::sectionMoved, this, &ItemViewWidget::saveState);
425
426 for (int i = 0; i < columns.count(); ++i)
427 {
428 const int column(columns.at(i).toInt());
429
430 setColumnHidden(column, false);
431
432 if (m_headerWidget)
433 {
434 m_headerWidget->moveSection(m_headerWidget->visualIndex(column), i);
435
436 if (m_headerWidget->sectionResizeMode(i) == QHeaderView::Stretch)
437 {
438 shouldStretchLastSection = false;
439 }
440 }
441 }
442
443 connect(m_headerWidget, &HeaderViewWidget::sectionMoved, this, &ItemViewWidget::saveState);
444 }
445
446 if (shouldStretchLastSection)
447 {
448 m_headerWidget->setStretchLastSection(true);
449 }
450
451 m_isInitialized = true;
452 }
453
currentChanged(const QModelIndex & current,const QModelIndex & previous)454 void ItemViewWidget::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
455 {
456 QTreeView::currentChanged(current, previous);
457
458 if (selectionModel() && selectionModel()->hasSelection())
459 {
460 if (m_sourceModel)
461 {
462 emit canMoveRowUpChanged(canMoveRowUp());
463 emit canMoveRowDownChanged(canMoveRowDown());
464 }
465
466 emit needsActionsUpdate();
467 }
468 }
469
moveRow(bool moveUp)470 void ItemViewWidget::moveRow(bool moveUp)
471 {
472 if (!m_sourceModel)
473 {
474 return;
475 }
476
477 const int sourceRow(currentIndex().row());
478 const int destinationRow(moveUp ? (sourceRow - 1) : (sourceRow + 1));
479
480 if ((moveUp && sourceRow > 0) || (!moveUp && sourceRow < (m_sourceModel->rowCount() - 1)))
481 {
482 m_sourceModel->insertRow(sourceRow, m_sourceModel->takeRow(destinationRow));
483
484 selectRow(getIndex(destinationRow, 0));
485 notifySelectionChanged();
486 markAsModified();
487 }
488 }
489
insertRow(const QList<QStandardItem * > & items)490 void ItemViewWidget::insertRow(const QList<QStandardItem*> &items)
491 {
492 if (!m_sourceModel)
493 {
494 return;
495 }
496
497 if (m_sourceModel->rowCount() > 0)
498 {
499 const int row(currentIndex().row() + 1);
500
501 if (items.count() > 0)
502 {
503 m_sourceModel->insertRow(row, items);
504 }
505 else
506 {
507 m_sourceModel->insertRow(row);
508 }
509
510 selectRow(getIndex(row, 0));
511 }
512 else
513 {
514 if (items.isEmpty())
515 {
516 QStandardItem *item(new QStandardItem());
517 item->setFlags(item->flags() | Qt::ItemNeverHasChildren);
518
519 m_sourceModel->appendRow(item);
520 }
521 else
522 {
523 m_sourceModel->appendRow(items);
524 }
525
526 selectRow(getIndex(0, 0));
527 }
528
529 markAsModified();
530 }
531
removeRow()532 void ItemViewWidget::removeRow()
533 {
534 if (!m_sourceModel)
535 {
536 return;
537 }
538
539 const int row(currentIndex().row());
540
541 if (row >= 0)
542 {
543 QStandardItem *parent(m_sourceModel->itemFromIndex(currentIndex().parent()));
544
545 if (parent)
546 {
547 parent->removeRow(row);
548 }
549 else
550 {
551 m_sourceModel->removeRow(row);
552 }
553
554 markAsModified();
555 }
556 }
557
moveUpRow()558 void ItemViewWidget::moveUpRow()
559 {
560 moveRow(true);
561 }
562
moveDownRow()563 void ItemViewWidget::moveDownRow()
564 {
565 moveRow(false);
566 }
567
selectRow(const QModelIndex & index)568 void ItemViewWidget::selectRow(const QModelIndex &index)
569 {
570 setCurrentIndex(index);
571 scrollTo(index);
572 }
573
markAsModified()574 void ItemViewWidget::markAsModified()
575 {
576 setModified(true);
577 }
578
saveState()579 void ItemViewWidget::saveState()
580 {
581 if (!m_isInitialized)
582 {
583 return;
584 }
585
586 const QString name(Utils::normalizeObjectName(objectName(), QLatin1String("ViewWidget")));
587
588 if (name.isEmpty())
589 {
590 return;
591 }
592
593 IniSettings settings(SessionsManager::getWritableDataPath(QLatin1String("views.ini")));
594 settings.beginGroup(name);
595
596 QStringList columns;
597
598 for (int i = 0; i < getColumnCount(); ++i)
599 {
600 const int section(m_headerWidget->logicalIndex(i));
601
602 if (section >= 0 && !isColumnHidden(section))
603 {
604 columns.append(QString::number(section));
605 }
606 }
607
608 settings.setValue(QLatin1String("columns"), columns.join(QLatin1Char(',')));
609 settings.setValue(QLatin1String("sortColumn"), ((m_sortColumn >= 0) ? QVariant(m_sortColumn) : QVariant()));
610 settings.setValue(QLatin1String("sortOrder"), ((m_sortColumn >= 0) ? QVariant((m_sortOrder == Qt::AscendingOrder) ? QLatin1String("ascending") : QLatin1String("descending")) : QVariant()));
611 settings.save();
612 }
613
handleOptionChanged(int identifier,const QVariant & value)614 void ItemViewWidget::handleOptionChanged(int identifier, const QVariant &value)
615 {
616 if (identifier == SettingsManager::Interface_ShowScrollBarsOption)
617 {
618 setHorizontalScrollBarPolicy(value.toBool() ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff);
619 setVerticalScrollBarPolicy(value.toBool() ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff);
620 }
621 }
622
notifySelectionChanged()623 void ItemViewWidget::notifySelectionChanged()
624 {
625 if (m_sourceModel)
626 {
627 emit canMoveRowUpChanged(canMoveRowUp());
628 emit canMoveRowDownChanged(canMoveRowDown());
629 }
630
631 emit needsActionsUpdate();
632 }
633
updateFilter()634 void ItemViewWidget::updateFilter()
635 {
636 for (int i = 0; i < getRowCount(); ++i)
637 {
638 applyFilter(getIndex(i, 0));
639 }
640 }
641
updateSize()642 void ItemViewWidget::updateSize()
643 {
644 if (!m_headerWidget || !model())
645 {
646 return;
647 }
648
649 int maximumSectionWidth(0);
650 int minimumTotalWidth(0);
651 QVector<int> widestSections;
652
653 for (int i = 0; i < m_headerWidget->count(); ++i)
654 {
655 const int width(model()->headerData(i, Qt::Horizontal, HeaderViewWidget::WidthRole).toInt());
656
657 if (width > 0)
658 {
659 m_headerWidget->resizeSection(i, width);
660
661 if (width > maximumSectionWidth)
662 {
663 widestSections = {i};
664
665 maximumSectionWidth = width;
666 }
667 else if (width == maximumSectionWidth)
668 {
669 widestSections.append(i);
670 }
671
672 minimumTotalWidth += width;
673 }
674 else
675 {
676 minimumTotalWidth += m_headerWidget->defaultSectionSize();
677 }
678 }
679
680 if (!widestSections.isEmpty() && minimumTotalWidth < m_headerWidget->width())
681 {
682 const int sectionWidth((m_headerWidget->width() - minimumTotalWidth) / widestSections.count());
683
684 for (int i = 0; i < widestSections.count(); ++i)
685 {
686 m_headerWidget->resizeSection(widestSections.at(i), (m_headerWidget->sectionSize(widestSections.at(i)) + sectionWidth));
687 }
688 }
689 }
690
setSort(int column,Qt::SortOrder order)691 void ItemViewWidget::setSort(int column, Qt::SortOrder order)
692 {
693 if (column == m_sortColumn && order == m_sortOrder)
694 {
695 return;
696 }
697
698 m_sortColumn = column;
699 m_sortOrder = order;
700
701 const int sortRole(m_proxyModel ? m_proxyModel->sortRole() : (m_sourceModel ? m_sourceModel->sortRole() : Qt::DisplayRole));
702
703 if (m_sortRoleMapping.contains(column))
704 {
705 if (m_proxyModel)
706 {
707 m_proxyModel->setSortRole(m_sortRoleMapping[column]);
708 }
709 else if (m_sourceModel)
710 {
711 m_sourceModel->setSortRole(m_sortRoleMapping[column]);
712 }
713 }
714
715 sortByColumn(column, order);
716 update();
717 saveState();
718
719 if (m_proxyModel)
720 {
721 m_proxyModel->setSortRole(sortRole);
722 }
723 else if (m_sourceModel)
724 {
725 m_sourceModel->setSortRole(sortRole);
726 }
727
728 emit sortChanged(column, order);
729 }
730
setColumnVisibility(int column,bool hide)731 void ItemViewWidget::setColumnVisibility(int column, bool hide)
732 {
733 if (column < 0)
734 {
735 for (int i = 0; i < getColumnCount(); ++i)
736 {
737 setColumnHidden(i, hide);
738 }
739 }
740 else
741 {
742 setColumnHidden(column, hide);
743 }
744
745 saveState();
746 }
747
setExclusive(bool isExclusive)748 void ItemViewWidget::setExclusive(bool isExclusive)
749 {
750 m_isExclusive = isExclusive;
751
752 update();
753 }
754
setFilterString(const QString & filter)755 void ItemViewWidget::setFilterString(const QString &filter)
756 {
757 if (filter == m_filterString || !model())
758 {
759 return;
760 }
761
762 if (m_filterString.isEmpty())
763 {
764 connect(model(), &QAbstractItemModel::rowsInserted, this, &ItemViewWidget::updateFilter);
765 connect(model(), &QAbstractItemModel::rowsMoved, this, &ItemViewWidget::updateFilter);
766 connect(model(), &QAbstractItemModel::rowsRemoved, this, &ItemViewWidget::updateFilter);
767 }
768
769 m_canGatherExpanded = m_filterString.isEmpty();
770 m_filterString = filter;
771
772 updateFilter();
773
774 if (m_filterString.isEmpty())
775 {
776 m_expandedBranches.clear();
777
778 disconnect(model(), &QAbstractItemModel::rowsInserted, this, &ItemViewWidget::updateFilter);
779 disconnect(model(), &QAbstractItemModel::rowsMoved, this, &ItemViewWidget::updateFilter);
780 disconnect(model(), &QAbstractItemModel::rowsRemoved, this, &ItemViewWidget::updateFilter);
781 }
782 }
783
setFilterRoles(const QSet<int> & roles)784 void ItemViewWidget::setFilterRoles(const QSet<int> &roles)
785 {
786 m_filterRoles = roles;
787 }
788
setData(const QModelIndex & index,const QVariant & value,int role)789 void ItemViewWidget::setData(const QModelIndex &index, const QVariant &value, int role)
790 {
791 if (m_sourceModel)
792 {
793 m_sourceModel->setData(index, value, role);
794 }
795 }
796
setModel(QAbstractItemModel * model)797 void ItemViewWidget::setModel(QAbstractItemModel *model)
798 {
799 setModel(model, false);
800 }
801
setModel(QAbstractItemModel * model,bool useSortProxy)802 void ItemViewWidget::setModel(QAbstractItemModel *model, bool useSortProxy)
803 {
804 QAbstractItemModel *activeModel(model);
805
806 if (model && useSortProxy)
807 {
808 m_proxyModel = new QSortFilterProxyModel(this);
809 m_proxyModel->setSourceModel(model);
810 m_proxyModel->setDynamicSortFilter(true);
811
812 activeModel = m_proxyModel;
813 }
814 else if (m_proxyModel)
815 {
816 m_proxyModel->deleteLater();
817 m_proxyModel = nullptr;
818 }
819
820 m_sourceModel = qobject_cast<QStandardItemModel*>(model);
821
822 QTreeView::setModel(activeModel);
823
824 if (!model)
825 {
826 emit needsActionsUpdate();
827
828 return;
829 }
830
831 if (isVisible())
832 {
833 ensureInitialized();
834 }
835
836 if (!model->parent())
837 {
838 model->setParent(this);
839 }
840
841 if (m_sourceModel)
842 {
843 connect(m_sourceModel, &QStandardItemModel::itemChanged, this, &ItemViewWidget::notifySelectionChanged);
844 }
845
846 emit needsActionsUpdate();
847
848 connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &ItemViewWidget::notifySelectionChanged);
849 connect(model, &QAbstractItemModel::dataChanged, this, &ItemViewWidget::markAsModified);
850 connect(model, &QAbstractItemModel::headerDataChanged, this, &ItemViewWidget::updateSize);
851 connect(model, &QAbstractItemModel::rowsInserted, this, &ItemViewWidget::markAsModified);
852 connect(model, &QAbstractItemModel::rowsRemoved, this, &ItemViewWidget::markAsModified);
853 connect(model, &QAbstractItemModel::rowsMoved, this, &ItemViewWidget::markAsModified);
854 }
855
setSortRoleMapping(const QMap<int,int> & mapping)856 void ItemViewWidget::setSortRoleMapping(const QMap<int, int> &mapping)
857 {
858 m_sortRoleMapping = mapping;
859 }
860
setViewMode(ItemViewWidget::ViewMode mode)861 void ItemViewWidget::setViewMode(ItemViewWidget::ViewMode mode)
862 {
863 m_viewMode = mode;
864
865 setIndentation((mode == TreeView) ? style()->pixelMetric(QStyle::PM_TreeViewIndentation) : 0);
866 }
867
setModified(bool isModified)868 void ItemViewWidget::setModified(bool isModified)
869 {
870 m_isModified = isModified;
871
872 if (isModified)
873 {
874 emit modified();
875 }
876
877 emit needsActionsUpdate();
878 }
879
getSourceModel() const880 QStandardItemModel* ItemViewWidget::getSourceModel() const
881 {
882 return m_sourceModel;
883 }
884
getProxyModel() const885 QSortFilterProxyModel* ItemViewWidget::getProxyModel() const
886 {
887 return m_proxyModel;
888 }
889
getItem(const QModelIndex & index) const890 QStandardItem* ItemViewWidget::getItem(const QModelIndex &index) const
891 {
892 return(m_sourceModel ? m_sourceModel->itemFromIndex(index) : nullptr);
893 }
894
getItem(int row,int column,const QModelIndex & parent) const895 QStandardItem* ItemViewWidget::getItem(int row, int column, const QModelIndex &parent) const
896 {
897 return(m_sourceModel ? m_sourceModel->itemFromIndex(getIndex(row, column, parent)) : nullptr);
898 }
899
getCheckedIndex(const QModelIndex & parent) const900 QModelIndex ItemViewWidget::getCheckedIndex(const QModelIndex &parent) const
901 {
902 if (!m_isExclusive || !m_sourceModel)
903 {
904 return {};
905 }
906
907 for (int i = 0; i < m_sourceModel->rowCount(parent); ++i)
908 {
909 const QModelIndex index(m_sourceModel->index(i, 0, parent));
910
911 if (index.data(Qt::CheckStateRole).toInt() == Qt::Checked)
912 {
913 return index;
914 }
915
916 const QModelIndex result(getCheckedIndex(index));
917
918 if (result.isValid())
919 {
920 return result;
921 }
922 }
923
924 return {};
925 }
926
getCurrentIndex(int column) const927 QModelIndex ItemViewWidget::getCurrentIndex(int column) const
928 {
929 if (!selectionModel() || !selectionModel()->hasSelection())
930 {
931 return {};
932 }
933
934 if (column >= 0)
935 {
936 return currentIndex().sibling(currentIndex().row(), column);
937 }
938
939 return currentIndex();
940 }
941
getIndex(int row,int column,const QModelIndex & parent) const942 QModelIndex ItemViewWidget::getIndex(int row, int column, const QModelIndex &parent) const
943 {
944 return (model() ? model()->index(row, column, parent) : QModelIndex());
945 }
946
sizeHint() const947 QSize ItemViewWidget::sizeHint() const
948 {
949 const QSize size(QTreeView::sizeHint());
950
951 if (m_sourceModel && m_sourceModel->columnCount() == 1)
952 {
953 return QSize((sizeHintForColumn(0) + (frameWidth() * 2)), size.height());
954 }
955
956 return size;
957 }
958
getViewMode() const959 ItemViewWidget::ViewMode ItemViewWidget::getViewMode() const
960 {
961 return m_viewMode;
962 }
963
getSortOrder() const964 Qt::SortOrder ItemViewWidget::getSortOrder() const
965 {
966 return m_sortOrder;
967 }
968
getSortColumn() const969 int ItemViewWidget::getSortColumn() const
970 {
971 return m_sortColumn;
972 }
973
getCurrentRow() const974 int ItemViewWidget::getCurrentRow() const
975 {
976 return ((selectionModel() && selectionModel()->hasSelection()) ? currentIndex().row() : -1);
977 }
978
getRowCount(const QModelIndex & parent) const979 int ItemViewWidget::getRowCount(const QModelIndex &parent) const
980 {
981 return (model() ? model()->rowCount(parent) : 0);
982 }
983
getColumnCount(const QModelIndex & parent) const984 int ItemViewWidget::getColumnCount(const QModelIndex &parent) const
985 {
986 return (model() ? model()->columnCount(parent) : 0);
987 }
988
canMoveRowUp() const989 bool ItemViewWidget::canMoveRowUp() const
990 {
991 return (currentIndex().row() > 0 && getRowCount() > 1);
992 }
993
canMoveRowDown() const994 bool ItemViewWidget::canMoveRowDown() const
995 {
996 const int currentRow(currentIndex().row());
997 const int rowCount(getRowCount());
998
999 return (currentRow >= 0 && rowCount > 1 && currentRow < (rowCount - 1));
1000 }
1001
isExclusive() const1002 bool ItemViewWidget::isExclusive() const
1003 {
1004 return m_isExclusive;
1005 }
1006
applyFilter(const QModelIndex & index,bool parentHasMatch)1007 bool ItemViewWidget::applyFilter(const QModelIndex &index, bool parentHasMatch)
1008 {
1009 const bool isFolder(!index.flags().testFlag(Qt::ItemNeverHasChildren));
1010 const bool hasFilter(!m_filterString.isEmpty());
1011 bool hasMatch(!hasFilter || (isFolder && parentHasMatch));
1012
1013 if (!hasMatch)
1014 {
1015 for (int i = 0; i < getColumnCount(index.parent()); ++i)
1016 {
1017 const QModelIndex childIndex(index.sibling(index.row(), i));
1018
1019 if (!childIndex.isValid())
1020 {
1021 continue;
1022 }
1023
1024 QSet<int>::iterator iterator;
1025
1026 for (iterator = m_filterRoles.begin(); iterator != m_filterRoles.end(); ++iterator)
1027 {
1028 const QVariant roleData(childIndex.data(*iterator));
1029
1030 if (!roleData.isNull() && roleData.toString().contains(m_filterString, Qt::CaseInsensitive))
1031 {
1032 hasMatch = true;
1033
1034 break;
1035 }
1036 }
1037
1038 if (hasMatch)
1039 {
1040 break;
1041 }
1042 }
1043 }
1044
1045 if (isFolder)
1046 {
1047 if (m_canGatherExpanded && isExpanded(index))
1048 {
1049 m_expandedBranches.insert(index);
1050 }
1051
1052 const int rowCount(getRowCount(index));
1053 bool folderHasMatch(false);
1054
1055 for (int i = 0; i < rowCount; ++i)
1056 {
1057 if (applyFilter(index.child(i, 0), hasMatch))
1058 {
1059 folderHasMatch = true;
1060 }
1061 }
1062
1063 if (!hasMatch)
1064 {
1065 hasMatch = folderHasMatch;
1066 }
1067 }
1068
1069 setRowHidden(index.row(), index.parent(), (hasFilter ? (!(hasMatch || parentHasMatch) || (isFolder && getRowCount(index) == 0)) : false));
1070
1071 if (isFolder)
1072 {
1073 setExpanded(index, ((hasMatch && hasFilter) || (!hasFilter && m_expandedBranches.contains(index))));
1074 }
1075
1076 return hasMatch;
1077 }
1078
isModified() const1079 bool ItemViewWidget::isModified() const
1080 {
1081 return m_isModified;
1082 }
1083
1084 }
1085