1 | /* |
2 | SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at> |
3 | SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | */ |
7 | |
8 | #include "kurlnavigatorbutton_p.h" |
9 | |
10 | #include "../utils_p.h" |
11 | #include "kurlnavigator.h" |
12 | #include "kurlnavigatormenu_p.h" |
13 | #include <kio/listjob.h> |
14 | #include <kio/statjob.h> |
15 | |
16 | #include <KLocalizedString> |
17 | #include <KStringHandler> |
18 | |
19 | #include <QCollator> |
20 | #include <QKeyEvent> |
21 | #include <QMimeData> |
22 | #include <QPainter> |
23 | #include <QStyleOption> |
24 | #include <QTimer> |
25 | |
26 | namespace KDEPrivate |
27 | { |
28 | QPointer<KUrlNavigatorMenu> KUrlNavigatorButton::; |
29 | |
30 | KUrlNavigatorButton::KUrlNavigatorButton(const QUrl &url, KUrlNavigator *parent) |
31 | : KUrlNavigatorButtonBase(parent) |
32 | , m_hoverArrow(false) |
33 | , m_pendingTextChange(false) |
34 | , m_replaceButton(false) |
35 | , m_showMnemonic(false) |
36 | , m_wheelSteps(0) |
37 | , m_url(url) |
38 | , m_subDir() |
39 | , m_openSubDirsTimer(nullptr) |
40 | , m_subDirsJob(nullptr) |
41 | { |
42 | setAcceptDrops(true); |
43 | setUrl(url); |
44 | setMouseTracking(true); |
45 | |
46 | m_openSubDirsTimer = new QTimer(this); |
47 | m_openSubDirsTimer->setSingleShot(true); |
48 | m_openSubDirsTimer->setInterval(300); |
49 | connect(sender: m_openSubDirsTimer, signal: &QTimer::timeout, context: this, slot: &KUrlNavigatorButton::startSubDirsJob); |
50 | |
51 | connect(sender: this, signal: &QAbstractButton::pressed, context: this, slot: &KUrlNavigatorButton::requestSubDirs); |
52 | } |
53 | |
54 | KUrlNavigatorButton::~KUrlNavigatorButton() |
55 | { |
56 | } |
57 | |
58 | void KUrlNavigatorButton::setUrl(const QUrl &url) |
59 | { |
60 | m_url = url; |
61 | |
62 | // Doing a text-resolving with KIO::stat() for all non-local |
63 | // URLs leads to problems for protocols where a limit is given for |
64 | // the number of parallel connections. A black-list |
65 | // is given where KIO::stat() should not be used: |
66 | static const QSet<QString> protocolBlacklist = QSet<QString>{ |
67 | QStringLiteral("nfs" ), |
68 | QStringLiteral("fish" ), |
69 | QStringLiteral("ftp" ), |
70 | QStringLiteral("sftp" ), |
71 | QStringLiteral("smb" ), |
72 | QStringLiteral("webdav" ), |
73 | QStringLiteral("mtp" ), |
74 | }; |
75 | |
76 | const bool startTextResolving = m_url.isValid() && !m_url.isLocalFile() && !protocolBlacklist.contains(value: m_url.scheme()); |
77 | |
78 | if (startTextResolving) { |
79 | m_pendingTextChange = true; |
80 | KIO::StatJob *job = KIO::stat(url: m_url, flags: KIO::HideProgressInfo); |
81 | connect(sender: job, signal: &KJob::result, context: this, slot: &KUrlNavigatorButton::statFinished); |
82 | Q_EMIT startedTextResolving(); |
83 | } else { |
84 | setText(m_url.fileName().replace(c: QLatin1Char('&'), after: QLatin1String("&&" ))); |
85 | } |
86 | } |
87 | |
88 | QUrl KUrlNavigatorButton::url() const |
89 | { |
90 | return m_url; |
91 | } |
92 | |
93 | void KUrlNavigatorButton::setText(const QString &text) |
94 | { |
95 | QString adjustedText = text; |
96 | if (adjustedText.isEmpty()) { |
97 | adjustedText = m_url.scheme(); |
98 | } |
99 | // Assure that the button always consists of one line |
100 | adjustedText.remove(c: QLatin1Char('\n')); |
101 | |
102 | KUrlNavigatorButtonBase::setText(adjustedText); |
103 | updateMinimumWidth(); |
104 | |
105 | // Assure that statFinished() does not overwrite a text that has been |
106 | // set by a client of the URL navigator button |
107 | m_pendingTextChange = false; |
108 | } |
109 | |
110 | void KUrlNavigatorButton::setActiveSubDirectory(const QString &subDir) |
111 | { |
112 | m_subDir = subDir; |
113 | |
114 | // We use a different (bold) font on active, so the size hint changes |
115 | updateGeometry(); |
116 | update(); |
117 | } |
118 | |
119 | QString KUrlNavigatorButton::activeSubDirectory() const |
120 | { |
121 | return m_subDir; |
122 | } |
123 | |
124 | QSize KUrlNavigatorButton::sizeHint() const |
125 | { |
126 | QFont adjustedFont(font()); |
127 | adjustedFont.setBold(m_subDir.isEmpty()); |
128 | // the minimum size is textWidth + arrowWidth() + 2 * BorderWidth; for the |
129 | // preferred size we add the BorderWidth 2 times again for having an uncluttered look |
130 | const int width = QFontMetrics(adjustedFont).size(flags: Qt::TextSingleLine, str: plainText()).width() + arrowWidth() + 4 * BorderWidth; |
131 | return QSize(width, KUrlNavigatorButtonBase::sizeHint().height()); |
132 | } |
133 | |
134 | void KUrlNavigatorButton::setShowMnemonic(bool show) |
135 | { |
136 | if (m_showMnemonic != show) { |
137 | m_showMnemonic = show; |
138 | update(); |
139 | } |
140 | } |
141 | |
142 | bool KUrlNavigatorButton::showMnemonic() const |
143 | { |
144 | return m_showMnemonic; |
145 | } |
146 | |
147 | void KUrlNavigatorButton::paintEvent(QPaintEvent *event) |
148 | { |
149 | Q_UNUSED(event); |
150 | |
151 | QPainter painter(this); |
152 | |
153 | QFont adjustedFont(font()); |
154 | adjustedFont.setBold(m_subDir.isEmpty()); |
155 | painter.setFont(adjustedFont); |
156 | |
157 | int buttonWidth = width(); |
158 | int preferredWidth = sizeHint().width(); |
159 | if (preferredWidth < minimumWidth()) { |
160 | preferredWidth = minimumWidth(); |
161 | } |
162 | if (buttonWidth > preferredWidth) { |
163 | buttonWidth = preferredWidth; |
164 | } |
165 | const int buttonHeight = height(); |
166 | |
167 | const QColor fgColor = foregroundColor(); |
168 | drawHoverBackground(painter: &painter); |
169 | |
170 | int textLeft = 0; |
171 | int textWidth = buttonWidth; |
172 | |
173 | const bool leftToRight = (layoutDirection() == Qt::LeftToRight); |
174 | |
175 | if (!m_subDir.isEmpty()) { |
176 | // draw arrow |
177 | const int arrowSize = arrowWidth(); |
178 | const int arrowX = leftToRight ? (buttonWidth - arrowSize) - BorderWidth : BorderWidth; |
179 | const int arrowY = (buttonHeight - arrowSize) / 2; |
180 | |
181 | QStyleOption option; |
182 | option.initFrom(w: this); |
183 | option.rect = QRect(arrowX, arrowY, arrowSize, arrowSize); |
184 | option.palette = palette(); |
185 | option.palette.setColor(acr: QPalette::Text, acolor: fgColor); |
186 | option.palette.setColor(acr: QPalette::WindowText, acolor: fgColor); |
187 | option.palette.setColor(acr: QPalette::ButtonText, acolor: fgColor); |
188 | |
189 | if (m_hoverArrow) { |
190 | // highlight the background of the arrow to indicate that the directories |
191 | // popup can be opened by a mouse click |
192 | QColor hoverColor = palette().color(cr: QPalette::HighlightedText); |
193 | hoverColor.setAlpha(96); |
194 | painter.setPen(Qt::NoPen); |
195 | painter.setBrush(hoverColor); |
196 | |
197 | int hoverX = arrowX; |
198 | if (!leftToRight) { |
199 | hoverX -= BorderWidth; |
200 | } |
201 | painter.drawRect(r: QRect(hoverX, 0, arrowSize + BorderWidth, buttonHeight)); |
202 | } |
203 | |
204 | if (leftToRight) { |
205 | style()->drawPrimitive(pe: QStyle::PE_IndicatorArrowRight, opt: &option, p: &painter, w: this); |
206 | } else { |
207 | style()->drawPrimitive(pe: QStyle::PE_IndicatorArrowLeft, opt: &option, p: &painter, w: this); |
208 | textLeft += arrowSize + 2 * BorderWidth; |
209 | } |
210 | |
211 | textWidth -= arrowSize + 2 * BorderWidth; |
212 | } |
213 | |
214 | painter.setPen(fgColor); |
215 | const bool clipped = isTextClipped(); |
216 | const QRect textRect(textLeft, 0, textWidth, buttonHeight); |
217 | if (clipped) { |
218 | QColor bgColor = fgColor; |
219 | bgColor.setAlpha(0); |
220 | QLinearGradient gradient(textRect.topLeft(), textRect.topRight()); |
221 | if (leftToRight) { |
222 | gradient.setColorAt(pos: 0.8, color: fgColor); |
223 | gradient.setColorAt(pos: 1.0, color: bgColor); |
224 | } else { |
225 | gradient.setColorAt(pos: 0.0, color: bgColor); |
226 | gradient.setColorAt(pos: 0.2, color: fgColor); |
227 | } |
228 | |
229 | QPen pen; |
230 | pen.setBrush(QBrush(gradient)); |
231 | painter.setPen(pen); |
232 | } |
233 | |
234 | int textFlags = clipped ? Qt::AlignVCenter : Qt::AlignCenter; |
235 | if (m_showMnemonic) { |
236 | textFlags |= Qt::TextShowMnemonic; |
237 | painter.drawText(r: textRect, flags: textFlags, text: text()); |
238 | } else { |
239 | painter.drawText(r: textRect, flags: textFlags, text: plainText()); |
240 | } |
241 | } |
242 | |
243 | void KUrlNavigatorButton::enterEvent(QEnterEvent *event) |
244 | { |
245 | KUrlNavigatorButtonBase::enterEvent(event); |
246 | |
247 | // if the text is clipped due to a small window width, the text should |
248 | // be shown as tooltip |
249 | if (isTextClipped()) { |
250 | setToolTip(plainText()); |
251 | } |
252 | } |
253 | |
254 | void KUrlNavigatorButton::leaveEvent(QEvent *event) |
255 | { |
256 | KUrlNavigatorButtonBase::leaveEvent(event); |
257 | setToolTip(QString()); |
258 | |
259 | if (m_hoverArrow) { |
260 | m_hoverArrow = false; |
261 | update(); |
262 | } |
263 | } |
264 | |
265 | void KUrlNavigatorButton::keyPressEvent(QKeyEvent *event) |
266 | { |
267 | switch (event->key()) { |
268 | case Qt::Key_Enter: |
269 | case Qt::Key_Return: |
270 | Q_EMIT navigatorButtonActivated(url: m_url, button: Qt::LeftButton, modifiers: event->modifiers()); |
271 | break; |
272 | case Qt::Key_Down: |
273 | case Qt::Key_Space: |
274 | startSubDirsJob(); |
275 | break; |
276 | default: |
277 | KUrlNavigatorButtonBase::keyPressEvent(event); |
278 | } |
279 | } |
280 | |
281 | void KUrlNavigatorButton::dropEvent(QDropEvent *event) |
282 | { |
283 | if (event->mimeData()->hasUrls()) { |
284 | setDisplayHintEnabled(hint: DraggedHint, enable: true); |
285 | |
286 | Q_EMIT urlsDroppedOnNavButton(destination: m_url, event); |
287 | |
288 | setDisplayHintEnabled(hint: DraggedHint, enable: false); |
289 | update(); |
290 | } |
291 | } |
292 | |
293 | void KUrlNavigatorButton::dragEnterEvent(QDragEnterEvent *event) |
294 | { |
295 | if (event->mimeData()->hasUrls()) { |
296 | setDisplayHintEnabled(hint: DraggedHint, enable: true); |
297 | event->acceptProposedAction(); |
298 | |
299 | update(); |
300 | } |
301 | } |
302 | |
303 | void KUrlNavigatorButton::dragMoveEvent(QDragMoveEvent *event) |
304 | { |
305 | QRect rect = event->answerRect(); |
306 | if (isAboveArrow(x: rect.center().x())) { |
307 | m_hoverArrow = true; |
308 | update(); |
309 | |
310 | if (m_subDirsMenu == nullptr) { |
311 | requestSubDirs(); |
312 | } else if (m_subDirsMenu->parent() != this) { |
313 | m_subDirsMenu->close(); |
314 | m_subDirsMenu->deleteLater(); |
315 | m_subDirsMenu = nullptr; |
316 | |
317 | requestSubDirs(); |
318 | } |
319 | } else { |
320 | if (m_openSubDirsTimer->isActive()) { |
321 | cancelSubDirsRequest(); |
322 | } |
323 | if (m_subDirsMenu) { |
324 | m_subDirsMenu->deleteLater(); |
325 | m_subDirsMenu = nullptr; |
326 | } |
327 | m_hoverArrow = false; |
328 | update(); |
329 | } |
330 | } |
331 | |
332 | void KUrlNavigatorButton::dragLeaveEvent(QDragLeaveEvent *event) |
333 | { |
334 | KUrlNavigatorButtonBase::dragLeaveEvent(event); |
335 | |
336 | m_hoverArrow = false; |
337 | setDisplayHintEnabled(hint: DraggedHint, enable: false); |
338 | update(); |
339 | } |
340 | |
341 | void KUrlNavigatorButton::mousePressEvent(QMouseEvent *event) |
342 | { |
343 | if (isAboveArrow(x: qRound(d: event->position().x())) && (event->button() == Qt::LeftButton)) { |
344 | // the mouse is pressed above the [>] button |
345 | startSubDirsJob(); |
346 | } |
347 | KUrlNavigatorButtonBase::mousePressEvent(e: event); |
348 | } |
349 | |
350 | void KUrlNavigatorButton::mouseReleaseEvent(QMouseEvent *event) |
351 | { |
352 | if (!isAboveArrow(x: qRound(d: event->position().x())) || (event->button() != Qt::LeftButton)) { |
353 | // the mouse has been released above the text area and not |
354 | // above the [>] button |
355 | Q_EMIT navigatorButtonActivated(url: m_url, button: event->button(), modifiers: event->modifiers()); |
356 | cancelSubDirsRequest(); |
357 | } |
358 | KUrlNavigatorButtonBase::mouseReleaseEvent(e: event); |
359 | } |
360 | |
361 | void KUrlNavigatorButton::mouseMoveEvent(QMouseEvent *event) |
362 | { |
363 | KUrlNavigatorButtonBase::mouseMoveEvent(event); |
364 | |
365 | const bool hoverArrow = isAboveArrow(x: qRound(d: event->position().x())); |
366 | if (hoverArrow != m_hoverArrow) { |
367 | m_hoverArrow = hoverArrow; |
368 | update(); |
369 | } |
370 | } |
371 | |
372 | void KUrlNavigatorButton::wheelEvent(QWheelEvent *event) |
373 | { |
374 | if (event->angleDelta().y() != 0) { |
375 | m_wheelSteps = event->angleDelta().y() / 120; |
376 | m_replaceButton = true; |
377 | startSubDirsJob(); |
378 | } |
379 | |
380 | KUrlNavigatorButtonBase::wheelEvent(event); |
381 | } |
382 | |
383 | void KUrlNavigatorButton::requestSubDirs() |
384 | { |
385 | if (!m_openSubDirsTimer->isActive() && (m_subDirsJob == nullptr)) { |
386 | m_openSubDirsTimer->start(); |
387 | } |
388 | } |
389 | |
390 | void KUrlNavigatorButton::startSubDirsJob() |
391 | { |
392 | if (m_subDirsJob != nullptr) { |
393 | return; |
394 | } |
395 | |
396 | const QUrl url = m_replaceButton ? KIO::upUrl(url: m_url) : m_url; |
397 | const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(object: parent()); |
398 | Q_ASSERT(urlNavigator); |
399 | m_subDirsJob = |
400 | KIO::listDir(url, flags: KIO::HideProgressInfo, listFlags: urlNavigator->showHiddenFolders() ? KIO::ListJob::ListFlag::IncludeHidden : KIO::ListJob::ListFlags{}); |
401 | m_subDirs.clear(); // just to be ++safe |
402 | |
403 | connect(sender: m_subDirsJob, signal: &KIO::ListJob::entries, context: this, slot: &KUrlNavigatorButton::addEntriesToSubDirs); |
404 | |
405 | if (m_replaceButton) { |
406 | connect(sender: m_subDirsJob, signal: &KJob::result, context: this, slot: &KUrlNavigatorButton::replaceButton); |
407 | } else { |
408 | connect(sender: m_subDirsJob, signal: &KJob::result, context: this, slot: &KUrlNavigatorButton::openSubDirsMenu); |
409 | } |
410 | } |
411 | |
412 | void KUrlNavigatorButton::addEntriesToSubDirs(KIO::Job *job, const KIO::UDSEntryList &entries) |
413 | { |
414 | Q_ASSERT(job == m_subDirsJob); |
415 | Q_UNUSED(job); |
416 | |
417 | for (const KIO::UDSEntry &entry : entries) { |
418 | if (entry.isDir()) { |
419 | const QString name = entry.stringValue(field: KIO::UDSEntry::UDS_NAME); |
420 | QString displayName = entry.stringValue(field: KIO::UDSEntry::UDS_DISPLAY_NAME); |
421 | if (displayName.isEmpty()) { |
422 | displayName = name; |
423 | } |
424 | if (name != QLatin1String("." ) && name != QLatin1String(".." )) { |
425 | m_subDirs.push_back(x: {.name: name, .displayName: displayName}); |
426 | } |
427 | } |
428 | } |
429 | } |
430 | |
431 | void KUrlNavigatorButton::slotUrlsDropped(QAction *action, QDropEvent *event) |
432 | { |
433 | const int result = action->data().toInt(); |
434 | QUrl url(m_url); |
435 | url.setPath(path: Utils::concatPaths(path1: url.path(), path2: m_subDirs.at(n: result).name)); |
436 | Q_EMIT urlsDroppedOnNavButton(destination: url, event); |
437 | } |
438 | |
439 | void KUrlNavigatorButton::(QAction *action, Qt::MouseButton button) |
440 | { |
441 | const int result = action->data().toInt(); |
442 | QUrl url(m_url); |
443 | url.setPath(path: Utils::concatPaths(path1: url.path(), path2: m_subDirs.at(n: result).name)); |
444 | Q_EMIT navigatorButtonActivated(url, button, modifiers: Qt::NoModifier); |
445 | } |
446 | |
447 | void KUrlNavigatorButton::statFinished(KJob *job) |
448 | { |
449 | if (m_pendingTextChange) { |
450 | m_pendingTextChange = false; |
451 | |
452 | const KIO::UDSEntry entry = static_cast<KIO::StatJob *>(job)->statResult(); |
453 | QString name = entry.stringValue(field: KIO::UDSEntry::UDS_DISPLAY_NAME); |
454 | if (name.isEmpty()) { |
455 | name = m_url.fileName(); |
456 | } |
457 | setText(name); |
458 | |
459 | Q_EMIT finishedTextResolving(); |
460 | } |
461 | } |
462 | |
463 | /** |
464 | * Helper struct for sorting folder names |
465 | */ |
466 | struct FolderNameNaturalLessThan { |
467 | FolderNameNaturalLessThan(bool sortHiddenLast) |
468 | : m_sortHiddenLast(sortHiddenLast) |
469 | { |
470 | m_collator.setCaseSensitivity(Qt::CaseInsensitive); |
471 | m_collator.setNumericMode(true); |
472 | } |
473 | |
474 | bool operator()(const KUrlNavigatorButton::SubDirInfo &a, const KUrlNavigatorButton::SubDirInfo &b) |
475 | { |
476 | if (m_sortHiddenLast) { |
477 | const bool isHiddenA = a.name.startsWith(c: QLatin1Char('.')); |
478 | const bool isHiddenB = b.name.startsWith(c: QLatin1Char('.')); |
479 | if (isHiddenA && !isHiddenB) { |
480 | return false; |
481 | } |
482 | if (!isHiddenA && isHiddenB) { |
483 | return true; |
484 | } |
485 | } |
486 | return m_collator.compare(s1: a.name, s2: b.name) < 0; |
487 | } |
488 | |
489 | private: |
490 | QCollator m_collator; |
491 | bool m_sortHiddenLast; |
492 | }; |
493 | |
494 | void KUrlNavigatorButton::(KJob *job) |
495 | { |
496 | Q_ASSERT(job == m_subDirsJob); |
497 | m_subDirsJob = nullptr; |
498 | |
499 | if (job->error() || m_subDirs.empty()) { |
500 | // clear listing |
501 | return; |
502 | } |
503 | |
504 | const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(object: parent()); |
505 | Q_ASSERT(urlNavigator); |
506 | FolderNameNaturalLessThan less(urlNavigator->showHiddenFolders() && urlNavigator->sortHiddenFoldersLast()); |
507 | std::sort(first: m_subDirs.begin(), last: m_subDirs.end(), comp: less); |
508 | setDisplayHintEnabled(hint: PopupActiveHint, enable: true); |
509 | update(); // ensure the button is drawn highlighted |
510 | |
511 | if (m_subDirsMenu != nullptr) { |
512 | m_subDirsMenu->close(); |
513 | m_subDirsMenu->deleteLater(); |
514 | m_subDirsMenu = nullptr; |
515 | } |
516 | |
517 | m_subDirsMenu = new KUrlNavigatorMenu(this); |
518 | initMenu(menu: m_subDirsMenu, startIndex: 0); |
519 | |
520 | const bool leftToRight = (layoutDirection() == Qt::LeftToRight); |
521 | const int = leftToRight ? width() - arrowWidth() - BorderWidth : 0; |
522 | const QPoint = parentWidget()->mapToGlobal(geometry().bottomLeft() + QPoint(popupX, 0)); |
523 | |
524 | QPointer<QObject> guard(this); |
525 | |
526 | m_subDirsMenu->exec(pos: popupPos); |
527 | |
528 | // If 'this' has been deleted in the menu's nested event loop, we have to return |
529 | // immediately because any access to a member variable might cause a crash. |
530 | if (!guard) { |
531 | return; |
532 | } |
533 | |
534 | m_subDirs.clear(); |
535 | delete m_subDirsMenu; |
536 | m_subDirsMenu = nullptr; |
537 | |
538 | setDisplayHintEnabled(hint: PopupActiveHint, enable: false); |
539 | } |
540 | |
541 | void KUrlNavigatorButton::replaceButton(KJob *job) |
542 | { |
543 | Q_ASSERT(job == m_subDirsJob); |
544 | m_subDirsJob = nullptr; |
545 | m_replaceButton = false; |
546 | |
547 | if (job->error() || m_subDirs.empty()) { |
548 | return; |
549 | } |
550 | |
551 | const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(object: parent()); |
552 | Q_ASSERT(urlNavigator); |
553 | FolderNameNaturalLessThan less(urlNavigator->showHiddenFolders() && urlNavigator->sortHiddenFoldersLast()); |
554 | std::sort(first: m_subDirs.begin(), last: m_subDirs.end(), comp: less); |
555 | |
556 | // Get index of the directory that is shown currently in the button |
557 | const QString currentDir = m_url.fileName(); |
558 | int currentIndex = 0; |
559 | const int subDirsCount = m_subDirs.size(); |
560 | while (currentIndex < subDirsCount) { |
561 | if (m_subDirs[currentIndex].name == currentDir) { |
562 | break; |
563 | } |
564 | ++currentIndex; |
565 | } |
566 | |
567 | // Adjust the index by respecting the wheel steps and |
568 | // trigger a replacing of the button content |
569 | int targetIndex = currentIndex - m_wheelSteps; |
570 | if (targetIndex < 0) { |
571 | targetIndex = 0; |
572 | } else if (targetIndex >= subDirsCount) { |
573 | targetIndex = subDirsCount - 1; |
574 | } |
575 | |
576 | QUrl url(KIO::upUrl(url: m_url)); |
577 | url.setPath(path: Utils::concatPaths(path1: url.path(), path2: m_subDirs[targetIndex].name)); |
578 | Q_EMIT navigatorButtonActivated(url, button: Qt::LeftButton, modifiers: Qt::NoModifier); |
579 | |
580 | m_subDirs.clear(); |
581 | } |
582 | |
583 | void KUrlNavigatorButton::cancelSubDirsRequest() |
584 | { |
585 | m_openSubDirsTimer->stop(); |
586 | if (m_subDirsJob != nullptr) { |
587 | m_subDirsJob->kill(); |
588 | m_subDirsJob = nullptr; |
589 | } |
590 | } |
591 | |
592 | QString KUrlNavigatorButton::plainText() const |
593 | { |
594 | // Replace all "&&" by '&' and remove all single |
595 | // '&' characters |
596 | const QString source = text(); |
597 | const int sourceLength = source.length(); |
598 | |
599 | QString dest; |
600 | dest.resize(size: sourceLength); |
601 | |
602 | int sourceIndex = 0; |
603 | int destIndex = 0; |
604 | while (sourceIndex < sourceLength) { |
605 | if (source.at(i: sourceIndex) == QLatin1Char('&')) { |
606 | ++sourceIndex; |
607 | if (sourceIndex >= sourceLength) { |
608 | break; |
609 | } |
610 | } |
611 | dest[destIndex] = source.at(i: sourceIndex); |
612 | ++sourceIndex; |
613 | ++destIndex; |
614 | } |
615 | |
616 | dest.resize(size: destIndex); |
617 | |
618 | return dest; |
619 | } |
620 | |
621 | int KUrlNavigatorButton::arrowWidth() const |
622 | { |
623 | // if there isn't arrow then return 0 |
624 | int width = 0; |
625 | if (!m_subDir.isEmpty()) { |
626 | width = height() / 2; |
627 | if (width < 4) { |
628 | width = 4; |
629 | } |
630 | } |
631 | |
632 | return width; |
633 | } |
634 | |
635 | bool KUrlNavigatorButton::isAboveArrow(int x) const |
636 | { |
637 | const bool leftToRight = (layoutDirection() == Qt::LeftToRight); |
638 | return leftToRight ? (x >= width() - arrowWidth()) : (x < arrowWidth()); |
639 | } |
640 | |
641 | bool KUrlNavigatorButton::isTextClipped() const |
642 | { |
643 | int availableWidth = width() - 2 * BorderWidth; |
644 | if (!m_subDir.isEmpty()) { |
645 | availableWidth -= arrowWidth() - BorderWidth; |
646 | } |
647 | |
648 | QFont adjustedFont(font()); |
649 | adjustedFont.setBold(m_subDir.isEmpty()); |
650 | return QFontMetrics(adjustedFont).size(flags: Qt::TextSingleLine, str: plainText()).width() >= availableWidth; |
651 | } |
652 | |
653 | void KUrlNavigatorButton::updateMinimumWidth() |
654 | { |
655 | const int oldMinWidth = minimumWidth(); |
656 | |
657 | int minWidth = sizeHint().width(); |
658 | if (minWidth < 40) { |
659 | minWidth = 40; |
660 | } else if (minWidth > 150) { |
661 | // don't let an overlong path name waste all the URL navigator space |
662 | minWidth = 150; |
663 | } |
664 | if (oldMinWidth != minWidth) { |
665 | setMinimumWidth(minWidth); |
666 | } |
667 | } |
668 | |
669 | void KUrlNavigatorButton::(KUrlNavigatorMenu *, int startIndex) |
670 | { |
671 | connect(sender: menu, signal: &KUrlNavigatorMenu::mouseButtonClicked, context: this, slot: &KUrlNavigatorButton::slotMenuActionClicked); |
672 | connect(sender: menu, signal: &KUrlNavigatorMenu::urlsDropped, context: this, slot: &KUrlNavigatorButton::slotUrlsDropped); |
673 | |
674 | // So that triggering a menu item with the keyboard works |
675 | connect(sender: menu, signal: &QMenu::triggered, context: this, slot: [this](QAction *act) { |
676 | slotMenuActionClicked(action: act, button: Qt::LeftButton); |
677 | }); |
678 | |
679 | menu->setLayoutDirection(Qt::LeftToRight); |
680 | |
681 | const int maxIndex = startIndex + 30; // Don't show more than 30 items in a menu |
682 | const int = m_subDirs.size(); |
683 | const int lastIndex = std::min(a: subDirsSize - 1, b: maxIndex); |
684 | for (int i = startIndex; i <= lastIndex; ++i) { |
685 | const auto &[subDirName, subDirDisplayName] = m_subDirs[i]; |
686 | QString text = KStringHandler::csqueeze(str: subDirDisplayName, maxlen: 60); |
687 | text.replace(c: QLatin1Char('&'), after: QLatin1String("&&" )); |
688 | QAction *action = new QAction(text, this); |
689 | if (m_subDir == subDirName) { |
690 | QFont font(action->font()); |
691 | font.setBold(true); |
692 | action->setFont(font); |
693 | } |
694 | action->setData(i); |
695 | menu->addAction(action); |
696 | } |
697 | if (subDirsSize > maxIndex) { |
698 | // If too much items are shown, move them into a sub menu |
699 | menu->addSeparator(); |
700 | KUrlNavigatorMenu * = new KUrlNavigatorMenu(menu); |
701 | subDirsMenu->setTitle(i18nc("@action:inmenu" , "More" )); |
702 | initMenu(menu: subDirsMenu, startIndex: maxIndex); |
703 | menu->addMenu(menu: subDirsMenu); |
704 | } |
705 | } |
706 | |
707 | } // namespace KDEPrivate |
708 | |
709 | #include "moc_kurlnavigatorbutton_p.cpp" |
710 | |