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"