File indexing completed on 2024-04-28 15:26:49

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 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0244 void KUrlNavigatorButton::enterEvent(QEnterEvent *event)
0245 #else
0246 void KUrlNavigatorButton::enterEvent(QEvent *event)
0247 #endif
0248 {
0249     KUrlNavigatorButtonBase::enterEvent(event);
0250 
0251     // if the text is clipped due to a small window width, the text should
0252     // be shown as tooltip
0253     if (isTextClipped()) {
0254         setToolTip(plainText());
0255     }
0256 }
0257 
0258 void KUrlNavigatorButton::leaveEvent(QEvent *event)
0259 {
0260     KUrlNavigatorButtonBase::leaveEvent(event);
0261     setToolTip(QString());
0262 
0263     if (m_hoverArrow) {
0264         m_hoverArrow = false;
0265         update();
0266     }
0267 }
0268 
0269 void KUrlNavigatorButton::keyPressEvent(QKeyEvent *event)
0270 {
0271     switch (event->key()) {
0272     case Qt::Key_Enter:
0273     case Qt::Key_Return:
0274         Q_EMIT navigatorButtonActivated(m_url, Qt::LeftButton, event->modifiers());
0275         break;
0276     case Qt::Key_Down:
0277     case Qt::Key_Space:
0278         startSubDirsJob();
0279         break;
0280     default:
0281         KUrlNavigatorButtonBase::keyPressEvent(event);
0282     }
0283 }
0284 
0285 void KUrlNavigatorButton::dropEvent(QDropEvent *event)
0286 {
0287     if (event->mimeData()->hasUrls()) {
0288         setDisplayHintEnabled(DraggedHint, true);
0289 
0290         Q_EMIT urlsDroppedOnNavButton(m_url, event);
0291 
0292         setDisplayHintEnabled(DraggedHint, false);
0293         update();
0294     }
0295 }
0296 
0297 void KUrlNavigatorButton::dragEnterEvent(QDragEnterEvent *event)
0298 {
0299     if (event->mimeData()->hasUrls()) {
0300         setDisplayHintEnabled(DraggedHint, true);
0301         event->acceptProposedAction();
0302 
0303         update();
0304     }
0305 }
0306 
0307 void KUrlNavigatorButton::dragMoveEvent(QDragMoveEvent *event)
0308 {
0309     QRect rect = event->answerRect();
0310     if (isAboveArrow(rect.center().x())) {
0311         m_hoverArrow = true;
0312         update();
0313 
0314         if (m_subDirsMenu == nullptr) {
0315             requestSubDirs();
0316         } else if (m_subDirsMenu->parent() != this) {
0317             m_subDirsMenu->close();
0318             m_subDirsMenu->deleteLater();
0319             m_subDirsMenu = nullptr;
0320 
0321             requestSubDirs();
0322         }
0323     } else {
0324         if (m_openSubDirsTimer->isActive()) {
0325             cancelSubDirsRequest();
0326         }
0327         m_subDirsMenu->deleteLater();
0328         m_subDirsMenu = nullptr;
0329         m_hoverArrow = false;
0330         update();
0331     }
0332 }
0333 
0334 void KUrlNavigatorButton::dragLeaveEvent(QDragLeaveEvent *event)
0335 {
0336     KUrlNavigatorButtonBase::dragLeaveEvent(event);
0337 
0338     m_hoverArrow = false;
0339     setDisplayHintEnabled(DraggedHint, false);
0340     update();
0341 }
0342 
0343 void KUrlNavigatorButton::mousePressEvent(QMouseEvent *event)
0344 {
0345     if (isAboveArrow(event->x()) && (event->button() == Qt::LeftButton)) {
0346         // the mouse is pressed above the [>] button
0347         startSubDirsJob();
0348     }
0349     KUrlNavigatorButtonBase::mousePressEvent(event);
0350 }
0351 
0352 void KUrlNavigatorButton::mouseReleaseEvent(QMouseEvent *event)
0353 {
0354     if (!isAboveArrow(event->x()) || (event->button() != Qt::LeftButton)) {
0355         // the mouse has been released above the text area and not
0356         // above the [>] button
0357         Q_EMIT navigatorButtonActivated(m_url, event->button(), event->modifiers());
0358         cancelSubDirsRequest();
0359     }
0360     KUrlNavigatorButtonBase::mouseReleaseEvent(event);
0361 }
0362 
0363 void KUrlNavigatorButton::mouseMoveEvent(QMouseEvent *event)
0364 {
0365     KUrlNavigatorButtonBase::mouseMoveEvent(event);
0366 
0367     const bool hoverArrow = isAboveArrow(event->x());
0368     if (hoverArrow != m_hoverArrow) {
0369         m_hoverArrow = hoverArrow;
0370         update();
0371     }
0372 }
0373 
0374 void KUrlNavigatorButton::wheelEvent(QWheelEvent *event)
0375 {
0376     if (event->angleDelta().y() != 0) {
0377         m_wheelSteps = event->angleDelta().y() / 120;
0378         m_replaceButton = true;
0379         startSubDirsJob();
0380     }
0381 
0382     KUrlNavigatorButtonBase::wheelEvent(event);
0383 }
0384 
0385 void KUrlNavigatorButton::requestSubDirs()
0386 {
0387     if (!m_openSubDirsTimer->isActive() && (m_subDirsJob == nullptr)) {
0388         m_openSubDirsTimer->start();
0389     }
0390 }
0391 
0392 void KUrlNavigatorButton::startSubDirsJob()
0393 {
0394     if (m_subDirsJob != nullptr) {
0395         return;
0396     }
0397 
0398     const QUrl url = m_replaceButton ? KIO::upUrl(m_url) : m_url;
0399     const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(parent());
0400     Q_ASSERT(urlNavigator);
0401     m_subDirsJob = KIO::listDir(url, KIO::HideProgressInfo, urlNavigator->showHiddenFolders());
0402     m_subDirs.clear(); // just to be ++safe
0403 
0404     connect(m_subDirsJob, &KIO::ListJob::entries, this, &KUrlNavigatorButton::addEntriesToSubDirs);
0405 
0406     if (m_replaceButton) {
0407         connect(m_subDirsJob, &KJob::result, this, &KUrlNavigatorButton::replaceButton);
0408     } else {
0409         connect(m_subDirsJob, &KJob::result, this, &KUrlNavigatorButton::openSubDirsMenu);
0410     }
0411 }
0412 
0413 void KUrlNavigatorButton::addEntriesToSubDirs(KIO::Job *job, const KIO::UDSEntryList &entries)
0414 {
0415     Q_ASSERT(job == m_subDirsJob);
0416     Q_UNUSED(job);
0417 
0418     for (const KIO::UDSEntry &entry : entries) {
0419         if (entry.isDir()) {
0420             const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
0421             QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
0422             if (displayName.isEmpty()) {
0423                 displayName = name;
0424             }
0425             if (name != QLatin1String(".") && name != QLatin1String("..")) {
0426                 m_subDirs.push_back({name, displayName});
0427             }
0428         }
0429     }
0430 }
0431 
0432 void KUrlNavigatorButton::slotUrlsDropped(QAction *action, QDropEvent *event)
0433 {
0434     const int result = action->data().toInt();
0435     QUrl url(m_url);
0436     url.setPath(Utils::concatPaths(url.path(), m_subDirs.at(result).name));
0437     Q_EMIT urlsDroppedOnNavButton(url, event);
0438 }
0439 
0440 void KUrlNavigatorButton::slotMenuActionClicked(QAction *action, Qt::MouseButton button)
0441 {
0442     const int result = action->data().toInt();
0443     QUrl url(m_url);
0444     url.setPath(Utils::concatPaths(url.path(), m_subDirs.at(result).name));
0445     Q_EMIT navigatorButtonActivated(url, button, Qt::NoModifier);
0446 }
0447 
0448 void KUrlNavigatorButton::statFinished(KJob *job)
0449 {
0450     if (m_pendingTextChange) {
0451         m_pendingTextChange = false;
0452 
0453         const KIO::UDSEntry entry = static_cast<KIO::StatJob *>(job)->statResult();
0454         QString name = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
0455         if (name.isEmpty()) {
0456             name = m_url.fileName();
0457         }
0458         setText(name);
0459 
0460         Q_EMIT finishedTextResolving();
0461     }
0462 }
0463 
0464 /**
0465  * Helper struct for sorting folder names
0466  */
0467 struct FolderNameNaturalLessThan {
0468     FolderNameNaturalLessThan(bool sortHiddenLast)
0469         : m_sortHiddenLast(sortHiddenLast)
0470     {
0471         m_collator.setCaseSensitivity(Qt::CaseInsensitive);
0472         m_collator.setNumericMode(true);
0473     }
0474 
0475     bool operator()(const KUrlNavigatorButton::SubDirInfo &a, const KUrlNavigatorButton::SubDirInfo &b)
0476     {
0477         if (m_sortHiddenLast) {
0478             const bool isHiddenA = a.name.startsWith(QLatin1Char('.'));
0479             const bool isHiddenB = b.name.startsWith(QLatin1Char('.'));
0480             if (isHiddenA && !isHiddenB) {
0481                 return false;
0482             }
0483             if (!isHiddenA && isHiddenB) {
0484                 return true;
0485             }
0486         }
0487         return m_collator.compare(a.name, b.name) < 0;
0488     }
0489 
0490 private:
0491     QCollator m_collator;
0492     bool m_sortHiddenLast;
0493 };
0494 
0495 void KUrlNavigatorButton::openSubDirsMenu(KJob *job)
0496 {
0497     Q_ASSERT(job == m_subDirsJob);
0498     m_subDirsJob = nullptr;
0499 
0500     if (job->error() || m_subDirs.empty()) {
0501         // clear listing
0502         return;
0503     }
0504 
0505     const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(parent());
0506     Q_ASSERT(urlNavigator);
0507     FolderNameNaturalLessThan less(urlNavigator->showHiddenFolders() && urlNavigator->sortHiddenFoldersLast());
0508     std::sort(m_subDirs.begin(), m_subDirs.end(), less);
0509     setDisplayHintEnabled(PopupActiveHint, true);
0510     update(); // ensure the button is drawn highlighted
0511 
0512     if (m_subDirsMenu != nullptr) {
0513         m_subDirsMenu->close();
0514         m_subDirsMenu->deleteLater();
0515         m_subDirsMenu = nullptr;
0516     }
0517 
0518     m_subDirsMenu = new KUrlNavigatorMenu(this);
0519     initMenu(m_subDirsMenu, 0);
0520 
0521     const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
0522     const int popupX = leftToRight ? width() - arrowWidth() - BorderWidth : 0;
0523     const QPoint popupPos = parentWidget()->mapToGlobal(geometry().bottomLeft() + QPoint(popupX, 0));
0524 
0525     QPointer<QObject> guard(this);
0526 
0527     m_subDirsMenu->exec(popupPos);
0528 
0529     // If 'this' has been deleted in the menu's nested event loop, we have to return
0530     // immediately because any access to a member variable might cause a crash.
0531     if (!guard) {
0532         return;
0533     }
0534 
0535     m_subDirs.clear();
0536     delete m_subDirsMenu;
0537     m_subDirsMenu = nullptr;
0538 
0539     setDisplayHintEnabled(PopupActiveHint, false);
0540 }
0541 
0542 void KUrlNavigatorButton::replaceButton(KJob *job)
0543 {
0544     Q_ASSERT(job == m_subDirsJob);
0545     m_subDirsJob = nullptr;
0546     m_replaceButton = false;
0547 
0548     if (job->error() || m_subDirs.empty()) {
0549         return;
0550     }
0551 
0552     const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(parent());
0553     Q_ASSERT(urlNavigator);
0554     FolderNameNaturalLessThan less(urlNavigator->showHiddenFolders() && urlNavigator->sortHiddenFoldersLast());
0555     std::sort(m_subDirs.begin(), m_subDirs.end(), less);
0556 
0557     // Get index of the directory that is shown currently in the button
0558     const QString currentDir = m_url.fileName();
0559     int currentIndex = 0;
0560     const int subDirsCount = m_subDirs.size();
0561     while (currentIndex < subDirsCount) {
0562         if (m_subDirs[currentIndex].name == currentDir) {
0563             break;
0564         }
0565         ++currentIndex;
0566     }
0567 
0568     // Adjust the index by respecting the wheel steps and
0569     // trigger a replacing of the button content
0570     int targetIndex = currentIndex - m_wheelSteps;
0571     if (targetIndex < 0) {
0572         targetIndex = 0;
0573     } else if (targetIndex >= subDirsCount) {
0574         targetIndex = subDirsCount - 1;
0575     }
0576 
0577     QUrl url(KIO::upUrl(m_url));
0578     url.setPath(Utils::concatPaths(url.path(), m_subDirs[targetIndex].name));
0579     Q_EMIT navigatorButtonActivated(url, Qt::LeftButton, Qt::NoModifier);
0580 
0581     m_subDirs.clear();
0582 }
0583 
0584 void KUrlNavigatorButton::cancelSubDirsRequest()
0585 {
0586     m_openSubDirsTimer->stop();
0587     if (m_subDirsJob != nullptr) {
0588         m_subDirsJob->kill();
0589         m_subDirsJob = nullptr;
0590     }
0591 }
0592 
0593 QString KUrlNavigatorButton::plainText() const
0594 {
0595     // Replace all "&&" by '&' and remove all single
0596     // '&' characters
0597     const QString source = text();
0598     const int sourceLength = source.length();
0599 
0600     QString dest;
0601     dest.resize(sourceLength);
0602 
0603     int sourceIndex = 0;
0604     int destIndex = 0;
0605     while (sourceIndex < sourceLength) {
0606         if (source.at(sourceIndex) == QLatin1Char('&')) {
0607             ++sourceIndex;
0608             if (sourceIndex >= sourceLength) {
0609                 break;
0610             }
0611         }
0612         dest[destIndex] = source.at(sourceIndex);
0613         ++sourceIndex;
0614         ++destIndex;
0615     }
0616 
0617     dest.resize(destIndex);
0618 
0619     return dest;
0620 }
0621 
0622 int KUrlNavigatorButton::arrowWidth() const
0623 {
0624     // if there isn't arrow then return 0
0625     int width = 0;
0626     if (!m_subDir.isEmpty()) {
0627         width = height() / 2;
0628         if (width < 4) {
0629             width = 4;
0630         }
0631     }
0632 
0633     return width;
0634 }
0635 
0636 bool KUrlNavigatorButton::isAboveArrow(int x) const
0637 {
0638     const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
0639     return leftToRight ? (x >= width() - arrowWidth()) : (x < arrowWidth());
0640 }
0641 
0642 bool KUrlNavigatorButton::isTextClipped() const
0643 {
0644     int availableWidth = width() - 2 * BorderWidth;
0645     if (!m_subDir.isEmpty()) {
0646         availableWidth -= arrowWidth() - BorderWidth;
0647     }
0648 
0649     QFont adjustedFont(font());
0650     adjustedFont.setBold(m_subDir.isEmpty());
0651     return QFontMetrics(adjustedFont).size(Qt::TextSingleLine, plainText()).width() >= availableWidth;
0652 }
0653 
0654 void KUrlNavigatorButton::updateMinimumWidth()
0655 {
0656     const int oldMinWidth = minimumWidth();
0657 
0658     int minWidth = sizeHint().width();
0659     if (minWidth < 40) {
0660         minWidth = 40;
0661     } else if (minWidth > 150) {
0662         // don't let an overlong path name waste all the URL navigator space
0663         minWidth = 150;
0664     }
0665     if (oldMinWidth != minWidth) {
0666         setMinimumWidth(minWidth);
0667     }
0668 }
0669 
0670 void KUrlNavigatorButton::initMenu(KUrlNavigatorMenu *menu, int startIndex)
0671 {
0672     connect(menu, &KUrlNavigatorMenu::mouseButtonClicked, this, &KUrlNavigatorButton::slotMenuActionClicked);
0673     connect(menu, &KUrlNavigatorMenu::urlsDropped, this, &KUrlNavigatorButton::slotUrlsDropped);
0674 
0675     // So that triggering a menu item with the keyboard works
0676     connect(menu, &QMenu::triggered, this, [this](QAction *act) {
0677         slotMenuActionClicked(act, Qt::LeftButton);
0678     });
0679 
0680     menu->setLayoutDirection(Qt::LeftToRight);
0681 
0682     const int maxIndex = startIndex + 30; // Don't show more than 30 items in a menu
0683     const int subDirsSize = m_subDirs.size();
0684     const int lastIndex = std::min(subDirsSize - 1, maxIndex);
0685     for (int i = startIndex; i <= lastIndex; ++i) {
0686         const auto &[subDirName, subDirDisplayName] = m_subDirs[i];
0687         QString text = KStringHandler::csqueeze(subDirDisplayName, 60);
0688         text.replace(QLatin1Char('&'), QLatin1String("&&"));
0689         QAction *action = new QAction(text, this);
0690         if (m_subDir == subDirName) {
0691             QFont font(action->font());
0692             font.setBold(true);
0693             action->setFont(font);
0694         }
0695         action->setData(i);
0696         menu->addAction(action);
0697     }
0698     if (subDirsSize > maxIndex) {
0699         // If too much items are shown, move them into a sub menu
0700         menu->addSeparator();
0701         KUrlNavigatorMenu *subDirsMenu = new KUrlNavigatorMenu(menu);
0702         subDirsMenu->setTitle(i18nc("@action:inmenu", "More"));
0703         initMenu(subDirsMenu, maxIndex);
0704         menu->addMenu(subDirsMenu);
0705     }
0706 }
0707 
0708 } // namespace KDEPrivate
0709 
0710 #include "moc_kurlnavigatorbutton_p.cpp"