hydrogen 1.2.3
Selection.h
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2024 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
23#ifndef SELECTION_H
24#define SELECTION_H
25
26#include <QtGui>
27#include <QWidget>
28#include <QMouseEvent>
29#include <QApplication>
30#include <QScrollArea>
31#include <vector>
32#include <set>
33#include <QDebug>
34#include <cassert>
35#include <memory>
36
38
44
46template <class Elem>
48public:
49
50 typedef Elem SelectionIndex;
51
54 virtual std::vector<SelectionIndex> elementsIntersecting( QRect r ) = 0;
55
57 virtual bool canDragElements() { return true; }
58
60 virtual QRect getKeyboardCursorRect() = 0;
61
63 virtual void validateSelection() = 0;
64
67 virtual void updateWidget() = 0;
68
70 virtual bool checkDeselectElements( std::vector<SelectionIndex> &elements ) {
71 return true;
72 }
73
74
85 virtual void mouseClickEvent( QMouseEvent *ev ) = 0;
86 virtual void mouseDragStartEvent( QMouseEvent *ev ) = 0;
87 virtual void mouseDragUpdateEvent( QMouseEvent *ev ) = 0;
88 virtual void mouseDragEndEvent( QMouseEvent *ev ) = 0;
89 virtual void selectionMoveUpdateEvent( QMouseEvent *ev ) {};
90 virtual void selectionMoveEndEvent( QInputEvent *ev ) = 0;
91 virtual void selectionMoveCancelEvent() {};
93
97 virtual void startMouseLasso( QMouseEvent *ev ) {}
98 virtual void startMouseMove( QMouseEvent *ev ) {}
99 virtual void endMouseGesture() {}
101
104 virtual QScrollArea *findScrollArea() {
105 QWidget *pThisWidget = dynamic_cast< QWidget *>( this );
106 if ( pThisWidget ) {
107 QWidget *pParent = dynamic_cast< QWidget *>( pThisWidget->parent() );
108 if ( pParent ) {
109 QScrollArea *pScrollArea = dynamic_cast< QScrollArea *>( pParent->parent() );
110 if ( pScrollArea ) {
111 return pScrollArea;
112 }
113 }
114 }
115 return nullptr;
116 }
117
118};
119
120
126
127class DragScroller : public QObject {
128 Q_OBJECT
129 QTimer *m_pTimer;
130 QScrollArea *m_pScrollArea;
131 const int m_nInterval = 20; // ms
132
133public:
134 DragScroller( QScrollArea *pScrollArea ) {
135 m_pTimer = nullptr;
136 m_pScrollArea = pScrollArea;
137 }
138
140 if ( m_pTimer != nullptr) {
141 delete m_pTimer;
142 }
143 }
144
145 void startDrag() {
146 if ( m_pTimer == nullptr ) {
147 m_pTimer = new QTimer( this );
148 m_pTimer->setInterval( m_nInterval );
149 connect( m_pTimer, &QTimer::timeout, this, &DragScroller::timeout );
150 }
151 m_pTimer->start();
152 }
153
154 void endDrag() {
155 m_pTimer->stop();
156 }
157
158public slots:
159 void timeout( void ) {
160 QWidget *pWidget = m_pScrollArea->widget();
161 QPoint pos = pWidget->mapFromGlobal( QCursor::pos() );
162 m_pScrollArea->ensureVisible( pos.x(), pos.y(), 1, 1 );
163 }
164};
165
189
191template<class Elem>
193
197 std::set<Elem> m_selectedElements;
198 std::set< SelectionWidget<Elem> *> m_selectionWidgets;
199 };
200
201 std::shared_ptr< SelectionGroup > m_pSelectionGroup;
203
204
205private:
207
227 Qt::MouseButton m_mouseButton;
228 QMouseEvent *m_pClickEvent;
230
231
254
255 QRect m_lasso;
259
263
266
267
268public:
269
271
272 m_pWidget = w;
273 m_mouseButton = Qt::NoButton;
275 m_pClickEvent = nullptr;
277 m_pSelectionGroup = std::make_shared< SelectionGroup >();
278 m_pSelectionGroup->m_selectionWidgets.insert( w );
279 m_pDragScroller = nullptr;
280
281 }
282
286 void merge( Selection *pOther ) {
287 if ( pOther != this ) {
288 pOther->m_pSelectionGroup->m_selectionWidgets.insert
289 ( m_pSelectionGroup->m_selectionWidgets.begin(),
290 m_pSelectionGroup->m_selectionWidgets.end() );
291 }
293 }
294
295 void dump() {
296
297 qDebug() << "Mouse state: " << ( m_mouseState == Up ? "Up" :
298 m_mouseState == Down ? "Down" :
299 m_mouseState == Dragging ? "Dragging" : "-" )
300 << "\n"
301 << "button: " << m_mouseButton << "\n"
302 << "Selection state: " << ( m_selectionState == Idle ? "Idle" :
303 m_selectionState == MouseLasso ? "MouseLasso" :
304 m_selectionState == MouseMoving ? "MouseMoving" :
305 m_selectionState == KeyboardLasso ? "KeyboardLasso" :
306 m_selectionState == KeyboardMoving ? "KeyboardMoving" :
307 "-")
308 << "";
309
310 }
311
312
314 bool isMoving() const {
316 }
317
319 bool isMouseGesture() const {
321 }
322
325 QPoint movingOffset() const {
326 return m_movingOffset;
327 }
328
330 bool isLasso() const {
332 }
333
335 bool isSelected( Elem e ) const {
336 return m_pSelectionGroup->m_selectedElements.find( e ) != m_pSelectionGroup->m_selectedElements.end();
337 }
338
339 void removeFromSelection( Elem e, bool bCheck = true ) {
340 std::vector<Elem> v { e };
341 if ( !bCheck || m_pWidget->checkDeselectElements( v ) ) {
342 m_pSelectionGroup->m_selectedElements.erase( e );
343 }
344 }
345
346 void addToSelection( Elem e ) {
347 m_pSelectionGroup->m_selectedElements.insert( e );
348 }
349
350 void clearSelection( bool bCheck = true ) {
351 std::vector<Elem> v ( m_pSelectionGroup->m_selectedElements.begin(),
352 m_pSelectionGroup->m_selectedElements.end() );
353 if ( !bCheck || m_pWidget->checkDeselectElements( v ) ) {
354 m_pSelectionGroup->m_selectedElements.clear();
355 }
356 }
357
368 typedef typename std::set<Elem>::iterator iterator;
369
370 iterator begin() { return m_pSelectionGroup->m_selectedElements.begin(); }
371 iterator end() { return m_pSelectionGroup->m_selectedElements.end(); }
373
376 for ( auto pW : m_pSelectionGroup->m_selectionWidgets ) {
377 pW->updateWidget();
378 }
379 }
380
383 if ( m_mouseState == Dragging ) {
384 mouseDragEnd();
385 }
387 if ( m_pClickEvent ) {
388 delete m_pClickEvent;
389 m_pClickEvent = nullptr;
390 }
392 m_pWidget->endMouseGesture();
393 m_pWidget->selectionMoveCancelEvent();
394 }
397 }
398
399 // -------------------------------------------------------------------------------------------------------
409
410 void mousePressEvent( QMouseEvent *ev ) {
411
412 // macOS ctrl+left-click is reported as a
413 // right-click. However, only the 'press' event is reported,
414 // there are no move or release events. This is enough for
415 // opening context menus, but not enough for drag gestures.
416 if ( m_mouseState == Up
417 && ev->button() == Qt::RightButton
418 && ev->buttons() == Qt::LeftButton
419 && ev->modifiers() & Qt::MetaModifier) {
420 // Deliver the event as a transient click with no effect on state
421 mouseClick( ev );
422 return;
423 }
424
425 // Check for inconsistent state. If there was a gesture in progress, abandon it.
426 if ( !( m_mouseButton & ev->buttons() ) && m_mouseState != Up ) {
427 delete m_pClickEvent;
428 m_pClickEvent = nullptr;
430 }
431
432 // Detect start of click or drag event
433 if ( m_mouseState == Up ) {
435 m_mouseButton = ev->button();
436 assert( m_pClickEvent == nullptr );
437#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
438 m_pClickEvent = new QMouseEvent(QEvent::MouseButtonPress,
439 ev->localPos(), ev->windowPos(), ev->screenPos(),
440 m_mouseButton, ev->buttons(), ev->modifiers(),
441 Qt::MouseEventSynthesizedByApplication);
442#else
443 m_pClickEvent = new QMouseEvent(QEvent::MouseButtonPress,
444 ev->localPos(), ev->windowPos(), ev->screenPos(),
445 m_mouseButton, ev->buttons(), ev->modifiers());
446#endif
447 m_pClickEvent->setTimestamp( ev->timestamp() );
448 }
449 }
450
451 void mouseMoveEvent( QMouseEvent *ev ) {
452
453 if ( m_mouseState == Down ) {
454 if ( (ev->pos() - m_pClickEvent->pos() ).manhattanLength() > QApplication::startDragDistance()
455 || (ev->timestamp() - m_pClickEvent->timestamp()) > QApplication::startDragTime() ) {
456 // Mouse has moved far enough to consider this a drag rather than a click.
459 }
460 } else if ( m_mouseState == Dragging ) {
461 mouseDragUpdate( ev );
462 }
463 }
464
465 void mouseReleaseEvent( QMouseEvent *ev ) {
466 if ( m_mouseState != Up && !( ev->buttons() & m_mouseButton) ) {
467 if ( m_mouseState == Down ) {
468 mouseClick( ev );
469 } else if ( m_mouseState == Dragging ) {
470 mouseDragEnd( ev );
471 }
473 delete m_pClickEvent;
474 m_pClickEvent = nullptr;
475
476 } else {
477 // Other mouse buttons may have been pressed since the
478 // last click was started, and may have been released to
479 // cause this event.
480 }
481 }
482
484
485
487 void paintSelection( QPainter *painter ) {
489 QPen pen( H2Core::Preferences::get_instance()->getColorTheme()
490 ->m_selectionHighlightColor );
491 pen.setStyle( Qt::DotLine );
492 pen.setWidth(2);
493 painter->setPen( pen );
494 painter->setBrush( Qt::NoBrush );
495
496 painter->drawRect( m_lasso );
497 }
498 }
499
500
501 // -------------------------------------------------------------------------------------------------------
507
508 void mouseClick( QMouseEvent *ev ) {
509 if ( ev->modifiers() & Qt::ControlModifier ) {
510 // Ctrl+click to add or remove element from selection.
511 QRect r = QRect( ev->pos(), ev->pos() );
512 std::vector<Elem> elems = m_pWidget->elementsIntersecting( r );
513 for ( Elem e : elems) {
514 if ( m_pSelectionGroup->m_selectedElements.find( e )
515 == m_pSelectionGroup->m_selectedElements.end() ) {
516 addToSelection( e );
517 } else {
519 }
520 }
522 } else {
523 if ( ev->button() != Qt::RightButton && !m_pSelectionGroup->m_selectedElements.empty() ) {
524 // Click without control or right button, and
525 // non-empty selection, will just clear selection
528 } else if ( ev->button() == Qt::RightButton && m_pSelectionGroup->m_selectedElements.empty() ) {
529 // Right-clicking with an empty selection will first attempt to select anything at the click
530 // position before passing the click through to the client.
531 QRect r = QRect( ev->pos(), ev->pos() );
532 std::vector<Elem> elems = m_pWidget->elementsIntersecting( r );
533 for ( Elem e : elems) {
534 addToSelection( e );
535 }
536 m_pWidget->mouseClickEvent( ev );
537 } else {
538 m_pWidget->mouseClickEvent( ev );
539 }
540 }
541 }
542
543 void mouseDragStart( QMouseEvent *ev ) {
544
545 if ( m_pDragScroller == nullptr ) {
546 m_pDragScroller = new DragScroller( m_pWidget->findScrollArea() );
547 }
549
550 if ( ev->button() == Qt::LeftButton) {
551 QRect r = QRect( m_pClickEvent->pos(), ev->pos() );
552 std::vector<Elem> elems = m_pWidget->elementsIntersecting( r );
553
554 /* Did the user start dragging a selected element, or an unselected element?
555 */
556 bool bHitSelected = false, bHitAny = false;
557 for ( Elem elem : elems ) {
558 bHitAny = true;
559 if ( isSelected( elem ) ) {
560 bHitSelected = true;
561 break;
562 }
563 }
564
565 if ( bHitSelected ) {
566 /* Move selection */
567 if ( bHitSelected ) {
569 m_movingOffset = ev->pos() - m_pClickEvent->pos();
570 m_pWidget->startMouseMove( ev );
571 }
572 } else if ( bHitAny && m_pWidget->canDragElements() ) {
573 // Allow mouseDragStartEvent to handle anything
574
575 } else {
576 // Didn't hit anything. Start new selection drag.
579 m_lasso.setTopLeft( m_pClickEvent->pos() );
580 m_lasso.setBottomRight( ev->pos() );
581 m_pWidget->startMouseLasso( ev );
582 m_pWidget->updateWidget();
583
584 }
585
586 }
587 m_pWidget->mouseDragStartEvent( ev );
588 }
589
590 void mouseDragUpdate( QMouseEvent *ev ) {
591
593 m_lasso.setBottomRight( ev->pos() );
594
595 if ( ev->modifiers() & Qt::ControlModifier ) {
596 // Start with previously selected elements
598 } else {
599 // Clear and rebuild selection
602 }
603 auto selected = m_pWidget->elementsIntersecting( m_lasso );
604 for ( auto s : selected ) {
606 addToSelection( s );
607 }
608 }
610
611 } else if ( m_selectionState == MouseMoving ) {
612 m_movingOffset = ev->pos() - m_pClickEvent->pos();
613 m_pWidget->selectionMoveUpdateEvent( ev );
615
616 } else {
617 // Pass drag update to widget
618 m_pWidget->mouseDragUpdateEvent( ev );
619 }
620 }
621
622 void mouseDragEnd( QMouseEvent *ev = nullptr ) {
623
625
629 m_pWidget->endMouseGesture();
631
632 } else if ( m_selectionState == MouseMoving ) {
634 m_pWidget->endMouseGesture();
635 m_pWidget->selectionMoveEndEvent( ev );
637
638 } else {
639 // Pass drag end to widget
640 m_pWidget->mouseDragEndEvent( ev );
641 }
642 }
644
645
646
647 // -------------------------------------------------------------------------------------------------------
650
658
659 bool keyPressEvent( QKeyEvent *ev ) {
660
661 if ( ev->matches( QKeySequence::SelectNextChar )
662 || ev->matches( QKeySequence::SelectPreviousChar )
663 || ev->matches( QKeySequence::SelectNextLine )
664 || ev->matches( QKeySequence::SelectPreviousLine )
665 || ev->matches( QKeySequence::SelectStartOfLine)
666 || ev->matches( QKeySequence::SelectEndOfLine )
667 || ev->matches( QKeySequence::SelectStartOfDocument )
668 || ev->matches( QKeySequence::SelectEndOfDocument ) ) {
669 // Selection keys will start or continue a selection lasso
670
672 // Already in keyboard lasso state, just moving the
673 // current selection. Wait for Widget to update
674 // keyboard cursor position before updating lasso
675 // dimensions.
676 } else {
677 // Begin keyboard cursor lasso.
679 m_keyboardCursorStart = m_pWidget->getKeyboardCursorRect();
681 }
682
683 } else if ( ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return ) {
684
685 // Key: Enter/Return: start or end a move or copy
686 if ( m_selectionState == Idle ) {
687
688 bool bHitselected = false;
689 for ( Elem e : m_pWidget->elementsIntersecting( m_pWidget->getKeyboardCursorRect() ) ) {
690 if ( m_pSelectionGroup->m_selectedElements.find( e )
691 != m_pSelectionGroup->m_selectedElements.end() ) {
692 bHitselected = true;
693 break;
694 }
695 }
696 if ( bHitselected ) {
697 // Hit "Enter" over a selected element. Begin move.
698 m_keyboardCursorStart = m_pWidget->getKeyboardCursorRect();
701 return true;
702 }
703
704 } else if ( m_selectionState == KeyboardLasso ) {
705 // If we hit 'Enter' from lasso mode, go directly to move
706 m_keyboardCursorStart = m_pWidget->getKeyboardCursorRect();
709 return true;
710
711 } else if ( m_selectionState == KeyboardMoving ) {
712 // End keyboard move
714 m_pWidget->selectionMoveEndEvent( ev );
715 return true;
716
717 } else if ( m_selectionState == KeyboardLasso ) {
718 // end keyboard lasso
720 return true;
721 }
722
723 } else if ( ev->key() == Qt::Key_Escape ) {
724
725 // Key: Escape: cancel any lasso or move/copy in progress; or clear any selection.
726 if ( m_selectionState == Idle ) {
727 if ( !m_pSelectionGroup->m_selectedElements.empty() ) {
730 return true;
731 }
732 } else {
734 m_pWidget->endMouseGesture();
735 m_pWidget->selectionMoveCancelEvent();
736 }
739 return true;
740 }
741
742 } else {
743 // Other keys should probably also cancel lasso, but not move?
747 }
748 }
749 return false;
750 }
751
752
756 void updateKeyboardCursorPosition( QRect cursor ) {
758 m_lasso = m_keyboardCursorStart.united( m_pWidget->getKeyboardCursorRect() );
759
760 // Clear and rebuild selection
762 auto selected = m_pWidget->elementsIntersecting( m_lasso );
763 for ( auto s : selected ) {
764 m_pSelectionGroup->m_selectedElements.insert( s );
765 }
766
767 } else if ( m_selectionState == KeyboardMoving ) {
768 QRect cursorPosition = m_pWidget->getKeyboardCursorRect();
769 m_movingOffset = cursorPosition.topLeft() - m_keyboardCursorStart.topLeft();
770
771 }
772 }
773
775
776};
777
778#endif // SELECTION_H
Drag scroller object.
Definition Selection.h:127
const int m_nInterval
Definition Selection.h:131
QTimer * m_pTimer
Definition Selection.h:129
QScrollArea * m_pScrollArea
Definition Selection.h:130
void startDrag()
Definition Selection.h:145
DragScroller(QScrollArea *pScrollArea)
Definition Selection.h:134
void timeout(void)
Definition Selection.h:159
void endDrag()
Definition Selection.h:154
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
SelectionWidget defines the interface used by the Selection manager to communicate with a widget impl...
Definition Selection.h:47
virtual void startMouseLasso(QMouseEvent *ev)
Definition Selection.h:97
virtual void mouseDragUpdateEvent(QMouseEvent *ev)=0
virtual void mouseDragEndEvent(QMouseEvent *ev)=0
Elem SelectionIndex
Definition Selection.h:50
virtual void updateWidget()=0
Selection or selection-related visual elements have changed, widget needs to be updated.
virtual void endMouseGesture()
Definition Selection.h:99
virtual void mouseDragStartEvent(QMouseEvent *ev)=0
virtual bool canDragElements()
Can elements be dragged as well as being selected? This may change to suit widget's current state.
Definition Selection.h:57
virtual std::vector< SelectionIndex > elementsIntersecting(QRect r)=0
Find list of elements which intersect a rectangular area.
virtual QRect getKeyboardCursorRect()=0
Calculate screen space occupied by keyboard cursor.
virtual QScrollArea * findScrollArea()
Find the QScrollArea, if any, which contains the widget.
Definition Selection.h:104
virtual void startMouseMove(QMouseEvent *ev)
Definition Selection.h:98
virtual void selectionMoveUpdateEvent(QMouseEvent *ev)
Definition Selection.h:89
virtual void validateSelection()=0
Ensure that the Selection contains only valid elements.
virtual void selectionMoveEndEvent(QInputEvent *ev)=0
virtual bool checkDeselectElements(std::vector< SelectionIndex > &elements)
Inform the client that we're deselecting elements.
Definition Selection.h:70
virtual void selectionMoveCancelEvent()
Definition Selection.h:91
virtual void mouseClickEvent(QMouseEvent *ev)=0
Selection management for editor widgets.
Definition Selection.h:192
void paintSelection(QPainter *painter)
Paint selection-related elements (ie lasso)
Definition Selection.h:487
bool isLasso() const
Is there an ongoing lasso gesture?
Definition Selection.h:330
void addToSelection(Elem e)
Definition Selection.h:346
void mouseReleaseEvent(QMouseEvent *ev)
Definition Selection.h:465
void mouseClick(QMouseEvent *ev)
Definition Selection.h:508
QMouseEvent * m_pClickEvent
Mouse event to deliver as 'click' or 'drag' events.
Definition Selection.h:228
Qt::MouseButton m_mouseButton
Mouse button that began the gesture.
Definition Selection.h:227
std::set< Elem >::iterator iterator
Definition Selection.h:368
void removeFromSelection(Elem e, bool bCheck=true)
Definition Selection.h:339
void updateWidgetGroup()
Update any widgets in this selection group.
Definition Selection.h:375
QPoint m_movingOffset
Offset that a selection has been moved by.
Definition Selection.h:256
void mouseDragUpdate(QMouseEvent *ev)
Definition Selection.h:590
enum Selection::MouseState m_mouseState
enum Selection::SelectionState m_selectionState
void clearSelection(bool bCheck=true)
Definition Selection.h:350
void merge(Selection *pOther)
Merge the selection groups of two Selection widgets.
Definition Selection.h:286
DragScroller * m_pDragScroller
Scroller to use while dragging selections.
Definition Selection.h:265
void cancelGesture()
Cancel any selection gesture (lasso, move, with keyboard or mouse) in progress.
Definition Selection.h:382
QRect m_keyboardCursorStart
Keyboard cursor position at the start of a keyboard gesture.
Definition Selection.h:257
bool keyPressEvent(QKeyEvent *ev)
Key press event filter.
Definition Selection.h:659
bool isMouseGesture() const
Is there a mouse gesture in progress?
Definition Selection.h:319
Selection(SelectionWidget< Elem > *w)
Definition Selection.h:270
void updateKeyboardCursorPosition(QRect cursor)
Update the keyboard cursor.
Definition Selection.h:756
bool isSelected(Elem e) const
Is an element in the set of currently selected elements?
Definition Selection.h:335
std::shared_ptr< SelectionGroup > m_pSelectionGroup
Definition Selection.h:201
void mousePressEvent(QMouseEvent *ev)
Definition Selection.h:410
void mouseDragEnd(QMouseEvent *ev=nullptr)
Definition Selection.h:622
@ KeyboardMoving
Definition Selection.h:253
@ KeyboardLasso
Definition Selection.h:253
bool isMoving() const
Is there an ongoing (and incomplete) selection movement gesture?
Definition Selection.h:314
QPoint movingOffset() const
During a selection "move" gesture, return the current movement position relative to the start positio...
Definition Selection.h:325
iterator end()
Definition Selection.h:371
void dump()
Definition Selection.h:295
iterator begin()
Definition Selection.h:370
void mouseDragStart(QMouseEvent *ev)
Definition Selection.h:543
std::set< Elem > m_checkpointSelectedElements
For gestures modifying a selection, store the initial selection set as a checkpoint to restore when r...
Definition Selection.h:262
SelectionWidget< Elem > * m_pWidget
Definition Selection.h:206
QRect m_lasso
Dimensions of a current selection lasso.
Definition Selection.h:255
void mouseMoveEvent(QMouseEvent *ev)
Definition Selection.h:451
Group of SelectionWidget objects sharing the same selection set.
Definition Selection.h:196
std::set< Elem > m_selectedElements
Definition Selection.h:197
std::set< SelectionWidget< Elem > * > m_selectionWidgets
Definition Selection.h:198