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