1/*
2 SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
3 SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kurlnavigatorplacesselector_p.h"
9
10#include <KProtocolInfo>
11#include <KUrlMimeData>
12#include <kfileplacesmodel.h>
13
14#include <QDragEnterEvent>
15#include <QDragLeaveEvent>
16#include <QDropEvent>
17#include <QMenu>
18#include <QMimeData>
19#include <QMimeDatabase>
20#include <QMouseEvent>
21#include <QPainter>
22#include <QPixmap>
23#include <QStyle>
24
25namespace KDEPrivate
26{
27KUrlNavigatorPlacesSelector::KUrlNavigatorPlacesSelector(KUrlNavigator *parent, KFilePlacesModel *placesModel)
28 : KUrlNavigatorButtonBase(parent)
29 , m_selectedItem(-1)
30 , m_placesModel(placesModel)
31{
32 connect(sender: m_placesModel, signal: &KFilePlacesModel::reloaded, context: this, slot: [this] {
33 updateSelection(url: m_selectedUrl);
34 });
35
36 m_placesMenu = new QMenu(this);
37 m_placesMenu->installEventFilter(filterObj: this);
38 connect(sender: m_placesMenu, signal: &QMenu::aboutToShow, context: this, slot: &KUrlNavigatorPlacesSelector::updateMenu);
39 connect(sender: m_placesMenu, signal: &QMenu::triggered, context: this, slot: [this](QAction *action) {
40 activatePlace(action, activationSignal: &KUrlNavigatorPlacesSelector::placeActivated);
41 });
42
43 setMenu(m_placesMenu);
44
45 setAcceptDrops(true);
46}
47
48KUrlNavigatorPlacesSelector::~KUrlNavigatorPlacesSelector()
49{
50}
51
52void KUrlNavigatorPlacesSelector::updateMenu()
53{
54 m_placesMenu->clear();
55
56 // Submenus have to be deleted explicitly (QTBUG-11070)
57 for (QObject *obj : QObjectList(m_placesMenu->children())) {
58 delete qobject_cast<QMenu *>(object: obj); // Noop for nullptr
59 }
60
61 QString previousGroup;
62 QMenu *subMenu = nullptr;
63
64 const int rowCount = m_placesModel->rowCount();
65 for (int i = 0; i < rowCount; ++i) {
66 QModelIndex index = m_placesModel->index(row: i, column: 0);
67 if (m_placesModel->isHidden(index)) {
68 continue;
69 }
70
71 QAction *placeAction = new QAction(m_placesModel->icon(index), m_placesModel->text(index), m_placesMenu);
72 placeAction->setData(i);
73
74 const QString &groupName = index.data(arole: KFilePlacesModel::GroupRole).toString();
75 if (previousGroup.isEmpty()) { // Skip first group heading.
76 previousGroup = groupName;
77 }
78
79 // Put all subsequent categories into a submenu.
80 if (previousGroup != groupName) {
81 QAction *subMenuAction = new QAction(groupName, m_placesMenu);
82 subMenu = new QMenu(m_placesMenu);
83 subMenu->installEventFilter(filterObj: this);
84 subMenuAction->setMenu(subMenu);
85
86 m_placesMenu->addAction(action: subMenuAction);
87
88 previousGroup = groupName;
89 }
90
91 if (subMenu) {
92 subMenu->addAction(action: placeAction);
93 } else {
94 m_placesMenu->addAction(action: placeAction);
95 }
96
97 if (i == m_selectedItem) {
98 setIcon(m_placesModel->icon(index));
99 }
100 }
101
102 const QModelIndex index = m_placesModel->index(row: m_selectedItem, column: 0);
103 if (QAction *teardown = m_placesModel->teardownActionForIndex(index)) {
104 m_placesMenu->addSeparator();
105
106 teardown->setParent(m_placesMenu);
107 m_placesMenu->addAction(action: teardown);
108 }
109}
110
111void KUrlNavigatorPlacesSelector::updateSelection(const QUrl &url)
112{
113 const QModelIndex index = m_placesModel->closestItem(url);
114 if (index.isValid()) {
115 m_selectedItem = index.row();
116 m_selectedUrl = url;
117 setIcon(m_placesModel->icon(index));
118 } else {
119 m_selectedItem = -1;
120 // No bookmark has been found which matches to the given Url.
121 // Show the protocol's icon as pixmap for indication, if available:
122 QIcon icon;
123 if (!url.scheme().isEmpty()) {
124 if (const QString iconName = KProtocolInfo::icon(protocol: url.scheme()); !iconName.isEmpty()) {
125 icon = QIcon::fromTheme(name: iconName);
126 }
127 }
128 if (icon.isNull()) {
129 icon = QIcon::fromTheme(QStringLiteral("folder"));
130 }
131 setIcon(icon);
132 }
133}
134
135QUrl KUrlNavigatorPlacesSelector::selectedPlaceUrl() const
136{
137 const QModelIndex index = m_placesModel->index(row: m_selectedItem, column: 0);
138 return index.isValid() ? m_placesModel->url(index) : QUrl();
139}
140
141QString KUrlNavigatorPlacesSelector::selectedPlaceText() const
142{
143 const QModelIndex index = m_placesModel->index(row: m_selectedItem, column: 0);
144 return index.isValid() ? m_placesModel->text(index) : QString();
145}
146
147QSize KUrlNavigatorPlacesSelector::sizeHint() const
148{
149 const int height = KUrlNavigatorButtonBase::sizeHint().height();
150 return QSize(height, height);
151}
152
153void KUrlNavigatorPlacesSelector::paintEvent(QPaintEvent *event)
154{
155 Q_UNUSED(event);
156 QPainter painter(this);
157 drawHoverBackground(painter: &painter);
158
159 // draw icon
160 const QPixmap pixmap = icon().pixmap(size: QSize(22, 22).expandedTo(otherSize: iconSize()), mode: QIcon::Normal);
161 style()->drawItemPixmap(painter: &painter, rect: rect(), alignment: Qt::AlignCenter, pixmap);
162}
163
164void KUrlNavigatorPlacesSelector::dragEnterEvent(QDragEnterEvent *event)
165{
166 if (event->mimeData()->hasUrls()) {
167 setDisplayHintEnabled(hint: DraggedHint, enable: true);
168 event->acceptProposedAction();
169
170 update();
171 }
172}
173
174void KUrlNavigatorPlacesSelector::dragLeaveEvent(QDragLeaveEvent *event)
175{
176 KUrlNavigatorButtonBase::dragLeaveEvent(event);
177
178 setDisplayHintEnabled(hint: DraggedHint, enable: false);
179 update();
180}
181
182void KUrlNavigatorPlacesSelector::dropEvent(QDropEvent *event)
183{
184 setDisplayHintEnabled(hint: DraggedHint, enable: false);
185 update();
186
187 QMimeDatabase db;
188 const QList<QUrl> urlList = KUrlMimeData::urlsFromMimeData(mimeData: event->mimeData());
189 for (const QUrl &url : urlList) {
190 QMimeType mimetype = db.mimeTypeForUrl(url);
191 if (mimetype.inherits(QStringLiteral("inode/directory"))) {
192 m_placesModel->addPlace(text: url.fileName(), url);
193 }
194 }
195}
196
197void KUrlNavigatorPlacesSelector::mouseReleaseEvent(QMouseEvent *event)
198{
199 if (event->button() == Qt::MiddleButton && geometry().contains(p: event->pos())) {
200 Q_EMIT tabRequested(url: KFilePlacesModel::convertedUrl(url: m_placesModel->url(index: m_placesModel->index(row: m_selectedItem, column: 0))));
201 event->accept();
202 return;
203 }
204
205 KUrlNavigatorButtonBase::mouseReleaseEvent(e: event);
206}
207
208void KUrlNavigatorPlacesSelector::activatePlace(QAction *action, ActivationSignal activationSignal)
209{
210 Q_ASSERT(action != nullptr);
211 if (action->data().toString() == QLatin1String("teardownAction")) {
212 QModelIndex index = m_placesModel->index(row: m_selectedItem, column: 0);
213 m_placesModel->requestTeardown(index);
214 return;
215 }
216
217 QModelIndex index = m_placesModel->index(row: action->data().toInt(), column: 0);
218
219 m_lastClickedIndex = QPersistentModelIndex();
220 m_lastActivationSignal = nullptr;
221
222 if (m_placesModel->setupNeeded(index)) {
223 connect(sender: m_placesModel, signal: &KFilePlacesModel::setupDone, context: this, slot: &KUrlNavigatorPlacesSelector::onStorageSetupDone);
224
225 m_lastClickedIndex = index;
226 m_lastActivationSignal = activationSignal;
227 m_placesModel->requestSetup(index);
228 return;
229 } else if (index.isValid()) {
230 if (activationSignal == &KUrlNavigatorPlacesSelector::placeActivated) {
231 m_selectedItem = index.row();
232 setIcon(m_placesModel->icon(index));
233 }
234
235 const QUrl url = KFilePlacesModel::convertedUrl(url: m_placesModel->url(index));
236 /*Q_EMIT*/ std::invoke(fn&: activationSignal, args: this, args: url);
237 }
238}
239
240void KUrlNavigatorPlacesSelector::onStorageSetupDone(const QModelIndex &index, bool success)
241{
242 disconnect(sender: m_placesModel, signal: &KFilePlacesModel::setupDone, receiver: this, slot: &KUrlNavigatorPlacesSelector::onStorageSetupDone);
243
244 if (m_lastClickedIndex == index) {
245 if (success) {
246 if (m_lastActivationSignal == &KUrlNavigatorPlacesSelector::placeActivated) {
247 m_selectedItem = index.row();
248 setIcon(m_placesModel->icon(index));
249 }
250
251 const QUrl url = KFilePlacesModel::convertedUrl(url: m_placesModel->url(index));
252 /*Q_EMIT*/ std::invoke(fn&: m_lastActivationSignal, args: this, args: url);
253 }
254 m_lastClickedIndex = QPersistentModelIndex();
255 m_lastActivationSignal = nullptr;
256 }
257}
258
259bool KUrlNavigatorPlacesSelector::eventFilter(QObject *watched, QEvent *event)
260{
261 if (auto *menu = qobject_cast<QMenu *>(object: watched)) {
262 if (event->type() == QEvent::MouseButtonRelease) {
263 QMouseEvent *me = static_cast<QMouseEvent *>(event);
264 if (me->button() == Qt::MiddleButton) {
265 if (QAction *action = menu->activeAction()) {
266 m_placesMenu->close(); // always close top menu
267
268 activatePlace(action, activationSignal: &KUrlNavigatorPlacesSelector::tabRequested);
269 return true;
270 }
271 }
272 }
273 }
274
275 return KUrlNavigatorButtonBase::eventFilter(watched, event);
276}
277
278} // namespace KDEPrivate
279
280#include "moc_kurlnavigatorplacesselector_p.cpp"
281

source code of kio/src/filewidgets/kurlnavigatorplacesselector.cpp