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 | |
25 | namespace KDEPrivate |
26 | { |
27 | KUrlNavigatorPlacesSelector::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 | |
48 | KUrlNavigatorPlacesSelector::~KUrlNavigatorPlacesSelector() |
49 | { |
50 | } |
51 | |
52 | void KUrlNavigatorPlacesSelector::() |
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 * = 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 * = 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 | |
111 | void 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 | |
135 | QUrl 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 | |
141 | QString 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 | |
147 | QSize KUrlNavigatorPlacesSelector::sizeHint() const |
148 | { |
149 | const int height = KUrlNavigatorButtonBase::sizeHint().height(); |
150 | return QSize(height, height); |
151 | } |
152 | |
153 | void 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 | |
164 | void 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 | |
174 | void KUrlNavigatorPlacesSelector::dragLeaveEvent(QDragLeaveEvent *event) |
175 | { |
176 | KUrlNavigatorButtonBase::dragLeaveEvent(event); |
177 | |
178 | setDisplayHintEnabled(hint: DraggedHint, enable: false); |
179 | update(); |
180 | } |
181 | |
182 | void 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 | |
197 | void 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 | |
208 | void 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 | |
240 | void 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 | |
259 | bool KUrlNavigatorPlacesSelector::eventFilter(QObject *watched, QEvent *event) |
260 | { |
261 | if (auto * = 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 | |