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 #ifndef QT_NO_ACCESSIBILITY
16 #include "accessibility/kitemlistviewaccessible.h"
18 #include <QApplication>
19 #include <QFontMetrics>
20 #include <QGraphicsScene>
21 #include <QGraphicsView>
24 #include <QStyleOption>
27 * Replaces the default viewport of KItemListContainer by a
28 * non-scrollable viewport. The scrolling is done in an optimized
29 * way by KItemListView internally.
31 class KItemListContainerViewport
: public QGraphicsView
36 KItemListContainerViewport(QGraphicsScene
*scene
, QWidget
*parent
);
39 void wheelEvent(QWheelEvent
*event
) override
;
42 KItemListContainerViewport::KItemListContainerViewport(QGraphicsScene
*scene
, QWidget
*parent
)
43 : QGraphicsView(scene
, parent
)
45 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff
);
46 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff
);
47 setViewportMargins(0, 0, 0, 0);
48 setFrameShape(QFrame::NoFrame
);
51 void KItemListContainerViewport::wheelEvent(QWheelEvent
*event
)
53 // Assure that the wheel-event gets forwarded to the parent
54 // and not handled at all by QGraphicsView.
58 KItemListContainer::KItemListContainer(KItemListController
*controller
, QWidget
*parent
)
59 : QAbstractScrollArea(parent
)
60 , m_controller(controller
)
61 , m_horizontalSmoothScroller(nullptr)
62 , m_verticalSmoothScroller(nullptr)
66 controller
->setParent(this);
68 QGraphicsView
*graphicsView
= new KItemListContainerViewport(new QGraphicsScene(this), this);
69 setViewport(graphicsView
);
71 m_horizontalSmoothScroller
= new KItemListSmoothScroller(horizontalScrollBar(), this);
72 m_verticalSmoothScroller
= new KItemListSmoothScroller(verticalScrollBar(), this);
74 if (controller
->model()) {
75 slotModelChanged(controller
->model(), nullptr);
77 if (controller
->view()) {
78 slotViewChanged(controller
->view(), nullptr);
81 connect(controller
, &KItemListController::modelChanged
, this, &KItemListContainer::slotModelChanged
);
82 connect(controller
, &KItemListController::viewChanged
, this, &KItemListContainer::slotViewChanged
);
84 m_scroller
= QScroller::scroller(viewport());
85 m_scroller
->grabGesture(viewport());
86 connect(controller
, &KItemListController::scrollerStop
, this, &KItemListContainer::stopScroller
);
87 connect(m_scroller
, &QScroller::stateChanged
, 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::contextMenuEvent(QContextMenuEvent
*event
)
146 // Note copied from the keyPressEvent() method above because the same reasons probably also apply here.
147 // TODO: We should find a better way to handle the context menu events in the view.
148 // The reasons why we need this hack are:
149 // 1. Without reimplementing contextMenuEvent() here, the event would not reach the QGraphicsView.
150 // 2. By default, the KItemListView does not have the keyboard focus in the QGraphicsScene, so
151 // simply sending the event to the QGraphicsView which is the KItemListContainer's viewport
153 KItemListView
*view
= m_controller
->view();
155 QApplication::sendEvent(view
, event
);
159 void KItemListContainer::showEvent(QShowEvent
*event
)
161 QAbstractScrollArea::showEvent(event
);
165 void KItemListContainer::resizeEvent(QResizeEvent
*event
)
167 QAbstractScrollArea::resizeEvent(event
);
171 void KItemListContainer::scrollContentsBy(int dx
, int dy
)
173 m_horizontalSmoothScroller
->scrollContentsBy(dx
);
174 m_verticalSmoothScroller
->scrollContentsBy(dy
);
177 void KItemListContainer::wheelEvent(QWheelEvent
*event
)
179 if (event
->modifiers().testFlag(Qt::ControlModifier
)) {
184 KItemListView
*view
= m_controller
->view();
190 const bool scrollHorizontally
= (qAbs(event
->angleDelta().y()) < qAbs(event
->angleDelta().x())) || (!verticalScrollBar()->isVisible());
191 KItemListSmoothScroller
*smoothScroller
= scrollHorizontally
? m_horizontalSmoothScroller
: m_verticalSmoothScroller
;
193 smoothScroller
->handleWheelEvent(event
);
196 void KItemListContainer::focusInEvent(QFocusEvent
*event
)
198 KItemListView
*view
= m_controller
->view();
200 QApplication::sendEvent(view
, event
);
202 // We need to set the focus to the view or accessibility software will only announce the container (which has no information available itself).
203 // For some reason actively setting the focus to the view needs to be delayed or the focus will immediately go back to this container.
204 QTimer::singleShot(0, this, [this, view
]() {
205 if (!isAncestorOf(QApplication::focusWidget())) {
208 #ifndef QT_NO_ACCESSIBILITY
209 static_cast<KItemListViewAccessible
*>(QAccessible::queryAccessibleInterface(view
))->setAccessibleFocusAndAnnounceAll();
215 void KItemListContainer::focusOutEvent(QFocusEvent
*event
)
217 KItemListView
*view
= m_controller
->view();
219 QApplication::sendEvent(view
, event
);
223 void KItemListContainer::slotScrollOrientationChanged(Qt::Orientation current
, Qt::Orientation previous
)
226 updateSmoothScrollers(current
);
229 void KItemListContainer::slotModelChanged(KItemModelBase
*current
, KItemModelBase
*previous
)
235 void KItemListContainer::slotViewChanged(KItemListView
*current
, KItemListView
*previous
)
237 QGraphicsScene
*scene
= static_cast<QGraphicsView
*>(viewport())->scene();
239 scene
->removeItem(previous
);
240 disconnect(previous
, &KItemListView::scrollOrientationChanged
, this, &KItemListContainer::slotScrollOrientationChanged
);
241 disconnect(previous
, &KItemListView::scrollOffsetChanged
, this, &KItemListContainer::updateScrollOffsetScrollBar
);
242 disconnect(previous
, &KItemListView::maximumScrollOffsetChanged
, this, &KItemListContainer::updateScrollOffsetScrollBar
);
243 disconnect(previous
, &KItemListView::itemOffsetChanged
, this, &KItemListContainer::updateItemOffsetScrollBar
);
244 disconnect(previous
, &KItemListView::maximumItemOffsetChanged
, this, &KItemListContainer::updateItemOffsetScrollBar
);
245 disconnect(previous
, &KItemListView::scrollTo
, this, &KItemListContainer::scrollTo
);
246 disconnect(m_horizontalSmoothScroller
, &KItemListSmoothScroller::scrollingStopped
, previous
, &KItemListView::scrollingStopped
);
247 disconnect(m_verticalSmoothScroller
, &KItemListSmoothScroller::scrollingStopped
, previous
, &KItemListView::scrollingStopped
);
248 m_horizontalSmoothScroller
->setTargetObject(nullptr);
249 m_verticalSmoothScroller
->setTargetObject(nullptr);
252 scene
->addItem(current
);
253 connect(current
, &KItemListView::scrollOrientationChanged
, this, &KItemListContainer::slotScrollOrientationChanged
);
254 connect(current
, &KItemListView::scrollOffsetChanged
, this, &KItemListContainer::updateScrollOffsetScrollBar
);
255 connect(current
, &KItemListView::maximumScrollOffsetChanged
, this, &KItemListContainer::updateScrollOffsetScrollBar
);
256 connect(current
, &KItemListView::itemOffsetChanged
, this, &KItemListContainer::updateItemOffsetScrollBar
);
257 connect(current
, &KItemListView::maximumItemOffsetChanged
, this, &KItemListContainer::updateItemOffsetScrollBar
);
258 connect(current
, &KItemListView::scrollTo
, this, &KItemListContainer::scrollTo
);
259 connect(m_horizontalSmoothScroller
, &KItemListSmoothScroller::scrollingStopped
, current
, &KItemListView::scrollingStopped
);
260 connect(m_verticalSmoothScroller
, &KItemListSmoothScroller::scrollingStopped
, current
, &KItemListView::scrollingStopped
);
262 m_horizontalSmoothScroller
->setTargetObject(current
);
263 m_verticalSmoothScroller
->setTargetObject(current
);
264 updateSmoothScrollers(current
->scrollOrientation());
268 void KItemListContainer::scrollTo(qreal offset
)
270 const KItemListView
*view
= m_controller
->view();
272 if (view
->scrollOrientation() == Qt::Vertical
) {
273 m_verticalSmoothScroller
->scrollTo(offset
);
275 m_horizontalSmoothScroller
->scrollTo(offset
);
280 void KItemListContainer::updateScrollOffsetScrollBar()
282 const KItemListView
*view
= m_controller
->view();
287 KItemListSmoothScroller
*smoothScroller
= nullptr;
288 QScrollBar
*scrollOffsetScrollBar
= nullptr;
292 if (view
->scrollOrientation() == Qt::Vertical
) {
293 smoothScroller
= m_verticalSmoothScroller
;
294 if (smoothScroller
->isAnimating()) {
297 scrollOffsetScrollBar
= verticalScrollBar();
299 // Don't scroll super fast when using a wheel mouse:
300 // We want to consider one "line" to be the text label which has a
301 // roughly fixed height rather than using the height of the icon which
303 const QFontMetrics
metrics(font());
304 singleStep
= metrics
.height() * QApplication::wheelScrollLines();
306 // We cannot use view->size().height() because this height might
307 // include the header widget, which is not part of the scrolled area.
308 pageStep
= view
->verticalPageStep();
310 // However, the total height of the view must be considered for the
311 // maximum value of the scroll bar. Note that the view's scrollOffset()
312 // refers to the offset of the top part of the view, which might be
313 // hidden behind the header.
314 maximum
= qMax(0, int(view
->maximumScrollOffset() - view
->size().height()));
316 smoothScroller
= m_horizontalSmoothScroller
;
317 if (smoothScroller
->isAnimating()) {
320 scrollOffsetScrollBar
= horizontalScrollBar();
321 singleStep
= view
->itemSize().width();
322 pageStep
= view
->size().width();
323 maximum
= qMax(0, int(view
->maximumScrollOffset() - view
->size().width()));
326 const int value
= view
->scrollOffset();
327 if (smoothScroller
->requestScrollBarUpdate(maximum
)) {
328 const bool updatePolicy
= (scrollOffsetScrollBar
->maximum() > 0 && maximum
== 0) || horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOn
;
330 scrollOffsetScrollBar
->setSingleStep(singleStep
);
331 scrollOffsetScrollBar
->setPageStep(pageStep
);
332 scrollOffsetScrollBar
->setMinimum(0);
333 scrollOffsetScrollBar
->setMaximum(maximum
);
334 scrollOffsetScrollBar
->setValue(value
);
337 // Prevent a potential endless layout loop (see bug #293318).
338 updateScrollOffsetScrollBarPolicy();
343 void KItemListContainer::updateItemOffsetScrollBar()
345 const KItemListView
*view
= m_controller
->view();
350 KItemListSmoothScroller
*smoothScroller
= nullptr;
351 QScrollBar
*itemOffsetScrollBar
= nullptr;
354 if (view
->scrollOrientation() == Qt::Vertical
) {
355 smoothScroller
= m_horizontalSmoothScroller
;
356 if (smoothScroller
->isAnimating()) {
359 itemOffsetScrollBar
= horizontalScrollBar();
360 singleStep
= view
->size().width() / 10;
361 pageStep
= view
->size().width();
363 smoothScroller
= m_verticalSmoothScroller
;
364 if (smoothScroller
->isAnimating()) {
367 itemOffsetScrollBar
= verticalScrollBar();
368 singleStep
= view
->size().height() / 10;
369 pageStep
= view
->size().height();
372 const int value
= view
->itemOffset();
373 const int maximum
= qMax(0, int(view
->maximumItemOffset()) - pageStep
);
374 if (smoothScroller
->requestScrollBarUpdate(maximum
)) {
375 itemOffsetScrollBar
->setSingleStep(singleStep
);
376 itemOffsetScrollBar
->setPageStep(pageStep
);
377 itemOffsetScrollBar
->setMinimum(0);
378 itemOffsetScrollBar
->setMaximum(maximum
);
379 itemOffsetScrollBar
->setValue(value
);
383 void KItemListContainer::stopScroller()
388 void KItemListContainer::updateGeometries()
390 QRect rect
= geometry();
392 int extra
= frameWidth() * 2;
394 option
.initFrom(this);
395 int scrollbarSpacing
= 0;
396 if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents
, &option
, this)) {
397 scrollbarSpacing
= style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing
, &option
, this);
400 const int widthDec
= verticalScrollBar()->isVisible() ? extra
+ scrollbarSpacing
+ style()->pixelMetric(QStyle::PM_ScrollBarExtent
, &option
, this) : extra
;
402 const int heightDec
=
403 horizontalScrollBar()->isVisible() ? extra
+ scrollbarSpacing
+ style()->pixelMetric(QStyle::PM_ScrollBarExtent
, &option
, this) : extra
;
405 const QRectF
newGeometry(0, 0, rect
.width() - widthDec
, rect
.height() - heightDec
);
406 if (m_controller
->view()->geometry() != newGeometry
) {
407 m_controller
->view()->setGeometry(newGeometry
);
409 // Get the real geometry of the view again since the scrollbars
410 // visibilities and the view geometry may have changed in re-layout.
411 static_cast<KItemListContainerViewport
*>(viewport())->scene()->setSceneRect(m_controller
->view()->geometry());
412 static_cast<KItemListContainerViewport
*>(viewport())->viewport()->setGeometry(m_controller
->view()->geometry().toRect());
414 updateScrollOffsetScrollBar();
415 updateItemOffsetScrollBar();
419 void KItemListContainer::updateSmoothScrollers(Qt::Orientation orientation
)
421 if (orientation
== Qt::Vertical
) {
422 m_verticalSmoothScroller
->setPropertyName("scrollOffset");
423 m_horizontalSmoothScroller
->setPropertyName("itemOffset");
425 m_horizontalSmoothScroller
->setPropertyName("scrollOffset");
426 m_verticalSmoothScroller
->setPropertyName("itemOffset");
429 const bool isRightToLeft
= m_controller
->view()->layoutDirection() == Qt::RightToLeft
;
430 QScrollBar
*hScrollBar
= horizontalScrollBar();
431 hScrollBar
->setInvertedAppearance(isRightToLeft
&& orientation
== Qt::Vertical
);
432 hScrollBar
->setInvertedControls(!isRightToLeft
|| orientation
== Qt::Vertical
);
435 void KItemListContainer::updateScrollOffsetScrollBarPolicy()
437 const KItemListView
*view
= m_controller
->view();
439 const bool vertical
= (view
->scrollOrientation() == Qt::Vertical
);
442 option
.initFrom(this);
443 const int scrollBarInc
= style()->pixelMetric(QStyle::PM_ScrollBarExtent
, &option
, this);
445 QSizeF newViewSize
= m_controller
->view()->size();
447 newViewSize
.rwidth() += scrollBarInc
;
449 newViewSize
.rheight() += scrollBarInc
;
452 const Qt::ScrollBarPolicy policy
= view
->scrollBarRequired(newViewSize
) ? Qt::ScrollBarAlwaysOn
: Qt::ScrollBarAsNeeded
;
454 setVerticalScrollBarPolicy(policy
);
456 setHorizontalScrollBarPolicy(policy
);
460 #include "kitemlistcontainer.moc"
461 #include "moc_kitemlistcontainer.cpp"