|
11 | 11 | #include <QGraphicsScene> |
12 | 12 | #include <QGraphicsSceneMouseEvent> |
13 | 13 | #include <QGraphicsView> |
| 14 | +#include <QMouseEvent> |
| 15 | +#include <QRubberBand> |
14 | 16 | #include <QStyleOptionGraphicsItem> |
15 | 17 | #include <QtWidgets/QCheckBox> |
16 | 18 | #include <QtWidgets/QMessageBox> |
@@ -143,7 +145,12 @@ class ThumbnailSequence::Impl { |
143 | 145 |
|
144 | 146 | void setSelectionModeEnabled(bool enabled); |
145 | 147 |
|
146 | | - private: |
| 148 | + void extendSelectionBySceneRect(const QRectF& sceneRect); |
| 149 | + |
| 150 | + static bool isDescendantOfCompositeItem(QGraphicsItem* item); |
| 151 | + |
| 152 | + class ViewportRubberBandFilter; |
| 153 | + |
147 | 154 | class ItemsByIdTag; |
148 | 155 | class ItemsInOrderTag; |
149 | 156 | class SelectedThenUnselectedTag; |
@@ -233,6 +240,26 @@ class ThumbnailSequence::Impl { |
233 | 240 | GraphicsScene m_graphicsScene; |
234 | 241 | QRectF m_sceneRect; |
235 | 242 | bool m_selectionMode; |
| 243 | + |
| 244 | + std::unique_ptr<ViewportRubberBandFilter> m_rubberBandFilter; |
| 245 | +}; |
| 246 | + |
| 247 | + |
| 248 | +class ThumbnailSequence::Impl::ViewportRubberBandFilter : public QObject { |
| 249 | + public: |
| 250 | + explicit ViewportRubberBandFilter(Impl* impl, QGraphicsView* view); |
| 251 | + |
| 252 | + ~ViewportRubberBandFilter() override; |
| 253 | + |
| 254 | + protected: |
| 255 | + bool eventFilter(QObject* watched, QEvent* event) override; |
| 256 | + |
| 257 | + private: |
| 258 | + Impl* m_impl; |
| 259 | + QGraphicsView* m_view; |
| 260 | + QRubberBand m_rubberBand; |
| 261 | + QPoint m_origin; |
| 262 | + bool m_active = false; |
236 | 263 | }; |
237 | 264 |
|
238 | 265 |
|
@@ -283,14 +310,14 @@ class ThumbnailSequence::CompositeItem : public QGraphicsItemGroup { |
283 | 310 |
|
284 | 311 | void updateAppearence(bool selected, bool selectionLeader); |
285 | 312 |
|
286 | | - virtual QRectF boundingRect() const; |
| 313 | + QRectF boundingRect() const override; |
287 | 314 |
|
288 | | - virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); |
| 315 | + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; |
289 | 316 |
|
290 | 317 | protected: |
291 | | - virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent* event); |
| 318 | + void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) override; |
292 | 319 |
|
293 | | - virtual void mousePressEvent(QGraphicsSceneMouseEvent* event); |
| 320 | + void mousePressEvent(QGraphicsSceneMouseEvent* event) override; |
294 | 321 |
|
295 | 322 | private: |
296 | 323 | // We no longer use QGraphicsView's selection mechanism, so we |
@@ -447,14 +474,18 @@ ThumbnailSequence::Impl::Impl(ThumbnailSequence& owner, const QSizeF& maxLogical |
447 | 474 | [&](QGraphicsSceneContextMenuEvent* evt) { this->sceneContextMenuEvent(evt); }); |
448 | 475 | } |
449 | 476 |
|
450 | | -ThumbnailSequence::Impl::~Impl() {} |
| 477 | +ThumbnailSequence::Impl::~Impl() { |
| 478 | + m_rubberBandFilter.reset(); |
| 479 | +} |
451 | 480 |
|
452 | 481 | void ThumbnailSequence::Impl::setThumbnailFactory(std::shared_ptr<ThumbnailFactory> factory) { |
453 | 482 | m_factory = std::move(factory); |
454 | 483 | } |
455 | 484 |
|
456 | 485 | void ThumbnailSequence::Impl::attachView(QGraphicsView* const view) { |
| 486 | + m_rubberBandFilter.reset(); |
457 | 487 | view->setScene(&m_graphicsScene); |
| 488 | + m_rubberBandFilter = std::make_unique<ViewportRubberBandFilter>(this, view); |
458 | 489 | } |
459 | 490 |
|
460 | 491 | void ThumbnailSequence::Impl::reset(const PageSequence& pages, |
@@ -1225,6 +1256,57 @@ void ThumbnailSequence::Impl::clearSelection() { |
1225 | 1256 | } |
1226 | 1257 | } |
1227 | 1258 |
|
| 1259 | +bool ThumbnailSequence::Impl::isDescendantOfCompositeItem(QGraphicsItem* item) { |
| 1260 | + for (; item; item = item->parentItem()) { |
| 1261 | + if (dynamic_cast<CompositeItem*>(item)) { |
| 1262 | + return true; |
| 1263 | + } |
| 1264 | + } |
| 1265 | + return false; |
| 1266 | +} |
| 1267 | + |
| 1268 | +void ThumbnailSequence::Impl::extendSelectionBySceneRect(const QRectF& sceneRect) { |
| 1269 | + if (!sceneRect.isValid()) { |
| 1270 | + return; |
| 1271 | + } |
| 1272 | + |
| 1273 | + const Item* firstHit = nullptr; |
| 1274 | + |
| 1275 | + for (const Item& item : m_itemsInOrder) { |
| 1276 | + const QRectF br(item.composite->mapToScene(item.composite->boundingRect()).boundingRect()); |
| 1277 | + if (!br.intersects(sceneRect)) { |
| 1278 | + continue; |
| 1279 | + } |
| 1280 | + if (!firstHit) { |
| 1281 | + firstHit = &item; |
| 1282 | + } |
| 1283 | + if (!item.isSelected()) { |
| 1284 | + item.setSelected(true); |
| 1285 | + moveToSelected(&item); |
| 1286 | + } |
| 1287 | + } |
| 1288 | + |
| 1289 | + if (!firstHit) { |
| 1290 | + return; |
| 1291 | + } |
| 1292 | + |
| 1293 | + const bool leaderStillValid |
| 1294 | + = m_selectionLeader && m_selectionLeader->isSelected() |
| 1295 | + && m_selectionLeader->composite->mapToScene(m_selectionLeader->composite->boundingRect()) |
| 1296 | + .boundingRect() |
| 1297 | + .intersects(sceneRect); |
| 1298 | + |
| 1299 | + if (!leaderStillValid) { |
| 1300 | + if (m_selectionLeader) { |
| 1301 | + m_selectionLeader->setSelectionLeader(false); |
| 1302 | + } |
| 1303 | + const_cast<Item*>(firstHit)->setSelectionLeader(true); |
| 1304 | + m_selectionLeader = firstHit; |
| 1305 | + } |
| 1306 | + |
| 1307 | + m_owner.emitNewSelectionLeader(m_selectionLeader->pageInfo, m_selectionLeader->composite, SELECTED_BY_USER); |
| 1308 | +} |
| 1309 | + |
1228 | 1310 | ThumbnailSequence::Impl::ItemsInOrder::iterator ThumbnailSequence::Impl::itemInsertPosition( |
1229 | 1311 | const ItemsInOrder::iterator begin, |
1230 | 1312 | const ItemsInOrder::iterator end, |
@@ -1568,3 +1650,63 @@ void ThumbnailSequence::CompositeItem::contextMenuEvent(QGraphicsSceneContextMen |
1568 | 1650 | event->accept(); // Prevent it from propagating further. |
1569 | 1651 | m_owner.contextMenuRequested(m_item->pageInfo, event->screenPos(), m_item->isSelected()); |
1570 | 1652 | } |
| 1653 | + |
| 1654 | +ThumbnailSequence::Impl::ViewportRubberBandFilter::ViewportRubberBandFilter(Impl* impl, QGraphicsView* view) |
| 1655 | + : QObject(view->viewport()), m_impl(impl), m_view(view), m_rubberBand(QRubberBand::Rectangle, view->viewport()) { |
| 1656 | + view->viewport()->installEventFilter(this); |
| 1657 | +} |
| 1658 | + |
| 1659 | +ThumbnailSequence::Impl::ViewportRubberBandFilter::~ViewportRubberBandFilter() { |
| 1660 | + if (m_view) { |
| 1661 | + m_view->viewport()->removeEventFilter(this); |
| 1662 | + } |
| 1663 | +} |
| 1664 | + |
| 1665 | +bool ThumbnailSequence::Impl::ViewportRubberBandFilter::eventFilter(QObject* watched, QEvent* event) { |
| 1666 | + if (!m_view || watched != m_view->viewport()) { |
| 1667 | + return QObject::eventFilter(watched, event); |
| 1668 | + } |
| 1669 | + |
| 1670 | + switch (event->type()) { |
| 1671 | + case QEvent::MouseButtonPress: { |
| 1672 | + auto* const me = static_cast<QMouseEvent*>(event); |
| 1673 | + if (me->button() != Qt::LeftButton || !(me->modifiers() & Qt::ControlModifier)) { |
| 1674 | + return false; |
| 1675 | + } |
| 1676 | + QGraphicsItem* const top = m_view->itemAt(me->pos()); |
| 1677 | + if (Impl::isDescendantOfCompositeItem(top)) { |
| 1678 | + return false; |
| 1679 | + } |
| 1680 | + m_origin = me->pos(); |
| 1681 | + m_active = true; |
| 1682 | + m_rubberBand.setGeometry(QRect(m_origin, QSize())); |
| 1683 | + m_rubberBand.show(); |
| 1684 | + return true; |
| 1685 | + } |
| 1686 | + case QEvent::MouseMove: { |
| 1687 | + if (!m_active) { |
| 1688 | + return false; |
| 1689 | + } |
| 1690 | + auto* const me = static_cast<QMouseEvent*>(event); |
| 1691 | + m_rubberBand.setGeometry(QRect(m_origin, me->pos()).normalized()); |
| 1692 | + return true; |
| 1693 | + } |
| 1694 | + case QEvent::MouseButtonRelease: { |
| 1695 | + auto* const me = static_cast<QMouseEvent*>(event); |
| 1696 | + if (!m_active || me->button() != Qt::LeftButton) { |
| 1697 | + return false; |
| 1698 | + } |
| 1699 | + m_active = false; |
| 1700 | + m_rubberBand.hide(); |
| 1701 | + const QRect r(m_rubberBand.geometry().normalized()); |
| 1702 | + if (r.width() > 3 && r.height() > 3) { |
| 1703 | + const QRectF sceneRect(m_view->mapToScene(r).boundingRect()); |
| 1704 | + m_impl->extendSelectionBySceneRect(sceneRect); |
| 1705 | + } |
| 1706 | + return true; |
| 1707 | + } |
| 1708 | + default: |
| 1709 | + break; |
| 1710 | + } |
| 1711 | + return false; |
| 1712 | +} |
0 commit comments