File indexing completed on 2024-04-28 07:44:11

0001 /*
0002     SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
0003     SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kurlnavigatorbutton_p.h"
0009 
0010 #include "../utils_p.h"
0011 #include "kurlnavigator.h"
0012 #include "kurlnavigatormenu_p.h"
0013 #include <kio/listjob.h>
0014 #include <kio/statjob.h>
0015 
0016 #include <KLocalizedString>
0017 #include <KStringHandler>
0018 
0019 #include <QCollator>
0020 #include <QKeyEvent>
0021 #include <QMimeData>
0022 #include <QPainter>
0023 #include <QStyleOption>
0024 #include <QTimer>
0025 
0026 namespace KDEPrivate
0027 {
0028 QPointer<KUrlNavigatorMenu> KUrlNavigatorButton::m_subDirsMenu;
0029 
0030 KUrlNavigatorButton::KUrlNavigatorButton(const QUrl &url, KUrlNavigator *parent)
0031     : KUrlNavigatorButtonBase(parent)
0032     , m_hoverArrow(false)
0033     , m_pendingTextChange(false)
0034     , m_replaceButton(false)
0035     , m_showMnemonic(false)
0036     , m_wheelSteps(0)
0037     , m_url(url)
0038     , m_subDir()
0039     , m_openSubDirsTimer(nullptr)
0040     , m_subDirsJob(nullptr)
0041 {
0042     setAcceptDrops(true);
0043     setUrl(url);
0044     setMouseTracking(true);
0045 
0046     m_openSubDirsTimer = new QTimer(this);
0047     m_openSubDirsTimer->setSingleShot(true);
0048     m_openSubDirsTimer->setInterval(300);
0049     connect(m_openSubDirsTimer, &QTimer::timeout, this, &KUrlNavigatorButton::startSubDirsJob);
0050 
0051     connect(this, &QAbstractButton::pressed, this, &KUrlNavigatorButton::requestSubDirs);
0052 }
0053 
0054 KUrlNavigatorButton::~KUrlNavigatorButton()
0055 {
0056 }
0057 
0058 void KUrlNavigatorButton::setUrl(const QUrl &url)
0059 {
0060     m_url = url;
0061 
0062     // Doing a text-resolving with KIO::stat() for all non-local
0063     // URLs leads to problems for protocols where a limit is given for
0064     // the number of parallel connections. A black-list
0065     // is given where KIO::stat() should not be used:
0066     static const QSet<QString> protocolBlacklist = QSet<QString>{
0067         QStringLiteral("nfs"),
0068         QStringLiteral("fish"),
0069         QStringLiteral("ftp"),
0070         QStringLiteral("sftp"),
0071         QStringLiteral("smb"),
0072         QStringLiteral("webdav"),
0073         QStringLiteral("mtp"),
0074     };
0075 
0076     const bool startTextResolving = m_url.isValid() && !m_url.isLocalFile() && !protocolBlacklist.contains(m_url.scheme());
0077 
0078     if (startTextResolving) {
0079         m_pendingTextChange = true;
0080         KIO::StatJob *job = KIO::stat(m_url, KIO::HideProgressInfo);
0081         connect(job, &KJob::result, this, &KUrlNavigatorButton::statFinished);
0082         Q_EMIT startedTextResolving();
0083     } else {
0084         setText(m_url.fileName().replace(QLatin1Char('&'), QLatin1String("&&")));
0085     }
0086 }
0087 
0088 QUrl KUrlNavigatorButton::url() const
0089 {
0090     return m_url;
0091 }
0092 
0093 void KUrlNavigatorButton::setText(const QString &text)
0094 {
0095     QString adjustedText = text;
0096     if (adjustedText.isEmpty()) {
0097         adjustedText = m_url.scheme();
0098     }
0099     // Assure that the button always consists of one line
0100     adjustedText.remove(QLatin1Char('\n'));
0101 
0102     KUrlNavigatorButtonBase::setText(adjustedText);
0103     updateMinimumWidth();
0104 
0105     // Assure that statFinished() does not overwrite a text that has been
0106     // set by a client of the URL navigator button
0107     m_pendingTextChange = false;
0108 }
0109 
0110 void KUrlNavigatorButton::setActiveSubDirectory(const QString &subDir)
0111 {
0112     m_subDir = subDir;
0113 
0114     // We use a different (bold) font on active, so the size hint changes
0115     updateGeometry();
0116     update();
0117 }
0118 
0119 QString KUrlNavigatorButton::activeSubDirectory() const
0120 {
0121     return m_subDir;
0122 }
0123 
0124 QSize KUrlNavigatorButton::sizeHint() const
0125 {
0126     QFont adjustedFont(font());
0127     adjustedFont.setBold(m_subDir.isEmpty());
0128     // the minimum size is textWidth + arrowWidth() + 2 * BorderWidth; for the
0129     // preferred size we add the BorderWidth 2 times again for having an uncluttered look
0130     const int width = QFontMetrics(adjustedFont).size(Qt::TextSingleLine, plainText()).width() + arrowWidth() + 4 * BorderWidth;
0131     return QSize(width, KUrlNavigatorButtonBase::sizeHint().height());
0132 }
0133 
0134 void KUrlNavigatorButton::setShowMnemonic(bool show)
0135 {
0136     if (m_showMnemonic != show) {
0137         m_showMnemonic = show;
0138         update();
0139     }
0140 }
0141 
0142 bool KUrlNavigatorButton::showMnemonic() const
0143 {
0144     return m_showMnemonic;
0145 }
0146 
0147 void KUrlNavigatorButton::paintEvent(QPaintEvent *event)
0148 {
0149     Q_UNUSED(event);
0150 
0151     QPainter painter(this);
0152 
0153     QFont adjustedFont(font());
0154     adjustedFont.setBold(m_subDir.isEmpty());
0155     painter.setFont(adjustedFont);
0156 
0157     int buttonWidth = width();
0158     int preferredWidth = sizeHint().width();
0159     if (preferredWidth < minimumWidth()) {
0160         preferredWidth = minimumWidth();
0161     }
0162     if (buttonWidth > preferredWidth) {
0163         buttonWidth = preferredWidth;
0164     }
0165     const int buttonHeight = height();
0166 
0167     const QColor fgColor = foregroundColor();
0168     drawHoverBackground(&painter);
0169 
0170     int textLeft = 0;
0171     int textWidth = buttonWidth;
0172 
0173     const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
0174 
0175     if (!m_subDir.isEmpty()) {
0176         // draw arrow
0177         const int arrowSize = arrowWidth();
0178         const int arrowX = leftToRight ? (buttonWidth - arrowSize) - BorderWidth : BorderWidth;
0179         const int arrowY = (buttonHeight - arrowSize) / 2;
0180 
0181         QStyleOption option;
0182         option.initFrom(this);
0183         option.rect = QRect(arrowX, arrowY, arrowSize, arrowSize);
0184         option.palette = palette();
0185         option.palette.setColor(QPalette::Text, fgColor);
0186         option.palette.setColor(QPalette::WindowText, fgColor);
0187         option.palette.setColor(QPalette::ButtonText, fgColor);
0188 
0189         if (m_hoverArrow) {
0190             // highlight the background of the arrow to indicate that the directories
0191             // popup can be opened by a mouse click
0192             QColor hoverColor = palette().color(QPalette::HighlightedText);
0193             hoverColor.setAlpha(96);
0194             painter.setPen(Qt::NoPen);
0195             painter.setBrush(hoverColor);
0196 
0197             int hoverX = arrowX;
0198             if (!leftToRight) {
0199                 hoverX -= BorderWidth;
0200             }
0201             painter.drawRect(QRect(hoverX, 0, arrowSize + BorderWidth, buttonHeight));
0202         }
0203 
0204         if (leftToRight) {
0205             style()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &option, &painter, this);
0206         } else {
0207             style()->drawPrimitive(QStyle::PE_IndicatorArrowLeft, &option, &painter, this);
0208             textLeft += arrowSize + 2 * BorderWidth;
0209         }
0210 
0211         textWidth -= arrowSize + 2 * BorderWidth;
0212     }
0213 
0214     painter.setPen(fgColor);
0215     const bool clipped = isTextClipped();
0216     const QRect textRect(textLeft, 0, textWidth, buttonHeight);
0217     if (clipped) {
0218         QColor bgColor = fgColor;
0219         bgColor.setAlpha(0);
0220         QLinearGradient gradient(textRect.topLeft(), textRect.topRight());
0221         if (leftToRight) {
0222             gradient.setColorAt(0.8, fgColor);
0223             gradient.setColorAt(1.0, bgColor);
0224         } else {
0225             gradient.setColorAt(0.0, bgColor);
0226             gradient.setColorAt(0.2, fgColor);
0227         }
0228 
0229         QPen pen;
0230         pen.setBrush(QBrush(gradient));
0231         painter.setPen(pen);
0232     }
0233 
0234     int textFlags = clipped ? Qt::AlignVCenter : Qt::AlignCenter;
0235     if (m_showMnemonic) {
0236         textFlags |= Qt::TextShowMnemonic;
0237         painter.drawText(textRect, textFlags, text());
0238     } else {
0239         painter.drawText(textRect, textFlags, plainText());
0240     }
0241 }
0242 
0243 void KUrlNavigatorButton::enterEvent(QEnterEvent *event)
0244 {
0245     KUrlNavigatorButtonBase::enterEvent(event);
0246 
0247     // if the text is clipped due to a small window width, the text should
0248     // be shown as tooltip
0249     if (isTextClipped()) {
0250         setToolTip(plainText());
0251     }
0252 }
0253 
0254 void KUrlNavigatorButton::leaveEvent(QEvent *event)
0255 {
0256     KUrlNavigatorButtonBase::leaveEvent(event);
0257     setToolTip(QString());
0258 
0259     if (m_hoverArrow) {
0260         m_hoverArrow = false;
0261         update();
0262     }
0263 }
0264 
0265 void KUrlNavigatorButton::keyPressEvent(QKeyEvent *event)
0266 {
0267     switch (event->key()) {
0268     case Qt::Key_Enter:
0269     case Qt::Key_Return:
0270         Q_EMIT navigatorButtonActivated(m_url, Qt::LeftButton, event->modifiers());
0271         break;
0272     case Qt::Key_Down:
0273     case Qt::Key_Space:
0274         startSubDirsJob();
0275         break;
0276     default:
0277         KUrlNavigatorButtonBase::keyPressEvent(event);
0278     }
0279 }
0280 
0281 void KUrlNavigatorButton::dropEvent(QDropEvent *event)
0282 {
0283     if (event->mimeData()->hasUrls()) {
0284         setDisplayHintEnabled(DraggedHint, true);
0285 
0286         Q_EMIT urlsDroppedOnNavButton(m_url, event);
0287 
0288         setDisplayHintEnabled(DraggedHint, false);
0289         update();
0290     }
0291 }
0292 
0293 void KUrlNavigatorButton::dragEnterEvent(QDragEnterEvent *event)
0294 {
0295     if (event->mimeData()->hasUrls()) {
0296         setDisplayHintEnabled(DraggedHint, true);
0297         event->acceptProposedAction();
0298 
0299         update();
0300     }
0301 }
0302 
0303 void KUrlNavigatorButton::dragMoveEvent(QDragMoveEvent *event)
0304 {
0305     QRect rect = event->answerRect();
0306     if (isAboveArrow(rect.center().x())) {
0307         m_hoverArrow = true;
0308         update();
0309 
0310         if (m_subDirsMenu == nullptr) {
0311             requestSubDirs();
0312         } else if (m_subDirsMenu->parent() != this) {
0313             m_subDirsMenu->close();
0314             m_subDirsMenu->deleteLater();
0315             m_subDirsMenu = nullptr;
0316 
0317             requestSubDirs();
0318         }
0319     } else {
0320         if (m_openSubDirsTimer->isActive()) {
0321             cancelSubDirsRequest();
0322         }
0323         m_subDirsMenu->deleteLater();
0324         m_subDirsMenu = nullptr;
0325         m_hoverArrow = false;
0326         update();
0327     }
0328 }
0329 
0330 void KUrlNavigatorButton::dragLeaveEvent(QDragLeaveEvent *event)
0331 {
0332     KUrlNavigatorButtonBase::dragLeaveEvent(event);
0333 
0334     m_hoverArrow = false;
0335     setDisplayHintEnabled(DraggedHint, false);
0336     update();
0337 }
0338 
0339 void KUrlNavigatorButton::mousePressEvent(QMouseEvent *event)
0340 {
0341     if (isAboveArrow(qRound(event->position().x())) && (event->button() == Qt::LeftButton)) {
0342         // the mouse is pressed above the [>] button
0343         startSubDirsJob();
0344     }
0345     KUrlNavigatorButtonBase::mousePressEvent(event);
0346 }
0347 
0348 void KUrlNavigatorButton::mouseReleaseEvent(QMouseEvent *event)
0349 {
0350     if (!isAboveArrow(qRound(event->position().x())) || (event->button() != Qt::LeftButton)) {
0351         // the mouse has been released above the text area and not
0352         // above the [>] button
0353         Q_EMIT navigatorButtonActivated(m_url, event->button(), event->modifiers());
0354         cancelSubDirsRequest();
0355     }
0356     KUrlNavigatorButtonBase::mouseReleaseEvent(event);
0357 }
0358 
0359 void KUrlNavigatorButton::mouseMoveEvent(QMouseEvent *event)
0360 {
0361     KUrlNavigatorButtonBase::mouseMoveEvent(event);
0362 
0363     const bool hoverArrow = isAboveArrow(qRound(event->position().x()));
0364     if (hoverArrow != m_hoverArrow) {
0365         m_hoverArrow = hoverArrow;
0366         update();
0367     }
0368 }
0369 
0370 void KUrlNavigatorButton::wheelEvent(QWheelEvent *event)
0371 {
0372     if (event->angleDelta().y() != 0) {
0373         m_wheelSteps = event->angleDelta().y() / 120;
0374         m_replaceButton = true;
0375         startSubDirsJob();
0376     }
0377 
0378     KUrlNavigatorButtonBase::wheelEvent(event);
0379 }
0380 
0381 void KUrlNavigatorButton::requestSubDirs()
0382 {
0383     if (!m_openSubDirsTimer->isActive() && (m_subDirsJob == nullptr)) {
0384         m_openSubDirsTimer->start();
0385     }
0386 }
0387 
0388 void KUrlNavigatorButton::startSubDirsJob()
0389 {
0390     if (m_subDirsJob != nullptr) {
0391         return;
0392     }
0393 
0394     const QUrl url = m_replaceButton ? KIO::upUrl(m_url) : m_url;
0395     const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(parent());
0396     Q_ASSERT(urlNavigator);
0397     m_subDirsJob =
0398         KIO::listDir(url, KIO::HideProgressInfo, urlNavigator->showHiddenFolders() ? KIO::ListJob::ListFlag::IncludeHidden : KIO::ListJob::ListFlags{});
0399     m_subDirs.clear(); // just to be ++safe
0400 
0401     connect(m_subDirsJob, &KIO::ListJob::entries, this, &KUrlNavigatorButton::addEntriesToSubDirs);
0402 
0403     if (m_replaceButton) {
0404         connect(m_subDirsJob, &KJob::result, this, &KUrlNavigatorButton::replaceButton);
0405     } else {
0406         connect(m_subDirsJob, &KJob::result, this, &KUrlNavigatorButton::openSubDirsMenu);
0407     }
0408 }
0409 
0410 void KUrlNavigatorButton::addEntriesToSubDirs(KIO::Job *job, const KIO::UDSEntryList &entries)
0411 {
0412     Q_ASSERT(job == m_subDirsJob);
0413     Q_UNUSED(job);
0414 
0415     for (const KIO::UDSEntry &entry : entries) {
0416         if (entry.isDir()) {
0417             const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
0418             QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
0419             if (displayName.isEmpty()) {
0420                 displayName = name;
0421             }
0422             if (name != QLatin1String(".") && name != QLatin1String("..")) {
0423                 m_subDirs.push_back({name, displayName});
0424             }
0425         }
0426     }
0427 }
0428 
0429 void KUrlNavigatorButton::slotUrlsDropped(QAction *action, QDropEvent *event)
0430 {
0431     const int result = action->data().toInt();
0432     QUrl url(m_url);
0433     url.setPath(Utils::concatPaths(url.path(), m_subDirs.at(result).name));
0434     Q_EMIT urlsDroppedOnNavButton(url, event);
0435 }
0436 
0437 void KUrlNavigatorButton::slotMenuActionClicked(QAction *action, Qt::MouseButton button)
0438 {
0439     const int result = action->data().toInt();
0440     QUrl url(m_url);
0441     url.setPath(Utils::concatPaths(url.path(), m_subDirs.at(result).name));
0442     Q_EMIT navigatorButtonActivated(url, button, Qt::NoModifier);
0443 }
0444 
0445 void KUrlNavigatorButton::statFinished(KJob *job)
0446 {
0447     if (m_pendingTextChange) {
0448         m_pendingTextChange = false;
0449 
0450         const KIO::UDSEntry entry = static_cast<KIO::StatJob *>(job)->statResult();
0451         QString name = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
0452         if (name.isEmpty()) {
0453             name = m_url.fileName();
0454         }
0455         setText(name);
0456 
0457         Q_EMIT finishedTextResolving();
0458     }
0459 }
0460 
0461 /**
0462  * Helper struct for sorting folder names
0463  */
0464 struct FolderNameNaturalLessThan {
0465     FolderNameNaturalLessThan(bool sortHiddenLast)
0466         : m_sortHiddenLast(sortHiddenLast)
0467     {
0468         m_collator.setCaseSensitivity(Qt::CaseInsensitive);
0469         m_collator.setNumericMode(true);
0470     }
0471 
0472     bool operator()(const KUrlNavigatorButton::SubDirInfo &a, const KUrlNavigatorButton::SubDirInfo &b)
0473     {
0474         if (m_sortHiddenLast) {
0475             const bool isHiddenA = a.name.startsWith(QLatin1Char('.'));
0476             const bool isHiddenB = b.name.startsWith(QLatin1Char('.'));
0477             if (isHiddenA && !isHiddenB) {
0478                 return false;
0479             }
0480             if (!isHiddenA && isHiddenB) {
0481                 return true;
0482             }
0483         }
0484         return m_collator.compare(a.name, b.name) < 0;
0485     }
0486 
0487 private:
0488     QCollator m_collator;
0489     bool m_sortHiddenLast;
0490 };
0491 
0492 void KUrlNavigatorButton::openSubDirsMenu(KJob *job)
0493 {
0494     Q_ASSERT(job == m_subDirsJob);
0495     m_subDirsJob = nullptr;
0496 
0497     if (job->error() || m_subDirs.empty()) {
0498         // clear listing
0499         return;
0500     }
0501 
0502     const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(parent());
0503     Q_ASSERT(urlNavigator);
0504     FolderNameNaturalLessThan less(urlNavigator->showHiddenFolders() && urlNavigator->sortHiddenFoldersLast());
0505     std::sort(m_subDirs.begin(), m_subDirs.end(), less);
0506     setDisplayHintEnabled(PopupActiveHint, true);
0507     update(); // ensure the button is drawn highlighted
0508 
0509     if (m_subDirsMenu != nullptr) {
0510         m_subDirsMenu->close();
0511         m_subDirsMenu->deleteLater();
0512         m_subDirsMenu = nullptr;
0513     }
0514 
0515     m_subDirsMenu = new KUrlNavigatorMenu(this);
0516     initMenu(m_subDirsMenu, 0);
0517 
0518     const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
0519     const int popupX = leftToRight ? width() - arrowWidth() - BorderWidth : 0;
0520     const QPoint popupPos = parentWidget()->mapToGlobal(geometry().bottomLeft() + QPoint(popupX, 0));
0521 
0522     QPointer<QObject> guard(this);
0523 
0524     m_subDirsMenu->exec(popupPos);
0525 
0526     // If 'this' has been deleted in the menu's nested event loop, we have to return
0527     // immediately because any access to a member variable might cause a crash.
0528     if (!guard) {
0529         return;
0530     }
0531 
0532     m_subDirs.clear();
0533     delete m_subDirsMenu;
0534     m_subDirsMenu = nullptr;
0535 
0536     setDisplayHintEnabled(PopupActiveHint, false);
0537 }
0538 
0539 void KUrlNavigatorButton::replaceButton(KJob *job)
0540 {
0541     Q_ASSERT(job == m_subDirsJob);
0542     m_subDirsJob = nullptr;
0543     m_replaceButton = false;
0544 
0545     if (job->error() || m_subDirs.empty()) {
0546         return;
0547     }
0548 
0549     const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(parent());
0550     Q_ASSERT(urlNavigator);
0551     FolderNameNaturalLessThan less(urlNavigator->showHiddenFolders() && urlNavigator->sortHiddenFoldersLast());
0552     std::sort(m_subDirs.begin(), m_subDirs.end(), less);
0553 
0554     // Get index of the directory that is shown currently in the button
0555     const QString currentDir = m_url.fileName();
0556     int currentIndex = 0;
0557     const int subDirsCount = m_subDirs.size();
0558     while (currentIndex < subDirsCount) {
0559         if (m_subDirs[currentIndex].name == currentDir) {
0560             break;
0561         }
0562         ++currentIndex;
0563     }
0564 
0565     // Adjust the index by respecting the wheel steps and
0566     // trigger a replacing of the button content
0567     int targetIndex = currentIndex - m_wheelSteps;
0568     if (targetIndex < 0) {
0569         targetIndex = 0;
0570     } else if (targetIndex >= subDirsCount) {
0571         targetIndex = subDirsCount - 1;
0572     }
0573 
0574     QUrl url(KIO::upUrl(m_url));
0575     url.setPath(Utils::concatPaths(url.path(), m_subDirs[targetIndex].name));
0576     Q_EMIT navigatorButtonActivated(url, Qt::LeftButton, Qt::NoModifier);
0577 
0578     m_subDirs.clear();
0579 }
0580 
0581 void KUrlNavigatorButton::cancelSubDirsRequest()
0582 {
0583     m_openSubDirsTimer->stop();
0584     if (m_subDirsJob != nullptr) {
0585         m_subDirsJob->kill();
0586         m_subDirsJob = nullptr;
0587     }
0588 }
0589 
0590 QString KUrlNavigatorButton::plainText() const
0591 {
0592     // Replace all "&&" by '&' and remove all single
0593     // '&' characters
0594     const QString source = text();
0595     const int sourceLength = source.length();
0596 
0597     QString dest;
0598     dest.resize(sourceLength);
0599 
0600     int sourceIndex = 0;
0601     int destIndex = 0;
0602     while (sourceIndex < sourceLength) {
0603         if (source.at(sourceIndex) == QLatin1Char('&')) {
0604             ++sourceIndex;
0605             if (sourceIndex >= sourceLength) {
0606                 break;
0607             }
0608         }
0609         dest[destIndex] = source.at(sourceIndex);
0610         ++sourceIndex;
0611         ++destIndex;
0612     }
0613 
0614     dest.resize(destIndex);
0615 
0616     return dest;
0617 }
0618 
0619 int KUrlNavigatorButton::arrowWidth() const
0620 {
0621     // if there isn't arrow then return 0
0622     int width = 0;
0623     if (!m_subDir.isEmpty()) {
0624         width = height() / 2;
0625         if (width < 4) {
0626             width = 4;
0627         }
0628     }
0629 
0630     return width;
0631 }
0632 
0633 bool KUrlNavigatorButton::isAboveArrow(int x) const
0634 {
0635     const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
0636     return leftToRight ? (x >= width() - arrowWidth()) : (x < arrowWidth());
0637 }
0638 
0639 bool KUrlNavigatorButton::isTextClipped() const
0640 {
0641     int availableWidth = width() - 2 * BorderWidth;
0642     if (!m_subDir.isEmpty()) {
0643         availableWidth -= arrowWidth() - BorderWidth;
0644     }
0645 
0646     QFont adjustedFont(font());
0647     adjustedFont.setBold(m_subDir.isEmpty());
0648     return QFontMetrics(adjustedFont).size(Qt::TextSingleLine, plainText()).width() >= availableWidth;
0649 }
0650 
0651 void KUrlNavigatorButton::updateMinimumWidth()
0652 {
0653     const int oldMinWidth = minimumWidth();
0654 
0655     int minWidth = sizeHint().width();
0656     if (minWidth < 40) {
0657         minWidth = 40;
0658     } else if (minWidth > 150) {
0659         // don't let an overlong path name waste all the URL navigator space
0660         minWidth = 150;
0661     }
0662     if (oldMinWidth != minWidth) {
0663         setMinimumWidth(minWidth);
0664     }
0665 }
0666 
0667 void KUrlNavigatorButton::initMenu(KUrlNavigatorMenu *menu, int startIndex)
0668 {
0669     connect(menu, &KUrlNavigatorMenu::mouseButtonClicked, this, &KUrlNavigatorButton::slotMenuActionClicked);
0670     connect(menu, &KUrlNavigatorMenu::urlsDropped, this, &KUrlNavigatorButton::slotUrlsDropped);
0671 
0672     // So that triggering a menu item with the keyboard works
0673     connect(menu, &QMenu::triggered, this, [this](QAction *act) {
0674         slotMenuActionClicked(act, Qt::LeftButton);
0675     });
0676 
0677     menu->setLayoutDirection(Qt::LeftToRight);
0678 
0679     const int maxIndex = startIndex + 30; // Don't show more than 30 items in a menu
0680     const int subDirsSize = m_subDirs.size();
0681     const int lastIndex = std::min(subDirsSize - 1, maxIndex);
0682     for (int i = startIndex; i <= lastIndex; ++i) {
0683         const auto &[subDirName, subDirDisplayName] = m_subDirs[i];
0684         QString text = KStringHandler::csqueeze(subDirDisplayName, 60);
0685         text.replace(QLatin1Char('&'), QLatin1String("&&"));
0686         QAction *action = new QAction(text, this);
0687         if (m_subDir == subDirName) {
0688             QFont font(action->font());
0689             font.setBold(true);
0690             action->setFont(font);
0691         }
0692         action->setData(i);
0693         menu->addAction(action);
0694     }
0695     if (subDirsSize > maxIndex) {
0696         // If too much items are shown, move them into a sub menu
0697         menu->addSeparator();
0698         KUrlNavigatorMenu *subDirsMenu = new KUrlNavigatorMenu(menu);
0699         subDirsMenu->setTitle(i18nc("@action:inmenu", "More"));
0700         initMenu(subDirsMenu, maxIndex);
0701         menu->addMenu(subDirsMenu);
0702     }
0703 }
0704 
0705 } // namespace KDEPrivate
0706 
0707 #include "moc_kurlnavigatorbutton_p.cpp"