]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontainer.cpp
Clean obsolete ifdefs since dolphin requires KF 5.101+
[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::slotScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
179 {
180 Q_UNUSED(previous)
181 updateSmoothScrollers(current);
182 }
183
184 void KItemListContainer::slotModelChanged(KItemModelBase *current, KItemModelBase *previous)
185 {
186 Q_UNUSED(current)
187 Q_UNUSED(previous)
188 }
189
190 void KItemListContainer::slotViewChanged(KItemListView *current, KItemListView *previous)
191 {
192 QGraphicsScene *scene = static_cast<QGraphicsView *>(viewport())->scene();
193 if (previous) {
194 scene->removeItem(previous);
195 disconnect(previous, &KItemListView::scrollOrientationChanged, this, &KItemListContainer::slotScrollOrientationChanged);
196 disconnect(previous, &KItemListView::scrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar);
197 disconnect(previous, &KItemListView::maximumScrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar);
198 disconnect(previous, &KItemListView::itemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar);
199 disconnect(previous, &KItemListView::maximumItemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar);
200 disconnect(previous, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo);
201 disconnect(m_horizontalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, previous, &KItemListView::scrollingStopped);
202 disconnect(m_verticalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, previous, &KItemListView::scrollingStopped);
203 m_horizontalSmoothScroller->setTargetObject(nullptr);
204 m_verticalSmoothScroller->setTargetObject(nullptr);
205 }
206 if (current) {
207 scene->addItem(current);
208 connect(current, &KItemListView::scrollOrientationChanged, this, &KItemListContainer::slotScrollOrientationChanged);
209 connect(current, &KItemListView::scrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar);
210 connect(current, &KItemListView::maximumScrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar);
211 connect(current, &KItemListView::itemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar);
212 connect(current, &KItemListView::maximumItemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar);
213 connect(current, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo);
214 connect(m_horizontalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, current, &KItemListView::scrollingStopped);
215 connect(m_verticalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, current, &KItemListView::scrollingStopped);
216
217 m_horizontalSmoothScroller->setTargetObject(current);
218 m_verticalSmoothScroller->setTargetObject(current);
219 updateSmoothScrollers(current->scrollOrientation());
220 }
221 }
222
223 void KItemListContainer::scrollTo(qreal offset)
224 {
225 const KItemListView *view = m_controller->view();
226 if (view) {
227 if (view->scrollOrientation() == Qt::Vertical) {
228 m_verticalSmoothScroller->scrollTo(offset);
229 } else {
230 m_horizontalSmoothScroller->scrollTo(offset);
231 }
232 }
233 }
234
235 void KItemListContainer::updateScrollOffsetScrollBar()
236 {
237 const KItemListView *view = m_controller->view();
238 if (!view) {
239 return;
240 }
241
242 KItemListSmoothScroller *smoothScroller = nullptr;
243 QScrollBar *scrollOffsetScrollBar = nullptr;
244 int singleStep = 0;
245 int pageStep = 0;
246 int maximum = 0;
247 if (view->scrollOrientation() == Qt::Vertical) {
248 smoothScroller = m_verticalSmoothScroller;
249 scrollOffsetScrollBar = verticalScrollBar();
250
251 // Don't scroll super fast when using a wheel mouse:
252 // We want to consider one "line" to be the text label which has a
253 // roughly fixed height rather than using the height of the icon which
254 // may be very tall
255 const QFontMetrics metrics(font());
256 singleStep = metrics.height() * QApplication::wheelScrollLines();
257
258 // We cannot use view->size().height() because this height might
259 // include the header widget, which is not part of the scrolled area.
260 pageStep = view->verticalPageStep();
261
262 // However, the total height of the view must be considered for the
263 // maximum value of the scroll bar. Note that the view's scrollOffset()
264 // refers to the offset of the top part of the view, which might be
265 // hidden behind the header.
266 maximum = qMax(0, int(view->maximumScrollOffset() - view->size().height()));
267 } else {
268 smoothScroller = m_horizontalSmoothScroller;
269 scrollOffsetScrollBar = horizontalScrollBar();
270 singleStep = view->itemSize().width();
271 pageStep = view->size().width();
272 maximum = qMax(0, int(view->maximumScrollOffset() - view->size().width()));
273 }
274
275 const int value = view->scrollOffset();
276 if (smoothScroller->requestScrollBarUpdate(maximum)) {
277 const bool updatePolicy = (scrollOffsetScrollBar->maximum() > 0 && maximum == 0) || horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOn;
278
279 scrollOffsetScrollBar->setSingleStep(singleStep);
280 scrollOffsetScrollBar->setPageStep(pageStep);
281 scrollOffsetScrollBar->setMinimum(0);
282 scrollOffsetScrollBar->setMaximum(maximum);
283 scrollOffsetScrollBar->setValue(value);
284
285 if (updatePolicy) {
286 // Prevent a potential endless layout loop (see bug #293318).
287 updateScrollOffsetScrollBarPolicy();
288 }
289 }
290 }
291
292 void KItemListContainer::updateItemOffsetScrollBar()
293 {
294 const KItemListView *view = m_controller->view();
295 if (!view) {
296 return;
297 }
298
299 KItemListSmoothScroller *smoothScroller = nullptr;
300 QScrollBar *itemOffsetScrollBar = nullptr;
301 int singleStep = 0;
302 int pageStep = 0;
303 if (view->scrollOrientation() == Qt::Vertical) {
304 smoothScroller = m_horizontalSmoothScroller;
305 itemOffsetScrollBar = horizontalScrollBar();
306 singleStep = view->size().width() / 10;
307 pageStep = view->size().width();
308 } else {
309 smoothScroller = m_verticalSmoothScroller;
310 itemOffsetScrollBar = verticalScrollBar();
311 singleStep = view->size().height() / 10;
312 pageStep = view->size().height();
313 }
314
315 const int value = view->itemOffset();
316 const int maximum = qMax(0, int(view->maximumItemOffset()) - pageStep);
317 if (smoothScroller->requestScrollBarUpdate(maximum)) {
318 itemOffsetScrollBar->setSingleStep(singleStep);
319 itemOffsetScrollBar->setPageStep(pageStep);
320 itemOffsetScrollBar->setMinimum(0);
321 itemOffsetScrollBar->setMaximum(maximum);
322 itemOffsetScrollBar->setValue(value);
323 }
324 }
325
326 void KItemListContainer::stopScroller()
327 {
328 m_scroller->stop();
329 }
330
331 void KItemListContainer::updateGeometries()
332 {
333 QRect rect = geometry();
334
335 int extra = frameWidth() * 2;
336 QStyleOption option;
337 option.initFrom(this);
338 int scrollbarSpacing = 0;
339 if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &option, this)) {
340 scrollbarSpacing = style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing, &option, this);
341 }
342
343 const int widthDec = verticalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra;
344
345 const int heightDec =
346 horizontalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra;
347
348 const QRectF newGeometry(0, 0, rect.width() - widthDec, rect.height() - heightDec);
349 if (m_controller->view()->geometry() != newGeometry) {
350 m_controller->view()->setGeometry(newGeometry);
351
352 // Get the real geometry of the view again since the scrollbars
353 // visibilities and the view geometry may have changed in re-layout.
354 static_cast<KItemListContainerViewport *>(viewport())->scene()->setSceneRect(m_controller->view()->geometry());
355 static_cast<KItemListContainerViewport *>(viewport())->viewport()->setGeometry(m_controller->view()->geometry().toRect());
356
357 updateScrollOffsetScrollBar();
358 updateItemOffsetScrollBar();
359 }
360 }
361
362 void KItemListContainer::updateSmoothScrollers(Qt::Orientation orientation)
363 {
364 if (orientation == Qt::Vertical) {
365 m_verticalSmoothScroller->setPropertyName("scrollOffset");
366 m_horizontalSmoothScroller->setPropertyName("itemOffset");
367 } else {
368 m_horizontalSmoothScroller->setPropertyName("scrollOffset");
369 m_verticalSmoothScroller->setPropertyName("itemOffset");
370 }
371 }
372
373 void KItemListContainer::updateScrollOffsetScrollBarPolicy()
374 {
375 const KItemListView *view = m_controller->view();
376 Q_ASSERT(view);
377 const bool vertical = (view->scrollOrientation() == Qt::Vertical);
378
379 QStyleOption option;
380 option.initFrom(this);
381 const int scrollBarInc = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this);
382
383 QSizeF newViewSize = m_controller->view()->size();
384 if (vertical) {
385 newViewSize.rwidth() += scrollBarInc;
386 } else {
387 newViewSize.rheight() += scrollBarInc;
388 }
389
390 const Qt::ScrollBarPolicy policy = view->scrollBarRequired(newViewSize) ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAsNeeded;
391 if (vertical) {
392 setVerticalScrollBarPolicy(policy);
393 } else {
394 setHorizontalScrollBarPolicy(policy);
395 }
396 }
397
398 #include "kitemlistcontainer.moc"
399 #include "moc_kitemlistcontainer.cpp"