File indexing completed on 2023-09-24 04:08:56
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"