]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistwidget.cpp
New selection effects
[dolphin.git] / src / kitemviews / kitemlistwidget.cpp
1 /*
2 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
3 *
4 * Based on the Itemviews NG project from Trolltech Labs
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9 #include "kitemlistwidget.h"
10
11 #include "kitemlistview.h"
12 #include "private/kitemlistselectiontoggle.h"
13
14 #include <KConfigGroup>
15 #include <KSharedConfig>
16
17 #include <QApplication>
18 #include <QPainter>
19 #include <QPropertyAnimation>
20 #include <QStyleOption>
21
22 KItemListWidgetInformant::KItemListWidgetInformant()
23 {
24 }
25
26 KItemListWidgetInformant::~KItemListWidgetInformant()
27 {
28 }
29
30 KItemListWidget::KItemListWidget(KItemListWidgetInformant *informant, QGraphicsItem *parent)
31 : QGraphicsWidget(parent)
32 , m_informant(informant)
33 , m_index(-1)
34 , m_selected(false)
35 , m_current(false)
36 , m_hovered(false)
37 , m_expansionAreaHovered(false)
38 , m_alternateBackground(false)
39 , m_enabledSelectionToggle(false)
40 , m_clickHighlighted(false)
41 , m_data()
42 , m_visibleRoles()
43 , m_columnWidths()
44 , m_leftPadding(0)
45 , m_rightPadding(0)
46 , m_styleOption()
47 , m_siblingsInfo()
48 , m_hoverOpacity(0)
49 , m_hoverCache(nullptr)
50 , m_hoverSequenceIndex(0)
51 , m_selectionToggle(nullptr)
52 , m_editedRole()
53 , m_iconSize(-1)
54 {
55 connect(&m_hoverSequenceTimer, &QTimer::timeout, this, &KItemListWidget::slotHoverSequenceTimerTimeout);
56 }
57
58 KItemListWidget::~KItemListWidget()
59 {
60 clearHoverCache();
61 }
62
63 void KItemListWidget::setIndex(int index)
64 {
65 if (m_index != index) {
66 delete m_selectionToggle;
67 m_selectionToggle = nullptr;
68
69 m_hoverOpacity = 0;
70
71 clearHoverCache();
72
73 m_index = index;
74 }
75 }
76
77 int KItemListWidget::index() const
78 {
79 return m_index;
80 }
81
82 void KItemListWidget::setData(const QHash<QByteArray, QVariant> &data, const QSet<QByteArray> &roles)
83 {
84 clearHoverCache();
85 if (roles.isEmpty()) {
86 m_data = data;
87 dataChanged(m_data);
88 } else {
89 for (const QByteArray &role : roles) {
90 m_data[role] = data[role];
91 }
92 dataChanged(m_data, roles);
93 }
94 update();
95 }
96
97 QHash<QByteArray, QVariant> KItemListWidget::data() const
98 {
99 return m_data;
100 }
101
102 QVariant KItemListWidget::value(const QByteArray &key) const
103 {
104 return m_data.value(key);
105 }
106
107 void KItemListWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
108 {
109 Q_UNUSED(option)
110
111 if (m_alternateBackground) {
112 QColor backgroundColor = m_styleOption.palette.color(QPalette::AlternateBase);
113 if (!widget->hasFocus()) {
114 QColor baseColor = m_styleOption.palette.color(QPalette::Base);
115 if (baseColor.lightnessF() > 0.5) {
116 // theme seems light
117 backgroundColor = backgroundColor.lighter(101);
118 } else {
119 // theme seems dark
120 backgroundColor = backgroundColor.darker(101);
121 }
122 }
123
124 const QRectF backgroundRect(0, 0, size().width(), size().height());
125 painter->fillRect(backgroundRect, backgroundColor);
126 }
127
128 if ((m_selected || m_current) && m_editedRole.isEmpty()) {
129 const QStyle::State activeState(isActiveWindow() && widget->hasFocus() ? QStyle::State_Active : 0);
130 drawItemStyleOption(painter, widget, activeState | QStyle::State_Enabled | QStyle::State_Selected | QStyle::State_Item);
131 }
132
133 if (m_hoverOpacity > 0.0) {
134 if (!m_hoverCache) {
135 // Initialize the m_hoverCache pixmap to improve the drawing performance
136 // when fading the hover background
137 m_hoverCache = new QPixmap(size().toSize());
138 m_hoverCache->fill(Qt::transparent);
139
140 QPainter pixmapPainter(m_hoverCache);
141 const QStyle::State activeState(isActiveWindow() && widget->hasFocus() ? QStyle::State_Active | QStyle::State_Enabled : 0);
142 drawItemStyleOption(&pixmapPainter, widget, activeState | QStyle::State_MouseOver | QStyle::State_Item);
143 }
144
145 const qreal opacity = painter->opacity();
146 painter->setOpacity(m_hoverOpacity * opacity);
147 painter->drawPixmap(0, 0, *m_hoverCache);
148 painter->setOpacity(opacity);
149 }
150 }
151
152 void KItemListWidget::setVisibleRoles(const QList<QByteArray> &roles)
153 {
154 const QList<QByteArray> previousRoles = m_visibleRoles;
155 m_visibleRoles = roles;
156
157 visibleRolesChanged(roles, previousRoles);
158 update();
159 }
160
161 QList<QByteArray> KItemListWidget::visibleRoles() const
162 {
163 return m_visibleRoles;
164 }
165
166 void KItemListWidget::setColumnWidth(const QByteArray &role, qreal width)
167 {
168 const qreal previousWidth = m_columnWidths.value(role);
169 if (previousWidth != width) {
170 m_columnWidths.insert(role, width);
171 columnWidthChanged(role, width, previousWidth);
172 update();
173 }
174 }
175
176 qreal KItemListWidget::columnWidth(const QByteArray &role) const
177 {
178 return m_columnWidths.value(role);
179 }
180
181 void KItemListWidget::setSidePadding(qreal leftPaddingWidth, qreal rightPaddingWidth)
182 {
183 bool changed = false;
184 if (m_leftPadding != leftPaddingWidth) {
185 m_leftPadding = leftPaddingWidth;
186 changed = true;
187 }
188
189 if (m_rightPadding != rightPaddingWidth) {
190 m_rightPadding = rightPaddingWidth;
191 changed = true;
192 }
193
194 if (!changed) {
195 return;
196 }
197
198 sidePaddingChanged(leftPaddingWidth, rightPaddingWidth);
199 update();
200 }
201
202 qreal KItemListWidget::leftPadding() const
203 {
204 return m_leftPadding;
205 }
206
207 qreal KItemListWidget::rightPadding() const
208 {
209 return m_rightPadding;
210 }
211
212 void KItemListWidget::setStyleOption(const KItemListStyleOption &option)
213 {
214 if (m_styleOption == option) {
215 return;
216 }
217
218 const KItemListStyleOption previous = m_styleOption;
219 clearHoverCache();
220 m_styleOption = option;
221 styleOptionChanged(option, previous);
222 update();
223 }
224
225 const KItemListStyleOption &KItemListWidget::styleOption() const
226 {
227 return m_styleOption;
228 }
229
230 void KItemListWidget::setSelected(bool selected)
231 {
232 if (m_selected != selected) {
233 m_selected = selected;
234 if (m_selectionToggle) {
235 m_selectionToggle->setChecked(selected);
236 }
237 selectedChanged(selected);
238 update();
239 }
240 }
241
242 bool KItemListWidget::isSelected() const
243 {
244 return m_selected;
245 }
246
247 void KItemListWidget::setCurrent(bool current)
248 {
249 if (m_current != current) {
250 m_current = current;
251 currentChanged(current);
252 update();
253 }
254 }
255
256 bool KItemListWidget::isCurrent() const
257 {
258 return m_current;
259 }
260
261 void KItemListWidget::setHovered(bool hovered)
262 {
263 if (hovered == m_hovered) {
264 return;
265 }
266
267 m_hovered = hovered;
268
269 m_hoverSequenceIndex = 0;
270
271 if (hovered) {
272 setHoverOpacity(1.0);
273
274 if (m_enabledSelectionToggle && !(QApplication::mouseButtons() & Qt::LeftButton)) {
275 initializeSelectionToggle();
276 }
277
278 hoverSequenceStarted();
279
280 const KConfigGroup globalConfig(KSharedConfig::openConfig(), QStringLiteral("PreviewSettings"));
281 const int interval = globalConfig.readEntry("HoverSequenceInterval", 700);
282
283 m_hoverSequenceTimer.start(interval);
284 } else {
285 setHoverOpacity(0.0);
286
287 if (m_selectionToggle) {
288 m_selectionToggle->deleteLater();
289 m_selectionToggle = nullptr;
290 }
291
292 hoverSequenceEnded();
293 m_hoverSequenceTimer.stop();
294 }
295
296 hoveredChanged(hovered);
297 update();
298 }
299
300 bool KItemListWidget::isHovered() const
301 {
302 return m_hovered;
303 }
304
305 void KItemListWidget::setExpansionAreaHovered(bool hovered)
306 {
307 if (hovered == m_expansionAreaHovered) {
308 return;
309 }
310 m_expansionAreaHovered = hovered;
311 update();
312 }
313
314 bool KItemListWidget::expansionAreaHovered() const
315 {
316 return m_expansionAreaHovered;
317 }
318
319 void KItemListWidget::setHoverPosition(const QPointF &pos)
320 {
321 if (m_selectionToggle) {
322 m_selectionToggle->setHovered(selectionToggleRect().contains(pos));
323 }
324 }
325
326 void KItemListWidget::setAlternateBackground(bool enable)
327 {
328 if (m_alternateBackground != enable) {
329 m_alternateBackground = enable;
330 alternateBackgroundChanged(enable);
331 update();
332 }
333 }
334
335 bool KItemListWidget::alternateBackground() const
336 {
337 return m_alternateBackground;
338 }
339
340 void KItemListWidget::setEnabledSelectionToggle(bool enable)
341 {
342 if (m_enabledSelectionToggle != enable) {
343 m_enabledSelectionToggle = enable;
344
345 // We want the change to take effect immediately.
346 if (m_enabledSelectionToggle) {
347 if (m_hovered) {
348 initializeSelectionToggle();
349 }
350 } else if (m_selectionToggle) {
351 m_selectionToggle->deleteLater();
352 m_selectionToggle = nullptr;
353 }
354
355 update();
356 }
357 }
358
359 bool KItemListWidget::enabledSelectionToggle() const
360 {
361 return m_enabledSelectionToggle;
362 }
363
364 void KItemListWidget::setSiblingsInformation(const QBitArray &siblings)
365 {
366 const QBitArray previous = m_siblingsInfo;
367 m_siblingsInfo = siblings;
368 siblingsInformationChanged(m_siblingsInfo, previous);
369 update();
370 }
371
372 QBitArray KItemListWidget::siblingsInformation() const
373 {
374 return m_siblingsInfo;
375 }
376
377 void KItemListWidget::setEditedRole(const QByteArray &role)
378 {
379 if (m_editedRole != role) {
380 const QByteArray previous = m_editedRole;
381 m_editedRole = role;
382 editedRoleChanged(role, previous);
383 }
384 }
385
386 QByteArray KItemListWidget::editedRole() const
387 {
388 return m_editedRole;
389 }
390
391 void KItemListWidget::setIconSize(int iconSize)
392 {
393 if (m_iconSize != iconSize) {
394 const int previousIconSize = m_iconSize;
395 m_iconSize = iconSize;
396 iconSizeChanged(iconSize, previousIconSize);
397 }
398 }
399
400 int KItemListWidget::iconSize() const
401 {
402 return m_iconSize;
403 }
404
405 bool KItemListWidget::contains(const QPointF &point) const
406 {
407 if (!QGraphicsWidget::contains(point)) {
408 return false;
409 }
410
411 return selectionRectFull().contains(point) || expansionToggleRect().contains(point);
412 }
413
414 QRectF KItemListWidget::textFocusRect() const
415 {
416 return textRect();
417 }
418
419 QRectF KItemListWidget::selectionToggleRect() const
420 {
421 return QRectF();
422 }
423
424 QRectF KItemListWidget::expansionToggleRect() const
425 {
426 return QRectF();
427 }
428
429 QPixmap KItemListWidget::createDragPixmap(const QStyleOptionGraphicsItem *option, QWidget *widget)
430 {
431 QPixmap pixmap(size().toSize() * widget->devicePixelRatio());
432 pixmap.setDevicePixelRatio(widget->devicePixelRatio());
433 pixmap.fill(Qt::transparent);
434
435 QPainter painter(&pixmap);
436
437 const bool oldAlternateBackground = m_alternateBackground;
438 const bool wasSelected = m_selected;
439 const bool wasHovered = m_hovered;
440
441 setAlternateBackground(false);
442 setHovered(false);
443
444 paint(&painter, option, widget);
445
446 setAlternateBackground(oldAlternateBackground);
447 setSelected(wasSelected);
448 setHovered(wasHovered);
449
450 return pixmap;
451 }
452
453 void KItemListWidget::startActivateSoonAnimation(int timeUntilActivation)
454 {
455 Q_UNUSED(timeUntilActivation)
456 }
457
458 void KItemListWidget::dataChanged(const QHash<QByteArray, QVariant> &current, const QSet<QByteArray> &roles)
459 {
460 Q_UNUSED(current)
461 Q_UNUSED(roles)
462 }
463
464 void KItemListWidget::visibleRolesChanged(const QList<QByteArray> &current, const QList<QByteArray> &previous)
465 {
466 Q_UNUSED(current)
467 Q_UNUSED(previous)
468 }
469
470 void KItemListWidget::columnWidthChanged(const QByteArray &role, qreal current, qreal previous)
471 {
472 Q_UNUSED(role)
473 Q_UNUSED(current)
474 Q_UNUSED(previous)
475 }
476
477 void KItemListWidget::sidePaddingChanged(qreal leftPaddingWidth, qreal rightPaddingWidth)
478 {
479 Q_UNUSED(leftPaddingWidth)
480 Q_UNUSED(rightPaddingWidth)
481 }
482
483 void KItemListWidget::styleOptionChanged(const KItemListStyleOption &current, const KItemListStyleOption &previous)
484 {
485 Q_UNUSED(previous)
486
487 // set the initial value of m_iconSize if not set
488 if (m_iconSize == -1) {
489 m_iconSize = current.iconSize;
490 }
491 }
492
493 void KItemListWidget::currentChanged(bool current)
494 {
495 Q_UNUSED(current)
496 }
497
498 void KItemListWidget::selectedChanged(bool selected)
499 {
500 Q_UNUSED(selected)
501 }
502
503 void KItemListWidget::hoveredChanged(bool hovered)
504 {
505 Q_UNUSED(hovered)
506 }
507
508 void KItemListWidget::alternateBackgroundChanged(bool enabled)
509 {
510 Q_UNUSED(enabled)
511 }
512
513 void KItemListWidget::siblingsInformationChanged(const QBitArray &current, const QBitArray &previous)
514 {
515 Q_UNUSED(current)
516 Q_UNUSED(previous)
517 }
518
519 void KItemListWidget::editedRoleChanged(const QByteArray &current, const QByteArray &previous)
520 {
521 Q_UNUSED(current)
522 Q_UNUSED(previous)
523 }
524
525 void KItemListWidget::iconSizeChanged(int current, int previous)
526 {
527 Q_UNUSED(current)
528 Q_UNUSED(previous)
529 }
530
531 void KItemListWidget::resizeEvent(QGraphicsSceneResizeEvent *event)
532 {
533 QGraphicsWidget::resizeEvent(event);
534 clearHoverCache();
535
536 if (m_selectionToggle) {
537 const QRectF &toggleRect = selectionToggleRect();
538 m_selectionToggle->setPos(toggleRect.topLeft());
539 m_selectionToggle->resize(toggleRect.size());
540 }
541 }
542
543 void KItemListWidget::hoverSequenceStarted()
544 {
545 }
546
547 void KItemListWidget::hoverSequenceIndexChanged(int sequenceIndex)
548 {
549 Q_UNUSED(sequenceIndex);
550 }
551
552 void KItemListWidget::hoverSequenceEnded()
553 {
554 }
555
556 qreal KItemListWidget::hoverOpacity() const
557 {
558 return m_hoverOpacity;
559 }
560
561 int KItemListWidget::hoverSequenceIndex() const
562 {
563 return m_hoverSequenceIndex;
564 }
565
566 void KItemListWidget::slotHoverSequenceTimerTimeout()
567 {
568 m_hoverSequenceIndex++;
569 hoverSequenceIndexChanged(m_hoverSequenceIndex);
570 }
571
572 void KItemListWidget::initializeSelectionToggle()
573 {
574 Q_ASSERT(m_enabledSelectionToggle);
575
576 if (!m_selectionToggle) {
577 m_selectionToggle = new KItemListSelectionToggle(this);
578 }
579
580 const QRectF toggleRect = selectionToggleRect();
581 m_selectionToggle->setPos(toggleRect.topLeft());
582 m_selectionToggle->resize(toggleRect.size());
583
584 m_selectionToggle->setChecked(isSelected());
585 }
586
587 void KItemListWidget::setHoverOpacity(qreal opacity)
588 {
589 m_hoverOpacity = opacity;
590 if (m_selectionToggle) {
591 m_selectionToggle->setOpacity(opacity);
592 }
593
594 if (m_hoverOpacity <= 0.0) {
595 delete m_hoverCache;
596 m_hoverCache = nullptr;
597 }
598
599 update();
600 }
601
602 void KItemListWidget::clearHoverCache()
603 {
604 delete m_hoverCache;
605 m_hoverCache = nullptr;
606 }
607
608 bool KItemListWidget::isPressed() const
609 {
610 return m_clickHighlighted;
611 }
612
613 void KItemListWidget::setPressed(bool enabled)
614 {
615 if (m_clickHighlighted != enabled) {
616 m_clickHighlighted = enabled;
617 clearHoverCache();
618 update();
619 }
620 }
621
622 void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QStyle::State styleState)
623 {
624 QStyleOptionViewItem viewItemOption;
625 constexpr int roundness = 5; // From Breeze style.
626 constexpr qreal penWidth = 1.5;
627 initStyleOption(&viewItemOption);
628 viewItemOption.state = styleState;
629 viewItemOption.viewItemPosition = QStyleOptionViewItem::OnlyOne;
630 viewItemOption.showDecorationSelected = true;
631 viewItemOption.rect = selectionRectFull().toRect();
632 QPainterPath path;
633 path.addRoundedRect(selectionRectFull().adjusted(penWidth, penWidth, -penWidth, -penWidth), roundness, roundness);
634 QColor backgroundColor{widget->palette().color(QPalette::Accent)};
635 painter->setRenderHint(QPainter::Antialiasing);
636 bool current = m_current && styleState & QStyle::State_Active;
637
638 // Background item, alpha values are from
639 // https://invent.kde.org/plasma/libplasma/-/blob/master/src/desktoptheme/breeze/widgets/viewitem.svg
640 backgroundColor.setAlphaF(0.0);
641
642 if (m_clickHighlighted) {
643 backgroundColor.setAlphaF(1.0);
644 } else {
645 if (m_selected && m_hovered) {
646 backgroundColor.setAlphaF(0.40);
647 } else if (m_selected) {
648 backgroundColor.setAlphaF(0.32);
649 } else if (m_hovered) {
650 backgroundColor = widget->palette().color(QPalette::Text);
651 backgroundColor.setAlphaF(0.06);
652 }
653 }
654
655 painter->fillPath(path, backgroundColor);
656
657 // Focus decoration
658 if (current) {
659 QColor focusColor{widget->palette().color(QPalette::Accent)};
660 focusColor = m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? focusColor.darker(110) : focusColor.lighter(110);
661 focusColor.setAlphaF(m_selected || m_hovered ? 0.8 : 0.6);
662 // Set the pen color lighter or darker depending on background color
663 QPen pen{focusColor, penWidth};
664 pen.setCosmetic(true);
665 painter->setPen(pen);
666 painter->drawPath(path);
667 }
668 }
669
670 #include "moc_kitemlistwidget.cpp"