File indexing completed on 2025-04-27 03:58:29
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2010-01-16 0007 * Description : Qt item view for images 0008 * 0009 * SPDX-FileCopyrightText: 2009-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0010 * SPDX-FileCopyrightText: 2011-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "itemviewcategorized.h" 0017 0018 // Qt includes 0019 0020 #include <QClipboard> 0021 #include <QHelpEvent> 0022 #include <QScrollBar> 0023 #include <QSortFilterProxyModel> 0024 #include <QStyle> 0025 #include <QApplication> 0026 0027 // Local includes 0028 0029 #include "digikam_debug.h" 0030 #include "thememanager.h" 0031 #include "ditemdelegate.h" 0032 #include "abstractitemdragdrophandler.h" 0033 #include "itemviewtooltip.h" 0034 0035 namespace Digikam 0036 { 0037 0038 class Q_DECL_HIDDEN ItemViewCategorized::Private 0039 { 0040 public: 0041 0042 explicit Private(ItemViewCategorized* const q) 0043 : delegate (nullptr), 0044 toolTip (nullptr), 0045 notificationToolTip (nullptr), 0046 showToolTip (false), 0047 usePointingHand (true), 0048 scrollStepFactor (10), 0049 currentMouseEvent (nullptr), 0050 ensureOneSelectedItem (false), 0051 ensureInitialSelectedItem (false), 0052 scrollCurrentToCenter (false), 0053 mouseButtonPressed (Qt::NoButton), 0054 hintAtSelectionRow (-1), 0055 q (q) 0056 { 0057 } 0058 0059 QModelIndex scrollPositionHint() const; 0060 0061 public: 0062 0063 DItemDelegate* delegate; 0064 ItemViewToolTip* toolTip; 0065 ItemViewToolTip* notificationToolTip; 0066 bool showToolTip; 0067 bool usePointingHand; 0068 int scrollStepFactor; 0069 0070 QMouseEvent* currentMouseEvent; 0071 bool ensureOneSelectedItem; 0072 bool ensureInitialSelectedItem; 0073 bool scrollCurrentToCenter; 0074 Qt::MouseButton mouseButtonPressed; 0075 QPersistentModelIndex hintAtSelectionIndex; 0076 int hintAtSelectionRow; 0077 QPersistentModelIndex hintAtScrollPosition; 0078 0079 ItemViewCategorized* const q; 0080 }; 0081 0082 QModelIndex ItemViewCategorized::Private::scrollPositionHint() const 0083 { 0084 if (q->verticalScrollBar()->value() == q->verticalScrollBar()->minimum()) 0085 { 0086 return QModelIndex(); 0087 } 0088 0089 QModelIndex hint = q->currentIndex(); 0090 0091 // If the user scrolled, do not take current item, but first visible 0092 0093 if (!hint.isValid() || !q->viewport()->rect().intersects(q->visualRect(hint))) 0094 { 0095 QList<QModelIndex> visibleIndexes = q->categorizedIndexesIn(q->viewport()->rect()); 0096 0097 if (!visibleIndexes.isEmpty()) 0098 { 0099 hint = visibleIndexes.first(); 0100 } 0101 } 0102 0103 return hint; 0104 } 0105 0106 // ------------------------------------------------------------------------------- 0107 0108 ItemViewCategorized::ItemViewCategorized(QWidget* const parent) 0109 : DCategorizedView(parent), 0110 d (new Private(this)) 0111 { 0112 setViewMode(QListView::IconMode); 0113 setLayoutDirection(Qt::LeftToRight); 0114 setFlow(QListView::LeftToRight); 0115 setResizeMode(QListView::Adjust); 0116 setMovement(QListView::Static); 0117 setWrapping(true); 0118 0119 // important optimization for layouting 0120 0121 setBatchSize(100); 0122 setUniformItemSizes(true); 0123 setLayoutMode(QListView::Batched); 0124 0125 // disable "feature" from DCategorizedView 0126 0127 setDrawDraggedItems(false); 0128 0129 setSelectionMode(QAbstractItemView::ExtendedSelection); 0130 0131 setDragEnabled(true); 0132 setEditTriggers(QAbstractItemView::NoEditTriggers); 0133 viewport()->setAcceptDrops(true); 0134 setMouseTracking(true); 0135 0136 connect(this, SIGNAL(clicked(QModelIndex)), 0137 this, SLOT(slotClicked(QModelIndex))); 0138 0139 connect(this, SIGNAL(entered(QModelIndex)), 0140 this, SLOT(slotEntered(QModelIndex))); 0141 0142 // --- NOTE: use dynamic binding as slots below are virtual methods which can be re-implemented in derived classes. 0143 0144 connect(this, &ItemViewCategorized::activated, 0145 this, &ItemViewCategorized::slotActivated); 0146 0147 connect(ThemeManager::instance(), &ThemeManager::signalThemeChanged, 0148 this, &ItemViewCategorized::slotThemeChanged); 0149 } 0150 0151 ItemViewCategorized::~ItemViewCategorized() 0152 { 0153 0154 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0155 0156 delete d->currentMouseEvent; 0157 d->currentMouseEvent = nullptr; 0158 0159 #endif 0160 0161 delete d; 0162 } 0163 0164 void ItemViewCategorized::setToolTip(ItemViewToolTip* tip) 0165 { 0166 d->toolTip = tip; 0167 } 0168 0169 void ItemViewCategorized::setItemDelegate(DItemDelegate* delegate) 0170 { 0171 if (d->delegate == delegate) 0172 { 0173 return; 0174 } 0175 0176 if (d->delegate) 0177 { 0178 disconnect(d->delegate, SIGNAL(gridSizeChanged(QSize)), 0179 this, SLOT(slotGridSizeChanged(QSize))); 0180 } 0181 0182 d->delegate = delegate; 0183 DCategorizedView::setItemDelegate(d->delegate); 0184 0185 connect(d->delegate, SIGNAL(gridSizeChanged(QSize)), 0186 this, SLOT(slotGridSizeChanged(QSize))); 0187 } 0188 0189 void ItemViewCategorized::setSpacing(int spacing) 0190 { 0191 d->delegate->setSpacing(spacing); 0192 } 0193 0194 void ItemViewCategorized::setUsePointingHandCursor(bool useCursor) 0195 { 0196 d->usePointingHand = useCursor; 0197 } 0198 0199 void ItemViewCategorized::setScrollStepGranularity(int factor) 0200 { 0201 d->scrollStepFactor = qMax(1, factor); 0202 } 0203 0204 DItemDelegate* ItemViewCategorized::delegate() const 0205 { 0206 return d->delegate; 0207 } 0208 0209 int ItemViewCategorized::numberOfSelectedIndexes() const 0210 { 0211 return selectedIndexes().size(); 0212 } 0213 0214 void ItemViewCategorized::toFirstIndex() 0215 { 0216 QModelIndex index = moveCursor(MoveHome, Qt::NoModifier); 0217 clearSelection(); 0218 setCurrentIndex(index); 0219 scrollToTop(); 0220 } 0221 0222 void ItemViewCategorized::toLastIndex() 0223 { 0224 QModelIndex index = moveCursor(MoveEnd, Qt::NoModifier); 0225 clearSelection(); 0226 setCurrentIndex(index); 0227 scrollToBottom(); 0228 } 0229 0230 void ItemViewCategorized::toNextIndex() 0231 { 0232 toIndex(moveCursor(MoveNext, Qt::NoModifier)); 0233 } 0234 0235 void ItemViewCategorized::toPreviousIndex() 0236 { 0237 toIndex(moveCursor(MovePrevious, Qt::NoModifier)); 0238 } 0239 0240 void ItemViewCategorized::toIndex(const QModelIndex& index) 0241 { 0242 if (!index.isValid()) 0243 { 0244 return; 0245 } 0246 0247 clearSelection(); 0248 setCurrentIndex(index); 0249 scrollTo(index); 0250 } 0251 0252 void ItemViewCategorized::awayFromSelection() 0253 { 0254 QItemSelection selection = selectionModel()->selection(); 0255 0256 if (selection.isEmpty()) 0257 { 0258 return; 0259 } 0260 0261 const QModelIndex first = model()->index(0, 0); 0262 const QModelIndex last = model()->index(model()->rowCount() - 1, 0); 0263 0264 if (selection.contains(first) && selection.contains(last)) 0265 { 0266 QItemSelection remaining(first, last); 0267 remaining.merge(selection, QItemSelectionModel::Toggle); 0268 QList<QModelIndex> indexes = remaining.indexes(); 0269 0270 if (indexes.isEmpty()) 0271 { 0272 clearSelection(); 0273 setCurrentIndex(QModelIndex()); 0274 } 0275 else 0276 { 0277 toIndex(remaining.indexes().first()); 0278 } 0279 } 0280 else if (selection.contains(last)) 0281 { 0282 setCurrentIndex(selection.indexes().first()); 0283 toPreviousIndex(); 0284 } 0285 else 0286 { 0287 setCurrentIndex(selection.indexes().last()); 0288 toNextIndex(); 0289 } 0290 } 0291 0292 void ItemViewCategorized::scrollToRelaxed(const QModelIndex& index, QAbstractItemView::ScrollHint hint) 0293 { 0294 if (viewport()->rect().intersects(visualRect(index))) 0295 { 0296 return; 0297 } 0298 0299 scrollTo(index, hint); 0300 } 0301 0302 void ItemViewCategorized::invertSelection() 0303 { 0304 const QModelIndex topLeft = model()->index(0, 0); 0305 const QModelIndex bottomRight = model()->index(model()->rowCount() - 1, 0); 0306 0307 const QItemSelection selection(topLeft, bottomRight); 0308 selectionModel()->select(selection, QItemSelectionModel::Toggle); 0309 } 0310 0311 void ItemViewCategorized::setSelectedIndexes(const QList<QModelIndex>& indexes) 0312 { 0313 if (selectedIndexes() == indexes) 0314 { 0315 return; 0316 } 0317 0318 QItemSelection mySelection; 0319 0320 Q_FOREACH (const QModelIndex& index, indexes) 0321 { 0322 mySelection.select(index, index); 0323 } 0324 0325 selectionModel()->select(mySelection, QItemSelectionModel::ClearAndSelect); 0326 } 0327 0328 void ItemViewCategorized::setToolTipEnabled(bool enable) 0329 { 0330 d->showToolTip = enable; 0331 } 0332 0333 bool ItemViewCategorized::isToolTipEnabled() const 0334 { 0335 return d->showToolTip; 0336 } 0337 0338 void ItemViewCategorized::slotThemeChanged() 0339 { 0340 viewport()->update(); 0341 } 0342 0343 void ItemViewCategorized::slotSetupChanged() 0344 { 0345 viewport()->update(); 0346 } 0347 0348 void ItemViewCategorized::slotGridSizeChanged(const QSize& gridSize) 0349 { 0350 setGridSize(gridSize); 0351 0352 if (!gridSize.isNull()) 0353 { 0354 horizontalScrollBar()->setSingleStep(gridSize.width() / d->scrollStepFactor); 0355 verticalScrollBar()->setSingleStep(gridSize.height() / d->scrollStepFactor); 0356 } 0357 } 0358 0359 void ItemViewCategorized::updateDelegateSizes() 0360 { 0361 QStyleOptionViewItem option; 0362 0363 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0364 0365 initViewItemOption(&option); 0366 0367 #else 0368 0369 option = viewOptions(); 0370 0371 #endif 0372 0373 /* 0374 int frameAroundContents = 0; 0375 0376 if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) 0377 { 0378 frameAroundContents = style()->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2; 0379 } 0380 0381 const int contentWidth = viewport()->width() - 1 0382 - frameAroundContents 0383 - style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, verticalScrollBar()); 0384 const int contentHeight = viewport()->height() - 1 0385 - frameAroundContents 0386 - style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, horizontalScrollBar()); 0387 option.rect = QRect(0, 0, contentWidth, contentHeight); 0388 */ 0389 option.rect = QRect(QPoint(0, 0), viewport()->size()); 0390 d->delegate->setDefaultViewOptions(option); 0391 } 0392 0393 void ItemViewCategorized::slotActivated(const QModelIndex& index) 0394 { 0395 Qt::KeyboardModifiers modifiers = Qt::NoModifier; 0396 Qt::MouseButtons buttons = Qt::NoButton; 0397 (void)modifiers; // prevent cppcheck warning. 0398 (void)buttons; // prevent cppcheck warning. 0399 0400 if (d->currentMouseEvent) 0401 { 0402 modifiers = d->currentMouseEvent->modifiers(); 0403 buttons = d->currentMouseEvent->buttons(); 0404 } 0405 else 0406 { 0407 modifiers = QApplication::queryKeyboardModifiers(); 0408 buttons = QApplication::mouseButtons(); 0409 } 0410 0411 // Ignore activation if Ctrl or Shift is pressed (for selection) 0412 0413 const bool shiftKeyPressed = modifiers & Qt::ShiftModifier; 0414 const bool controlKeyPressed = modifiers & Qt::ControlModifier; 0415 const bool rightClickPressed = buttons & Qt::RightButton; 0416 0417 if (shiftKeyPressed || controlKeyPressed || rightClickPressed) 0418 { 0419 return; 0420 } 0421 0422 if (d->currentMouseEvent) 0423 { 0424 // if the activation is caused by mouse click (not keyboard) 0425 // we need to check the hot area 0426 0427 if (d->currentMouseEvent->isAccepted() && 0428 !d->delegate->acceptsActivation(d->currentMouseEvent->pos(), visualRect(index), index)) 0429 { 0430 return; 0431 } 0432 } 0433 0434 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0435 0436 delete d->currentMouseEvent; 0437 0438 #endif 0439 0440 d->currentMouseEvent = nullptr; 0441 indexActivated(index, modifiers); 0442 } 0443 0444 void ItemViewCategorized::slotClicked(const QModelIndex& index) 0445 { 0446 if (d->currentMouseEvent) 0447 { 0448 Q_EMIT clicked(d->currentMouseEvent, index); 0449 } 0450 } 0451 0452 void ItemViewCategorized::slotEntered(const QModelIndex& index) 0453 { 0454 if (d->currentMouseEvent) 0455 { 0456 Q_EMIT entered(d->currentMouseEvent, index); 0457 } 0458 } 0459 0460 void ItemViewCategorized::reset() 0461 { 0462 DCategorizedView::reset(); 0463 0464 // FIXME : Emitting this causes a crash importstackedview, because the model is not yet set. 0465 // atm there's a check against null models though. 0466 0467 Q_EMIT selectionChanged(); 0468 Q_EMIT selectionCleared(); 0469 0470 d->ensureInitialSelectedItem = true; 0471 d->hintAtScrollPosition = QModelIndex(); 0472 d->hintAtSelectionIndex = QModelIndex(); 0473 d->hintAtSelectionRow = -1; 0474 verticalScrollBar()->setValue(verticalScrollBar()->minimum()); 0475 horizontalScrollBar()->setValue(horizontalScrollBar()->minimum()); 0476 } 0477 0478 void ItemViewCategorized::selectionChanged(const QItemSelection& selectedItems, const QItemSelection& deselectedItems) 0479 { 0480 DCategorizedView::selectionChanged(selectedItems, deselectedItems); 0481 0482 Q_EMIT selectionChanged(); 0483 0484 if (!selectionModel()->hasSelection()) 0485 { 0486 Q_EMIT selectionCleared(); 0487 } 0488 0489 userInteraction(); 0490 } 0491 0492 void ItemViewCategorized::rowsInserted(const QModelIndex& parent, int start, int end) 0493 { 0494 DCategorizedView::rowsInserted(parent, start, end); 0495 0496 if (start == 0) 0497 { 0498 ensureSelectionAfterChanges(); 0499 } 0500 } 0501 0502 void ItemViewCategorized::rowsRemoved(const QModelIndex& parent, int start, int end) 0503 { 0504 DCategorizedView::rowsRemoved(parent, start, end); 0505 0506 if (d->scrollCurrentToCenter) 0507 { 0508 scrollTo(currentIndex(), QAbstractItemView::PositionAtCenter); 0509 } 0510 } 0511 0512 void ItemViewCategorized::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) 0513 { 0514 DCategorizedView::rowsAboutToBeRemoved(parent, start, end); 0515 0516 // Ensure one selected item 0517 0518 int totalToRemove = end - start + 1; 0519 bool remainingRows = model()->rowCount(parent) > totalToRemove; 0520 0521 if (!remainingRows) 0522 { 0523 return; 0524 } 0525 0526 QItemSelection removed(model()->index(start, 0), model()->index(end, 0)); 0527 0528 if (selectionModel()->hasSelection()) 0529 { 0530 // find out which selected indexes are left after rows are removed 0531 0532 QItemSelection selected = selectionModel()->selection(); 0533 QModelIndex current = currentIndex(); 0534 QModelIndex indexToAnchor; 0535 0536 if (selected.contains(current)) 0537 { 0538 indexToAnchor = current; 0539 } 0540 else if (!selected.isEmpty()) 0541 { 0542 indexToAnchor = selected.first().topLeft(); 0543 } 0544 0545 selected.merge(removed, QItemSelectionModel::Deselect); 0546 0547 if (selected.isEmpty()) 0548 { 0549 QModelIndex newCurrent = nextIndexHint(indexToAnchor, removed.first() /*a range*/); 0550 setCurrentIndex(newCurrent); 0551 } 0552 } 0553 0554 QModelIndex hint = d->scrollPositionHint(); 0555 0556 if (removed.contains(hint)) 0557 { 0558 d->hintAtScrollPosition = nextIndexHint(hint, removed.first() /*a range*/); 0559 } 0560 } 0561 0562 void ItemViewCategorized::layoutAboutToBeChanged() 0563 { 0564 if (selectionModel()) 0565 { 0566 d->ensureOneSelectedItem = selectionModel()->hasSelection(); 0567 } 0568 else 0569 { 0570 qCWarning(DIGIKAM_GENERAL_LOG) << "Called without selection model, check whether the models are ok.."; 0571 } 0572 0573 QModelIndex current = currentIndex(); 0574 0575 // store some hints so that if all selected items were removed do not need to default to 0,0. 0576 0577 if (d->ensureOneSelectedItem) 0578 { 0579 QItemSelection currentSelection = selectionModel()->selection(); 0580 QModelIndex indexToAnchor; 0581 0582 if (currentSelection.contains(current)) 0583 { 0584 indexToAnchor = current; 0585 } 0586 else if (!currentSelection.isEmpty()) 0587 { 0588 indexToAnchor = currentSelection.first().topLeft(); 0589 } 0590 0591 if (indexToAnchor.isValid()) 0592 { 0593 d->hintAtSelectionRow = indexToAnchor.row(); 0594 d->hintAtSelectionIndex = nextIndexHint(indexToAnchor, QItemSelectionRange(indexToAnchor)); 0595 } 0596 } 0597 0598 // some precautions to keep current scroll position 0599 0600 d->hintAtScrollPosition = d->scrollPositionHint(); 0601 } 0602 0603 QModelIndex ItemViewCategorized::nextIndexHint(const QModelIndex& indexToAnchor, const QItemSelectionRange& removed) const 0604 { 0605 Q_UNUSED(indexToAnchor); 0606 0607 if (removed.bottomRight().row() == (model()->rowCount() - 1)) 0608 { 0609 if (removed.topLeft().row() == 0) 0610 { 0611 return QModelIndex(); 0612 } 0613 0614 return model()->index(removed.topLeft().row() - 1, 0); // last remaining, no next one left 0615 } 0616 else 0617 { 0618 return model()->index(removed.bottomRight().row() + 1, 0); // next remaining 0619 } 0620 } 0621 0622 void ItemViewCategorized::layoutWasChanged() 0623 { 0624 // connected queued to layoutChanged() 0625 0626 ensureSelectionAfterChanges(); 0627 0628 if (d->hintAtScrollPosition.isValid()) 0629 { 0630 scrollToRelaxed(d->hintAtScrollPosition); 0631 d->hintAtScrollPosition = QModelIndex(); 0632 } 0633 else 0634 { 0635 scrollToRelaxed(currentIndex()); 0636 } 0637 } 0638 0639 void ItemViewCategorized::userInteraction() 0640 { 0641 // as soon as the user did anything affecting selection, we don't interfere anymore 0642 0643 d->ensureInitialSelectedItem = false; 0644 d->hintAtSelectionIndex = QModelIndex(); 0645 } 0646 0647 void ItemViewCategorized::ensureSelectionAfterChanges() 0648 { 0649 if (d->ensureInitialSelectedItem && model()->rowCount()) 0650 { 0651 // Ensure the item (0,0) is selected, if the model was reset previously 0652 // and the user did not change the selection since reset. 0653 // Caveat: Item at (0,0) may have changed. 0654 0655 bool hadInitial = d->ensureInitialSelectedItem; 0656 d->ensureInitialSelectedItem = false; 0657 d->ensureOneSelectedItem = false; 0658 QModelIndex index = model()->index(0, 0); 0659 0660 if (index.isValid()) 0661 { 0662 selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Clear); 0663 setCurrentIndex(index); 0664 0665 // we want ensureInitial set to false if and only if the selection 0666 // is done from any other place than the previous line (i.e., by user action) 0667 // Effect: we select whatever is the current index(0,0) 0668 0669 if (hadInitial) 0670 { 0671 d->ensureInitialSelectedItem = true; 0672 } 0673 } 0674 } 0675 else if (d->ensureOneSelectedItem) 0676 { 0677 // ensure we have a selection if there was one before 0678 0679 d->ensureOneSelectedItem = false; 0680 0681 if (model()->rowCount() && selectionModel()->selection().isEmpty()) 0682 { 0683 QModelIndex index; 0684 0685 if (d->hintAtSelectionIndex.isValid()) 0686 { 0687 index = d->hintAtSelectionIndex; 0688 } 0689 else if (d->hintAtSelectionRow != -1) 0690 { 0691 index = model()->index(qMin(model()->rowCount(), d->hintAtSelectionRow), 0); 0692 } 0693 else 0694 { 0695 index = currentIndex(); 0696 } 0697 0698 if (!index.isValid()) 0699 { 0700 index = model()->index(0, 0); 0701 } 0702 0703 d->hintAtSelectionRow = -1; 0704 d->hintAtSelectionIndex = QModelIndex(); 0705 0706 if (index.isValid()) 0707 { 0708 setCurrentIndex(index); 0709 selectionModel()->select(index, QItemSelectionModel::SelectCurrent); 0710 } 0711 } 0712 } 0713 } 0714 0715 QModelIndex ItemViewCategorized::indexForCategoryAt(const QPoint& pos) const 0716 { 0717 return categoryAt(pos); 0718 } 0719 0720 QModelIndex ItemViewCategorized::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) 0721 { 0722 QModelIndex current = currentIndex(); 0723 0724 if (!current.isValid()) 0725 { 0726 return DCategorizedView::moveCursor(cursorAction, modifiers); 0727 } 0728 0729 // We want a simple wrapping navigation. 0730 // Default behavior we do not want: right/left does never change row; Next/Previous is equivalent to Down/Up 0731 0732 switch (cursorAction) 0733 { 0734 case MoveNext: 0735 case MoveRight: 0736 { 0737 QModelIndex next = model()->index(current.row() + 1, 0); 0738 0739 if (next.isValid()) 0740 { 0741 return next; 0742 } 0743 else 0744 { 0745 return current; 0746 } 0747 0748 break; 0749 } 0750 0751 case MovePrevious: 0752 case MoveLeft: 0753 { 0754 QModelIndex previous = model()->index(current.row() - 1, 0); 0755 0756 if (previous.isValid()) 0757 { 0758 return previous; 0759 } 0760 else 0761 { 0762 return current; 0763 } 0764 0765 break; 0766 } 0767 0768 default: 0769 { 0770 break; 0771 } 0772 } 0773 0774 return DCategorizedView::moveCursor(cursorAction, modifiers); 0775 } 0776 0777 0778 void ItemViewCategorized::showContextMenuOnIndex(QContextMenuEvent*, const QModelIndex&) 0779 { 0780 // implemented in subclass 0781 } 0782 0783 void ItemViewCategorized::showContextMenu(QContextMenuEvent*) 0784 { 0785 // implemented in subclass 0786 } 0787 0788 void ItemViewCategorized::indexActivated(const QModelIndex&, Qt::KeyboardModifiers) 0789 { 0790 } 0791 0792 bool ItemViewCategorized::showToolTip(const QModelIndex& index, QStyleOptionViewItem& option, QHelpEvent* he) 0793 { 0794 QRect innerRect; 0795 QPoint pos; 0796 0797 if (he) 0798 { 0799 pos = he->pos(); 0800 } 0801 else 0802 { 0803 pos = option.rect.center(); 0804 } 0805 0806 if (d->delegate->acceptsToolTip(pos, option.rect, index, &innerRect)) 0807 { 0808 if (!innerRect.isNull()) 0809 { 0810 option.rect = innerRect; 0811 } 0812 0813 d->toolTip->show(option, index); 0814 0815 return true; 0816 } 0817 0818 return false; 0819 } 0820 0821 void ItemViewCategorized::contextMenuEvent(QContextMenuEvent* event) 0822 { 0823 userInteraction(); 0824 QModelIndex index = indexAt(event->pos()); 0825 0826 if (index.isValid()) 0827 { 0828 showContextMenuOnIndex(event, index); 0829 } 0830 else 0831 { 0832 showContextMenu(event); 0833 } 0834 } 0835 0836 void ItemViewCategorized::leaveEvent(QEvent* event) 0837 { 0838 hideIndexNotification(); 0839 0840 if (d->mouseButtonPressed != Qt::RightButton) 0841 { 0842 d->mouseButtonPressed = Qt::NoButton; 0843 } 0844 0845 DCategorizedView::leaveEvent(event); 0846 } 0847 0848 void ItemViewCategorized::mousePressEvent(QMouseEvent* event) 0849 { 0850 userInteraction(); 0851 0852 const QModelIndex index = indexAt(event->pos()); 0853 0854 // Clear selection on click on empty area. Standard behavior, but not done by QAbstractItemView for some reason. 0855 0856 Qt::KeyboardModifiers modifiers = event->modifiers(); 0857 const Qt::MouseButton button = event->button(); 0858 const bool rightButtonPressed = button & Qt::RightButton; 0859 const bool shiftKeyPressed = modifiers & Qt::ShiftModifier; 0860 const bool controlKeyPressed = modifiers & Qt::ControlModifier; 0861 d->mouseButtonPressed = button; 0862 0863 if (!index.isValid() && !rightButtonPressed && !shiftKeyPressed && !controlKeyPressed) 0864 { 0865 clearSelection(); 0866 } 0867 0868 // store event for entered(), clicked(), activated() signal handlers 0869 0870 if (!rightButtonPressed) 0871 { 0872 0873 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0874 0875 delete d->currentMouseEvent; 0876 d->currentMouseEvent = event->clone(); 0877 0878 #else 0879 0880 d->currentMouseEvent = event; 0881 0882 #endif 0883 0884 } 0885 else 0886 { 0887 0888 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0889 0890 delete d->currentMouseEvent; 0891 0892 #endif 0893 0894 d->currentMouseEvent = nullptr; 0895 } 0896 0897 DCategorizedView::mousePressEvent(event); 0898 0899 if (!index.isValid()) 0900 { 0901 Q_EMIT viewportClicked(event); 0902 } 0903 } 0904 0905 void ItemViewCategorized::mouseReleaseEvent(QMouseEvent* event) 0906 { 0907 userInteraction(); 0908 0909 if (d->scrollCurrentToCenter) 0910 { 0911 scrollTo(currentIndex(), QAbstractItemView::PositionAtCenter); 0912 } 0913 0914 DCategorizedView::mouseReleaseEvent(event); 0915 } 0916 0917 void ItemViewCategorized::mouseMoveEvent(QMouseEvent* event) 0918 { 0919 QModelIndex index = indexAt(event->pos()); 0920 QRect indexVisualRect; 0921 0922 if (index.isValid()) 0923 { 0924 indexVisualRect = visualRect(index); 0925 0926 if (d->usePointingHand && 0927 d->delegate->acceptsActivation(event->pos(), indexVisualRect, index)) 0928 { 0929 setCursor(Qt::PointingHandCursor); 0930 } 0931 else 0932 { 0933 unsetCursor(); 0934 } 0935 } 0936 else 0937 { 0938 unsetCursor(); 0939 } 0940 0941 if (d->notificationToolTip && d->notificationToolTip->isVisible()) 0942 { 0943 if (!d->notificationToolTip->rect().adjusted(-50, -50, 50, 50).contains(event->pos())) 0944 { 0945 hideIndexNotification(); 0946 } 0947 } 0948 0949 DCategorizedView::mouseMoveEvent(event); 0950 0951 d->delegate->mouseMoved(event, indexVisualRect, index); 0952 } 0953 0954 void ItemViewCategorized::wheelEvent(QWheelEvent* event) 0955 { 0956 // DCategorizedView updates the single step at some occasions in a private methody 0957 0958 horizontalScrollBar()->setSingleStep(d->delegate->gridSize().height() / d->scrollStepFactor); 0959 verticalScrollBar()->setSingleStep(d->delegate->gridSize().width() / d->scrollStepFactor); 0960 0961 if (event->modifiers() & Qt::ControlModifier) 0962 { 0963 const int delta = event->angleDelta().y(); 0964 0965 if (delta > 0) 0966 { 0967 Q_EMIT zoomInStep(); 0968 } 0969 else if (delta < 0) 0970 { 0971 Q_EMIT zoomOutStep(); 0972 } 0973 0974 event->accept(); 0975 return; 0976 } 0977 0978 if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff && event->angleDelta().y() != 0) 0979 { 0980 QWheelEvent n(event->position(), event->globalPosition(), event->pixelDelta(), 0981 event->angleDelta(), event->buttons(), event->modifiers(), 0982 event->phase(), event->inverted(), event->source()); 0983 0984 QApplication::sendEvent(horizontalScrollBar(), &n); 0985 event->setAccepted(n.isAccepted()); 0986 } 0987 else 0988 { 0989 DCategorizedView::wheelEvent(event); 0990 } 0991 } 0992 0993 void ItemViewCategorized::keyPressEvent(QKeyEvent* event) 0994 { 0995 userInteraction(); 0996 0997 if (event == QKeySequence::Copy) 0998 { 0999 copy(); 1000 event->accept(); 1001 return; 1002 } 1003 else if (event == QKeySequence::Paste) 1004 { 1005 paste(); 1006 event->accept(); 1007 return; 1008 } 1009 1010 /* 1011 // from dolphincontroller.cpp 1012 const QItemSelectionModel* selModel = m_itemView->selectionModel(); 1013 const QModelIndex currentIndex = selModel->currentIndex(); 1014 const bool trigger = currentIndex.isValid() && 1015 ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter)) && 1016 (selModel->selectedIndexes().count() > 0); 1017 if (trigger) 1018 { 1019 const QModelIndexList indexList = selModel->selectedIndexes(); 1020 Q_FOREACH (const QModelIndex& index, indexList) 1021 { 1022 Q_EMIT itemTriggered(itemForIndex(index)); 1023 } 1024 } 1025 */ 1026 DCategorizedView::keyPressEvent(event); 1027 1028 Q_EMIT keyPressed(event); 1029 } 1030 1031 void ItemViewCategorized::resizeEvent(QResizeEvent* e) 1032 { 1033 QModelIndex oldPosition = d->scrollPositionHint(); 1034 DCategorizedView::resizeEvent(e); 1035 updateDelegateSizes(); 1036 scrollToRelaxed(oldPosition, QAbstractItemView::PositionAtTop); 1037 } 1038 1039 bool ItemViewCategorized::viewportEvent(QEvent* event) 1040 { 1041 switch (event->type()) 1042 { 1043 case QEvent::FontChange: 1044 { 1045 updateDelegateSizes(); 1046 break; 1047 } 1048 1049 case QEvent::ToolTip: 1050 { 1051 if (!d->showToolTip) 1052 { 1053 return true; 1054 } 1055 1056 QHelpEvent* he = static_cast<QHelpEvent*>(event); 1057 const QModelIndex index = indexAt(he->pos()); 1058 1059 if (!index.isValid()) 1060 { 1061 break; 1062 } 1063 1064 QStyleOptionViewItem option; 1065 1066 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 1067 1068 initViewItemOption(&option); 1069 1070 #else 1071 1072 option = viewOptions(); 1073 1074 #endif 1075 1076 option.rect = visualRect(index); 1077 option.state |= ((index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None); 1078 showToolTip(index, option, he); 1079 return true; 1080 } 1081 1082 default: 1083 { 1084 break; 1085 } 1086 } 1087 1088 return DCategorizedView::viewportEvent(event); 1089 } 1090 1091 void ItemViewCategorized::showIndexNotification(const QModelIndex& index, const QString& message) 1092 { 1093 hideIndexNotification(); 1094 1095 if (!index.isValid()) 1096 { 1097 return; 1098 } 1099 1100 if (!d->notificationToolTip) 1101 { 1102 d->notificationToolTip = new ItemViewToolTip(this); 1103 } 1104 1105 d->notificationToolTip->setTipContents(message); 1106 1107 QStyleOptionViewItem option; 1108 1109 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 1110 1111 initViewItemOption(&option); 1112 1113 #else 1114 1115 option = viewOptions(); 1116 1117 #endif 1118 1119 option.rect = visualRect(index); 1120 option.state |= ((index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None); 1121 d->notificationToolTip->show(option, index); 1122 } 1123 1124 void ItemViewCategorized::hideIndexNotification() 1125 { 1126 if (d->notificationToolTip) 1127 { 1128 d->notificationToolTip->hide(); 1129 } 1130 } 1131 1132 /** 1133 * cut(), copy(), paste(), dragEnterEvent(), dragMoveEvent(), dropEvent(), startDrag() 1134 * are implemented by DragDropViewImplementation 1135 */ 1136 QModelIndex ItemViewCategorized::mapIndexForDragDrop(const QModelIndex& index) const 1137 { 1138 return filterModel()->mapToSource(index); 1139 } 1140 1141 QPixmap ItemViewCategorized::pixmapForDrag(const QList<QModelIndex>& indexes) const 1142 { 1143 QStyleOptionViewItem option; 1144 1145 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 1146 1147 initViewItemOption(&option); 1148 1149 #else 1150 1151 option = viewOptions(); 1152 1153 #endif 1154 1155 option.rect = viewport()->rect(); 1156 1157 return d->delegate->pixmapForDrag(option, indexes); 1158 } 1159 1160 void ItemViewCategorized::setScrollCurrentToCenter(bool enabled) 1161 { 1162 d->scrollCurrentToCenter = enabled; 1163 } 1164 1165 void ItemViewCategorized::scrollTo(const QModelIndex& index, ScrollHint hint) 1166 { 1167 if (d->scrollCurrentToCenter && (d->mouseButtonPressed == Qt::NoButton)) 1168 { 1169 hint = QAbstractItemView::PositionAtCenter; 1170 } 1171 1172 d->mouseButtonPressed = Qt::NoButton; 1173 DCategorizedView::scrollTo(index, hint); 1174 } 1175 1176 } // namespace Digikam 1177 1178 #include "moc_itemviewcategorized.cpp"