]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontainer.cpp
Fixed grouping again, implemented permission and rating grouping.
[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::contextMenuEvent(QContextMenuEvent *event)
142 {
143 // Note copied from the keyPressEvent() method above because the same reasons probably also apply here.
144 // TODO: We should find a better way to handle the context menu events in the view.
145 // The reasons why we need this hack are:
146 // 1. Without reimplementing contextMenuEvent() here, the event would not reach the QGraphicsView.
147 // 2. By default, the KItemListView does not have the keyboard focus in the QGraphicsScene, so
148 // simply sending the event to the QGraphicsView which is the KItemListContainer's viewport
149 // does not work.
150 KItemListView *view = m_controller->view();
151 if (view) {
152 QApplication::sendEvent(view, event);
153 }
154 }
155
156 void KItemListContainer::showEvent(QShowEvent *event)
157 {
158 QAbstractScrollArea::showEvent(event);
159 updateGeometries();
160 }
161
162 void KItemListContainer::resizeEvent(QResizeEvent *event)
163 {
164 QAbstractScrollArea::resizeEvent(event);
165 updateGeometries();
166 }
167
168 void KItemListContainer::scrollContentsBy(int dx, int dy)
169 {
170 m_horizontalSmoothScroller->scrollContentsBy(dx);
171 m_verticalSmoothScroller->scrollContentsBy(dy);
172 }
173
174 void KItemListContainer::wheelEvent(QWheelEvent *event)
175 {
176 if (event->modifiers().testFlag(Qt::ControlModifier)) {
177 event->ignore();
178 return;
179 }
180
181 KItemListView *view = m_controller->view();
182 if (!view) {
183 event->ignore();
184 return;
185 }
186
187 const bool scrollHorizontally = (qAbs(event->angleDelta().y()) < qAbs(event->angleDelta().x())) || (!verticalScrollBar()->isVisible());
188 KItemListSmoothScroller *smoothScroller = scrollHorizontally ? m_horizontalSmoothScroller : m_verticalSmoothScroller;
189
190 smoothScroller->handleWheelEvent(event);
191 }
192
193 void KItemListContainer::focusInEvent(QFocusEvent *event)
194 {
195 KItemListView *view = m_controller->view();
196 if (view) {
197 QApplication::sendEvent(view, event);
198 }
199 }
200
201 void KItemListContainer::focusOutEvent(QFocusEvent *event)
202 {
203 KItemListView *view = m_controller->view();
204 if (view) {
205 QApplication::sendEvent(view, event);
206 }
207 }
208
209 void KItemListContainer::slotScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
210 {
211 Q_UNUSED(previous)
212 updateSmoothScrollers(current);
213 }
214
215 void KItemListContainer::slotModelChanged(KItemModelBase *current, KItemModelBase *previous)
216 {
217 Q_UNUSED(current)
218 Q_UNUSED(previous)
219 }
220
221 void KItemListContainer::slotViewChanged(KItemListView *current, KItemListView *previous)
222 {
223 QGraphicsScene *scene = static_cast<QGraphicsView *>(viewport())->scene();
224 if (previous) {
225 scene->removeItem(previous);
226 disconnect(previous, &KItemListView::scrollOrientationChanged, this, &KItemListContainer::slotScrollOrientationChanged);
227 disconnect(previous, &KItemListView::scrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar);
228 disconnect(previous, &KItemListView::maximumScrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar);
229 disconnect(previous, &KItemListView::itemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar);
230 disconnect(previous, &KItemListView::maximumItemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar);
231 disconnect(previous, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo);
232 disconnect(m_horizontalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, previous, &KItemListView::scrollingStopped);
233 disconnect(m_verticalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, previous, &KItemListView::scrollingStopped);
234 m_horizontalSmoothScroller->setTargetObject(nullptr);
235 m_verticalSmoothScroller->setTargetObject(nullptr);
236 }
237 if (current) {
238 scene->addItem(current);
239 connect(current, &KItemListView::scrollOrientationChanged, this, &KItemListContainer::slotScrollOrientationChanged);
240 connect(current, &KItemListView::scrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar);
241 connect(current, &KItemListView::maximumScrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar);
242 connect(current, &KItemListView::itemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar);
243 connect(current, &KItemListView::maximumItemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar);
244 connect(current, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo);
245 connect(m_horizontalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, current, &KItemListView::scrollingStopped);
246 connect(m_verticalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, current, &KItemListView::scrollingStopped);
247
248 m_horizontalSmoothScroller->setTargetObject(current);
249 m_verticalSmoothScroller->setTargetObject(current);
250 updateSmoothScrollers(current->scrollOrientation());
251 }
252 }
253
254 void KItemListContainer::scrollTo(qreal offset)
255 {
256 const KItemListView *view = m_controller->view();
257 if (view) {
258 if (view->scrollOrientation() == Qt::Vertical) {
259 m_verticalSmoothScroller->scrollTo(offset);
260 } else {
261 m_horizontalSmoothScroller->scrollTo(offset);
262 }
263 }
264 }
265
266 void KItemListContainer::updateScrollOffsetScrollBar()
267 {
268 const KItemListView *view = m_controller->view();
269 if (!view) {
270 return;
271 }
272
273 KItemListSmoothScroller *smoothScroller = nullptr;
274 QScrollBar *scrollOffsetScrollBar = nullptr;
275 int singleStep = 0;
276 int pageStep = 0;
277 int maximum = 0;
278 if (view->scrollOrientation() == Qt::Vertical) {
279 smoothScroller = m_verticalSmoothScroller;
280 scrollOffsetScrollBar = verticalScrollBar();
281
282 // Don't scroll super fast when using a wheel mouse:
283 // We want to consider one "line" to be the text label which has a
284 // roughly fixed height rather than using the height of the icon which
285 // may be very tall
286 const QFontMetrics metrics(font());
287 singleStep = metrics.height() * QApplication::wheelScrollLines();
288
289 // We cannot use view->size().height() because this height might
290 // include the header widget, which is not part of the scrolled area.
291 pageStep = view->verticalPageStep();
292
293 // However, the total height of the view must be considered for the
294 // maximum value of the scroll bar. Note that the view's scrollOffset()
295 // refers to the offset of the top part of the view, which might be
296 // hidden behind the header.
297 maximum = qMax(0, int(view->maximumScrollOffset() - view->size().height()));
298 } else {
299 smoothScroller = m_horizontalSmoothScroller;
300 scrollOffsetScrollBar = horizontalScrollBar();
301 singleStep = view->itemSize().width();
302 pageStep = view->size().width();
303 maximum = qMax(0, int(view->maximumScrollOffset() - view->size().width()));
304 }
305
306 const int value = view->scrollOffset();
307 if (smoothScroller->requestScrollBarUpdate(maximum)) {
308 const bool updatePolicy = (scrollOffsetScrollBar->maximum() > 0 && maximum == 0) || horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOn;
309
310 scrollOffsetScrollBar->setSingleStep(singleStep);
311 scrollOffsetScrollBar->setPageStep(pageStep);
312 scrollOffsetScrollBar->setMinimum(0);
313 scrollOffsetScrollBar->setMaximum(maximum);
314 scrollOffsetScrollBar->setValue(value);
315
316 if (updatePolicy) {
317 // Prevent a potential endless layout loop (see bug #293318).
318 updateScrollOffsetScrollBarPolicy();
319 }
320 }
321 }
322
323 void KItemListContainer::updateItemOffsetScrollBar()
324 {
325 const KItemListView *view = m_controller->view();
326 if (!view) {
327 return;
328 }
329
330 KItemListSmoothScroller *smoothScroller = nullptr;
331 QScrollBar *itemOffsetScrollBar = nullptr;
332 int singleStep = 0;
333 int pageStep = 0;
334 if (view->scrollOrientation() == Qt::Vertical) {
335 smoothScroller = m_horizontalSmoothScroller;
336 itemOffsetScrollBar = horizontalScrollBar();
337 singleStep = view->size().width() / 10;
338 pageStep = view->size().width();
339 } else {
340 smoothScroller = m_verticalSmoothScroller;
341 itemOffsetScrollBar = verticalScrollBar();
342 singleStep = view->size().height() / 10;
343 pageStep = view->size().height();
344 }
345
346 const int value = view->itemOffset();
347 const int maximum = qMax(0, int(view->maximumItemOffset()) - pageStep);
348 if (smoothScroller->requestScrollBarUpdate(maximum)) {
349 itemOffsetScrollBar->setSingleStep(singleStep);
350 itemOffsetScrollBar->setPageStep(pageStep);
351 itemOffsetScrollBar->setMinimum(0);
352 itemOffsetScrollBar->setMaximum(maximum);
353 itemOffsetScrollBar->setValue(value);
354 }
355 }
356
357 void KItemListContainer::stopScroller()
358 {
359 m_scroller->stop();
360 }
361
362 void KItemListContainer::updateGeometries()
363 {
364 QRect rect = geometry();
365
366 int extra = frameWidth() * 2;
367 QStyleOption option;
368 option.initFrom(this);
369 int scrollbarSpacing = 0;
370 if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &option, this)) {
371 scrollbarSpacing = style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing, &option, this);
372 }
373
374 const int widthDec = verticalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra;
375
376 const int heightDec =
377 horizontalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra;
378
379 const QRectF newGeometry(0, 0, rect.width() - widthDec, rect.height() - heightDec);
380 if (m_controller->view()->geometry() != newGeometry) {
381 m_controller->view()->setGeometry(newGeometry);
382
383 // Get the real geometry of the view again since the scrollbars
384 // visibilities and the view geometry may have changed in re-layout.
385 static_cast<KItemListContainerViewport *>(viewport())->scene()->setSceneRect(m_controller->view()->geometry());
386 static_cast<KItemListContainerViewport *>(viewport())->viewport()->setGeometry(m_controller->view()->geometry().toRect());
387
388 updateScrollOffsetScrollBar();
389 updateItemOffsetScrollBar();
390 }
391 }
392
393 void KItemListContainer::updateSmoothScrollers(Qt::Orientation orientation)
394 {
395 if (orientation == Qt::Vertical) {
396 m_verticalSmoothScroller->setPropertyName("scrollOffset");
397 m_horizontalSmoothScroller->setPropertyName("itemOffset");
398 } else {
399 m_horizontalSmoothScroller->setPropertyName("scrollOffset");
400 m_verticalSmoothScroller->setPropertyName("itemOffset");
401 }
402
403 const bool isRightToLeft = m_controller->view()->layoutDirection() == Qt::RightToLeft;
404 QScrollBar *hScrollBar = horizontalScrollBar();
405 hScrollBar->setInvertedAppearance(isRightToLeft && orientation == Qt::Vertical);
406 hScrollBar->setInvertedControls(!isRightToLeft || orientation == Qt::Vertical);
407 }
408
409 void KItemListContainer::updateScrollOffsetScrollBarPolicy()
410 {
411 const KItemListView *view = m_controller->view();
412 Q_ASSERT(view);
413 const bool vertical = (view->scrollOrientation() == Qt::Vertical);
414
415 QStyleOption option;
416 option.initFrom(this);
417 const int scrollBarInc = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this);
418
419 QSizeF newViewSize = m_controller->view()->size();
420 if (vertical) {
421 newViewSize.rwidth() += scrollBarInc;
422 } else {
423 newViewSize.rheight() += scrollBarInc;
424 }
425
426 const Qt::ScrollBarPolicy policy = view->scrollBarRequired(newViewSize) ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAsNeeded;
427 if (vertical) {
428 setVerticalScrollBarPolicy(policy);
429 } else {
430 setHorizontalScrollBarPolicy(policy);
431 }
432 }
433
434 #include "kitemlistcontainer.moc"
435 #include "moc_kitemlistcontainer.cpp"