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

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