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