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