diff options
| -rw-r--r-- | src/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemlistview.cpp | 12 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemlistview.h | 2 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemlistwidget.cpp | 16 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemlistwidget.h | 2 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistheader.cpp | 483 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistheader.h | 89 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistheaderwidget.cpp | 507 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistheaderwidget_p.h (renamed from src/kitemviews/kitemlistheader_p.h) | 45 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistview.cpp | 283 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistview.h | 77 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistwidget.cpp | 26 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistwidget.h | 12 | ||||
| -rw-r--r-- | src/settings/dolphin_directoryviewpropertysettings.kcfg | 5 | ||||
| -rw-r--r-- | src/views/dolphinitemlistcontainer.cpp | 6 | ||||
| -rw-r--r-- | src/views/dolphinview.cpp | 113 | ||||
| -rw-r--r-- | src/views/dolphinview.h | 1 | ||||
| -rw-r--r-- | src/views/viewproperties.cpp | 14 | ||||
| -rw-r--r-- | src/views/viewproperties.h | 3 |
19 files changed, 1006 insertions, 691 deletions
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<QByteArray, QSizeF> KFileItemListView::visibleRolesSizes(const KItemRangeList& itemRanges) const +QHash<QByteArray, qreal> KFileItemListView::columnWidths(const KItemRangeList& itemRanges) const { QElapsedTimer timer; timer.start(); - QHash<QByteArray, QSizeF> sizes; + QHash<QByteArray, qreal> widths; int calculatedItemCount = 0; bool maxTimeExceeded = false; @@ -208,10 +208,10 @@ QHash<QByteArray, QSizeF> 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<QByteArray, QSizeF> 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<int>& 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<QByteArray, QSizeF> visibleRolesSizes(const KItemRangeList& itemRanges) const; + virtual QHash<QByteArray, qreal> columnWidths(const KItemRangeList& itemRanges) const; /** @reimp */ virtual QPixmap createDragPixmap(const QSet<int>& 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<QByteArray>& current, m_dirtyLayout = true; } -void KFileItemListWidget::visibleRolesSizesChanged(const QHash<QByteArray, QSizeF>& current, - const QHash<QByteArray, QSizeF>& 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<QByteArray, QVariant>& current, const QSet<QByteArray>& roles = QSet<QByteArray>()); virtual void visibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous); - virtual void visibleRolesSizesChanged(const QHash<QByteArray, QSizeF>& current, const QHash<QByteArray, QSizeF>& 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 <[email protected]> * + * Copyright (C) 2012 by Peter Penz <[email protected]> * * * * 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 <KAction> -#include <KMenu> -#include "kitemmodelbase.h" - -#include <QApplication> -#include <QGraphicsSceneHoverEvent> -#include <QPainter> -#include <QStyleOptionHeader> - -#include <KDebug> - -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<QByteArray>& roles) -{ - m_visibleRoles = roles; - update(); -} - -QList<QByteArray> KItemListHeader::visibleRoles() const -{ - return m_visibleRoles; -} - -void KItemListHeader::setVisibleRolesWidths(const QHash<QByteArray, qreal>& rolesWidths) -{ - m_visibleRolesWidths = rolesWidths; - - // Assure that no width is smaller than the minimum allowed width - const qreal minWidth = minimumRoleWidth(); - QMutableHashIterator<QByteArray, qreal> it(m_visibleRolesWidths); - while (it.hasNext()) { - it.next(); - if (it.value() < minWidth) { - m_visibleRolesWidths.insert(it.key(), minWidth); - } - } - - update(); -} - -QHash<QByteArray, qreal> 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<QByteArray, qreal>& 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 <[email protected]> * + * * + * 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 <libdolphin_export.h> +#include <QHash> +#include <QObject> + +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<QByteArray, qreal>& 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 <[email protected]> * + * * + * 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 <KAction> +#include <KMenu> +#include "kitemmodelbase.h" + +#include <QApplication> +#include <QGraphicsSceneHoverEvent> +#include <QPainter> +#include <QStyleOptionHeader> + +#include <KDebug> + +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<QByteArray>& roles) +{ + m_columns = roles; + update(); +} + +QList<QByteArray> 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 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 <libdolphin_export.h> #include <QGraphicsWidget> @@ -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<QByteArray>& roles); - QList<QByteArray> visibleRoles() const; + void setAutomaticColumnResizing(bool automatic); + bool automaticColumnResizing() const; - void setVisibleRolesWidths(const QHash<QByteArray, qreal>& rolesWidths); - QHash<QByteArray, qreal> visibleRolesWidths() const; + void setColumns(const QList<QByteArray>& roles); + QList<QByteArray> 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<QByteArray> m_visibleRoles; - QHash<QByteArray, qreal> m_visibleRolesWidths; + QList<QByteArray> m_columns; + QHash<QByteArray, qreal> 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<QByteArray>& 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<QByteArray>& 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<QByteArray, QSizeF> KItemListView::visibleRolesSizes(const KItemRangeList& itemRanges) const +QHash<QByteArray, qreal> KItemListView::columnWidths(const KItemRangeList& itemRanges) const { Q_UNUSED(itemRanges); - return QHash<QByteArray, QSizeF>(); + return QHash<QByteArray, qreal>(); } 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<int>& indexes) const @@ -803,7 +819,7 @@ QList<KItemListWidget*> 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<int, KItemListWidget*> 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<QByteArray, qreal> KItemListView::headerRolesWidths() const +void KItemListView::applyColumnWidthsFromHeader() { - QHash<QByteArray, qreal> 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<QByteArray, QSizeF> it(m_stretchedVisibleRolesSizes); + // Update the role sizes for all visible widgets + QHashIterator<int, KItemListWidget*> 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<QByteArray, QSizeF> it (m_visibleRolesSizes); + const qreal minHeaderRoleWidth = m_headerWidget->minimumColumnWidth(); + QMutableHashIterator<QByteArray, qreal> 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<QByteArray, QSizeF> updatedSizes = visibleRolesSizes(itemRanges); - QHashIterator<QByteArray, QSizeF> it(updatedSizes); + const QHash<QByteArray, qreal> updatedWidths = columnWidths(itemRanges); + QHashIterator<QByteArray, qreal> 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<int, KItemListWidget*> 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<QByteArray, QSizeF> it(m_visibleRolesSizes); + QHashIterator<QByteArray, qreal> 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<QByteArray, QSizeF> 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<QByteArray, QSizeF> visibleRolesSizes(const KItemRangeList& itemRanges) const; + virtual QHash<QByteArray, qreal> 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<QByteArray, qreal> 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<QByteArray> m_visibleRoles; - QHash<QByteArray, QSizeF> m_visibleRolesSizes; - QHash<QByteArray, QSizeF> m_stretchedVisibleRolesSizes; KItemListWidgetCreatorBase* m_widgetCreator; KItemListGroupHeaderCreatorBase* m_groupHeaderCreator; KItemListStyleOption m_styleOption; @@ -643,6 +645,8 @@ private: QHash<int, KItemListWidget*> m_visibleItems; QHash<KItemListWidget*, KItemListGroupHeader*> m_visibleGroups; + QHash<QByteArray, qreal> 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<QByteArray> KItemListWidget::visibleRoles() const return m_visibleRoles; } -void KItemListWidget::setVisibleRolesSizes(const QHash<QByteArray, QSizeF> rolesSizes) -{ - const QHash<QByteArray, QSizeF> 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<QByteArray, QSizeF> 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<QByteArray>& current, Q_UNUSED(previous); } -void KItemListWidget::visibleRolesSizesChanged(const QHash<QByteArray, QSizeF>& current, - const QHash<QByteArray, QSizeF>& 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<QByteArray>& roles); QList<QByteArray> visibleRoles() const; - void setVisibleRolesSizes(const QHash<QByteArray, QSizeF> rolesSizes); - QHash<QByteArray, QSizeF> 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<QByteArray, QVariant>& current, const QSet<QByteArray>& roles = QSet<QByteArray>()); virtual void visibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous); - virtual void visibleRolesSizesChanged(const QHash<QByteArray, QSizeF>& current, const QHash<QByteArray, QSizeF>& 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<QByteArray, QVariant> m_data; QList<QByteArray> m_visibleRoles; - QHash<QByteArray, QSizeF> m_visibleRolesSizes; + QHash<QByteArray, qreal> 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 @@ <default></default> </entry> + <entry name="HeaderColumnWidths" type="IntList"> + <label context="@label">Header column widths</label> + <default></default> + </entry> + <entry name="Timestamp" type="DateTime" > <label context="@label">Properties last changed</label> <whatsthis context="@info:whatsthis">The last time these properties were changed by the user.</whatsthis> 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 <KLocale> #include <kitemviews/kfileitemmodel.h> #include <kitemviews/kfileitemlistview.h> +#include <kitemviews/kitemlistheader.h> #include <kitemviews/kitemlistselectionmanager.h> #include <kitemviews/kitemlistview.h> #include <kitemviews/kitemlistcontroller.h> @@ -161,6 +162,8 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) : this, SLOT(slotSortRoleChangedByHeader(QByteArray,QByteArray))); connect(view, SIGNAL(visibleRolesChanged(QList<QByteArray>,QList<QByteArray>)), this, SLOT(slotVisibleRolesChangedByHeader(QList<QByteArray>,QList<QByteArray>))); + connect(view->header(), SIGNAL(columnWidthChanged(QByteArray,qreal,qreal)), + this, SLOT(slotHeaderColumnWidthChanged(QByteArray,qreal,qreal))); KItemListSelectionManager* selectionManager = controller->selectionManager(); connect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), @@ -774,7 +777,9 @@ void DolphinView::slotViewContextMenuRequested(const QPointF& pos) void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos) { - QWeakPointer<KMenu> menu = new KMenu(QApplication::activeWindow()); + ViewProperties props(url()); + + QPointer<KMenu> menu = new KMenu(QApplication::activeWindow()); KItemListView* view = m_container->controller()->view(); const QSet<QByteArray> 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(); - ViewProperties props(url()); - QList<QByteArray> visibleRoles = view->visibleRoles(); - if (action->isChecked()) { - visibleRoles.append(selectedRole); + 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<int>()); + 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<int> columnWidths; + foreach (const QByteArray& role, view->visibleRoles()) { + columnWidths.append(header->columnWidth(role)); + } + + props.setHeaderColumnWidths(columnWidths); + header->setAutomaticColumnResizing(false); } else { - visibleRoles.removeOne(selectedRole); + // Show or hide the selected role + const QByteArray selectedRole = action->data().toByteArray(); + + QList<QByteArray> 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<QByteArray> visibleRoles = m_container->visibleRoles(); + + ViewProperties props(url()); + QList<int> 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<int> headerColumnWidths = props.headerColumnWidths(); + const int rolesCount = m_visibleRoles.count(); + if (headerColumnWidths.count() == rolesCount) { + header->setAutomaticColumnResizing(false); + + QHash<QByteArray, qreal> 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<QByteArray> ViewProperties::visibleRoles() const return roles; } +void ViewProperties::setHeaderColumnWidths(const QList<int>& widths) +{ + if (m_node->headerColumnWidths() != widths) { + m_node->setHeaderColumnWidths(widths); + update(); + } +} + +QList<int> 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<QByteArray> visibleRoles() const; + void setHeaderColumnWidths(const QList<int>& widths); + QList<int> headerColumnWidths() const; + /** * Sets the directory properties view mode, show preview, * show hidden files, sorting and sort order like |
