]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontainer.cpp
DolphinView: Conform to global scroll speed
[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 pageStep = 0;
290 int maximum = 0;
291 if (view->scrollOrientation() == Qt::Vertical) {
292 smoothScroller = m_verticalSmoothScroller;
293 if (smoothScroller->isAnimating()) {
294 return;
295 }
296 scrollOffsetScrollBar = verticalScrollBar();
297
298 // We cannot use view->size().height() because this height might
299 // include the header widget, which is not part of the scrolled area.
300 pageStep = view->verticalPageStep();
301
302 // However, the total height of the view must be considered for the
303 // maximum value of the scroll bar. Note that the view's scrollOffset()
304 // refers to the offset of the top part of the view, which might be
305 // hidden behind the header.
306 maximum = qMax(0, int(view->maximumScrollOffset() - view->size().height()));
307 } else {
308 smoothScroller = m_horizontalSmoothScroller;
309 if (smoothScroller->isAnimating()) {
310 return;
311 }
312 scrollOffsetScrollBar = horizontalScrollBar();
313 pageStep = view->size().width();
314 maximum = qMax(0, int(view->maximumScrollOffset() - view->size().width()));
315 }
316
317 const int singleStep = view->scrollSingleStep();
318 const int value = view->scrollOffset();
319 if (smoothScroller->requestScrollBarUpdate(maximum)) {
320 const bool updatePolicy = (scrollOffsetScrollBar->maximum() > 0 && maximum == 0) || horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOn;
321
322 scrollOffsetScrollBar->setSingleStep(singleStep);
323 scrollOffsetScrollBar->setPageStep(pageStep);
324 scrollOffsetScrollBar->setMinimum(0);
325 scrollOffsetScrollBar->setMaximum(maximum);
326 scrollOffsetScrollBar->setValue(value);
327
328 if (updatePolicy) {
329 // Prevent a potential endless layout loop (see bug #293318).
330 updateScrollOffsetScrollBarPolicy();
331 }
332 }
333 }
334
335 void KItemListContainer::updateItemOffsetScrollBar()
336 {
337 const KItemListView *view = m_controller->view();
338 if (!view) {
339 return;
340 }
341
342 KItemListSmoothScroller *smoothScroller = nullptr;
343 QScrollBar *itemOffsetScrollBar = nullptr;
344 int singleStep = 0;
345 int pageStep = 0;
346 if (view->scrollOrientation() == Qt::Vertical) {
347 smoothScroller = m_horizontalSmoothScroller;
348 if (smoothScroller->isAnimating()) {
349 return;
350 }
351 itemOffsetScrollBar = horizontalScrollBar();
352 singleStep = view->size().width() / 10;
353 pageStep = view->size().width();
354 } else {
355 smoothScroller = m_verticalSmoothScroller;
356 if (smoothScroller->isAnimating()) {
357 return;
358 }
359 itemOffsetScrollBar = verticalScrollBar();
360 singleStep = view->size().height() / 10;
361 pageStep = view->size().height();
362 }
363
364 const int value = view->itemOffset();
365 const int maximum = qMax(0, int(view->maximumItemOffset()) - pageStep);
366 if (smoothScroller->requestScrollBarUpdate(maximum)) {
367 itemOffsetScrollBar->setSingleStep(singleStep);
368 itemOffsetScrollBar->setPageStep(pageStep);
369 itemOffsetScrollBar->setMinimum(0);
370 itemOffsetScrollBar->setMaximum(maximum);
371 itemOffsetScrollBar->setValue(value);
372 }
373 }
374
375 void KItemListContainer::stopScroller()
376 {
377 m_scroller->stop();
378 }
379
380 void KItemListContainer::updateGeometries()
381 {
382 QRect rect = geometry();
383
384 int extra = frameWidth() * 2;
385 QStyleOption option;
386 option.initFrom(this);
387 int scrollbarSpacing = 0;
388 if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &option, this)) {
389 scrollbarSpacing = style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing, &option, this);
390 }
391
392 const int widthDec = verticalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra;
393
394 const int heightDec =
395 horizontalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra;
396
397 const QRectF newGeometry(0, 0, rect.width() - widthDec, rect.height() - heightDec);
398 if (m_controller->view()->geometry() != newGeometry) {
399 m_controller->view()->setGeometry(newGeometry);
400
401 // Get the real geometry of the view again since the scrollbars
402 // visibilities and the view geometry may have changed in re-layout.
403 static_cast<KItemListContainerViewport *>(viewport())->scene()->setSceneRect(m_controller->view()->geometry());
404 static_cast<KItemListContainerViewport *>(viewport())->viewport()->setGeometry(m_controller->view()->geometry().toRect());
405
406 updateScrollOffsetScrollBar();
407 updateItemOffsetScrollBar();
408 }
409 }
410
411 void KItemListContainer::updateSmoothScrollers(Qt::Orientation orientation)
412 {
413 if (orientation == Qt::Vertical) {
414 m_verticalSmoothScroller->setPropertyName("scrollOffset");
415 m_horizontalSmoothScroller->setPropertyName("itemOffset");
416 } else {
417 m_horizontalSmoothScroller->setPropertyName("scrollOffset");
418 m_verticalSmoothScroller->setPropertyName("itemOffset");
419 }
420
421 const bool isRightToLeft = m_controller->view()->layoutDirection() == Qt::RightToLeft;
422 QScrollBar *hScrollBar = horizontalScrollBar();
423 hScrollBar->setInvertedAppearance(isRightToLeft && orientation == Qt::Vertical);
424 hScrollBar->setInvertedControls(!isRightToLeft || orientation == Qt::Vertical);
425 }
426
427 void KItemListContainer::updateScrollOffsetScrollBarPolicy()
428 {
429 const KItemListView *view = m_controller->view();
430 Q_ASSERT(view);
431 const bool vertical = (view->scrollOrientation() == Qt::Vertical);
432
433 QStyleOption option;
434 option.initFrom(this);
435 const int scrollBarInc = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this);
436
437 QSizeF newViewSize = m_controller->view()->size();
438 if (vertical) {
439 newViewSize.rwidth() += scrollBarInc;
440 } else {
441 newViewSize.rheight() += scrollBarInc;
442 }
443
444 const Qt::ScrollBarPolicy policy = view->scrollBarRequired(newViewSize) ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAsNeeded;
445 if (vertical) {
446 setVerticalScrollBarPolicy(policy);
447 } else {
448 setHorizontalScrollBarPolicy(policy);
449 }
450 }
451
452 #include "kitemlistcontainer.moc"
453 #include "moc_kitemlistcontainer.cpp"