[PATCH] KUrlNavigatorButton: Use arrow as separators
authorAkseli Lahtinen <akselmo@akselmo.dev>
Fri, 4 Apr 2025 20:00:59 +0000 (20:00 +0000)
committerPatrick Franz <deltaone@debian.org>
Fri, 25 Apr 2025 20:41:52 +0000 (22:41 +0200)
Due to the feedback of regular separator not being good enough for
separating folders, use an arrow separator instead.

This also refactors bunch of code around the button, so it should
be easier to tweak over time.

Remove icons due to the added clutter. They would work with full height chevrons but those are not technically feasible at this time.

BUG: 501587
BUG: 501575
BUG: 501582
BUG: 501589
BUG: 501706
BUG: 501803
FIXED-IN: 6.14

Gbp-Pq: Name upstream_KUrlNavigatorButton.patch

src/filewidgets/kurlnavigator.cpp
src/filewidgets/kurlnavigator.h
src/filewidgets/kurlnavigatorbutton.cpp
src/filewidgets/kurlnavigatorbutton_p.h
src/filewidgets/kurlnavigatorbuttonbase.cpp
src/filewidgets/kurlnavigatortogglebutton.cpp

index 737d039b118798c53aae5abca12510a81c327479..9c99b1a914ac2cd02e4b6ca7d637362495e513c9 100644 (file)
@@ -199,8 +199,9 @@ public:
     bool m_active = true;
     bool m_showPlacesSelector = false;
     bool m_showFullPath = false;
+    bool m_backgroundEnabled = true;
 
