]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontainer.cpp
GIT_SILENT made messages (after extraction)
[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 <QScroller>
21 #include <QStyleOption>
22
23 /**
24 * Replaces the default viewport of KItemListContainer by a
25 * non-scrollable viewport. The scrolling is done in an optimized
26 * way by KItemListView internally.
27 */
28 class KItemListContainerViewport : public QGraphicsView
29 {
30 Q_OBJECT
31
32 public:
33 KItemListContainerViewport(QGraphicsScene* scene, QWidget* parent);
34 protected:
35 void wheelEvent(QWheelEvent* event) override;
36 };
37
38 KItemListContainerViewport::KItemListContainerViewport(QGraphicsScene* scene, QWidget* parent) :
39 QGraphicsView(scene, parent)
40 {
41 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
42 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
43 setViewportMargins(0, 0, 0, 0);
44 setFrameShape(QFrame::NoFrame);
45 }
46
47 void KItemListContainerViewport::wheelEvent(QWheelEvent* event)
48 {
49 // Assure that the wheel-event gets forwarded to the parent
50 // and not handled at all by QGraphicsView.
51 event->ignore();
52 }
53
54 KItemListContainer::KItemListContainer(KItemListController* controller, QWidget* parent) :
55 QAbstractScrollArea(parent),
56 m_controller(controller),
57 m_horizontalSmoothScroller(nullptr),
58 m_verticalSmoothScroller(nullptr),
59 m_scroller(nullptr)
60 {
61 Q_ASSERT(controller);
62 controller->setParent(this);
63
64 QGraphicsView* graphicsView = new KItemListContainerViewport(new QGraphicsScene(this), this);
65 setViewport(graphicsView);
66
67 m_horizontalSmoothScroller = new KItemListSmoothScroller(horizontalScrollBar(), this);
68 m_verticalSmoothScroller = new KItemListSmoothScroller(verticalScrollBar(), this);
69
70 if (controller->model()) {
71 slotModelChanged(controller->model(), nullptr);
72 }
73 if (controller->view()) {
74 slotViewChanged(controller->view(), nullptr);
75 }
76
77 connect(controller, &KItemListController::modelChanged,
78 this, &KItemListContainer::slotModelChanged);
79 connect(controller, &KItemListController::viewChanged,
80 this, &KItemListContainer::slotViewChanged);
81
82 m_scroller = QScroller::scroller(viewport());
83 m_scroller->grabGesture(viewport());
84 connect(controller, &KItemListController::scrollerStop,
85 this, &KItemListContainer::stopScroller);
86 connect(m_scroller, &QScroller::stateChanged,
87 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::showEvent(QShowEvent* event)
145 {
146 QAbstractScrollArea::showEvent(event);
147 updateGeometries();
148 }
149
150 void KItemListContainer::resizeEvent(QResizeEvent* event)
151 {
152 QAbstractScrollArea::resizeEvent(event);
153 updateGeometries();
154 }
155
156 void KItemListContainer::scrollContentsBy(int dx, int dy)
157 {
158 m_horizontalSmoothScroller->scrollContentsBy(dx);
159 m_verticalSmoothScroller->scrollContentsBy(dy);
160 }
161
162 void KItemListContainer::wheelEvent(QWheelEvent* event)
163 {
164 if (event->modifiers().testFlag(Qt::ControlModifier)) {
165 event->ignore();
166 return;
167 }
168
169 KItemListView* view = m_controller->view();
170 if (!view) {
171 event->ignore();
172 return;
173 }
174
175 const bool scrollHorizontally = (event->angleDelta().x() != 0) ||
176 (event->angleDelta().y() != 0 && !verticalScrollBar()->isVisible());
177 KItemListSmoothScroller* smoothScroller = scrollHorizontally ?
178 m_horizontalSmoothScroller : m_verticalSmoothScroller;
179
180 smoothScroller->handleWheelEvent(event);
181 }
182
183 void KItemListContainer::slotScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
184 {
185 Q_UNUSED(previous)
186 updateSmoothScrollers(current);
187 }
188
189 void KItemListContainer::slotModelChanged(KItemModelBase* current, KItemModelBase* previous)
190 {
191 Q_UNUSED(current)
192 Q_UNUSED(previous)
193 }
194
195 void KItemListContainer::slotViewChanged(KItemListView* current, KItemListView* previous)
196 {
197 QGraphicsScene* scene = static_cast<QGraphicsView*>(viewport())->scene();
198 if (previous) {
199 scene->removeItem(previous);
200 disconnect(previous, &KItemListView::scrollOrientationChanged,
201 this, &KItemListContainer::slotScrollOrientationChanged);
202 disconnect(previous, &KItemListView::scrollOffsetChanged,
203 this, &KItemListContainer::updateScrollOffsetScrollBar);
204 disconnect(previous, &KItemListView::maximumScrollOffsetChanged,
205 this, &KItemListContainer::updateScrollOffsetScrollBar);
206 disconnect(previous, &KItemListView::itemOffsetChanged,
207 this, &KItemListContainer::updateItemOffsetScrollBar);
208 disconnect(previous, &KItemListView::maximumItemOffsetChanged,
209 this, &KItemListContainer::updateItemOffsetScrollBar);
210 disconnect(previous, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo);
211 m_horizontalSmoothScroller->setTargetObject(nullptr);
212 m_verticalSmoothScroller->setTargetObject(nullptr);
213 }
214 if (current) {
215 scene->addItem(current);
216 connect(current, &KItemListView::scrollOrientationChanged,
217 this, &KItemListContainer::slotScrollOrientationChanged);
218 connect(current, &KItemListView::scrollOffsetChanged,
219 this, &KItemListContainer::updateScrollOffsetScrollBar);
220 connect(current, &KItemListView::maximumScrollOffsetChanged,
221 this, &KItemListContainer::updateScrollOffsetScrollBar);
222 connect(current, &KItemListView::itemOffsetChanged,
223 this, &KItemListContainer::updateItemOffsetScrollBar);
224 connect(current, &KItemListView::maximumItemOffsetChanged,
225 this, &KItemListContainer::updateItemOffsetScrollBar);
226 connect(current, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo);
227 m_horizontalSmoothScroller->setTargetObject(current);
228 m_verticalSmoothScroller->setTargetObject(current);
229 updateSmoothScrollers(current->scrollOrientation());
230 }
231 }
232
233 void KItemListContainer::scrollTo(qreal offset)
234 {
235 const KItemListView* view = m_controller->view();
236 if (view) {
237 if (view->scrollOrientation() == Qt::Vertical) {
238 m_verticalSmoothScroller->scrollTo(offset);
239 } else {
240 m_horizontalSmoothScroller->scrollTo(offset);
241 }
242 }
243 }
244
245 void KItemListContainer::updateScrollOffsetScrollBar()
246 {
247 const KItemListView* view = m_controller->view();
248 if (!view) {
249 return;
250 }
251
252 KItemListSmoothScroller* smoothScroller = nullptr;
253 QScrollBar* scrollOffsetScrollBar = nullptr;
254 int singleStep = 0;
255 int pageStep = 0;
256 int maximum = 0;
257 if (view->scrollOrientation() == Qt::Vertical) {
258 smoothScroller = m_verticalSmoothScroller;
259 scrollOffsetScrollBar = verticalScrollBar();
260
261 // Don't scroll super fast when using a wheel mouse:
262 // We want to consider one "line" to be the text label which has a
263 // roughly fixed height rather than using the height of the icon which
264 // may be very tall
265 const QFontMetrics metrics(font());
266 singleStep = metrics.height() * QApplication::wheelScrollLines();
267
268 // We cannot use view->size().height() because this height might
269 // include the header widget, which is not part of the scrolled area.
270 pageStep = view->verticalPageStep();
271
272 // However, the total height of the view must be considered for the
273 // maximum value of the scroll bar. Note that the view's scrollOffset()
274 // refers to the offset of the top part of the view, which might be
275 // hidden behind the header.
276 maximum = qMax(0, int(view->maximumScrollOffset() - view->size().height()));
277 } else {
278 smoothScroller = m_horizontalSmoothScroller;
279 scrollOffsetScrollBar = horizontalScrollBar();
280 singleStep = view->itemSize().width();
281 pageStep = view->size().width();
282 maximum = qMax(0, int(view->maximumScrollOffset() - view->size().width()));
283 }
284
285 const int value = view->scrollOffset();
286 if (smoothScroller->requestScrollBarUpdate(maximum)) {
287 const bool updatePolicy = (scrollOffsetScrollBar->maximum() > 0 && maximum == 0)
288 || horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOn;
289
290 scrollOffsetScrollBar->setSingleStep(singleStep);
291 scrollOffsetScrollBar->setPageStep(pageStep);
292 scrollOffsetScrollBar->setMinimum(0);
293 scrollOffsetScrollBar->setMaximum(maximum);
294 scrollOffsetScrollBar->setValue(value);
295
296 if (updatePolicy) {
297 // Prevent a potential endless layout loop (see bug #293318).
298 updateScrollOffsetScrollBarPolicy();
299 }
300 }
301 }
302
303 void KItemListContainer::updateItemOffsetScrollBar()
304 {
305 const KItemListView* view = m_controller->view();
306 if (!view) {
307 return;
308 }
309
310 KItemListSmoothScroller* smoothScroller = nullptr;
311 QScrollBar* itemOffsetScrollBar = nullptr;
312 int singleStep = 0;
313 int pageStep = 0;
314 if (view->scrollOrientation() == Qt::Vertical) {
315 smoothScroller = m_horizontalSmoothScroller;
316 itemOffsetScrollBar = horizontalScrollBar();
317 singleStep = view->size().width() / 10;
318 pageStep = view->size().width();
319 } else {
320 smoothScroller = m_verticalSmoothScroller;
321 itemOffsetScrollBar = verticalScrollBar();
322 singleStep = view->size().height() / 10;
323 pageStep = view->size().height();
324 }
325
326 const int value = view->itemOffset();
327 const int maximum = qMax(0, int(view->maximumItemOffset()) - pageStep);
328 if (smoothScroller->requestScrollBarUpdate(maximum)) {
329 itemOffsetScrollBar->setSingleStep(singleStep);
330 itemOffsetScrollBar->setPageStep(pageStep);
331 itemOffsetScrollBar->setMinimum(0);
332 itemOffsetScrollBar->setMaximum(maximum);
333 itemOffsetScrollBar->setValue(value);
334 }
335 }
336
337 void KItemListContainer::stopScroller()
338 {
339 m_scroller->stop();
340 }
341
342 void KItemListContainer::updateGeometries()
343 {
344 QRect rect = geometry();
345
346 int extra = frameWidth() * 2;
347 QStyleOption option;
348 option.initFrom(this);
349 int scrollbarSpacing = 0;
350 if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &option, this)) {
351 scrollbarSpacing = style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing, &option, this);
352 }
353
354 const int widthDec = verticalScrollBar()->isVisible()
355 ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this)
356 : extra;
357
358 const int heightDec = horizontalScrollBar()->isVisible()
359 ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this)
360 : extra;
361
362 const QRectF newGeometry(0, 0, rect.width() - widthDec,
363 rect.height() - heightDec);
364 if (m_controller->view()->geometry() != newGeometry) {
365 m_controller->view()->setGeometry(newGeometry);
366
367 // Get the real geometry of the view again since the scrollbars
368 // visibilities and the view geometry may have changed in re-layout.
369 static_cast<KItemListContainerViewport*>(viewport())->scene()->setSceneRect(m_controller->view()->geometry());
370 static_cast<KItemListContainerViewport*>(viewport())->viewport()->setGeometry(m_controller->view()->geometry().toRect());
371
372 updateScrollOffsetScrollBar();
373 updateItemOffsetScrollBar();
374 }
375 }
376
377 void KItemListContainer::updateSmoothScrollers(Qt::Orientation orientation)
378 {
379 if (orientation == Qt::Vertical) {
380 m_verticalSmoothScroller->setPropertyName("scrollOffset");
381 m_horizontalSmoothScroller->setPropertyName("itemOffset");
382 } else {
383 m_horizontalSmoothScroller->setPropertyName("scrollOffset");
384 m_verticalSmoothScroller->setPropertyName("itemOffset");
385 }
386 }
387
388 void KItemListContainer::updateScrollOffsetScrollBarPolicy()
389 {
390 const KItemListView* view = m_controller->view();
391 Q_ASSERT(view);
392 const bool vertical = (view->scrollOrientation() == Qt::Vertical);
393
394 QStyleOption option;
395 option.initFrom(this);
396 const int scrollBarInc = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this);
397
398 QSizeF newViewSize = m_controller->view()->size();
399 if (vertical) {
400 newViewSize.rwidth() += scrollBarInc;
401 } else {
402 newViewSize.rheight() += scrollBarInc;
403 }
404
405 const Qt::ScrollBarPolicy policy = view->scrollBarRequired(newViewSize)
406 ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAsNeeded;
407 if (vertical) {
408 setVerticalScrollBarPolicy(policy);
409 } else {
410 setHorizontalScrollBarPolicy(policy);
411 }
412 }
413
414 #include "kitemlistcontainer.moc"