1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
20 #include "kitemlistheaderwidget.h"
21 #include "kitemviews/kitemmodelbase.h"
23 #include <QApplication>
24 #include <QGraphicsSceneHoverEvent>
26 #include <QStyleOptionHeader>
29 KItemListHeaderWidget::KItemListHeaderWidget(QGraphicsWidget
* parent
) :
30 QGraphicsWidget(parent
),
31 m_automaticColumnResizing(true),
36 m_preferredColumnWidths(),
37 m_hoveredRoleIndex(-1),
38 m_pressedRoleIndex(-1),
39 m_roleOperation(NoRoleOperation
),
44 m_movingRole
.xDec
= 0;
45 m_movingRole
.index
= -1;
47 setAcceptHoverEvents(true);
50 KItemListHeaderWidget::~KItemListHeaderWidget()
54 void KItemListHeaderWidget::setModel(KItemModelBase
* model
)
56 if (m_model
== model
) {
61 disconnect(m_model
, &KItemModelBase::sortRoleChanged
,
62 this, &KItemListHeaderWidget::slotSortRoleChanged
);
63 disconnect(m_model
, &KItemModelBase::sortOrderChanged
,
64 this, &KItemListHeaderWidget::slotSortOrderChanged
);
70 connect(m_model
, &KItemModelBase::sortRoleChanged
,
71 this, &KItemListHeaderWidget::slotSortRoleChanged
);
72 connect(m_model
, &KItemModelBase::sortOrderChanged
,
73 this, &KItemListHeaderWidget::slotSortOrderChanged
);
77 KItemModelBase
* KItemListHeaderWidget::model() const
82 void KItemListHeaderWidget::setAutomaticColumnResizing(bool automatic
)
84 m_automaticColumnResizing
= automatic
;
87 bool KItemListHeaderWidget::automaticColumnResizing() const
89 return m_automaticColumnResizing
;
92 void KItemListHeaderWidget::setColumns(const QList
<QByteArray
>& roles
)
94 foreach (const QByteArray
& role
, roles
) {
95 if (!m_columnWidths
.contains(role
)) {
96 m_columnWidths
.remove(role
);
97 m_preferredColumnWidths
.remove(role
);
105 QList
<QByteArray
> KItemListHeaderWidget::columns() const
110 void KItemListHeaderWidget::setColumnWidth(const QByteArray
& role
, qreal width
)
112 const qreal minWidth
= minimumColumnWidth();
113 if (width
< minWidth
) {
117 if (m_columnWidths
.value(role
) != width
) {
118 m_columnWidths
.insert(role
, width
);
123 qreal
KItemListHeaderWidget::columnWidth(const QByteArray
& role
) const
125 return m_columnWidths
.value(role
);
128 void KItemListHeaderWidget::setPreferredColumnWidth(const QByteArray
& role
, qreal width
)
130 m_preferredColumnWidths
.insert(role
, width
);
133 qreal
KItemListHeaderWidget::preferredColumnWidth(const QByteArray
& role
) const
135 return m_preferredColumnWidths
.value(role
);
138 void KItemListHeaderWidget::setOffset(qreal offset
)
140 if (m_offset
!= offset
) {
146 qreal
KItemListHeaderWidget::offset() const
151 qreal
KItemListHeaderWidget::minimumColumnWidth() const
153 QFontMetricsF
fontMetrics(font());
154 return fontMetrics
.height() * 4;
157 void KItemListHeaderWidget::paint(QPainter
* painter
, const QStyleOptionGraphicsItem
* option
, QWidget
* widget
)
167 painter
->setFont(font());
168 painter
->setPen(palette().text().color());
172 foreach (const QByteArray
& role
, m_columns
) {
173 const qreal roleWidth
= m_columnWidths
.value(role
);
174 const QRectF
rect(x
, 0, roleWidth
, size().height());
175 paintRole(painter
, role
, rect
, orderIndex
, widget
);
180 if (!m_movingRole
.pixmap
.isNull()) {
181 Q_ASSERT(m_roleOperation
== MoveRoleOperation
);
182 painter
->drawPixmap(m_movingRole
.x
, 0, m_movingRole
.pixmap
);
186 void KItemListHeaderWidget::mousePressEvent(QGraphicsSceneMouseEvent
* event
)
188 if (event
->button() & Qt::LeftButton
) {
189 updatePressedRoleIndex(event
->pos());
190 m_pressedMousePos
= event
->pos();
191 m_roleOperation
= isAboveRoleGrip(m_pressedMousePos
, m_pressedRoleIndex
) ?
192 ResizeRoleOperation
: NoRoleOperation
;
199 void KItemListHeaderWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent
* event
)
201 QGraphicsWidget::mouseReleaseEvent(event
);
203 if (m_pressedRoleIndex
== -1) {
207 switch (m_roleOperation
) {
208 case NoRoleOperation
: {
209 // Only a click has been done and no moving or resizing has been started
210 const QByteArray sortRole
= m_model
->sortRole();
211 const int sortRoleIndex
= m_columns
.indexOf(sortRole
);
212 if (m_pressedRoleIndex
== sortRoleIndex
) {
213 // Toggle the sort order
214 const Qt::SortOrder previous
= m_model
->sortOrder();
215 const Qt::SortOrder current
= (m_model
->sortOrder() == Qt::AscendingOrder
) ?
216 Qt::DescendingOrder
: Qt::AscendingOrder
;
217 m_model
->setSortOrder(current
);
218 emit
sortOrderChanged(current
, previous
);
220 // Change the sort role and reset to the ascending order
221 const QByteArray previous
= m_model
->sortRole();
222 const QByteArray current
= m_columns
[m_pressedRoleIndex
];
223 const bool resetSortOrder
= m_model
->sortOrder() == Qt::DescendingOrder
;
224 m_model
->setSortRole(current
, !resetSortOrder
);
225 emit
sortRoleChanged(current
, previous
);
227 if (resetSortOrder
) {
228 m_model
->setSortOrder(Qt::AscendingOrder
);
229 emit
sortOrderChanged(Qt::AscendingOrder
, Qt::DescendingOrder
);
235 case ResizeRoleOperation
: {
236 const QByteArray pressedRole
= m_columns
[m_pressedRoleIndex
];
237 const qreal currentWidth
= m_columnWidths
.value(pressedRole
);
238 emit
columnWidthChangeFinished(pressedRole
, currentWidth
);
242 case MoveRoleOperation
:
243 m_movingRole
.pixmap
= QPixmap();
245 m_movingRole
.xDec
= 0;
246 m_movingRole
.index
= -1;
253 m_pressedRoleIndex
= -1;
254 m_roleOperation
= NoRoleOperation
;
257 QApplication::restoreOverrideCursor();
260 void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent
* event
)
262 QGraphicsWidget::mouseMoveEvent(event
);
264 switch (m_roleOperation
) {
265 case NoRoleOperation
:
266 if ((event
->pos() - m_pressedMousePos
).manhattanLength() >= QApplication::startDragDistance()) {
267 // A role gets dragged by the user. Create a pixmap of the role that will get
268 // synchronized on each furter mouse-move-event with the mouse-position.
269 m_roleOperation
= MoveRoleOperation
;
270 const int roleIndex
= roleIndexAt(m_pressedMousePos
);
271 m_movingRole
.index
= roleIndex
;
272 if (roleIndex
== 0) {
273 // TODO: It should be configurable whether moving the first role is allowed.
274 // In the context of Dolphin this is not required, however this should be
275 // changed if KItemViews are used in a more generic way.
276 QApplication::setOverrideCursor(QCursor(Qt::ForbiddenCursor
));
278 m_movingRole
.pixmap
= createRolePixmap(roleIndex
);
280 qreal roleX
= -m_offset
;
281 for (int i
= 0; i
< roleIndex
; ++i
) {
282 const QByteArray role
= m_columns
[i
];
283 roleX
+= m_columnWidths
.value(role
);
286 m_movingRole
.xDec
= event
->pos().x() - roleX
;
287 m_movingRole
.x
= roleX
;
293 case ResizeRoleOperation
: {
294 const QByteArray pressedRole
= m_columns
[m_pressedRoleIndex
];
296 qreal previousWidth
= m_columnWidths
.value(pressedRole
);
297 qreal currentWidth
= previousWidth
;
298 currentWidth
+= event
->pos().x() - event
->lastPos().x();
299 currentWidth
= qMax(minimumColumnWidth(), currentWidth
);
301 m_columnWidths
.insert(pressedRole
, currentWidth
);
304 emit
columnWidthChanged(pressedRole
, currentWidth
, previousWidth
);
308 case MoveRoleOperation
: {
309 // TODO: It should be configurable whether moving the first role is allowed.
310 // In the context of Dolphin this is not required, however this should be
311 // changed if KItemViews are used in a more generic way.
312 if (m_movingRole
.index
> 0) {
313 m_movingRole
.x
= event
->pos().x() - m_movingRole
.xDec
;
316 const int targetIndex
= targetOfMovingRole();
317 if (targetIndex
> 0 && targetIndex
!= m_movingRole
.index
) {
318 const QByteArray role
= m_columns
[m_movingRole
.index
];
319 const int previousIndex
= m_movingRole
.index
;
320 m_movingRole
.index
= targetIndex
;
321 emit
columnMoved(role
, targetIndex
, previousIndex
);
323 m_movingRole
.xDec
= event
->pos().x() - roleXPosition(role
);
334 void KItemListHeaderWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent
* event
)
336 QGraphicsItem::mouseDoubleClickEvent(event
);
338 const int roleIndex
= roleIndexAt(event
->pos());
339 if (roleIndex
>= 0 && isAboveRoleGrip(event
->pos(), roleIndex
)) {
340 const QByteArray role
= m_columns
.at(roleIndex
);
342 qreal previousWidth
= columnWidth(role
);
343 setColumnWidth(role
, preferredColumnWidth(role
));
344 qreal currentWidth
= columnWidth(role
);
346 emit
columnWidthChanged(role
, currentWidth
, previousWidth
);
347 emit
columnWidthChangeFinished(role
, currentWidth
);
351 void KItemListHeaderWidget::hoverEnterEvent(QGraphicsSceneHoverEvent
* event
)
353 QGraphicsWidget::hoverEnterEvent(event
);
354 updateHoveredRoleIndex(event
->pos());
357 void KItemListHeaderWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent
* event
)
359 QGraphicsWidget::hoverLeaveEvent(event
);
360 if (m_hoveredRoleIndex
!= -1) {
361 m_hoveredRoleIndex
= -1;
366 void KItemListHeaderWidget::hoverMoveEvent(QGraphicsSceneHoverEvent
* event
)
368 QGraphicsWidget::hoverMoveEvent(event
);
370 const QPointF
& pos
= event
->pos();
371 updateHoveredRoleIndex(pos
);
372 if (m_hoveredRoleIndex
>= 0 && isAboveRoleGrip(pos
, m_hoveredRoleIndex
)) {
373 setCursor(Qt::SplitHCursor
);
379 void KItemListHeaderWidget::slotSortRoleChanged(const QByteArray
& current
, const QByteArray
& previous
)
386 void KItemListHeaderWidget::slotSortOrderChanged(Qt::SortOrder current
, Qt::SortOrder previous
)
393 void KItemListHeaderWidget::paintRole(QPainter
* painter
,
394 const QByteArray
& role
,
397 QWidget
* widget
) const
399 // The following code is based on the code from QHeaderView::paintSection().
400 // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
401 QStyleOptionHeader option
;
402 option
.section
= orderIndex
;
403 option
.state
= QStyle::State_None
| QStyle::State_Raised
| QStyle::State_Horizontal
;
405 option
.state
|= QStyle::State_Enabled
;
407 if (window() && window()->isActiveWindow()) {
408 option
.state
|= QStyle::State_Active
;
410 if (m_hoveredRoleIndex
== orderIndex
) {
411 option
.state
|= QStyle::State_MouseOver
;
413 if (m_pressedRoleIndex
== orderIndex
) {
414 option
.state
|= QStyle::State_Sunken
;
416 if (m_model
->sortRole() == role
) {
417 option
.sortIndicator
= (m_model
->sortOrder() == Qt::AscendingOrder
) ?
418 QStyleOptionHeader::SortDown
: QStyleOptionHeader::SortUp
;
420 option
.rect
= rect
.toRect();
422 bool paintBackgroundForEmptyArea
= false;
424 if (m_columns
.count() == 1) {
425 option
.position
= QStyleOptionHeader::OnlyOneSection
;
426 } else if (orderIndex
== 0) {
427 option
.position
= QStyleOptionHeader::Beginning
;
428 } else if (orderIndex
== m_columns
.count() - 1) {
429 // We are just painting the header for the last column. Check if there
430 // is some empty space to the right which needs to be filled.
431 if (rect
.right() < size().width()) {
432 option
.position
= QStyleOptionHeader::Middle
;
433 paintBackgroundForEmptyArea
= true;
435 option
.position
= QStyleOptionHeader::End
;
438 option
.position
= QStyleOptionHeader::Middle
;
441 option
.orientation
= Qt::Horizontal
;
442 option
.selectedPosition
= QStyleOptionHeader::NotAdjacent
;
443 option
.text
= m_model
->roleDescription(role
);
445 style()->drawControl(QStyle::CE_Header
, &option
, painter
, widget
);
447 if (paintBackgroundForEmptyArea
) {
448 option
.state
= QStyle::State_None
| QStyle::State_Raised
| QStyle::State_Horizontal
;
449 option
.section
= m_columns
.count();
450 option
.sortIndicator
= QStyleOptionHeader::None
;
452 qreal backgroundRectX
= rect
.x() + rect
.width();
453 QRectF
backgroundRect(backgroundRectX
, 0.0, size().width() - backgroundRectX
, rect
.height());
454 option
.rect
= backgroundRect
.toRect();
455 option
.position
= QStyleOptionHeader::End
;
456 option
.text
= QString();
458 style()->drawControl(QStyle::CE_Header
, &option
, painter
, widget
);
462 void KItemListHeaderWidget::updatePressedRoleIndex(const QPointF
& pos
)
464 const int pressedIndex
= roleIndexAt(pos
);
465 if (m_pressedRoleIndex
!= pressedIndex
) {
466 m_pressedRoleIndex
= pressedIndex
;
471 void KItemListHeaderWidget::updateHoveredRoleIndex(const QPointF
& pos
)
473 const int hoverIndex
= roleIndexAt(pos
);
474 if (m_hoveredRoleIndex
!= hoverIndex
) {
475 m_hoveredRoleIndex
= hoverIndex
;
480 int KItemListHeaderWidget::roleIndexAt(const QPointF
& pos
) const
485 foreach (const QByteArray
& role
, m_columns
) {
487 x
+= m_columnWidths
.value(role
);
496 bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF
& pos
, int roleIndex
) const
499 for (int i
= 0; i
<= roleIndex
; ++i
) {
500 const QByteArray role
= m_columns
[i
];
501 x
+= m_columnWidths
.value(role
);
504 const int grip
= style()->pixelMetric(QStyle::PM_HeaderGripMargin
);
505 return pos
.x() >= (x
- grip
) && pos
.x() <= x
;
508 QPixmap
KItemListHeaderWidget::createRolePixmap(int roleIndex
) const
510 const QByteArray role
= m_columns
[roleIndex
];
511 const qreal roleWidth
= m_columnWidths
.value(role
);
512 const QRect
rect(0, 0, roleWidth
, size().height());
514 QImage
image(rect
.size(), QImage::Format_ARGB32_Premultiplied
);
516 QPainter
painter(&image
);
517 paintRole(&painter
, role
, rect
, roleIndex
);
519 // Apply a highlighting-color
520 const QPalette::ColorGroup group
= isActiveWindow() ? QPalette::Active
: QPalette::Inactive
;
521 QColor highlightColor
= palette().color(group
, QPalette::Highlight
);
522 highlightColor
.setAlpha(64);
523 painter
.fillRect(rect
, highlightColor
);
525 // Make the image transparent
526 painter
.setCompositionMode(QPainter::CompositionMode_DestinationIn
);
527 painter
.fillRect(0, 0, image
.width(), image
.height(), QColor(0, 0, 0, 192));
529 return QPixmap::fromImage(image
);
532 int KItemListHeaderWidget::targetOfMovingRole() const
534 const int movingWidth
= m_movingRole
.pixmap
.width();
535 const int movingLeft
= m_movingRole
.x
;
536 const int movingRight
= movingLeft
+ movingWidth
- 1;
539 qreal targetLeft
= -m_offset
;
540 while (targetIndex
< m_columns
.count()) {
541 const QByteArray role
= m_columns
[targetIndex
];
542 const qreal targetWidth
= m_columnWidths
.value(role
);
543 const qreal targetRight
= targetLeft
+ targetWidth
- 1;
545 const bool isInTarget
= (targetWidth
>= movingWidth
&&
546 movingLeft
>= targetLeft
&&
547 movingRight
<= targetRight
) ||
548 (targetWidth
< movingWidth
&&
549 movingLeft
<= targetLeft
&&
550 movingRight
>= targetRight
);
556 targetLeft
+= targetWidth
;
560 return m_movingRole
.index
;
563 qreal
KItemListHeaderWidget::roleXPosition(const QByteArray
& role
) const
566 foreach (const QByteArray
& visibleRole
, m_columns
) {
567 if (visibleRole
== role
) {
571 x
+= m_columnWidths
.value(visibleRole
);