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"