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
);
36 void wheelEvent(QWheelEvent
*event
) override
;
39 KItemListContainerViewport::KItemListContainerViewport(QGraphicsScene
*scene
, QWidget
*parent
)
40 : QGraphicsView(scene
, parent
)
42 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff
);
43 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff
);
44 setViewportMargins(0, 0, 0, 0);
45 setFrameShape(QFrame::NoFrame
);
48 void KItemListContainerViewport::wheelEvent(QWheelEvent
*event
)
50 // Assure that the wheel-event gets forwarded to the parent
51 // and not handled at all by QGraphicsView.
55 KItemListContainer::KItemListContainer(KItemListController
*controller
, QWidget
*parent
)
56 : QAbstractScrollArea(parent
)
57 , m_controller(controller
)
58 , m_horizontalSmoothScroller(nullptr)
59 , m_verticalSmoothScroller(nullptr)
63 controller
->setParent(this);
65 QGraphicsView
*graphicsView
= new KItemListContainerViewport(new QGraphicsScene(this), this);
66 setViewport(graphicsView
);
68 m_horizontalSmoothScroller
= new KItemListSmoothScroller(horizontalScrollBar(), this);
69 m_verticalSmoothScroller
= new KItemListSmoothScroller(verticalScrollBar(), this);
71 if (controller
->model()) {
72 slotModelChanged(controller
->model(), nullptr);
74 if (controller
->view()) {
75 slotViewChanged(controller
->view(), nullptr);
78 connect(controller
, &KItemListController::modelChanged
, this, &KItemListContainer::slotModelChanged
);
79 connect(controller
, &KItemListController::viewChanged
, this, &KItemListContainer::slotViewChanged
);
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
);
87 KItemListContainer::~KItemListContainer()
89 // Don't rely on the QObject-order to delete the controller, otherwise
90 // the QGraphicsScene might get deleted before the view.
92 m_controller
= nullptr;
95 KItemListController
*KItemListContainer::controller() const
100 void KItemListContainer::setEnabledFrame(bool enable
)
102 QGraphicsView
*graphicsView
= qobject_cast
<QGraphicsView
*>(viewport());
104 setFrameShape(QFrame::StyledPanel
);
105 graphicsView
->setPalette(palette());
106 graphicsView
->viewport()->setAutoFillBackground(true);
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
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);
121 bool KItemListContainer::enabledFrame() const
123 const QGraphicsView
*graphicsView
= qobject_cast
<QGraphicsView
*>(viewport());
124 return graphicsView
->autoFillBackground();
127 void KItemListContainer::keyPressEvent(QKeyEvent
*event
)
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
135 KItemListView
*view
= m_controller
->view();
137 QApplication::sendEvent(view
, event
);
141 void KItemListContainer::contextMenuEvent(QContextMenuEvent
*event
)
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
150 KItemListView
*view
= m_controller
->view();
152 QApplication::sendEvent(view
, event
);
156 void KItemListContainer::showEvent(QShowEvent
*event
)
158 QAbstractScrollArea::showEvent(event
);
162 void KItemListContainer::resizeEvent(QResizeEvent
*event
)
164 QAbstractScrollArea::resizeEvent(event
);
168 void KItemListContainer::scrollContentsBy(int dx
, int dy
)
170 m_horizontalSmoothScroller
->scrollContentsBy(dx
);
171 m_verticalSmoothScroller
->scrollContentsBy(dy
);
174 void KItemListContainer::wheelEvent(QWheelEvent
*event
)
176 if (event
->modifiers().testFlag(Qt::ControlModifier
)) {
181 KItemListView
*view
= m_controller
->view();
187 const bool scrollHorizontally
= (qAbs(event
->angleDelta().y()) < qAbs(event
->angleDelta().x())) || (!verticalScrollBar()->isVisible());
188 KItemListSmoothScroller
*smoothScroller
= scrollHorizontally
? m_horizontalSmoothScroller
: m_verticalSmoothScroller
;
190 smoothScroller
->handleWheelEvent(event
);
193 void KItemListContainer::focusInEvent(QFocusEvent
*event
)
195 KItemListView
*view
= m_controller
->view();
197 QApplication::sendEvent(view
, event
);
201 void KItemListContainer::focusOutEvent(QFocusEvent
*event
)
203 KItemListView
*view
= m_controller
->view();
205 QApplication::sendEvent(view
, event
);
209 void KItemListContainer::slotScrollOrientationChanged(Qt::Orientation current
, Qt::Orientation previous
)
212 updateSmoothScrollers(current
);
215 void KItemListContainer::slotModelChanged(KItemModelBase
*current
, KItemModelBase
*previous
)
221 void KItemListContainer::slotViewChanged(KItemListView
*current
, KItemListView
*previous
)
223 QGraphicsScene
*scene
= static_cast<QGraphicsView
*>(viewport())->scene();
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);
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
);
248 m_horizontalSmoothScroller
->setTargetObject(current
);
249 m_verticalSmoothScroller
->setTargetObject(current
);
250 updateSmoothScrollers(current
->scrollOrientation());
254 void KItemListContainer::scrollTo(qreal offset
)
256 const KItemListView
*view
= m_controller
->view();
258 if (view
->scrollOrientation() == Qt::Vertical
) {
259 m_verticalSmoothScroller
->scrollTo(offset
);
261 m_horizontalSmoothScroller
->scrollTo(offset
);
266 void KItemListContainer::updateScrollOffsetScrollBar()
268 const KItemListView
*view
= m_controller
->view();
273 KItemListSmoothScroller
*smoothScroller
= nullptr;
274 QScrollBar
*scrollOffsetScrollBar
= nullptr;
278 if (view
->scrollOrientation() == Qt::Vertical
) {
279 smoothScroller
= m_verticalSmoothScroller
;
280 scrollOffsetScrollBar
= verticalScrollBar();
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
286 const QFontMetrics
metrics(font());
287 singleStep
= metrics
.height() * QApplication::wheelScrollLines();
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();
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()));
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()));
306 const int value
= view
->scrollOffset();
307 if (smoothScroller
->requestScrollBarUpdate(maximum
)) {
308 const bool updatePolicy
= (scrollOffsetScrollBar
->maximum() > 0 && maximum
== 0) || horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOn
;
310 scrollOffsetScrollBar
->setSingleStep(singleStep
);
311 scrollOffsetScrollBar
->setPageStep(pageStep
);
312 scrollOffsetScrollBar
->setMinimum(0);
313 scrollOffsetScrollBar
->setMaximum(maximum
);
314 scrollOffsetScrollBar
->setValue(value
);
317 // Prevent a potential endless layout loop (see bug #293318).
318 updateScrollOffsetScrollBarPolicy();
323 void KItemListContainer::updateItemOffsetScrollBar()
325 const KItemListView
*view
= m_controller
->view();
330 KItemListSmoothScroller
*smoothScroller
= nullptr;
331 QScrollBar
*itemOffsetScrollBar
= nullptr;
334 if (view
->scrollOrientation() == Qt::Vertical
) {
335 smoothScroller
= m_horizontalSmoothScroller
;
336 itemOffsetScrollBar
= horizontalScrollBar();
337 singleStep
= view
->size().width() / 10;
338 pageStep
= view
->size().width();
340 smoothScroller
= m_verticalSmoothScroller
;
341 itemOffsetScrollBar
= verticalScrollBar();
342 singleStep
= view
->size().height() / 10;
343 pageStep
= view
->size().height();
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
);
357 void KItemListContainer::stopScroller()
362 void KItemListContainer::updateGeometries()
364 QRect rect
= geometry();
366 int extra
= frameWidth() * 2;
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);
374 const int widthDec
= verticalScrollBar()->isVisible() ? extra
+ scrollbarSpacing
+ style()->pixelMetric(QStyle::PM_ScrollBarExtent
, &option
, this) : extra
;
376 const int heightDec
=
377 horizontalScrollBar()->isVisible() ? extra
+ scrollbarSpacing
+ style()->pixelMetric(QStyle::PM_ScrollBarExtent
, &option
, this) : extra
;
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
);
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());
388 updateScrollOffsetScrollBar();
389 updateItemOffsetScrollBar();
393 void KItemListContainer::updateSmoothScrollers(Qt::Orientation orientation
)
395 if (orientation
== Qt::Vertical
) {
396 m_verticalSmoothScroller
->setPropertyName("scrollOffset");
397 m_horizontalSmoothScroller
->setPropertyName("itemOffset");
399 m_horizontalSmoothScroller
->setPropertyName("scrollOffset");
400 m_verticalSmoothScroller
->setPropertyName("itemOffset");
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
);
409 void KItemListContainer::updateScrollOffsetScrollBarPolicy()
411 const KItemListView
*view
= m_controller
->view();
413 const bool vertical
= (view
->scrollOrientation() == Qt::Vertical
);
416 option
.initFrom(this);
417 const int scrollBarInc
= style()->pixelMetric(QStyle::PM_ScrollBarExtent
, &option
, this);
419 QSizeF newViewSize
= m_controller
->view()->size();
421 newViewSize
.rwidth() += scrollBarInc
;
423 newViewSize
.rheight() += scrollBarInc
;
426 const Qt::ScrollBarPolicy policy
= view
->scrollBarRequired(newViewSize
) ? Qt::ScrollBarAlwaysOn
: Qt::ScrollBarAsNeeded
;
428 setVerticalScrollBarPolicy(policy
);
430 setHorizontalScrollBarPolicy(policy
);
434 #include "kitemlistcontainer.moc"
435 #include "moc_kitemlistcontainer.cpp"