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 &current, 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