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