From d06be0b483aab169903c230070163e3312682e93 Mon Sep 17 00:00:00 2001 From: Spekular Date: Tue, 26 Apr 2022 17:54:22 +0200 Subject: [PATCH 01/18] MVP --- data/themes/classic/cursor_select_left.png | Bin 0 -> 206 bytes data/themes/classic/cursor_select_right.png | Bin 0 -> 193 bytes data/themes/classic/style.css | 2 + data/themes/default/cursor_select_left.png | Bin 0 -> 206 bytes data/themes/default/cursor_select_right.png | Bin 0 -> 193 bytes data/themes/default/style.css | 2 + include/SetupDialog.h | 3 + include/TimeLineWidget.h | 18 +++ src/gui/TimeLineWidget.cpp | 153 ++++++++++++++++---- src/gui/modals/SetupDialog.cpp | 38 ++++- 10 files changed, 184 insertions(+), 32 deletions(-) create mode 100644 data/themes/classic/cursor_select_left.png create mode 100644 data/themes/classic/cursor_select_right.png create mode 100644 data/themes/default/cursor_select_left.png create mode 100644 data/themes/default/cursor_select_right.png diff --git a/data/themes/classic/cursor_select_left.png b/data/themes/classic/cursor_select_left.png new file mode 100644 index 0000000000000000000000000000000000000000..eaa80e0bbe3ac8e5ded5e721e82a9dd1a8b8b338 GIT binary patch literal 206 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!V6vx+V@QPi+v$#62NZZ%%>VwMe>H+X%*!@+ z?->UH#}g^Gswz{%;-5%M$1W(mFQ!zLzxsgbxnvt53E}mB<~f+nIeqG*z5@dzivXOs p$5P}y^R$!lvI6;>1s;*b3=DjSL74G){)!Z!V3?!lvI6;>1s;*b3=DjSL74G){)!Z!V6vx+V@QPi+v$#62NZZ%%>VwMe>H+X%*!@+ z?->UH#}g^Gswz{%;-5%M$1W(mFQ!zLzxsgbxnvt53E}mB<~f+nIeqG*z5@dzivXOs p$5P}y^R$!lvI6;>1s;*b3=DjSL74G){)!Z!V3?start( 1000 / 60 ); // 60 fps connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int,int ) ), this, SLOT( update() ) ); + + m_cursorSelectLeft = QCursor(embed::getIconPixmap("cursor_select_left"), + m_mouseHotspotSelLeft.width(), m_mouseHotspotSelLeft.height()); + m_cursorSelectRight = QCursor(embed::getIconPixmap("cursor_select_right"), + m_mouseHotspotSelRight.width(), m_mouseHotspotSelRight.height()); } @@ -116,6 +126,33 @@ void TimeLineWidget::setXOffset(const int x) +TimePos TimeLineWidget::getClickedTime(const QMouseEvent *event) +{ + return getClickedTime(event->x()); +} + + +TimePos TimeLineWidget::getClickedTime(const int xPosition) +{ + // How far into the timeline we clicked, measuring pixels from the leftmost part of the editor + const int pixelDelta = qMax(xPosition - m_xOffset - m_moveXOff, 0); + return m_begin + static_cast(pixelDelta * TimePos::ticksPerBar() / m_ppb); +} + + + + +TimePos TimeLineWidget::getEnd() +{ + // widget width - track label area - margins - 1 + auto contentWidth = width() - m_xOffset - 9; + auto ticksPerPixel = TimePos::ticksPerBar() / m_ppb; + return m_begin + (contentWidth * ticksPerPixel); +} + + + + void TimeLineWidget::addToolButtons( QToolBar * _tool_bar ) { NStateButton * autoScroll = new NStateButton( _tool_bar ); @@ -252,6 +289,14 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) QRect outerRectangle( loopStart, loopRectMargin, loopRectWidth - 1, loopRectHeight - 1 ); p.fillRect( outerRectangle, loopPointsActive ? getActiveLoopBrush() : getInactiveLoopBrush()); + QRect leftHandle(loopStart, loopRectMargin, 5, loopRectHeight - 1); + QRect rightHandle(loopEndR - 5, loopRectMargin, 5, loopRectHeight - 1); + if (ConfigManager::inst()->value( "app", "loopmarkermode" ) == "Handles") + { + p.fillRect(leftHandle, Qt::magenta); + p.fillRect(rightHandle, Qt::magenta); + } + // Draw the bar lines and numbers // Activate hinting on the font QFont font = p.font(); @@ -284,6 +329,7 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) } } + // // Draw the main rectangle (outer border) p.setPen( loopPointsActive ? getActiveLoopColor() : getInactiveLoopColor() ); p.setBrush( Qt::NoBrush ); @@ -294,6 +340,7 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) p.setPen( loopPointsActive ? getActiveLoopInnerColor() : getInactiveLoopInnerColor() ); p.setBrush( Qt::NoBrush ); p.drawRect( innerRectangle ); + // // Only draw the position marker if the position line is in view if (m_posMarkerX >= m_xOffset && m_posMarkerX < width() - s_posMarkerPixmap->width() / 2) @@ -308,16 +355,77 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) -void TimeLineWidget::mousePressEvent( QMouseEvent* event ) +TimeLineWidget::actions TimeLineWidget::getLoopAction(QMouseEvent* event) { - if( event->x() < m_xOffset ) + if (!(event->modifiers() & Qt::ShiftModifier)){ return NoAction; } + + const TimePos t = getClickedTime(event); + const QString loopMode = ConfigManager::inst()->value( "app", "loopmarkermode" ); + + if (loopMode == "Handles") { - return; + const int leftMost = std::max(markerX(loopBegin()), m_xOffset) + 8; + const int deltaLeft = event->x() - leftMost; + const int rightMost = std::min(markerX(loopEnd()) + 9, width()); + const int deltaRight = rightMost - event->x(); + + if (deltaLeft < 0 || deltaRight < 0) { return NoAction; } + else if (deltaLeft <= 5 && deltaLeft < deltaRight) { return MoveLoopBegin; } + else if (deltaRight <= 5) { return MoveLoopEnd; } + else { return NoAction; } // TODO: Loop slide } - if( event->button() == Qt::LeftButton && !(event->modifiers() & Qt::ShiftModifier) ) + else /**if (loopMode == "Grab closest")**/ + { + const TimePos loopMid = (m_loopPos[0] + m_loopPos[1])/2; + return t < loopMid ? MoveLoopBegin : MoveLoopEnd; + } + // TODO: shortcut mode +} + + + +void TimeLineWidget::updateCursor(actions action) +{ + if (action == NoAction){ setCursor(Qt::ArrowCursor); } + else if (action == MoveLoopBegin){ setCursor(m_cursorSelectLeft); } + else if (action == MoveLoopEnd){ setCursor(m_cursorSelectRight); } + // TODO: loop slide +} + + + + +void TimeLineWidget::mousePressEvent(QMouseEvent* event) +{ + // TODO: properly fix cursor hotspot, this doesn't seem to help + m_cursorSelectLeft = QCursor(embed::getIconPixmap("cursor_select_left"), + m_mouseHotspotSelLeft.width(), m_mouseHotspotSelLeft.height()); + m_cursorSelectRight = QCursor(embed::getIconPixmap("cursor_select_right"), + m_mouseHotspotSelRight.width(), m_mouseHotspotSelRight.height()); + + + if (event->x() < m_xOffset) { return; } + + const bool shift = event->modifiers() & Qt::ShiftModifier; + const bool ctrl = event->modifiers() & Qt::ControlModifier; + + if (shift) // loop marker manipulation + { + m_action = getLoopAction(event); + updateCursor(m_action); + + m_loopPos[(m_action == MoveLoopBegin) ? 0 : 1] = getClickedTime(event); + std::sort(std::begin(m_loopPos), std::end(m_loopPos)); + } + else if (event->button() == Qt::LeftButton && ctrl) // selection + { + m_action = SelectSongClip; + m_initalXSelect = event->x(); + } + else if (event->button() == Qt::LeftButton && !ctrl) // move playhead { m_action = MovePositionMarker; - if( event->x() - m_xOffset < s_posMarkerPixmap->width() ) + if (event->x() - m_xOffset < s_posMarkerPixmap->width()) { m_moveXOff = event->x() - m_xOffset; } @@ -326,30 +434,16 @@ void TimeLineWidget::mousePressEvent( QMouseEvent* event ) m_moveXOff = s_posMarkerPixmap->width() / 2; } } - else if( event->button() == Qt::LeftButton && (event->modifiers() & Qt::ShiftModifier) ) - { - m_action = SelectSongClip; - m_initalXSelect = event->x(); - } - else if( event->button() == Qt::RightButton ) - { - m_moveXOff = s_posMarkerPixmap->width() / 2; - const TimePos t = m_begin + static_cast( qMax( event->x() - m_xOffset - m_moveXOff, 0 ) * TimePos::ticksPerBar() / m_ppb ); - const TimePos loopMid = ( m_loopPos[0] + m_loopPos[1] ) / 2; + else if (event->button() == Qt::RightButton){} // TODO: right click menu - m_action = t < loopMid ? MoveLoopBegin : MoveLoopEnd; - std::sort(std::begin(m_loopPos), std::end(m_loopPos)); - m_loopPos[( m_action == MoveLoopBegin ) ? 0 : 1] = t; - } - - if( m_action == MoveLoopBegin || m_action == MoveLoopEnd ) + if (m_action == MoveLoopBegin || m_action == MoveLoopEnd) { delete m_hint; m_hint = TextFloat::displayMessage( tr( "Hint" ), - tr( "Press <%1> to disable magnetic loop points." ).arg(UI_CTRL_KEY), - embed::getIconPixmap( "hint" ), 0 ); + tr( "Press <%1> to disable magnetic loop points." ).arg(UI_CTRL_KEY), + embed::getIconPixmap( "hint" ), 0 ); } - mouseMoveEvent( event ); + mouseMoveEvent(event); } @@ -358,7 +452,7 @@ void TimeLineWidget::mousePressEvent( QMouseEvent* event ) void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) { parentWidget()->update(); // essential for widgets that this timeline had taken their mouse move event from. - const TimePos t = m_begin + static_cast( qMax( event->x() - m_xOffset - m_moveXOff, 0 ) * TimePos::ticksPerBar() / m_ppb ); + const TimePos t = getClickedTime(event); switch( m_action ) { @@ -375,11 +469,10 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) updatePosition(); positionMarkerMoved(); break; - case MoveLoopBegin: case MoveLoopEnd: { - const int i = m_action - MoveLoopBegin; // i == 0 || i == 1 + const int i = m_action == MoveLoopBegin ? 0 : 1; const bool control = event->modifiers() & Qt::ControlModifier; if (control) { @@ -404,11 +497,12 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) update(); break; } - case SelectSongClip: + case SelectSongClip: emit regionSelectedFromPixels( m_initalXSelect , event->x() ); - break; + break; default: + updateCursor(getLoopAction(event)); break; } } @@ -422,4 +516,5 @@ void TimeLineWidget::mouseReleaseEvent( QMouseEvent* event ) m_hint = nullptr; if ( m_action == SelectSongClip ) { emit selectionFinished(); } m_action = NoAction; + std::sort(std::begin(m_loopPos), std::end(m_loopPos)); } diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 035912a4789..9ecab365741 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -114,6 +114,8 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : "app", "disablebackup").toInt()), m_openLastProject(ConfigManager::inst()->value( "app", "openlastproject").toInt()), + m_loopMarkerMode(ConfigManager::inst()->value( + "app", "loopmarkermode")), m_lang(ConfigManager::inst()->value( "app", "language")), m_saveInterval( ConfigManager::inst()->value( @@ -158,7 +160,7 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : // TODO: Equivalent to the new setWindowFlag(Qt::WindowContextHelpButtonHint, false) setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setModal(true); - setFixedSize(454, 400); + setFixedSize(454, 440); Engine::projectJournal()->setJournalling(false); @@ -188,7 +190,7 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : // Settings widget. QWidget * settings_w = new QWidget(main_w); - settings_w->setFixedSize(360, 360); + settings_w->setFixedSize(360, 400); // General widget. QWidget * general_w = new QWidget(settings_w); @@ -246,7 +248,30 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : addLedCheckBox(tr("Show warning when deleting tracks"), gui_tw, counter, m_trackDeletionWarning, SLOT(toggleTrackDeletionWarning(bool)), false); - gui_tw->setFixedHeight(YDelta + YDelta * counter); + QComboBox * changeLoop = new QComboBox(gui_tw); + changeLoop->move(XDelta, YDelta * counter + 20); + + m_loopMarkerModes.append(QString("Grab closest")); + m_loopMarkerModes.append(QString("Handles")); + + for (QString mode : m_loopMarkerModes) + { + changeLoop->addItem(mode); + } + + for(int i = 0; i < changeLoop->count(); ++i) + { + if(m_loopMarkerMode == m_loopMarkerModes.at(i)) + { + changeLoop->setCurrentIndex(i); + break; + } + } + + connect(changeLoop, SIGNAL(currentIndexChanged(int)), + this, SLOT(setLoopMarkerMode(int))); + + gui_tw->setFixedHeight(2 * YDelta + 20 + YDelta * counter); counter = 0; @@ -929,6 +954,7 @@ void SetupDialog::accept() QString::number(!m_disableBackup)); ConfigManager::inst()->setValue("app", "openlastproject", QString::number(m_openLastProject)); + ConfigManager::inst()->setValue("app", "loopmarkermode", m_loopMarkerMode); ConfigManager::inst()->setValue("app", "language", m_lang); ConfigManager::inst()->setValue("ui", "saveinterval", QString::number(m_saveInterval)); @@ -1065,6 +1091,12 @@ void SetupDialog::toggleOpenLastProject(bool enabled) } +void SetupDialog::setLoopMarkerMode(int lang) +{ + m_loopMarkerMode = m_loopMarkerModes[lang]; +} + + void SetupDialog::setLanguage(int lang) { m_lang = m_languages[lang]; From 7dc9ac0774f6e6b272a875a09d22d74313d07b38 Mon Sep 17 00:00:00 2001 From: Spekular Date: Tue, 26 Apr 2022 22:50:49 +0200 Subject: [PATCH 02/18] Simplify position calculations, remove unnecessary members --- include/TimeLineWidget.h | 4 +- src/gui/TimeLineWidget.cpp | 80 ++++++++++++---------------------- src/gui/editors/SongEditor.cpp | 3 +- 3 files changed, 31 insertions(+), 56 deletions(-) diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index 2fc52a80575..d9ee3cb3bd6 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -232,8 +232,8 @@ public slots: bool m_changedPosition; + // Width of the unused region on the widget's left (above track labels or piano) int m_xOffset; - int m_posMarkerX; float m_ppb; float m_snapSize; Song::PlayPos & m_pos; @@ -261,8 +261,6 @@ public slots: TimeLineWidget::actions getLoopAction(QMouseEvent* event); void updateCursor(actions action); - int m_moveXOff; - signals: void positionChanged( const TimePos & _t ); diff --git a/src/gui/TimeLineWidget.cpp b/src/gui/TimeLineWidget.cpp index b484f9e29c1..0b128b9d816 100644 --- a/src/gui/TimeLineWidget.cpp +++ b/src/gui/TimeLineWidget.cpp @@ -62,7 +62,6 @@ TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb, m_behaviourAtStop( BackToZero ), m_changedPosition( true ), m_xOffset( xoff ), - m_posMarkerX( 0 ), m_ppb( ppb ), m_snapSize( 1.0 ), m_pos( pos ), @@ -70,8 +69,7 @@ TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb, m_mode( mode ), m_savedPos( -1 ), m_hint( nullptr ), - m_action( NoAction ), - m_moveXOff( 0 ) + m_action( NoAction ) { m_loopPos[0] = 0; m_loopPos[1] = DefaultTicksPerBar; @@ -85,8 +83,6 @@ TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb, setAttribute( Qt::WA_OpaquePaintEvent, true ); move( 0, yoff ); - m_xOffset -= s_posMarkerPixmap->width() / 2; - setMouseTracking(true); m_pos.m_timeLine = this; @@ -120,7 +116,6 @@ TimeLineWidget::~TimeLineWidget() void TimeLineWidget::setXOffset(const int x) { m_xOffset = x; - if (s_posMarkerPixmap != nullptr) { m_xOffset -= s_posMarkerPixmap->width() / 2; } } @@ -135,7 +130,7 @@ TimePos TimeLineWidget::getClickedTime(const QMouseEvent *event) TimePos TimeLineWidget::getClickedTime(const int xPosition) { // How far into the timeline we clicked, measuring pixels from the leftmost part of the editor - const int pixelDelta = qMax(xPosition - m_xOffset - m_moveXOff, 0); + const int pixelDelta = qMax(xPosition - m_xOffset, 0); return m_begin + static_cast(pixelDelta * TimePos::ticksPerBar() / m_ppb); } @@ -144,8 +139,7 @@ TimePos TimeLineWidget::getClickedTime(const int xPosition) TimePos TimeLineWidget::getEnd() { - // widget width - track label area - margins - 1 - auto contentWidth = width() - m_xOffset - 9; + auto contentWidth = width() - m_xOffset; auto ticksPerPixel = TimePos::ticksPerBar() / m_ppb; return m_begin + (contentWidth * ticksPerPixel); } @@ -224,17 +218,11 @@ void TimeLineWidget::loadSettings( const QDomElement & _this ) -void TimeLineWidget::updatePosition( const TimePos & ) +void TimeLineWidget::updatePosition( const TimePos & newPos ) { - const int new_x = markerX( m_pos ); - - if( new_x != m_posMarkerX ) - { - m_posMarkerX = new_x; - m_changedPosition = true; - emit positionChanged( m_pos ); - update(); - } + m_changedPosition = true; + emit positionChanged( m_pos ); + update(); } @@ -273,30 +261,21 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) p.fillRect( 0, 0, width(), height(), p.background() ); // Clip so that we only draw everything starting from the offset - const int leftMargin = m_xOffset + s_posMarkerPixmap->width() / 2; - p.setClipRect(leftMargin, 0, width() - leftMargin, height() ); + p.setClipRect(m_xOffset, 0, width() - m_xOffset, height() ); - // Draw the loop rectangle + // Variables for the loop rectangle int const & loopRectMargin = getLoopRectangleVerticalPadding(); int const loopRectHeight = this->height() - 2 * loopRectMargin; - int const loopStart = markerX( loopBegin() ) + 8; - int const loopEndR = markerX( loopEnd() ) + 9; + int const loopStart = markerX(loopBegin()); + int const loopEndR = markerX(loopEnd()); int const loopRectWidth = loopEndR - loopStart; bool const loopPointsActive = loopPointsEnabled(); - // Draw the main rectangle (inner fill only) + // Draw the main loop rectangle (inner fill only) QRect outerRectangle( loopStart, loopRectMargin, loopRectWidth - 1, loopRectHeight - 1 ); p.fillRect( outerRectangle, loopPointsActive ? getActiveLoopBrush() : getInactiveLoopBrush()); - QRect leftHandle(loopStart, loopRectMargin, 5, loopRectHeight - 1); - QRect rightHandle(loopEndR - 5, loopRectMargin, 5, loopRectHeight - 1); - if (ConfigManager::inst()->value( "app", "loopmarkermode" ) == "Handles") - { - p.fillRect(leftHandle, Qt::magenta); - p.fillRect(rightHandle, Qt::magenta); - } - // Draw the bar lines and numbers // Activate hinting on the font QFont font = p.font(); @@ -309,7 +288,7 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) QColor const & barNumberColor = getBarNumberColor(); bar_t barNumber = m_begin.getBar(); - int const x = m_xOffset + s_posMarkerPixmap->width() / 2 - + int const x = m_xOffset - ( ( static_cast( m_begin * m_ppb ) / TimePos::ticksPerBar() ) % static_cast( m_ppb ) ); for( int i = 0; x + i * m_ppb < width(); ++i ) @@ -329,26 +308,33 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) } } - // - // Draw the main rectangle (outer border) + // Draw the loop rectangle's outer outline p.setPen( loopPointsActive ? getActiveLoopColor() : getInactiveLoopColor() ); p.setBrush( Qt::NoBrush ); p.drawRect( outerRectangle ); - // Draw the inner border outline (no fill) + // Draw the loop rectangle's inner outline QRect innerRectangle = outerRectangle.adjusted( 1, 1, -1, -1 ); p.setPen( loopPointsActive ? getActiveLoopInnerColor() : getInactiveLoopInnerColor() ); p.setBrush( Qt::NoBrush ); p.drawRect( innerRectangle ); - // + + // Draw loop handles if necessary + QRect leftHandle(loopStart, loopRectMargin, 5, loopRectHeight - 1); + QRect rightHandle(loopEndR - 5, loopRectMargin, 5, loopRectHeight - 1); + if (ConfigManager::inst()->value( "app", "loopmarkermode" ) == "Handles") + { + p.fillRect(leftHandle, Qt::magenta); + p.fillRect(rightHandle, Qt::magenta); + } // Only draw the position marker if the position line is in view - if (m_posMarkerX >= m_xOffset && m_posMarkerX < width() - s_posMarkerPixmap->width() / 2) + if (markerX(m_pos) >= m_xOffset && markerX(m_pos) < width() - s_posMarkerPixmap->width() / 2) { // Let the position marker extrude to the left p.setClipping(false); p.setOpacity(0.6); - p.drawPixmap(m_posMarkerX, height() - s_posMarkerPixmap->height(), *s_posMarkerPixmap); + p.drawPixmap(markerX(m_pos) - (s_posMarkerPixmap->width() / 2), height() - s_posMarkerPixmap->height(), *s_posMarkerPixmap); } } @@ -364,12 +350,12 @@ TimeLineWidget::actions TimeLineWidget::getLoopAction(QMouseEvent* event) if (loopMode == "Handles") { - const int leftMost = std::max(markerX(loopBegin()), m_xOffset) + 8; + const int leftMost = std::max(markerX(loopBegin()), m_xOffset); const int deltaLeft = event->x() - leftMost; - const int rightMost = std::min(markerX(loopEnd()) + 9, width()); + const int rightMost = std::min(markerX(loopEnd()), width()); const int deltaRight = rightMost - event->x(); - if (deltaLeft < 0 || deltaRight < 0) { return NoAction; } + if (deltaLeft < 0 || deltaRight < 0) { return NoAction; } // Clicked outside loop else if (deltaLeft <= 5 && deltaLeft < deltaRight) { return MoveLoopBegin; } else if (deltaRight <= 5) { return MoveLoopEnd; } else { return NoAction; } // TODO: Loop slide @@ -425,14 +411,6 @@ void TimeLineWidget::mousePressEvent(QMouseEvent* event) else if (event->button() == Qt::LeftButton && !ctrl) // move playhead { m_action = MovePositionMarker; - if (event->x() - m_xOffset < s_posMarkerPixmap->width()) - { - m_moveXOff = event->x() - m_xOffset; - } - else - { - m_moveXOff = s_posMarkerPixmap->width() / 2; - } } else if (event->button() == Qt::RightButton){} // TODO: right click menu diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index c70705aaf75..280a3925c37 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -812,8 +812,7 @@ void SongEditor::updatePosition( const TimePos & t ) m_scrollBack = false; } - const int x = m_song->m_playPos[Song::Mode_PlaySong].m_timeLine-> - markerX( t ) + 8; + const int x = m_song->m_playPos[Song::Mode_PlaySong].m_timeLine->markerX( t ); if( x >= trackOpWidth + widgetWidth -1 ) { m_positionLine->show(); From 76bedae63cc484eef7ea93a41227aca4b3e21bc4 Mon Sep 17 00:00:00 2001 From: Spekular Date: Mon, 16 May 2022 19:13:10 +0200 Subject: [PATCH 03/18] Add dual-button mode --- src/gui/TimeLineWidget.cpp | 13 ++++++++++--- src/gui/modals/SetupDialog.cpp | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/gui/TimeLineWidget.cpp b/src/gui/TimeLineWidget.cpp index 0b128b9d816..23ddbb39486 100644 --- a/src/gui/TimeLineWidget.cpp +++ b/src/gui/TimeLineWidget.cpp @@ -348,7 +348,7 @@ TimeLineWidget::actions TimeLineWidget::getLoopAction(QMouseEvent* event) const TimePos t = getClickedTime(event); const QString loopMode = ConfigManager::inst()->value( "app", "loopmarkermode" ); - if (loopMode == "Handles") + if (loopMode == "Handles" && event->button() == Qt::LeftButton) { const int leftMost = std::max(markerX(loopBegin()), m_xOffset); const int deltaLeft = event->x() - leftMost; @@ -360,12 +360,19 @@ TimeLineWidget::actions TimeLineWidget::getLoopAction(QMouseEvent* event) else if (deltaRight <= 5) { return MoveLoopEnd; } else { return NoAction; } // TODO: Loop slide } - else /**if (loopMode == "Grab closest")**/ + else if (loopMode == "Grab closest" && event->button() == Qt::LeftButton) { const TimePos loopMid = (m_loopPos[0] + m_loopPos[1])/2; return t < loopMid ? MoveLoopBegin : MoveLoopEnd; } - // TODO: shortcut mode + else if (loopMode == "Dual-button") + { + if (event->button() == Qt::LeftButton){ return MoveLoopBegin; } + else if (event->button() == Qt::RightButton){ return MoveLoopEnd; } + } + + //Fallback + return NoAction; } diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 9ecab365741..911e9793469 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -253,6 +253,7 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : m_loopMarkerModes.append(QString("Grab closest")); m_loopMarkerModes.append(QString("Handles")); + m_loopMarkerModes.append(QString("Dual-button")); for (QString mode : m_loopMarkerModes) { From d4dcdd3cb1db4d4618c502b8b19ef72f82f3efa9 Mon Sep 17 00:00:00 2001 From: Spekular Date: Mon, 16 May 2022 19:49:57 +0200 Subject: [PATCH 04/18] Cursor update refactor and hotspot bugfix --- data/themes/classic/style.css | 5 +++-- data/themes/default/style.css | 6 ++++-- include/TimeLineWidget.h | 2 +- src/gui/TimeLineWidget.cpp | 15 ++++++++------- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index 07c792c7e84..0499a5bf75e 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -652,6 +652,9 @@ TimeLineWidget { qproperty-activeLoopInnerColor: rgba( 74, 155, 100, 255 ); qproperty-barLineColor: rgb( 192, 192, 192 ); qproperty-barNumberColor: rgb( 192, 192, 192 ); + /* Cursor hotspots for loop marker adjustment */ + qproperty-mouseHotspotSelLeft: 0px 0px; + qproperty-mouseHotspotSelRight: 32px 0px; } QTreeView { @@ -678,8 +681,6 @@ ClipView { /* finger tip offset of cursor */ qproperty-mouseHotspotHand: 3px 3px; qproperty-mouseHotspotKnife: 0px 0px; - qproperty-mouseHotspotSelLeft: 0px 0px; - qproperty-mouseHotspotSelRight: 32px 0px; font-size: 11px; } diff --git a/data/themes/default/style.css b/data/themes/default/style.css index de63f8007d2..cfb047588ce 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -698,6 +698,10 @@ TimeLineWidget { qproperty-barLineColor: rgb( 192, 192, 192 ); qproperty-barNumberColor: rgb( 192, 192, 192 ); + + /* Cursor hotspots for loop marker adjustment */ + qproperty-mouseHotspotSelLeft: 0px 0px; + qproperty-mouseHotspotSelRight: 32px 0px; } QTreeView { @@ -724,8 +728,6 @@ ClipView { /* finger tip offset of cursor */ qproperty-mouseHotspotHand: 7px 2px; qproperty-mouseHotspotKnife: 0px 0px; - qproperty-mouseHotspotSelLeft: 0px 0px; - qproperty-mouseHotspotSelRight: 32px 0px; font-size: 11px; } diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index d9ee3cb3bd6..491d57b804f 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -259,7 +259,7 @@ public slots: } m_action; TimeLineWidget::actions getLoopAction(QMouseEvent* event); - void updateCursor(actions action); + QCursor actionCursor(actions action); signals: diff --git a/src/gui/TimeLineWidget.cpp b/src/gui/TimeLineWidget.cpp index 23ddbb39486..2182b9f2aa3 100644 --- a/src/gui/TimeLineWidget.cpp +++ b/src/gui/TimeLineWidget.cpp @@ -377,12 +377,13 @@ TimeLineWidget::actions TimeLineWidget::getLoopAction(QMouseEvent* event) -void TimeLineWidget::updateCursor(actions action) +QCursor TimeLineWidget::actionCursor(actions action) { - if (action == NoAction){ setCursor(Qt::ArrowCursor); } - else if (action == MoveLoopBegin){ setCursor(m_cursorSelectLeft); } - else if (action == MoveLoopEnd){ setCursor(m_cursorSelectRight); } + if (action == MoveLoopBegin){ return m_cursorSelectLeft; } + else if (action == MoveLoopEnd){ return m_cursorSelectRight; } // TODO: loop slide + // Fall back to normal cursor if no action or action cursor not specified + return Qt::ArrowCursor; } @@ -405,7 +406,7 @@ void TimeLineWidget::mousePressEvent(QMouseEvent* event) if (shift) // loop marker manipulation { m_action = getLoopAction(event); - updateCursor(m_action); + setCursor(actionCursor(m_action)); m_loopPos[(m_action == MoveLoopBegin) ? 0 : 1] = getClickedTime(event); std::sort(std::begin(m_loopPos), std::end(m_loopPos)); @@ -487,7 +488,7 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) break; default: - updateCursor(getLoopAction(event)); + setCursor(actionCursor(getLoopAction(event))); break; } } @@ -502,4 +503,4 @@ void TimeLineWidget::mouseReleaseEvent( QMouseEvent* event ) if ( m_action == SelectSongClip ) { emit selectionFinished(); } m_action = NoAction; std::sort(std::begin(m_loopPos), std::end(m_loopPos)); -} +} \ No newline at end of file From 6e7fb1cdad1720c83a95238981fb83e34eca3077 Mon Sep 17 00:00:00 2001 From: Spekular Date: Mon, 16 May 2022 20:31:15 +0200 Subject: [PATCH 05/18] Add loop drag mode --- include/TimeLineWidget.h | 5 +++++ src/gui/TimeLineWidget.cpp | 39 ++++++++++++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index 491d57b804f..d1a2b9b9561 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -241,6 +241,10 @@ public slots: const TimePos & m_begin; const Song::PlayModes m_mode; TimePos m_loopPos[2]; + // When in MoveLoop mode we need the initial positions. Storing only the latest + // position allows for unquantized drag but fails when toggling quantization. + TimePos m_oldLoopPos[2]; + TimePos m_dragStartPos; TimePos m_savedPos; @@ -255,6 +259,7 @@ public slots: MovePositionMarker, MoveLoopBegin, MoveLoopEnd, + MoveLoop, SelectSongClip, } m_action; diff --git a/src/gui/TimeLineWidget.cpp b/src/gui/TimeLineWidget.cpp index 2182b9f2aa3..3c786e0989c 100644 --- a/src/gui/TimeLineWidget.cpp +++ b/src/gui/TimeLineWidget.cpp @@ -67,6 +67,7 @@ TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb, m_pos( pos ), m_begin( begin ), m_mode( mode ), + m_dragStartPos(0), m_savedPos( -1 ), m_hint( nullptr ), m_action( NoAction ) @@ -350,15 +351,17 @@ TimeLineWidget::actions TimeLineWidget::getLoopAction(QMouseEvent* event) if (loopMode == "Handles" && event->button() == Qt::LeftButton) { + // Loop start and end pos, or closest edge of screen if loop extends off it const int leftMost = std::max(markerX(loopBegin()), m_xOffset); - const int deltaLeft = event->x() - leftMost; const int rightMost = std::min(markerX(loopEnd()), width()); + // Distance from click to handle, positive aimed towards center of loop + const int deltaLeft = event->x() - leftMost; const int deltaRight = rightMost - event->x(); if (deltaLeft < 0 || deltaRight < 0) { return NoAction; } // Clicked outside loop else if (deltaLeft <= 5 && deltaLeft < deltaRight) { return MoveLoopBegin; } else if (deltaRight <= 5) { return MoveLoopEnd; } - else { return NoAction; } // TODO: Loop slide + else { return MoveLoop; } } else if (loopMode == "Grab closest" && event->button() == Qt::LeftButton) { @@ -379,9 +382,9 @@ TimeLineWidget::actions TimeLineWidget::getLoopAction(QMouseEvent* event) QCursor TimeLineWidget::actionCursor(actions action) { - if (action == MoveLoopBegin){ return m_cursorSelectLeft; } + if (action == MoveLoop){ return Qt::SizeHorCursor; } + else if (action == MoveLoopBegin){ return m_cursorSelectLeft; } else if (action == MoveLoopEnd){ return m_cursorSelectRight; } - // TODO: loop slide // Fall back to normal cursor if no action or action cursor not specified return Qt::ArrowCursor; } @@ -391,7 +394,7 @@ QCursor TimeLineWidget::actionCursor(actions action) void TimeLineWidget::mousePressEvent(QMouseEvent* event) { - // TODO: properly fix cursor hotspot, this doesn't seem to help + // For whatever reason hotspots can't be set properly in the constructor m_cursorSelectLeft = QCursor(embed::getIconPixmap("cursor_select_left"), m_mouseHotspotSelLeft.width(), m_mouseHotspotSelLeft.height()); m_cursorSelectRight = QCursor(embed::getIconPixmap("cursor_select_right"), @@ -408,8 +411,17 @@ void TimeLineWidget::mousePressEvent(QMouseEvent* event) m_action = getLoopAction(event); setCursor(actionCursor(m_action)); - m_loopPos[(m_action == MoveLoopBegin) ? 0 : 1] = getClickedTime(event); - std::sort(std::begin(m_loopPos), std::end(m_loopPos)); + if (m_action == MoveLoopBegin || m_action == MoveLoopEnd) + { + m_loopPos[(m_action == MoveLoopBegin) ? 0 : 1] = getClickedTime(event); + std::sort(std::begin(m_loopPos), std::end(m_loopPos)); + } + else if (m_action == MoveLoop) + { + m_dragStartPos = getClickedTime(event); + m_oldLoopPos[0] = m_loopPos[0]; + m_oldLoopPos[1] = m_loopPos[1]; + } } else if (event->button() == Qt::LeftButton && ctrl) // selection { @@ -439,6 +451,7 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) { parentWidget()->update(); // essential for widgets that this timeline had taken their mouse move event from. const TimePos t = getClickedTime(event); + const bool control = event->modifiers() & Qt::ControlModifier; switch( m_action ) { @@ -459,7 +472,6 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) case MoveLoopEnd: { const int i = m_action == MoveLoopBegin ? 0 : 1; - const bool control = event->modifiers() & Qt::ControlModifier; if (control) { // no ctrl-press-hint when having ctrl pressed @@ -483,6 +495,17 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) update(); break; } + case MoveLoop: + { + TimePos dragDelta = t - m_dragStartPos; + for (int i = 0; i <= 1; i++) + { + m_loopPos[i] = m_oldLoopPos[i] + dragDelta; + if (!control) { m_loopPos[i] = m_loopPos[i].quantize(m_snapSize); } + } + + break; + } case SelectSongClip: emit regionSelectedFromPixels( m_initalXSelect , event->x() ); break; From 6988715a01ec285c476cdfc5a14604914f887584 Mon Sep 17 00:00:00 2001 From: Spekular Date: Mon, 16 May 2022 21:13:09 +0200 Subject: [PATCH 06/18] Make handle color and width themeable --- data/themes/classic/style.css | 9 ++++++++- data/themes/default/style.css | 7 ++++++- include/TimeLineWidget.h | 6 ++++++ src/gui/TimeLineWidget.cpp | 13 +++++++++---- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index 0499a5bf75e..71d0ff2693b 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -643,15 +643,22 @@ TimeLineWidget { background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #8796a7, stop: 1.0 #3e454e ); - qproperty-inactiveLoopColor: rgba( 52, 63, 53, 64 ); + qproperty-inactiveLoopColor: rgba( 52, 63, 53, 64 ); qproperty-inactiveLoopBrush: rgba( 255, 255, 255, 32 ); qproperty-inactiveLoopInnerColor: rgba( 255, 255, 255, 32 ); + qproperty-inactiveLoopHandleColor: rgba( 192, 192, 192, 100 ); qproperty-activeLoopColor: rgba( 52, 63, 53, 255 ); qproperty-activeLoopBrush: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #378d59, stop: 1.0 #297e36 ); qproperty-activeLoopInnerColor: rgba( 74, 155, 100, 255 ); + qproperty-activeLoopHandleColor: rgba( 192, 192, 192, 200 ); + + /* Width of loop marker handles (when handle mode is active) */ + qproperty-loopHandleWidth: 8; + qproperty-barLineColor: rgb( 192, 192, 192 ); qproperty-barNumberColor: rgb( 192, 192, 192 ); + /* Cursor hotspots for loop marker adjustment */ qproperty-mouseHotspotSelLeft: 0px 0px; qproperty-mouseHotspotSelRight: 32px 0px; diff --git a/data/themes/default/style.css b/data/themes/default/style.css index cfb047588ce..277eaeb3afc 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -681,20 +681,25 @@ TimeLineWidget { /* Properties for the loop indicator rectangle in inactive state: - LoopColor: Color of the outermost border - LoopBrush: Brush to paint the main portion of the rectangle - - LoopInnerColor: Color used to paint the inlayed border */ + - LoopInnerColor: Color used to paint the inlayed border + - LoopHandleColor: Color used to paint loop marker handles */ qproperty-inactiveLoopColor: #3B424A; qproperty-inactiveLoopBrush: #3B424A; qproperty-inactiveLoopInnerColor: #3B424A; + qproperty-inactiveLoopHandleColor: rgba( 192, 192, 192, 100 ); /* Properties for the loop indicator rectangle in active state. See above for detailed description. */ qproperty-activeLoopColor: #21A14F; qproperty-activeLoopBrush: #21A14F; qproperty-activeLoopInnerColor: #21A14F; + qproperty-activeLoopHandleColor: rgba( 192, 192, 192, 200 ); /* Vertical padding for the loop indicator rectangle. A value of zero draws the rectangle at the full height of the widget. */ qproperty-loopRectangleVerticalPadding: 1; + /* Width of loop marker handles (when handle mode is active) */ + qproperty-loopHandleWidth: 8; qproperty-barLineColor: rgb( 192, 192, 192 ); qproperty-barNumberColor: rgb( 192, 192, 192 ); diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index d1a2b9b9561..faa4762323f 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -47,10 +47,13 @@ class TimeLineWidget : public QWidget, public JournallingObject Q_PROPERTY( QColor inactiveLoopColor READ getInactiveLoopColor WRITE setInactiveLoopColor ) Q_PROPERTY( QBrush inactiveLoopBrush READ getInactiveLoopBrush WRITE setInactiveLoopBrush ) Q_PROPERTY( QColor inactiveLoopInnerColor READ getInactiveLoopInnerColor WRITE setInactiveLoopInnerColor ) + Q_PROPERTY(QColor inactiveLoopHandleColor MEMBER m_inactiveLoopHandleColor) Q_PROPERTY( QColor activeLoopColor READ getActiveLoopColor WRITE setActiveLoopColor ) Q_PROPERTY( QBrush activeLoopBrush READ getActiveLoopBrush WRITE setActiveLoopBrush ) Q_PROPERTY( QColor activeLoopInnerColor READ getActiveLoopInnerColor WRITE setActiveLoopInnerColor ) + Q_PROPERTY(QColor activeLoopHandleColor MEMBER m_activeLoopHandleColor) Q_PROPERTY( int loopRectangleVerticalPadding READ getLoopRectangleVerticalPadding WRITE setLoopRectangleVerticalPadding ) + Q_PROPERTY(int loopHandleWidth MEMBER m_loopHandleWidth) Q_PROPERTY( QSize mouseHotspotSelLeft WRITE setMouseHotspotSelLeft ) Q_PROPERTY( QSize mouseHotspotSelRight WRITE setMouseHotspotSelRight ) @@ -211,12 +214,15 @@ public slots: QColor m_inactiveLoopColor; QBrush m_inactiveLoopBrush; QColor m_inactiveLoopInnerColor; + QColor m_inactiveLoopHandleColor; QColor m_activeLoopColor; QBrush m_activeLoopBrush; QColor m_activeLoopInnerColor; + QColor m_activeLoopHandleColor; int m_loopRectangleVerticalPadding; + int m_loopHandleWidth; QColor m_barLineColor; QColor m_barNumberColor; diff --git a/src/gui/TimeLineWidget.cpp b/src/gui/TimeLineWidget.cpp index 3c786e0989c..8a6415ea847 100644 --- a/src/gui/TimeLineWidget.cpp +++ b/src/gui/TimeLineWidget.cpp @@ -47,10 +47,13 @@ TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb, m_inactiveLoopColor( 52, 63, 53, 64 ), m_inactiveLoopBrush( QColor( 255, 255, 255, 32 ) ), m_inactiveLoopInnerColor( 255, 255, 255, 32 ), + m_inactiveLoopHandleColor( 255, 255, 255, 32 ), m_activeLoopColor( 52, 63, 53, 255 ), m_activeLoopBrush( QColor( 55, 141, 89 ) ), m_activeLoopInnerColor( 74, 155, 100, 255 ), + m_activeLoopHandleColor( 74, 155, 100, 255 ), m_loopRectangleVerticalPadding( 1 ), + m_loopHandleWidth( 5 ), m_barLineColor( 192, 192, 192 ), m_barNumberColor( m_barLineColor.darker( 120 ) ), m_mouseHotspotSelLeft( 0, 0 ), @@ -321,12 +324,14 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) p.drawRect( innerRectangle ); // Draw loop handles if necessary - QRect leftHandle(loopStart, loopRectMargin, 5, loopRectHeight - 1); - QRect rightHandle(loopEndR - 5, loopRectMargin, 5, loopRectHeight - 1); + const int hw = std::min(m_loopHandleWidth, loopRectWidth/2); + QRect leftHandle(loopStart, 0.5, hw, loopRectHeight); + QRect rightHandle(loopEndR - hw, 0.5, hw, loopRectHeight); if (ConfigManager::inst()->value( "app", "loopmarkermode" ) == "Handles") { - p.fillRect(leftHandle, Qt::magenta); - p.fillRect(rightHandle, Qt::magenta); + auto color = loopPointsActive ? m_activeLoopHandleColor : m_inactiveLoopHandleColor; + p.fillRect(leftHandle, color); + p.fillRect(rightHandle, color); } // Only draw the position marker if the position line is in view From a5f055d7420cacf3cc6893415e6eb3f230e492f7 Mon Sep 17 00:00:00 2001 From: Spekular Date: Tue, 31 May 2022 23:05:12 +0200 Subject: [PATCH 07/18] Set cursor and display handles when shift+hovering --- include/SongEditor.h | 1 + include/TimeLineWidget.h | 8 ++-- src/gui/TimeLineWidget.cpp | 68 +++++++++++++++++++++++----------- src/gui/editors/SongEditor.cpp | 10 +++++ 4 files changed, 62 insertions(+), 25 deletions(-) diff --git a/include/SongEditor.h b/include/SongEditor.h index db52f7352c8..334771a81e5 100644 --- a/include/SongEditor.h +++ b/include/SongEditor.h @@ -109,6 +109,7 @@ private slots: private: void keyPressEvent( QKeyEvent * ke ) override; + void keyReleaseEvent( QKeyEvent * ke ) override; void wheelEvent( QWheelEvent * we ) override; bool allowRubberband() const override; diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index faa4762323f..07afa148774 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -199,7 +199,7 @@ public slots: void toggleAutoScroll( int _n ); void toggleLoopPoints( int _n ); void toggleBehaviourAtStop( int _n ); - + void setShiftHeld(bool held); protected: void paintEvent( QPaintEvent * _pe ) override; @@ -207,7 +207,6 @@ public slots: void mouseMoveEvent( QMouseEvent * _me ) override; void mouseReleaseEvent( QMouseEvent * _me ) override; - private: static QPixmap * s_posMarkerPixmap; @@ -257,7 +256,7 @@ public slots: TextFloat * m_hint; int m_initalXSelect; - + bool m_shiftHeld; enum actions { @@ -269,7 +268,8 @@ public slots: SelectSongClip, } m_action; - TimeLineWidget::actions getLoopAction(QMouseEvent* event); + actions getLoopAction(QMouseEvent* event); + actions getLoopAction(QString mode, int xPos, Qt::MouseButton button); QCursor actionCursor(actions action); diff --git a/src/gui/TimeLineWidget.cpp b/src/gui/TimeLineWidget.cpp index 8a6415ea847..2c9ffc23392 100644 --- a/src/gui/TimeLineWidget.cpp +++ b/src/gui/TimeLineWidget.cpp @@ -73,6 +73,7 @@ TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb, m_dragStartPos(0), m_savedPos( -1 ), m_hint( nullptr ), + m_shiftHeld(false), m_action( NoAction ) { m_loopPos[0] = 0; @@ -257,6 +258,23 @@ void TimeLineWidget::toggleBehaviourAtStop( int _n ) +void TimeLineWidget::setShiftHeld( bool held ) +{ + m_shiftHeld = held; + if (m_shiftHeld) + { + setCursor(actionCursor(getLoopAction( + ConfigManager::inst()->value( "app", "loopmarkermode" ), + QWidget::mapFromGlobal(QCursor::pos()).x(), + Qt::NoButton + ))); + } + else { setCursor(actionCursor(getLoopAction("", 0, Qt::NoButton))); } +} + + + + void TimeLineWidget::paintEvent( QPaintEvent * ) { QPainter p( this ); @@ -324,10 +342,11 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) p.drawRect( innerRectangle ); // Draw loop handles if necessary - const int hw = std::min(m_loopHandleWidth, loopRectWidth/2); + bool handleMode = ConfigManager::inst()->value( "app", "loopmarkermode" ) == "Handles"; + const int hw = std::min(m_loopHandleWidth, loopRectWidth/2 - 1); QRect leftHandle(loopStart, 0.5, hw, loopRectHeight); QRect rightHandle(loopEndR - hw, 0.5, hw, loopRectHeight); - if (ConfigManager::inst()->value( "app", "loopmarkermode" ) == "Handles") + if (handleMode && underMouse() && m_shiftHeld) { auto color = loopPointsActive ? m_activeLoopHandleColor : m_inactiveLoopHandleColor; p.fillRect(leftHandle, color); @@ -349,34 +368,38 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) TimeLineWidget::actions TimeLineWidget::getLoopAction(QMouseEvent* event) { - if (!(event->modifiers() & Qt::ShiftModifier)){ return NoAction; } + const QString mode = ConfigManager::inst()->value("app", "loopmarkermode"); + return getLoopAction(mode, event->x(), event->button()); +} + + - const TimePos t = getClickedTime(event); - const QString loopMode = ConfigManager::inst()->value( "app", "loopmarkermode" ); - if (loopMode == "Handles" && event->button() == Qt::LeftButton) +TimeLineWidget::actions TimeLineWidget::getLoopAction(QString mode, int xPos, Qt::MouseButton button) +{ + if (mode == "Handles") { // Loop start and end pos, or closest edge of screen if loop extends off it const int leftMost = std::max(markerX(loopBegin()), m_xOffset); const int rightMost = std::min(markerX(loopEnd()), width()); // Distance from click to handle, positive aimed towards center of loop - const int deltaLeft = event->x() - leftMost; - const int deltaRight = rightMost - event->x(); + const int deltaLeft = xPos - leftMost; + const int deltaRight = rightMost - xPos; if (deltaLeft < 0 || deltaRight < 0) { return NoAction; } // Clicked outside loop else if (deltaLeft <= 5 && deltaLeft < deltaRight) { return MoveLoopBegin; } else if (deltaRight <= 5) { return MoveLoopEnd; } else { return MoveLoop; } } - else if (loopMode == "Grab closest" && event->button() == Qt::LeftButton) + else if (mode == "Grab closest") { const TimePos loopMid = (m_loopPos[0] + m_loopPos[1])/2; - return t < loopMid ? MoveLoopBegin : MoveLoopEnd; + return getClickedTime(xPos) < loopMid ? MoveLoopBegin : MoveLoopEnd; } - else if (loopMode == "Dual-button") + else if (mode == "Dual-button") { - if (event->button() == Qt::LeftButton){ return MoveLoopBegin; } - else if (event->button() == Qt::RightButton){ return MoveLoopEnd; } + if (button == Qt::LeftButton){ return MoveLoopBegin; } + else if (button == Qt::RightButton){ return MoveLoopEnd; } } //Fallback @@ -387,6 +410,12 @@ TimeLineWidget::actions TimeLineWidget::getLoopAction(QMouseEvent* event) QCursor TimeLineWidget::actionCursor(actions action) { + // For whatever reason hotspots can't be set properly in the constructor + m_cursorSelectLeft = QCursor(embed::getIconPixmap("cursor_select_left"), + m_mouseHotspotSelLeft.width(), m_mouseHotspotSelLeft.height()); + m_cursorSelectRight = QCursor(embed::getIconPixmap("cursor_select_right"), + m_mouseHotspotSelRight.width(), m_mouseHotspotSelRight.height()); + if (action == MoveLoop){ return Qt::SizeHorCursor; } else if (action == MoveLoopBegin){ return m_cursorSelectLeft; } else if (action == MoveLoopEnd){ return m_cursorSelectRight; } @@ -399,13 +428,6 @@ QCursor TimeLineWidget::actionCursor(actions action) void TimeLineWidget::mousePressEvent(QMouseEvent* event) { - // For whatever reason hotspots can't be set properly in the constructor - m_cursorSelectLeft = QCursor(embed::getIconPixmap("cursor_select_left"), - m_mouseHotspotSelLeft.width(), m_mouseHotspotSelLeft.height()); - m_cursorSelectRight = QCursor(embed::getIconPixmap("cursor_select_right"), - m_mouseHotspotSelRight.width(), m_mouseHotspotSelRight.height()); - - if (event->x() < m_xOffset) { return; } const bool shift = event->modifiers() & Qt::ShiftModifier; @@ -516,9 +538,13 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) break; default: - setCursor(actionCursor(getLoopAction(event))); break; } + + if (m_shiftHeld && event->buttons() == Qt::NoButton) + { + setCursor(actionCursor(getLoopAction(event))); + } } diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index 280a3925c37..9881f0c39a2 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -529,6 +529,16 @@ void SongEditor::keyPressEvent( QKeyEvent * ke ) { QWidget::keyPressEvent( ke ); } + + if (!ke->isAutoRepeat()){ m_timeLine->setShiftHeld(isShiftPressed); } +} + + + + +void SongEditor::keyReleaseEvent( QKeyEvent * ke ) +{ + m_timeLine->setShiftHeld(ke->modifiers() & Qt::ShiftModifier); } From f71eae3c7116816a8421a89c2e409c2ab5943046 Mon Sep 17 00:00:00 2001 From: Dominic Clark Date: Tue, 19 Dec 2023 19:06:46 +0000 Subject: [PATCH 08/18] Align handles with loop at all scale factors --- src/gui/editors/TimeLineWidget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index 485f7bf589f..c0b57eb77c7 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -281,8 +281,8 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) // Draw loop handles if necessary bool handleMode = ConfigManager::inst()->value( "app", "loopmarkermode" ) == "Handles"; const int hw = std::min(m_loopHandleWidth, loopRectWidth/2 - 1); - QRect leftHandle(loopStart, 0.5, hw, loopRectHeight); - QRect rightHandle(loopEndR - hw, 0.5, hw, loopRectHeight); + QRectF leftHandle(loopStart - .5, loopRectMargin - .5, hw, loopRectHeight); + QRectF rightHandle(loopEndR - hw - .5, loopRectMargin - .5, hw, loopRectHeight); if (handleMode && underMouse() && m_shiftHeld) { auto color = loopPointsActive ? m_activeLoopHandleColor : m_inactiveLoopHandleColor; From 13c0e768e4c94a1ec999c1f8d7d5b5792235805b Mon Sep 17 00:00:00 2001 From: Dominic Clark Date: Tue, 19 Dec 2023 20:26:59 +0000 Subject: [PATCH 09/18] Fix formatting --- data/themes/classic/style.css | 2 +- data/themes/default/style.css | 2 +- include/SongEditor.h | 2 +- include/TimeLineWidget.h | 42 ++++++----- src/gui/editors/SongEditor.cpp | 4 +- src/gui/editors/TimeLineWidget.cpp | 114 ++++++++++++----------------- src/gui/modals/SetupDialog.cpp | 17 ++--- 7 files changed, 83 insertions(+), 100 deletions(-) diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index c9be7af7467..8f3b6c55f27 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -663,7 +663,7 @@ lmms--gui--TimeLineWidget { background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #8796a7, stop: 1.0 #3e454e ); - qproperty-inactiveLoopColor: rgba( 52, 63, 53, 64 ); + qproperty-inactiveLoopColor: rgba( 52, 63, 53, 64 ); qproperty-inactiveLoopBrush: rgba( 255, 255, 255, 32 ); qproperty-inactiveLoopInnerColor: rgba( 255, 255, 255, 32 ); qproperty-inactiveLoopHandleColor: rgba( 192, 192, 192, 100 ); diff --git a/data/themes/default/style.css b/data/themes/default/style.css index e0a2a560e74..75b2db5bafe 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -705,7 +705,7 @@ lmms--gui--TimeLineWidget { - LoopColor: Color of the outermost border - LoopBrush: Brush to paint the main portion of the rectangle - LoopInnerColor: Color used to paint the inlayed border - - LoopHandleColor: Color used to paint loop marker handles */ + - LoopHandleColor: Color used to paint loop marker handles */ qproperty-inactiveLoopColor: #3B424A; qproperty-inactiveLoopBrush: #3B424A; qproperty-inactiveLoopInnerColor: #3B424A; diff --git a/include/SongEditor.h b/include/SongEditor.h index e1b4d8b7ac0..14a11e70194 100644 --- a/include/SongEditor.h +++ b/include/SongEditor.h @@ -115,7 +115,7 @@ private slots: private: void keyPressEvent( QKeyEvent * ke ) override; - void keyReleaseEvent( QKeyEvent * ke ) override; + void keyReleaseEvent(QKeyEvent* ke) override; void wheelEvent( QWheelEvent * we ) override; bool allowRubberband() const override; diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index 4da069269b3..bc6a4eedf3b 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -68,8 +68,8 @@ class TimeLineWidget : public QWidget Q_PROPERTY(QColor activeLoopHandleColor MEMBER m_activeLoopHandleColor) Q_PROPERTY( int loopRectangleVerticalPadding READ getLoopRectangleVerticalPadding WRITE setLoopRectangleVerticalPadding ) Q_PROPERTY(int loopHandleWidth MEMBER m_loopHandleWidth) - Q_PROPERTY( QSize mouseHotspotSelLeft WRITE setMouseHotspotSelLeft ) - Q_PROPERTY( QSize mouseHotspotSelRight WRITE setMouseHotspotSelRight ) + Q_PROPERTY(QSize mouseHotspotSelLeft WRITE setMouseHotspotSelLeft) + Q_PROPERTY(QSize mouseHotspotSelRight WRITE setMouseHotspotSelRight) enum class AutoScrollState { @@ -108,8 +108,8 @@ class TimeLineWidget : public QWidget inline int const & getLoopRectangleVerticalPadding() const { return m_loopRectangleVerticalPadding; } inline void setLoopRectangleVerticalPadding(int const & loopRectangleVerticalPadding) { m_loopRectangleVerticalPadding = loopRectangleVerticalPadding; } - inline void setMouseHotspotSelLeft(const QSize & s) { m_mouseHotspotSelLeft = s; } - inline void setMouseHotspotSelRight(const QSize & s) { m_mouseHotspotSelRight = s; } + void setMouseHotspotSelLeft(const QSize& s) { m_mouseHotspotSelLeft = s; } + void setMouseHotspotSelRight(const QSize& s) { m_mouseHotspotSelRight = s; } inline Song::PlayPos & pos() { @@ -128,10 +128,10 @@ class TimeLineWidget : public QWidget } void setXOffset(const int x); - TimePos getClickedTime(const QMouseEvent *event); - TimePos getClickedTime(const int xPosition); + auto getClickedTime(const QMouseEvent* event) -> TimePos; + auto getClickedTime(int xPosition) -> TimePos; // Rightmost position visible on timeline (disregards parent editor scrollbar) - TimePos getEnd(); + auto getEnd() -> TimePos; void addToolButtons(QToolBar* _tool_bar ); @@ -162,6 +162,20 @@ public slots: void mouseReleaseEvent( QMouseEvent * _me ) override; private: + enum class Action + { + NoAction, + MovePositionMarker, + MoveLoopBegin, + MoveLoopEnd, + MoveLoop, + SelectSongClip, + }; + + auto getLoopAction(QMouseEvent* event) -> Action; + auto getLoopAction(QString mode, int xPos, Qt::MouseButton button) -> Action; + auto actionCursor(Action action) -> QCursor; + QPixmap m_posMarkerPixmap = embed::getIconPixmap("playpos_marker"); QColor m_inactiveLoopColor; @@ -207,19 +221,7 @@ public slots: int m_initalXSelect; bool m_shiftHeld; - enum class Action - { - NoAction, - MovePositionMarker, - MoveLoopBegin, - MoveLoopEnd, - MoveLoop, - SelectSongClip, - } m_action; - - Action getLoopAction(QMouseEvent* event); - Action getLoopAction(QString mode, int xPos, Qt::MouseButton button); - QCursor actionCursor(Action action); + Action m_action; }; } // namespace lmms::gui diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index ee607d90236..cec13b66b5a 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -535,13 +535,13 @@ void SongEditor::keyPressEvent( QKeyEvent * ke ) QWidget::keyPressEvent(ke); } - if (!ke->isAutoRepeat()){ m_timeLine->setShiftHeld(isShiftPressed); } + if (!ke->isAutoRepeat()) { m_timeLine->setShiftHeld(isShiftPressed); } } -void SongEditor::keyReleaseEvent( QKeyEvent * ke ) +void SongEditor::keyReleaseEvent(QKeyEvent* ke) { m_timeLine->setShiftHeld(ke->modifiers() & Qt::ShiftModifier); } diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index c0b57eb77c7..05bff875d32 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -52,17 +52,17 @@ TimeLineWidget::TimeLineWidget(const int xoff, const int yoff, const float ppb, m_inactiveLoopColor( 52, 63, 53, 64 ), m_inactiveLoopBrush( QColor( 255, 255, 255, 32 ) ), m_inactiveLoopInnerColor( 255, 255, 255, 32 ), - m_inactiveLoopHandleColor( 255, 255, 255, 32 ), + m_inactiveLoopHandleColor(255, 255, 255, 32), m_activeLoopColor( 52, 63, 53, 255 ), m_activeLoopBrush( QColor( 55, 141, 89 ) ), m_activeLoopInnerColor( 74, 155, 100, 255 ), - m_activeLoopHandleColor( 74, 155, 100, 255 ), + m_activeLoopHandleColor(74, 155, 100, 255), m_loopRectangleVerticalPadding( 1 ), - m_loopHandleWidth( 5 ), + m_loopHandleWidth(5), m_barLineColor( 192, 192, 192 ), m_barNumberColor( m_barLineColor.darker( 120 ) ), - m_mouseHotspotSelLeft( 0, 0 ), - m_mouseHotspotSelRight( 0, 0 ), + m_mouseHotspotSelLeft(0, 0), + m_mouseHotspotSelRight(0, 0), m_cursorSelectLeft(QCursor(embed::getIconPixmap("cursor_select_left"))), m_cursorSelectRight(QCursor(embed::getIconPixmap("cursor_select_right"))), m_autoScroll( AutoScrollState::Enabled ), @@ -77,7 +77,7 @@ TimeLineWidget::TimeLineWidget(const int xoff, const int yoff, const float ppb, m_dragStartPos(0), m_hint( nullptr ), m_shiftHeld(false), - m_action( Action::NoAction ) + m_action(Action::NoAction) { setAttribute( Qt::WA_OpaquePaintEvent, true ); move( 0, yoff ); @@ -111,35 +111,26 @@ void TimeLineWidget::setXOffset(const int x) m_xOffset = x; } - - - -TimePos TimeLineWidget::getClickedTime(const QMouseEvent *event) +auto TimeLineWidget::getClickedTime(const QMouseEvent* event) -> TimePos { return getClickedTime(event->x()); } -TimePos TimeLineWidget::getClickedTime(const int xPosition) +auto TimeLineWidget::getClickedTime(const int xPosition) -> TimePos { // How far into the timeline we clicked, measuring pixels from the leftmost part of the editor - const int pixelDelta = qMax(xPosition - m_xOffset, 0); + const auto pixelDelta = std::max(xPosition - m_xOffset, 0); return m_begin + static_cast(pixelDelta * TimePos::ticksPerBar() / m_ppb); } - - - -TimePos TimeLineWidget::getEnd() +auto TimeLineWidget::getEnd() -> TimePos { - auto contentWidth = width() - m_xOffset; - auto ticksPerPixel = TimePos::ticksPerBar() / m_ppb; - return m_begin + (contentWidth * ticksPerPixel); + const auto contentWidth = width() - m_xOffset; + const auto ticksPerPixel = TimePos::ticksPerBar() / m_ppb; + return m_begin + (contentWidth * ticksPerPixel); } - - - void TimeLineWidget::addToolButtons( QToolBar * _tool_bar ) { auto autoScroll = new NStateButton(_tool_bar); @@ -188,7 +179,7 @@ void TimeLineWidget::addToolButtons( QToolBar * _tool_bar ) void TimeLineWidget::updatePosition() { m_changedPosition = true; - emit positionChanged( m_pos ); + emit positionChanged(m_pos); update(); } @@ -197,13 +188,13 @@ void TimeLineWidget::toggleAutoScroll( int _n ) m_autoScroll = static_cast( _n ); } -void TimeLineWidget::setShiftHeld( bool held ) +void TimeLineWidget::setShiftHeld(bool held) { m_shiftHeld = held; if (m_shiftHeld) { setCursor(actionCursor(getLoopAction( - ConfigManager::inst()->value( "app", "loopmarkermode" ), + ConfigManager::inst()->value("app", "loopmarkermode"), QWidget::mapFromGlobal(QCursor::pos()).x(), Qt::NoButton ))); @@ -219,10 +210,10 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) p.fillRect( 0, 0, width(), height(), p.background() ); // Clip so that we only draw everything starting from the offset - p.setClipRect(m_xOffset, 0, width() - m_xOffset, height() ); + p.setClipRect(m_xOffset, 0, width() - m_xOffset, height()); // Variables for the loop rectangle - int const & loopRectMargin = getLoopRectangleVerticalPadding(); + int const loopRectMargin = getLoopRectangleVerticalPadding(); int const loopRectHeight = this->height() - 2 * loopRectMargin; int const loopStart = markerX(m_timeline->loopBegin()); int const loopEndR = markerX(m_timeline->loopEnd()); @@ -279,10 +270,10 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) p.drawRect( innerRectangle ); // Draw loop handles if necessary - bool handleMode = ConfigManager::inst()->value( "app", "loopmarkermode" ) == "Handles"; - const int hw = std::min(m_loopHandleWidth, loopRectWidth/2 - 1); - QRectF leftHandle(loopStart - .5, loopRectMargin - .5, hw, loopRectHeight); - QRectF rightHandle(loopEndR - hw - .5, loopRectMargin - .5, hw, loopRectHeight); + const auto handleMode = ConfigManager::inst()->value("app", "loopmarkermode") == "Handles"; + const auto hw = std::min(m_loopHandleWidth, loopRectWidth / 2 - 1); + const auto leftHandle = QRectF(loopStart - .5, loopRectMargin - .5, hw, loopRectHeight); + const auto rightHandle = QRectF(loopEndR - hw - .5, loopRectMargin - .5, hw, loopRectHeight); if (handleMode && underMouse() && m_shiftHeld) { auto color = loopPointsActive ? m_activeLoopHandleColor : m_inactiveLoopHandleColor; @@ -296,32 +287,27 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) // Let the position marker extrude to the left p.setClipping(false); p.setOpacity(0.6); - p.drawPixmap(markerX(m_pos) - (m_posMarkerPixmap.width() / 2), height() - m_posMarkerPixmap.height(), m_posMarkerPixmap); + p.drawPixmap(markerX(m_pos) - (m_posMarkerPixmap.width() / 2), + height() - m_posMarkerPixmap.height(), m_posMarkerPixmap); } } - - - -TimeLineWidget::Action TimeLineWidget::getLoopAction(QMouseEvent* event) +auto TimeLineWidget::getLoopAction(QMouseEvent* event) -> TimeLineWidget::Action { - const QString mode = ConfigManager::inst()->value("app", "loopmarkermode"); + const auto mode = ConfigManager::inst()->value("app", "loopmarkermode"); return getLoopAction(mode, event->x(), event->button()); } - - - -TimeLineWidget::Action TimeLineWidget::getLoopAction(QString mode, int xPos, Qt::MouseButton button) +auto TimeLineWidget::getLoopAction(QString mode, int xPos, Qt::MouseButton button) -> TimeLineWidget::Action { if (mode == "Handles") { // Loop start and end pos, or closest edge of screen if loop extends off it - const int leftMost = std::max(markerX(m_timeline->loopBegin()), m_xOffset); - const int rightMost = std::min(markerX(m_timeline->loopEnd()), width()); + const auto leftMost = std::max(markerX(m_timeline->loopBegin()), m_xOffset); + const auto rightMost = std::min(markerX(m_timeline->loopEnd()), width()); // Distance from click to handle, positive aimed towards center of loop - const int deltaLeft = xPos - leftMost; - const int deltaRight = rightMost - xPos; + const auto deltaLeft = xPos - leftMost; + const auto deltaRight = rightMost - xPos; if (deltaLeft < 0 || deltaRight < 0) { return Action::NoAction; } // Clicked outside loop else if (deltaLeft <= 5 && deltaLeft < deltaRight) { return Action::MoveLoopBegin; } @@ -330,22 +316,20 @@ TimeLineWidget::Action TimeLineWidget::getLoopAction(QString mode, int xPos, Qt: } else if (mode == "Grab closest") { - const TimePos loopMid = (m_timeline->loopBegin() + m_timeline->loopEnd())/2; + const TimePos loopMid = (m_timeline->loopBegin() + m_timeline->loopEnd()) / 2; return getClickedTime(xPos) < loopMid ? Action::MoveLoopBegin : Action::MoveLoopEnd; } else if (mode == "Dual-button") { - if (button == Qt::LeftButton){ return Action::MoveLoopBegin; } - else if (button == Qt::RightButton){ return Action::MoveLoopEnd; } + if (button == Qt::LeftButton) { return Action::MoveLoopBegin; } + else if (button == Qt::RightButton) { return Action::MoveLoopEnd; } } - //Fallback + // Fallback return Action::NoAction; } - - -QCursor TimeLineWidget::actionCursor(Action action) +auto TimeLineWidget::actionCursor(Action action) -> QCursor { // For whatever reason hotspots can't be set properly in the constructor m_cursorSelectLeft = QCursor(embed::getIconPixmap("cursor_select_left"), @@ -353,22 +337,19 @@ QCursor TimeLineWidget::actionCursor(Action action) m_cursorSelectRight = QCursor(embed::getIconPixmap("cursor_select_right"), m_mouseHotspotSelRight.width(), m_mouseHotspotSelRight.height()); - if (action == Action::MoveLoop){ return Qt::SizeHorCursor; } - else if (action == Action::MoveLoopBegin){ return m_cursorSelectLeft; } - else if (action == Action::MoveLoopEnd){ return m_cursorSelectRight; } + if (action == Action::MoveLoop) { return Qt::SizeHorCursor; } + else if (action == Action::MoveLoopBegin) { return m_cursorSelectLeft; } + else if (action == Action::MoveLoopEnd) { return m_cursorSelectRight; } // Fall back to normal cursor if no action or action cursor not specified return Qt::ArrowCursor; } - - - void TimeLineWidget::mousePressEvent(QMouseEvent* event) { if (event->x() < m_xOffset) { return; } - const bool shift = event->modifiers() & Qt::ShiftModifier; - const bool ctrl = event->modifiers() & Qt::ControlModifier; + const auto shift = event->modifiers() & Qt::ShiftModifier; + const auto ctrl = event->modifiers() & Qt::ControlModifier; if (shift) // loop marker manipulation { @@ -390,15 +371,16 @@ void TimeLineWidget::mousePressEvent(QMouseEvent* event) { m_action = Action::MovePositionMarker; } - else if (event->button() == Qt::RightButton){} // TODO: right click menu + else if (event->button() == Qt::RightButton) {} // TODO: right click menu if (m_action == Action::MoveLoopBegin || m_action == Action::MoveLoopEnd) { delete m_hint; - m_hint = TextFloat::displayMessage( tr( "Hint" ), - tr( "Press <%1> to disable magnetic loop points." ).arg(UI_CTRL_KEY), - embed::getIconPixmap( "hint" ), 0 ); + m_hint = TextFloat::displayMessage(tr("Hint"), + tr("Press <%1> to disable magnetic loop points.").arg(UI_CTRL_KEY), + embed::getIconPixmap("hint"), 0); } + mouseMoveEvent(event); } @@ -408,8 +390,9 @@ void TimeLineWidget::mousePressEvent(QMouseEvent* event) void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) { parentWidget()->update(); // essential for widgets that this timeline had taken their mouse move event from. + auto timeAtCursor = getClickedTime(event); - const bool control = event->modifiers() & Qt::ControlModifier; + const auto control = event->modifiers() & Qt::ControlModifier; switch( m_action ) { @@ -466,7 +449,6 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) if (!control) { point = point.quantize(m_snapSize); } } m_timeline->setLoopPoints(loopPos[0], loopPos[1]); - break; } case Action::SelectSongClip: diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index c7fa1133f89..2695a048c98 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -120,8 +120,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : "app", "disablebackup").toInt()), m_openLastProject(ConfigManager::inst()->value( "app", "openlastproject").toInt()), - m_loopMarkerMode(ConfigManager::inst()->value( - "app", "loopmarkermode")), + m_loopMarkerMode(ConfigManager::inst()->value("app", "loopmarkermode")), m_lang(ConfigManager::inst()->value( "app", "language")), m_saveInterval( ConfigManager::inst()->value( @@ -257,7 +256,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : addCheckBox(tr("Show warning when deleting a mixer channel that is in use"), guiGroupBox, guiGroupLayout, m_mixerChannelDeletionWarning, SLOT(toggleMixerChannelDeletionWarning(bool)), false); - QComboBox * changeLoop = new QComboBox(guiGroupBox); + const auto changeLoop = new QComboBox(guiGroupBox); m_loopMarkerModes.append(QString("Grab closest")); m_loopMarkerModes.append(QString("Handles")); @@ -268,17 +267,17 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : changeLoop->addItem(mode); } - for(int i = 0; i < changeLoop->count(); ++i) + for (int i = 0; i < changeLoop->count(); ++i) { - if(m_loopMarkerMode == m_loopMarkerModes.at(i)) + if (m_loopMarkerMode == m_loopMarkerModes.at(i)) { changeLoop->setCurrentIndex(i); break; } } - connect(changeLoop, SIGNAL(currentIndexChanged(int)), - this, SLOT(setLoopMarkerMode(int))); + connect(changeLoop, static_cast(&QComboBox::currentIndexChanged), + this, &SetupDialog::setLoopMarkerMode); guiGroupLayout->addWidget(changeLoop); @@ -1089,9 +1088,9 @@ void SetupDialog::toggleOpenLastProject(bool enabled) } -void SetupDialog::setLoopMarkerMode(int lang) +void SetupDialog::setLoopMarkerMode(int mode) { - m_loopMarkerMode = m_loopMarkerModes[lang]; + m_loopMarkerMode = m_loopMarkerModes[mode]; } From 62d1e4760ff4e7d5143dcd111d96d780f0683e58 Mon Sep 17 00:00:00 2001 From: Dominic Clark Date: Wed, 20 Dec 2023 01:47:11 +0000 Subject: [PATCH 10/18] Improve cursor handling --- data/themes/classic/style.css | 4 ++-- data/themes/default/style.css | 4 ++-- include/TimeLineWidget.h | 37 ++++++++++++++++++++++-------- src/gui/editors/TimeLineWidget.cpp | 33 ++++++++------------------ 4 files changed, 41 insertions(+), 37 deletions(-) diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index 8f3b6c55f27..9eeb41993be 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -680,8 +680,8 @@ lmms--gui--TimeLineWidget { qproperty-barNumberColor: rgb( 192, 192, 192 ); /* Cursor hotspots for loop marker adjustment */ - qproperty-mouseHotspotSelLeft: 0px 0px; - qproperty-mouseHotspotSelRight: 32px 0px; + qproperty-mouseHotspotSelLeft: 0px 16px; + qproperty-mouseHotspotSelRight: 32px 16px; } QTreeView { diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 75b2db5bafe..7963f51a417 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -728,8 +728,8 @@ lmms--gui--TimeLineWidget { qproperty-barNumberColor: rgb( 192, 192, 192 ); /* Cursor hotspots for loop marker adjustment */ - qproperty-mouseHotspotSelLeft: 0px 0px; - qproperty-mouseHotspotSelRight: 32px 0px; + qproperty-mouseHotspotSelLeft: 0px 16px; + qproperty-mouseHotspotSelRight: 32px 16px; } lmms--gui--TrackContainerView QLabel diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index bc6a4eedf3b..7683936aed8 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -68,8 +68,8 @@ class TimeLineWidget : public QWidget Q_PROPERTY(QColor activeLoopHandleColor MEMBER m_activeLoopHandleColor) Q_PROPERTY( int loopRectangleVerticalPadding READ getLoopRectangleVerticalPadding WRITE setLoopRectangleVerticalPadding ) Q_PROPERTY(int loopHandleWidth MEMBER m_loopHandleWidth) - Q_PROPERTY(QSize mouseHotspotSelLeft WRITE setMouseHotspotSelLeft) - Q_PROPERTY(QSize mouseHotspotSelRight WRITE setMouseHotspotSelRight) + Q_PROPERTY(QSize mouseHotspotSelLeft READ mouseHotspotSelLeft WRITE setMouseHotspotSelLeft) + Q_PROPERTY(QSize mouseHotspotSelRight READ mouseHotspotSelRight WRITE setMouseHotspotSelRight) enum class AutoScrollState { @@ -108,8 +108,27 @@ class TimeLineWidget : public QWidget inline int const & getLoopRectangleVerticalPadding() const { return m_loopRectangleVerticalPadding; } inline void setLoopRectangleVerticalPadding(int const & loopRectangleVerticalPadding) { m_loopRectangleVerticalPadding = loopRectangleVerticalPadding; } - void setMouseHotspotSelLeft(const QSize& s) { m_mouseHotspotSelLeft = s; } - void setMouseHotspotSelRight(const QSize& s) { m_mouseHotspotSelRight = s; } + auto mouseHotspotSelLeft() const -> QSize + { + const auto point = m_cursorSelectLeft.hotSpot(); + return QSize{point.x(), point.y()}; + } + + void setMouseHotspotSelLeft(const QSize& s) + { + m_cursorSelectLeft = QCursor{m_cursorSelectLeft.pixmap(), s.width(), s.height()}; + } + + auto mouseHotspotSelRight() const -> QSize + { + const auto point = m_cursorSelectRight.hotSpot(); + return QSize{point.x(), point.y()}; + } + + void setMouseHotspotSelRight(const QSize& s) + { + m_cursorSelectRight = QCursor{m_cursorSelectRight.pixmap(), s.width(), s.height()}; + } inline Song::PlayPos & pos() { @@ -174,7 +193,7 @@ public slots: auto getLoopAction(QMouseEvent* event) -> Action; auto getLoopAction(QString mode, int xPos, Qt::MouseButton button) -> Action; - auto actionCursor(Action action) -> QCursor; + auto actionCursor(Action action) const -> QCursor; QPixmap m_posMarkerPixmap = embed::getIconPixmap("playpos_marker"); @@ -193,11 +212,9 @@ public slots: QColor m_barLineColor; QColor m_barNumberColor; - - QSize m_mouseHotspotSelLeft; - QSize m_mouseHotspotSelRight; - QCursor m_cursorSelectLeft; - QCursor m_cursorSelectRight; + + QCursor m_cursorSelectLeft = QCursor{embed::getIconPixmap("cursor_select_left"), 0, 16}; + QCursor m_cursorSelectRight = QCursor{embed::getIconPixmap("cursor_select_right"), 32, 16}; AutoScrollState m_autoScroll; diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index 05bff875d32..24266cce799 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -61,10 +61,6 @@ TimeLineWidget::TimeLineWidget(const int xoff, const int yoff, const float ppb, m_loopHandleWidth(5), m_barLineColor( 192, 192, 192 ), m_barNumberColor( m_barLineColor.darker( 120 ) ), - m_mouseHotspotSelLeft(0, 0), - m_mouseHotspotSelRight(0, 0), - m_cursorSelectLeft(QCursor(embed::getIconPixmap("cursor_select_left"))), - m_cursorSelectRight(QCursor(embed::getIconPixmap("cursor_select_right"))), m_autoScroll( AutoScrollState::Enabled ), m_changedPosition( true ), m_xOffset( xoff ), @@ -89,11 +85,6 @@ TimeLineWidget::TimeLineWidget(const int xoff, const int yoff, const float ppb, updateTimer->start( 1000 / 60 ); // 60 fps connect( Engine::getSong(), SIGNAL(timeSignatureChanged(int,int)), this, SLOT(update())); - - m_cursorSelectLeft = QCursor(embed::getIconPixmap("cursor_select_left"), - m_mouseHotspotSelLeft.width(), m_mouseHotspotSelLeft.height()); - m_cursorSelectRight = QCursor(embed::getIconPixmap("cursor_select_right"), - m_mouseHotspotSelRight.width(), m_mouseHotspotSelRight.height()); } @@ -310,8 +301,8 @@ auto TimeLineWidget::getLoopAction(QString mode, int xPos, Qt::MouseButton butto const auto deltaRight = rightMost - xPos; if (deltaLeft < 0 || deltaRight < 0) { return Action::NoAction; } // Clicked outside loop - else if (deltaLeft <= 5 && deltaLeft < deltaRight) { return Action::MoveLoopBegin; } - else if (deltaRight <= 5) { return Action::MoveLoopEnd; } + else if (deltaLeft <= m_loopHandleWidth && deltaLeft < deltaRight) { return Action::MoveLoopBegin; } + else if (deltaRight <= m_loopHandleWidth) { return Action::MoveLoopEnd; } else { return Action::MoveLoop; } } else if (mode == "Grab closest") @@ -329,19 +320,15 @@ auto TimeLineWidget::getLoopAction(QString mode, int xPos, Qt::MouseButton butto return Action::NoAction; } -auto TimeLineWidget::actionCursor(Action action) -> QCursor +auto TimeLineWidget::actionCursor(Action action) const -> QCursor { - // For whatever reason hotspots can't be set properly in the constructor - m_cursorSelectLeft = QCursor(embed::getIconPixmap("cursor_select_left"), - m_mouseHotspotSelLeft.width(), m_mouseHotspotSelLeft.height()); - m_cursorSelectRight = QCursor(embed::getIconPixmap("cursor_select_right"), - m_mouseHotspotSelRight.width(), m_mouseHotspotSelRight.height()); - - if (action == Action::MoveLoop) { return Qt::SizeHorCursor; } - else if (action == Action::MoveLoopBegin) { return m_cursorSelectLeft; } - else if (action == Action::MoveLoopEnd) { return m_cursorSelectRight; } - // Fall back to normal cursor if no action or action cursor not specified - return Qt::ArrowCursor; + switch (action) { + case Action::MoveLoop: return Qt::SizeHorCursor; + case Action::MoveLoopBegin: return m_cursorSelectLeft; + case Action::MoveLoopEnd: return m_cursorSelectRight; + // Fall back to normal cursor if no action or action cursor not specified + default: return Qt::ArrowCursor; + } } void TimeLineWidget::mousePressEvent(QMouseEvent* event) From 8128d558b5599c000825e261c3bf9f2d19e40586 Mon Sep 17 00:00:00 2001 From: Dominic Clark Date: Thu, 21 Dec 2023 00:50:04 +0000 Subject: [PATCH 11/18] Improve shift modifier handling --- include/SongEditor.h | 1 - include/TimeLineWidget.h | 3 --- src/gui/editors/SongEditor.cpp | 10 ---------- src/gui/editors/TimeLineWidget.cpp | 31 +++++++++--------------------- 4 files changed, 9 insertions(+), 36 deletions(-) diff --git a/include/SongEditor.h b/include/SongEditor.h index 14a11e70194..ee9e83f44c3 100644 --- a/include/SongEditor.h +++ b/include/SongEditor.h @@ -115,7 +115,6 @@ private slots: private: void keyPressEvent( QKeyEvent * ke ) override; - void keyReleaseEvent(QKeyEvent* ke) override; void wheelEvent( QWheelEvent * we ) override; bool allowRubberband() const override; diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index 7683936aed8..aa8787253d3 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -172,7 +172,6 @@ public slots: m_snapSize = snapSize; } void toggleAutoScroll( int _n ); - void setShiftHeld(bool held); protected: void paintEvent( QPaintEvent * _pe ) override; @@ -192,7 +191,6 @@ public slots: }; auto getLoopAction(QMouseEvent* event) -> Action; - auto getLoopAction(QString mode, int xPos, Qt::MouseButton button) -> Action; auto actionCursor(Action action) const -> QCursor; QPixmap m_posMarkerPixmap = embed::getIconPixmap("playpos_marker"); @@ -236,7 +234,6 @@ public slots: TextFloat * m_hint; int m_initalXSelect; - bool m_shiftHeld; Action m_action; }; diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index cec13b66b5a..400c03aa90c 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -534,16 +534,6 @@ void SongEditor::keyPressEvent( QKeyEvent * ke ) { QWidget::keyPressEvent(ke); } - - if (!ke->isAutoRepeat()) { m_timeLine->setShiftHeld(isShiftPressed); } -} - - - - -void SongEditor::keyReleaseEvent(QKeyEvent* ke) -{ - m_timeLine->setShiftHeld(ke->modifiers() & Qt::ShiftModifier); } diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index 24266cce799..cba8dabd5d3 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -72,7 +73,6 @@ TimeLineWidget::TimeLineWidget(const int xoff, const int yoff, const float ppb, m_mode( mode ), m_dragStartPos(0), m_hint( nullptr ), - m_shiftHeld(false), m_action(Action::NoAction) { setAttribute( Qt::WA_OpaquePaintEvent, true ); @@ -179,20 +179,6 @@ void TimeLineWidget::toggleAutoScroll( int _n ) m_autoScroll = static_cast( _n ); } -void TimeLineWidget::setShiftHeld(bool held) -{ - m_shiftHeld = held; - if (m_shiftHeld) - { - setCursor(actionCursor(getLoopAction( - ConfigManager::inst()->value("app", "loopmarkermode"), - QWidget::mapFromGlobal(QCursor::pos()).x(), - Qt::NoButton - ))); - } - else { setCursor(actionCursor(getLoopAction("", 0, Qt::NoButton))); } -} - void TimeLineWidget::paintEvent( QPaintEvent * ) { QPainter p( this ); @@ -265,7 +251,7 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) const auto hw = std::min(m_loopHandleWidth, loopRectWidth / 2 - 1); const auto leftHandle = QRectF(loopStart - .5, loopRectMargin - .5, hw, loopRectHeight); const auto rightHandle = QRectF(loopEndR - hw - .5, loopRectMargin - .5, hw, loopRectHeight); - if (handleMode && underMouse() && m_shiftHeld) + if (handleMode && underMouse() && QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) { auto color = loopPointsActive ? m_activeLoopHandleColor : m_inactiveLoopHandleColor; p.fillRect(leftHandle, color); @@ -286,11 +272,9 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) auto TimeLineWidget::getLoopAction(QMouseEvent* event) -> TimeLineWidget::Action { const auto mode = ConfigManager::inst()->value("app", "loopmarkermode"); - return getLoopAction(mode, event->x(), event->button()); -} + const auto xPos = event->x(); + const auto button = event->button(); -auto TimeLineWidget::getLoopAction(QString mode, int xPos, Qt::MouseButton button) -> TimeLineWidget::Action -{ if (mode == "Handles") { // Loop start and end pos, or closest edge of screen if loop extends off it @@ -446,9 +430,12 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) break; } - if (m_shiftHeld && event->buttons() == Qt::NoButton) + if (event->buttons() == Qt::NoButton) { - setCursor(actionCursor(getLoopAction(event))); + setCursor(QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier) + ? actionCursor(getLoopAction(event)) + : Qt::ArrowCursor + ); } } From 64c69823b7706b77be8ed885cc6a23933a9b0b3b Mon Sep 17 00:00:00 2001 From: Dominic Clark Date: Thu, 21 Dec 2023 02:15:58 +0000 Subject: [PATCH 12/18] Make options translatable --- include/SetupDialog.h | 4 ++-- src/gui/editors/TimeLineWidget.cpp | 12 +++++------ src/gui/modals/SetupDialog.cpp | 33 +++++++++++------------------- 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/include/SetupDialog.h b/include/SetupDialog.h index a2d50fc1511..ce81bb47778 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -86,7 +86,7 @@ private slots: void toggleMMPZ(bool enabled); void toggleDisableBackup(bool enabled); void toggleOpenLastProject(bool enabled); - void setLoopMarkerMode(int mode); + void loopMarkerModeChanged(); void setLanguage(int lang); // Performance settings widget. @@ -149,7 +149,7 @@ private slots: bool m_disableBackup; bool m_openLastProject; QString m_loopMarkerMode; - QStringList m_loopMarkerModes; + QComboBox* m_loopMarkerComboBox; QString m_lang; QStringList m_languages; diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index cba8dabd5d3..3388346db77 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -247,7 +247,7 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) p.drawRect( innerRectangle ); // Draw loop handles if necessary - const auto handleMode = ConfigManager::inst()->value("app", "loopmarkermode") == "Handles"; + const auto handleMode = ConfigManager::inst()->value("app", "loopmarkermode") == "handles"; const auto hw = std::min(m_loopHandleWidth, loopRectWidth / 2 - 1); const auto leftHandle = QRectF(loopStart - .5, loopRectMargin - .5, hw, loopRectHeight); const auto rightHandle = QRectF(loopEndR - hw - .5, loopRectMargin - .5, hw, loopRectHeight); @@ -275,7 +275,7 @@ auto TimeLineWidget::getLoopAction(QMouseEvent* event) -> TimeLineWidget::Action const auto xPos = event->x(); const auto button = event->button(); - if (mode == "Handles") + if (mode == "handles") { // Loop start and end pos, or closest edge of screen if loop extends off it const auto leftMost = std::max(markerX(m_timeline->loopBegin()), m_xOffset); @@ -289,19 +289,17 @@ auto TimeLineWidget::getLoopAction(QMouseEvent* event) -> TimeLineWidget::Action else if (deltaRight <= m_loopHandleWidth) { return Action::MoveLoopEnd; } else { return Action::MoveLoop; } } - else if (mode == "Grab closest") + else if (mode == "closest") { const TimePos loopMid = (m_timeline->loopBegin() + m_timeline->loopEnd()) / 2; return getClickedTime(xPos) < loopMid ? Action::MoveLoopBegin : Action::MoveLoopEnd; } - else if (mode == "Dual-button") + else // Default to dual-button mode { if (button == Qt::LeftButton) { return Action::MoveLoopBegin; } else if (button == Qt::RightButton) { return Action::MoveLoopEnd; } + return Action::NoAction; } - - // Fallback - return Action::NoAction; } auto TimeLineWidget::actionCursor(Action action) const -> QCursor diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 2695a048c98..1dc2780d7f3 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -256,30 +256,21 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : addCheckBox(tr("Show warning when deleting a mixer channel that is in use"), guiGroupBox, guiGroupLayout, m_mixerChannelDeletionWarning, SLOT(toggleMixerChannelDeletionWarning(bool)), false); - const auto changeLoop = new QComboBox(guiGroupBox); + m_loopMarkerComboBox = new QComboBox{guiGroupBox}; - m_loopMarkerModes.append(QString("Grab closest")); - m_loopMarkerModes.append(QString("Handles")); - m_loopMarkerModes.append(QString("Dual-button")); + m_loopMarkerComboBox->addItem(tr("Dual-button"), "dual"); + m_loopMarkerComboBox->addItem(tr("Grab closest"), "closest"); + m_loopMarkerComboBox->addItem(tr("Handles"), "handles"); - for (QString mode : m_loopMarkerModes) - { - changeLoop->addItem(mode); - } - - for (int i = 0; i < changeLoop->count(); ++i) - { - if (m_loopMarkerMode == m_loopMarkerModes.at(i)) - { - changeLoop->setCurrentIndex(i); - break; - } + if (const auto index = m_loopMarkerComboBox->findData(m_loopMarkerMode); index != -1) { + m_loopMarkerComboBox->setCurrentIndex(index); } - connect(changeLoop, static_cast(&QComboBox::currentIndexChanged), - this, &SetupDialog::setLoopMarkerMode); + connect(m_loopMarkerComboBox, qOverload(&QComboBox::currentIndexChanged), + this, &SetupDialog::loopMarkerModeChanged); - guiGroupLayout->addWidget(changeLoop); + guiGroupLayout->addWidget(new QLabel{tr("Loop marker edit mode"), guiGroupBox}); + guiGroupLayout->addWidget(m_loopMarkerComboBox); generalControlsLayout->addWidget(guiGroupBox); @@ -1088,9 +1079,9 @@ void SetupDialog::toggleOpenLastProject(bool enabled) } -void SetupDialog::setLoopMarkerMode(int mode) +void SetupDialog::loopMarkerModeChanged() { - m_loopMarkerMode = m_loopMarkerModes[mode]; + m_loopMarkerMode = m_loopMarkerComboBox->currentData().toString(); } From adfb6c1af84b211dd81625d48b943623e63a8741 Mon Sep 17 00:00:00 2001 From: Dominic Clark Date: Thu, 21 Dec 2023 17:04:46 +0000 Subject: [PATCH 13/18] Add context menu --- include/TimeLineWidget.h | 1 + src/gui/editors/TimeLineWidget.cpp | 44 +++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index aa8787253d3..0482bafda5a 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -178,6 +178,7 @@ public slots: void mousePressEvent( QMouseEvent * _me ) override; void mouseMoveEvent( QMouseEvent * _me ) override; void mouseReleaseEvent( QMouseEvent * _me ) override; + void contextMenuEvent(QContextMenuEvent* event) override; private: enum class Action diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index 3388346db77..d4accd1ac9d 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -340,7 +341,6 @@ void TimeLineWidget::mousePressEvent(QMouseEvent* event) { m_action = Action::MovePositionMarker; } - else if (event->button() == Qt::RightButton) {} // TODO: right click menu if (m_action == Action::MoveLoopBegin || m_action == Action::MoveLoopEnd) { @@ -350,6 +350,8 @@ void TimeLineWidget::mousePressEvent(QMouseEvent* event) embed::getIconPixmap("hint"), 0); } + setContextMenuPolicy(m_action == Action::NoAction ? Qt::DefaultContextMenu : Qt::PreventContextMenu); + mouseMoveEvent(event); } @@ -448,5 +450,45 @@ void TimeLineWidget::mouseReleaseEvent( QMouseEvent* event ) m_action = Action::NoAction; } +void TimeLineWidget::contextMenuEvent(QContextMenuEvent* event) +{ + if (event->x() < m_xOffset) { return; } + + auto menu = QMenu{}; + + menu.addAction("Set loop begin here", [this, event] { + auto begin = getClickedTime(event->x()); + const auto end = m_timeline->loopEnd(); + if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) { begin = begin.quantize(m_snapSize); } + if (begin == end) { m_timeline->setLoopEnd(end + m_snapSize * TimePos::ticksPerBar()); } + m_timeline->setLoopBegin(begin); + update(); + }); + menu.addAction("Set loop end here", [this, event] { + const auto begin = m_timeline->loopBegin(); + auto end = getClickedTime(event->x()); + if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) { end = end.quantize(m_snapSize); } + if (begin == end) { m_timeline->setLoopBegin(begin - m_snapSize * TimePos::ticksPerBar()); } + m_timeline->setLoopEnd(end); + update(); + }); + + menu.addSeparator(); + + const auto loopMenu = menu.addMenu(tr("Loop marker edit mode")); + const auto loopMode = ConfigManager::inst()->value("app", "loopmarkermode", "dual"); + const auto addLoopModeAction = [loopMenu, &loopMode](QString text, QString mode) { + const auto action = loopMenu->addAction(text, [mode] { + ConfigManager::inst()->setValue("app", "loopmarkermode", mode); + }); + action->setCheckable(true); + if (loopMode == mode) { action->setChecked(true); } + }; + addLoopModeAction(tr("Dual-button"), "dual"); + addLoopModeAction(tr("Grab closest"), "closest"); + addLoopModeAction(tr("Handles"), "handles"); + + menu.exec(event->globalPos()); +} } // namespace lmms::gui From 626dc6d8adedf754e867afa78eda3b58fd8903bd Mon Sep 17 00:00:00 2001 From: Dominic Clark Date: Thu, 21 Dec 2023 18:03:30 +0000 Subject: [PATCH 14/18] Tidy up further --- include/TimeLineWidget.h | 13 ++++----- src/gui/editors/TimeLineWidget.cpp | 45 ++++++++---------------------- src/gui/modals/SetupDialog.cpp | 7 ++--- 3 files changed, 18 insertions(+), 47 deletions(-) diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index 0482bafda5a..47518f78904 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -147,10 +147,6 @@ class TimeLineWidget : public QWidget } void setXOffset(const int x); - auto getClickedTime(const QMouseEvent* event) -> TimePos; - auto getClickedTime(int xPosition) -> TimePos; - // Rightmost position visible on timeline (disregards parent editor scrollbar) - auto getEnd() -> TimePos; void addToolButtons(QToolBar* _tool_bar ); @@ -191,7 +187,8 @@ public slots: SelectSongClip, }; - auto getLoopAction(QMouseEvent* event) -> Action; + auto getClickedTime(int xPosition) const -> TimePos; + auto getLoopAction(QMouseEvent* event) const -> Action; auto actionCursor(Action action) const -> QCursor; QPixmap m_posMarkerPixmap = embed::getIconPixmap("playpos_marker"); @@ -199,15 +196,15 @@ public slots: QColor m_inactiveLoopColor; QBrush m_inactiveLoopBrush; QColor m_inactiveLoopInnerColor; - QColor m_inactiveLoopHandleColor; + QColor m_inactiveLoopHandleColor = QColor{255, 255, 255, 32}; QColor m_activeLoopColor; QBrush m_activeLoopBrush; QColor m_activeLoopInnerColor; - QColor m_activeLoopHandleColor; + QColor m_activeLoopHandleColor = QColor{74, 155, 100, 255}; int m_loopRectangleVerticalPadding; - int m_loopHandleWidth; + int m_loopHandleWidth = 5; QColor m_barLineColor; QColor m_barNumberColor; diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index d4accd1ac9d..7b5d124236c 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -54,13 +54,10 @@ TimeLineWidget::TimeLineWidget(const int xoff, const int yoff, const float ppb, m_inactiveLoopColor( 52, 63, 53, 64 ), m_inactiveLoopBrush( QColor( 255, 255, 255, 32 ) ), m_inactiveLoopInnerColor( 255, 255, 255, 32 ), - m_inactiveLoopHandleColor(255, 255, 255, 32), m_activeLoopColor( 52, 63, 53, 255 ), m_activeLoopBrush( QColor( 55, 141, 89 ) ), m_activeLoopInnerColor( 74, 155, 100, 255 ), - m_activeLoopHandleColor(74, 155, 100, 255), m_loopRectangleVerticalPadding( 1 ), - m_loopHandleWidth(5), m_barLineColor( 192, 192, 192 ), m_barNumberColor( m_barLineColor.darker( 120 ) ), m_autoScroll( AutoScrollState::Enabled ), @@ -72,9 +69,8 @@ TimeLineWidget::TimeLineWidget(const int xoff, const int yoff, const float ppb, m_timeline{&timeline}, m_begin( begin ), m_mode( mode ), - m_dragStartPos(0), m_hint( nullptr ), - m_action(Action::NoAction) + m_action( Action::NoAction ) { setAttribute( Qt::WA_OpaquePaintEvent, true ); move( 0, yoff ); @@ -103,26 +99,6 @@ void TimeLineWidget::setXOffset(const int x) m_xOffset = x; } -auto TimeLineWidget::getClickedTime(const QMouseEvent* event) -> TimePos -{ - return getClickedTime(event->x()); -} - - -auto TimeLineWidget::getClickedTime(const int xPosition) -> TimePos -{ - // How far into the timeline we clicked, measuring pixels from the leftmost part of the editor - const auto pixelDelta = std::max(xPosition - m_xOffset, 0); - return m_begin + static_cast(pixelDelta * TimePos::ticksPerBar() / m_ppb); -} - -auto TimeLineWidget::getEnd() -> TimePos -{ - const auto contentWidth = width() - m_xOffset; - const auto ticksPerPixel = TimePos::ticksPerBar() / m_ppb; - return m_begin + (contentWidth * ticksPerPixel); -} - void TimeLineWidget::addToolButtons( QToolBar * _tool_bar ) { auto autoScroll = new NStateButton(_tool_bar); @@ -270,7 +246,14 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) } } -auto TimeLineWidget::getLoopAction(QMouseEvent* event) -> TimeLineWidget::Action +auto TimeLineWidget::getClickedTime(const int xPosition) const -> TimePos +{ + // How far into the timeline we clicked, measuring pixels from the leftmost part of the editor + const auto pixelDelta = std::max(xPosition - m_xOffset, 0); + return m_begin + static_cast(pixelDelta * TimePos::ticksPerBar() / m_ppb); +} + +auto TimeLineWidget::getLoopAction(QMouseEvent* event) const -> TimeLineWidget::Action { const auto mode = ConfigManager::inst()->value("app", "loopmarkermode"); const auto xPos = event->x(); @@ -328,7 +311,7 @@ void TimeLineWidget::mousePressEvent(QMouseEvent* event) if (m_action == Action::MoveLoop) { - m_dragStartPos = getClickedTime(event); + m_dragStartPos = getClickedTime(event->x()); m_oldLoopPos = {m_timeline->loopBegin(), m_timeline->loopEnd()}; } } @@ -355,14 +338,11 @@ void TimeLineWidget::mousePressEvent(QMouseEvent* event) mouseMoveEvent(event); } - - - void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) { parentWidget()->update(); // essential for widgets that this timeline had taken their mouse move event from. - auto timeAtCursor = getClickedTime(event); + auto timeAtCursor = getClickedTime(event->x()); const auto control = event->modifiers() & Qt::ControlModifier; switch( m_action ) @@ -439,9 +419,6 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) } } - - - void TimeLineWidget::mouseReleaseEvent( QMouseEvent* event ) { delete m_hint; diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 1dc2780d7f3..3521d7514e3 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -120,7 +120,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : "app", "disablebackup").toInt()), m_openLastProject(ConfigManager::inst()->value( "app", "openlastproject").toInt()), - m_loopMarkerMode(ConfigManager::inst()->value("app", "loopmarkermode")), + m_loopMarkerMode{ConfigManager::inst()->value("app", "loopmarkermode", "dual")}, m_lang(ConfigManager::inst()->value( "app", "language")), m_saveInterval( ConfigManager::inst()->value( @@ -262,10 +262,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : m_loopMarkerComboBox->addItem(tr("Grab closest"), "closest"); m_loopMarkerComboBox->addItem(tr("Handles"), "handles"); - if (const auto index = m_loopMarkerComboBox->findData(m_loopMarkerMode); index != -1) { - m_loopMarkerComboBox->setCurrentIndex(index); - } - + m_loopMarkerComboBox->setCurrentIndex(m_loopMarkerComboBox->findData(m_loopMarkerMode)); connect(m_loopMarkerComboBox, qOverload(&QComboBox::currentIndexChanged), this, &SetupDialog::loopMarkerModeChanged); From 5b4351202d6d16fc52772052b430de7c78fd216a Mon Sep 17 00:00:00 2001 From: Dominic Clark Date: Fri, 22 Dec 2023 14:06:16 +0000 Subject: [PATCH 15/18] Address review feedback --- include/TimeLineWidget.h | 28 +++++++++++++-------------- src/gui/editors/TimeLineWidget.cpp | 31 ++++++++---------------------- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index 47518f78904..5c683cfd9f9 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -193,33 +193,31 @@ public slots: QPixmap m_posMarkerPixmap = embed::getIconPixmap("playpos_marker"); - QColor m_inactiveLoopColor; - QBrush m_inactiveLoopBrush; - QColor m_inactiveLoopInnerColor; + QColor m_inactiveLoopColor = QColor{52, 63, 53, 64}; + QBrush m_inactiveLoopBrush = QColor{255, 255, 255, 32}; + QColor m_inactiveLoopInnerColor = QColor{255, 255, 255, 32}; QColor m_inactiveLoopHandleColor = QColor{255, 255, 255, 32}; - QColor m_activeLoopColor; - QBrush m_activeLoopBrush; - QColor m_activeLoopInnerColor; + QColor m_activeLoopColor = QColor{52, 63, 53, 255}; + QBrush m_activeLoopBrush = QColor{55, 141, 89}; + QColor m_activeLoopInnerColor = QColor{74, 155, 100, 255}; QColor m_activeLoopHandleColor = QColor{74, 155, 100, 255}; - int m_loopRectangleVerticalPadding; + int m_loopRectangleVerticalPadding = 1; int m_loopHandleWidth = 5; - QColor m_barLineColor; - QColor m_barNumberColor; + QColor m_barLineColor = QColor{192, 192, 192}; + QColor m_barNumberColor = m_barLineColor.darker(120); QCursor m_cursorSelectLeft = QCursor{embed::getIconPixmap("cursor_select_left"), 0, 16}; QCursor m_cursorSelectRight = QCursor{embed::getIconPixmap("cursor_select_right"), 32, 16}; - AutoScrollState m_autoScroll; - - bool m_changedPosition; + AutoScrollState m_autoScroll = AutoScrollState::Enabled; // Width of the unused region on the widget's left (above track labels or piano) int m_xOffset; float m_ppb; - float m_snapSize; + float m_snapSize = 1.f; Song::PlayPos & m_pos; Timeline* m_timeline; // Leftmost position visible in parent editor @@ -230,10 +228,10 @@ public slots: std::array m_oldLoopPos; TimePos m_dragStartPos; - TextFloat * m_hint; + TextFloat* m_hint = nullptr; int m_initalXSelect; - Action m_action; + Action m_action = Action::NoAction; }; } // namespace lmms::gui diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index 7b5d124236c..1a4bd92df4d 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -50,27 +50,13 @@ namespace TimeLineWidget::TimeLineWidget(const int xoff, const int yoff, const float ppb, Song::PlayPos& pos, Timeline& timeline, const TimePos& begin, Song::PlayMode mode, QWidget* parent) : - QWidget( parent ), - m_inactiveLoopColor( 52, 63, 53, 64 ), - m_inactiveLoopBrush( QColor( 255, 255, 255, 32 ) ), - m_inactiveLoopInnerColor( 255, 255, 255, 32 ), - m_activeLoopColor( 52, 63, 53, 255 ), - m_activeLoopBrush( QColor( 55, 141, 89 ) ), - m_activeLoopInnerColor( 74, 155, 100, 255 ), - m_loopRectangleVerticalPadding( 1 ), - m_barLineColor( 192, 192, 192 ), - m_barNumberColor( m_barLineColor.darker( 120 ) ), - m_autoScroll( AutoScrollState::Enabled ), - m_changedPosition( true ), - m_xOffset( xoff ), - m_ppb( ppb ), - m_snapSize( 1.0 ), - m_pos( pos ), + QWidget{parent}, + m_xOffset{xoff}, + m_ppb{ppb}, + m_pos{pos}, m_timeline{&timeline}, - m_begin( begin ), - m_mode( mode ), - m_hint( nullptr ), - m_action( Action::NoAction ) + m_begin{begin}, + m_mode{mode} { setAttribute( Qt::WA_OpaquePaintEvent, true ); move( 0, yoff ); @@ -146,7 +132,6 @@ void TimeLineWidget::addToolButtons( QToolBar * _tool_bar ) void TimeLineWidget::updatePosition() { - m_changedPosition = true; emit positionChanged(m_pos); update(); } @@ -230,7 +215,7 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) const auto rightHandle = QRectF(loopEndR - hw - .5, loopRectMargin - .5, hw, loopRectHeight); if (handleMode && underMouse() && QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) { - auto color = loopPointsActive ? m_activeLoopHandleColor : m_inactiveLoopHandleColor; + const auto color = loopPointsActive ? m_activeLoopHandleColor : m_inactiveLoopHandleColor; p.fillRect(leftHandle, color); p.fillRect(rightHandle, color); } @@ -392,7 +377,7 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) } case Action::MoveLoop: { - TimePos dragDelta = timeAtCursor - m_dragStartPos; + const TimePos dragDelta = timeAtCursor - m_dragStartPos; auto loopPos = m_oldLoopPos; for (auto& point : loopPos) { From 70db42b8485240b09208d4b469a410c48ec7f4a5 Mon Sep 17 00:00:00 2001 From: Dominic Clark Date: Fri, 22 Dec 2023 14:30:36 +0000 Subject: [PATCH 16/18] Rename loop edit mode option --- src/gui/editors/TimeLineWidget.cpp | 2 +- src/gui/modals/SetupDialog.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index 1a4bd92df4d..6ff98ee83f1 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -437,7 +437,7 @@ void TimeLineWidget::contextMenuEvent(QContextMenuEvent* event) menu.addSeparator(); - const auto loopMenu = menu.addMenu(tr("Loop marker edit mode")); + const auto loopMenu = menu.addMenu(tr("Loop edit mode (hold shift)")); const auto loopMode = ConfigManager::inst()->value("app", "loopmarkermode", "dual"); const auto addLoopModeAction = [loopMenu, &loopMode](QString text, QString mode) { const auto action = loopMenu->addAction(text, [mode] { diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 3521d7514e3..fffa94c82ba 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -266,7 +266,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : connect(m_loopMarkerComboBox, qOverload(&QComboBox::currentIndexChanged), this, &SetupDialog::loopMarkerModeChanged); - guiGroupLayout->addWidget(new QLabel{tr("Loop marker edit mode"), guiGroupBox}); + guiGroupLayout->addWidget(new QLabel{tr("Loop edit mode"), guiGroupBox}); guiGroupLayout->addWidget(m_loopMarkerComboBox); generalControlsLayout->addWidget(guiGroupBox); From c8ab1e63092880f1422d719cbd256a9ab05e4f28 Mon Sep 17 00:00:00 2001 From: Dominic Clark Date: Fri, 22 Dec 2023 14:45:00 +0000 Subject: [PATCH 17/18] Add missing `tr` calls --- src/gui/editors/TimeLineWidget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index 6ff98ee83f1..5dab33582ac 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -418,7 +418,7 @@ void TimeLineWidget::contextMenuEvent(QContextMenuEvent* event) auto menu = QMenu{}; - menu.addAction("Set loop begin here", [this, event] { + menu.addAction(tr("Set loop begin here"), [this, event] { auto begin = getClickedTime(event->x()); const auto end = m_timeline->loopEnd(); if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) { begin = begin.quantize(m_snapSize); } @@ -426,7 +426,7 @@ void TimeLineWidget::contextMenuEvent(QContextMenuEvent* event) m_timeline->setLoopBegin(begin); update(); }); - menu.addAction("Set loop end here", [this, event] { + menu.addAction(tr("Set loop end here"), [this, event] { const auto begin = m_timeline->loopBegin(); auto end = getClickedTime(event->x()); if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) { end = end.quantize(m_snapSize); } From 9ac308020b2e7af73fb4bd7bd03aaa0f29cbb72d Mon Sep 17 00:00:00 2001 From: Dominic Clark Date: Fri, 22 Dec 2023 16:32:19 +0000 Subject: [PATCH 18/18] Scope variables more tightly --- src/gui/editors/TimeLineWidget.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index 5dab33582ac..7657e2916cd 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -210,12 +210,13 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) // Draw loop handles if necessary const auto handleMode = ConfigManager::inst()->value("app", "loopmarkermode") == "handles"; - const auto hw = std::min(m_loopHandleWidth, loopRectWidth / 2 - 1); - const auto leftHandle = QRectF(loopStart - .5, loopRectMargin - .5, hw, loopRectHeight); - const auto rightHandle = QRectF(loopEndR - hw - .5, loopRectMargin - .5, hw, loopRectHeight); if (handleMode && underMouse() && QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) { + const auto handleWidth = std::min(m_loopHandleWidth, loopRectWidth / 2 - 1); + const auto leftHandle = QRectF(loopStart - .5, loopRectMargin - .5, handleWidth, loopRectHeight); + const auto rightHandle = QRectF(loopEndR - handleWidth - .5, loopRectMargin - .5, handleWidth, loopRectHeight); const auto color = loopPointsActive ? m_activeLoopHandleColor : m_inactiveLoopHandleColor; + p.fillRect(leftHandle, color); p.fillRect(rightHandle, color); }