-/***************************************************************************
- * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
- * *
- * 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 *
- ***************************************************************************/
+/*
+ * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
#include "kitemlistheaderwidget.h"
-
-#include <kitemviews/kitemmodelbase.h>
+#include "kitemviews/kitemmodelbase.h"
#include <QApplication>
#include <QGraphicsSceneHoverEvent>
#include <QPainter>
#include <QStyleOptionHeader>
-#include <KDebug>
-
-KItemListHeaderWidget::KItemListHeaderWidget(QGraphicsWidget* parent) :
- QGraphicsWidget(parent),
- m_automaticColumnResizing(true),
- m_model(0),
- m_offset(0),
- m_columns(),
- m_columnWidths(),
- m_preferredColumnWidths(),
- m_hoveredRoleIndex(-1),
- m_pressedRoleIndex(-1),
- m_roleOperation(NoRoleOperation),
- m_pressedMousePos(),
- m_movingRole()
+KItemListHeaderWidget::KItemListHeaderWidget(QGraphicsWidget *parent)
+ : QGraphicsWidget(parent)
+ , m_automaticColumnResizing(true)
+ , m_model(nullptr)
+ , m_offset(0)
+ , m_sidePadding(0)
+ , m_columns()
+ , m_columnWidths()
+ , m_preferredColumnWidths()
+ , m_hoveredIndex(-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);
+ // TODO update when font changes at runtime
+ setFont(QApplication::font("QHeaderView"));
}
KItemListHeaderWidget::~KItemListHeaderWidget()
{
}
-void KItemListHeaderWidget::setModel(KItemModelBase* model)
+void KItemListHeaderWidget::setModel(KItemModelBase *model)
{
if (m_model == model) {
return;
}
if (m_model) {
- disconnect(m_model, &KItemModelBase::sortRoleChanged,
- this, &KItemListHeaderWidget::slotSortRoleChanged);
- disconnect(m_model, &KItemModelBase::sortOrderChanged,
- this, &KItemListHeaderWidget::slotSortOrderChanged);
+ disconnect(m_model, &KItemModelBase::sortRoleChanged, this, &KItemListHeaderWidget::slotSortRoleChanged);
+ disconnect(m_model, &KItemModelBase::sortOrderChanged, this, &KItemListHeaderWidget::slotSortOrderChanged);
}
m_model = model;
if (m_model) {
- connect(m_model, &KItemModelBase::sortRoleChanged,
- this, &KItemListHeaderWidget::slotSortRoleChanged);
- connect(m_model, &KItemModelBase::sortOrderChanged,
- this, &KItemListHeaderWidget::slotSortOrderChanged);
+ connect(m_model, &KItemModelBase::sortRoleChanged, this, &KItemListHeaderWidget::slotSortRoleChanged);
+ connect(m_model, &KItemModelBase::sortOrderChanged, this, &KItemListHeaderWidget::slotSortOrderChanged);
}
}
-KItemModelBase* KItemListHeaderWidget::model() const
+KItemModelBase *KItemListHeaderWidget::model() const
{
return m_model;
}
return m_automaticColumnResizing;
}
-void KItemListHeaderWidget::setColumns(const QList<QByteArray>& roles)
+void KItemListHeaderWidget::setColumns(const QList<QByteArray> &roles)
{
- foreach (const QByteArray& role, roles) {
+ for (const QByteArray &role : roles) {
if (!m_columnWidths.contains(role)) {
- m_columnWidths.remove(role);
m_preferredColumnWidths.remove(role);
}
}
return m_columns;
}
-void KItemListHeaderWidget::setColumnWidth(const QByteArray& role, qreal width)
+void KItemListHeaderWidget::setColumnWidth(const QByteArray &role, qreal width)
{
const qreal minWidth = minimumColumnWidth();
if (width < minWidth) {
}
}
-qreal KItemListHeaderWidget::columnWidth(const QByteArray& role) const
+qreal KItemListHeaderWidget::columnWidth(const QByteArray &role) const
{
return m_columnWidths.value(role);
}
-void KItemListHeaderWidget::setPreferredColumnWidth(const QByteArray& role, qreal width)
+void KItemListHeaderWidget::setPreferredColumnWidth(const QByteArray &role, qreal width)
{
m_preferredColumnWidths.insert(role, width);
}
-qreal KItemListHeaderWidget::preferredColumnWidth(const QByteArray& role) const
+qreal KItemListHeaderWidget::preferredColumnWidth(const QByteArray &role) const
{
return m_preferredColumnWidths.value(role);
}
return m_offset;
}
+void KItemListHeaderWidget::setSidePadding(qreal width)
+{
+ if (m_sidePadding != width) {
+ m_sidePadding = width;
+ Q_EMIT sidePaddingChanged(width);
+ update();
+ }
+}
+
+qreal KItemListHeaderWidget::sidePadding() const
+{
+ return m_sidePadding;
+}
+
qreal KItemListHeaderWidget::minimumColumnWidth() const
{
QFontMetricsF fontMetrics(font());
return fontMetrics.height() * 4;
}
-void KItemListHeaderWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+void KItemListHeaderWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
- Q_UNUSED(option);
- Q_UNUSED(widget);
+ Q_UNUSED(option)
+ Q_UNUSED(widget)
if (!m_model) {
return;
painter->setFont(font());
painter->setPen(palette().text().color());
- qreal x = -m_offset;
+ qreal x = -m_offset + m_sidePadding;
int orderIndex = 0;
- foreach (const QByteArray& role, m_columns) {
+ for (const QByteArray &role : std::as_const(m_columns)) {
const qreal roleWidth = m_columnWidths.value(role);
const QRectF rect(x, 0, roleWidth, size().height());
paintRole(painter, role, rect, orderIndex, widget);
}
}
-void KItemListHeaderWidget::mousePressEvent(QGraphicsSceneMouseEvent* event)
+void KItemListHeaderWidget::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() & Qt::LeftButton) {
- updatePressedRoleIndex(event->pos());
m_pressedMousePos = event->pos();
- m_roleOperation = isAboveRoleGrip(m_pressedMousePos, m_pressedRoleIndex) ?
- ResizeRoleOperation : NoRoleOperation;
+ if (isAbovePaddingGrip(m_pressedMousePos, PaddingGrip::Leading)) {
+ m_roleOperation = ResizePaddingColumnOperation;
+ } else {
+ updatePressedRoleIndex(event->pos());
+ m_roleOperation = isAboveRoleGrip(m_pressedMousePos, m_pressedRoleIndex) ? ResizeRoleOperation : NoRoleOperation;
+ }
event->accept();
} else {
event->ignore();
}
}
-void KItemListHeaderWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
+void KItemListHeaderWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsWidget::mouseReleaseEvent(event);
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;
+ const Qt::SortOrder current = (m_model->sortOrder() == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder;
m_model->setSortOrder(current);
- emit sortOrderChanged(current, previous);
+ Q_EMIT sortOrderChanged(current, previous);
} else {
// Change the sort role and reset to the ascending order
const QByteArray previous = m_model->sortRole();
const QByteArray current = m_columns[m_pressedRoleIndex];
- m_model->setSortRole(current);
- emit sortRoleChanged(current, previous);
+ const bool resetSortOrder = m_model->sortOrder() == Qt::DescendingOrder;
+ m_model->setSortRole(current, !resetSortOrder);
+ Q_EMIT sortRoleChanged(current, previous);
- if (m_model->sortOrder() == Qt::DescendingOrder) {
+ if (resetSortOrder) {
m_model->setSortOrder(Qt::AscendingOrder);
- emit sortOrderChanged(Qt::AscendingOrder, Qt::DescendingOrder);
+ Q_EMIT sortOrderChanged(Qt::AscendingOrder, Qt::DescendingOrder);
}
}
break;
}
+ case ResizeRoleOperation: {
+ const QByteArray pressedRole = m_columns[m_pressedRoleIndex];
+ const qreal currentWidth = m_columnWidths.value(pressedRole);
+ Q_EMIT columnWidthChangeFinished(pressedRole, currentWidth);
+ break;
+ }
+
case MoveRoleOperation:
m_movingRole.pixmap = QPixmap();
m_movingRole.x = 0;
QApplication::restoreOverrideCursor();
}
-void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
+void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsWidget::mouseMoveEvent(event);
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.
+ // synchronized on each further mouse-move-event with the mouse-position.
m_roleOperation = MoveRoleOperation;
const int roleIndex = roleIndexAt(m_pressedMousePos);
m_movingRole.index = roleIndex;
} else {
m_movingRole.pixmap = createRolePixmap(roleIndex);
- qreal roleX = -m_offset;
+ qreal roleX = -m_offset + m_sidePadding;
for (int i = 0; i < roleIndex; ++i) {
const QByteArray role = m_columns[i];
roleX += m_columnWidths.value(role);
m_columnWidths.insert(pressedRole, currentWidth);
update();
- emit columnWidthChanged(pressedRole, currentWidth, previousWidth);
+ Q_EMIT columnWidthChanged(pressedRole, currentWidth, previousWidth);
+ break;
+ }
+
+ case ResizePaddingColumnOperation: {
+ qreal currentWidth = m_sidePadding;
+ currentWidth += event->pos().x() - event->lastPos().x();
+ currentWidth = qMax(0.0, currentWidth);
+
+ m_sidePadding = currentWidth;
+
+ update();
+
+ Q_EMIT sidePaddingChanged(currentWidth);
+
break;
}
const QByteArray role = m_columns[m_movingRole.index];
const int previousIndex = m_movingRole.index;
m_movingRole.index = targetIndex;
- emit columnMoved(role, targetIndex, previousIndex);
+ Q_EMIT columnMoved(role, targetIndex, previousIndex);
m_movingRole.xDec = event->pos().x() - roleXPosition(role);
}
}
}
-void KItemListHeaderWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event)
+void KItemListHeaderWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsItem::mouseDoubleClickEvent(event);
setColumnWidth(role, preferredColumnWidth(role));
qreal currentWidth = columnWidth(role);
- emit columnWidthChanged(role, currentWidth, previousWidth);
+ Q_EMIT columnWidthChanged(role, currentWidth, previousWidth);
+ Q_EMIT columnWidthChangeFinished(role, currentWidth);
}
}
-void KItemListHeaderWidget::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
+void KItemListHeaderWidget::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
QGraphicsWidget::hoverEnterEvent(event);
- updateHoveredRoleIndex(event->pos());
+ updateHoveredIndex(event->pos());
}
-void KItemListHeaderWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
+void KItemListHeaderWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
QGraphicsWidget::hoverLeaveEvent(event);
- if (m_hoveredRoleIndex != -1) {
- m_hoveredRoleIndex = -1;
+ if (m_hoveredIndex != -1) {
+ Q_EMIT columnUnHovered(m_hoveredIndex);
+ m_hoveredIndex = -1;
update();
}
}
-void KItemListHeaderWidget::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
+void KItemListHeaderWidget::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
QGraphicsWidget::hoverMoveEvent(event);
- const QPointF& pos = event->pos();
- updateHoveredRoleIndex(pos);
- if (m_hoveredRoleIndex >= 0 && isAboveRoleGrip(pos, m_hoveredRoleIndex)) {
+ const QPointF &pos = event->pos();
+ updateHoveredIndex(pos);
+ if ((m_hoveredIndex >= 0 && isAboveRoleGrip(pos, m_hoveredIndex)) || isAbovePaddingGrip(pos, PaddingGrip::Leading)
+ || isAbovePaddingGrip(pos, PaddingGrip::Trailing)) {
setCursor(Qt::SplitHCursor);
} else {
unsetCursor();
}
}
-void KItemListHeaderWidget::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous)
+void KItemListHeaderWidget::slotSortRoleChanged(const QByteArray ¤t, const QByteArray &previous)
{
- Q_UNUSED(current);
- Q_UNUSED(previous);
+ Q_UNUSED(current)
+ Q_UNUSED(previous)
update();
}
void KItemListHeaderWidget::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
{
- Q_UNUSED(current);
- Q_UNUSED(previous);
+ Q_UNUSED(current)
+ Q_UNUSED(previous)
update();
}
-void KItemListHeaderWidget::paintRole(QPainter* painter,
- const QByteArray& role,
- const QRectF& rect,
- int orderIndex,
- QWidget* widget) const
+void KItemListHeaderWidget::paintRole(QPainter *painter, const QByteArray &role, const QRectF &rect, int orderIndex, QWidget *widget) const
{
+ const auto direction = widget ? widget->layoutDirection() : qApp->layoutDirection();
+
// The following code is based on the code from QHeaderView::paintSection().
- // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+ // SPDX-FileCopyrightText: 2011 Nokia Corporation and/or its subsidiary(-ies).
QStyleOptionHeader option;
+ option.direction = direction;
+ option.textAlignment = direction == Qt::LeftToRight ? Qt::AlignLeft : Qt::AlignRight;
+
option.section = orderIndex;
option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
if (isEnabled()) {
if (window() && window()->isActiveWindow()) {
option.state |= QStyle::State_Active;
}
- if (m_hoveredRoleIndex == orderIndex) {
+ if (m_hoveredIndex == 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.sortIndicator = (m_model->sortOrder() == Qt::AscendingOrder) ? QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp;
}
option.rect = rect.toRect();
+ option.orientation = Qt::Horizontal;
+ option.selectedPosition = QStyleOptionHeader::NotAdjacent;
+ option.text = m_model->roleDescription(role);
- bool paintBackgroundForEmptyArea = false;
+ // First we paint any potential empty (padding) space on left and/or right of this role's column.
+ const auto paintPadding = [&](int section, const QRectF &rect, const QStyleOptionHeader::SectionPosition &pos) {
+ QStyleOptionHeader padding;
+ padding.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
+ padding.section = section;
+ padding.sortIndicator = QStyleOptionHeader::None;
+ padding.rect = rect.toRect();
+ padding.position = pos;
+ padding.text = QString();
+ style()->drawControl(QStyle::CE_Header, &padding, painter, widget);
+ };
if (m_columns.count() == 1) {
- option.position = QStyleOptionHeader::OnlyOneSection;
+ option.position = QStyleOptionHeader::Middle;
+ paintPadding(0, QRectF(0.0, 0.0, rect.left(), rect.height()), QStyleOptionHeader::Beginning);
+ paintPadding(1, QRectF(rect.left(), 0.0, size().width() - rect.left(), rect.height()), QStyleOptionHeader::End);
} else if (orderIndex == 0) {
- option.position = QStyleOptionHeader::Beginning;
+ // Paint the header for the first column; check if there is some empty space to the left which needs to be filled.
+ if (rect.left() > 0) {
+ option.position = QStyleOptionHeader::Middle;
+ paintPadding(0, QRectF(0.0, 0.0, rect.left(), rect.height()), QStyleOptionHeader::Beginning);
+ } else {
+ option.position = QStyleOptionHeader::Beginning;
+ }
} else if (orderIndex == m_columns.count() - 1) {
- // We are just painting the header for the last column. Check if there
- // is some empty space to the right which needs to be filled.
+ // Paint the header for the last column; check if there is some empty space to the right which needs to be filled.
if (rect.right() < size().width()) {
option.position = QStyleOptionHeader::Middle;
- paintBackgroundForEmptyArea = true;
+ paintPadding(m_columns.count(), QRectF(rect.left(), 0.0, size().width() - rect.left(), rect.height()), QStyleOptionHeader::End);
} else {
option.position = QStyleOptionHeader::End;
}
option.position = QStyleOptionHeader::Middle;
}
- option.orientation = Qt::Horizontal;
- option.selectedPosition = QStyleOptionHeader::NotAdjacent;
- option.text = m_model->roleDescription(role);
-
style()->drawControl(QStyle::CE_Header, &option, painter, widget);
-
- if (paintBackgroundForEmptyArea) {
- option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
- option.section = m_columns.count();
- option.sortIndicator = QStyleOptionHeader::None;
-
- qreal backgroundRectX = rect.x() + rect.width();
- QRectF backgroundRect(backgroundRectX, 0.0, size().width() - backgroundRectX, rect.height());
- option.rect = backgroundRect.toRect();
- option.position = QStyleOptionHeader::End;
- option.text = QString();
-
- style()->drawControl(QStyle::CE_Header, &option, painter, widget);
- }
}
-void KItemListHeaderWidget::updatePressedRoleIndex(const QPointF& pos)
+void KItemListHeaderWidget::updatePressedRoleIndex(const QPointF &pos)
{
const int pressedIndex = roleIndexAt(pos);
if (m_pressedRoleIndex != pressedIndex) {
}
}
-void KItemListHeaderWidget::updateHoveredRoleIndex(const QPointF& pos)
+void KItemListHeaderWidget::updateHoveredIndex(const QPointF &pos)
{
const int hoverIndex = roleIndexAt(pos);
- if (m_hoveredRoleIndex != hoverIndex) {
- m_hoveredRoleIndex = hoverIndex;
+
+ if (m_hoveredIndex != hoverIndex) {
+ if (m_hoveredIndex != -1) {
+ Q_EMIT columnUnHovered(m_hoveredIndex);
+ }
+ m_hoveredIndex = hoverIndex;
+ if (m_hoveredIndex != -1) {
+ Q_EMIT columnHovered(m_hoveredIndex);
+ }
update();
}
}
-int KItemListHeaderWidget::roleIndexAt(const QPointF& pos) const
+int KItemListHeaderWidget::roleIndexAt(const QPointF &pos) const
{
int index = -1;
- qreal x = -m_offset;
- foreach (const QByteArray& role, m_columns) {
+ qreal x = -m_offset + m_sidePadding;
+ for (const QByteArray &role : std::as_const(m_columns)) {
++index;
x += m_columnWidths.value(role);
if (pos.x() <= x) {
return index;
}
-bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF& pos, int roleIndex) const
+bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF &pos, int roleIndex) const
{
- qreal x = -m_offset;
+ qreal x = -m_offset + m_sidePadding;
for (int i = 0; i <= roleIndex; ++i) {
const QByteArray role = m_columns[i];
x += m_columnWidths.value(role);
return pos.x() >= (x - grip) && pos.x() <= x;
}
+bool KItemListHeaderWidget::isAbovePaddingGrip(const QPointF &pos, PaddingGrip paddingGrip) const
+{
+ const qreal lx = -m_offset + m_sidePadding;
+ const int grip = style()->pixelMetric(QStyle::PM_HeaderGripMargin);
+
+ switch (paddingGrip) {
+ case Leading:
+ return pos.x() >= (lx - grip) && pos.x() <= lx;
+ case Trailing: {
+ qreal rx = lx;
+ for (const QByteArray &role : std::as_const(m_columns)) {
+ rx += m_columnWidths.value(role);
+ }
+ return pos.x() >= (rx - grip) && pos.x() <= rx;
+ }
+ default:
+ return false;
+ }
+}
+
QPixmap KItemListHeaderWidget::createRolePixmap(int roleIndex) const
{
const QByteArray role = m_columns[roleIndex];
const int movingRight = movingLeft + movingWidth - 1;
int targetIndex = 0;
- qreal targetLeft = -m_offset;
+ qreal targetLeft = -m_offset + m_sidePadding;
while (targetIndex < m_columns.count()) {
const QByteArray role = m_columns[targetIndex];
const qreal targetWidth = m_columnWidths.value(role);
const qreal targetRight = targetLeft + targetWidth - 1;
- const bool isInTarget = (targetWidth >= movingWidth &&
- movingLeft >= targetLeft &&
- movingRight <= targetRight) ||
- (targetWidth < movingWidth &&
- movingLeft <= targetLeft &&
- movingRight >= targetRight);
+ const bool isInTarget = (targetWidth >= movingWidth && movingLeft >= targetLeft && movingRight <= targetRight)
+ || (targetWidth < movingWidth && movingLeft <= targetLeft && movingRight >= targetRight);
if (isInTarget) {
return targetIndex;
return m_movingRole.index;
}
-qreal KItemListHeaderWidget::roleXPosition(const QByteArray& role) const
+qreal KItemListHeaderWidget::roleXPosition(const QByteArray &role) const
{
- qreal x = -m_offset;
- foreach (const QByteArray& visibleRole, m_columns) {
+ qreal x = -m_offset + m_sidePadding;
+ for (const QByteArray &visibleRole : std::as_const(m_columns)) {
if (visibleRole == role) {
return x;
}
return -1;
}
+#include "moc_kitemlistheaderwidget.cpp"