-    int m_padding = 4;
+    int m_padding = 5;
 
     struct {
         bool showHidden = false;
@@ -215,6 +216,8 @@ KUrlNavigatorPrivate::KUrlNavigatorPrivate(const QUrl &url, KUrlNavigator *qq, K
 {
     m_layout->setSpacing(0);
     m_layout->setContentsMargins(0, 0, 0, 0);
+    QStyleOption option;
+    option.initFrom(q);
 
     q->connect(m_coreUrlNavigator, &KCoreUrlNavigator::currentLocationUrlChanged, q, [this]() {
         Q_EMIT q->urlChanged(m_coreUrlNavigator->currentLocationUrl());
@@ -306,6 +309,7 @@ KUrlNavigatorPrivate::KUrlNavigatorPrivate(const QUrl &url, KUrlNavigator *qq, K
     m_layout->addWidget(m_dropDownButton);
     m_layout->addWidget(m_pathBox, 1);
     m_layout->addWidget(m_badgeWidgetContainer);
+    m_layout->addSpacing(q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing, &option, q));
     m_layout->addWidget(m_toggleEditableMode);
 
     q->setContextMenuPolicy(Qt::CustomContextMenu);
@@ -316,7 +320,8 @@ KUrlNavigatorPrivate::KUrlNavigatorPrivate(const QUrl &url, KUrlNavigator *qq, K
     // Make sure pathBox does not portrude outside of the above frameLineEdit background
     const int paddingLeft = q->style()->pixelMetric(QStyle::PM_LayoutLeftMargin);
     const int paddingRight = q->style()->pixelMetric(QStyle::PM_LayoutRightMargin);
-    q->setContentsMargins(paddingLeft, 0, paddingRight, 0);
+    q->rect().adjust(0, -1, 0, 1);
+    q->setContentsMargins(paddingLeft, 1, paddingRight, 1);
     m_pathBox->setContentsMargins(paddingLeft, 0, paddingRight, 0);
 }
 
@@ -550,11 +555,14 @@ void KUrlNavigatorPrivate::switchView()
     m_toggleEditableMode->setChecked(m_editable);
     updateContent();
     if (q->isUrlEditable()) {
+        m_pathBox->setFixedHeight(m_badgeWidgetContainer->height());
         m_pathBox->setFocus();
     }
 
     q->requestActivation();
     Q_EMIT q->editableStateChanged(m_editable);
+    // Make sure the colors are updated
+    q->update();
 }
 
 void KUrlNavigatorPrivate::dropUrls(const QUrl &destination, QDropEvent *event, KUrlNavigatorButton *dropButton)
@@ -826,7 +834,7 @@ void KUrlNavigatorPrivate::updateButtonVisibility()
 
     // Subtract all widgets from the available width, that must be shown anyway
     // Make sure to take the padding into account
-    int availableWidth = q->width() - (m_padding * 2) - m_toggleEditableMode->minimumWidth();
+    int availableWidth = q->width() - m_toggleEditableMode->minimumWidth();
 
     availableWidth -= m_badgeWidgetContainer->width();
 
@@ -838,18 +846,10 @@ void KUrlNavigatorPrivate::updateButtonVisibility()
         availableWidth -= m_schemes->width();
     }
 
-    // Check whether buttons must be hidden at all...
-    int requiredButtonWidth = 0;
-    for (const auto *button : std::as_const(m_navButtons)) {
-        requiredButtonWidth += button->minimumWidth();
-    }
+    availableWidth -= m_dropDownButton->width();
 
-    if (requiredButtonWidth > availableWidth) {
-        // At least one button must be hidden. This implies that the
-        // drop-down button must get visible, which again decreases the
-        // available width.
-        availableWidth -= m_dropDownButton->width();
-    }
+    // Count the paddings of previous button and current button
+    availableWidth -= m_padding * 4;
 
     // Hide buttons...
     bool isLastButton = true;
@@ -904,7 +904,7 @@ void KUrlNavigatorPrivate::updateButtonVisibility()
 void KUrlNavigatorPrivate::updateTabOrder()
 {
     QMultiMap<int, QWidget *> visibleChildrenSortedByX;
-    const auto childWidgets = q->findChildren<QWidget *>();
+    const auto childWidgets = q->findChildren<KUrlNavigatorButtonBase *>();
     for (auto childWidget : childWidgets) {
         if (childWidget->isVisible()) {
             if (q->layoutDirection() == Qt::LeftToRight) {
@@ -1408,22 +1408,40 @@ QWidget *KUrlNavigator::badgeWidget() const
     }
 }
 
+void KUrlNavigator::setBackgroundEnabled(bool enabled)
+{
+    d->m_backgroundEnabled = enabled;
+}
+
+bool KUrlNavigator::isBackgroundEnabled() const
+{
+    return d->m_backgroundEnabled;
+}
+
 void KUrlNavigator::paintEvent(QPaintEvent *event)
 {
     Q_UNUSED(event);
     QPainter painter(this);
     QStyleOptionFrame option;
     option.initFrom(this);
-    option.state = QStyle::State_Sunken;
+    option.state = QStyle::State_None;
 
     if (hasFocus()) {
         option.palette.setColor(QPalette::Window, palette().color(QPalette::Highlight));
     }
 
-    // Adjust the rectangle due to how QRect coordinates work
-    option.rect = option.rect.adjusted(1, 0, -1, 0);
-    // Draw the background
-    style()->drawPrimitive(QStyle::PE_FrameLineEdit, &option, &painter, this);
+    if (d->m_backgroundEnabled) {
+        // Draw primitive always, but change color if not editable
+        if (!d->m_editable) {
+            option.palette.setColor(QPalette::Base, palette().alternateBase().color());
+        }
+        style()->drawPrimitive(QStyle::PE_FrameLineEdit, &option, &painter, this);
+    } else {
+        // Draw primitive only for the input field
+        if (d->m_editable) {
+            style()->drawPrimitive(QStyle::PE_FrameLineEdit, &option, &painter, this);
+        }
+    }
 }
 
 #include "moc_kurlnavigator.cpp"
index 7fe7ff77dd500ea4159a7e9cf722a09f5543eae2..635cac80314fd65896c8ee11d6d2de83512c77a9 100644 (file)
@@ -332,6 +332,21 @@ public:
      */
     QWidget *badgeWidget() const;
 
+    /**
+     * Sets the background painting enabled or disabled for the buttons layout.
+     * In frameless styles, its recommended to set the background to disabled.
+     * Does not affect the input mode.
+     * @since 6.14
+     */
+    void setBackgroundEnabled(bool enabled);
+
+    /**
+     * Returns true if the background of the buttons layout is being painted.
+     * Does not represent the input mode background.
+     * @since 6.14
+     */
+    bool isBackgroundEnabled() const;
+
 public Q_SLOTS:
     /**
      * Sets the location to \a url. The old URL is added to the history.
index ee104d305b5c0bf8dd40ba3ffc8dfc9ecd9a3299..c6bc91a688fdad41839f22f249042890bd69ee1a 100644 (file)
@@ -29,7 +29,8 @@ QPointer<KUrlNavigatorMenu> KUrlNavigatorButton::m_subDirsMenu;
 
 KUrlNavigatorButton::KUrlNavigatorButton(const QUrl &url, KUrlNavigator *parent)
     : KUrlNavigatorButtonBase(parent)
-    , m_hoverOverIcon(false)
+    , m_hoverOverArrow(false)
+    , m_hoverOverButton(false)
     , m_pendingTextChange(false)
     , m_replaceButton(false)
     , m_showMnemonic(false)
@@ -39,7 +40,7 @@ KUrlNavigatorButton::KUrlNavigatorButton(const QUrl &url, KUrlNavigator *parent)
     , m_subDir()
     , m_openSubDirsTimer(nullptr)
     , m_subDirsJob(nullptr)
-    , m_padding(10)
+    , m_padding(5)
 {
     setAcceptDrops(true);
     setUrl(url);
@@ -131,7 +132,7 @@ QSize KUrlNavigatorButton::sizeHint() const
     // preferred width is textWidth, iconWidth and padding combined
     // add extra padding in end to make sure the space between divider and button is consistent
     // the first padding is used between icon and text, second in the end of text
-    const int width = QFontMetrics(adjustedFont).size(Qt::TextSingleLine, plainText()).width() + iconWidth() + (m_padding * 2);
+    const int width = m_padding + textWidth() + arrowWidth() + m_padding;
     return QSize(width, KUrlNavigatorButtonBase::sizeHint().height());
 }
 
@@ -172,6 +173,8 @@ void KUrlNavigatorButton::paintEvent(QPaintEvent *event)
     painter.setFont(adjustedFont);
 
     int buttonWidth = width();
+    int arrowWidth = KUrlNavigatorButton::arrowWidth();
+
     int preferredWidth = sizeHint().width();
     if (preferredWidth < minimumWidth()) {
         preferredWidth = minimumWidth();
@@ -180,44 +183,24 @@ void KUrlNavigatorButton::paintEvent(QPaintEvent *event)
         buttonWidth = preferredWidth;
     }
     const int buttonHeight = height();
-
     const QColor fgColor = foregroundColor();
-    drawHoverBackground(&painter);
-
-    int textLeft = 0;
-    int textWidth = buttonWidth;
-
     const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
 
-    // draw folder icon
-    const int iconW = iconWidth();
-    const int iconX = !leftToRight ? (buttonWidth - iconW) - m_padding / 2 : m_padding / 2;
-    const int iconY = (buttonHeight - iconW) / 2;
-
-    QStyleOption option;
-    option.initFrom(this);
-    option.rect = QRect(iconX, iconY, iconW, iconW);
-    option.palette = palette();
-    option.palette.setColor(QPalette::Text, fgColor);
-    option.palette.setColor(QPalette::WindowText, fgColor);
-    option.palette.setColor(QPalette::ButtonText, fgColor);
-
-    if (m_hoverOverIcon) {
-        option.rect = QRect(iconX - m_padding / 2, 0, iconW + m_padding, buttonHeight).marginsRemoved(QMargins(0, 2, 0, 2));
-        style()->drawPrimitive(QStyle::PE_PanelButtonTool, &option, &painter, this);
-    }
-
-    const int widthModifier = iconW + m_padding / 2;
-    auto pixmap = icon().pixmap(iconSize(), devicePixelRatioF());
-    style()->drawItemPixmap(&painter, QRect(iconX, iconY, iconW, iconW), Qt::AlignCenter, pixmap);
+    // Prepare sizes for icon
+    QRect textRect;
+    const int textRectWidth = buttonWidth - arrowWidth - m_padding;
     if (leftToRight) {
-        textLeft += widthModifier;
+        textRect = QRect(m_padding, 0, textRectWidth, buttonHeight);
+    } else {
+        // If no separator is drawn, we can start writing text from 0
+        textRect = QRect(m_drawSeparator ? arrowWidth : 0, 0, textRectWidth, buttonHeight);
     }
-    textWidth -= widthModifier;
 
+    drawHoverBackground(&painter);
+
+    // Draw gradient overlay if text is clipped
     painter.setPen(fgColor);
     const bool clipped = isTextClipped();
-    QRect textRect(textLeft, 0, textWidth, buttonHeight);
     if (clipped) {
         QColor bgColor = fgColor;
         bgColor.setAlpha(0);
@@ -237,14 +220,8 @@ void KUrlNavigatorButton::paintEvent(QPaintEvent *event)
         painter.setPen(pen);
     }
 
+    // Draw folder name
     int textFlags = Qt::AlignVCenter;
-
-    if (leftToRight) {
-        textRect.setLeft(textRect.left() + m_padding / 2);
-    } else {
-        textRect.setRight(textRect.right() - m_padding / 2);
-    }
-
     if (m_showMnemonic) {
         textFlags |= Qt::TextShowMnemonic;
         painter.drawText(textRect, textFlags, text());
@@ -252,18 +229,26 @@ void KUrlNavigatorButton::paintEvent(QPaintEvent *event)
         painter.drawText(textRect, textFlags, plainText());
     }
 
+    // Draw separator arrow
     if (m_drawSeparator) {
         QStyleOption option;
         option.initFrom(this);
+        option.palette = palette();
+        option.palette.setColor(QPalette::Text, fgColor);
+        option.palette.setColor(QPalette::WindowText, fgColor);
+        option.palette.setColor(QPalette::ButtonText, fgColor);
+
         if (leftToRight) {
-            option.rect = QRect(rect().topRight(), rect().bottomRight());
+            option.rect = QRect(textRect.right(), 0, arrowWidth, buttonHeight);
         } else {
-            option.rect = QRect(rect().topLeft(), rect().bottomLeft());
+            // Separator is the first item in RtL mode
+            option.rect = QRect(0, 0, arrowWidth, buttonHeight);
         }
 
-        // Draw CE_Splitter instead of PE_IndicatorToolBarSeparator, since the latter
-        // will be turned off if application style has separators turned off
-        style()->drawControl(QStyle::CE_Splitter, &option, &painter, this);
+        if (!m_hoverOverArrow) {
+            option.state = QStyle::State_None;
+        }
+        style()->drawPrimitive(leftToRight ? QStyle::PE_IndicatorArrowRight : QStyle::PE_IndicatorArrowLeft, &option, &painter, this);
     }
 }
 
@@ -276,6 +261,10 @@ void KUrlNavigatorButton::enterEvent(QEnterEvent *event)
     if (isTextClipped()) {
         setToolTip(plainText());
     }
+    if (!m_hoverOverButton) {
+        m_hoverOverButton = true;
+        update();
+    }
 }
 
 void KUrlNavigatorButton::leaveEvent(QEvent *event)
@@ -283,8 +272,12 @@ void KUrlNavigatorButton::leaveEvent(QEvent *event)
     KUrlNavigatorButtonBase::leaveEvent(event);
     setToolTip(QString());
 
-    if (m_hoverOverIcon) {
-        m_hoverOverIcon = false;
+    if (m_hoverOverArrow) {
+        m_hoverOverArrow = false;
+        update();
+    }
+    if (m_hoverOverButton) {
+        m_hoverOverButton = false;
         update();
     }
 }
@@ -330,8 +323,9 @@ void KUrlNavigatorButton::dragEnterEvent(QDragEnterEvent *event)
 void KUrlNavigatorButton::dragMoveEvent(QDragMoveEvent *event)
 {
     QRect rect = event->answerRect();
-    if (isAboveIcon(rect.center().x())) {
-        m_hoverOverIcon = true;
+
+    if (isAboveSeparator(rect.center().x())) {
+        m_hoverOverArrow = true;
         update();
 
         if (m_subDirsMenu == nullptr) {
@@ -351,7 +345,7 @@ void KUrlNavigatorButton::dragMoveEvent(QDragMoveEvent *event)
             m_subDirsMenu->deleteLater();
             m_subDirsMenu = nullptr;
         }
-        m_hoverOverIcon = false;
+        m_hoverOverArrow = false;
         update();
     }
 }
@@ -360,14 +354,14 @@ void KUrlNavigatorButton::dragLeaveEvent(QDragLeaveEvent *event)
 {
     KUrlNavigatorButtonBase::dragLeaveEvent(event);
 
-    m_hoverOverIcon = false;
+    m_hoverOverArrow = false;
     setDisplayHintEnabled(DraggedHint, false);
     update();
 }
 
 void KUrlNavigatorButton::mousePressEvent(QMouseEvent *event)
 {
-    if (isAboveIcon(qRound(event->position().x())) && (event->button() == Qt::LeftButton)) {
+    if (isAboveSeparator(qRound(event->position().x())) && (event->button() == Qt::LeftButton)) {
         // the mouse is pressed above the folder button
         startSubDirsJob();
     }
@@ -376,7 +370,7 @@ void KUrlNavigatorButton::mousePressEvent(QMouseEvent *event)
 
 void KUrlNavigatorButton::mouseReleaseEvent(QMouseEvent *event)
 {
-    if (!isAboveIcon(qRound(event->position().x())) || (event->button() != Qt::LeftButton)) {
+    if (!isAboveSeparator(qRound(event->position().x())) || (event->button() != Qt::LeftButton)) {
         // the mouse has been released above the text area and not
         // above the folder button
         Q_EMIT navigatorButtonActivated(m_url, event->button(), event->modifiers());
@@ -389,9 +383,9 @@ void KUrlNavigatorButton::mouseMoveEvent(QMouseEvent *event)
 {
     KUrlNavigatorButtonBase::mouseMoveEvent(event);
 
-    const bool hoverOverIcon = isAboveIcon(qRound(event->position().x()));
-    if (hoverOverIcon != m_hoverOverIcon) {
-        m_hoverOverIcon = hoverOverIcon;
+    const bool hoverOverIcon = isAboveSeparator(qRound(event->position().x()));
+    if (hoverOverIcon != m_hoverOverArrow) {
+        m_hoverOverArrow = hoverOverIcon;
         update();
     }
 }
@@ -551,7 +545,7 @@ void KUrlNavigatorButton::openSubDirsMenu(KJob *job)
     initMenu(m_subDirsMenu, 0);
 
     const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
-    const int popupX = !leftToRight ? width() - iconWidth() : 0;
+    const int popupX = leftToRight ? width() - arrowWidth() : 0;
     const QPoint popupPos = parentWidget()->mapToGlobal(geometry().bottomLeft() + QPoint(popupX, 0));
 
     QPointer<QObject> guard(this);
@@ -651,28 +645,39 @@ QString KUrlNavigatorButton::plainText() const
     return dest;
 }
 
-int KUrlNavigatorButton::iconWidth() const
+int KUrlNavigatorButton::arrowWidth() const
+{
+    // if there isn't arrow then return 0
+    int width = 0;
+    if (!m_subDir.isEmpty()) {
+        width = height() / 2;
+        if (width < 4) {
+            width = 4;
+        }
+    }
+
+    return width;
+}
+
+int KUrlNavigatorButton::textWidth() const
 {
-    return iconSize().width() * devicePixelRatioF();
+    QFont adjustedFont(font());
+    adjustedFont.setBold(m_subDir.isEmpty());
+    return QFontMetrics(adjustedFont).size(Qt::TextSingleLine, plainText()).width();
 }
 
-bool KUrlNavigatorButton::isAboveIcon(int x) const
+bool KUrlNavigatorButton::isAboveSeparator(int x) const
 {
     const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
-    return !leftToRight ? (x >= width() - iconWidth() - m_padding) : (x < iconWidth() + m_padding);
+    return leftToRight ? (x >= width() - arrowWidth()) : (x < arrowWidth() + m_padding);
 }
 
 bool KUrlNavigatorButton::isTextClipped() const
 {
     // Ignore padding when resizing, so text doesnt go under it
-    int availableWidth = width() - m_padding;
-    if (!m_subDir.isEmpty()) {
-        availableWidth -= iconWidth();
-    }
+    int availableWidth = width() - arrowWidth() - m_padding;
 
-    QFont adjustedFont(font());
-    adjustedFont.setBold(m_subDir.isEmpty());
-    return QFontMetrics(adjustedFont).size(Qt::TextSingleLine, plainText()).width() >= availableWidth;
+    return textWidth() >= availableWidth;
 }
 
 void KUrlNavigatorButton::updateMinimumWidth()
@@ -680,8 +685,8 @@ void KUrlNavigatorButton::updateMinimumWidth()
     const int oldMinWidth = minimumWidth();
 
     int minWidth = sizeHint().width();
-    if (minWidth < 40) {
-        minWidth = 40;
+    if (minWidth < 10) {
+        minWidth = 10;
     } else if (minWidth > 150) {
         // don't let an overlong path name waste all the URL navigator space
         minWidth = 150;
index f10c49d7f74d50ae5e1de784978a7b911f07edd7..00bdaf463112fb459afc3659d0894ed2528903de 100644 (file)
@@ -171,14 +171,16 @@ private:
      */
     QString plainText() const;
 
-    int iconWidth() const;
-    bool isAboveIcon(int x) const;
+    int arrowWidth() const;
+    int textWidth() const;
+    bool isAboveSeparator(int x) const;
     bool isTextClipped() const;
     void updateMinimumWidth();
     void initMenu(KUrlNavigatorMenu *menu, int startIndex);
 
 private:
-    bool m_hoverOverIcon;
+    bool m_hoverOverArrow;
+    bool m_hoverOverButton;
     bool m_pendingTextChange;
     bool m_replaceButton;
     bool m_showMnemonic;
index 461d1d9b96576e2d28fdf349d6378721d84b93dd..fe1f0c302c6254864175a50b4424ed68f9e37ed7 100644 (file)
@@ -1,8 +1,8 @@
 /*
-    SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz@gmx.at>
-    SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
+SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz@gmx.at>
+SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
 
-    SPDX-License-Identifier: LGPL-2.0-or-later
+SPDX-License-Identifier: LGPL-2.0-or-later
 */
 
 #include "kurlnavigatorbuttonbase_p.h"
@@ -90,18 +90,14 @@ void KUrlNavigatorButtonBase::drawHoverBackground(QPainter *painter)
 {
     const bool isHighlighted = isDisplayHintEnabled(EnteredHint) || isDisplayHintEnabled(DraggedHint) || isDisplayHintEnabled(PopupActiveHint);
 
-    QColor backgroundColor = isHighlighted ? palette().color(QPalette::Highlight) : Qt::transparent;
-    if (!m_active && isHighlighted) {
-        backgroundColor.setAlpha(128);
-    }
+    QStyleOptionButton buttonOption;
+    buttonOption.initFrom(this);
+    buttonOption.rect = QRect(0, 0, width(), height());
+    buttonOption.features = QStyleOptionButton::Flat;
 
-    if (backgroundColor != Qt::transparent) {
-        // TODO: the backgroundColor should be applied to the style
-        QStyleOptionViewItem option;
-        option.initFrom(this);
-        option.state = QStyle::State_Enabled | QStyle::State_MouseOver;
-        option.viewItemPosition = QStyleOptionViewItem::OnlyOne;
-        style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, this);
+    // Draw button graphic
+    if (isHighlighted) {
+        style()->drawPrimitive(QStyle::PE_PanelButtonCommand, &buttonOption, painter, this);
     }
 }
 
index 88e45cda32f5ad3049ed742833d89ff9cb822bb9..fa0db1fcef06f5c64e8734f5691fc1ddd346801b 100644 (file)
@@ -15,7 +15,7 @@
 
 namespace KDEPrivate
 {
-static constexpr int s_iconSize = KIconLoader::SizeSmallMedium;
+static constexpr int s_iconSize = KIconLoader::SizeSmall;
 
 KUrlNavigatorToggleButton::KUrlNavigatorToggleButton(KUrlNavigator *parent)
     : KUrlNavigatorButtonBase(parent)