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_p.h"
24 #include "kitemmodelbase.h"
26 #include <QApplication>
27 #include <QGraphicsSceneHoverEvent>
29 #include <QStyleOptionHeader>
33 KItemListHeaderWidget::KItemListHeaderWidget(QGraphicsWidget
* parent
) :
34 QGraphicsWidget(parent
),
35 m_automaticColumnResizing(false),
39 m_hoveredRoleIndex(-1),
40 m_pressedRoleIndex(-1),
41 m_roleOperation(NoRoleOperation
),
46 m_movingRole
.xDec
= 0;
47 m_movingRole
.index
= -1;
49 setAcceptHoverEvents(true);
51 QStyleOptionHeader option
;
52 const QSize headerSize
= style()->sizeFromContents(QStyle::CT_HeaderSection
, &option
, QSize());
53 resize(0, headerSize
.height());
56 KItemListHeaderWidget::~KItemListHeaderWidget()
60 void KItemListHeaderWidget::setModel(KItemModelBase
* model
)
62 if (m_model
== model
) {
67 disconnect(m_model
, SIGNAL(sortRoleChanged(QByteArray
,QByteArray
)),
68 this, SLOT(slotSortRoleChanged(QByteArray
,QByteArray
)));
69 disconnect(m_model
, SIGNAL(sortOrderChanged(Qt::SortOrder
,Qt::SortOrder
)),
70 this, SLOT(slotSortOrderChanged(Qt::SortOrder
,Qt::SortOrder
)));
76 connect(m_model
, SIGNAL(sortRoleChanged(QByteArray
,QByteArray
)),
77 this, SLOT(slotSortRoleChanged(QByteArray
,QByteArray
)));
78 connect(m_model
, SIGNAL(sortOrderChanged(Qt::SortOrder
,Qt::SortOrder
)),
79 this, SLOT(slotSortOrderChanged(Qt::SortOrder
,Qt::SortOrder
)));
83 KItemModelBase
* KItemListHeaderWidget::model() const
88 void KItemListHeaderWidget::setAutomaticColumnResizing(bool automatic
)
90 m_automaticColumnResizing
= automatic
;
93 bool KItemListHeaderWidget::automaticColumnResizing() const
95 return m_automaticColumnResizing
;
98 void KItemListHeaderWidget::setColumns(const QList
<QByteArray
>& roles
)
104 QList
<QByteArray
> KItemListHeaderWidget::columns() const
109 void KItemListHeaderWidget::setColumnWidth(const QByteArray
& role
, qreal width
)
111 const qreal minWidth
= minimumColumnWidth();
112 if (width
< minWidth
) {
116 if (m_columnsWidths
.value(role
) != width
) {
117 m_columnsWidths
.insert(role
, width
);
122 qreal
KItemListHeaderWidget::columnWidth(const QByteArray
& role
) const
124 return m_columnsWidths
.value(role
);
127 qreal
KItemListHeaderWidget::minimumColumnWidth() const
129 QFontMetricsF
fontMetrics(font());
130 return fontMetrics
.height() * 4;
133 void KItemListHeaderWidget::paint(QPainter
* painter
, const QStyleOptionGraphicsItem
* option
, QWidget
* widget
)
143 painter
->setFont(font());
144 painter
->setPen(palette().text().color());
148 foreach (const QByteArray
& role
, m_columns
) {
149 const qreal roleWidth
= m_columnsWidths
.value(role
);
150 const QRectF
rect(x
, 0, roleWidth
, size().height());
151 paintRole(painter
, role
, rect
, orderIndex
, widget
);
156 // Draw background without roles
159 opt
.rect
= QRect(x
, 0, size().width() - x
, size().height());
160 opt
.state
|= QStyle::State_Horizontal
;
161 style()->drawControl(QStyle::CE_HeaderEmptyArea
, &opt
, painter
);
163 if (!m_movingRole
.pixmap
.isNull()) {
164 Q_ASSERT(m_roleOperation
== MoveRoleOperation
);
165 painter
->drawPixmap(m_movingRole
.x
, 0, m_movingRole
.pixmap
);
169 void KItemListHeaderWidget::mousePressEvent(QGraphicsSceneMouseEvent
* event
)
171 if (event
->button() & Qt::LeftButton
) {
172 updatePressedRoleIndex(event
->pos());
173 m_pressedMousePos
= event
->pos();
174 m_roleOperation
= isAboveRoleGrip(m_pressedMousePos
, m_pressedRoleIndex
) ?
175 ResizeRoleOperation
: NoRoleOperation
;
182 void KItemListHeaderWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent
* event
)
184 QGraphicsWidget::mouseReleaseEvent(event
);
186 if (m_pressedRoleIndex
== -1) {
190 switch (m_roleOperation
) {
191 case NoRoleOperation
: {
192 // Only a click has been done and no moving or resizing has been started
193 const QByteArray sortRole
= m_model
->sortRole();
194 const int sortRoleIndex
= m_columns
.indexOf(sortRole
);
195 if (m_pressedRoleIndex
== sortRoleIndex
) {
196 // Toggle the sort order
197 const Qt::SortOrder previous
= m_model
->sortOrder();
198 const Qt::SortOrder current
= (m_model
->sortOrder() == Qt::AscendingOrder
) ?
199 Qt::DescendingOrder
: Qt::AscendingOrder
;
200 m_model
->setSortOrder(current
);
201 emit
sortOrderChanged(current
, previous
);
203 // Change the sort role
204 const QByteArray previous
= m_model
->sortRole();
205 const QByteArray current
= m_columns
[m_pressedRoleIndex
];
206 m_model
->setSortRole(current
);
207 emit
sortRoleChanged(current
, previous
);
212 case MoveRoleOperation
:
213 m_movingRole
.pixmap
= QPixmap();
215 m_movingRole
.xDec
= 0;
216 m_movingRole
.index
= -1;
223 m_pressedRoleIndex
= -1;
224 m_roleOperation
= NoRoleOperation
;
227 QApplication::restoreOverrideCursor();
230 void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent
* event
)
232 QGraphicsWidget::mouseMoveEvent(event
);
234 switch (m_roleOperation
) {
235 case NoRoleOperation
:
236 if ((event
->pos() - m_pressedMousePos
).manhattanLength() >= QApplication::startDragDistance()) {
237 // A role gets dragged by the user. Create a pixmap of the role that will get
238 // synchronized on each furter mouse-move-event with the mouse-position.
239 m_roleOperation
= MoveRoleOperation
;
240 const int roleIndex
= roleIndexAt(m_pressedMousePos
);
241 m_movingRole
.index
= roleIndex
;
242 if (roleIndex
== 0) {
243 // TODO: It should be configurable whether moving the first role is allowed.
244 // In the context of Dolphin this is not required, however this should be
245 // changed if KItemViews are used in a more generic way.
246 QApplication::setOverrideCursor(QCursor(Qt::ForbiddenCursor
));
248 m_movingRole
.pixmap
= createRolePixmap(roleIndex
);
251 for (int i
= 0; i
< roleIndex
; ++i
) {
252 const QByteArray role
= m_columns
[i
];
253 roleX
+= m_columnsWidths
.value(role
);
256 m_movingRole
.xDec
= event
->pos().x() - roleX
;
257 m_movingRole
.x
= roleX
;
263 case ResizeRoleOperation
: {
264 const QByteArray pressedRole
= m_columns
[m_pressedRoleIndex
];
266 qreal previousWidth
= m_columnsWidths
.value(pressedRole
);
267 qreal currentWidth
= previousWidth
;
268 currentWidth
+= event
->pos().x() - event
->lastPos().x();
269 currentWidth
= qMax(minimumColumnWidth(), currentWidth
);
271 m_columnsWidths
.insert(pressedRole
, currentWidth
);
274 emit
columnWidthChanged(pressedRole
, currentWidth
, previousWidth
);
278 case MoveRoleOperation
: {
279 // TODO: It should be configurable whether moving the first role is allowed.
280 // In the context of Dolphin this is not required, however this should be
281 // changed if KItemViews are used in a more generic way.
282 if (m_movingRole
.index
> 0) {
283 m_movingRole
.x
= event
->pos().x() - m_movingRole
.xDec
;
286 const int targetIndex
= targetOfMovingRole();
287 if (targetIndex
> 0 && targetIndex
!= m_movingRole
.index
) {
288 const QByteArray role
= m_columns
[m_movingRole
.index
];
289 const int previousIndex
= m_movingRole
.index
;
290 m_movingRole
.index
= targetIndex
;
291 emit
columnMoved(role
, targetIndex
, previousIndex
);
293 m_movingRole
.xDec
= event
->pos().x() - roleXPosition(role
);
304 void KItemListHeaderWidget::hoverEnterEvent(QGraphicsSceneHoverEvent
* event
)
306 QGraphicsWidget::hoverEnterEvent(event
);
307 updateHoveredRoleIndex(event
->pos());
310 void KItemListHeaderWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent
* event
)
312 QGraphicsWidget::hoverLeaveEvent(event
);
313 if (m_hoveredRoleIndex
!= -1) {
314 m_hoveredRoleIndex
= -1;
319 void KItemListHeaderWidget::hoverMoveEvent(QGraphicsSceneHoverEvent
* event
)
321 QGraphicsWidget::hoverMoveEvent(event
);
323 const QPointF
& pos
= event
->pos();
324 updateHoveredRoleIndex(pos
);
325 if (m_hoveredRoleIndex
>= 0 && isAboveRoleGrip(pos
, m_hoveredRoleIndex
)) {
326 setCursor(Qt::SplitHCursor
);
332 void KItemListHeaderWidget::slotSortRoleChanged(const QByteArray
& current
, const QByteArray
& previous
)
339 void KItemListHeaderWidget::slotSortOrderChanged(Qt::SortOrder current
, Qt::SortOrder previous
)
346 void KItemListHeaderWidget::paintRole(QPainter
* painter
,
347 const QByteArray
& role
,
350 QWidget
* widget
) const
352 // The following code is based on the code from QHeaderView::paintSection().
353 // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
354 QStyleOptionHeader option
;
355 option
.section
= orderIndex
;
356 option
.state
= QStyle::State_None
| QStyle::State_Raised
| QStyle::State_Horizontal
;
358 option
.state
|= QStyle::State_Enabled
;
360 if (window() && window()->isActiveWindow()) {
361 option
.state
|= QStyle::State_Active
;
363 if (m_hoveredRoleIndex
== orderIndex
) {
364 option
.state
|= QStyle::State_MouseOver
;
366 if (m_pressedRoleIndex
== orderIndex
) {
367 option
.state
|= QStyle::State_Sunken
;
369 if (m_model
->sortRole() == role
) {
370 option
.sortIndicator
= (m_model
->sortOrder() == Qt::AscendingOrder
) ?
371 QStyleOptionHeader::SortDown
: QStyleOptionHeader::SortUp
;
373 option
.rect
= rect
.toRect();
375 if (m_columns
.count() == 1) {
376 option
.position
= QStyleOptionHeader::OnlyOneSection
;
377 } else if (orderIndex
== 0) {
378 option
.position
= QStyleOptionHeader::Beginning
;
379 } else if (orderIndex
== m_columns
.count() - 1) {
380 option
.position
= QStyleOptionHeader::End
;
382 option
.position
= QStyleOptionHeader::Middle
;
385 option
.orientation
= Qt::Horizontal
;
386 option
.selectedPosition
= QStyleOptionHeader::NotAdjacent
;
387 option
.text
= m_model
->roleDescription(role
);
389 style()->drawControl(QStyle::CE_Header
, &option
, painter
, widget
);
392 void KItemListHeaderWidget::updatePressedRoleIndex(const QPointF
& pos
)
394 const int pressedIndex
= roleIndexAt(pos
);
395 if (m_pressedRoleIndex
!= pressedIndex
) {
396 m_pressedRoleIndex
= pressedIndex
;
401 void KItemListHeaderWidget::updateHoveredRoleIndex(const QPointF
& pos
)
403 const int hoverIndex
= roleIndexAt(pos
);
404 if (m_hoveredRoleIndex
!= hoverIndex
) {
405 m_hoveredRoleIndex
= hoverIndex
;
410 int KItemListHeaderWidget::roleIndexAt(const QPointF
& pos
) const
415 foreach (const QByteArray
& role
, m_columns
) {
417 x
+= m_columnsWidths
.value(role
);
426 bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF
& pos
, int roleIndex
) const
429 for (int i
= 0; i
<= roleIndex
; ++i
) {
430 const QByteArray role
= m_columns
[i
];
431 x
+= m_columnsWidths
.value(role
);
434 const int grip
= style()->pixelMetric(QStyle::PM_HeaderGripMargin
);
435 return pos
.x() >= (x
- grip
) && pos
.x() <= x
;
438 QPixmap
KItemListHeaderWidget::createRolePixmap(int roleIndex
) const
440 const QByteArray role
= m_columns
[roleIndex
];
441 const qreal roleWidth
= m_columnsWidths
.value(role
);
442 const QRect
rect(0, 0, roleWidth
, size().height());
444 QImage
image(rect
.size(), QImage::Format_ARGB32_Premultiplied
);
446 QPainter
painter(&image
);
447 paintRole(&painter
, role
, rect
, roleIndex
);
449 // Apply a highlighting-color
450 const QPalette::ColorGroup group
= isActiveWindow() ? QPalette::Active
: QPalette::Inactive
;
451 QColor highlightColor
= palette().color(group
, QPalette::Highlight
);
452 highlightColor
.setAlpha(64);
453 painter
.fillRect(rect
, highlightColor
);
455 // Make the image transparent
456 painter
.setCompositionMode(QPainter::CompositionMode_DestinationIn
);
457 painter
.fillRect(0, 0, image
.width(), image
.height(), QColor(0, 0, 0, 192));
459 return QPixmap::fromImage(image
);
462 int KItemListHeaderWidget::targetOfMovingRole() const
464 const int movingWidth
= m_movingRole
.pixmap
.width();
465 const int movingLeft
= m_movingRole
.x
;
466 const int movingRight
= movingLeft
+ movingWidth
- 1;
469 qreal targetLeft
= 0;
470 while (targetIndex
< m_columns
.count()) {
471 const QByteArray role
= m_columns
[targetIndex
];
472 const qreal targetWidth
= m_columnsWidths
.value(role
);
473 const qreal targetRight
= targetLeft
+ targetWidth
- 1;
475 const bool isInTarget
= (targetWidth
>= movingWidth
&&
476 movingLeft
>= targetLeft
&&
477 movingRight
<= targetRight
) ||
478 (targetWidth
< movingWidth
&&
479 movingLeft
<= targetLeft
&&
480 movingRight
>= targetRight
);
486 targetLeft
+= targetWidth
;
490 return m_movingRole
.index
;
493 qreal
KItemListHeaderWidget::roleXPosition(const QByteArray
& role
) const
496 foreach (const QByteArray
& visibleRole
, m_columns
) {
497 if (visibleRole
== role
) {
501 x
+= m_columnsWidths
.value(visibleRole
);
507 #include "kitemlistheaderwidget_p.moc"