]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/private/kitemlistheaderwidget.cpp
Clean includes + port to QMenu
[dolphin.git] / src / kitemviews / private / kitemlistheaderwidget.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * *
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. *
8 * *
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. *
13 * *
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 ***************************************************************************/
19
20 #include "kitemlistheaderwidget.h"
21
22 #include <QMenu>
23 #include <kitemviews/kitemmodelbase.h>
24
25 #include <QApplication>
26 #include <QGraphicsSceneHoverEvent>
27 #include <QPainter>
28 #include <QStyleOptionHeader>
29
30 #include <KDebug>
31
32 KItemListHeaderWidget::KItemListHeaderWidget(QGraphicsWidget* parent) :
33 QGraphicsWidget(parent),
34 m_automaticColumnResizing(true),
35 m_model(0),
36 m_offset(0),
37 m_columns(),
38 m_columnWidths(),
39 m_preferredColumnWidths(),
40 m_hoveredRoleIndex(-1),
41 m_pressedRoleIndex(-1),
42 m_roleOperation(NoRoleOperation),
43 m_pressedMousePos(),
44 m_movingRole()
45 {
46 m_movingRole.x = 0;
47 m_movingRole.xDec = 0;
48 m_movingRole.index = -1;
49
50 setAcceptHoverEvents(true);
51 }
52
53 KItemListHeaderWidget::~KItemListHeaderWidget()
54 {
55 }
56
57 void KItemListHeaderWidget::setModel(KItemModelBase* model)
58 {
59 if (m_model == model) {
60 return;
61 }
62
63 if (m_model) {
64 disconnect(m_model, &KItemModelBase::sortRoleChanged,
65 this, &KItemListHeaderWidget::slotSortRoleChanged);
66 disconnect(m_model, &KItemModelBase::sortOrderChanged,
67 this, &KItemListHeaderWidget::slotSortOrderChanged);
68 }
69
70 m_model = model;
71
72 if (m_model) {
73 connect(m_model, &KItemModelBase::sortRoleChanged,
74 this, &KItemListHeaderWidget::slotSortRoleChanged);
75 connect(m_model, &KItemModelBase::sortOrderChanged,
76 this, &KItemListHeaderWidget::slotSortOrderChanged);
77 }
78 }
79
80 KItemModelBase* KItemListHeaderWidget::model() const
81 {
82 return m_model;
83 }
84
85 void KItemListHeaderWidget::setAutomaticColumnResizing(bool automatic)
86 {
87 m_automaticColumnResizing = automatic;
88 }
89
90 bool KItemListHeaderWidget::automaticColumnResizing() const
91 {
92 return m_automaticColumnResizing;
93 }
94
95 void KItemListHeaderWidget::setColumns(const QList<QByteArray>& roles)
96 {
97 foreach (const QByteArray& role, roles) {
98 if (!m_columnWidths.contains(role)) {
99 m_columnWidths.remove(role);
100 m_preferredColumnWidths.remove(role);
101 }
102 }
103
104 m_columns = roles;
105 update();
106 }
107
108 QList<QByteArray> KItemListHeaderWidget::columns() const
109 {
110 return m_columns;
111 }
112
113 void KItemListHeaderWidget::setColumnWidth(const QByteArray& role, qreal width)
114 {
115 const qreal minWidth = minimumColumnWidth();
116 if (width < minWidth) {
117 width = minWidth;
118 }
119
120 if (m_columnWidths.value(role) != width) {
121 m_columnWidths.insert(role, width);
122 update();
123 }
124 }
125
126 qreal KItemListHeaderWidget::columnWidth(const QByteArray& role) const
127 {
128 return m_columnWidths.value(role);
129 }
130
131 void KItemListHeaderWidget::setPreferredColumnWidth(const QByteArray& role, qreal width)
132 {
133 m_preferredColumnWidths.insert(role, width);
134 }
135
136 qreal KItemListHeaderWidget::preferredColumnWidth(const QByteArray& role) const
137 {
138 return m_preferredColumnWidths.value(role);
139 }
140
141 void KItemListHeaderWidget::setOffset(qreal offset)
142 {
143 if (m_offset != offset) {
144 m_offset = offset;
145 update();
146 }
147 }
148
149 qreal KItemListHeaderWidget::offset() const
150 {
151 return m_offset;
152 }
153
154 qreal KItemListHeaderWidget::minimumColumnWidth() const
155 {
156 QFontMetricsF fontMetrics(font());
157 return fontMetrics.height() * 4;
158 }
159
160 void KItemListHeaderWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
161 {
162 Q_UNUSED(option);
163 Q_UNUSED(widget);
164
165 if (!m_model) {
166 return;
167 }
168
169 // Draw roles
170 painter->setFont(font());
171 painter->setPen(palette().text().color());
172
173 qreal x = -m_offset;
174 int orderIndex = 0;
175 foreach (const QByteArray& role, m_columns) {
176 const qreal roleWidth = m_columnWidths.value(role);
177 const QRectF rect(x, 0, roleWidth, size().height());
178 paintRole(painter, role, rect, orderIndex, widget);
179 x += roleWidth;
180 ++orderIndex;
181 }
182
183 if (!m_movingRole.pixmap.isNull()) {
184 Q_ASSERT(m_roleOperation == MoveRoleOperation);
185 painter->drawPixmap(m_movingRole.x, 0, m_movingRole.pixmap);
186 }
187 }
188
189 void KItemListHeaderWidget::mousePressEvent(QGraphicsSceneMouseEvent* event)
190 {
191 if (event->button() & Qt::LeftButton) {
192 updatePressedRoleIndex(event->pos());
193 m_pressedMousePos = event->pos();
194 m_roleOperation = isAboveRoleGrip(m_pressedMousePos, m_pressedRoleIndex) ?
195 ResizeRoleOperation : NoRoleOperation;
196 event->accept();
197 } else {
198 event->ignore();
199 }
200 }
201
202 void KItemListHeaderWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
203 {
204 QGraphicsWidget::mouseReleaseEvent(event);
205
206 if (m_pressedRoleIndex == -1) {
207 return;
208 }
209
210 switch (m_roleOperation) {
211 case NoRoleOperation: {
212 // Only a click has been done and no moving or resizing has been started
213 const QByteArray sortRole = m_model->sortRole();
214 const int sortRoleIndex = m_columns.indexOf(sortRole);
215 if (m_pressedRoleIndex == sortRoleIndex) {
216 // Toggle the sort order
217 const Qt::SortOrder previous = m_model->sortOrder();
218 const Qt::SortOrder current = (m_model->sortOrder() == Qt::AscendingOrder) ?
219 Qt::DescendingOrder : Qt::AscendingOrder;
220 m_model->setSortOrder(current);
221 emit sortOrderChanged(current, previous);
222 } else {
223 // Change the sort role and reset to the ascending order
224 const QByteArray previous = m_model->sortRole();
225 const QByteArray current = m_columns[m_pressedRoleIndex];
226 m_model->setSortRole(current);
227 emit sortRoleChanged(current, previous);
228
229 if (m_model->sortOrder() == Qt::DescendingOrder) {
230 m_model->setSortOrder(Qt::AscendingOrder);
231 emit sortOrderChanged(Qt::AscendingOrder, Qt::DescendingOrder);
232 }
233 }
234 break;
235 }
236
237 case MoveRoleOperation:
238 m_movingRole.pixmap = QPixmap();
239 m_movingRole.x = 0;
240 m_movingRole.xDec = 0;
241 m_movingRole.index = -1;
242 break;
243
244 default:
245 break;
246 }
247
248 m_pressedRoleIndex = -1;
249 m_roleOperation = NoRoleOperation;
250 update();
251
252 QApplication::restoreOverrideCursor();
253 }
254
255 void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
256 {
257 QGraphicsWidget::mouseMoveEvent(event);
258
259 switch (m_roleOperation) {
260 case NoRoleOperation:
261 if ((event->pos() - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
262 // A role gets dragged by the user. Create a pixmap of the role that will get
263 // synchronized on each furter mouse-move-event with the mouse-position.
264 m_roleOperation = MoveRoleOperation;
265 const int roleIndex = roleIndexAt(m_pressedMousePos);
266 m_movingRole.index = roleIndex;
267 if (roleIndex == 0) {
268 // TODO: It should be configurable whether moving the first role is allowed.
269 // In the context of Dolphin this is not required, however this should be
270 // changed if KItemViews are used in a more generic way.
271 QApplication::setOverrideCursor(QCursor(Qt::ForbiddenCursor));
272 } else {
273 m_movingRole.pixmap = createRolePixmap(roleIndex);
274
275 qreal roleX = -m_offset;
276 for (int i = 0; i < roleIndex; ++i) {
277 const QByteArray role = m_columns[i];
278 roleX += m_columnWidths.value(role);
279 }
280
281 m_movingRole.xDec = event->pos().x() - roleX;
282 m_movingRole.x = roleX;
283 update();
284 }
285 }
286 break;
287
288 case ResizeRoleOperation: {
289 const QByteArray pressedRole = m_columns[m_pressedRoleIndex];
290
291 qreal previousWidth = m_columnWidths.value(pressedRole);
292 qreal currentWidth = previousWidth;
293 currentWidth += event->pos().x() - event->lastPos().x();
294 currentWidth = qMax(minimumColumnWidth(), currentWidth);
295
296 m_columnWidths.insert(pressedRole, currentWidth);
297 update();
298
299 emit columnWidthChanged(pressedRole, currentWidth, previousWidth);
300 break;
301 }
302
303 case MoveRoleOperation: {
304 // TODO: It should be configurable whether moving the first role is allowed.
305 // In the context of Dolphin this is not required, however this should be
306 // changed if KItemViews are used in a more generic way.
307 if (m_movingRole.index > 0) {
308 m_movingRole.x = event->pos().x() - m_movingRole.xDec;
309 update();
310
311 const int targetIndex = targetOfMovingRole();
312 if (targetIndex > 0 && targetIndex != m_movingRole.index) {
313 const QByteArray role = m_columns[m_movingRole.index];
314 const int previousIndex = m_movingRole.index;
315 m_movingRole.index = targetIndex;
316 emit columnMoved(role, targetIndex, previousIndex);
317
318 m_movingRole.xDec = event->pos().x() - roleXPosition(role);
319 }
320 }
321 break;
322 }
323
324 default:
325 break;
326 }
327 }
328
329 void KItemListHeaderWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event)
330 {
331 QGraphicsItem::mouseDoubleClickEvent(event);
332
333 const int roleIndex = roleIndexAt(event->pos());
334 if (roleIndex >= 0 && isAboveRoleGrip(event->pos(), roleIndex)) {
335 const QByteArray role = m_columns.at(roleIndex);
336
337 qreal previousWidth = columnWidth(role);
338 setColumnWidth(role, preferredColumnWidth(role));
339 qreal currentWidth = columnWidth(role);
340
341 emit columnWidthChanged(role, currentWidth, previousWidth);
342 }
343 }
344
345 void KItemListHeaderWidget::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
346 {
347 QGraphicsWidget::hoverEnterEvent(event);
348 updateHoveredRoleIndex(event->pos());
349 }
350
351 void KItemListHeaderWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
352 {
353 QGraphicsWidget::hoverLeaveEvent(event);
354 if (m_hoveredRoleIndex != -1) {
355 m_hoveredRoleIndex = -1;
356 update();
357 }
358 }
359
360 void KItemListHeaderWidget::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
361 {
362 QGraphicsWidget::hoverMoveEvent(event);
363
364 const QPointF& pos = event->pos();
365 updateHoveredRoleIndex(pos);
366 if (m_hoveredRoleIndex >= 0 && isAboveRoleGrip(pos, m_hoveredRoleIndex)) {
367 setCursor(Qt::SplitHCursor);
368 } else {
369 unsetCursor();
370 }
371 }
372
373 void KItemListHeaderWidget::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous)
374 {
375 Q_UNUSED(current);
376 Q_UNUSED(previous);
377 update();
378 }
379
380 void KItemListHeaderWidget::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
381 {
382 Q_UNUSED(current);
383 Q_UNUSED(previous);
384 update();
385 }
386
387 void KItemListHeaderWidget::paintRole(QPainter* painter,
388 const QByteArray& role,
389 const QRectF& rect,
390 int orderIndex,
391 QWidget* widget) const
392 {
393 // The following code is based on the code from QHeaderView::paintSection().
394 // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
395 QStyleOptionHeader option;
396 option.section = orderIndex;
397 option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
398 if (isEnabled()) {
399 option.state |= QStyle::State_Enabled;
400 }
401 if (window() && window()->isActiveWindow()) {
402 option.state |= QStyle::State_Active;
403 }
404 if (m_hoveredRoleIndex == orderIndex) {
405 option.state |= QStyle::State_MouseOver;
406 }
407 if (m_pressedRoleIndex == orderIndex) {
408 option.state |= QStyle::State_Sunken;
409 }
410 if (m_model->sortRole() == role) {
411 option.sortIndicator = (m_model->sortOrder() == Qt::AscendingOrder) ?
412 QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp;
413 }
414 option.rect = rect.toRect();
415
416 bool paintBackgroundForEmptyArea = false;
417
418 if (m_columns.count() == 1) {
419 option.position = QStyleOptionHeader::OnlyOneSection;
420 } else if (orderIndex == 0) {
421 option.position = QStyleOptionHeader::Beginning;
422 } else if (orderIndex == m_columns.count() - 1) {
423 // We are just painting the header for the last column. Check if there
424 // is some empty space to the right which needs to be filled.
425 if (rect.right() < size().width()) {
426 option.position = QStyleOptionHeader::Middle;
427 paintBackgroundForEmptyArea = true;
428 } else {
429 option.position = QStyleOptionHeader::End;
430 }
431 } else {
432 option.position = QStyleOptionHeader::Middle;
433 }
434
435 option.orientation = Qt::Horizontal;
436 option.selectedPosition = QStyleOptionHeader::NotAdjacent;
437 option.text = m_model->roleDescription(role);
438
439 style()->drawControl(QStyle::CE_Header, &option, painter, widget);
440
441 if (paintBackgroundForEmptyArea) {
442 option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
443 option.section = m_columns.count();
444 option.sortIndicator = QStyleOptionHeader::None;
445
446 qreal backgroundRectX = rect.x() + rect.width();
447 QRectF backgroundRect(backgroundRectX, 0.0, size().width() - backgroundRectX, rect.height());
448 option.rect = backgroundRect.toRect();
449 option.position = QStyleOptionHeader::End;
450 option.text = QString();
451
452 style()->drawControl(QStyle::CE_Header, &option, painter, widget);
453 }
454 }
455
456 void KItemListHeaderWidget::updatePressedRoleIndex(const QPointF& pos)
457 {
458 const int pressedIndex = roleIndexAt(pos);
459 if (m_pressedRoleIndex != pressedIndex) {
460 m_pressedRoleIndex = pressedIndex;
461 update();
462 }
463 }
464
465 void KItemListHeaderWidget::updateHoveredRoleIndex(const QPointF& pos)
466 {
467 const int hoverIndex = roleIndexAt(pos);
468 if (m_hoveredRoleIndex != hoverIndex) {
469 m_hoveredRoleIndex = hoverIndex;
470 update();
471 }
472 }
473
474 int KItemListHeaderWidget::roleIndexAt(const QPointF& pos) const
475 {
476 int index = -1;
477
478 qreal x = -m_offset;
479 foreach (const QByteArray& role, m_columns) {
480 ++index;
481 x += m_columnWidths.value(role);
482 if (pos.x() <= x) {
483 break;
484 }
485 }
486
487 return index;
488 }
489
490 bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF& pos, int roleIndex) const
491 {
492 qreal x = -m_offset;
493 for (int i = 0; i <= roleIndex; ++i) {
494 const QByteArray role = m_columns[i];
495 x += m_columnWidths.value(role);
496 }
497
498 const int grip = style()->pixelMetric(QStyle::PM_HeaderGripMargin);
499 return pos.x() >= (x - grip) && pos.x() <= x;
500 }
501
502 QPixmap KItemListHeaderWidget::createRolePixmap(int roleIndex) const
503 {
504 const QByteArray role = m_columns[roleIndex];
505 const qreal roleWidth = m_columnWidths.value(role);
506 const QRect rect(0, 0, roleWidth, size().height());
507
508 QImage image(rect.size(), QImage::Format_ARGB32_Premultiplied);
509
510 QPainter painter(&image);
511 paintRole(&painter, role, rect, roleIndex);
512
513 // Apply a highlighting-color
514 const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive;
515 QColor highlightColor = palette().color(group, QPalette::Highlight);
516 highlightColor.setAlpha(64);
517 painter.fillRect(rect, highlightColor);
518
519 // Make the image transparent
520 painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
521 painter.fillRect(0, 0, image.width(), image.height(), QColor(0, 0, 0, 192));
522
523 return QPixmap::fromImage(image);
524 }
525
526 int KItemListHeaderWidget::targetOfMovingRole() const
527 {
528 const int movingWidth = m_movingRole.pixmap.width();
529 const int movingLeft = m_movingRole.x;
530 const int movingRight = movingLeft + movingWidth - 1;
531
532 int targetIndex = 0;
533 qreal targetLeft = -m_offset;
534 while (targetIndex < m_columns.count()) {
535 const QByteArray role = m_columns[targetIndex];
536 const qreal targetWidth = m_columnWidths.value(role);
537 const qreal targetRight = targetLeft + targetWidth - 1;
538
539 const bool isInTarget = (targetWidth >= movingWidth &&
540 movingLeft >= targetLeft &&
541 movingRight <= targetRight) ||
542 (targetWidth < movingWidth &&
543 movingLeft <= targetLeft &&
544 movingRight >= targetRight);
545
546 if (isInTarget) {
547 return targetIndex;
548 }
549
550 targetLeft += targetWidth;
551 ++targetIndex;
552 }
553
554 return m_movingRole.index;
555 }
556
557 qreal KItemListHeaderWidget::roleXPosition(const QByteArray& role) const
558 {
559 qreal x = -m_offset;
560 foreach (const QByteArray& visibleRole, m_columns) {
561 if (visibleRole == role) {
562 return x;
563 }
564
565 x += m_columnWidths.value(visibleRole);
566 }
567
568 return -1;
569 }
570