]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Full row highlight implementation
authorTom Lin <tom91136@gmail.com>
Sun, 16 Jan 2022 14:01:32 +0000 (14:01 +0000)
committerFelix Ernst <fe.a.ernst@gmail.com>
Sun, 16 Jan 2022 14:01:32 +0000 (14:01 +0000)
This commit implements full-row selection and hover highlights for the
details view mode.

This commit also contains fixes for 444680, 444753, both uncovered
during this change.

BUG: 181438
BUG: 444680
BUG: 444753
FIXED-IN: 22.04

16 files changed:
src/kitemviews/kitemlistcontroller.cpp
src/kitemviews/kitemlistheader.cpp
src/kitemviews/kitemlistheader.h
src/kitemviews/kitemlistview.cpp
src/kitemviews/kitemlistview.h
src/kitemviews/kitemlistwidget.cpp
src/kitemviews/kitemlistwidget.h
src/kitemviews/kstandarditemlistview.cpp
src/kitemviews/kstandarditemlistwidget.cpp
src/kitemviews/kstandarditemlistwidget.h
src/kitemviews/private/kitemlistheaderwidget.cpp
src/kitemviews/private/kitemlistheaderwidget.h
src/settings/dolphin_detailsmodesettings.kcfg
src/views/dolphinitemlistview.cpp
src/views/dolphinview.cpp
src/views/dolphinview.h

index abd61f8ae7671171236bac8fbd50fee5629ad4f6..80c28f25ca1d98647111da6ba8064390739670f5 100644 (file)
@@ -574,7 +574,7 @@ bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const
         return false;
     }
 
-    if (m_pressedIndex.has_value()) {
+    if (m_pressedIndex.has_value() && !m_view->rubberBand()->isActive()) {
         // Check whether a dragging should be started
         if (event->buttons() & Qt::LeftButton) {
             const QPointF pos = transform.map(event->pos());
@@ -828,27 +828,76 @@ bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const
         return false;
     }
 
-    KItemListWidget* oldHoveredWidget = hoveredWidget();
-    const QPointF pos = transform.map(event->pos());
-    KItemListWidget* newHoveredWidget = widgetForPos(pos);
+    // We identify the widget whose expansionArea had been hovered before this hoverMoveEvent() triggered.
+    // we can't use hoveredWidget() here (it handles the icon+text rect, not the expansion rect)
+    // like hoveredWidget(), we find the hovered widget for the expansion rect
+    const auto visibleItemListWidgets = m_view->visibleItemListWidgets();
+    const auto oldHoveredExpansionWidgetIterator = std::find_if(visibleItemListWidgets.begin(), visibleItemListWidgets.end(), [](auto &widget) {
+        return widget->expansionAreaHovered();
+    });
+    const auto oldHoveredExpansionWidget = oldHoveredExpansionWidgetIterator == visibleItemListWidgets.end() ?
+                                           std::nullopt : std::make_optional(*oldHoveredExpansionWidgetIterator);
 
-    if (oldHoveredWidget != newHoveredWidget) {
-        if (oldHoveredWidget) {
+    const auto unhoverOldHoveredWidget = [&]() {
+        if (auto oldHoveredWidget = hoveredWidget(); oldHoveredWidget) {
+            // handle the text+icon one
             oldHoveredWidget->setHovered(false);
             Q_EMIT itemUnhovered(oldHoveredWidget->index());
         }
+    };
 
-        if (newHoveredWidget) {
-            newHoveredWidget->setHovered(true);
-            const QPointF mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
-            newHoveredWidget->setHoverPosition(mappedPos);
-            Q_EMIT itemHovered(newHoveredWidget->index());
+    const auto unhoverOldExpansionWidget = [&]() {
+        if (oldHoveredExpansionWidget) {
+            // then the expansion toggle
+            (*oldHoveredExpansionWidget)->setExpansionAreaHovered(false);
         }
-    } else if (oldHoveredWidget) {
-        const QPointF mappedPos = oldHoveredWidget->mapFromItem(m_view, pos);
-        oldHoveredWidget->setHoverPosition(mappedPos);
-    }
+    };
+
+    const QPointF pos = transform.map(event->pos());
+    if (KItemListWidget *newHoveredWidget = widgetForPos(pos); newHoveredWidget) {
+        // something got hovered, work out which part and set hover for the appropriate widget
+        const auto mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
+        const bool isOnExpansionToggle = newHoveredWidget->expansionToggleRect().contains(mappedPos);
+
+        if (isOnExpansionToggle) {
+            // make sure we unhover the old one first if old!=new
+            if (oldHoveredExpansionWidget && *oldHoveredExpansionWidget != newHoveredWidget) {
+                (*oldHoveredExpansionWidget)->setExpansionAreaHovered(false);
+            }
+            // we also unhover any old icon+text hovers, in case the mouse movement from icon+text to expansion toggle is too fast (i.e. newHoveredWidget is never null between the transition)
+            unhoverOldHoveredWidget();
+
+
+            newHoveredWidget->setExpansionAreaHovered(true);
+        } else {
+            // make sure we unhover the old one first if old!=new
+            if (auto oldHoveredWidget = hoveredWidget(); oldHoveredWidget && oldHoveredWidget != newHoveredWidget) {
+                oldHoveredWidget->setHovered(false);
+                Q_EMIT itemUnhovered(oldHoveredWidget->index());
+            }
+            // we also unhover any old expansion toggle hovers, in case the mouse movement from expansion toggle to icon+text is too fast (i.e. newHoveredWidget is never null between the transition)
+            unhoverOldExpansionWidget();
+
+            const bool isOverIconAndText = newHoveredWidget->iconRect().contains(mappedPos) || newHoveredWidget->textRect().contains(mappedPos);
+            const bool hasMultipleSelection = m_selectionManager->selectedItems().count() > 1;
 
+            if (hasMultipleSelection && !isOverIconAndText) {
+                // In case we have multiple selections, clicking on any row will deselect the selection.
+                // So, as a visual cue for signalling that clicking anywhere won't select, but clear current highlights,
+                // we disable hover of the *row*(i.e. blank space to the right of the icon+text)
+
+                // (no-op in this branch for masked hover)
+            } else {
+                newHoveredWidget->setHovered(true);
+                newHoveredWidget->setHoverPosition(mappedPos);
+                Q_EMIT itemHovered(newHoveredWidget->index());
+            }
+        }
+    } else {
+        // unhover any currently hovered expansion and text+icon widgets
+        unhoverOldHoveredWidget();
+        unhoverOldExpansionWidget();
+    }
     return false;
 }
 
@@ -1303,10 +1352,7 @@ KItemListWidget* KItemListController::widgetForPos(const QPointF& pos) const
     const auto widgets = m_view->visibleItemListWidgets();
     for (KItemListWidget* widget : widgets) {
         const QPointF mappedPos = widget->mapFromItem(m_view, pos);
-
-        const bool hovered = widget->contains(mappedPos) &&
-                             !widget->expansionToggleRect().contains(mappedPos);
-        if (hovered) {
+        if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) {
             return widget;
         }
     }
@@ -1447,6 +1493,7 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
 
     const bool shiftPressed = modifiers & Qt::ShiftModifier;
     const bool controlPressed = modifiers & Qt::ControlModifier;
+    const bool rightClick = buttons & Qt::RightButton;
 
     // The previous selection is cleared if either
     // 1. The selection mode is SingleSelection, or
@@ -1459,8 +1506,32 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
     const bool pressedItemAlreadySelected = m_pressedIndex.has_value() && m_selectionManager->isSelected(m_pressedIndex.value());
     const bool clearSelection = m_selectionBehavior == SingleSelection ||
                                 (!shiftOrControlPressed && !pressedItemAlreadySelected);
+
+
+    // When this method returns false, a rubberBand selection is created using KItemListController::startRubberBand via the caller.
     if (clearSelection) {
+        const int selectedItemsCount = m_selectionManager->selectedItems().count();
         m_selectionManager->clearSelection();
+        // clear and bail when we got an existing multi-selection
+        if (selectedItemsCount  > 1 && m_pressedIndex.has_value()) {
+            const auto row = m_view->m_visibleItems.value(m_pressedIndex.value());
+            const auto mappedPos = row->mapFromItem(m_view, pos);
+            if (pressedItemAlreadySelected || row->iconRect().contains(mappedPos) || row->textRect().contains(mappedPos)) {
+                // we are indeed inside the text/icon rect, keep m_pressedIndex what it is
+                // and short-circuit for single-click activation (it will then propagate to onRelease and activate the item)
+                // or we just keep going for double-click activation
+                if (m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) {
+                    return true; // event handled, don't create rubber band
+                }
+            } else {
+                // we're not inside the text/icon rect, as we've already cleared the selection
+                // we can just stop here and make sure handlers down the line (i.e. onRelease) don't activate
+                m_pressedIndex.reset();
+                // we don't stop event propagation and proceed to create a rubber band and let onRelease
+                // decide (based on m_pressedIndex) whether we're in a drag (drag => new rubber band, click => don't select the item)
+                return false;
+            }
+        }
     } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (buttons & Qt::LeftButton)) {
         // The user might want to start dragging multiple items, but if he clicks the item
         // in order to trigger it instead, the other selected items must be deselected.
@@ -1479,7 +1550,14 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
         m_selectionManager->endAnchoredSelection();
     }
 
-    if (buttons & Qt::RightButton) {
+    if (rightClick) {
+
+        // Do header hit check and short circuit before commencing any state changing effects
+        if (m_view->headerBoundaries().contains(pos)) {
+            Q_EMIT headerContextMenuRequested(screenPos);
+            return true;
+        }
+
         // Stop rubber band from persisting after right-clicks
         KItemListRubberBand* rubberBand = m_view->rubberBand();
         if (rubberBand->isActive()) {
@@ -1491,6 +1569,17 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
 
     if (m_pressedIndex.has_value()) {
         m_selectionManager->setCurrentItem(m_pressedIndex.value());
+        const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); // anything outside of row.contains() will be the empty region of the row rect
+        const bool hitTargetIsRowEmptyRegion = !row->contains(row->mapFromItem(m_view, pos));
+        // again, when this method returns false, a rubberBand selection is created as the event is not consumed;
+        // createRubberBand here tells us whether to return true or false.
+        bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty());
+
+        if (rightClick && hitTargetIsRowEmptyRegion) {
+            // we got a right click outside the text rect, default to action on the current url and not the pressed item
+            Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), screenPos);
+            return true;
+        }
 
         switch (m_selectionBehavior) {
         case NoSelection:
@@ -1504,6 +1593,7 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
             if (controlPressed && !shiftPressed) {
                 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
                 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
+                createRubberBand = false; // multi selection, don't propagate any further
             } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
                 // Select the pressed item and start a new anchored selection
                 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select);
@@ -1516,20 +1606,16 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
             break;
         }
 
-        if (buttons & Qt::RightButton) {
+        if (rightClick) {
             Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), screenPos);
         }
