]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontainer.cpp
128140e2e4cdf88422676bae110b3371a44be9af
[dolphin.git] / src / kitemviews / kitemlistcontainer.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 "kitemlistcontainer.h"
10
11 #include "kitemlistcontroller.h"
12 #include "kitemlistview.h"
13 #include "private/kitemlistsmoothscroller.h"
14
15 #ifndef QT_NO_ACCESSIBILITY
16 #include "accessibility/kitemlistviewaccessible.h"
17 #endif
18 #include <QApplication>
19 #include <QFontMetrics>
20 #include <QGraphicsScene>
21 #include <QGraphicsView>
22 #include <QScrollBar>
23 #include <QScroller>
24 #include <QStyleOption>
25
26 /**
27 * Replaces the default viewport of KItemListContainer by a
28 * non-scrollable viewport. The scrolling is done in an optimized
29 * way by KItemListView internally.
30 */
31 class KItemListContainerViewport : public QGraphicsView
32 {
33 Q_OBJECT
34
35 public:
36 KItemListContainerViewport(QGraphicsScene *scene, QWidget *parent);
37
38 protected:
39 void wheelEvent(QWheelEvent *event) override;
40 };
41
42 KItemListContainerViewport::KItemListContainerViewport(QGraphicsScene *scene, QWidget *parent)
43 : QGraphicsView(scene, parent)
44 {
45 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
46 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
47 setViewportMargins(0, 0, 0, 0);
48 setFrameShape(QFrame::NoFrame);
49 }
50
51 void KItemListContainerViewport::wheelEvent(QWheelEvent *event)
52 {
53 // Assure that the wheel-event gets forwarded to the parent
54 // and not handled at all by QGraphicsView.
55 event->ignore();
56 }
57
58 KItemListContainer::KItemListContainer(KItemListController *controller, QWidget *parent)
59 : QAbstractScrollArea(parent)
60 , m_controller(controller)
61 , m_horizontalSmoothScroller(nullptr)
62 , m_verticalSmoothScroller(nullptr)
63 , m_scroller(nullptr)
64 {
65 Q_ASSERT(controller);
66 controller->setParent(this);
67
68 QGraphicsView *graphicsView = new KItemListContainerViewport(new QGraphicsScene(this), this);
69 setViewport(graphicsView);
70
71 m_horizontalSmoothScroller = new KItemListSmoothScroller(horizontalScrollBar(), this);
72 m_verticalSmoothScroller = new KItemListSmoothScroller(verticalScrollBar(), this);
73
74 if (controller->model()) {
75 slotModelChanged(controller->model(), nullptr);
76 }
77 if (controller->view()) {
78 slotViewChanged(controller->view(), nullptr);
79 }
80
81 connect(controller, &KItemListController::modelChanged, this, &KItemListContainer::slotModelChanged);
82 connect(controller, &KItemListController::viewChanged, this, &KItemListContainer::slotViewChanged);
83
84 m_scroller = QScroller::scroller(viewport());
85 m_scroller->grabGesture(viewport());
86 connect(controller, &KItemListController::scrollerStop, this, &KItemListContainer::stopScroller);
87 connect(m_scroller, &QScroller::stateChanged, controller, &KItemListController::slotStateChanged);
88 }
89
90 KItemListContainer::~KItemListContainer()
91 {
92 // Don't rely on the QObject-order to delete the controller, otherwise
93 // the QGraphicsScene might get deleted before the view.
94 delete m_controller;
95 m_controller = nullptr;
96 }
97
98 KItemListController *KItemListContainer::controller() const
99 {
100 return m_controller;
101 }
102
103 void KItemListContainer::setEnabledFrame(bool enable)
104 {
105 QGraphicsView *graphicsView = qobject_cast<QGraphicsView *>(viewport());
106 if (enable) {
107 setFrameShape(QFrame::StyledPanel);
108 graphicsView->setPalette(palette());
109 graphicsView->viewport()->setAutoFillBackground(true);
110 } else {
111 setFrameShape(QFrame::NoFrame);
112 // Make the background of the container transparent and apply the window-text color
113 // to the text color, so that enough contrast is given for all color
114 // schemes
115 QPalette p = graphicsView->palette();
116 p.setColor(QPalette::Active, QPalette::Text, p.color(QPalette::Active, QPalette::WindowText));
117 p.setColor(QPalette::Inactive, QPalette::Text, p.color(QPalette::Inactive, QPalette::WindowText));
118 p.setColor(QPalette::Disabled, QPalette::Text, p.color(QPalette::Disabled, QPalette::WindowText));
119 graphicsView->setPalette(p);
120 graphicsView->viewport()->setAutoFillBackground(false);
121 }
122 }
123
124 bool KItemListContainer::enabledFrame() const
125 {
126 const QGraphicsView *graphicsView = qobject_cast<QGraphicsView *>(viewport());
127 return graphicsView->autoFillBackground();
128 }
129
130 void KItemListContainer::keyPressEvent(QKeyEvent *event)
131 {
132 // TODO: We should find a better way to handle the key press events in the view.
133 // The reasons why we need this hack are:
134 // 1. Without reimplementing keyPressEvent() here, the event would not reach the QGraphicsView.
135 // 2. By default, the KItemListView does not have the keyboard focus in the QGraphicsScene, so
136 // simply sending the event to the QGraphicsView which is the KItemListContainer's viewport
137 // does not work.
138 KItemListView *view = m_controller->view();
139 if (view) {
140 QApplication::sendEvent(view, event);
141 }
142 }
143
144 void KItemListContainer::contextMenuEvent(QContextMenuEvent *event)
145 {
146 // Note copied from the keyPressEvent() method above because the same reasons probably also apply here.
147 // TODO: We should find a better way to handle the context menu events in the view.
148 // The reasons why we need this hack are:
149 // 1. Without reimplementing contextMenuEvent() here, the event would not reach the QGraphicsView.
150 // 2. By default, the KItemListView does not have the keyboard focus in the QGraphicsScene, so
151 // simply sending the event to the QGraphicsView which is the KItemListContainer's viewport
152 // does not work.
153 KItemListView *view = m_controller->view();
154 if (view) {
155 QApplication::sendEvent(view, event);
156 }
157 }
158
159 void KItemListContainer::showEvent(QShowEvent *event)
160 {
161 QAbstractScrollArea::showEvent(event);
162 updateGeometries();
163 }
164
165 void KItemListContainer::resizeEvent(QResizeEvent *event)
166 {
167 QAbstractScrollArea::resizeEvent(event);
168 updateGeometries();
169 }
170
171 void KItemListContainer::scrollContentsBy(int dx, int dy)
172 {
173 m_horizontalSmoothScroller->scrollContentsBy(dx);
174 m_verticalSmoothScroller->scrollContentsBy(dy);
175 }
176
177 void KItemListContainer::wheelEvent(QWheelEvent *event)
178 {
179 if (event->modifiers().testFlag(Qt::ControlModifier)) {
180 event->ignore();
181 return;
182 }
183
184 KItemListView *view = m_controller->view();
185 if (!view) {
186 event->ignore();
187 return;
188 }
189
190 const bool scrollHorizontally = (qAbs(event->angleDelta().y()) < qAbs(event->angleDelta().x())) || (!verticalScrollBar()->isVisible());
191 KItemListSmoothScroller *smoothScroller = scrollHorizontally ? m_horizontalSmoothScroller : m_verticalSmoothScroller;
192
193 smoothScroller->handleWheelEvent(event);
194 }
195
196 void KItemListContainer::focusInEvent(QFocusEvent *event)
197 {
198 KItemListView *view = m_controller->view();
199 if (view) {
200 QApplication::sendEvent(view, event);
201
202 // We need to set the focus to the view or accessibility software will only announce the container (which has no information available itself).
203 // For some reason actively setting the focus to the view needs to be delayed or the focus will immediately go back to this container.
204 QTimer::singleShot(0, this, [this, view]() {
205 if (!isAncestorOf(QApplication::focusWidget())) {
206 view->setFocus();
207 }
208 #ifndef QT_NO_ACCESSIBILITY
209 static_cast<KItemListViewAccessible *>(QAccessible::queryAccessibleInterface(view))->setAccessibleFocusAndAnnounceAll();
210 #endif
211 });
212 }
213 }
214
215 void KItemListContainer::focusOutEvent(QFocusEvent *event)
216 {
217 KItemListView *view = m_controller->view();
218 if (view) {
219 QApplication::sendEvent(view, event);
220 }
221 }
222
223 void KItemListContainer::slotScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
224 {
225 Q_UNUSED(previous)
226 updateSmoothScrollers(current);
227 }
228
229 void KItemListContainer::slotModelChanged(KItemModelBase *current, KItemModelBase *previous)
230 {
231 Q_UNUSED(current)
232 Q_UNUSED(previous)
233 }
234
235 void KItemListContainer::slotViewChanged(KItemListView *current, KItemListView *previous)
236 {
237 QGraphicsScene *scene = static_cast<QGraphicsView *>(viewport())->scene();
238 if (previous) {
239 scene->removeItem(previous);
240 disconnect(previous, &KItemListView::scrollOrientationChanged, this, &KItemListContainer::slotScrollOrientationChanged);
241 disconnect(previous, &KItemListView::scrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar);
242 disconnect(previous, &KItemListView::maximumScrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar);
243 disconnect(previous, &KItemListView::itemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar);
244 disconnect(previous, &KItemListView::maximumItemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar);
245 disconnect(previous, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo);
246 disconnect(m_horizontalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, previous, &KItemListView::scrollingStopped);
247 disconnect(m_verticalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, previous, &KItemListView::scrollingStopped);
248 m_horizontalSmoothScroller->setTargetObject(nullptr);
249 m_verticalSmoothScroller->setTargetObject(nullptr);
250 }
251 if (current) {
252 scene->addItem(current);
253 connect(current, &KItemListView::scrollOrientationChanged, this, &KItemListContainer::slotScrollOrientationChanged);
254 connect(current, &KItemListView::scrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar);
255 connect(current, &KItemListView::maximumScrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar);
256 connect(current, &KItemListView::itemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar);
257 connect(current, &KItemListView::maximumItemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar);
258 connect(current, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo);
259 connect(m_horizontalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, current, &KItemListView::scrollingStopped);
260 connect(m_verticalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, current, &KItemListView::scrollingStopped);
261
262 m_horizontalSmoothScroller->setTargetObject(current);
263 m_verticalSmoothScroller->setTargetObject(current);
264 updateSmoothScrollers(current->scrollOrientation());
265 }
266 }
267
268 void KItemListContainer::scrollTo(qreal offset)
269 {
270 const KItemListView *view = m_controller->view();
271 if (view) {
272 if (view->scrollOrientation() == Qt::Vertical) {
273 m_verticalSmoothScroller->scrollTo(offset);
274 } else {
275 m_horizontalSmoothScroller->scrollTo(offset);
276 }
277 }
278 }
279
280 void KItemListContainer::updateScrollOffsetScrollBar()
281 {
282 const KItemListView *view = m_controller->view();
283 if (!view) {
284 return;
285 }
286
287 KItemListSmoothScroller *smoothScroller = nullptr;
288 QScrollBar *scrollOffsetScrollBar = nullptr;
289 int singleStep = 0;
290 int pageStep = 0;
291 int maximum = 0;
292 if (view->scrollOrientation() == Qt::Vertical) {
293 smoothScroller = m_verticalSmoothScroller;
294 if (smoothScroller->isAnimating()) {
295 return;
296 }
297 scrollOffsetScrollBar = verticalScrollBar();
298
299 // Don't scroll super fast when using a wheel mouse:
300 // We want to consider one "line" to be the text label which has a
301 // roughly fixed height rather than using the height of the icon which
302 // may be very tall
303 const QFontMetrics metrics(font());
304 singleStep = metrics.height() * QApplication::wheelScrollLines();
305
306 // We cannot use view->size().height() because this height might
307 // include the header widget, which is not part of the scrolled area.
308 pageStep = view->verticalPageStep();
309
310 // However, the total height of the view must be considered for the
311 // maximum value of the scroll bar. Note that the view's scrollOffset()
312 // refers to the offset of the top part of the view, which might be
313 // hidden behind the header.
314 maximum = qMax(0, int(view->maximumScrollOffset() - view->size().height()));
315 } else {
316 smoothScroller = m_horizontalSmoothScroller;
317 if (smoothScroller->isAnimating()) {
318 return;
319 }
320 scrollOffsetScrollBar = horizontalScrollBar();
321 singleStep = view->itemSize().width();
322 pageStep = view->size().width();
323 maximum = qMax(0, int(view->maximumScrollOffset() - view->size().width()));
324 }
325
326 const int value = view->scrollOffset();
327 if (smoothScroller->requestScrollBarUpdate(maximum)) {
328 const bool updatePolicy = (scrollOffsetScrollBar->maximum() > 0 && maximum == 0) || horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOn;
329
330 scrollOffsetScrollBar->setSingleStep(singleStep);
331 scrollOffsetScrollBar->setPageStep(pageStep);
332 scrollOffsetScrollBar->setMinimum(0);
333 scrollOffsetScrollBar->setMaximum(maximum);
334 scrollOffsetScrollBar->setValue(value);
335
336 if (updatePolicy) {
337 // Prevent a potential endless layout loop (see bug #293318).
338 updateScrollOffsetScrollBarPolicy();
339 }
340 }
341 }
342
343 void KItemListContainer::updateItemOffsetScrollBar()
344 {
345 const KItemListView *view = m_controller->view();
346 if (!view) {
347 return;
348 }
349
350 KItemListSmoothScroller *smoothScroller = nullptr;
351 QScrollBar *itemOffsetScrollBar = nullptr;
352 int singleStep = 0;
353 int pageStep = 0;
354 if (view->scrollOrientation() == Qt::Vertical) {
355 smoothScroller = m_horizontalSmoothScroller;
356 if (smoothScroller->isAnimating()) {
357 return;
358 }
359 itemOffsetScrollBar = horizontalScrollBar();
360 singleStep = view->size().width() / 10;
361 pageStep = view->size().width();
362 } else {
363 smoothScroller = m_verticalSmoothScroller;
364 if (smoothScroller->isAnimating()) {
365 return;
366 }
367 itemOffsetScrollBar = verticalScrollBar();
368 singleStep = view->size().height() / 10;
369 pageStep = view->size().height();
370 }
371
372 const int value = view->itemOffset();
373 const int maximum = qMax(0, int(view->maximumItemOffset()) - pageStep);
374 if (smoothScroller->requestScrollBarUpdate(maximum)) {
375 itemOffsetScrollBar->setSingleStep(singleStep);
376 itemOffsetScrollBar->setPageStep(pageStep);
377 itemOffsetScrollBar->setMinimum(0);
378 itemOffsetScrollBar->setMaximum(maximum);
379 itemOffsetScrollBar->setValue(value);
380 }
381 }
382
383 void KItemListContainer::stopScroller()
384 {
385 m_scroller->stop();
386 }
387
388 void KItemListContainer::updateGeometries()
389 {
390 QRect rect = geometry();
391
392 int extra = frameWidth() * 2;
393 QStyleOption option;
394 option.initFrom(this);
395 int scrollbarSpacing = 0;
396 if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &option, this)) {
397 scrollbarSpacing = style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing, &option, this);
398 }
399
400 const int widthDec = verticalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra;
401
402 const int heightDec =
403 horizontalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra;
404
405 const QRectF newGeometry(0, 0, rect.width() - widthDec, rect.height() - heightDec);
406 if (m_controller->view()->geometry() != newGeometry) {
407 m_controller->view()->setGeometry(newGeometry);
408
409 // Get the real geometry of the view again since the scrollbars
410 // visibilities and the view geometry may have changed in re-layout.
411 static_cast<KItemListContainerViewport *>(viewport())->scene()->setSceneRect(m_controller->view()->geometry());
412 static_cast<KItemListContainerViewport *>(viewport())->viewport()->setGeometry(m_controller->view()->geometry().toRect());
413
414 updateScrollOffsetScrollBar();
415 updateItemOffsetScrollBar();
416 }
417 }
418
419 void KItemListContainer::updateSmoothScrollers(Qt::Orientation orientation)
420 {
421 if (orientation == Qt::Vertical) {
422 m_verticalSmoothScroller->setPropertyName("scrollOffset");
423 m_horizontalSmoothScroller->setPropertyName("itemOffset");
424 } else {
425 m_horizontalSmoothScroller->setPropertyName("scrollOffset");
426 m_verticalSmoothScroller->setPropertyName("itemOffset");
427 }
428
429 const bool isRightToLeft = m_controller->view()->layoutDirection() == Qt::RightToLeft;
430 QScrollBar *hScrollBar = horizontalScrollBar();
431 hScrollBar->setInvertedAppearance(isRightToLeft && orientation == Qt::Vertical);
432 hScrollBar->setInvertedControls(!isRightToLeft || orientation == Qt::Vertical);
433 }
434
435 void KItemListContainer::updateScrollOffsetScrollBarPolicy()
436 {
437 const KItemListView *view = m_controller->view();
438 Q_ASSERT(view);
439 const bool vertical = (view->scrollOrientation() == Qt::Vertical);
440
441 QStyleOption option;
442 option.initFrom(this);
443 const int scrollBarInc = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this);
444
445 QSizeF newViewSize = m_controller->view()->size();
446 if (vertical) {
447 newViewSize.rwidth() += scrollBarInc;
448 } else {
449 newViewSize.rheight() += scrollBarInc;
450 }
451
452 const Qt::ScrollBarPolicy policy = view->scrollBarRequired(newViewSize) ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAsNeeded;
453 if (vertical) {
454 setVerticalScrollBarPolicy(policy);
455 } else {
456 setHorizontalScrollBarPolicy(policy);
457 }
458 }
459
460 #include "kitemlistcontainer.moc"
461 #include "moc_kitemlistcontainer.cpp"