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
26namespace KDEPrivate
27{
28QPointer<KUrlNavigatorMenu> KUrlNavigatorButton::m_subDirsMenu;
29
30KUrlNavigatorButton::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
54KUrlNavigatorButton::~KUrlNavigatorButton()
55{
56}
57
58void 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
88QUrl KUrlNavigatorButton::url() const
89{
90 return m_url;
91}
92
93void 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
110void 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
119QString KUrlNavigatorButton::activeSubDirectory() const
120{
121 return m_subDir;
122}
123
124QSize 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
134void KUrlNavigatorButton::setShowMnemonic(bool show)
135{
136 if (m_showMnemonic != show) {
137 m_showMnemonic = show;
138 update();
139 }
140}
141
142bool KUrlNavigatorButton::showMnemonic() const
143{
144 return m_showMnemonic;
145}
146
147void 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
243void 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
254void 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
265void 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
281void 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
293void 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
303void 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
332void KUrlNavigatorButton::dragLeaveEvent(QDragLeaveEvent *event)
333{
334 KUrlNavigatorButtonBase::dragLeaveEvent(event);
335
336 m_hoverArrow = false;
337 setDisplayHintEnabled(hint: DraggedHint, enable: false);
338 update();
339}
340
341void 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
350void 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
361void 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
372void 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
383void KUrlNavigatorButton::requestSubDirs()
384{
385 if (!m_openSubDirsTimer->isActive() && (m_subDirsJob == nullptr)) {
386 m_openSubDirsTimer->start();
387 }
388}
389
390void 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
412void 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
431void 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
439void KUrlNavigatorButton::slotMenuActionClicked(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
447void 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 */
466struct 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
489private:
490 QCollator m_collator;
491 bool m_sortHiddenLast;
492};
493
494void KUrlNavigatorButton::openSubDirsMenu(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 popupX = leftToRight ? width() - arrowWidth() - BorderWidth : 0;
522 const QPoint popupPos = 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
541void 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
583void KUrlNavigatorButton::cancelSubDirsRequest()
584{
585 m_openSubDirsTimer->stop();
586 if (m_subDirsJob != nullptr) {
587 m_subDirsJob->kill();
588 m_subDirsJob = nullptr;
589 }
590}
591
592QString 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
621int 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
635bool KUrlNavigatorButton::isAboveArrow(int x) const
636{
637 const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
638 return leftToRight ? (x >= width() - arrowWidth()) : (x < arrowWidth());
639}
640
641bool 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
653void 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
669void KUrlNavigatorButton::initMenu(KUrlNavigatorMenu *menu, 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 subDirsSize = 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 *subDirsMenu = 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

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