File indexing completed on 2025-01-19 03:58:03

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2017-05-15
0007  * Description : menu to manage bookmarks
0008  *
0009  * SPDX-FileCopyrightText: 2017-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "bookmarksmenu.h"
0016 
0017 // Qt includes
0018 
0019 #include <QUrl>
0020 #include <QDebug>
0021 
0022 // Local includes
0023 
0024 #include "bookmarknode.h"
0025 
0026 Q_DECLARE_METATYPE(QModelIndex)
0027 
0028 namespace Digikam
0029 {
0030 
0031 class Q_DECL_HIDDEN ModelMenu::Private
0032 {
0033 public:
0034 
0035     explicit Private()
0036       : maxRows         (7),
0037         firstSeparator  (-1),
0038         maxWidth        (-1),
0039         hoverRole       (0),
0040         separatorRole   (0),
0041         model           (nullptr)
0042     {
0043     }
0044 
0045     int                   maxRows;
0046     int                   firstSeparator;
0047     int                   maxWidth;
0048     int                   hoverRole;
0049     int                   separatorRole;
0050     QAbstractItemModel*   model;
0051     QPersistentModelIndex root;
0052 };
0053 
0054 ModelMenu::ModelMenu(QWidget* const parent)
0055     : QMenu(parent),
0056       d    (new Private)
0057 {
0058     // --- NOTE: use dynamic binding as slotAboutToShow() is a virtual method which can be re-implemented in derived classes.
0059 
0060     connect(this, &ModelMenu::aboutToShow,
0061             this, &ModelMenu::slotAboutToShow);
0062 }
0063 
0064 ModelMenu::~ModelMenu()
0065 {
0066     delete d;
0067 }
0068 
0069 bool ModelMenu::prePopulated()
0070 {
0071     return false;
0072 }
0073 
0074 void ModelMenu::postPopulated()
0075 {
0076 }
0077 
0078 void ModelMenu::setModel(QAbstractItemModel* model)
0079 {
0080     d->model = model;
0081 }
0082 
0083 QAbstractItemModel* ModelMenu::model() const
0084 {
0085     return d->model;
0086 }
0087 
0088 void ModelMenu::setMaxRows(int max)
0089 {
0090     d->maxRows = max;
0091 }
0092 
0093 int ModelMenu::maxRows() const
0094 {
0095     return d->maxRows;
0096 }
0097 
0098 void ModelMenu::setFirstSeparator(int offset)
0099 {
0100     d->firstSeparator = offset;
0101 }
0102 
0103 int ModelMenu::firstSeparator() const
0104 {
0105     return d->firstSeparator;
0106 }
0107 
0108 void ModelMenu::setRootIndex(const QModelIndex& index)
0109 {
0110     d->root = index;
0111 }
0112 
0113 QModelIndex ModelMenu::rootIndex() const
0114 {
0115     return d->root;
0116 }
0117 
0118 void ModelMenu::setHoverRole(int role)
0119 {
0120     d->hoverRole = role;
0121 }
0122 
0123 int ModelMenu::hoverRole() const
0124 {
0125     return d->hoverRole;
0126 }
0127 
0128 void ModelMenu::setSeparatorRole(int role)
0129 {
0130     d->separatorRole = role;
0131 }
0132 
0133 int ModelMenu::separatorRole() const
0134 {
0135     return d->separatorRole;
0136 }
0137 
0138 void ModelMenu::slotAboutToShow()
0139 {
0140     if (QMenu* const menu = qobject_cast<QMenu*>(sender()))
0141     {
0142         QVariant v = menu->menuAction()->data();
0143 
0144         if (v.canConvert<QModelIndex>())
0145         {
0146             QModelIndex idx = qvariant_cast<QModelIndex>(v);
0147             createMenu(idx, -1, menu, menu);
0148 
0149             disconnect(menu, SIGNAL(aboutToShow()),
0150                        this, SLOT(slotAboutToShow()));
0151 
0152             return;
0153         }
0154     }
0155 
0156     clear();
0157 
0158     // NOTE: use dynamic binding as this virtual method can be re-implemented in derived classes.
0159 
0160     if (this->prePopulated())
0161     {
0162         addSeparator();
0163     }
0164 
0165     int max = d->maxRows;
0166 
0167     if (max != -1)
0168     {
0169         max += d->firstSeparator;
0170     }
0171 
0172     createMenu(d->root, max, this, this);
0173     postPopulated();
0174 }
0175 
0176 void ModelMenu::createMenu(const QModelIndex& parent, int max, QMenu* parentMenu, QMenu* menu)
0177 {
0178     if (!menu)
0179     {
0180         QString title = parent.data().toString();
0181         menu          = new QMenu(title, this);
0182         QIcon icon    = qvariant_cast<QIcon>(parent.data(Qt::DecorationRole));
0183         menu->setIcon(icon);
0184         parentMenu->addMenu(menu);
0185         QVariant v;
0186         v.setValue(parent);
0187         menu->menuAction()->setData(v);
0188 
0189         connect(menu, SIGNAL(aboutToShow()),
0190                 this, SLOT(slotAboutToShow()));
0191 
0192         return;
0193     }
0194 
0195     int end = d->model->rowCount(parent);
0196 
0197     if (max != -1)
0198     {
0199         end = qMin(max, end);
0200     }
0201 
0202     connect(menu, SIGNAL(triggered(QAction*)),
0203             this, SLOT(slotTriggered(QAction*)));
0204 
0205     connect(menu, SIGNAL(hovered(QAction*)),
0206             this, SLOT(slotHovered(QAction*)));
0207 
0208     for (int i = 0 ; i < end ; ++i)
0209     {
0210         QModelIndex idx = d->model->index(i, 0, parent);
0211 
0212         if (d->model->hasChildren(idx))
0213         {
0214             createMenu(idx, -1, menu);
0215         }
0216         else
0217         {
0218             if ((d->separatorRole != 0) && idx.data(d->separatorRole).toBool())
0219             {
0220                 addSeparator();
0221             }
0222             else
0223             {
0224                 menu->addAction(makeAction(idx));
0225             }
0226         }
0227 
0228         if ((menu == this) && (i == (d->firstSeparator - 1)))
0229         {
0230             addSeparator();
0231         }
0232     }
0233 }
0234 
0235 QAction* ModelMenu::makeAction(const QModelIndex& index)
0236 {
0237     QIcon icon            = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
0238     QAction* const action = makeAction(icon, index.data().toString(), this);
0239     QVariant v;
0240     v.setValue(index);
0241     action->setData(v);
0242 
0243     return action;
0244 }
0245 
0246 QAction* ModelMenu::makeAction(const QIcon& icon, const QString& text, QObject* const parent)
0247 {
0248     QFontMetrics fm(font());
0249 
0250     if (d->maxWidth == -1)
0251     {
0252         d->maxWidth = fm.horizontalAdvance(QLatin1Char('m')) * 30;
0253     }
0254 
0255     QString smallText = fm.elidedText(text, Qt::ElideMiddle, d->maxWidth);
0256 
0257     return (new QAction(icon, smallText, parent));
0258 }
0259 
0260 void ModelMenu::slotTriggered(QAction* action)
0261 {
0262     QVariant v = action->data();
0263 
0264     if (v.canConvert<QModelIndex>())
0265     {
0266         QModelIndex idx = qvariant_cast<QModelIndex>(v);
0267         Q_EMIT activated(idx);
0268     }
0269 }
0270 
0271 void ModelMenu::slotHovered(QAction* action)
0272 {
0273     QVariant v = action->data();
0274 
0275     if (v.canConvert<QModelIndex>())
0276     {
0277         QModelIndex idx       = qvariant_cast<QModelIndex>(v);
0278         QString hoveredString = idx.data(d->hoverRole).toString();
0279 
0280         if (!hoveredString.isEmpty())
0281         {
0282             Q_EMIT hovered(hoveredString);
0283         }
0284     }
0285 }
0286 
0287 // ------------------------------------------------------------------------------
0288 
0289 class Q_DECL_HIDDEN BookmarksMenu::Private
0290 {
0291 public:
0292 
0293     explicit Private()
0294       : manager(nullptr)
0295     {
0296     }
0297 
0298     BookmarksManager* manager;
0299     QList<QAction*>   initActions;
0300 };
0301 
0302 BookmarksMenu::BookmarksMenu(BookmarksManager* const mngr, QWidget* const parent)
0303     : ModelMenu(parent),
0304       d        (new Private)
0305 {
0306     d->manager = mngr;
0307 
0308     connect(this, SIGNAL(activated(QModelIndex)),
0309             this, SLOT(slotActivated(QModelIndex)));
0310 
0311     setMaxRows(-1);
0312     setHoverRole(BookmarksModel::UrlStringRole);
0313     setSeparatorRole(BookmarksModel::SeparatorRole);
0314 }
0315 
0316 BookmarksMenu::~BookmarksMenu()
0317 {
0318     delete d;
0319 }
0320 
0321 void BookmarksMenu::slotActivated(const QModelIndex& index)
0322 {
0323     Q_EMIT openUrl(index.data(BookmarksModel::UrlRole).toUrl());
0324 }
0325 
0326 bool BookmarksMenu::prePopulated()
0327 {
0328     setModel(d->manager->bookmarksModel());
0329     setRootIndex(d->manager->bookmarksModel()->index(d->manager->bookmarks()->children().first()));
0330 
0331     // initial actions
0332 
0333     Q_FOREACH (QAction* const ac, d->initActions)
0334     {
0335         if (ac)
0336         {
0337             addAction(ac);
0338         }
0339     }
0340 
0341     if (!d->initActions.isEmpty())
0342     {
0343         addSeparator();
0344     }
0345 
0346     createMenu(rootIndex(), 0, this, this);
0347 
0348     return true;
0349 }
0350 
0351 void BookmarksMenu::setInitialActions(const QList<QAction*>& actions)
0352 {
0353     d->initActions = actions;
0354 
0355     Q_FOREACH (QAction* const ac, d->initActions)
0356     {
0357         if (ac)
0358         {
0359             addAction(ac);
0360         }
0361     }
0362 }
0363 
0364 } // namespace Digikam
0365 
0366 #include "moc_bookmarksmenu.cpp"