File indexing completed on 2024-12-01 03:40:54
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org> 0004 SPDX-FileCopyrightText: 2022 Kai Uwe Broulik <kde@broulik.de> 0005 0006 SPDX-License-Identifier: LGPL-2.0-only 0007 */ 0008 0009 #ifndef KFILEPLACESVIEW_P_H 0010 #define KFILEPLACESVIEW_P_H 0011 0012 #include <KIO/FileSystemFreeSpaceJob> 0013 #include <KIO/Global> 0014 0015 #include <QAbstractItemDelegate> 0016 #include <QDateTime> 0017 #include <QDeadlineTimer> 0018 #include <QGestureEvent> 0019 #include <QMouseEvent> 0020 #include <QPointer> 0021 #include <QScroller> 0022 #include <QTimer> 0023 0024 class KFilePlacesView; 0025 class QTimeLine; 0026 0027 struct PlaceFreeSpaceInfo { 0028 QDeadlineTimer timeout; 0029 KIO::filesize_t used = 0; 0030 KIO::filesize_t size = 0; 0031 QPointer<KIO::FileSystemFreeSpaceJob> job; 0032 }; 0033 0034 class KFilePlacesViewDelegate : public QAbstractItemDelegate 0035 { 0036 Q_OBJECT 0037 public: 0038 explicit KFilePlacesViewDelegate(KFilePlacesView *parent); 0039 ~KFilePlacesViewDelegate() override; 0040 QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; 0041 void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; 0042 0043 int iconSize() const; 0044 void setIconSize(int newSize); 0045 0046 void paletteChange(); 0047 0048 void addAppearingItem(const QModelIndex &index); 0049 void setAppearingItemProgress(qreal value); 0050 void addDisappearingItem(const QModelIndex &index); 0051 void addDisappearingItemGroup(const QModelIndex &index); 0052 void setDisappearingItemProgress(qreal value); 0053 void setDeviceBusyAnimationRotation(qreal angle); 0054 0055 void setShowHoverIndication(bool show); 0056 void setHoveredHeaderArea(const QModelIndex &index); 0057 void setHoveredAction(const QModelIndex &index); 0058 0059 qreal contentsOpacity(const QModelIndex &index) const; 0060 0061 bool pointIsHeaderArea(const QPoint &pos) const; 0062 bool pointIsTeardownAction(const QPoint &pos) const; 0063 0064 void startDrag(); 0065 0066 int sectionHeaderHeight(const QModelIndex &index) const; 0067 bool indexIsSectionHeader(const QModelIndex &index) const; 0068 int actionIconSize() const; 0069 0070 void checkFreeSpace(); 0071 void checkFreeSpace(const QModelIndex &index) const; 0072 void startPollingFreeSpace() const; 0073 void stopPollingFreeSpace() const; 0074 0075 void clearFreeSpaceInfo(); 0076 0077 protected: 0078 bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) override; 0079 0080 private: 0081 QString groupNameFromIndex(const QModelIndex &index) const; 0082 QModelIndex previousVisibleIndex(const QModelIndex &index) const; 0083 void drawSectionHeader(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; 0084 0085 QColor textColor(const QStyleOption &option) const; 0086 QColor baseColor(const QStyleOption &option) const; 0087 QColor mixedColor(const QColor &c1, const QColor &c2, int c1Percent) const; 0088 0089 KFilePlacesView *m_view; 0090 int m_iconSize; 0091 0092 QList<QPersistentModelIndex> m_appearingItems; 0093 qreal m_appearingHeightScale; 0094 qreal m_appearingOpacity; 0095 0096 QList<QPersistentModelIndex> m_disappearingItems; 0097 qreal m_disappearingHeightScale; 0098 qreal m_disappearingOpacity; 0099 0100 qreal m_busyAnimationRotation = 0.0; 0101 0102 bool m_showHoverIndication; 0103 QPersistentModelIndex m_hoveredHeaderArea; 0104 QPersistentModelIndex m_hoveredAction; 0105 mutable bool m_dragStarted; 0106 0107 QMap<QPersistentModelIndex, QTimeLine *> m_timeLineMap; 0108 QMap<QTimeLine *, QPersistentModelIndex> m_timeLineInverseMap; 0109 0110 mutable QTimer m_pollFreeSpace; 0111 mutable QMap<QPersistentModelIndex, PlaceFreeSpaceInfo> m_freeSpaceInfo; 0112 // constructing KColorScheme is expensive, cache the negative color 0113 mutable QColor m_warningCapacityBarColor; 0114 }; 0115 0116 class KFilePlacesEventWatcher : public QObject 0117 { 0118 Q_OBJECT 0119 0120 public: 0121 explicit KFilePlacesEventWatcher(KFilePlacesView *parent = nullptr) 0122 : QObject(parent) 0123 , m_scroller(nullptr) 0124 , q(parent) 0125 , m_rubberBand(nullptr) 0126 , m_isTouchEvent(false) 0127 , m_mousePressed(false) 0128 , m_tapAndHoldActive(false) 0129 , m_lastMouseSource(Qt::MouseEventNotSynthesized) 0130 { 0131 m_rubberBand = new QRubberBand(QRubberBand::Rectangle, parent); 0132 } 0133 0134 const QModelIndex hoveredHeaderAreaIndex() 0135 { 0136 return m_hoveredHeaderAreaIndex; 0137 } 0138 0139 const QModelIndex hoveredActionIndex() 0140 { 0141 return m_hoveredActionIndex; 0142 } 0143 0144 QScroller *m_scroller; 0145 0146 public Q_SLOTS: 0147 void qScrollerStateChanged(const QScroller::State newState) 0148 { 0149 if (newState == QScroller::Inactive) { 0150 m_isTouchEvent = false; 0151 } 0152 } 0153 0154 Q_SIGNALS: 0155 void entryMiddleClicked(const QModelIndex &index); 0156 0157 void headerAreaEntered(const QModelIndex &index); 0158 void headerAreaLeft(const QModelIndex &index); 0159 0160 void actionEntered(const QModelIndex &index); 0161 void actionLeft(const QModelIndex &index); 0162 void actionClicked(const QModelIndex &index); 0163 0164 void windowActivated(); 0165 void windowDeactivated(); 0166 0167 void paletteChanged(); 0168 0169 protected: 0170 bool eventFilter(QObject *watched, QEvent *event) override 0171 { 0172 switch (event->type()) { 0173 case QEvent::MouseMove: { 0174 if (m_isTouchEvent && !m_tapAndHoldActive) { 0175 return true; 0176 } 0177 0178 m_tapAndHoldActive = false; 0179 if (m_rubberBand->isVisible()) { 0180 m_rubberBand->hide(); 0181 } 0182 0183 QAbstractItemView *view = qobject_cast<QAbstractItemView *>(watched->parent()); 0184 const QPoint pos = static_cast<QMouseEvent *>(event)->pos(); 0185 const QModelIndex index = view->indexAt(pos); 0186 0187 QModelIndex headerAreaIndex; 0188 QModelIndex actionIndex; 0189 if (index.isValid()) { 0190 if (auto *delegate = qobject_cast<KFilePlacesViewDelegate *>(view->itemDelegate())) { 0191 if (delegate->pointIsHeaderArea(pos)) { 0192 headerAreaIndex = index; 0193 } else if (delegate->pointIsTeardownAction(pos)) { 0194 actionIndex = index; 0195 } 0196 } 0197 } 0198 0199 if (headerAreaIndex != m_hoveredHeaderAreaIndex) { 0200 if (m_hoveredHeaderAreaIndex.isValid()) { 0201 Q_EMIT headerAreaLeft(m_hoveredHeaderAreaIndex); 0202 } 0203 m_hoveredHeaderAreaIndex = headerAreaIndex; 0204 if (headerAreaIndex.isValid()) { 0205 Q_EMIT headerAreaEntered(headerAreaIndex); 0206 } 0207 } 0208 0209 if (actionIndex != m_hoveredActionIndex) { 0210 if (m_hoveredActionIndex.isValid()) { 0211 Q_EMIT actionLeft(m_hoveredActionIndex); 0212 } 0213 m_hoveredActionIndex = actionIndex; 0214 if (actionIndex.isValid()) { 0215 Q_EMIT actionEntered(actionIndex); 0216 } 0217 } 0218 0219 break; 0220 } 0221 case QEvent::Leave: 0222 if (m_hoveredHeaderAreaIndex.isValid()) { 0223 Q_EMIT headerAreaLeft(m_hoveredHeaderAreaIndex); 0224 } 0225 m_hoveredHeaderAreaIndex = QModelIndex(); 0226 0227 if (m_hoveredActionIndex.isValid()) { 0228 Q_EMIT actionLeft(m_hoveredActionIndex); 0229 } 0230 m_hoveredActionIndex = QModelIndex(); 0231 0232 break; 0233 case QEvent::MouseButtonPress: { 0234 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event); 0235 m_mousePressed = true; 0236 m_lastMouseSource = mouseEvent->source(); 0237 0238 if (m_isTouchEvent) { 0239 return true; 0240 } 0241 onPressed(mouseEvent); 0242 Q_FALLTHROUGH(); 0243 } 0244 case QEvent::MouseButtonDblClick: { 0245 // Prevent the selection clearing by clicking on the viewport directly 0246 QAbstractItemView *view = qobject_cast<QAbstractItemView *>(watched->parent()); 0247 if (!view->indexAt(static_cast<QMouseEvent *>(event)->pos()).isValid()) { 0248 return true; 0249 } 0250 break; 0251 } 0252 case QEvent::MouseButtonRelease: { 0253 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event); 0254 if (mouseEvent->button() == Qt::LeftButton || mouseEvent->button() == Qt::MiddleButton) { 0255 QAbstractItemView *view = qobject_cast<QAbstractItemView *>(watched->parent()); 0256 const QModelIndex index = view->indexAt(mouseEvent->pos()); 0257 0258 if (mouseEvent->button() == Qt::LeftButton) { 0259 if (m_clickedActionIndex.isValid()) { 0260 if (auto *delegate = qobject_cast<KFilePlacesViewDelegate *>(view->itemDelegate())) { 0261 if (delegate->pointIsTeardownAction(mouseEvent->pos())) { 0262 if (m_clickedActionIndex == index) { 0263 Q_EMIT actionClicked(m_clickedActionIndex); 0264 // filter out, avoid QAbstractItemView::clicked being emitted 0265 return true; 0266 } 0267 } 0268 } 0269 } 0270 m_clickedActionIndex = index; 0271 } else if (mouseEvent->button() == Qt::MiddleButton) { 0272 if (m_middleClickedIndex.isValid() && m_middleClickedIndex == index) { 0273 Q_EMIT entryMiddleClicked(m_middleClickedIndex); 0274 } 0275 m_middleClickedIndex = QPersistentModelIndex(); 0276 } 0277 } 0278 break; 0279 } 0280 case QEvent::WindowActivate: 0281 Q_EMIT windowActivated(); 0282 break; 0283 case QEvent::WindowDeactivate: 0284 Q_EMIT windowDeactivated(); 0285 break; 0286 case QEvent::PaletteChange: 0287 Q_EMIT paletteChanged(); 0288 break; 0289 case QEvent::TouchBegin: { 0290 m_isTouchEvent = true; 0291 m_mousePressed = false; 0292 break; 0293 } 0294 case QEvent::Gesture: { 0295 gestureEvent(static_cast<QGestureEvent *>(event)); 0296 event->accept(); 0297 return true; 0298 } 0299 default: 0300 return false; 0301 } 0302 0303 return false; 0304 } 0305 0306 void onPressed(QMouseEvent *mouseEvent) 0307 { 0308 if (mouseEvent->button() == Qt::LeftButton || mouseEvent->button() == Qt::MiddleButton) { 0309 QAbstractItemView *view = qobject_cast<QAbstractItemView *>(q); 0310 const QModelIndex index = view->indexAt(mouseEvent->pos()); 0311 if (index.isValid()) { 0312 if (mouseEvent->button() == Qt::LeftButton) { 0313 if (auto *delegate = qobject_cast<KFilePlacesViewDelegate *>(view->itemDelegate())) { 0314 if (delegate->pointIsTeardownAction(mouseEvent->pos())) { 0315 m_clickedActionIndex = index; 0316 } 0317 } 0318 } else if (mouseEvent->button() == Qt::MiddleButton) { 0319 m_middleClickedIndex = index; 0320 } 0321 } 0322 } 0323 } 0324 0325 void gestureEvent(QGestureEvent *event) 0326 { 0327 if (QGesture *gesture = event->gesture(Qt::TapGesture)) { 0328 tapTriggered(static_cast<QTapGesture *>(gesture)); 0329 } 0330 if (QGesture *gesture = event->gesture(Qt::TapAndHoldGesture)) { 0331 tapAndHoldTriggered(static_cast<QTapAndHoldGesture *>(gesture)); 0332 } 0333 } 0334 0335 void tapAndHoldTriggered(QTapAndHoldGesture *tap) 0336 { 0337 if (tap->state() == Qt::GestureFinished) { 0338 if (!m_mousePressed) { 0339 return; 0340 } 0341 0342 // the TapAndHold gesture is triggerable with the mouse and stylus, we don't want this 0343 if (m_lastMouseSource == Qt::MouseEventNotSynthesized || !m_isTouchEvent) { 0344 return; 0345 } 0346 0347 m_tapAndHoldActive = true; 0348 m_scroller->stop(); 0349 0350 // simulate a mousePressEvent, to allow KFilePlacesView to select the items 0351 const QPointF tapGlobalPos = tap->position(); // QTapAndHoldGesture::position is global 0352 const QPointF tapViewportPos(q->viewport()->mapFromGlobal(tapGlobalPos)); 0353 QMouseEvent fakeMousePress(QEvent::MouseButtonPress, tapViewportPos, tapGlobalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); 0354 onPressed(&fakeMousePress); 0355 q->mousePressEvent(&fakeMousePress); 0356 0357 const QPoint tapIndicatorSize(80, 80); 0358 const QPoint pos(q->mapFromGlobal(tapGlobalPos.toPoint())); 0359 const QRect tapIndicatorRect(pos - (tapIndicatorSize / 2), pos + (tapIndicatorSize / 2)); 0360 m_rubberBand->setGeometry(tapIndicatorRect.normalized()); 0361 m_rubberBand->show(); 0362 } 0363 } 0364 0365 void tapTriggered(QTapGesture *tap) 0366 { 0367 static bool scrollerWasScrolling = false; 0368 0369 if (tap->state() == Qt::GestureStarted) { 0370 m_tapAndHoldActive = false; 0371 // if QScroller state is Scrolling or Dragging, the user makes the tap to stop the scrolling 0372 auto const scrollerState = m_scroller->state(); 0373 if (scrollerState == QScroller::Scrolling || scrollerState == QScroller::Dragging) { 0374 scrollerWasScrolling = true; 0375 } else { 0376 scrollerWasScrolling = false; 0377 } 0378 } 0379 0380 if (tap->state() == Qt::GestureFinished && !scrollerWasScrolling) { 0381 m_isTouchEvent = false; 0382 0383 // with touch you can touch multiple widgets at the same time, but only one widget will get a mousePressEvent. 0384 // we use this to select the right window 0385 if (!m_mousePressed) { 0386 return; 0387 } 0388 0389 if (m_rubberBand->isVisible()) { 0390 m_rubberBand->hide(); 0391 } 0392 // simulate a mousePressEvent, to allow KFilePlacesView to select the items 0393 const QPointF tapPosition = tap->position(); // QTapGesture::position is local 0394 const QPointF globalTapPosition = q->mapToGlobal(tapPosition); 0395 QMouseEvent fakeMousePress(QEvent::MouseButtonPress, 0396 tapPosition, 0397 globalTapPosition, 0398 m_tapAndHoldActive ? Qt::RightButton : Qt::LeftButton, 0399 m_tapAndHoldActive ? Qt::RightButton : Qt::LeftButton, 0400 Qt::NoModifier); 0401 onPressed(&fakeMousePress); 0402 q->mousePressEvent(&fakeMousePress); 0403 0404 if (m_tapAndHoldActive) { 0405 // simulate a contextMenuEvent 0406 QContextMenuEvent fakeContextMenu(QContextMenuEvent::Mouse, tapPosition.toPoint(), globalTapPosition.toPoint()); 0407 q->contextMenuEvent(&fakeContextMenu); 0408 } 0409 m_tapAndHoldActive = false; 0410 } 0411 } 0412 0413 private: 0414 QPersistentModelIndex m_hoveredHeaderAreaIndex; 0415 QPersistentModelIndex m_middleClickedIndex; 0416 QPersistentModelIndex m_hoveredActionIndex; 0417 QPersistentModelIndex m_clickedActionIndex; 0418 0419 KFilePlacesView *const q; 0420 0421 QRubberBand *m_rubberBand; 0422 bool m_isTouchEvent; 0423 bool m_mousePressed; 0424 bool m_tapAndHoldActive; 0425 Qt::MouseEventSource m_lastMouseSource; 0426 }; 0427 0428 #endif