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