]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistheaderwidget.cpp
d3a8f001b53bf139c1c356718e79a46b8e13e289
[dolphin.git] / src / kitemviews / 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_p.h"
21
22 #include <KAction>
23 #include <KMenu>
24 #include "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_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 QStyleOptionHeader option;
53 const QSize headerSize = style()->sizeFromContents(QStyle::CT_HeaderSection, &option, QSize());
54 resize(0, headerSize.height());
55 }
56
57 KItemListHeaderWidget::~KItemListHeaderWidget()
58 {
59 }
60
61 void KItemListHeaderWidget::setModel(KItemModelBase* model)
62 {
63 if (m_model == model) {
64 return;
65 }
66
67 if (m_model) {
68 disconnect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
69 this, SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
70 disconnect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
71 this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
72 }
73
74 m_model = model;
75
76 if (m_model) {
77 connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
78 this, SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
79 connect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
80 this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
81 }
82 }
83
84 KItemModelBase* KItemListHeaderWidget::model() const
85 {
86 return m_model;
87 }
88
89 void KItemListHeaderWidget::setAutomaticColumnResizing(bool automatic)
90 {
91 m_automaticColumnResizing = automatic;
92 }
93
94 bool KItemListHeaderWidget::automaticColumnResizing() const
95 {
96 return m_automaticColumnResizing;
97 }
98
99 void KItemListHeaderWidget::setColumns(const QList<QByteArray>& roles)
100 {
101 foreach (const QByteArray& role, roles) {
102 if (!m_columnWidths.contains(role)) {
103 m_columnWidths.remove(role);
104 m_preferredColumnWidths.remove(role);
105 }
106 }
107
108 m_columns = roles;
109 update();
110 }
111
112 QList<QByteArray> KItemListHeaderWidget::columns() const
113 {
114 return m_columns;
115 }
116
117 void KItemListHeaderWidget::setColumnWidth(const QByteArray& role, qreal width)
118 {
119 const qreal minWidth = minimumColumnWidth();
120 if (width < minWidth) {
121 width = minWidth;
122 }
123
124 if (m_columnWidths.value(role) != width) {
125 m_columnWidths.insert(role, width);
126 update();
127 }
128 }
129
130 qreal KItemListHeaderWidget::columnWidth(const QByteArray& role) const
131 {
132 return m_columnWidths.value(role);
133 }
134
135 void KItemListHeaderWidget::setPreferredColumnWidth(const QByteArray& role, qreal width)
136 {
137 m_preferredColumnWidths.insert(role, width);
138 }
139
140 qreal KItemListHeaderWidget::preferredColumnWidth(const QByteArray& role) const
141 {
142 return m_preferredColumnWidths.value(role);
143 }
144
145 qreal KItemListHeaderWidget::minimumColumnWidth() const
146 {
147 QFontMetricsF fontMetrics(font());
148 return fontMetrics.height() * 4;
149 }
150
151 void KItemListHeaderWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
152 {
153 Q_UNUSED(option);
154 Q_UNUSED(widget);
155
156 if (!m_model) {
157 return;
158 }
159
160 // Draw roles
161 painter->setFont(font());
162 painter->setPen(palette().text().color());
163
164 qreal x = 0;
165 int orderIndex = 0;
166 foreach (const QByteArray& role, m_columns) {
167 const qreal roleWidth = m_columnWidths.value(role);
168 const QRectF rect(x, 0, roleWidth, size().height());
169 paintRole(painter, role, rect, orderIndex, widget);
170 x += roleWidth;
171 ++orderIndex;
172 }
173
174 // Draw background without roles
175 QStyleOption opt;
176 opt.init(widget);
177 opt.rect = QRect(x, 0, size().width() - x, size().height());
178 opt.state |= QStyle::State_Horizontal;
179 style()->drawControl(QStyle::CE_HeaderEmptyArea, &opt, painter);
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
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 break;
228 }
229
230 case MoveRoleOperation:
231 m_movingRole.pixmap = QPixmap();
232 m_movingRole.x = 0;
233 m_movingRole.xDec = 0;
234 m_movingRole.index = -1;
235 break;
236
237 default:
238 break;
239 }
240
241 m_pressedRoleIndex = -1;
242 m_roleOperation = NoRoleOperation;
243 update();
244
245 QApplication::restoreOverrideCursor();
246 }
247
248 void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
249 {
250 QGraphicsWidget::mouseMoveEvent(event);
251
252 switch (m_roleOperation) {
253 case NoRoleOperation:
254 if ((event->pos() - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
255 // A role gets dragged by the user. Create a pixmap of the role that will get
256 // synchronized on each furter mouse-move-event with the mouse-position.
257 m_roleOperation = MoveRoleOperation;
258 const int roleIndex = roleIndexAt(m_pressedMousePos);
259 m_movingRole.index = roleIndex;
260 if (roleIndex == 0) {
261 // TODO: It should be configurable whether moving the first role is allowed.
262 // In the context of Dolphin this is not required, however this should be
263 // changed if KItemViews are used in a more generic way.
264 QApplication::setOverrideCursor(QCursor(Qt::ForbiddenCursor));
265 } else {
266 m_movingRole.pixmap = createRolePixmap(roleIndex);
267
268 qreal roleX = 0;
269 for (int i = 0; i < roleIndex; ++i) {
270 const QByteArray role = m_columns[i];
271 roleX += m_columnWidths.value(role);
272 }
273
274 m_movingRole.xDec = event->pos().x() - roleX;
275 m_movingRole.x = roleX;
276 update();
277 }
278 }
279 break;
280
281 case ResizeRoleOperation: {
282 const QByteArray pressedRole = m_columns[m_pressedRoleIndex];
283
284 qreal previousWidth = m_columnWidths.value(pressedRole);
285 qreal currentWidth = previousWidth;
286 currentWidth += event->pos().x() - event->lastPos().x();
287 currentWidth = qMax(minimumColumnWidth(), currentWidth);
288
289 m_columnWidths.insert(pressedRole, currentWidth);
290 update();
291
292 emit columnWidthChanged(pressedRole, currentWidth, previousWidth);
293 break;
294 }
295
296 case MoveRoleOperation: {
297 // TODO: It should be configurable whether moving the first role is allowed.
298 // In the context of Dolphin this is not required, however this should be
299 // changed if KItemViews are used in a more generic way.
300 if (m_movingRole.index > 0) {
301 m_movingRole.x = event->pos().x() - m_movingRole.xDec;
302 update();
303
304 const int targetIndex = targetOfMovingRole();
305 if (targetIndex > 0 && targetIndex != m_movingRole.index) {
306 const QByteArray role = m_columns[m_movingRole.index];
307 const int previousIndex = m_movingRole.index;
308 m_movingRole.index = targetIndex;
309 emit columnMoved(role, targetIndex, previousIndex);
310
311 m_movingRole.xDec = event->pos().x() - roleXPosition(role);
312 }
313 }
314 break;
315 }
316
317 default:
318 break;
319 }
320 }
321
322 void KItemListHeaderWidget::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
323 {
324 QGraphicsWidget::hoverEnterEvent(event);
325 updateHoveredRoleIndex(event->pos());
326 }
327
328 void KItemListHeaderWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
329 {
330 QGraphicsWidget::hoverLeaveEvent(event);
331 if (m_hoveredRoleIndex != -1) {
332 m_hoveredRoleIndex = -1;
333 update();
334 }
335 }
336
337 void KItemListHeaderWidget::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
338 {
339 QGraphicsWidget::hoverMoveEvent(event);
340
341 const QPointF& pos = event->pos();
342 updateHoveredRoleIndex(pos);
343 if (m_hoveredRoleIndex >= 0 && isAboveRoleGrip(pos, m_hoveredRoleIndex)) {
344 setCursor(Qt::SplitHCursor);
345 } else {
346 unsetCursor();
347 }
348 }
349
350 void KItemListHeaderWidget::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous)
351 {
352 Q_UNUSED(current);
353 Q_UNUSED(previous);
354 update();
355 }
356
357 void KItemListHeaderWidget::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
358 {
359 Q_UNUSED(current);
360 Q_UNUSED(previous);
361 update();
362 }
363
364 void KItemListHeaderWidget::paintRole(QPainter* painter,
365 const QByteArray& role,
366 const QRectF& rect,
367 int orderIndex,
368 QWidget* widget) const
369 {
370 // The following code is based on the code from QHeaderView::paintSection().
371 // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
372 QStyleOptionHeader option;
373 option.section = orderIndex;
374 option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
375 if (isEnabled()) {
376 option.state |= QStyle::State_Enabled;
377 }
378 if (window() && window()->isActiveWindow()) {
379 option.state |= QStyle::State_Active;
380 }
381 if (m_hoveredRoleIndex == orderIndex) {
382 option.state |= QStyle::State_MouseOver;
383 }
384 if (m_pressedRoleIndex == orderIndex) {
385 option.state |= QStyle::State_Sunken;
386 }
387 if (m_model->sortRole() == role) {
388 option.sortIndicator = (m_model->sortOrder() == Qt::AscendingOrder) ?
389 QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp;
390 }
391 option.rect = rect.toRect();
392
393 if (m_columns.count() == 1) {
394 option.position = QStyleOptionHeader::OnlyOneSection;
395 } else if (orderIndex == 0) {
396 option.position = QStyleOptionHeader::Beginning;
397 } else if (orderIndex == m_columns.count() - 1) {
398 option.position = QStyleOptionHeader::End;
399 } else {
400 option.position = QStyleOptionHeader::Middle;
401 }
402
403 option.orientation = Qt::Horizontal;
404 option.selectedPosition = QStyleOptionHeader::NotAdjacent;
405 option.text = m_model->roleDescription(role);
406
407 style()->drawControl(QStyle::CE_Header, &option, painter, widget);
408 }
409
410 void KItemListHeaderWidget::updatePressedRoleIndex(const QPointF& pos)
411 {
412 const int pressedIndex = roleIndexAt(pos);
413 if (m_pressedRoleIndex != pressedIndex) {
414 m_pressedRoleIndex = pressedIndex;
415 update();
416 }
417 }
418
419 void KItemListHeaderWidget::updateHoveredRoleIndex(const QPointF& pos)
420 {
421 const int hoverIndex = roleIndexAt(pos);
422 if (m_hoveredRoleIndex != hoverIndex) {
423 m_hoveredRoleIndex = hoverIndex;
424 update();
425 }
426 }
427
428 int KItemListHeaderWidget::roleIndexAt(const QPointF& pos) const
429 {
430 int index = -1;
431
432 qreal x = 0;
433 foreach (const QByteArray& role, m_columns) {
434 ++index;
435 x += m_columnWidths.value(role);
436 if (pos.x() <= x) {
437 break;
438 }
439 }
440
441 return index;
442 }
443
444 bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF& pos, int roleIndex) const
445 {
446 qreal x = 0;
447 for (int i = 0; i <= roleIndex; ++i) {
448 const QByteArray role = m_columns[i];
449 x += m_columnWidths.value(role);
450 }
451
452 const int grip = style()->pixelMetric(QStyle::PM_HeaderGripMargin);
453 return pos.x() >= (x - grip) && pos.x() <= x;
454 }
455
456 QPixmap KItemListHeaderWidget::createRolePixmap(int roleIndex) const
457 {
458 const QByteArray role = m_columns[roleIndex];
459 const qreal roleWidth = m_columnWidths.value(role);
460 const QRect rect(0, 0, roleWidth, size().height());
461
462 QImage image(rect.size(), QImage::Format_ARGB32_Premultiplied);
463
464 QPainter painter(&image);
465 paintRole(&painter, role, rect, roleIndex);
466
467 // Apply a highlighting-color
468 const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive;
469 QColor highlightColor = palette().color(group, QPalette::Highlight);
470 highlightColor.setAlpha(64);
471 painter.fillRect(rect, highlightColor);
472
473 // Make the image transparent
474 painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
475 painter.fillRect(0, 0, image.width(), image.height(), QColor(0, 0, 0, 192));
476
477 return QPixmap::fromImage(image);
478 }
479
480 int KItemListHeaderWidget::targetOfMovingRole() const
481 {
482 const int movingWidth = m_movingRole.pixmap.width();
483 const int movingLeft = m_movingRole.x;
484 const int movingRight = movingLeft + movingWidth - 1;
485
486 int targetIndex = 0;
487 qreal targetLeft = 0;
488 while (targetIndex < m_columns.count()) {
489 const QByteArray role = m_columns[targetIndex];
490 const qreal targetWidth = m_columnWidths.value(role);
491 const qreal targetRight = targetLeft + targetWidth - 1;
492
493 const bool isInTarget = (targetWidth >= movingWidth &&
494 movingLeft >= targetLeft &&
495 movingRight <= targetRight) ||
496 (targetWidth < movingWidth &&
497 movingLeft <= targetLeft &&
498 movingRight >= targetRight);
499
500 if (isInTarget) {
501 return targetIndex;
502 }
503
504 targetLeft += targetWidth;
505 ++targetIndex;
506 }
507
508 return m_movingRole.index;
509 }
510
511 qreal KItemListHeaderWidget::roleXPosition(const QByteArray& role) const
512 {
513 qreal x = 0;
514 foreach (const QByteArray& visibleRole, m_columns) {
515 if (visibleRole == role) {
516 return x;
517 }
518
519 x += m_columnWidths.value(visibleRole);
520 }
521
522 return -1;
523 }
524
525 #include "kitemlistheaderwidget_p.moc"