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

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