File indexing completed on 2024-04-28 05:45:06
0001 /* 0002 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com> 0003 * 0004 * Based on the Itemviews NG project from Trolltech Labs 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "kitemlistcontainer.h" 0010 0011 #include "kitemlistcontroller.h" 0012 #include "kitemlistview.h" 0013 #include "private/kitemlistsmoothscroller.h" 0014 0015 #include <QApplication> 0016 #include <QFontMetrics> 0017 #include <QGraphicsScene> 0018 #include <QGraphicsView> 0019 #include <QScrollBar> 0020 #include <QScroller> 0021 #include <QStyleOption> 0022 0023 /** 0024 * Replaces the default viewport of KItemListContainer by a 0025 * non-scrollable viewport. The scrolling is done in an optimized 0026 * way by KItemListView internally. 0027 */ 0028 class KItemListContainerViewport : public QGraphicsView 0029 { 0030 Q_OBJECT 0031 0032 public: 0033 KItemListContainerViewport(QGraphicsScene *scene, QWidget *parent); 0034 0035 protected: 0036 void wheelEvent(QWheelEvent *event) override; 0037 }; 0038 0039 KItemListContainerViewport::KItemListContainerViewport(QGraphicsScene *scene, QWidget *parent) 0040 : QGraphicsView(scene, parent) 0041 { 0042 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0043 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0044 setViewportMargins(0, 0, 0, 0); 0045 setFrameShape(QFrame::NoFrame); 0046 } 0047 0048 void KItemListContainerViewport::wheelEvent(QWheelEvent *event) 0049 { 0050 // Assure that the wheel-event gets forwarded to the parent 0051 // and not handled at all by QGraphicsView. 0052 event->ignore(); 0053 } 0054 0055 KItemListContainer::KItemListContainer(KItemListController *controller, QWidget *parent) 0056 : QAbstractScrollArea(parent) 0057 , m_controller(controller) 0058 , m_horizontalSmoothScroller(nullptr) 0059 , m_verticalSmoothScroller(nullptr) 0060 , m_scroller(nullptr) 0061 { 0062 Q_ASSERT(controller); 0063 controller->setParent(this); 0064 0065 QGraphicsView *graphicsView = new KItemListContainerViewport(new QGraphicsScene(this), this); 0066 setViewport(graphicsView); 0067 0068 m_horizontalSmoothScroller = new KItemListSmoothScroller(horizontalScrollBar(), this); 0069 m_verticalSmoothScroller = new KItemListSmoothScroller(verticalScrollBar(), this); 0070 0071 if (controller->model()) { 0072 slotModelChanged(controller->model(), nullptr); 0073 } 0074 if (controller->view()) { 0075 slotViewChanged(controller->view(), nullptr); 0076 } 0077 0078 connect(controller, &KItemListController::modelChanged, this, &KItemListContainer::slotModelChanged); 0079 connect(controller, &KItemListController::viewChanged, this, &KItemListContainer::slotViewChanged); 0080 0081 m_scroller = QScroller::scroller(viewport()); 0082 m_scroller->grabGesture(viewport()); 0083 connect(controller, &KItemListController::scrollerStop, this, &KItemListContainer::stopScroller); 0084 connect(m_scroller, &QScroller::stateChanged, controller, &KItemListController::slotStateChanged); 0085 } 0086 0087 KItemListContainer::~KItemListContainer() 0088 { 0089 // Don't rely on the QObject-order to delete the controller, otherwise 0090 // the QGraphicsScene might get deleted before the view. 0091 delete m_controller; 0092 m_controller = nullptr; 0093 } 0094 0095 KItemListController *KItemListContainer::controller() const 0096 { 0097 return m_controller; 0098 } 0099 0100 void KItemListContainer::setEnabledFrame(bool enable) 0101 { 0102 QGraphicsView *graphicsView = qobject_cast<QGraphicsView *>(viewport()); 0103 if (enable) { 0104 setFrameShape(QFrame::StyledPanel); 0105 graphicsView->setPalette(palette()); 0106 graphicsView->viewport()->setAutoFillBackground(true); 0107 } else { 0108 setFrameShape(QFrame::NoFrame); 0109 // Make the background of the container transparent and apply the window-text color 0110 // to the text color, so that enough contrast is given for all color 0111 // schemes 0112 QPalette p = graphicsView->palette(); 0113 p.setColor(QPalette::Active, QPalette::Text, p.color(QPalette::Active, QPalette::WindowText)); 0114 p.setColor(QPalette::Inactive, QPalette::Text, p.color(QPalette::Inactive, QPalette::WindowText)); 0115 p.setColor(QPalette::Disabled, QPalette::Text, p.color(QPalette::Disabled, QPalette::WindowText)); 0116 graphicsView->setPalette(p); 0117 graphicsView->viewport()->setAutoFillBackground(false); 0118 } 0119 } 0120 0121 bool KItemListContainer::enabledFrame() const 0122 { 0123 const QGraphicsView *graphicsView = qobject_cast<QGraphicsView *>(viewport()); 0124 return graphicsView->autoFillBackground(); 0125 } 0126 0127 void KItemListContainer::keyPressEvent(QKeyEvent *event) 0128 { 0129 // TODO: We should find a better way to handle the key press events in the view. 0130 // The reasons why we need this hack are: 0131 // 1. Without reimplementing keyPressEvent() here, the event would not reach the QGraphicsView. 0132 // 2. By default, the KItemListView does not have the keyboard focus in the QGraphicsScene, so 0133 // simply sending the event to the QGraphicsView which is the KItemListContainer's viewport 0134 // does not work. 0135 KItemListView *view = m_controller->view(); 0136 if (view) { 0137 QApplication::sendEvent(view, event); 0138 } 0139 } 0140 0141 void KItemListContainer::contextMenuEvent(QContextMenuEvent *event) 0142 { 0143 // Note copied from the keyPressEvent() method above because the same reasons probably also apply here. 0144 // TODO: We should find a better way to handle the context menu events in the view. 0145 // The reasons why we need this hack are: 0146 // 1. Without reimplementing contextMenuEvent() here, the event would not reach the QGraphicsView. 0147 // 2. By default, the KItemListView does not have the keyboard focus in the QGraphicsScene, so 0148 // simply sending the event to the QGraphicsView which is the KItemListContainer's viewport 0149 // does not work. 0150 KItemListView *view = m_controller->view(); 0151 if (view) { 0152 QApplication::sendEvent(view, event); 0153 } 0154 } 0155 0156 void KItemListContainer::showEvent(QShowEvent *event) 0157 { 0158 QAbstractScrollArea::showEvent(event); 0159 updateGeometries(); 0160 } 0161 0162 void KItemListContainer::resizeEvent(QResizeEvent *event) 0163 { 0164 QAbstractScrollArea::resizeEvent(event); 0165 updateGeometries(); 0166 } 0167 0168 void KItemListContainer::scrollContentsBy(int dx, int dy) 0169 { 0170 m_horizontalSmoothScroller->scrollContentsBy(dx); 0171 m_verticalSmoothScroller->scrollContentsBy(dy); 0172 } 0173 0174 void KItemListContainer::wheelEvent(QWheelEvent *event) 0175 { 0176 if (event->modifiers().testFlag(Qt::ControlModifier)) { 0177 event->ignore(); 0178 return; 0179 } 0180 0181 KItemListView *view = m_controller->view(); 0182 if (!view) { 0183 event->ignore(); 0184 return; 0185 } 0186 0187 const bool scrollHorizontally = (qAbs(event->angleDelta().y()) < qAbs(event->angleDelta().x())) || (!verticalScrollBar()->isVisible()); 0188 KItemListSmoothScroller *smoothScroller = scrollHorizontally ? m_horizontalSmoothScroller : m_verticalSmoothScroller; 0189 0190 smoothScroller->handleWheelEvent(event); 0191 } 0192 0193 void KItemListContainer::focusInEvent(QFocusEvent *event) 0194 { 0195 KItemListView *view = m_controller->view(); 0196 if (view) { 0197 QApplication::sendEvent(view, event); 0198 } 0199 } 0200 0201 void KItemListContainer::focusOutEvent(QFocusEvent *event) 0202 { 0203 KItemListView *view = m_controller->view(); 0204 if (view) { 0205 QApplication::sendEvent(view, event); 0206 } 0207 } 0208 0209 void KItemListContainer::slotScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) 0210 { 0211 Q_UNUSED(previous) 0212 updateSmoothScrollers(current); 0213 } 0214 0215 void KItemListContainer::slotModelChanged(KItemModelBase *current, KItemModelBase *previous) 0216 { 0217 Q_UNUSED(current) 0218 Q_UNUSED(previous) 0219 } 0220 0221 void KItemListContainer::slotViewChanged(KItemListView *current, KItemListView *previous) 0222 { 0223 QGraphicsScene *scene = static_cast<QGraphicsView *>(viewport())->scene(); 0224 if (previous) { 0225 scene->removeItem(previous); 0226 disconnect(previous, &KItemListView::scrollOrientationChanged, this, &KItemListContainer::slotScrollOrientationChanged); 0227 disconnect(previous, &KItemListView::scrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar); 0228 disconnect(previous, &KItemListView::maximumScrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar); 0229 disconnect(previous, &KItemListView::itemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar); 0230 disconnect(previous, &KItemListView::maximumItemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar); 0231 disconnect(previous, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo); 0232 disconnect(m_horizontalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, previous, &KItemListView::scrollingStopped); 0233 disconnect(m_verticalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, previous, &KItemListView::scrollingStopped); 0234 m_horizontalSmoothScroller->setTargetObject(nullptr); 0235 m_verticalSmoothScroller->setTargetObject(nullptr); 0236 } 0237 if (current) { 0238 scene->addItem(current); 0239 connect(current, &KItemListView::scrollOrientationChanged, this, &KItemListContainer::slotScrollOrientationChanged); 0240 connect(current, &KItemListView::scrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar); 0241 connect(current, &KItemListView::maximumScrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar); 0242 connect(current, &KItemListView::itemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar); 0243 connect(current, &KItemListView::maximumItemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar); 0244 connect(current, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo); 0245 connect(m_horizontalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, current, &KItemListView::scrollingStopped); 0246 connect(m_verticalSmoothScroller, &KItemListSmoothScroller::scrollingStopped, current, &KItemListView::scrollingStopped); 0247 0248 m_horizontalSmoothScroller->setTargetObject(current); 0249 m_verticalSmoothScroller->setTargetObject(current); 0250 updateSmoothScrollers(current->scrollOrientation()); 0251 } 0252 } 0253 0254 void KItemListContainer::scrollTo(qreal offset) 0255 { 0256 const KItemListView *view = m_controller->view(); 0257 if (view) { 0258 if (view->scrollOrientation() == Qt::Vertical) { 0259 m_verticalSmoothScroller->scrollTo(offset); 0260 } else { 0261 m_horizontalSmoothScroller->scrollTo(offset); 0262 } 0263 } 0264 } 0265 0266 void KItemListContainer::updateScrollOffsetScrollBar() 0267 { 0268 const KItemListView *view = m_controller->view(); 0269 if (!view) { 0270 return; 0271 } 0272 0273 KItemListSmoothScroller *smoothScroller = nullptr; 0274 QScrollBar *scrollOffsetScrollBar = nullptr; 0275 int singleStep = 0; 0276 int pageStep = 0; 0277 int maximum = 0; 0278 if (view->scrollOrientation() == Qt::Vertical) { 0279 smoothScroller = m_verticalSmoothScroller; 0280 scrollOffsetScrollBar = verticalScrollBar(); 0281 0282 // Don't scroll super fast when using a wheel mouse: 0283 // We want to consider one "line" to be the text label which has a 0284 // roughly fixed height rather than using the height of the icon which 0285 // may be very tall 0286 const QFontMetrics metrics(font()); 0287 singleStep = metrics.height() * QApplication::wheelScrollLines(); 0288 0289 // We cannot use view->size().height() because this height might 0290 // include the header widget, which is not part of the scrolled area. 0291 pageStep = view->verticalPageStep(); 0292 0293 // However, the total height of the view must be considered for the 0294 // maximum value of the scroll bar. Note that the view's scrollOffset() 0295 // refers to the offset of the top part of the view, which might be 0296 // hidden behind the header. 0297 maximum = qMax(0, int(view->maximumScrollOffset() - view->size().height())); 0298 } else { 0299 smoothScroller = m_horizontalSmoothScroller; 0300 scrollOffsetScrollBar = horizontalScrollBar(); 0301 singleStep = view->itemSize().width(); 0302 pageStep = view->size().width(); 0303 maximum = qMax(0, int(view->maximumScrollOffset() - view->size().width())); 0304 } 0305 0306 const int value = view->scrollOffset(); 0307 if (smoothScroller->requestScrollBarUpdate(maximum)) { 0308 const bool updatePolicy = (scrollOffsetScrollBar->maximum() > 0 && maximum == 0) || horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOn; 0309 0310 scrollOffsetScrollBar->setSingleStep(singleStep); 0311 scrollOffsetScrollBar->setPageStep(pageStep); 0312 scrollOffsetScrollBar->setMinimum(0); 0313 scrollOffsetScrollBar->setMaximum(maximum); 0314 scrollOffsetScrollBar->setValue(value); 0315 0316 if (updatePolicy) { 0317 // Prevent a potential endless layout loop (see bug #293318). 0318 updateScrollOffsetScrollBarPolicy(); 0319 } 0320 } 0321 } 0322 0323 void KItemListContainer::updateItemOffsetScrollBar() 0324 { 0325 const KItemListView *view = m_controller->view(); 0326 if (!view) { 0327 return; 0328 } 0329 0330 KItemListSmoothScroller *smoothScroller = nullptr; 0331 QScrollBar *itemOffsetScrollBar = nullptr; 0332 int singleStep = 0; 0333 int pageStep = 0; 0334 if (view->scrollOrientation() == Qt::Vertical) { 0335 smoothScroller = m_horizontalSmoothScroller; 0336 itemOffsetScrollBar = horizontalScrollBar(); 0337 singleStep = view->size().width() / 10; 0338 pageStep = view->size().width(); 0339 } else { 0340 smoothScroller = m_verticalSmoothScroller; 0341 itemOffsetScrollBar = verticalScrollBar(); 0342 singleStep = view->size().height() / 10; 0343 pageStep = view->size().height(); 0344 } 0345 0346 const int value = view->itemOffset(); 0347 const int maximum = qMax(0, int(view->maximumItemOffset()) - pageStep); 0348 if (smoothScroller->requestScrollBarUpdate(maximum)) { 0349 itemOffsetScrollBar->setSingleStep(singleStep); 0350 itemOffsetScrollBar->setPageStep(pageStep); 0351 itemOffsetScrollBar->setMinimum(0); 0352 itemOffsetScrollBar->setMaximum(maximum); 0353 itemOffsetScrollBar->setValue(value); 0354 } 0355 } 0356 0357 void KItemListContainer::stopScroller() 0358 { 0359 m_scroller->stop(); 0360 } 0361 0362 void KItemListContainer::updateGeometries() 0363 { 0364 QRect rect = geometry(); 0365 0366 int extra = frameWidth() * 2; 0367 QStyleOption option; 0368 option.initFrom(this); 0369 int scrollbarSpacing = 0; 0370 if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &option, this)) { 0371 scrollbarSpacing = style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing, &option, this); 0372 } 0373 0374 const int widthDec = verticalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra; 0375 0376 const int heightDec = 0377 horizontalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra; 0378 0379 const QRectF newGeometry(0, 0, rect.width() - widthDec, rect.height() - heightDec); 0380 if (m_controller->view()->geometry() != newGeometry) { 0381 m_controller->view()->setGeometry(newGeometry); 0382 0383 // Get the real geometry of the view again since the scrollbars 0384 // visibilities and the view geometry may have changed in re-layout. 0385 static_cast<KItemListContainerViewport *>(viewport())->scene()->setSceneRect(m_controller->view()->geometry()); 0386 static_cast<KItemListContainerViewport *>(viewport())->viewport()->setGeometry(m_controller->view()->geometry().toRect()); 0387 0388 updateScrollOffsetScrollBar(); 0389 updateItemOffsetScrollBar(); 0390 } 0391 } 0392 0393 void KItemListContainer::updateSmoothScrollers(Qt::Orientation orientation) 0394 { 0395 if (orientation == Qt::Vertical) { 0396 m_verticalSmoothScroller->setPropertyName("scrollOffset"); 0397 m_horizontalSmoothScroller->setPropertyName("itemOffset"); 0398 } else { 0399 m_horizontalSmoothScroller->setPropertyName("scrollOffset"); 0400 m_verticalSmoothScroller->setPropertyName("itemOffset"); 0401 } 0402 } 0403 0404 void KItemListContainer::updateScrollOffsetScrollBarPolicy() 0405 { 0406 const KItemListView *view = m_controller->view(); 0407 Q_ASSERT(view); 0408 const bool vertical = (view->scrollOrientation() == Qt::Vertical); 0409 0410 QStyleOption option; 0411 option.initFrom(this); 0412 const int scrollBarInc = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this); 0413 0414 QSizeF newViewSize = m_controller->view()->size(); 0415 if (vertical) { 0416 newViewSize.rwidth() += scrollBarInc; 0417 } else { 0418 newViewSize.rheight() += scrollBarInc; 0419 } 0420 0421 const Qt::ScrollBarPolicy policy = view->scrollBarRequired(newViewSize) ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAsNeeded; 0422 if (vertical) { 0423 setVerticalScrollBarPolicy(policy); 0424 } else { 0425 setHorizontalScrollBarPolicy(policy); 0426 } 0427 } 0428 0429 #include "kitemlistcontainer.moc" 0430 #include "moc_kitemlistcontainer.cpp"