-
-        return true;
+        return !createRubberBand;
     }
 
-    if (buttons & Qt::RightButton) {
-        const QRectF headerBounds = m_view->headerBoundaries();
-        if (headerBounds.contains(pos)) {
-            Q_EMIT headerContextMenuRequested(screenPos);
-        } else {
-            Q_EMIT viewContextMenuRequested(screenPos);
-        }
+    if (rightClick) {
+        // header right click handling would have been done before this so just normal context
+        // menu here is fine
+        Q_EMIT viewContextMenuRequested(screenPos);
         return true;
     }
 
@@ -1554,16 +1640,35 @@ bool KItemListController::onRelease(const QPointF& pos, const Qt::KeyboardModifi
     const bool shiftOrControlPressed = modifiers & Qt::ShiftModifier ||
                                        controlPressed;
 
+    const std::optional<int> index = m_view->itemAt(pos);
+
     KItemListRubberBand* rubberBand = m_view->rubberBand();
+    bool rubberBandRelease = false;
     if (rubberBand->isActive()) {
         disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
         rubberBand->setActive(false);
         m_oldSelection.clear();
         m_view->setAutoScroll(false);
+        rubberBandRelease = true;
+        // We check for actual rubber band drag here: if delta between start and end is less than drag threshold,
+        // then we have a single click on one of the rows
+        if ((rubberBand->endPosition() - rubberBand->startPosition()).manhattanLength() < QApplication::startDragDistance()) {
+            rubberBandRelease = false; // since we're only selecting, unmark rubber band release flag
+            // m_pressedIndex will have no value if we came from a multi-selection clearing onPress
+            // in that case, we don't select anything
+            if (index.has_value() && m_pressedIndex.has_value()) {
+                if (controlPressed && m_selectionBehavior == MultiSelection) {
+                    m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
+                } else {
+                    m_selectionManager->setSelected(index.value());
+                }
+                if (!m_selectionManager->isAnchoredSelectionActive()) {
+                    m_selectionManager->beginAnchoredSelection(index.value());
+                }
+            }
+        }
     }
 
-    const std::optional<int> index = m_view->itemAt(pos);
-
     if (index.has_value() && index == m_pressedIndex) {
         // The release event is done above the same item as the press event
 
@@ -1587,11 +1692,13 @@ bool KItemListController::onRelease(const QPointF& pos, const Qt::KeyboardModifi
                 // The mouse click should only update the selection, not trigger the item, except when
                 // we are in single selection mode
                 emitItemActivated = false;
-            } else if (!(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)) {
-                if (touch) {
-                emitItemActivated = true;
+            } else {
+                const bool singleClickActivation = m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced;
+                if (!singleClickActivation) {
+                    emitItemActivated = touch;
                 } else {
-                emitItemActivated = false;
+                    // activate on single click only if we didn't come from a rubber band release
+                    emitItemActivated = !rubberBandRelease;
                 }
             }
             if (emitItemActivated) {
index dedeb57e3a9f2d2c2eb6fa36e3e0680f6a6604a5..22e70603b0516ebeced4dbdc7cda40105a108308 100644 (file)
@@ -61,6 +61,20 @@ qreal KItemListHeader::preferredColumnWidth(const QByteArray& role) const
     return m_headerWidget->preferredColumnWidth(role);
 }
 
+void KItemListHeader::setLeadingPadding(qreal width){
+    if (m_headerWidget->leadingPadding() != width) {
+        m_headerWidget->setLeadingPadding(width);
+        if (m_headerWidget->automaticColumnResizing()) {
+            m_view->applyAutomaticColumnWidths();
+        }
+        m_view->doLayout(KItemListView::NoAnimation);
+    }
+}
+
+qreal KItemListHeader::leadingPadding() const{
+    return m_headerWidget->leadingPadding();
+}
+
 KItemListHeader::KItemListHeader(KItemListView* listView) :
     QObject(listView),
     m_view(listView)
@@ -72,5 +86,7 @@ KItemListHeader::KItemListHeader(KItemListView* listView) :
             this, &KItemListHeader::columnWidthChanged);
     connect(m_headerWidget, &KItemListHeaderWidget::columnWidthChangeFinished,
             this, &KItemListHeader::columnWidthChangeFinished);
+    connect(m_headerWidget, &KItemListHeaderWidget::leadingPaddingChanged,
+            this, &KItemListHeader::leadingPaddingChanged);
 }
 
index 6875a0f5e75cd804a51a72c2d27bf9b8a35f696c..e2653e41eab68d041a2e616eb3b3bf41f5a336b0 100644 (file)
@@ -58,7 +58,16 @@ public:
      */
     qreal preferredColumnWidth(const QByteArray& role) const;
 
+    /**
+     * Sets the width of the column *before* the first column.
+     * This is intended to facilitate an empty region for deselection in the main viewport.
+     */
+    void setLeadingPadding(qreal width);
+    qreal leadingPadding() const;
+
 Q_SIGNALS:
+    void leadingPaddingChanged(qreal width);
+
     /**
      * Is emitted if the width of a column has been adjusted by the user with the mouse
      * (no signal is emitted if KItemListHeader::setColumnWidth() is invoked).
index a4074c6cb4cff45ca4c7542e76903f3e4958778a..86583db1ec958a25ed5b3fa7e1ea3febec725f76 100644 (file)
@@ -397,7 +397,7 @@ std::optional<int> KItemListView::itemAt(const QPointF& pos) const
 
         const KItemListWidget* widget = it.value();
         const QPointF mappedPos = widget->mapFromItem(this, pos);
-        if (widget->contains(mappedPos)) {
+        if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) {
             return it.key();
         }
     }
@@ -477,6 +477,32 @@ bool KItemListView::supportsItemExpanding() const
     return m_supportsItemExpanding;
 }
 
+void KItemListView::setHighlightEntireRow(bool highlightEntireRow)
+{
+    if (m_highlightEntireRow != highlightEntireRow) {
+        m_highlightEntireRow = highlightEntireRow;
+        onHighlightEntireRowChanged(highlightEntireRow);
+    }
+}
+
+bool KItemListView::highlightEntireRow() const
+{
+    return m_highlightEntireRow;
+}
+
+void KItemListView::setAlternateBackgrounds(bool alternate)
+{
+    if (m_alternateBackgrounds != alternate) {
+        m_alternateBackgrounds = alternate;
+        updateAlternateBackgrounds();
+    }
+}
+
+bool KItemListView::alternateBackgrounds() const
+{
+    return m_alternateBackgrounds;
+}
+
 QRectF KItemListView::itemRect(int index) const
 {
     return m_layouter->itemRect(index);
@@ -581,6 +607,8 @@ void KItemListView::setHeaderVisible(bool visible)
 
         connect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged,
                 this, &KItemListView::slotHeaderColumnWidthChanged);
+        connect(m_headerWidget, &KItemListHeaderWidget::leadingPaddingChanged,
+                this, &KItemListView::slotLeadingPaddingChanged);
         connect(m_headerWidget, &KItemListHeaderWidget::columnMoved,
                 this, &KItemListView::slotHeaderColumnMoved);
         connect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged,
@@ -593,6 +621,8 @@ void KItemListView::setHeaderVisible(bool visible)
     } else if (!visible && m_headerWidget->isVisible()) {
         disconnect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged,
                    this, &KItemListView::slotHeaderColumnWidthChanged);
+        disconnect(m_headerWidget, &KItemListHeaderWidget::leadingPaddingChanged,
+                this, &KItemListView::slotLeadingPaddingChanged);
         disconnect(m_headerWidget, &KItemListHeaderWidget::columnMoved,
                    this, &KItemListView::slotHeaderColumnMoved);
         disconnect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged,
@@ -753,7 +783,7 @@ void KItemListView::setItemSize(const QSizeF& size)
                                                 size,
                                                 m_layouter->itemMargin());
 
-    const bool alternateBackgroundsChanged = (m_visibleRoles.count() > 1) &&
+    const bool alternateBackgroundsChanged = m_alternateBackgrounds &&
                                              (( m_itemSize.isEmpty() && !size.isEmpty()) ||
                                               (!m_itemSize.isEmpty() && size.isEmpty()));
 
@@ -929,6 +959,11 @@ void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, co
     Q_UNUSED(previous)
 }
 
+void KItemListView::onHighlightEntireRowChanged(bool highlightEntireRow)
+{
+    Q_UNUSED(highlightEntireRow)
+}
+
 void KItemListView::onSupportsItemExpandingChanged(bool supportsExpanding)
 {
     Q_UNUSED(supportsExpanding)
@@ -1521,6 +1556,16 @@ void KItemListView::slotHeaderColumnWidthChanged(const QByteArray& role,
     doLayout(NoAnimation);
 }
 
+void KItemListView::slotLeadingPaddingChanged(qreal width)
+{
+    Q_UNUSED(width)
+    if (m_headerWidget->automaticColumnResizing()) {
+        applyAutomaticColumnWidths();
+    }
+    applyColumnWidthsFromHeader();
+    doLayout(NoAnimation);
+}
+
 void KItemListView::slotHeaderColumnMoved(const QByteArray& role,
                                           int currentIndex,
                                           int previousIndex)
@@ -2225,7 +2270,7 @@ void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget* widget)
 
 bool KItemListView::useAlternateBackgrounds() const
 {
-    return m_itemSize.isEmpty() && m_visibleRoles.count() > 1;
+    return m_alternateBackgrounds && m_itemSize.isEmpty();
 }
 
 QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeList& itemRanges) const
@@ -2283,7 +2328,7 @@ QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeLi
 void KItemListView::applyColumnWidthsFromHeader()
 {
     // Apply the new size to the layouter
-    const qreal requiredWidth = columnWidthsSum();
+    const qreal requiredWidth = columnWidthsSum() + m_headerWidget->leadingPadding();
     const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth),
                                  m_itemSize.height());
     m_layouter->setItemSize(dynamicItemSize);
@@ -2301,6 +2346,7 @@ void KItemListView::updateWidgetColumnWidths(KItemListWidget* widget)
     for (const QByteArray& role : qAsConst(m_visibleRoles)) {
         widget->setColumnWidth(role, m_headerWidget->columnWidth(role));
     }
+    widget->setLeadingPadding(m_headerWidget->leadingPadding());
 }
 
 void KItemListView::updatePreferredColumnWidths(const KItemRangeList& itemRanges)
@@ -2378,7 +2424,7 @@ void KItemListView::applyAutomaticColumnWidths()
     qreal firstColumnWidth = m_headerWidget->columnWidth(firstRole);
     QSizeF dynamicItemSize = m_itemSize;
 
-    qreal requiredWidth = columnWidthsSum();
+    qreal requiredWidth = columnWidthsSum() + m_headerWidget->leadingPadding();
     const qreal availableWidth = size().width();
     if (requiredWidth < availableWidth) {
         // Stretch the first column to use the whole remaining width
index cbb178119010abac9c9d18742208704ef08b7218..228989cc40b63621d86efa719de3c21fdb81c6bc 100644 (file)
@@ -205,6 +205,12 @@ public:
     void setSupportsItemExpanding(bool supportsExpanding);
     bool supportsItemExpanding() const;
 
+    void setHighlightEntireRow(bool highlightEntireRow);
+    bool highlightEntireRow() const;
+
+    void setAlternateBackgrounds(bool alternate);
+    bool alternateBackgrounds() const;
+
     /**
      * @return The rectangle of the item relative to the top/left of
      *         the currently visible area (see KItemListView::offset()).
@@ -374,6 +380,7 @@ protected:
     virtual void onScrollOffsetChanged(qreal current, qreal previous);
     virtual void onVisibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous);
     virtual void onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous);
+    virtual void onHighlightEntireRowChanged(bool highlightEntireRow);
     virtual void onSupportsItemExpandingChanged(bool supportsExpanding);
 
     virtual void onTransactionBegin();
@@ -426,6 +433,8 @@ private Q_SLOTS:
                                       qreal currentWidth,
                                       qreal previousWidth);
 
+    void slotLeadingPaddingChanged(qreal width);
+
     /**
      * Is invoked if a column has been moved by the user. Applies
      * the moved role to the view.
@@ -707,6 +716,8 @@ private:
 private:
     bool m_enabledSelectionToggles;
     bool m_grouped;
+    bool m_highlightEntireRow;
+    bool m_alternateBackgrounds;
     bool m_supportsItemExpanding;
     bool m_editingRole;
     int m_activeTransactions; // Counter for beginTransaction()/endTransaction()
index 69a38432a4e21fe360b2610858c67ce21cd9f71b..79ffee2102c8d89134da1e4fdf89a1cd6022cc77 100644 (file)
@@ -34,6 +34,7 @@ KItemListWidget::KItemListWidget(KItemListWidgetInformant* informant, QGraphicsI
     m_selected(false),
     m_current(false),
     m_hovered(false),
+    m_expansionAreaHovered(false),
     m_alternateBackground(false),
     m_enabledSelectionToggle(false),
     m_data(),
@@ -180,6 +181,18 @@ qreal KItemListWidget::columnWidth(const QByteArray& role) const
     return m_columnWidths.value(role);
 }
 
+qreal KItemListWidget::leadingPadding() const {
+    return m_leadingPadding;
+}
+
+void KItemListWidget::setLeadingPadding(qreal width) {
+    if (m_leadingPadding != width){
+        m_leadingPadding = width;
+        leadingPaddingChanged(width);
+        update();
+    }
+}
+
 void KItemListWidget::setStyleOption(const KItemListStyleOption& option)
 {
     if (m_styleOption == option) {
@@ -280,6 +293,20 @@ bool KItemListWidget::isHovered() const
     return m_hovered;
 }
 
+void KItemListWidget::setExpansionAreaHovered(bool hovered)
+{
+    if (hovered == m_expansionAreaHovered) {
+        return;
+    }
+    m_expansionAreaHovered = hovered;
+    update();
+}
+
+bool KItemListWidget::expansionAreaHovered() const
+{
+    return m_expansionAreaHovered;
+}
+
 void KItemListWidget::setHoverPosition(const QPointF& pos)
 {
     if (m_selectionToggle) {
@@ -416,6 +443,11 @@ void KItemListWidget::columnWidthChanged(const QByteArray& role,
     Q_UNUSED(previous)
 }
 
+void KItemListWidget::leadingPaddingChanged(qreal width)
+{
+    Q_UNUSED(width)
+}
+
 void KItemListWidget::styleOptionChanged(const KItemListStyleOption& current,
                                          const KItemListStyleOption& previous)
 {
index c56c41ab300d42141a147530ef71b1adb8a23d71..0b82266c43b87a704dc727d3dc590f2b9af4851a 100644 (file)
@@ -80,6 +80,9 @@ public:
     void setColumnWidth(const QByteArray& role, qreal width);
     qreal columnWidth(const QByteArray& role) const;
 
+    void setLeadingPadding(qreal width);
+    qreal leadingPadding() const;
+
     void setStyleOption(const KItemListStyleOption& option);
     const KItemListStyleOption& styleOption() const;
 
@@ -94,6 +97,9 @@ public:
     void setHovered(bool hovered);
     bool isHovered() const;
 
+    void setExpansionAreaHovered(bool hover);
+    bool expansionAreaHovered() const;
+
     void setHoverPosition(const QPointF& pos);
 
     void setAlternateBackground(bool enable);
@@ -182,6 +188,7 @@ protected:
     virtual void dataChanged(const QHash<QByteArray, QVariant>& current, const QSet<QByteArray>& roles = QSet<QByteArray>());
     virtual void visibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous);
     virtual void columnWidthChanged(const QByteArray& role, qreal current, qreal previous);
+    virtual void leadingPaddingChanged(qreal width);
     virtual void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous);
     virtual void currentChanged(bool current);
     virtual void selectedChanged(bool selected);
@@ -190,6 +197,7 @@ protected:
     virtual void siblingsInformationChanged(const QBitArray& current, const QBitArray& previous);
     virtual void editedRoleChanged(const QByteArray& current, const QByteArray& previous);
     void resizeEvent(QGraphicsSceneResizeEvent* event) override;
+    void clearHoverCache();
 
     /**
      * Called when the user starts hovering this item.
@@ -225,7 +233,6 @@ private Q_SLOTS:
 private:
     void initializeSelectionToggle();
     void setHoverOpacity(qreal opacity);
-    void clearHoverCache();
     void drawItemStyleOption(QPainter* painter, QWidget* widget, QStyle::State styleState);
 
 private:
@@ -236,11 +243,13 @@ private:
     bool m_selected;
     bool m_current;
     bool m_hovered;
+    bool m_expansionAreaHovered;
     bool m_alternateBackground;
     bool m_enabledSelectionToggle;
     QHash<QByteArray, QVariant> m_data;
     QList<QByteArray> m_visibleRoles;
     QHash<QByteArray, qreal> m_columnWidths;
+    qreal m_leadingPadding;
     KItemListStyleOption m_styleOption;
     QBitArray m_siblingsInfo;
 
index 6edbefad80fc2f519f66ac0a8c8027ddd085aed8..4b7c2d9a42ac84224fd23d55e47aa448fa8faae9 100644 (file)
@@ -17,6 +17,7 @@ KStandardItemListView::KStandardItemListView(QGraphicsWidget* parent) :
     setAcceptDrops(true);
     setScrollOrientation(Qt::Vertical);
     setVisibleRoles({"text"});
+    setAlternateBackgrounds(true);
 }
 
 KStandardItemListView::~KStandardItemListView()
@@ -34,6 +35,8 @@ void KStandardItemListView::setItemLayout(ItemLayout layout)
     const ItemLayout previous = m_itemLayout;
     m_itemLayout = layout;
 
+    // keep the leading padding option unchanged here
+    setHighlightEntireRow(layout == DetailsLayout);
     setSupportsItemExpanding(itemLayoutSupportsItemExpanding(layout));
     setScrollOrientation(layout == CompactLayout ? Qt::Horizontal : Qt::Vertical);
 
@@ -69,6 +72,7 @@ void KStandardItemListView::initializeItemListWidget(KItemListWidget* item)
     default:            Q_ASSERT(false); break;
     }
 
+    standardItemListWidget->setHighlightEntireRow(highlightEntireRow());
     standardItemListWidget->setSupportsItemExpanding(supportsItemExpanding());
 }
 
index 3106b47b40834f6d48aa38eac36e27dfe4f33b59..b17fac6d55568fef1a6aa508c3658ccadce446a2 100644 (file)
@@ -264,6 +264,7 @@ KStandardItemListWidget::KStandardItemListWidget(KItemListWidgetInformant* infor
     m_pixmapPos(),
     m_pixmap(),
     m_scaledPixmapSize(),
+    m_columnWidthSum(),
     m_iconRect(),
     m_hoverPixmap(),
     m_textRect(),
@@ -307,6 +308,18 @@ KStandardItemListWidget::Layout KStandardItemListWidget::layout() const
     return m_layout;
 }
 
+void KStandardItemListWidget::setHighlightEntireRow(bool highlightEntireRow) {
+    if (m_highlightEntireRow != highlightEntireRow) {
+        m_highlightEntireRow = highlightEntireRow;
+        m_dirtyLayout = true;
+        update();
+    }
+}
+
+bool KStandardItemListWidget::highlightEntireRow() const {
+    return m_highlightEntireRow;
+}
+
 void KStandardItemListWidget::setSupportsItemExpanding(bool supportsItemExpanding)
 {
     if (m_supportsItemExpanding != supportsItemExpanding) {
@@ -508,7 +521,11 @@ QRectF KStandardItemListWidget::selectionRect() const
     case DetailsLayout: {
         const int padding = styleOption().padding;
         QRectF adjustedIconRect = iconRect().adjusted(-padding, -padding, padding, padding);
-        return adjustedIconRect | m_textRect;
+        QRectF result = adjustedIconRect | m_textRect;
+        if (m_highlightEntireRow) {
+            result.setRight(m_columnWidthSum + leadingPadding());
+        }
+        return result;
     }
 
     default:
@@ -725,6 +742,11 @@ void KStandardItemListWidget::columnWidthChanged(const QByteArray& role,
     m_dirtyLayout = true;
 }
 
+void KStandardItemListWidget::leadingPaddingChanged(qreal padding) {
+    Q_UNUSED(padding)
+    m_dirtyLayout = true;
+}
+
 void KStandardItemListWidget::styleOptionChanged(const KItemListStyleOption& current,
                                              const KItemListStyleOption& previous)
 {
@@ -917,10 +939,13 @@ void KStandardItemListWidget::triggerCacheRefreshing()
     m_isHidden = isHidden();
     m_customizedFont = customizedFont(styleOption().font);
     m_customizedFontMetrics = QFontMetrics(m_customizedFont);
+    m_columnWidthSum = std::accumulate(m_sortedVisibleRoles.begin(), m_sortedVisibleRoles.end(),
+                                       qreal(), [this](qreal sum, const auto &role){ return sum + columnWidth(role); });
 
     updateExpansionArea();
     updateTextsCache();
     updatePixmapCache();
+    clearHoverCache();
 
     m_dirtyLayout = false;
     m_dirtyContent = false;
@@ -938,7 +963,8 @@ void KStandardItemListWidget::updateExpansionArea()
             const qreal inc = (widgetHeight - option.iconSize) / 2;
             const qreal x = expandedParentsCount * widgetHeight + inc;
             const qreal y = inc;
-            m_expansionArea = QRectF(x, y, option.iconSize, option.iconSize);
+            const qreal xPadding = m_highlightEntireRow ? leadingPadding() : 0;
+            m_expansionArea = QRectF(xPadding + x, y, option.iconSize, option.iconSize);
             return;
         }
     }
@@ -1375,7 +1401,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
     if (m_supportsItemExpanding) {
         firstColumnInc += (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2;
     } else {
-        firstColumnInc += option.padding;
+        firstColumnInc += option.padding + leadingPadding();
     }
 
     qreal x = firstColumnInc;
@@ -1391,7 +1417,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
 
         const bool isTextRole = (role == "text");
         if (isTextRole) {
-            availableTextWidth -= firstColumnInc;
+            availableTextWidth -= firstColumnInc - leadingPadding();
         }
 
         if (requiredWidth > availableTextWidth) {
@@ -1413,7 +1439,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
 
             // The column after the name should always be aligned on the same x-position independent
             // from the expansion-level shown in the name column
-            x -= firstColumnInc;
+            x -= firstColumnInc - leadingPadding();
         } else if (isRoleRightAligned(role)) {
             textInfo->pos.rx() += roleWidth - requiredWidth - columnWidthInc;
         }
@@ -1425,8 +1451,11 @@ void KStandardItemListWidget::updateAdditionalInfoTextColor()
     QColor c1;
     if (m_customTextColor.isValid()) {
         c1 = m_customTextColor;
-    } else if (isSelected() && m_layout != DetailsLayout) {
-        c1 = styleOption().palette.highlightedText().color();
+    } else if (isSelected()) {
+        // The detail text colour needs to match the main text (HighlightedText) for the same level
+        // of readability. We short circuit early here to avoid interpolating with another colour.
+        m_additionalInfoTextColor = styleOption().palette.color(QPalette::HighlightedText);
+        return;
     } else {
         c1 = styleOption().palette.text().color();
     }
@@ -1465,15 +1494,15 @@ void KStandardItemListWidget::drawSiblingsInformation(QPainter* painter)
     const int x = (m_expansionArea.left() + m_expansionArea.right() - siblingSize) / 2;
     QRect siblingRect(x, 0, siblingSize, siblingSize);
 
-    QStyleOption option;
-    option.palette.setColor(QPalette::Text, option.palette.color(normalTextColorRole()));
     bool isItemSibling = true;
 
     const QBitArray siblings = siblingsInformation();
+    QStyleOption option;
+    const auto normalColor = option.palette.color(normalTextColorRole());
+    const auto highlightColor = option.palette.color(expansionAreaHovered() ? QPalette::Highlight : normalTextColorRole());
     for (int i = siblings.count() - 1; i >= 0; --i) {
         option.rect = siblingRect;
         option.state = siblings.at(i) ? QStyle::State_Sibling : QStyle::State_None;
-
         if (isItemSibling) {
             option.state |= QStyle::State_Item;
             if (m_isExpandable) {
@@ -1482,7 +1511,10 @@ void KStandardItemListWidget::drawSiblingsInformation(QPainter* painter)
             if (data().value("isExpanded").toBool()) {
                 option.state |= QStyle::State_Open;
             }
+            option.palette.setColor(QPalette::Text, highlightColor);
             isItemSibling = false;
+        } else {
+            option.palette.setColor(QPalette::Text, normalColor);
         }
 
         style()->drawPrimitive(QStyle::PE_IndicatorBranch, &option, painter);
index f39f51fe486ba53ad5a50453a705040c2b022f94..3edec0db55c8e75d4ddf6f0cd9004fbc7787a6ff 100644 (file)
@@ -88,6 +88,9 @@ public:
     void setLayout(Layout layout);
     Layout layout() const;
 
+    void setHighlightEntireRow(bool highlightEntireRow);
+    bool highlightEntireRow() const;
+
     void setSupportsItemExpanding(bool supportsItemExpanding);
     bool supportsItemExpanding() const;
 
@@ -168,6 +171,7 @@ protected:
     void dataChanged(const QHash<QByteArray, QVariant>& current, const QSet<QByteArray>& roles = QSet<QByteArray>()) override;
     void visibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous) override;
     void columnWidthChanged(const QByteArray& role, qreal current, qreal previous) override;
+    void leadingPaddingChanged(qreal width) override;
     void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous) override;
     void hoveredChanged(bool hovered) override;
     void selectedChanged(bool selected) override;
@@ -241,6 +245,7 @@ private:
     QFont m_customizedFont;
     QFontMetrics m_customizedFontMetrics;
     bool m_isExpandable;
+    bool m_highlightEntireRow;
     bool m_supportsItemExpanding;
 
     bool m_dirtyLayout;
@@ -252,6 +257,7 @@ private:
     QPixmap m_pixmap;
     QSize m_scaledPixmapSize; //Size of the pixmap in device independent pixels
 
+    qreal m_columnWidthSum;
     QRectF m_iconRect;          // Cache for KItemListWidget::iconRect()
     QPixmap m_hoverPixmap;      // Cache for modified m_pixmap when hovering the item
 
index 9a7e850a9feee4e81ccc3fc59fd8a90ff51400b4..8b39c25fe208289a7bd3e083c07d535fb024d463 100644 (file)
@@ -18,6 +18,7 @@ KItemListHeaderWidget::KItemListHeaderWidget(QGraphicsWidget* parent) :
     m_automaticColumnResizing(true),
     m_model(nullptr),
     m_offset(0),
+    m_leadingPadding(0),
     m_columns(),
     m_columnWidths(),
     m_preferredColumnWidths(),
@@ -134,6 +135,20 @@ qreal KItemListHeaderWidget::offset() const
     return m_offset;
 }
 
+void KItemListHeaderWidget::setLeadingPadding(qreal width)
+{
+    if (m_leadingPadding != width) {
+        m_leadingPadding = width;
+        leadingPaddingChanged(width);
+        update();
+    }
+}
+
+qreal KItemListHeaderWidget::leadingPadding() const
+{
+    return m_leadingPadding;
+}
+
 qreal KItemListHeaderWidget::minimumColumnWidth() const
 {
     QFontMetricsF fontMetrics(font());
@@ -153,7 +168,7 @@ void KItemListHeaderWidget::paint(QPainter* painter, const QStyleOptionGraphicsI
     painter->setFont(font());
     painter->setPen(palette().text().color());
 
-    qreal x = -m_offset;
+    qreal x = -m_offset + m_leadingPadding;
     int orderIndex = 0;
     for (const QByteArray& role : qAsConst(m_columns)) {
         const qreal roleWidth = m_columnWidths.value(role);
@@ -172,10 +187,14 @@ void KItemListHeaderWidget::paint(QPainter* painter, const QStyleOptionGraphicsI
 void KItemListHeaderWidget::mousePressEvent(QGraphicsSceneMouseEvent* event)
 {
     if (event->button() & Qt::LeftButton) {
-        updatePressedRoleIndex(event->pos());
         m_pressedMousePos = event->pos();
-        m_roleOperation = isAboveRoleGrip(m_pressedMousePos, m_pressedRoleIndex) ?
-                          ResizeRoleOperation : NoRoleOperation;
+        if (isAbovePaddingGrip(m_pressedMousePos, PaddingGrip::Leading)) {
+            m_roleOperation = ResizeLeadingColumnOperation;
+        } else {
+            updatePressedRoleIndex(event->pos());
+            m_roleOperation = isAboveRoleGrip(m_pressedMousePos, m_pressedRoleIndex) ?
+                              ResizeRoleOperation : NoRoleOperation;
+        }
         event->accept();
     } else {
         event->ignore();
@@ -263,7 +282,7 @@ void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
             } else {
                 m_movingRole.pixmap = createRolePixmap(roleIndex);
 
-                qreal roleX = -m_offset;
+                qreal roleX = -m_offset + m_leadingPadding;
                 for (int i = 0; i < roleIndex; ++i) {
                     const QByteArray role = m_columns[i];
                     roleX += m_columnWidths.value(role);
@@ -291,6 +310,20 @@ void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
         break;
     }
 
+    case ResizeLeadingColumnOperation: {
+        qreal currentWidth = m_leadingPadding;
+        currentWidth += event->pos().x() - event->lastPos().x();
+        currentWidth = qMax(0.0, currentWidth);
+
+        m_leadingPadding = currentWidth;
+
+        update();
+
+        Q_EMIT leadingPaddingChanged(currentWidth);
+
+        break;
+    }
+
     case MoveRoleOperation: {
         // TODO: It should be configurable whether moving the first role is allowed.
         // In the context of Dolphin this is not required, however this should be
@@ -355,7 +388,9 @@ void KItemListHeaderWidget::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
 
     const QPointF& pos = event->pos();
     updateHoveredRoleIndex(pos);
-    if (m_hoveredRoleIndex >= 0 && isAboveRoleGrip(pos, m_hoveredRoleIndex)) {
+    if ((m_hoveredRoleIndex >= 0 && isAboveRoleGrip(pos, m_hoveredRoleIndex)) ||
+            isAbovePaddingGrip(pos, PaddingGrip::Leading) ||
+        isAbovePaddingGrip(pos, PaddingGrip::Trailing)) {
         setCursor(Qt::SplitHCursor);
     } else {
         unsetCursor();
@@ -404,19 +439,39 @@ void KItemListHeaderWidget::paintRole(QPainter* painter,
                                 QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp;
     }
     option.rect = rect.toRect();
+    option.orientation = Qt::Horizontal;
+    option.selectedPosition = QStyleOptionHeader::NotAdjacent;
+    option.text = m_model->roleDescription(role);
 
-    bool paintBackgroundForEmptyArea = false;
+    // First we paint any potential empty (padding) space on left and/or right of this role's column.
+    const auto paintPadding = [&](int section, const QRectF &rect, const QStyleOptionHeader::SectionPosition &pos){
+        QStyleOptionHeader padding;
+        padding.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
+        padding.section = section;
+        padding.sortIndicator = QStyleOptionHeader::None;
+        padding.rect = rect.toRect();
+        padding.position = pos;
+        padding.text = QString();
+        style()->drawControl(QStyle::CE_Header, &padding, painter, widget);
+    };
 
     if (m_columns.count() == 1) {
-        option.position = QStyleOptionHeader::OnlyOneSection;
+        option.position = QStyleOptionHeader::Middle;
+        paintPadding(0, QRectF(0.0, 0.0, rect.left(), rect.height()), QStyleOptionHeader::Beginning);
+        paintPadding(1, QRectF(rect.left(), 0.0, size().width() - rect.left(), rect.height()), QStyleOptionHeader::End);
     } else if (orderIndex == 0) {
-        option.position = QStyleOptionHeader::Beginning;
+        // Paint the header for the first column; check if there is some empty space to the left which needs to be filled.
+        if (rect.left() > 0) {
+            option.position = QStyleOptionHeader::Middle;
+            paintPadding(0,QRectF(0.0, 0.0, rect.left(), rect.height()), QStyleOptionHeader::Beginning);
+        } else {
+            option.position = QStyleOptionHeader::Beginning;
+        }
     } else if (orderIndex == m_columns.count() - 1) {
-        // We are just painting the header for the last column. Check if there
-        // is some empty space to the right which needs to be filled.
+        // Paint the header for the last column; check if there is some empty space to the right which needs to be filled.
         if (rect.right() < size().width()) {
             option.position = QStyleOptionHeader::Middle;
-            paintBackgroundForEmptyArea = true;
+            paintPadding(m_columns.count(), QRectF(rect.left(), 0.0, size().width() - rect.left(), rect.height()), QStyleOptionHeader::End);
         } else {
             option.position = QStyleOptionHeader::End;
         }
@@ -424,25 +479,7 @@ void KItemListHeaderWidget::paintRole(QPainter* painter,
         option.position = QStyleOptionHeader::Middle;
     }
 
-    option.orientation = Qt::Horizontal;
-    option.selectedPosition = QStyleOptionHeader::NotAdjacent;
-    option.text = m_model->roleDescription(role);
-
     style()->drawControl(QStyle::CE_Header, &option, painter, widget);
-
-    if (paintBackgroundForEmptyArea) {
-        option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
-        option.section = m_columns.count();
-        option.sortIndicator = QStyleOptionHeader::None;
-
-        qreal backgroundRectX = rect.x() + rect.width();
-        QRectF backgroundRect(backgroundRectX, 0.0, size().width() - backgroundRectX, rect.height());
-        option.rect = backgroundRect.toRect();
-        option.position = QStyleOptionHeader::End;
-        option.text = QString();
-
-        style()->drawControl(QStyle::CE_Header, &option, painter, widget);
-    }
 }
 
 void KItemListHeaderWidget::updatePressedRoleIndex(const QPointF& pos)
@@ -467,7 +504,7 @@ int KItemListHeaderWidget::roleIndexAt(const QPointF& pos) const
 {
     int index = -1;
 
-    qreal x = -m_offset;
+    qreal x = -m_offset + m_leadingPadding;
     for (const QByteArray& role : qAsConst(m_columns)) {
         ++index;
         x += m_columnWidths.value(role);
@@ -481,7 +518,7 @@ int KItemListHeaderWidget::roleIndexAt(const QPointF& pos) const
 
 bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF& pos, int roleIndex) const
 {
-    qreal x = -m_offset;
+    qreal x = -m_offset + m_leadingPadding;
     for (int i = 0; i <= roleIndex; ++i) {
         const QByteArray role = m_columns[i];
         x += m_columnWidths.value(role);
@@ -491,6 +528,27 @@ bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF& pos, int roleIndex) c
     return pos.x() >= (x - grip) && pos.x() <= x;
 }
 
+bool KItemListHeaderWidget::isAbovePaddingGrip(const QPointF& pos, PaddingGrip paddingGrip) const
+{
+    const qreal lx = -m_offset + m_leadingPadding;
+    const int grip = style()->pixelMetric(QStyle::PM_HeaderGripMargin);
+
+    switch (paddingGrip) {
+    case Leading:
+        return pos.x() >= (lx - grip) && pos.x() <= lx;
+    case Trailing:
+    {
+        qreal rx = lx;
+        for (const QByteArray& role : qAsConst(m_columns)) {
+            rx += m_columnWidths.value(role);
+        }
+        return pos.x() >= (rx - grip) && pos.x() <= rx;
+    }
+    default:
+        return false;
+    }
+}
+
 QPixmap KItemListHeaderWidget::createRolePixmap(int roleIndex) const
 {
     const QByteArray role = m_columns[roleIndex];
@@ -522,7 +580,7 @@ int KItemListHeaderWidget::targetOfMovingRole() const
     const int movingRight = movingLeft + movingWidth - 1;
 
     int targetIndex = 0;
-    qreal targetLeft = -m_offset;
+    qreal targetLeft = -m_offset + m_leadingPadding;
     while (targetIndex < m_columns.count()) {
         const QByteArray role = m_columns[targetIndex];
         const qreal targetWidth = m_columnWidths.value(role);
@@ -548,7 +606,7 @@ int KItemListHeaderWidget::targetOfMovingRole() const
 
 qreal KItemListHeaderWidget::roleXPosition(const QByteArray& role) const
 {
-    qreal x = -m_offset;
+    qreal x = -m_offset + m_leadingPadding;
     for (const QByteArray& visibleRole : qAsConst(m_columns)) {
         if (visibleRole == role) {
             return x;
index 44adc23c5b263028b43e133dc110e1074dc11c6d..58f0dc98ee4ed24f3f240d44bdc06f9795d45fdb 100644 (file)
@@ -50,6 +50,9 @@ public:
     void setOffset(qreal offset);
     qreal offset() const;
 
+    void setLeadingPadding(qreal width);
+    qreal leadingPadding() const;
+
     qreal minimumColumnWidth() const;
 
     void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override;
@@ -63,6 +66,8 @@ Q_SIGNALS:
                             qreal currentWidth,
                             qreal previousWidth);
 
+    void leadingPaddingChanged(qreal width);
+
     /**
      * Is emitted if the user has released the mouse button after adjusting the
      * width of a visible role.
@@ -105,6 +110,13 @@ private Q_SLOTS:
     void slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous);
 
 private:
+
+    enum PaddingGrip
+    {
+        Leading,
+        Trailing,
+    };
+
     void paintRole(QPainter* painter,
                    const QByteArray& role,
                    const QRectF& rect,
@@ -115,6 +127,7 @@ private:
     void updateHoveredRoleIndex(const QPointF& pos);
     int roleIndexAt(const QPointF& pos) const;
     bool isAboveRoleGrip(const QPointF& pos, int roleIndex) const;
+    bool isAbovePaddingGrip(const QPointF& pos, PaddingGrip paddingGrip) const;
 
     /**
      * Creates a pixmap of the role with the index \a roleIndex that is shown
@@ -138,12 +151,14 @@ private:
     {
         NoRoleOperation,
         ResizeRoleOperation,
+        ResizeLeadingColumnOperation,
         MoveRoleOperation
     };
 
     bool m_automaticColumnResizing;
     KItemModelBase* m_model;
     qreal m_offset;
+    qreal m_leadingPadding;
     QList<QByteArray> m_columns;
     QHash<QByteArray, qreal> m_columnWidths;
     QHash<QByteArray, qreal> m_preferredColumnWidths;
index c8238f1e827df548c91915c104c0da315c6d1615..2a70cc1ff0850e5ebd394e3dc0fc9bab6144f035 100644 (file)
             <label>Position of columns</label>
             <default>0,1,2,3,4,5,6,7,8</default>
         </entry>
+        <entry name="LeadingPadding" type="UInt">
+            <label>Leading Column Padding</label>
+            <default>20</default>
+        </entry>
         <entry name="ExpandableFolders" type="Bool">
             <label>Expandable folders</label>
             <default>true</default>
index 864d180c8ee3f38ee78146723b2bd398407f3316..92358924660c21c485ad44c91755052c5d599e3d 100644 (file)
@@ -73,6 +73,7 @@ void DolphinItemListView::readSettings()
     beginTransaction();
 
     setEnabledSelectionToggles(GeneralSettings::showSelectionToggle());
+    setHighlightEntireRow(DetailsModeSettings::leadingPadding());
     setSupportsItemExpanding(itemLayoutSupportsItemExpanding(itemLayout()));
 
     updateFont();
index 0cd38c9eb50c0a248e89dc70e5146efdc16c605f..0a9783fe13092c16ec3cdbca5fb792c10e005576 100644 (file)
@@ -8,6 +8,7 @@
 #include "dolphinview.h"
 
 #include "dolphin_generalsettings.h"
+#include "dolphin_detailsmodesettings.h"
 #include "dolphinitemlistview.h"
 #include "dolphinnewfilemenuobserver.h"
 #include "draganddrophelper.h"
@@ -201,6 +202,8 @@ DolphinView::DolphinView(const QUrl& url, QWidget* parent) :
             this, &DolphinView::slotRoleEditingCanceled);
     connect(m_view->header(), &KItemListHeader::columnWidthChangeFinished,
             this, &DolphinView::slotHeaderColumnWidthChangeFinished);
+    connect(m_view->header(), &KItemListHeader::leadingPaddingChanged,
+            this, &DolphinView::slotLeadingPaddingWidthChanged);
 
     KItemListSelectionManager* selectionManager = controller->selectionManager();
     connect(selectionManager, &KItemListSelectionManager::selectionChanged,
@@ -1117,6 +1120,10 @@ void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos)
     QActionGroup* widthsGroup = new QActionGroup(menu);
     const bool autoColumnWidths = props.headerColumnWidths().isEmpty();
 
+    QAction* toggleLeadingPaddingAction = menu->addAction(i18nc("@action:inmenu", "Leading Column Padding"));
+    toggleLeadingPaddingAction->setCheckable(true);
+    toggleLeadingPaddingAction->setChecked(view->header()->leadingPadding() > 0);
+
     QAction* autoAdjustWidthsAction = menu->addAction(i18nc("@action:inmenu", "Automatic Column Widths"));
     autoAdjustWidthsAction->setCheckable(true);
     autoAdjustWidthsAction->setChecked(autoColumnWidths);
@@ -1147,6 +1154,8 @@ void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos)
             }
             props.setHeaderColumnWidths(columnWidths);
             header->setAutomaticColumnResizing(false);
+        } else if (action == toggleLeadingPaddingAction) {
+            header->setLeadingPadding(toggleLeadingPaddingAction->isChecked() ? 20 : 0);
         } else {
             // Show or hide the selected role
             const QByteArray selectedRole = action->data().toByteArray();
@@ -1199,6 +1208,13 @@ void DolphinView::slotHeaderColumnWidthChangeFinished(const QByteArray& role, qr
     props.setHeaderColumnWidths(columnWidths);
 }
 
+void DolphinView::slotLeadingPaddingWidthChanged(qreal width)
+{
+    ViewProperties props(viewPropertiesUrl());
+    DetailsModeSettings::setLeadingPadding(int(width));
+    m_view->writeSettings();
+}
+
 void DolphinView::slotItemHovered(int index)
 {
     const KFileItem item = m_model->fileItem(index);
@@ -1992,6 +2008,7 @@ void DolphinView::applyViewProperties(const ViewProperties& props)
         } else {
             header->setAutomaticColumnResizing(true);
         }
+        header->setLeadingPadding(DetailsModeSettings::leadingPadding());
     }
 
     m_view->endTransaction();
index 8566bc1007cd763ba84718d7fc544abc40aa60e7..e93ca4fa026bebe041ad80791ad55d8d690ffdcf 100644 (file)
@@ -635,6 +635,7 @@ private Q_SLOTS:
     void slotViewContextMenuRequested(const QPointF& pos);
     void slotHeaderContextMenuRequested(const QPointF& pos);
     void slotHeaderColumnWidthChangeFinished(const QByteArray& role, qreal current);
+    void slotLeadingPaddingWidthChanged(qreal width);
     void slotItemHovered(int index);
     void slotItemUnhovered(int index);
     void slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event);