File indexing completed on 2024-07-14 03:54:11

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