File indexing completed on 2024-04-28 15:26:50

0001 /*
0002     SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
0003     SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kurlnavigatorplacesselector_p.h"
0009 
0010 #include <KProtocolInfo>
0011 #include <KUrlMimeData>
0012 #include <kfileplacesmodel.h>
0013 
0014 #include <QDragEnterEvent>
0015 #include <QDragLeaveEvent>
0016 #include <QDropEvent>
0017 #include <QMenu>
0018 #include <QMimeData>
0019 #include <QMimeDatabase>
0020 #include <QMouseEvent>
0021 #include <QPainter>
0022 #include <QPixmap>
0023 #include <QStyle>
0024 
0025 namespace KDEPrivate
0026 {
0027 KUrlNavigatorPlacesSelector::KUrlNavigatorPlacesSelector(KUrlNavigator *parent, KFilePlacesModel *placesModel)
0028     : KUrlNavigatorButtonBase(parent)
0029     , m_selectedItem(-1)
0030     , m_placesModel(placesModel)
0031 {
0032     setFocusPolicy(Qt::NoFocus);
0033 
0034     m_placesMenu = new QMenu(this);
0035     m_placesMenu->installEventFilter(this);
0036 
0037     updateMenu();
0038 
0039     connect(m_placesModel, &KFilePlacesModel::reloaded, this, &KUrlNavigatorPlacesSelector::updateMenu);
0040     connect(m_placesMenu, &QMenu::triggered, this, [this](QAction *action) {
0041         activatePlace(action, &KUrlNavigatorPlacesSelector::placeActivated);
0042     });
0043 
0044     setMenu(m_placesMenu);
0045 
0046     setAcceptDrops(true);
0047 }
0048 
0049 KUrlNavigatorPlacesSelector::~KUrlNavigatorPlacesSelector()
0050 {
0051 }
0052 
0053 void KUrlNavigatorPlacesSelector::updateMenu()
0054 {
0055     m_placesMenu->clear();
0056 
0057     // Submenus have to be deleted explicitly (QTBUG-11070)
0058     for (QObject *obj : QObjectList(m_placesMenu->children())) {
0059         delete qobject_cast<QMenu *>(obj); // Noop for nullptr
0060     }
0061 
0062     updateSelection(m_selectedUrl);
0063 
0064     QString previousGroup;
0065     QMenu *subMenu = nullptr;
0066 
0067     const int rowCount = m_placesModel->rowCount();
0068     for (int i = 0; i < rowCount; ++i) {
0069         QModelIndex index = m_placesModel->index(i, 0);
0070         if (m_placesModel->isHidden(index)) {
0071             continue;
0072         }
0073 
0074         QAction *placeAction = new QAction(m_placesModel->icon(index), m_placesModel->text(index), m_placesMenu);
0075         placeAction->setData(i);
0076 
0077         const QString &groupName = index.data(KFilePlacesModel::GroupRole).toString();
0078         if (previousGroup.isEmpty()) { // Skip first group heading.
0079             previousGroup = groupName;
0080         }
0081 
0082         // Put all subsequent categories into a submenu.
0083         if (previousGroup != groupName) {
0084             QAction *subMenuAction = new QAction(groupName, m_placesMenu);
0085             subMenu = new QMenu(m_placesMenu);
0086             subMenu->installEventFilter(this);
0087             subMenuAction->setMenu(subMenu);
0088 
0089             m_placesMenu->addAction(subMenuAction);
0090 
0091             previousGroup = groupName;
0092         }
0093 
0094         if (subMenu) {
0095             subMenu->addAction(placeAction);
0096         } else {
0097             m_placesMenu->addAction(placeAction);
0098         }
0099 
0100         if (i == m_selectedItem) {
0101             setIcon(m_placesModel->icon(index));
0102         }
0103     }
0104 
0105     updateTeardownAction();
0106 }
0107 
0108 void KUrlNavigatorPlacesSelector::updateTeardownAction()
0109 {
0110     const QString teardownActionId = QStringLiteral("teardownAction");
0111 
0112     const auto actions = m_placesMenu->actions();
0113     for (QAction *action : actions) {
0114         if (action->data() == teardownActionId) {
0115             delete action;
0116         }
0117     }
0118 
0119     const QModelIndex index = m_placesModel->index(m_selectedItem, 0);
0120     QAction *teardown = m_placesModel->teardownActionForIndex(index);
0121     if (teardown) {
0122         QAction *separator = m_placesMenu->addSeparator();
0123         separator->setData(teardownActionId);
0124 
0125         teardown->setParent(m_placesMenu);
0126         teardown->setData(teardownActionId);
0127         m_placesMenu->addAction(teardown);
0128     }
0129 }
0130 
0131 void KUrlNavigatorPlacesSelector::updateSelection(const QUrl &url)
0132 {
0133     const QModelIndex index = m_placesModel->closestItem(url);
0134     if (index.isValid()) {
0135         m_selectedItem = index.row();
0136         m_selectedUrl = url;
0137         setIcon(m_placesModel->icon(index));
0138     } else {
0139         m_selectedItem = -1;
0140         // No bookmark has been found which matches to the given Url.
0141         // Show the protocol's icon as pixmap for indication, if available:
0142         QIcon icon;
0143         if (!url.scheme().isEmpty()) {
0144             if (const QString iconName = KProtocolInfo::icon(url.scheme()); !iconName.isEmpty()) {
0145                 icon = QIcon::fromTheme(iconName);
0146             }
0147         }
0148         if (icon.isNull()) {
0149             icon = QIcon::fromTheme(QStringLiteral("folder"));
0150         }
0151         setIcon(icon);
0152     }
0153     updateTeardownAction();
0154 }
0155 
0156 QUrl KUrlNavigatorPlacesSelector::selectedPlaceUrl() const
0157 {
0158     const QModelIndex index = m_placesModel->index(m_selectedItem, 0);
0159     return index.isValid() ? m_placesModel->url(index) : QUrl();
0160 }
0161 
0162 QString KUrlNavigatorPlacesSelector::selectedPlaceText() const
0163 {
0164     const QModelIndex index = m_placesModel->index(m_selectedItem, 0);
0165     return index.isValid() ? m_placesModel->text(index) : QString();
0166 }
0167 
0168 QSize KUrlNavigatorPlacesSelector::sizeHint() const
0169 {
0170     const int height = KUrlNavigatorButtonBase::sizeHint().height();
0171     return QSize(height, height);
0172 }
0173 
0174 void KUrlNavigatorPlacesSelector::paintEvent(QPaintEvent *event)
0175 {
0176     Q_UNUSED(event);
0177     QPainter painter(this);
0178     drawHoverBackground(&painter);
0179 
0180     // draw icon
0181     const QPixmap pixmap = icon().pixmap(QSize(22, 22).expandedTo(iconSize()), QIcon::Normal);
0182     style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter, pixmap);
0183 }
0184 
0185 void KUrlNavigatorPlacesSelector::dragEnterEvent(QDragEnterEvent *event)
0186 {
0187     if (event->mimeData()->hasUrls()) {
0188         setDisplayHintEnabled(DraggedHint, true);
0189         event->acceptProposedAction();
0190 
0191         update();
0192     }
0193 }
0194 
0195 void KUrlNavigatorPlacesSelector::dragLeaveEvent(QDragLeaveEvent *event)
0196 {
0197     KUrlNavigatorButtonBase::dragLeaveEvent(event);
0198 
0199     setDisplayHintEnabled(DraggedHint, false);
0200     update();
0201 }
0202 
0203 void KUrlNavigatorPlacesSelector::dropEvent(QDropEvent *event)
0204 {
0205     setDisplayHintEnabled(DraggedHint, false);
0206     update();
0207 
0208     QMimeDatabase db;
0209     const QList<QUrl> urlList = KUrlMimeData::urlsFromMimeData(event->mimeData());
0210     for (const QUrl &url : urlList) {
0211         QMimeType mimetype = db.mimeTypeForUrl(url);
0212         if (mimetype.inherits(QStringLiteral("inode/directory"))) {
0213             m_placesModel->addPlace(url.fileName(), url);
0214         }
0215     }
0216 }
0217 
0218 void KUrlNavigatorPlacesSelector::mouseReleaseEvent(QMouseEvent *event)
0219 {
0220     if (event->button() == Qt::MiddleButton && geometry().contains(event->pos())) {
0221         Q_EMIT tabRequested(KFilePlacesModel::convertedUrl(m_placesModel->url(m_placesModel->index(m_selectedItem, 0))));
0222         event->accept();
0223         return;
0224     }
0225 
0226     KUrlNavigatorButtonBase::mouseReleaseEvent(event);
0227 }
0228 
0229 void KUrlNavigatorPlacesSelector::activatePlace(QAction *action, ActivationSignal activationSignal)
0230 {
0231     Q_ASSERT(action != nullptr);
0232     if (action->data().toString() == QLatin1String("teardownAction")) {
0233         QModelIndex index = m_placesModel->index(m_selectedItem, 0);
0234         m_placesModel->requestTeardown(index);
0235         return;
0236     }
0237 
0238     QModelIndex index = m_placesModel->index(action->data().toInt(), 0);
0239 
0240     m_lastClickedIndex = QPersistentModelIndex();
0241     m_lastActivationSignal = nullptr;
0242 
0243     if (m_placesModel->setupNeeded(index)) {
0244         connect(m_placesModel, &KFilePlacesModel::setupDone, this, &KUrlNavigatorPlacesSelector::onStorageSetupDone);
0245 
0246         m_lastClickedIndex = index;
0247         m_lastActivationSignal = activationSignal;
0248         m_placesModel->requestSetup(index);
0249         return;
0250     } else if (index.isValid()) {
0251         if (activationSignal == &KUrlNavigatorPlacesSelector::placeActivated) {
0252             m_selectedItem = index.row();
0253             setIcon(m_placesModel->icon(index));
0254             updateTeardownAction();
0255         }
0256 
0257         const QUrl url = KFilePlacesModel::convertedUrl(m_placesModel->url(index));
0258         /*Q_EMIT*/ std::invoke(activationSignal, this, url);
0259     }
0260 }
0261 
0262 void KUrlNavigatorPlacesSelector::onStorageSetupDone(const QModelIndex &index, bool success)
0263 {
0264     disconnect(m_placesModel, &KFilePlacesModel::setupDone, this, &KUrlNavigatorPlacesSelector::onStorageSetupDone);
0265 
0266     if (m_lastClickedIndex == index) {
0267         if (success) {
0268             if (m_lastActivationSignal == &KUrlNavigatorPlacesSelector::placeActivated) {
0269                 m_selectedItem = index.row();
0270                 setIcon(m_placesModel->icon(index));
0271                 updateTeardownAction();
0272             }
0273 
0274             const QUrl url = KFilePlacesModel::convertedUrl(m_placesModel->url(index));
0275             /*Q_EMIT*/ std::invoke(m_lastActivationSignal, this, url);
0276         }
0277         m_lastClickedIndex = QPersistentModelIndex();
0278         m_lastActivationSignal = nullptr;
0279     }
0280 }
0281 
0282 bool KUrlNavigatorPlacesSelector::eventFilter(QObject *watched, QEvent *event)
0283 {
0284     if (auto *menu = qobject_cast<QMenu *>(watched)) {
0285         if (event->type() == QEvent::MouseButtonRelease) {
0286             QMouseEvent *me = static_cast<QMouseEvent *>(event);
0287             if (me->button() == Qt::MiddleButton) {
0288                 if (QAction *action = menu->activeAction()) {
0289                     m_placesMenu->close(); // always close top menu
0290 
0291                     activatePlace(action, &KUrlNavigatorPlacesSelector::tabRequested);
0292                     return true;
0293                 }
0294             }
0295         }
0296     }
0297 
0298     return KUrlNavigatorButtonBase::eventFilter(watched, event);
0299 }
0300 
0301 } // namespace KDEPrivate
0302 
0303 #include "moc_kurlnavigatorplacesselector_p.cpp"