diff options
| author | Peter Penz <[email protected]> | 2012-03-23 22:26:17 +0100 |
|---|---|---|
| committer | Peter Penz <[email protected]> | 2012-03-23 22:31:55 +0100 |
| commit | 3f88f79f862a570b68fe64781955cf7d14124127 (patch) | |
| tree | ef9231ea05a90c495f2d59970f2c251204e97c22 /src/kitemviews/kitemlistheaderwidget.cpp | |
| parent | 3fc96ef97bbea25418bb22a18e82f6b874eedb38 (diff) | |
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
Diffstat (limited to 'src/kitemviews/kitemlistheaderwidget.cpp')
| -rw-r--r-- | src/kitemviews/kitemlistheaderwidget.cpp | 507 |
1 files changed, 507 insertions, 0 deletions
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" |
