2 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
4 * Based on the Itemviews NG project from Trolltech Labs
6 * SPDX-License-Identifier: GPL-2.0-or-later
9 #include "kitemlistcontainer.h"
11 #include "kitemlistcontroller.h"
12 #include "kitemlistview.h"
13 #include "private/kitemlistsmoothscroller.h"
15 #include <QApplication>
16 #include <QFontMetrics>
17 #include <QGraphicsScene>
18 #include <QGraphicsView>
21 #include <QStyleOption>
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.
28 class KItemListContainerViewport
: public QGraphicsView
33 KItemListContainerViewport(QGraphicsScene
* scene
, QWidget
* parent
);
35 void wheelEvent(QWheelEvent
* event
) override
;
38 KItemListContainerViewport::KItemListContainerViewport(QGraphicsScene
* scene
, QWidget
* parent
) :
39 QGraphicsView(scene
, parent
)
41 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff
);
42 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff
);
43 setViewportMargins(0, 0, 0, 0);
44 setFrameShape(QFrame::NoFrame
);
47 void KItemListContainerViewport::wheelEvent(QWheelEvent
* event
)
49 // Assure that the wheel-event gets forwarded to the parent
50 // and not handled at all by QGraphicsView.
54 KItemListContainer::KItemListContainer(KItemListController
* controller
, QWidget
* parent
) :
55 QAbstractScrollArea(parent
),
56 m_controller(controller
),
57 m_horizontalSmoothScroller(nullptr),
58 m_verticalSmoothScroller(nullptr),
62 controller
->setParent(this);
64 QGraphicsView
* graphicsView
= new KItemListContainerViewport(new QGraphicsScene(this), this);
65 setViewport(graphicsView
);
67 m_horizontalSmoothScroller
= new KItemListSmoothScroller(horizontalScrollBar(), this);
68 m_verticalSmoothScroller
= new KItemListSmoothScroller(verticalScrollBar(), this);
70 if (controller
->model()) {
71 slotModelChanged(controller
->model(), nullptr);
73 if (controller
->view()) {
74 slotViewChanged(controller
->view(), nullptr);
77 connect(controller
, &KItemListController::modelChanged
,
78 this, &KItemListContainer::slotModelChanged
);
79 connect(controller
, &KItemListController::viewChanged
,
80 this, &KItemListContainer::slotViewChanged
);
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
);
90 KItemListContainer::~KItemListContainer()
92 // Don't rely on the QObject-order to delete the controller, otherwise
93 // the QGraphicsScene might get deleted before the view.
95 m_controller
= nullptr;
98 KItemListController
* KItemListContainer::controller() const
103 void KItemListContainer::setEnabledFrame(bool enable
)
105 QGraphicsView
* graphicsView
= qobject_cast
<QGraphicsView
*>(viewport());
107 setFrameShape(QFrame::StyledPanel
);
108 graphicsView
->setPalette(palette());
109 graphicsView
->viewport()->setAutoFillBackground(true);
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
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);
124 bool KItemListContainer::enabledFrame() const
126 const QGraphicsView
* graphicsView
= qobject_cast
<QGraphicsView
*>(viewport());
127 return graphicsView
->autoFillBackground();
130 void KItemListContainer::keyPressEvent(QKeyEvent
* event
)
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
138 KItemListView
* view
= m_controller
->view();
140 QApplication::sendEvent(view
, event
);
144 void KItemListContainer::showEvent(QShowEvent
* event
)
146 QAbstractScrollArea::showEvent(event
);
150 void KItemListContainer::resizeEvent(QResizeEvent
* event
)
152 QAbstractScrollArea::resizeEvent(event
);
156 void KItemListContainer::scrollContentsBy(int dx
, int dy
)
158 m_horizontalSmoothScroller
->scrollContentsBy(dx
);
159 m_verticalSmoothScroller
->scrollContentsBy(dy
);
162 void KItemListContainer::wheelEvent(QWheelEvent
* event
)
164 if (event
->modifiers().testFlag(Qt::ControlModifier
)) {
169 KItemListView
* view
= m_controller
->view();
175 const bool scrollHorizontally
= (qAbs(event
->angleDelta().y()) < qAbs(event
->angleDelta().x())) ||
176 (!verticalScrollBar()->isVisible());
177 KItemListSmoothScroller
* smoothScroller
= scrollHorizontally
?
178 m_horizontalSmoothScroller
: m_verticalSmoothScroller
;
180 smoothScroller
->handleWheelEvent(event
);
183 void KItemListContainer::slotScrollOrientationChanged(Qt::Orientation current
, Qt::Orientation previous
)
186 updateSmoothScrollers(current
);
189 void KItemListContainer::slotModelChanged(KItemModelBase
* current
, KItemModelBase
* previous
)
195 void KItemListContainer::slotViewChanged(KItemListView
* current
, KItemListView
* previous
)
197 QGraphicsScene
* scene
= static_cast<QGraphicsView
*>(viewport())->scene();
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 disconnect(m_horizontalSmoothScroller
, &KItemListSmoothScroller::scrollingStopped
, previous
, &KItemListView::scrollingStopped
);
212 disconnect(m_verticalSmoothScroller
, &KItemListSmoothScroller::scrollingStopped
, previous
, &KItemListView::scrollingStopped
);
213 m_horizontalSmoothScroller
->setTargetObject(nullptr);
214 m_verticalSmoothScroller
->setTargetObject(nullptr);
217 scene
->addItem(current
);
218 connect(current
, &KItemListView::scrollOrientationChanged
,
219 this, &KItemListContainer::slotScrollOrientationChanged
);
220 connect(current
, &KItemListView::scrollOffsetChanged
,
221 this, &KItemListContainer::updateScrollOffsetScrollBar
);
222 connect(current
, &KItemListView::maximumScrollOffsetChanged
,
223 this, &KItemListContainer::updateScrollOffsetScrollBar
);
224 connect(current
, &KItemListView::itemOffsetChanged
,
225 this, &KItemListContainer::updateItemOffsetScrollBar
);
226 connect(current
, &KItemListView::maximumItemOffsetChanged
,
227 this, &KItemListContainer::updateItemOffsetScrollBar
);
228 connect(current
, &KItemListView::scrollTo
, this, &KItemListContainer::scrollTo
);
229 connect(m_horizontalSmoothScroller
, &KItemListSmoothScroller::scrollingStopped
, current
, &KItemListView::scrollingStopped
);
230 connect(m_verticalSmoothScroller
, &KItemListSmoothScroller::scrollingStopped
, current
, &KItemListView::scrollingStopped
);
232 m_horizontalSmoothScroller
->setTargetObject(current
);
233 m_verticalSmoothScroller
->setTargetObject(current
);
234 updateSmoothScrollers(current
->scrollOrientation());
238 void KItemListContainer::scrollTo(qreal offset
)
240 const KItemListView
* view
= m_controller
->view();
242 if (view
->scrollOrientation() == Qt::Vertical
) {
243 m_verticalSmoothScroller
->scrollTo(offset
);
245 m_horizontalSmoothScroller
->scrollTo(offset
);
250 void KItemListContainer::updateScrollOffsetScrollBar()
252 const KItemListView
* view
= m_controller
->view();
257 KItemListSmoothScroller
* smoothScroller
= nullptr;
258 QScrollBar
* scrollOffsetScrollBar
= nullptr;
262 if (view
->scrollOrientation() == Qt::Vertical
) {
263 smoothScroller
= m_verticalSmoothScroller
;
264 scrollOffsetScrollBar
= verticalScrollBar();
266 // Don't scroll super fast when using a wheel mouse:
267 // We want to consider one "line" to be the text label which has a
268 // roughly fixed height rather than using the height of the icon which
270 const QFontMetrics
metrics(font());
271 singleStep
= metrics
.height() * QApplication::wheelScrollLines();
273 // We cannot use view->size().height() because this height might
274 // include the header widget, which is not part of the scrolled area.
275 pageStep
= view
->verticalPageStep();
277 // However, the total height of the view must be considered for the
278 // maximum value of the scroll bar. Note that the view's scrollOffset()
279 // refers to the offset of the top part of the view, which might be
280 // hidden behind the header.
281 maximum
= qMax(0, int(view
->maximumScrollOffset() - view
->size().height()));
283 smoothScroller
= m_horizontalSmoothScroller
;
284 scrollOffsetScrollBar
= horizontalScrollBar();
285 singleStep
= view
->itemSize().width();
286 pageStep
= view
->size().width();
287 maximum
= qMax(0, int(view
->maximumScrollOffset() - view
->size().width()));
290 const int value
= view
->scrollOffset();
291 if (smoothScroller
->requestScrollBarUpdate(maximum
)) {
292 const bool updatePolicy
= (scrollOffsetScrollBar
->maximum() > 0 && maximum
== 0)
293 || horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOn
;
295 scrollOffsetScrollBar
->setSingleStep(singleStep
);
296 scrollOffsetScrollBar
->setPageStep(pageStep
);
297 scrollOffsetScrollBar
->setMinimum(0);
298 scrollOffsetScrollBar
->setMaximum(maximum
);
299 scrollOffsetScrollBar
->setValue(value
);
302 // Prevent a potential endless layout loop (see bug #293318).
303 updateScrollOffsetScrollBarPolicy();
308 void KItemListContainer::updateItemOffsetScrollBar()
310 const KItemListView
* view
= m_controller
->view();
315 KItemListSmoothScroller
* smoothScroller
= nullptr;
316 QScrollBar
* itemOffsetScrollBar
= nullptr;
319 if (view
->scrollOrientation() == Qt::Vertical
) {
320 smoothScroller
= m_horizontalSmoothScroller
;
321 itemOffsetScrollBar
= horizontalScrollBar();
322 singleStep
= view
->size().width() / 10;
323 pageStep
= view
->size().width();
325 smoothScroller
= m_verticalSmoothScroller
;
326 itemOffsetScrollBar
= verticalScrollBar();
327 singleStep
= view
->size().height() / 10;
328 pageStep
= view
->size().height();
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
);
342 void KItemListContainer::stopScroller()
347 void KItemListContainer::updateGeometries()
349 QRect rect
= geometry();
351 int extra
= frameWidth() * 2;
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);
359 const int widthDec
= verticalScrollBar()->isVisible()
360 ? extra
+ scrollbarSpacing
+ style()->pixelMetric(QStyle::PM_ScrollBarExtent
, &option
, this)
363 const int heightDec
= horizontalScrollBar()->isVisible()
364 ? extra
+ scrollbarSpacing
+ style()->pixelMetric(QStyle::PM_ScrollBarExtent
, &option
, this)
367 const QRectF
newGeometry(0, 0, rect
.width() - widthDec
,
368 rect
.height() - heightDec
);
369 if (m_controller
->view()->geometry() != newGeometry
) {
370 m_controller
->view()->setGeometry(newGeometry
);
372 // Get the real geometry of the view again since the scrollbars
373 // visibilities and the view geometry may have changed in re-layout.
374 static_cast<KItemListContainerViewport
*>(viewport())->scene()->setSceneRect(m_controller
->view()->geometry());
375 static_cast<KItemListContainerViewport
*>(viewport())->viewport()->setGeometry(m_controller
->view()->geometry().toRect());
377 updateScrollOffsetScrollBar();
378 updateItemOffsetScrollBar();
382 void KItemListContainer::updateSmoothScrollers(Qt::Orientation orientation
)
384 if (orientation
== Qt::Vertical
) {
385 m_verticalSmoothScroller
->setPropertyName("scrollOffset");
386 m_horizontalSmoothScroller
->setPropertyName("itemOffset");
388 m_horizontalSmoothScroller
->setPropertyName("scrollOffset");
389 m_verticalSmoothScroller
->setPropertyName("itemOffset");
393 void KItemListContainer::updateScrollOffsetScrollBarPolicy()
395 const KItemListView
* view
= m_controller
->view();
397 const bool vertical
= (view
->scrollOrientation() == Qt::Vertical
);
400 option
.initFrom(this);
401 const int scrollBarInc
= style()->pixelMetric(QStyle::PM_ScrollBarExtent
, &option
, this);
403 QSizeF newViewSize
= m_controller
->view()->size();
405 newViewSize
.rwidth() += scrollBarInc
;
407 newViewSize
.rheight() += scrollBarInc
;
410 const Qt::ScrollBarPolicy policy
= view
->scrollBarRequired(newViewSize
)
411 ? Qt::ScrollBarAlwaysOn
: Qt::ScrollBarAsNeeded
;
413 setVerticalScrollBarPolicy(policy
);
415 setHorizontalScrollBarPolicy(policy
);
419 #include "kitemlistcontainer.moc"