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