File indexing completed on 2025-04-27 03:58:28

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-04-29
0007  * Description : Qt item view for images - delegate additions
0008  *
0009  * SPDX-FileCopyrightText: 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "itemdelegateoverlay.h"
0016 
0017 // Qt includes
0018 
0019 #include <QApplication>
0020 #include <QEvent>
0021 #include <QMouseEvent>
0022 
0023 // KDE includes
0024 
0025 #include <klocalizedstring.h>
0026 
0027 // Local includes
0028 
0029 #include "digikam_debug.h"
0030 #include "itemviewdelegate.h"
0031 #include "itemviewhoverbutton.h"
0032 
0033 namespace Digikam
0034 {
0035 
0036 ItemDelegateOverlay::ItemDelegateOverlay(QObject* const parent)
0037     : QObject   (parent),
0038       m_view    (nullptr),
0039       m_delegate(nullptr)
0040 {
0041 }
0042 
0043 ItemDelegateOverlay::~ItemDelegateOverlay()
0044 {
0045 }
0046 
0047 void ItemDelegateOverlay::setActive(bool)
0048 {
0049 }
0050 
0051 void ItemDelegateOverlay::visualChange()
0052 {
0053 }
0054 
0055 void ItemDelegateOverlay::mouseMoved(QMouseEvent*, const QRect&, const QModelIndex&)
0056 {
0057 }
0058 
0059 void ItemDelegateOverlay::paint(QPainter*, const QStyleOptionViewItem&, const QModelIndex&)
0060 {
0061 }
0062 
0063 void ItemDelegateOverlay::setView(QAbstractItemView* view)
0064 {
0065     if (m_view)
0066     {
0067         disconnect(this, SIGNAL(update(QModelIndex)),
0068                    m_view, SLOT(update(QModelIndex)));
0069     }
0070 
0071     m_view = view;
0072 
0073     if (m_view)
0074     {
0075         connect(this, SIGNAL(update(QModelIndex)),
0076                 m_view, SLOT(update(QModelIndex)));
0077     }
0078 }
0079 
0080 QAbstractItemView* ItemDelegateOverlay::view() const
0081 {
0082     return m_view;
0083 }
0084 
0085 void ItemDelegateOverlay::setDelegate(QAbstractItemDelegate* delegate)
0086 {
0087     if (m_delegate)
0088     {
0089         disconnect(m_delegate, SIGNAL(visualChange()),
0090                    this, SLOT(visualChange()));
0091     }
0092 
0093     m_delegate = delegate;
0094 
0095     if (m_delegate)
0096     {
0097         connect(m_delegate, SIGNAL(visualChange()),
0098                 this, SLOT(visualChange()));
0099     }
0100 }
0101 
0102 QAbstractItemDelegate* ItemDelegateOverlay::delegate() const
0103 {
0104     return m_delegate;
0105 }
0106 
0107 bool ItemDelegateOverlay::affectsMultiple(const QModelIndex& index) const
0108 {
0109     // note how selectionModel->selectedIndexes().contains() can scale badly
0110 
0111     QItemSelectionModel* const selectionModel = view()->selectionModel();
0112 
0113     if (!selectionModel->hasSelection())
0114     {
0115         return false;
0116     }
0117 
0118     if (!selectionModel->isSelected(index))
0119     {
0120         return false;
0121     }
0122 
0123     return viewHasMultiSelection();
0124 }
0125 
0126 bool ItemDelegateOverlay::viewHasMultiSelection() const
0127 {
0128     QItemSelection selection = view()->selectionModel()->selection();
0129 
0130     if (selection.size() > 1)
0131     {
0132         return true;
0133     }
0134 
0135     return (selection.indexes().size() > 1);
0136 }
0137 
0138 QList<QModelIndex> ItemDelegateOverlay::affectedIndexes(const QModelIndex& index) const
0139 {
0140     if (!affectsMultiple(index))
0141     {
0142         return QList<QModelIndex>() << index;
0143     }
0144     else
0145     {
0146         return view()->selectionModel()->selectedIndexes();
0147     }
0148 }
0149 
0150 int ItemDelegateOverlay::numberOfAffectedIndexes(const QModelIndex& index) const
0151 {
0152     if (!affectsMultiple(index))
0153     {
0154         return 1;
0155     }
0156 
0157     // scales better than selectedIndexes().count()
0158 
0159     int count = 0;
0160 
0161     Q_FOREACH (const QItemSelectionRange& range, view()->selectionModel()->selection())
0162     {
0163         // cppcheck-suppress useStlAlgorithm
0164         count += range.height();
0165     }
0166 
0167     return count;
0168 }
0169 
0170 // --------------------------------------------------------------------------------------------
0171 
0172 AbstractWidgetDelegateOverlay::AbstractWidgetDelegateOverlay(QObject* const parent)
0173     : ItemDelegateOverlay         (parent),
0174       m_widget                    (nullptr),
0175       m_mouseButtonPressedOnWidget(false)
0176 {
0177 }
0178 
0179 void AbstractWidgetDelegateOverlay::setActive(bool active)
0180 {
0181     if (active)
0182     {
0183         if (m_widget)
0184         {
0185             delete m_widget;
0186             m_widget = nullptr;
0187         }
0188 
0189         m_widget = createWidget();
0190 
0191         m_widget->setFocusPolicy(Qt::NoFocus);
0192         m_widget->hide(); // hide per default
0193 
0194         m_view->viewport()->installEventFilter(this);
0195         m_widget->installEventFilter(this);
0196 
0197         if (view()->model())
0198         {
0199             connect(m_view->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
0200                     this, SLOT(slotRowsRemoved(QModelIndex,int,int)));
0201 
0202             connect(m_view->model(), SIGNAL(layoutChanged()),
0203                     this, SLOT(slotLayoutChanged()));
0204 
0205             connect(m_view->model(), SIGNAL(modelReset()),
0206                     this, SLOT(slotReset()));
0207         }
0208 
0209         connect(m_view, SIGNAL(entered(QModelIndex)),
0210                 this, SLOT(slotEntered(QModelIndex)));
0211 
0212         connect(m_view, SIGNAL(viewportEntered()),
0213                 this, SLOT(slotViewportEntered()));
0214     }
0215     else
0216     {
0217         delete m_widget;
0218         m_widget = nullptr;
0219 
0220         if (m_view)
0221         {
0222             m_view->viewport()->removeEventFilter(this);
0223 
0224             if (view()->model())
0225             {
0226                 disconnect(m_view->model(), nullptr, this, nullptr);
0227             }
0228 
0229             disconnect(m_view, SIGNAL(entered(QModelIndex)),
0230                        this, SLOT(slotEntered(QModelIndex)));
0231 
0232             disconnect(m_view, SIGNAL(viewportEntered()),
0233                        this, SLOT(slotViewportEntered()));
0234         }
0235     }
0236 }
0237 
0238 void AbstractWidgetDelegateOverlay::hide()
0239 {
0240     if (m_widget)
0241     {
0242         m_widget->hide();
0243     }
0244 }
0245 
0246 QWidget* AbstractWidgetDelegateOverlay::parentWidget() const
0247 {
0248     return m_view->viewport();
0249 }
0250 
0251 void AbstractWidgetDelegateOverlay::slotReset()
0252 {
0253     hide();
0254 }
0255 
0256 void AbstractWidgetDelegateOverlay::slotEntered(const QModelIndex& index)
0257 {
0258     hide();
0259 
0260     if (!checkIndexOnEnter(index))
0261     {
0262         return;
0263     }
0264 
0265     m_widget->show();
0266 }
0267 
0268 bool AbstractWidgetDelegateOverlay::checkIndexOnEnter(const QModelIndex& index) const
0269 {
0270     if (!index.isValid())
0271     {
0272         return false;
0273     }
0274 
0275     if (QApplication::keyboardModifiers() & (Qt::ShiftModifier | Qt::ControlModifier))
0276     {
0277         return false;
0278     }
0279 
0280     if (!checkIndex(index))
0281     {
0282         return false;
0283     }
0284 
0285     return true;
0286 }
0287 
0288 bool AbstractWidgetDelegateOverlay::checkIndex(const QModelIndex& index) const
0289 {
0290     Q_UNUSED(index);
0291 
0292     return true;
0293 }
0294 
0295 void AbstractWidgetDelegateOverlay::slotViewportEntered()
0296 {
0297     hide();
0298 }
0299 
0300 void AbstractWidgetDelegateOverlay::slotRowsRemoved(const QModelIndex&, int, int)
0301 {
0302     hide();
0303 }
0304 
0305 void AbstractWidgetDelegateOverlay::slotLayoutChanged()
0306 {
0307     hide();
0308 }
0309 
0310 void AbstractWidgetDelegateOverlay::viewportLeaveEvent(QObject*, QEvent*)
0311 {
0312     hide();
0313 }
0314 
0315 void AbstractWidgetDelegateOverlay::widgetEnterEvent()
0316 {
0317 }
0318 
0319 void AbstractWidgetDelegateOverlay::widgetLeaveEvent()
0320 {
0321 }
0322 
0323 void AbstractWidgetDelegateOverlay::widgetEnterNotifyMultiple(const QModelIndex& index)
0324 {
0325     if (index.isValid() && affectsMultiple(index))
0326     {
0327         Q_EMIT requestNotification(index, notifyMultipleMessage(index, numberOfAffectedIndexes(index)));
0328     }
0329 }
0330 
0331 void AbstractWidgetDelegateOverlay::widgetLeaveNotifyMultiple()
0332 {
0333     Q_EMIT hideNotification();
0334 }
0335 
0336 QString AbstractWidgetDelegateOverlay::notifyMultipleMessage(const QModelIndex&, int number)
0337 {
0338     return i18ncp("@info: item overlay",
0339                   "Applying operation to\nthe selected picture",
0340                   "Applying operation to \n\"%1\" selected pictures",
0341                   number);
0342 }
0343 
0344 bool AbstractWidgetDelegateOverlay::eventFilter(QObject* obj, QEvent* event)
0345 {
0346     if (m_widget && (obj == m_widget->parent()))   // events on view's viewport
0347     {
0348         switch (event->type())
0349         {
0350             case QEvent::Leave:
0351             {
0352                 viewportLeaveEvent(obj, event);
0353                 break;
0354             }
0355 
0356             case QEvent::MouseMove:
0357             {
0358                 if (m_mouseButtonPressedOnWidget)
0359                 {
0360                     // Don't forward mouse move events to the viewport,
0361                     // otherwise a rubberband selection will be shown when
0362                     // clicking on the selection toggle and moving the mouse
0363                     // above the viewport.
0364                     return true;
0365                 }
0366 
0367                 break;
0368             }
0369 
0370             case QEvent::MouseButtonRelease:
0371             {
0372                 m_mouseButtonPressedOnWidget = false;
0373                 break;
0374             }
0375 
0376             default:
0377             {
0378                 break;
0379             }
0380         }
0381     }
0382     else if (obj == m_widget)
0383     {
0384         switch (event->type())
0385         {
0386             case QEvent::MouseButtonPress:
0387             {
0388                 if (static_cast<QMouseEvent*>(event)->buttons() & Qt::LeftButton)
0389                 {
0390                     m_mouseButtonPressedOnWidget = true;
0391                 }
0392 
0393                 break;
0394             }
0395 
0396             case QEvent::MouseButtonRelease:
0397             {
0398                 m_mouseButtonPressedOnWidget = false;
0399                 break;
0400             }
0401 
0402             case QEvent::Enter:
0403             {
0404                 widgetEnterEvent();
0405                 break;
0406             }
0407 
0408             case QEvent::Leave:
0409             {
0410                 widgetLeaveEvent();
0411                 break;
0412             }
0413 
0414             default:
0415             {
0416                 break;
0417             }
0418         }
0419     }
0420 
0421     return ItemDelegateOverlay::eventFilter(obj, event);
0422 }
0423 
0424 // ------------------------------------------------------------------------------------------
0425 
0426 HoverButtonDelegateOverlay::HoverButtonDelegateOverlay(QObject* const parent)
0427     : AbstractWidgetDelegateOverlay(parent)
0428 {
0429 }
0430 
0431 ItemViewHoverButton* HoverButtonDelegateOverlay::button() const
0432 {
0433     return static_cast<ItemViewHoverButton*>(m_widget);
0434 }
0435 
0436 void HoverButtonDelegateOverlay::setActive(bool active)
0437 {
0438     AbstractWidgetDelegateOverlay::setActive(active);
0439 
0440     if (active)
0441     {
0442         button()->initIcon();
0443     }
0444 }
0445 
0446 QWidget* HoverButtonDelegateOverlay::createWidget()
0447 {
0448     return createButton();
0449 }
0450 
0451 void HoverButtonDelegateOverlay::visualChange()
0452 {
0453     if (m_widget && m_widget->isVisible())
0454     {
0455         updateButton(button()->index());
0456     }
0457 }
0458 
0459 void HoverButtonDelegateOverlay::slotReset()
0460 {
0461     AbstractWidgetDelegateOverlay::slotReset();
0462 
0463     button()->reset();
0464 }
0465 
0466 void HoverButtonDelegateOverlay::slotEntered(const QModelIndex& index)
0467 {
0468     AbstractWidgetDelegateOverlay::slotEntered(index);
0469 
0470     if (index.isValid() && checkIndex(index))
0471     {
0472         button()->setIndex(index);
0473         updateButton(index);
0474     }
0475     else
0476     {
0477         button()->setIndex(index);
0478     }
0479 }
0480 
0481 // -----------------------------------------------------------------------------------
0482 
0483 class Q_DECL_HIDDEN PersistentWidgetDelegateOverlay::Private
0484 {
0485 public:
0486 
0487     explicit Private()
0488       : persistent  (false),
0489         restoreFocus(false)
0490     {
0491     }
0492 
0493     bool                  persistent;
0494     bool                  restoreFocus;
0495 
0496     QPersistentModelIndex index;
0497     QPersistentModelIndex enteredIndex;
0498 };
0499 
0500 PersistentWidgetDelegateOverlay::PersistentWidgetDelegateOverlay(QObject* const parent)
0501     : AbstractWidgetDelegateOverlay(parent),
0502       d                            (new Private)
0503 {
0504 }
0505 
0506 PersistentWidgetDelegateOverlay::~PersistentWidgetDelegateOverlay()
0507 {
0508     delete d;
0509 }
0510 
0511 QModelIndex PersistentWidgetDelegateOverlay::index() const
0512 {
0513     return d->index;
0514 }
0515 
0516 void PersistentWidgetDelegateOverlay::setActive(bool active)
0517 {
0518     d->persistent = false;
0519 
0520     AbstractWidgetDelegateOverlay::setActive(active);
0521 
0522     if (active)
0523     {
0524         connect(m_view->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
0525                 this, SLOT(leavePersistentMode()));
0526 
0527         connect(m_view, SIGNAL(viewportClicked(const QMouseEvent*)),
0528                 this, SLOT(leavePersistentMode()));
0529     }
0530     else
0531     {
0532         if (m_view)
0533         {
0534             disconnect(m_view->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
0535                        this, SLOT(leavePersistentMode()));
0536 
0537             disconnect(m_view, SIGNAL(viewportClicked(const QMouseEvent*)),
0538                        this, SLOT(leavePersistentMode()));
0539         }
0540     }
0541 }
0542 
0543 void PersistentWidgetDelegateOverlay::setPersistent(bool persistent)
0544 {
0545     if (d->persistent == persistent)
0546     {
0547         return;
0548     }
0549 
0550     d->persistent = persistent;
0551 
0552     if      (d->persistent && d->index.isValid())
0553     {
0554         showOnIndex(d->index);
0555     }
0556     else if (!d->persistent)
0557     {
0558         if (d->enteredIndex.isValid())
0559         {
0560             slotEntered(d->enteredIndex);
0561         }
0562         else
0563         {
0564             hide();
0565         }
0566     }
0567 }
0568 
0569 void PersistentWidgetDelegateOverlay::enterPersistentMode()
0570 {
0571     setPersistent(true);
0572 }
0573 
0574 void PersistentWidgetDelegateOverlay::leavePersistentMode()
0575 {
0576     setPersistent(false);
0577 }
0578 
0579 bool PersistentWidgetDelegateOverlay::isPersistent() const
0580 {
0581     return d->persistent;
0582 }
0583 
0584 void PersistentWidgetDelegateOverlay::slotEntered(const QModelIndex& index)
0585 {
0586     d->enteredIndex = index;
0587 
0588     if (d->persistent && m_widget->isVisible())
0589     {
0590         return;
0591     }
0592 
0593     hide();
0594 
0595     if (!checkIndexOnEnter(index))
0596     {
0597         return;
0598     }
0599 
0600     m_widget->show();
0601 
0602     showOnIndex(index);
0603 }
0604 
0605 void PersistentWidgetDelegateOverlay::slotReset()
0606 {
0607     setPersistent(false);
0608     d->restoreFocus = false;
0609     AbstractWidgetDelegateOverlay::slotReset();
0610 }
0611 
0612 void PersistentWidgetDelegateOverlay::slotViewportEntered()
0613 {
0614     d->enteredIndex = QModelIndex();
0615 
0616     if (!d->persistent)
0617     {
0618         AbstractWidgetDelegateOverlay::slotViewportEntered();
0619     }
0620 }
0621 
0622 void PersistentWidgetDelegateOverlay::viewportLeaveEvent(QObject* obj, QEvent* event)
0623 {
0624     setPersistent(false);
0625     d->restoreFocus = false;
0626     AbstractWidgetDelegateOverlay::viewportLeaveEvent(obj, event);
0627 }
0628 
0629 void PersistentWidgetDelegateOverlay::slotRowsRemoved(const QModelIndex& parent, int begin, int end)
0630 {
0631     AbstractWidgetDelegateOverlay::slotRowsRemoved(parent, begin, end);
0632     setPersistent(false);
0633 }
0634 
0635 void PersistentWidgetDelegateOverlay::slotLayoutChanged()
0636 {
0637     AbstractWidgetDelegateOverlay::slotLayoutChanged();
0638     setPersistent(false);
0639 }
0640 
0641 void PersistentWidgetDelegateOverlay::hide()
0642 {
0643     if (!d->restoreFocus && m_widget->isVisible())
0644     {
0645         QWidget* const f = QApplication::focusWidget();
0646         d->restoreFocus  = f && m_widget->isAncestorOf(f);
0647     }
0648 
0649     AbstractWidgetDelegateOverlay::hide();
0650 }
0651 
0652 void PersistentWidgetDelegateOverlay::showOnIndex(const QModelIndex& index)
0653 {
0654     d->index = QPersistentModelIndex(index);
0655     restoreFocus();
0656 }
0657 
0658 void PersistentWidgetDelegateOverlay::storeFocus()
0659 {
0660     d->restoreFocus = true;
0661 }
0662 
0663 void PersistentWidgetDelegateOverlay::restoreFocus()
0664 {
0665     if (d->restoreFocus)
0666     {
0667         setFocusOnWidget();
0668         d->restoreFocus = false;
0669     }
0670 }
0671 
0672 void PersistentWidgetDelegateOverlay::setFocusOnWidget()
0673 {
0674     m_widget->setFocus();
0675 }
0676 
0677 // -----------------------------------------------------------------------------------
0678 
0679 void ItemDelegateOverlayContainer::installOverlay(ItemDelegateOverlay* overlay)
0680 {
0681     if (!overlay->acceptsDelegate(asDelegate()))
0682     {
0683         qCDebug(DIGIKAM_WIDGETS_LOG) << "Cannot accept delegate" << asDelegate()
0684                                      << "for installing" << overlay;
0685         return;
0686     }
0687 
0688     overlay->setDelegate(asDelegate());
0689     m_overlays << overlay;
0690 
0691     // let the view call setActive
0692 
0693     QObject::connect(overlay, SIGNAL(destroyed(QObject*)),
0694                      asDelegate(), SLOT(overlayDestroyed(QObject*)));
0695 
0696     QObject::connect(overlay, SIGNAL(requestNotification(QModelIndex,QString)),
0697                      asDelegate(), SIGNAL(requestNotification(QModelIndex,QString)));
0698 
0699     QObject::connect(overlay, SIGNAL(hideNotification()),
0700                      asDelegate(), SIGNAL(hideNotification()));
0701 }
0702 
0703 QList<ItemDelegateOverlay*> ItemDelegateOverlayContainer::overlays() const
0704 {
0705     return m_overlays;
0706 }
0707 
0708 void ItemDelegateOverlayContainer::removeOverlay(ItemDelegateOverlay* overlay)
0709 {
0710     overlay->setActive(false);
0711     overlay->setDelegate(nullptr);
0712     m_overlays.removeAll(overlay);
0713     QObject::disconnect(overlay, nullptr, asDelegate(), nullptr);
0714 }
0715 
0716 void ItemDelegateOverlayContainer::setAllOverlaysActive(bool active)
0717 {
0718     Q_FOREACH (ItemDelegateOverlay* const overlay, m_overlays)
0719     {
0720         overlay->setActive(active);
0721     }
0722 }
0723 
0724 void ItemDelegateOverlayContainer::setViewOnAllOverlays(QAbstractItemView* view)
0725 {
0726     Q_FOREACH (ItemDelegateOverlay* const overlay, m_overlays)
0727     {
0728         overlay->setView(view);
0729     }
0730 }
0731 
0732 void ItemDelegateOverlayContainer::removeAllOverlays()
0733 {
0734     Q_FOREACH (ItemDelegateOverlay* const overlay, m_overlays)
0735     {
0736         overlay->setActive(false);
0737         overlay->setDelegate(nullptr);
0738         overlay->setView(nullptr);
0739     }
0740 
0741     m_overlays.clear();
0742 }
0743 
0744 void ItemDelegateOverlayContainer::overlayDestroyed(QObject* o)
0745 {
0746     ItemDelegateOverlay* const overlay = qobject_cast<ItemDelegateOverlay*>(o);
0747 
0748     if (overlay)
0749     {
0750         removeOverlay(overlay);
0751     }
0752 }
0753 
0754 void ItemDelegateOverlayContainer::mouseMoved(QMouseEvent* e,
0755                                               const QRect& visualRect,
0756                                               const QModelIndex& index)
0757 {
0758     Q_FOREACH (ItemDelegateOverlay* const overlay, m_overlays)
0759     {
0760         overlay->mouseMoved(e, visualRect, index);
0761     }
0762 }
0763 
0764 void ItemDelegateOverlayContainer::drawOverlays(QPainter* p,
0765                                                 const QStyleOptionViewItem& option,
0766                                                 const QModelIndex& index) const
0767 {
0768     Q_FOREACH (ItemDelegateOverlay* const overlay, m_overlays)
0769     {
0770         overlay->paint(p, option, index);
0771     }
0772 }
0773 
0774 } // namespace Digikam
0775 
0776 #include "moc_itemdelegateoverlay.cpp"