From 3f88f79f862a570b68fe64781955cf7d14124127 Mon Sep 17 00:00:00 2001 From: Peter Penz Date: Fri, 23 Mar 2012 22:26:17 +0100 Subject: [PATCH] Details view: Optionally remember user changed column-widths If the user changed a column-width in the details-view, up to now the width got reset when changing a directory or when restarting Dolphin. Now the column-widths automatically get remembered for each directory in case if the user has modified the width. The automatic resizing is still turn on per default. The storing of the custom column-width can easily be reset by right clicking on the header and selecting "Automatic Column Widths" from the context-menu. Some finetuning is still necessary (e.g. the "Adjust View Properties" dialog currently is not aware about this setting) but this will be fixed during the next weeks. BUG: 264434 FIXED-IN: 4.9.0 --- src/CMakeLists.txt | 1 + src/kitemviews/kfileitemlistview.cpp | 12 +- src/kitemviews/kfileitemlistview.h | 2 +- src/kitemviews/kfileitemlistwidget.cpp | 16 +- src/kitemviews/kfileitemlistwidget.h | 2 +- src/kitemviews/kitemlistheader.cpp | 483 ++--------------- src/kitemviews/kitemlistheader.h | 89 +++ src/kitemviews/kitemlistheaderwidget.cpp | 507 ++++++++++++++++++ ...stheader_p.h => kitemlistheaderwidget_p.h} | 45 +- src/kitemviews/kitemlistview.cpp | 283 +++++----- src/kitemviews/kitemlistview.h | 77 +-- src/kitemviews/kitemlistwidget.cpp | 26 +- src/kitemviews/kitemlistwidget.h | 12 +- ...dolphin_directoryviewpropertysettings.kcfg | 5 + src/views/dolphinitemlistcontainer.cpp | 6 +- src/views/dolphinview.cpp | 113 +++- src/views/dolphinview.h | 1 + src/views/viewproperties.cpp | 14 + src/views/viewproperties.h | 3 + 19 files changed, 1006 insertions(+), 691 deletions(-) create mode 100644 src/kitemviews/kitemlistheader.h create mode 100644 src/kitemviews/kitemlistheaderwidget.cpp rename src/kitemviews/{kitemlistheader_p.h => kitemlistheaderwidget_p.h} (78%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 39fd94aa0..2ab5e9cdd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,6 +29,7 @@ set(dolphinprivate_LIB_SRCS kitemviews/kitemlistcontroller.cpp kitemviews/kitemlistgroupheader.cpp kitemviews/kitemlistheader.cpp + kitemviews/kitemlistheaderwidget.cpp kitemviews/kitemlistkeyboardsearchmanager.cpp kitemviews/kitemlistrubberband.cpp kitemviews/kitemlistselectionmanager.cpp diff --git a/src/kitemviews/kfileitemlistview.cpp b/src/kitemviews/kfileitemlistview.cpp index 54d858c1e..483dabb83 100644 --- a/src/kitemviews/kfileitemlistview.cpp +++ b/src/kitemviews/kfileitemlistview.cpp @@ -193,12 +193,12 @@ QSizeF KFileItemListView::itemSizeHint(int index) const return QSize(); } -QHash KFileItemListView::visibleRolesSizes(const KItemRangeList& itemRanges) const +QHash KFileItemListView::columnWidths(const KItemRangeList& itemRanges) const { QElapsedTimer timer; timer.start(); - QHash sizes; + QHash widths; int calculatedItemCount = 0; bool maxTimeExceeded = false; @@ -208,10 +208,10 @@ QHash KFileItemListView::visibleRolesSizes(const KItemRangeL for (int i = startIndex; i <= endIndex; ++i) { foreach (const QByteArray& visibleRole, visibleRoles()) { - QSizeF maxSize = sizes.value(visibleRole, QSizeF(0, 0)); + qreal maxWidth = widths.value(visibleRole, 0); const QSizeF itemSize = visibleRoleSizeHint(i, visibleRole); - maxSize = maxSize.expandedTo(itemSize); - sizes.insert(visibleRole, maxSize); + maxWidth = qMax(itemSize.width(), maxWidth); + widths.insert(visibleRole, maxWidth); } if (calculatedItemCount > 100 && timer.elapsed() > 200) { @@ -238,7 +238,7 @@ QHash KFileItemListView::visibleRolesSizes(const KItemRangeL } kDebug() << "[TIME] Calculated dynamic item size for " << rangesItemCount << "items:" << timer.elapsed(); #endif - return sizes; + return widths; } QPixmap KFileItemListView::createDragPixmap(const QSet& indexes) const diff --git a/src/kitemviews/kfileitemlistview.h b/src/kitemviews/kfileitemlistview.h index 12d0d452b..23a84d4b9 100644 --- a/src/kitemviews/kfileitemlistview.h +++ b/src/kitemviews/kfileitemlistview.h @@ -78,7 +78,7 @@ public: virtual QSizeF itemSizeHint(int index) const; /** @reimp */ - virtual QHash visibleRolesSizes(const KItemRangeList& itemRanges) const; + virtual QHash columnWidths(const KItemRangeList& itemRanges) const; /** @reimp */ virtual QPixmap createDragPixmap(const QSet& indexes) const; diff --git a/src/kitemviews/kfileitemlistwidget.cpp b/src/kitemviews/kfileitemlistwidget.cpp index fb0f4df57..5c865d1ca 100644 --- a/src/kitemviews/kfileitemlistwidget.cpp +++ b/src/kitemviews/kfileitemlistwidget.cpp @@ -139,7 +139,7 @@ void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsIte // with the icon. This can happen if the user has minimized the width // of the name-column to a very small value. const qreal minX = m_pixmapPos.x() + m_pixmap.width() + 4 * itemListStyleOption.padding; - if (m_textPos[Name + 1].x() < minX) { + if (m_textPos[Name].x() + columnWidth("name") > minX) { clipAdditionalInfoBounds = true; painter->save(); painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip); @@ -347,9 +347,11 @@ void KFileItemListWidget::visibleRolesChanged(const QList& current, m_dirtyLayout = true; } -void KFileItemListWidget::visibleRolesSizesChanged(const QHash& current, - const QHash& previous) +void KFileItemListWidget::columnWidthChanged(const QByteArray& role, + qreal current, + qreal previous) { + Q_UNUSED(role); Q_UNUSED(current); Q_UNUSED(previous); m_dirtyLayout = true; @@ -783,8 +785,8 @@ void KFileItemListWidget::updateDetailsLayoutTextCache() // Elide the text in case it does not fit into the available column-width qreal requiredWidth = option.fontMetrics.width(text); - const qreal columnWidth = visibleRolesSizes().value(role, QSizeF(0, 0)).width(); - qreal availableTextWidth = columnWidth - 2 * columnPadding; + const qreal roleWidth = columnWidth(role); + qreal availableTextWidth = roleWidth - 2 * columnPadding; if (textId == Name) { availableTextWidth -= firstColumnInc; } @@ -796,7 +798,7 @@ void KFileItemListWidget::updateDetailsLayoutTextCache() m_text[textId].setText(text); m_textPos[textId] = QPointF(x + columnPadding, y); - x += columnWidth; + x += roleWidth; switch (textId) { case Name: { @@ -813,7 +815,7 @@ void KFileItemListWidget::updateDetailsLayoutTextCache() } case Size: // The values for the size should be right aligned - m_textPos[textId].rx() += columnWidth - requiredWidth - 2 * columnPadding; + m_textPos[textId].rx() += roleWidth - requiredWidth - 2 * columnPadding; break; default: diff --git a/src/kitemviews/kfileitemlistwidget.h b/src/kitemviews/kfileitemlistwidget.h index 495831335..3e6cf8d2a 100644 --- a/src/kitemviews/kfileitemlistwidget.h +++ b/src/kitemviews/kfileitemlistwidget.h @@ -85,7 +85,7 @@ protected: virtual void dataChanged(const QHash& current, const QSet& roles = QSet()); virtual void visibleRolesChanged(const QList& current, const QList& previous); - virtual void visibleRolesSizesChanged(const QHash& current, const QHash& previous); + virtual void columnWidthChanged(const QByteArray& role, qreal current, qreal previous); virtual void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous); virtual void hoveredChanged(bool hovered); virtual void selectedChanged(bool selected); diff --git a/src/kitemviews/kitemlistheader.cpp b/src/kitemviews/kitemlistheader.cpp index ea714c96f..f1050538d 100644 --- a/src/kitemviews/kitemlistheader.cpp +++ b/src/kitemviews/kitemlistheader.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2011 by Peter Penz * + * Copyright (C) 2012 by Peter Penz * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -17,484 +17,63 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ -#include "kitemlistheader_p.h" - -#include -#include -#include "kitemmodelbase.h" - -#include -#include -#include -#include - -#include - -KItemListHeader::KItemListHeader(QGraphicsWidget* parent) : - QGraphicsWidget(parent), - m_model(0), - m_visibleRoles(), - m_visibleRolesWidths(), - m_hoveredRoleIndex(-1), - m_pressedRoleIndex(-1), - m_roleOperation(NoRoleOperation), - m_pressedMousePos(), - m_movingRole() -{ - m_movingRole.x = 0; - m_movingRole.xDec = 0; - m_movingRole.index = -1; - - setAcceptHoverEvents(true); - - QStyleOptionHeader option; - const QSize headerSize = style()->sizeFromContents(QStyle::CT_HeaderSection, &option, QSize()); - resize(0, headerSize.height()); -} +#include "kitemlistheader.h" +#include "kitemlistheaderwidget_p.h" +#include "kitemlistview.h" KItemListHeader::~KItemListHeader() { } -void KItemListHeader::setModel(KItemModelBase* model) -{ - if (m_model == model) { - return; - } - - if (m_model) { - disconnect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), - this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); - disconnect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), - this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder))); - } - - m_model = model; - - if (m_model) { - connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), - this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); - connect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), - this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder))); - } -} - -KItemModelBase* KItemListHeader::model() const -{ - return m_model; -} - -void KItemListHeader::setVisibleRoles(const QList& roles) -{ - m_visibleRoles = roles; - update(); -} - -QList KItemListHeader::visibleRoles() const -{ - return m_visibleRoles; -} - -void KItemListHeader::setVisibleRolesWidths(const QHash& rolesWidths) -{ - m_visibleRolesWidths = rolesWidths; - - // Assure that no width is smaller than the minimum allowed width - const qreal minWidth = minimumRoleWidth(); - QMutableHashIterator it(m_visibleRolesWidths); - while (it.hasNext()) { - it.next(); - if (it.value() < minWidth) { - m_visibleRolesWidths.insert(it.key(), minWidth); - } - } - - update(); -} - -QHash KItemListHeader::visibleRolesWidths() const -{ - return m_visibleRolesWidths; -} - -qreal KItemListHeader::minimumRoleWidth() const -{ - QFontMetricsF fontMetrics(font()); - return fontMetrics.height() * 4; -} - -void KItemListHeader::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) -{ - Q_UNUSED(option); - Q_UNUSED(widget); - - if (!m_model) { - return; - } - - // Draw roles - painter->setFont(font()); - painter->setPen(palette().text().color()); - - qreal x = 0; - int orderIndex = 0; - foreach (const QByteArray& role, m_visibleRoles) { - const qreal roleWidth = m_visibleRolesWidths.value(role); - const QRectF rect(x, 0, roleWidth, size().height()); - paintRole(painter, role, rect, orderIndex, widget); - x += roleWidth; - ++orderIndex; - } - - // Draw background without roles - QStyleOption opt; - opt.init(widget); - opt.rect = QRect(x, 0, size().width() - x, size().height()); - opt.state |= QStyle::State_Horizontal; - style()->drawControl(QStyle::CE_HeaderEmptyArea, &opt, painter); - - if (!m_movingRole.pixmap.isNull()) { - Q_ASSERT(m_roleOperation == MoveRoleOperation); - painter->drawPixmap(m_movingRole.x, 0, m_movingRole.pixmap); - } -} - -void KItemListHeader::mousePressEvent(QGraphicsSceneMouseEvent* event) -{ - if (event->button() & Qt::LeftButton) { - updatePressedRoleIndex(event->pos()); - m_pressedMousePos = event->pos(); - m_roleOperation = isAboveRoleGrip(m_pressedMousePos, m_pressedRoleIndex) ? - ResizeRoleOperation : NoRoleOperation; - event->accept(); - } else { - event->ignore(); - } -} - -void KItemListHeader::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +void KItemListHeader::setAutomaticColumnResizing(bool automatic) { - QGraphicsWidget::mouseReleaseEvent(event); - - if (m_pressedRoleIndex == -1) { - return; - } - - switch (m_roleOperation) { - case NoRoleOperation: { - // Only a click has been done and no moving or resizing has been started - const QByteArray sortRole = m_model->sortRole(); - const int sortRoleIndex = m_visibleRoles.indexOf(sortRole); - if (m_pressedRoleIndex == sortRoleIndex) { - // Toggle the sort order - const Qt::SortOrder previous = m_model->sortOrder(); - const Qt::SortOrder current = (m_model->sortOrder() == Qt::AscendingOrder) ? - Qt::DescendingOrder : Qt::AscendingOrder; - m_model->setSortOrder(current); - emit sortOrderChanged(current, previous); - } else { - // Change the sort role - const QByteArray previous = m_model->sortRole(); - const QByteArray current = m_visibleRoles[m_pressedRoleIndex]; - m_model->setSortRole(current); - emit sortRoleChanged(current, previous); - } - break; - } - - case MoveRoleOperation: - m_movingRole.pixmap = QPixmap(); - m_movingRole.x = 0; - m_movingRole.xDec = 0; - m_movingRole.index = -1; - break; - - default: - break; - } - - m_pressedRoleIndex = -1; - m_roleOperation = NoRoleOperation; - update(); - - QApplication::restoreOverrideCursor(); -} - -void KItemListHeader::mouseMoveEvent(QGraphicsSceneMouseEvent* event) -{ - QGraphicsWidget::mouseMoveEvent(event); - - switch (m_roleOperation) { - case NoRoleOperation: - if ((event->pos() - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) { - // A role gets dragged by the user. Create a pixmap of the role that will get - // synchronized on each furter mouse-move-event with the mouse-position. - m_roleOperation = MoveRoleOperation; - const int roleIndex = roleIndexAt(m_pressedMousePos); - m_movingRole.index = roleIndex; - if (roleIndex == 0) { - // 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 - // changed if KItemViews are used in a more generic way. - QApplication::setOverrideCursor(QCursor(Qt::ForbiddenCursor)); - } else { - m_movingRole.pixmap = createRolePixmap(roleIndex); - - qreal roleX = 0; - for (int i = 0; i < roleIndex; ++i) { - const QByteArray role = m_visibleRoles[i]; - roleX += m_visibleRolesWidths.value(role); - } - - m_movingRole.xDec = event->pos().x() - roleX; - m_movingRole.x = roleX; - update(); - } + if (m_headerWidget->automaticColumnResizing() != automatic) { + m_headerWidget->setAutomaticColumnResizing(automatic); + if (automatic) { + m_view->updateColumnWidthsForHeader(); } - break; - - case ResizeRoleOperation: { - const QByteArray pressedRole = m_visibleRoles[m_pressedRoleIndex]; - - qreal previousWidth = m_visibleRolesWidths.value(pressedRole); - qreal currentWidth = previousWidth; - currentWidth += event->pos().x() - event->lastPos().x(); - currentWidth = qMax(minimumRoleWidth(), currentWidth); - - m_visibleRolesWidths.insert(pressedRole, currentWidth); - update(); - - emit visibleRoleWidthChanged(pressedRole, currentWidth, previousWidth); - 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 - // changed if KItemViews are used in a more generic way. - if (m_movingRole.index > 0) { - m_movingRole.x = event->pos().x() - m_movingRole.xDec; - update(); - - const int targetIndex = targetOfMovingRole(); - if (targetIndex > 0 && targetIndex != m_movingRole.index) { - const QByteArray role = m_visibleRoles[m_movingRole.index]; - const int previousIndex = m_movingRole.index; - m_movingRole.index = targetIndex; - emit visibleRoleMoved(role, targetIndex, previousIndex); - - m_movingRole.xDec = event->pos().x() - roleXPosition(role); - } - } - break; - } - - default: - break; - } -} - -void KItemListHeader::hoverEnterEvent(QGraphicsSceneHoverEvent* event) -{ - QGraphicsWidget::hoverEnterEvent(event); - updateHoveredRoleIndex(event->pos()); -} - -void KItemListHeader::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) -{ - QGraphicsWidget::hoverLeaveEvent(event); - if (m_hoveredRoleIndex != -1) { - m_hoveredRoleIndex = -1; - update(); - } -} - -void KItemListHeader::hoverMoveEvent(QGraphicsSceneHoverEvent* event) -{ - QGraphicsWidget::hoverMoveEvent(event); - - const QPointF& pos = event->pos(); - updateHoveredRoleIndex(pos); - if (m_hoveredRoleIndex >= 0 && isAboveRoleGrip(pos, m_hoveredRoleIndex)) { - setCursor(Qt::SplitHCursor); - } else { - unsetCursor(); - } -} - -void KItemListHeader::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous) -{ - Q_UNUSED(current); - Q_UNUSED(previous); - update(); -} - -void KItemListHeader::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) -{ - Q_UNUSED(current); - Q_UNUSED(previous); - update(); -} - -void KItemListHeader::paintRole(QPainter* painter, - const QByteArray& role, - const QRectF& rect, - int orderIndex, - QWidget* widget) const -{ - // The following code is based on the code from QHeaderView::paintSection(). - // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). - QStyleOptionHeader option; - option.section = orderIndex; - option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal; - if (isEnabled()) { - option.state |= QStyle::State_Enabled; - } - if (window() && window()->isActiveWindow()) { - option.state |= QStyle::State_Active; - } - if (m_hoveredRoleIndex == orderIndex) { - option.state |= QStyle::State_MouseOver; - } - if (m_pressedRoleIndex == orderIndex) { - option.state |= QStyle::State_Sunken; - } - if (m_model->sortRole() == role) { - option.sortIndicator = (m_model->sortOrder() == Qt::AscendingOrder) ? - QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp; - } - option.rect = rect.toRect(); - - if (m_visibleRoles.count() == 1) { - option.position = QStyleOptionHeader::OnlyOneSection; - } else if (orderIndex == 0) { - option.position = QStyleOptionHeader::Beginning; - } else if (orderIndex == m_visibleRoles.count() - 1) { - option.position = QStyleOptionHeader::End; - } else { - 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); -} - -void KItemListHeader::updatePressedRoleIndex(const QPointF& pos) -{ - const int pressedIndex = roleIndexAt(pos); - if (m_pressedRoleIndex != pressedIndex) { - m_pressedRoleIndex = pressedIndex; - update(); - } -} - -void KItemListHeader::updateHoveredRoleIndex(const QPointF& pos) -{ - const int hoverIndex = roleIndexAt(pos); - if (m_hoveredRoleIndex != hoverIndex) { - m_hoveredRoleIndex = hoverIndex; - update(); } } -int KItemListHeader::roleIndexAt(const QPointF& pos) const +bool KItemListHeader::automaticColumnResizing() const { - int index = -1; - - qreal x = 0; - foreach (const QByteArray& role, m_visibleRoles) { - ++index; - x += m_visibleRolesWidths.value(role); - if (pos.x() <= x) { - break; - } - } - - return index; + return m_headerWidget->automaticColumnResizing(); } -bool KItemListHeader::isAboveRoleGrip(const QPointF& pos, int roleIndex) const +void KItemListHeader::setColumnWidth(const QByteArray& role, qreal width) { - qreal x = 0; - for (int i = 0; i <= roleIndex; ++i) { - const QByteArray role = m_visibleRoles[i]; - x += m_visibleRolesWidths.value(role); + if (!m_headerWidget->automaticColumnResizing()) { + m_headerWidget->setColumnWidth(role, width); + m_view->applyColumnWidthsFromHeader(); } - - const int grip = style()->pixelMetric(QStyle::PM_HeaderGripMargin); - return pos.x() >= (x - grip) && pos.x() <= x; } -QPixmap KItemListHeader::createRolePixmap(int roleIndex) const +qreal KItemListHeader::columnWidth(const QByteArray& role) const { - const QByteArray role = m_visibleRoles[roleIndex]; - const qreal roleWidth = m_visibleRolesWidths.value(role); - const QRect rect(0, 0, roleWidth, size().height()); - - QImage image(rect.size(), QImage::Format_ARGB32_Premultiplied); - - QPainter painter(&image); - paintRole(&painter, role, rect, roleIndex); - - // Apply a highlighting-color - const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive; - QColor highlightColor = palette().color(group, QPalette::Highlight); - highlightColor.setAlpha(64); - painter.fillRect(rect, highlightColor); - - // Make the image transparent - painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); - painter.fillRect(0, 0, image.width(), image.height(), QColor(0, 0, 0, 192)); - - return QPixmap::fromImage(image); + return m_headerWidget->columnWidth(role); } -int KItemListHeader::targetOfMovingRole() const +void KItemListHeader::setColumnWidths(const QHash& columnWidths) { - const int movingWidth = m_movingRole.pixmap.width(); - const int movingLeft = m_movingRole.x; - const int movingRight = movingLeft + movingWidth - 1; - - int targetIndex = 0; - qreal targetLeft = 0; - while (targetIndex < m_visibleRoles.count()) { - const QByteArray role = m_visibleRoles[targetIndex]; - const qreal targetWidth = m_visibleRolesWidths.value(role); - const qreal targetRight = targetLeft + targetWidth - 1; - - const bool isInTarget = (targetWidth >= movingWidth && - movingLeft >= targetLeft && - movingRight <= targetRight) || - (targetWidth < movingWidth && - movingLeft <= targetLeft && - movingRight >= targetRight); - - if (isInTarget) { - return targetIndex; + if (!m_headerWidget->automaticColumnResizing()) { + foreach (const QByteArray& role, m_view->visibleRoles()) { + const qreal width = columnWidths.value(role); + m_headerWidget->setColumnWidth(role, width); } - targetLeft += targetWidth; - ++targetIndex; + m_view->applyColumnWidthsFromHeader(); } - - return m_movingRole.index; } -qreal KItemListHeader::roleXPosition(const QByteArray& role) const +KItemListHeader::KItemListHeader(KItemListView* listView) : + QObject(listView->parent()), + m_view(listView) { - qreal x = 0; - foreach (const QByteArray& visibleRole, m_visibleRoles) { - if (visibleRole == role) { - return x; - } - - x += m_visibleRolesWidths.value(visibleRole); - } + m_headerWidget = m_view->m_headerWidget; + Q_ASSERT(m_headerWidget); - return -1; + connect(m_headerWidget, SIGNAL(columnWidthChanged(QByteArray,qreal,qreal)), + this, SIGNAL(columnWidthChanged(QByteArray,qreal,qreal))); } -#include "kitemlistheader_p.moc" +#include "kitemlistheader.moc" diff --git a/src/kitemviews/kitemlistheader.h b/src/kitemviews/kitemlistheader.h new file mode 100644 index 000000000..d0aae7fc1 --- /dev/null +++ b/src/kitemviews/kitemlistheader.h @@ -0,0 +1,89 @@ +/*************************************************************************** + * Copyright (C) 2012 by Peter Penz * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef KITEMLISTHEADER_H +#define KITEMLISTHEADER_H + +#include +#include +#include + +class KItemListHeaderWidget; +class KItemListView; + +/** + * @brief Provides access to the header of a KItemListView. + * + * Each column of the header represents a visible role + * accessible by KItemListView::visibleRoles(). + */ +class LIBDOLPHINPRIVATE_EXPORT KItemListHeader : public QObject +{ + Q_OBJECT + +public: + virtual ~KItemListHeader(); + + /** + * If set to true, KItemListView will automatically adjust the + * widths of the columns. If set to false, the size can be + * manually adjusted by KItemListHeader::setColumnWidth(). + */ + void setAutomaticColumnResizing(bool automatic); + bool automaticColumnResizing() const; + + /** + * Sets the width of the column for the given role. Note that + * the width only gets applied if KItemListHeader::automaticColumnResizing() + * has been turned off. + */ + void setColumnWidth(const QByteArray& role, qreal width); + qreal columnWidth(const QByteArray& role) const; + + /** + * Sets the widths of the columns for all roles. From a performance point of + * view calling this method should be preferred over several setColumnWidth() + * calls in case if the width of more than one column should be changed. + * Note that the widths only get applied if KItemListHeader::automaticColumnResizing() + * has been turned off. + */ + void setColumnWidths(const QHash& columnWidths); + +signals: + /** + * 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). + */ + void columnWidthChanged(const QByteArray& role, + qreal currentWidth, + qreal previousWidth); + +private: + KItemListHeader(KItemListView* listView); + +private: + KItemListView* m_view; + KItemListHeaderWidget* m_headerWidget; + + friend class KItemListView; // Constructs the KItemListHeader instance +}; + +#endif + + diff --git a/src/kitemviews/kitemlistheaderwidget.cpp b/src/kitemviews/kitemlistheaderwidget.cpp new file mode 100644 index 000000000..2f3058ac7 --- /dev/null +++ b/src/kitemviews/kitemlistheaderwidget.cpp @@ -0,0 +1,507 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kitemlistheaderwidget_p.h" + +#include +#include +#include "kitemmodelbase.h" + +#include +#include +#include +#include + +#include + +KItemListHeaderWidget::KItemListHeaderWidget(QGraphicsWidget* parent) : + QGraphicsWidget(parent), + m_automaticColumnResizing(false), + m_model(0), + m_columns(), + m_columnsWidths(), + m_hoveredRoleIndex(-1), + m_pressedRoleIndex(-1), + m_roleOperation(NoRoleOperation), + m_pressedMousePos(), + m_movingRole() +{ + m_movingRole.x = 0; + m_movingRole.xDec = 0; + m_movingRole.index = -1; + + setAcceptHoverEvents(true); + + QStyleOptionHeader option; + const QSize headerSize = style()->sizeFromContents(QStyle::CT_HeaderSection, &option, QSize()); + resize(0, headerSize.height()); +} + +KItemListHeaderWidget::~KItemListHeaderWidget() +{ +} + +void KItemListHeaderWidget::setModel(KItemModelBase* model) +{ + if (m_model == model) { + return; + } + + if (m_model) { + disconnect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), + this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); + disconnect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), + this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder))); + } + + m_model = model; + + if (m_model) { + connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), + this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); + connect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), + this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder))); + } +} + +KItemModelBase* KItemListHeaderWidget::model() const +{ + return m_model; +} + +void KItemListHeaderWidget::setAutomaticColumnResizing(bool automatic) +{ + m_automaticColumnResizing = automatic; +} + +bool KItemListHeaderWidget::automaticColumnResizing() const +{ + return m_automaticColumnResizing; +} + +void KItemListHeaderWidget::setColumns(const QList& roles) +{ + m_columns = roles; + update(); +} + +QList KItemListHeaderWidget::columns() const +{ + return m_columns; +} + +void KItemListHeaderWidget::setColumnWidth(const QByteArray& role, qreal width) +{ + const qreal minWidth = minimumColumnWidth(); + if (width < minWidth) { + width = minWidth; + } + + if (m_columnsWidths.value(role) != width) { + m_columnsWidths.insert(role, width); + update(); + } +} + +qreal KItemListHeaderWidget::columnWidth(const QByteArray& role) const +{ + return m_columnsWidths.value(role); +} + +qreal KItemListHeaderWidget::minimumColumnWidth() const +{ + QFontMetricsF fontMetrics(font()); + return fontMetrics.height() * 4; +} + +void KItemListHeaderWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + if (!m_model) { + return; + } + + // Draw roles + painter->setFont(font()); + painter->setPen(palette().text().color()); + + qreal x = 0; + int orderIndex = 0; + foreach (const QByteArray& role, m_columns) { + const qreal roleWidth = m_columnsWidths.value(role); + const QRectF rect(x, 0, roleWidth, size().height()); + paintRole(painter, role, rect, orderIndex, widget); + x += roleWidth; + ++orderIndex; + } + + // Draw background without roles + QStyleOption opt; + opt.init(widget); + opt.rect = QRect(x, 0, size().width() - x, size().height()); + opt.state |= QStyle::State_Horizontal; + style()->drawControl(QStyle::CE_HeaderEmptyArea, &opt, painter); + + if (!m_movingRole.pixmap.isNull()) { + Q_ASSERT(m_roleOperation == MoveRoleOperation); + painter->drawPixmap(m_movingRole.x, 0, m_movingRole.pixmap); + } +} + +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; + event->accept(); + } else { + event->ignore(); + } +} + +void KItemListHeaderWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + QGraphicsWidget::mouseReleaseEvent(event); + + if (m_pressedRoleIndex == -1) { + return; + } + + switch (m_roleOperation) { + case NoRoleOperation: { + // Only a click has been done and no moving or resizing has been started + const QByteArray sortRole = m_model->sortRole(); + const int sortRoleIndex = m_columns.indexOf(sortRole); + if (m_pressedRoleIndex == sortRoleIndex) { + // Toggle the sort order + const Qt::SortOrder previous = m_model->sortOrder(); + const Qt::SortOrder current = (m_model->sortOrder() == Qt::AscendingOrder) ? + Qt::DescendingOrder : Qt::AscendingOrder; + m_model->setSortOrder(current); + emit sortOrderChanged(current, previous); + } else { + // Change the sort role + const QByteArray previous = m_model->sortRole(); + const QByteArray current = m_columns[m_pressedRoleIndex]; + m_model->setSortRole(current); + emit sortRoleChanged(current, previous); + } + break; + } + + case MoveRoleOperation: + m_movingRole.pixmap = QPixmap(); + m_movingRole.x = 0; + m_movingRole.xDec = 0; + m_movingRole.index = -1; + break; + + default: + break; + } + + m_pressedRoleIndex = -1; + m_roleOperation = NoRoleOperation; + update(); + + QApplication::restoreOverrideCursor(); +} + +void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + QGraphicsWidget::mouseMoveEvent(event); + + switch (m_roleOperation) { + case NoRoleOperation: + if ((event->pos() - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) { + // A role gets dragged by the user. Create a pixmap of the role that will get + // synchronized on each furter mouse-move-event with the mouse-position. + m_roleOperation = MoveRoleOperation; + const int roleIndex = roleIndexAt(m_pressedMousePos); + m_movingRole.index = roleIndex; + if (roleIndex == 0) { + // 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 + // changed if KItemViews are used in a more generic way. + QApplication::setOverrideCursor(QCursor(Qt::ForbiddenCursor)); + } else { + m_movingRole.pixmap = createRolePixmap(roleIndex); + + qreal roleX = 0; + for (int i = 0; i < roleIndex; ++i) { + const QByteArray role = m_columns[i]; + roleX += m_columnsWidths.value(role); + } + + m_movingRole.xDec = event->pos().x() - roleX; + m_movingRole.x = roleX; + update(); + } + } + break; + + case ResizeRoleOperation: { + const QByteArray pressedRole = m_columns[m_pressedRoleIndex]; + + qreal previousWidth = m_columnsWidths.value(pressedRole); + qreal currentWidth = previousWidth; + currentWidth += event->pos().x() - event->lastPos().x(); + currentWidth = qMax(minimumColumnWidth(), currentWidth); + + m_columnsWidths.insert(pressedRole, currentWidth); + update(); + + emit columnWidthChanged(pressedRole, currentWidth, previousWidth); + 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 + // changed if KItemViews are used in a more generic way. + if (m_movingRole.index > 0) { + m_movingRole.x = event->pos().x() - m_movingRole.xDec; + update(); + + const int targetIndex = targetOfMovingRole(); + if (targetIndex > 0 && targetIndex != m_movingRole.index) { + const QByteArray role = m_columns[m_movingRole.index]; + const int previousIndex = m_movingRole.index; + m_movingRole.index = targetIndex; + emit columnMoved(role, targetIndex, previousIndex); + + m_movingRole.xDec = event->pos().x() - roleXPosition(role); + } + } + break; + } + + default: + break; + } +} + +void KItemListHeaderWidget::hoverEnterEvent(QGraphicsSceneHoverEvent* event) +{ + QGraphicsWidget::hoverEnterEvent(event); + updateHoveredRoleIndex(event->pos()); +} + +void KItemListHeaderWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + QGraphicsWidget::hoverLeaveEvent(event); + if (m_hoveredRoleIndex != -1) { + m_hoveredRoleIndex = -1; + update(); + } +} + +void KItemListHeaderWidget::hoverMoveEvent(QGraphicsSceneHoverEvent* event) +{ + QGraphicsWidget::hoverMoveEvent(event); + + const QPointF& pos = event->pos(); + updateHoveredRoleIndex(pos); + if (m_hoveredRoleIndex >= 0 && isAboveRoleGrip(pos, m_hoveredRoleIndex)) { + setCursor(Qt::SplitHCursor); + } else { + unsetCursor(); + } +} + +void KItemListHeaderWidget::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); + update(); +} + +void KItemListHeaderWidget::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); + update(); +} + +void KItemListHeaderWidget::paintRole(QPainter* painter, + const QByteArray& role, + const QRectF& rect, + int orderIndex, + QWidget* widget) const +{ + // The following code is based on the code from QHeaderView::paintSection(). + // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + QStyleOptionHeader option; + option.section = orderIndex; + option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal; + if (isEnabled()) { + option.state |= QStyle::State_Enabled; + } + if (window() && window()->isActiveWindow()) { + option.state |= QStyle::State_Active; + } + if (m_hoveredRoleIndex == orderIndex) { + option.state |= QStyle::State_MouseOver; + } + if (m_pressedRoleIndex == orderIndex) { + option.state |= QStyle::State_Sunken; + } + if (m_model->sortRole() == role) { + option.sortIndicator = (m_model->sortOrder() == Qt::AscendingOrder) ? + QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp; + } + option.rect = rect.toRect(); + + if (m_columns.count() == 1) { + option.position = QStyleOptionHeader::OnlyOneSection; + } else if (orderIndex == 0) { + option.position = QStyleOptionHeader::Beginning; + } else if (orderIndex == m_columns.count() - 1) { + option.position = QStyleOptionHeader::End; + } else { + 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); +} + +void KItemListHeaderWidget::updatePressedRoleIndex(const QPointF& pos) +{ + const int pressedIndex = roleIndexAt(pos); + if (m_pressedRoleIndex != pressedIndex) { + m_pressedRoleIndex = pressedIndex; + update(); + } +} + +void KItemListHeaderWidget::updateHoveredRoleIndex(const QPointF& pos) +{ + const int hoverIndex = roleIndexAt(pos); + if (m_hoveredRoleIndex != hoverIndex) { + m_hoveredRoleIndex = hoverIndex; + update(); + } +} + +int KItemListHeaderWidget::roleIndexAt(const QPointF& pos) const +{ + int index = -1; + + qreal x = 0; + foreach (const QByteArray& role, m_columns) { + ++index; + x += m_columnsWidths.value(role); + if (pos.x() <= x) { + break; + } + } + + return index; +} + +bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF& pos, int roleIndex) const +{ + qreal x = 0; + for (int i = 0; i <= roleIndex; ++i) { + const QByteArray role = m_columns[i]; + x += m_columnsWidths.value(role); + } + + const int grip = style()->pixelMetric(QStyle::PM_HeaderGripMargin); + return pos.x() >= (x - grip) && pos.x() <= x; +} + +QPixmap KItemListHeaderWidget::createRolePixmap(int roleIndex) const +{ + const QByteArray role = m_columns[roleIndex]; + const qreal roleWidth = m_columnsWidths.value(role); + const QRect rect(0, 0, roleWidth, size().height()); + + QImage image(rect.size(), QImage::Format_ARGB32_Premultiplied); + + QPainter painter(&image); + paintRole(&painter, role, rect, roleIndex); + + // Apply a highlighting-color + const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive; + QColor highlightColor = palette().color(group, QPalette::Highlight); + highlightColor.setAlpha(64); + painter.fillRect(rect, highlightColor); + + // Make the image transparent + painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); + painter.fillRect(0, 0, image.width(), image.height(), QColor(0, 0, 0, 192)); + + return QPixmap::fromImage(image); +} + +int KItemListHeaderWidget::targetOfMovingRole() const +{ + const int movingWidth = m_movingRole.pixmap.width(); + const int movingLeft = m_movingRole.x; + const int movingRight = movingLeft + movingWidth - 1; + + int targetIndex = 0; + qreal targetLeft = 0; + while (targetIndex < m_columns.count()) { + const QByteArray role = m_columns[targetIndex]; + const qreal targetWidth = m_columnsWidths.value(role); + const qreal targetRight = targetLeft + targetWidth - 1; + + const bool isInTarget = (targetWidth >= movingWidth && + movingLeft >= targetLeft && + movingRight <= targetRight) || + (targetWidth < movingWidth && + movingLeft <= targetLeft && + movingRight >= targetRight); + + if (isInTarget) { + return targetIndex; + } + + targetLeft += targetWidth; + ++targetIndex; + } + + return m_movingRole.index; +} + +qreal KItemListHeaderWidget::roleXPosition(const QByteArray& role) const +{ + qreal x = 0; + foreach (const QByteArray& visibleRole, m_columns) { + if (visibleRole == role) { + return x; + } + + x += m_columnsWidths.value(visibleRole); + } + + return -1; +} + +#include "kitemlistheaderwidget_p.moc" diff --git a/src/kitemviews/kitemlistheader_p.h b/src/kitemviews/kitemlistheaderwidget_p.h similarity index 78% rename from src/kitemviews/kitemlistheader_p.h rename to src/kitemviews/kitemlistheaderwidget_p.h index a0e54f5e3..ea8bb1ef9 100644 --- a/src/kitemviews/kitemlistheader_p.h +++ b/src/kitemviews/kitemlistheaderwidget_p.h @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ -#ifndef KITEMLISTHEADER_H -#define KITEMLISTHEADER_H +#ifndef KITEMLISTHEADERWIDGET_H +#define KITEMLISTHEADERWIDGET_H #include #include @@ -28,42 +28,48 @@ class KItemModelBase; /** - * @brief Header for KItemListView that shows the currently used roles. + * @brief Widget the implements the header for KItemListView showing the currently used roles. + * + * The widget is an internal API, the user of KItemListView may only access the + * class KItemListHeader. */ -class LIBDOLPHINPRIVATE_EXPORT KItemListHeader : public QGraphicsWidget +class LIBDOLPHINPRIVATE_EXPORT KItemListHeaderWidget : public QGraphicsWidget { Q_OBJECT public: - KItemListHeader(QGraphicsWidget* parent = 0); - virtual ~KItemListHeader(); + KItemListHeaderWidget(QGraphicsWidget* parent = 0); + virtual ~KItemListHeaderWidget(); void setModel(KItemModelBase* model); KItemModelBase* model() const; - void setVisibleRoles(const QList& roles); - QList visibleRoles() const; + void setAutomaticColumnResizing(bool automatic); + bool automaticColumnResizing() const; - void setVisibleRolesWidths(const QHash& rolesWidths); - QHash visibleRolesWidths() const; + void setColumns(const QList& roles); + QList columns() const; - qreal minimumRoleWidth() const; + void setColumnWidth(const QByteArray& role, qreal width); + qreal columnWidth(const QByteArray& role) const; + + qreal minimumColumnWidth() const; virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); signals: /** * Is emitted if the width of a visible role has been adjusted by the user with the mouse - * (no signal is emitted if KItemListHeader::setVisibleRoles() is invoked). + * (no signal is emitted if KItemListHeader::setVisibleRoleWidth() is invoked). */ - void visibleRoleWidthChanged(const QByteArray& role, - qreal currentWidth, - qreal previousWidth); + void columnWidthChanged(const QByteArray& role, + qreal currentWidth, + qreal previousWidth); /** - * Is emitted if the position of the visible role has been changed. + * Is emitted if the position of the column has been changed. */ - void visibleRoleMoved(const QByteArray& role, int currentIndex, int previousIndex); + void columnMoved(const QByteArray& role, int currentIndex, int previousIndex); /** * Is emitted if the user has changed the sort order by clicking on a @@ -130,9 +136,10 @@ private: MoveRoleOperation }; + bool m_automaticColumnResizing; KItemModelBase* m_model; - QList m_visibleRoles; - QHash m_visibleRolesWidths; + QList m_columns; + QHash m_columnsWidths; int m_hoveredRoleIndex; int m_pressedRoleIndex; diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index 0880687e1..ff1cbc39a 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -23,7 +23,8 @@ #include "kitemlistview.h" #include "kitemlistcontroller.h" -#include "kitemlistheader_p.h" +#include "kitemlistheader.h" +#include "kitemlistheaderwidget_p.h" #include "kitemlistrubberband_p.h" #include "kitemlistselectionmanager.h" #include "kitemlistsizehintresolver_p.h" @@ -61,13 +62,12 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : m_controller(0), m_model(0), m_visibleRoles(), - m_visibleRolesSizes(), - m_stretchedVisibleRolesSizes(), m_widgetCreator(0), m_groupHeaderCreator(0), m_styleOption(), m_visibleItems(), m_visibleGroups(), + m_columnWidthsCache(), m_visibleCells(), m_sizeHintResolver(0), m_layouter(0), @@ -83,7 +83,7 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : m_autoScrollIncrement(0), m_autoScrollTimer(0), m_header(0), - m_useHeaderWidths(false) + m_headerWidget(0) { setAcceptHoverEvents(true); @@ -103,6 +103,11 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : m_rubberBand = new KItemListRubberBand(this); connect(m_rubberBand, SIGNAL(activationChanged(bool)), this, SLOT(slotRubberBandActivationChanged(bool))); + + m_headerWidget = new KItemListHeaderWidget(this); + m_headerWidget->setVisible(false); + + m_header = new KItemListHeader(this); } KItemListView::~KItemListView() @@ -171,7 +176,15 @@ void KItemListView::setItemSize(const QSizeF& itemSize) } if (itemSize.isEmpty()) { - updateVisibleRolesSizes(); + if (m_headerWidget->automaticColumnResizing()) { + updateColumnWidthsCache(); + } else { + // Only apply the changed height and respect the header widths + // set by the user + const qreal currentWidth = m_layouter->itemSize().width(); + const QSizeF newSize(currentWidth, itemSize.height()); + m_layouter->setItemSize(newSize); + } } else { m_layouter->setItemSize(itemSize); } @@ -224,8 +237,8 @@ void KItemListView::setItemOffset(qreal offset) } m_layouter->setItemOffset(offset); - if (m_header) { - m_header->setPos(-offset, 0); + if (m_headerWidget->isVisible()) { + m_headerWidget->setPos(-offset, 0); } // Don't check whether the m_layoutTimer is active: Changing the @@ -258,7 +271,7 @@ void KItemListView::setVisibleRoles(const QList& roles) it.next(); KItemListWidget* widget = it.value(); widget->setVisibleRoles(roles); - widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes); + updateWidgetColumnWidths(widget); if (alternateBackgroundsChanged) { updateAlternateBackgroundForWidget(widget); } @@ -267,13 +280,12 @@ void KItemListView::setVisibleRoles(const QList& roles) m_sizeHintResolver->clearCache(); m_layouter->markAsDirty(); - if (m_header) { - m_header->setVisibleRoles(roles); - m_header->setVisibleRolesWidths(headerRolesWidths()); - m_useHeaderWidths = false; + if (m_headerWidget->isVisible()) { + m_headerWidget->setColumns(roles); + m_headerWidget->setAutomaticColumnResizing(true); } - updateVisibleRolesSizes(); + updateColumnWidthsCache(); doLayout(NoAnimation); onVisibleRolesChanged(roles, previousRoles); @@ -402,26 +414,18 @@ void KItemListView::setGeometry(const QRectF& rect) // Changing the geometry does not require to do an expensive // update of the visible-roles sizes, only the stretched sizes // need to be adjusted to the new size. - updateStretchedVisibleRolesSizes(); + updateColumnWidthsForHeader(); - if (m_useHeaderWidths) { + if (!m_headerWidget->automaticColumnResizing()) { QSizeF dynamicItemSize = m_layouter->itemSize(); if (m_itemSize.width() < 0) { - const qreal requiredWidth = visibleRolesSizesWidthSum(); + const qreal requiredWidth = columnWidthsSum(); if (newSize.width() > requiredWidth) { dynamicItemSize.setWidth(newSize.width()); } const qreal headerWidth = qMax(newSize.width(), requiredWidth); - m_header->resize(headerWidth, m_header->size().height()); - } - - if (m_itemSize.height() < 0) { - const qreal requiredHeight = visibleRolesSizesHeightSum(); - if (newSize.height() > requiredHeight) { - dynamicItemSize.setHeight(newSize.height()); - } - // TODO: KItemListHeader is not prepared for vertical alignment + m_headerWidget->resize(headerWidth, m_headerWidget->size().height()); } m_layouter->setItemSize(dynamicItemSize); @@ -513,10 +517,10 @@ QSizeF KItemListView::itemSizeHint(int index) const return itemSize(); } -QHash KItemListView::visibleRolesSizes(const KItemRangeList& itemRanges) const +QHash KItemListView::columnWidths(const KItemRangeList& itemRanges) const { Q_UNUSED(itemRanges); - return QHash(); + return QHash(); } void KItemListView::setSupportsItemExpanding(bool supportsExpanding) @@ -554,8 +558,8 @@ QRectF KItemListView::itemContextRect(int index) const void KItemListView::scrollToItem(int index) { QRectF viewGeometry = geometry(); - if (m_header) { - const qreal headerHeight = m_header->size().height(); + if (m_headerWidget->isVisible()) { + const qreal headerHeight = m_headerWidget->size().height(); viewGeometry.adjust(0, headerHeight, 0, 0); } const QRectF currentRect = itemRect(index); @@ -610,40 +614,52 @@ bool KItemListView::isTransactionActive() const return m_activeTransactions > 0; } -void KItemListView::setHeaderShown(bool show) +void KItemListView::setHeaderVisible(bool visible) { - if (show && !m_header) { - m_header = new KItemListHeader(this); - m_header->setPos(0, 0); - m_header->setModel(m_model); - m_header->setVisibleRoles(m_visibleRoles); - m_header->setVisibleRolesWidths(headerRolesWidths()); - m_header->setZValue(1); + if (visible && !m_headerWidget->isVisible()) { + m_headerWidget->setPos(0, 0); + m_headerWidget->setModel(m_model); + m_headerWidget->setColumns(m_visibleRoles); + m_headerWidget->setZValue(1); - connect(m_header, SIGNAL(visibleRoleWidthChanged(QByteArray,qreal,qreal)), - this, SLOT(slotVisibleRoleWidthChanged(QByteArray,qreal,qreal))); - connect(m_header, SIGNAL(visibleRoleMoved(QByteArray,int,int)), - this, SLOT(slotVisibleRoleMoved(QByteArray,int,int))); - connect(m_header, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), + connect(m_headerWidget, SIGNAL(columnWidthChanged(QByteArray,qreal,qreal)), + this, SLOT(slotHeaderColumnWidthChanged(QByteArray,qreal,qreal))); + connect(m_headerWidget, SIGNAL(columnMoved(QByteArray,int,int)), + this, SLOT(slotHeaderColumnMoved(QByteArray,int,int))); + connect(m_headerWidget, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), this, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder))); - connect(m_header, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), + connect(m_headerWidget, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), this, SIGNAL(sortRoleChanged(QByteArray,QByteArray))); - m_useHeaderWidths = false; + m_headerWidget->setAutomaticColumnResizing(true); + + m_layouter->setHeaderHeight(m_headerWidget->size().height()); + m_headerWidget->setVisible(true); + } else if (!visible && m_headerWidget->isVisible()) { + disconnect(m_headerWidget, SIGNAL(columnWidthChanged(QByteArray,qreal,qreal)), + this, SLOT(slotHeaderColumnWidthChanged(QByteArray,qreal,qreal))); + disconnect(m_headerWidget, SIGNAL(columnMoved(QByteArray,int,int)), + this, SLOT(slotHeaderColumnMoved(QByteArray,int,int))); + disconnect(m_headerWidget, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), + this, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder))); + disconnect(m_headerWidget, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), + this, SIGNAL(sortRoleChanged(QByteArray,QByteArray))); - m_layouter->setHeaderHeight(m_header->size().height()); - } else if (!show && m_header) { - delete m_header; - m_header = 0; - m_useHeaderWidths = false; m_layouter->setHeaderHeight(0); + m_headerWidget->setAutomaticColumnResizing(true); + m_headerWidget->setVisible(false); } } -bool KItemListView::isHeaderShown() const +bool KItemListView::isHeaderVisible() const { - return m_header != 0; + return m_headerWidget->isVisible(); +} + +KItemListHeader* KItemListView::header() const +{ + return m_header; } QPixmap KItemListView::createDragPixmap(const QSet& indexes) const @@ -803,7 +819,7 @@ QList KItemListView::visibleItemListWidgets() const void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) { - updateVisibleRolesSizes(itemRanges); + updateColumnWidthsCache(itemRanges); const bool hasMultipleRanges = (itemRanges.count() > 1); if (hasMultipleRanges) { @@ -902,7 +918,7 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) { - updateVisibleRolesSizes(); + updateColumnWidthsCache(); const bool hasMultipleRanges = (itemRanges.count() > 1); if (hasMultipleRanges) { @@ -1034,7 +1050,7 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, { const bool updateSizeHints = itemSizeHintUpdateRequired(roles); if (updateSizeHints) { - updateVisibleRolesSizes(itemRanges); + updateColumnWidthsCache(itemRanges); } foreach (const KItemRange& itemRange, itemRanges) { @@ -1206,46 +1222,36 @@ void KItemListView::slotRubberBandActivationChanged(bool active) update(); } -void KItemListView::slotVisibleRoleWidthChanged(const QByteArray& role, - qreal currentWidth, - qreal previousWidth) +void KItemListView::slotHeaderColumnWidthChanged(const QByteArray& role, + qreal currentWidth, + qreal previousWidth) { Q_UNUSED(previousWidth); - m_useHeaderWidths = true; + m_headerWidget->setAutomaticColumnResizing(false); - if (m_visibleRolesSizes.contains(role)) { - QSizeF roleSize = m_visibleRolesSizes.value(role); - roleSize.setWidth(currentWidth); - m_visibleRolesSizes.insert(role, roleSize); - m_stretchedVisibleRolesSizes.insert(role, roleSize); + if (m_columnWidthsCache.contains(role)) { + m_columnWidthsCache.insert(role, currentWidth); // Apply the new size to the layouter - QSizeF dynamicItemSize = m_itemSize; - if (dynamicItemSize.width() < 0) { - const qreal requiredWidth = visibleRolesSizesWidthSum(); - dynamicItemSize.setWidth(qMax(size().width(), requiredWidth)); - } - if (dynamicItemSize.height() < 0) { - const qreal requiredHeight = visibleRolesSizesHeightSum(); - dynamicItemSize.setHeight(qMax(size().height(), requiredHeight)); - } - + const qreal requiredWidth = columnWidthsSum(); + const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth), + m_itemSize.height()); m_layouter->setItemSize(dynamicItemSize); // Update the role sizes for all visible widgets QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); - it.value()->setVisibleRolesSizes(m_stretchedVisibleRolesSizes); + updateWidgetColumnWidths(it.value()); } doLayout(NoAnimation); } } -void KItemListView::slotVisibleRoleMoved(const QByteArray& role, - int currentIndex, - int previousIndex) +void KItemListView::slotHeaderColumnMoved(const QByteArray& role, + int currentIndex, + int previousIndex) { Q_ASSERT(m_visibleRoles[previousIndex] == role); @@ -1743,7 +1749,7 @@ void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType) void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index) { widget->setVisibleRoles(m_visibleRoles); - widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes); + updateWidgetColumnWidths(widget); widget->setStyleOption(m_styleOption); const KItemListSelectionManager* selectionManager = m_controller->selectionManager(); @@ -1907,22 +1913,39 @@ bool KItemListView::useAlternateBackgrounds() const return m_itemSize.isEmpty() && m_visibleRoles.count() > 1; } -QHash KItemListView::headerRolesWidths() const +void KItemListView::applyColumnWidthsFromHeader() { - QHash rolesWidths; + qreal roleWidthSum = 0; + foreach (const QByteArray& role, m_visibleRoles) { + const qreal width = m_headerWidget->columnWidth(role); + m_columnWidthsCache.insert(role, width); + roleWidthSum += width; + } + + // Apply the new size to the layouter + const QSizeF dynamicItemSize(qMax(size().width(), roleWidthSum), + m_itemSize.height()); + m_layouter->setItemSize(dynamicItemSize); - QHashIterator it(m_stretchedVisibleRolesSizes); + // Update the role sizes for all visible widgets + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); - rolesWidths.insert(it.key(), it.value().width()); + updateWidgetColumnWidths(it.value()); } + doLayout(NoAnimation); +} - return rolesWidths; +void KItemListView::updateWidgetColumnWidths(KItemListWidget* widget) +{ + foreach (const QByteArray& role, m_visibleRoles) { + widget->setColumnWidth(role, m_headerWidget->columnWidth(role)); + } } -void KItemListView::updateVisibleRolesSizes(const KItemRangeList& itemRanges) +void KItemListView::updateColumnWidthsCache(const KItemRangeList& itemRanges) { - if (!m_itemSize.isEmpty() || m_useHeaderWidths) { + if (!m_itemSize.isEmpty() || !m_headerWidget->automaticColumnResizing()) { return; } @@ -1933,37 +1956,35 @@ void KItemListView::updateVisibleRolesSizes(const KItemRangeList& itemRanges) } if (itemCount == rangesItemCount) { - m_visibleRolesSizes = visibleRolesSizes(itemRanges); - if (m_header) { + m_columnWidthsCache = columnWidths(itemRanges); + if (m_headerWidget->isVisible()) { // Assure the the sizes are not smaller than the minimum defined by the header - // TODO: Currently only implemented for a top-aligned header - const qreal minHeaderRoleWidth = m_header->minimumRoleWidth(); - QMutableHashIterator it (m_visibleRolesSizes); + const qreal minHeaderRoleWidth = m_headerWidget->minimumColumnWidth(); + QMutableHashIterator it (m_columnWidthsCache); while (it.hasNext()) { it.next(); - const QSizeF& size = it.value(); - if (size.width() < minHeaderRoleWidth) { - const QSizeF newSize(minHeaderRoleWidth, size.height()); - m_visibleRolesSizes.insert(it.key(), newSize); + const qreal width = it.value(); + if (width < minHeaderRoleWidth) { + m_columnWidthsCache.insert(it.key(), minHeaderRoleWidth); } } } } else { // Only a sub range of the roles need to be determined. - // The chances are good that the sizes of the sub ranges - // already fit into the available sizes and hence no + // The chances are good that the widths of the sub ranges + // already fit into the available widths and hence no // expensive update might be required. bool updateRequired = false; - const QHash updatedSizes = visibleRolesSizes(itemRanges); - QHashIterator it(updatedSizes); + const QHash updatedWidths = columnWidths(itemRanges); + QHashIterator it(updatedWidths); while (it.hasNext()) { it.next(); const QByteArray& role = it.key(); - const QSizeF& updatedSize = it.value(); - const QSizeF currentSize = m_visibleRolesSizes.value(role); - if (updatedSize.width() > currentSize.width() || updatedSize.height() > currentSize.height()) { - m_visibleRolesSizes.insert(role, updatedSize); + const qreal updatedWidth = it.value(); + const qreal currentWidth = m_columnWidthsCache.value(role); + if (updatedWidth > currentWidth) { + m_columnWidthsCache.insert(role, updatedWidth); updateRequired = true; } } @@ -1975,10 +1996,10 @@ void KItemListView::updateVisibleRolesSizes(const KItemRangeList& itemRanges) } } - updateStretchedVisibleRolesSizes(); + updateColumnWidthsForHeader(); } -void KItemListView::updateVisibleRolesSizes() +void KItemListView::updateColumnWidthsCache() { if (!m_model) { return; @@ -1986,13 +2007,13 @@ void KItemListView::updateVisibleRolesSizes() const int itemCount = m_model->count(); if (itemCount > 0) { - updateVisibleRolesSizes(KItemRangeList() << KItemRange(0, itemCount)); + updateColumnWidthsCache(KItemRangeList() << KItemRange(0, itemCount)); } } -void KItemListView::updateStretchedVisibleRolesSizes() +void KItemListView::updateColumnWidthsForHeader() { - if (!m_itemSize.isEmpty() || m_useHeaderWidths || m_visibleRoles.isEmpty()) { + if (!m_itemSize.isEmpty() || !m_headerWidget->automaticColumnResizing() || m_visibleRoles.isEmpty()) { return; } @@ -2000,74 +2021,64 @@ void KItemListView::updateStretchedVisibleRolesSizes() // visible role sizes and apply them to the layouter. If the // size does not use the available view-size the size of the // first role will get stretched. - m_stretchedVisibleRolesSizes = m_visibleRolesSizes; + + foreach (const QByteArray& role, m_visibleRoles) { + m_headerWidget->setColumnWidth(role, m_columnWidthsCache.value(role)); + } + const QByteArray role = m_visibleRoles.first(); - QSizeF firstRoleSize = m_stretchedVisibleRolesSizes.value(role); + qreal firstColumnWidth = m_columnWidthsCache.value(role); QSizeF dynamicItemSize = m_itemSize; if (dynamicItemSize.width() <= 0) { - const qreal requiredWidth = visibleRolesSizesWidthSum(); + const qreal requiredWidth = columnWidthsSum(); const qreal availableWidth = size().width(); if (requiredWidth != availableWidth) { // Stretch the first role to use the whole remaining width - firstRoleSize.rwidth() += availableWidth - requiredWidth; + firstColumnWidth += availableWidth - requiredWidth; // TODO: A proper calculation of the minimum width depends on the implementation // of KItemListWidget. Probably a kind of minimum size-hint should be introduced // later. const qreal minWidth = m_styleOption.iconSize * 2 + 200; - if (firstRoleSize.width() < minWidth) { - firstRoleSize.rwidth() = minWidth; + if (firstColumnWidth < minWidth) { + firstColumnWidth = minWidth; } - m_stretchedVisibleRolesSizes.insert(role, firstRoleSize); + + m_headerWidget->setColumnWidth(role, firstColumnWidth); } dynamicItemSize.rwidth() = qMax(requiredWidth, availableWidth); } - // TODO: A dynamic item height (dynamicItemSize.height() <= 0) - // is not handled currently - m_layouter->setItemSize(dynamicItemSize); - if (m_header) { - m_header->setVisibleRolesWidths(headerRolesWidths()); - m_header->resize(dynamicItemSize.width(), m_header->size().height()); + if (m_headerWidget->isVisible()) { + m_headerWidget->resize(dynamicItemSize.width(), m_headerWidget->size().height()); } // Update the role sizes for all visible widgets QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); - it.value()->setVisibleRolesSizes(m_stretchedVisibleRolesSizes); + updateWidgetColumnWidths(it.value()); } } -qreal KItemListView::visibleRolesSizesWidthSum() const +qreal KItemListView::columnWidthsSum() const { qreal widthSum = 0; - QHashIterator it(m_visibleRolesSizes); + QHashIterator it(m_columnWidthsCache); while (it.hasNext()) { it.next(); - widthSum += it.value().width(); + widthSum += it.value(); } return widthSum; } -qreal KItemListView::visibleRolesSizesHeightSum() const -{ - qreal heightSum = 0; - QHashIterator it(m_visibleRolesSizes); - while (it.hasNext()) { - it.next(); - heightSum += it.value().height(); - } - return heightSum; -} - QRectF KItemListView::headerBoundaries() const { - return m_header ? m_header->geometry() : QRectF(); + return m_headerWidget->isVisible() ? m_headerWidget->geometry() : QRectF(); } bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize, diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h index bbdb4081c..70ba03061 100644 --- a/src/kitemviews/kitemlistview.h +++ b/src/kitemviews/kitemlistview.h @@ -36,6 +36,7 @@ class KItemListController; class KItemListGroupHeaderCreatorBase; class KItemListHeader; +class KItemListHeaderWidget; class KItemListSizeHintResolver; class KItemListRubberBand; class KItemListViewAnimation; @@ -192,13 +193,13 @@ public: virtual QSizeF itemSizeHint(int index) const; /** - * @param itemRanges Items that must be checked for getting the visible roles sizes. - * @return The size of each visible role in case if KItemListView::itemSize() - * is empty. This allows to have dynamic but equal role sizes between - * all items, like used in the classic "table-views". Per default an - * empty hash is returned. + * @param itemRanges Items that must be checked for getting the widths of columns. + * @return The width of the column of each visible role. The width will + * be respected if the width of the item size is <= 0 (see + * KItemListView::setItemSize()). Per default an empty hash + * is returned. */ - virtual QHash visibleRolesSizes(const KItemRangeList& itemRanges) const; + virtual QHash columnWidths(const KItemRangeList& itemRanges) const; /** * If set to true, items having child-items can be expanded to show the child-items as @@ -253,12 +254,18 @@ public: bool isTransactionActive() const; /** - * Turns on the header if \p show is true. Per default the - * header is not shown. Usually the header is turned on when + * Turns on the header if \p visible is true. Per default the + * header is not visible. Usually the header is turned on when * showing a classic "table-view" to describe the shown columns. */ - void setHeaderShown(bool show); - bool isHeaderShown() const; + void setHeaderVisible(bool visible); + bool isHeaderVisible() const; + + /** + * @return Header of the list. The header is also available if it is not shown + * (see KItemListView::setHeaderShown()). + */ + KItemListHeader* header() const; /** * @return Pixmap that is used for a drag operation based on the @@ -364,22 +371,22 @@ private slots: void slotRubberBandActivationChanged(bool active); /** - * Is invoked if the visible role-width of one role in the header has + * Is invoked if the column-width of one role in the header has * been changed by the user. It is remembered that the user has modified * the role-width, so that it won't be changed anymore automatically to * calculate an optimized width. */ - void slotVisibleRoleWidthChanged(const QByteArray& role, - qreal currentWidth, - qreal previousWidth); + void slotHeaderColumnWidthChanged(const QByteArray& role, + qreal currentWidth, + qreal previousWidth); /** - * Is invoked if a visible role has been moved by the user. Applies + * Is invoked if a column has been moved by the user. Applies * the moved role to the view. */ - void slotVisibleRoleMoved(const QByteArray& role, - int currentIndex, - int previousIndex); + void slotHeaderColumnMoved(const QByteArray& role, + int currentIndex, + int previousIndex); /** * Triggers the autoscrolling if autoScroll() is enabled by checking the @@ -517,10 +524,13 @@ private: */ bool useAlternateBackgrounds() const; + void applyColumnWidthsFromHeader(); + /** - * @return The widths of each visible role that is shown in the KItemListHeader. + * Applies the roles-sizes from m_stretchedVisibleRolesSizes + * to \a widget. */ - QHash headerRolesWidths() const; + void updateWidgetColumnWidths(KItemListWidget* widget); /** * Updates m_visibleRolesSizes by calling KItemListView::visibleRolesSizes(). @@ -528,29 +538,23 @@ private: * are used (see m_useHeaderWidths). Also m_strechedVisibleRolesSizes will be adjusted * to respect the available view-size. */ - void updateVisibleRolesSizes(const KItemRangeList& itemRanges); + void updateColumnWidthsCache(const KItemRangeList& itemRanges); /** * Convenience method for updateVisibleRoleSizes(KItemRangeList() << KItemRange(0, m_model->count()). */ - void updateVisibleRolesSizes(); + void updateColumnWidthsCache(); /** - * Updates m_stretchedVisibleRolesSizes based on m_visibleRolesSizes and the available - * view-size. Nothing will be done if m_itemRect is not empty or custom header-widths - * are used (see m_useHeaderWidths). + * Updates the column widhts of the header based on m_columnWidthsCache and the available + * view-size. */ - void updateStretchedVisibleRolesSizes(); + void updateColumnWidthsForHeader(); /** - * @return Sum of the widths of all visible roles. + * @return Sum of the widths of all columns. */ - qreal visibleRolesSizesWidthSum() const; - - /** - * @return Sum of the heights of all visible roles. - */ - qreal visibleRolesSizesHeightSum() const; + qreal columnWidthsSum() const; /** * @return Boundaries of the header. An empty rectangle is returned @@ -634,8 +638,6 @@ private: KItemListController* m_controller; KItemModelBase* m_model; QList m_visibleRoles; - QHash m_visibleRolesSizes; - QHash m_stretchedVisibleRolesSizes; KItemListWidgetCreatorBase* m_widgetCreator; KItemListGroupHeaderCreatorBase* m_groupHeaderCreator; KItemListStyleOption m_styleOption; @@ -643,6 +645,8 @@ private: QHash m_visibleItems; QHash m_visibleGroups; + QHash m_columnWidthsCache; // Cache for columnWidths() result + struct Cell { Cell() : column(-1), row(-1) {} @@ -671,9 +675,10 @@ private: QTimer* m_autoScrollTimer; KItemListHeader* m_header; - bool m_useHeaderWidths; + KItemListHeaderWidget* m_headerWidget; friend class KItemListContainer; // Accesses scrollBarRequired() + friend class KItemListHeader; // Accesses m_headerWidget friend class KItemListController; friend class KItemListControllerTest; }; diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp index 951fb396c..b91e87167 100644 --- a/src/kitemviews/kitemlistwidget.cpp +++ b/src/kitemviews/kitemlistwidget.cpp @@ -44,7 +44,7 @@ KItemListWidget::KItemListWidget(QGraphicsItem* parent) : m_enabledSelectionToggle(false), m_data(), m_visibleRoles(), - m_visibleRolesSizes(), + m_columnWidths(), m_styleOption(), m_siblingsInfo(), m_hoverOpacity(0), @@ -177,18 +177,20 @@ QList KItemListWidget::visibleRoles() const return m_visibleRoles; } -void KItemListWidget::setVisibleRolesSizes(const QHash rolesSizes) -{ - const QHash previousRolesSizes = m_visibleRolesSizes; - m_visibleRolesSizes = rolesSizes; - visibleRolesSizesChanged(rolesSizes, previousRolesSizes); - update(); +void KItemListWidget::setColumnWidth(const QByteArray& role, qreal width) +{ + if (m_columnWidths.value(role) != width) { + const qreal previousWidth = width; + m_columnWidths.insert(role, width); + columnWidthChanged(role, width, previousWidth); + update(); + } } -QHash KItemListWidget::visibleRolesSizes() const +qreal KItemListWidget::columnWidth(const QByteArray& role) const { - return m_visibleRolesSizes; + return m_columnWidths.value(role); } void KItemListWidget::setStyleOption(const KItemListStyleOption& option) @@ -352,9 +354,11 @@ void KItemListWidget::visibleRolesChanged(const QList& current, Q_UNUSED(previous); } -void KItemListWidget::visibleRolesSizesChanged(const QHash& current, - const QHash& previous) +void KItemListWidget::columnWidthChanged(const QByteArray& role, + qreal current, + qreal previous) { + Q_UNUSED(role); Q_UNUSED(current); Q_UNUSED(previous); } diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h index 80f944748..66d96d449 100644 --- a/src/kitemviews/kitemlistwidget.h +++ b/src/kitemviews/kitemlistwidget.h @@ -65,8 +65,12 @@ public: void setVisibleRoles(const QList& roles); QList visibleRoles() const; - void setVisibleRolesSizes(const QHash rolesSizes); - QHash visibleRolesSizes() const; + /** + * Sets the width of a role that should be used if the alignment of the content + * should be done in columns. + */ + void setColumnWidth(const QByteArray& role, qreal width); + qreal columnWidth(const QByteArray& role) const; void setStyleOption(const KItemListStyleOption& option); const KItemListStyleOption& styleOption() const; @@ -133,7 +137,7 @@ public: protected: virtual void dataChanged(const QHash& current, const QSet& roles = QSet()); virtual void visibleRolesChanged(const QList& current, const QList& previous); - virtual void visibleRolesSizesChanged(const QHash& current, const QHash& previous); + virtual void columnWidthChanged(const QByteArray& role, qreal current, qreal previous); virtual void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous); virtual void currentChanged(bool current); virtual void selectedChanged(bool selected); @@ -168,7 +172,7 @@ private: bool m_enabledSelectionToggle; QHash m_data; QList m_visibleRoles; - QHash m_visibleRolesSizes; + QHash m_columnWidths; KItemListStyleOption m_styleOption; QBitArray m_siblingsInfo; diff --git a/src/settings/dolphin_directoryviewpropertysettings.kcfg b/src/settings/dolphin_directoryviewpropertysettings.kcfg index 3f2c46a9c..fced36b52 100644 --- a/src/settings/dolphin_directoryviewpropertysettings.kcfg +++ b/src/settings/dolphin_directoryviewpropertysettings.kcfg @@ -62,6 +62,11 @@ + + + + + The last time these properties were changed by the user. diff --git a/src/views/dolphinitemlistcontainer.cpp b/src/views/dolphinitemlistcontainer.cpp index 1ece52093..822439948 100644 --- a/src/views/dolphinitemlistcontainer.cpp +++ b/src/views/dolphinitemlistcontainer.cpp @@ -133,17 +133,17 @@ void DolphinItemListContainer::setItemLayout(KFileItemListView::Layout layout) switch (layout) { case KFileItemListView::IconsLayout: m_fileItemListView->setScrollOrientation(Qt::Vertical); - m_fileItemListView->setHeaderShown(false); + m_fileItemListView->setHeaderVisible(false); m_fileItemListView->setSupportsItemExpanding(false); break; case KFileItemListView::DetailsLayout: m_fileItemListView->setScrollOrientation(Qt::Vertical); - m_fileItemListView->setHeaderShown(true); + m_fileItemListView->setHeaderVisible(true); m_fileItemListView->setSupportsItemExpanding(DetailsModeSettings::expandableFolders()); break; case KFileItemListView::CompactLayout: m_fileItemListView->setScrollOrientation(Qt::Horizontal); - m_fileItemListView->setHeaderShown(false); + m_fileItemListView->setHeaderVisible(false); m_fileItemListView->setSupportsItemExpanding(false); break; default: diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index f3d386b3b..241f24fd4 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -161,6 +162,8 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) : this, SLOT(slotSortRoleChangedByHeader(QByteArray,QByteArray))); connect(view, SIGNAL(visibleRolesChanged(QList,QList)), this, SLOT(slotVisibleRolesChangedByHeader(QList,QList))); + connect(view->header(), SIGNAL(columnWidthChanged(QByteArray,qreal,qreal)), + this, SLOT(slotHeaderColumnWidthChanged(QByteArray,qreal,qreal))); KItemListSelectionManager* selectionManager = controller->selectionManager(); connect(selectionManager, SIGNAL(selectionChanged(QSet,QSet)), @@ -774,7 +777,9 @@ void DolphinView::slotViewContextMenuRequested(const QPointF& pos) void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos) { - QWeakPointer menu = new KMenu(QApplication::activeWindow()); + ViewProperties props(url()); + + QPointer menu = new KMenu(QApplication::activeWindow()); KItemListView* view = m_container->controller()->view(); const QSet visibleRolesSet = view->visibleRoles().toSet(); @@ -793,11 +798,11 @@ void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos) const QString text = fileItemModel()->roleDescription(info.role); QAction* action = 0; if (info.group.isEmpty()) { - action = menu.data()->addAction(text); + action = menu->addAction(text); } else { if (!groupMenu || info.group != groupName) { groupName = info.group; - groupMenu = menu.data()->addMenu(groupName); + groupMenu = menu->addMenu(groupName); } action = groupMenu->addAction(text); @@ -808,24 +813,84 @@ void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos) action->setData(info.role); } - QAction* action = menu.data()->exec(pos.toPoint()); - if (action) { - // Show or hide the selected role - const QByteArray selectedRole = action->data().toByteArray(); + menu->addSeparator(); + + QActionGroup* widthsGroup = new QActionGroup(menu); + const bool autoColumnWidths = props.headerColumnWidths().isEmpty(); + + QAction* autoAdjustWidthsAction = menu->addAction(i18nc("@action:inmenu", "Automatic Column Widths")); + autoAdjustWidthsAction->setCheckable(true); + autoAdjustWidthsAction->setChecked(autoColumnWidths); + autoAdjustWidthsAction->setActionGroup(widthsGroup); + + QAction* customWidthsAction = menu->addAction(i18nc("@action:inmenu", "Custom Column Widths")); + customWidthsAction->setCheckable(true); + customWidthsAction->setChecked(!autoColumnWidths); + customWidthsAction->setActionGroup(widthsGroup); + + QAction* action = menu->exec(pos.toPoint()); + if (menu && action) { + if (action == autoAdjustWidthsAction) { + // Clear the column-widths from the viewproperties and turn on + // the automatic resizing of the columns + props.setHeaderColumnWidths(QList()); + KItemListHeader* header = m_container->controller()->view()->header(); + header->setAutomaticColumnResizing(true); + } else if (action == customWidthsAction) { + // Apply the current column-widths as custom column-widths and turn + // off the automatic resizing of the columns + const KItemListView* view = m_container->controller()->view(); + KItemListHeader* header = view->header(); + + QList columnWidths; + foreach (const QByteArray& role, view->visibleRoles()) { + columnWidths.append(header->columnWidth(role)); + } - ViewProperties props(url()); - QList visibleRoles = view->visibleRoles(); - if (action->isChecked()) { - visibleRoles.append(selectedRole); + props.setHeaderColumnWidths(columnWidths); + header->setAutomaticColumnResizing(false); } else { - visibleRoles.removeOne(selectedRole); + // Show or hide the selected role + const QByteArray selectedRole = action->data().toByteArray(); + + QList visibleRoles = view->visibleRoles(); + if (action->isChecked()) { + visibleRoles.append(selectedRole); + } else { + visibleRoles.removeOne(selectedRole); + } + + view->setVisibleRoles(visibleRoles); + props.setVisibleRoles(visibleRoles); } + } - view->setVisibleRoles(visibleRoles); - props.setVisibleRoles(visibleRoles); + delete menu; +} + +void DolphinView::slotHeaderColumnWidthChanged(const QByteArray& role, qreal current, qreal previous) +{ + Q_UNUSED(previous); + + const QList visibleRoles = m_container->visibleRoles(); + + ViewProperties props(url()); + QList columnWidths = props.headerColumnWidths(); + if (columnWidths.count() != visibleRoles.count()) { + columnWidths.clear(); + columnWidths.reserve(visibleRoles.count()); + const KItemListHeader* header = m_container->controller()->view()->header(); + foreach (const QByteArray& role, visibleRoles) { + const int width = header->columnWidth(role); + columnWidths.append(width); + } } - delete menu.data(); + const int roleIndex = visibleRoles.indexOf(role); + Q_ASSERT(roleIndex >= 0 && roleIndex < columnWidths.count()); + columnWidths[roleIndex] = current; + + props.setHeaderColumnWidths(columnWidths); } void DolphinView::slotItemHovered(int index) @@ -1294,6 +1359,24 @@ void DolphinView::applyViewProperties() } } + KItemListView* itemListView = m_container->controller()->view(); + if (itemListView->isHeaderVisible()) { + KItemListHeader* header = itemListView->header(); + const QList headerColumnWidths = props.headerColumnWidths(); + const int rolesCount = m_visibleRoles.count(); + if (headerColumnWidths.count() == rolesCount) { + header->setAutomaticColumnResizing(false); + + QHash columnWidths; + for (int i = 0; i < rolesCount; ++i) { + columnWidths.insert(m_visibleRoles[i], headerColumnWidths[i]); + } + header->setColumnWidths(columnWidths); + } else { + header->setAutomaticColumnResizing(true); + } + } + m_container->endTransaction(); } diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h index 48d0646c4..71128569a 100644 --- a/src/views/dolphinview.h +++ b/src/views/dolphinview.h @@ -543,6 +543,7 @@ private slots: void slotItemContextMenuRequested(int index, const QPointF& pos); void slotViewContextMenuRequested(const QPointF& pos); void slotHeaderContextMenuRequested(const QPointF& pos); + void slotHeaderColumnWidthChanged(const QByteArray& role, qreal current, qreal previous); void slotItemHovered(int index); void slotItemUnhovered(int index); void slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event); diff --git a/src/views/viewproperties.cpp b/src/views/viewproperties.cpp index 8588bb238..83958ad00 100644 --- a/src/views/viewproperties.cpp +++ b/src/views/viewproperties.cpp @@ -292,6 +292,19 @@ QList ViewProperties::visibleRoles() const return roles; } +void ViewProperties::setHeaderColumnWidths(const QList& widths) +{ + if (m_node->headerColumnWidths() != widths) { + m_node->setHeaderColumnWidths(widths); + update(); + } +} + +QList ViewProperties::headerColumnWidths() const +{ + return m_node->headerColumnWidths(); +} + void ViewProperties::setDirProperties(const ViewProperties& props) { setViewMode(props.viewMode()); @@ -302,6 +315,7 @@ void ViewProperties::setDirProperties(const ViewProperties& props) setSortOrder(props.sortOrder()); setSortFoldersFirst(props.sortFoldersFirst()); setVisibleRoles(props.visibleRoles()); + setHeaderColumnWidths(props.headerColumnWidths()); } void ViewProperties::setAutoSaveEnabled(bool autoSave) diff --git a/src/views/viewproperties.h b/src/views/viewproperties.h index 96a5515ef..303c04227 100644 --- a/src/views/viewproperties.h +++ b/src/views/viewproperties.h @@ -88,6 +88,9 @@ public: */ QList visibleRoles() const; + void setHeaderColumnWidths(const QList& widths); + QList headerColumnWidths() const; + /** * Sets the directory properties view mode, show preview, * show hidden files, sorting and sort order like -- 2.47.3