/* * TimeLineWidget.cpp - class timeLine, representing a time-line with position marker * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include "TimeLineWidget.h" #include "embed.h" #include "Engine.h" #include "templates.h" #include "NStateButton.h" #include "GuiApplication.h" #include "TextFloat.h" #include "SongEditor.h" #if QT_VERSION < 0x040800 #define MiddleButton MidButton #endif QPixmap * TimeLineWidget::s_posMarkerPixmap = NULL; TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppt, Song::PlayPos & pos, const MidiTime & begin, 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( AutoScrollEnabled ), m_loopPoints( LoopPointsDisabled ), m_behaviourAtStop( BackToZero ), m_changedPosition( true ), m_xOffset( xoff ), m_posMarkerX( 0 ), m_ppt( ppt ), m_pos( pos ), m_begin( begin ), m_savedPos( -1 ), m_hint( NULL ), m_action( NoAction ), m_moveXOff( 0 ) { m_loopPos[0] = 0; m_loopPos[1] = DefaultTicksPerTact; if( s_posMarkerPixmap == NULL ) { s_posMarkerPixmap = new QPixmap( embed::getIconPixmap( "playpos_marker" ) ); } setAttribute( Qt::WA_OpaquePaintEvent, true ); move( 0, yoff ); m_xOffset -= s_posMarkerPixmap->width() / 2; m_pos.m_timeLine = this; QTimer * updateTimer = new QTimer( this ); connect( updateTimer, SIGNAL( timeout() ), this, SLOT( updatePosition() ) ); updateTimer->start( 50 ); } TimeLineWidget::~TimeLineWidget() { if( gui->songEditor() ) { m_pos.m_timeLine = NULL; } delete m_hint; } void TimeLineWidget::addToolButtons( QToolBar * _tool_bar ) { NStateButton * autoScroll = new NStateButton( _tool_bar ); autoScroll->setGeneralToolTip( tr( "Enable/disable auto-scrolling" ) ); autoScroll->addState( embed::getIconPixmap( "autoscroll_on" ) ); autoScroll->addState( embed::getIconPixmap( "autoscroll_off" ) ); connect( autoScroll, SIGNAL( changedState( int ) ), this, SLOT( toggleAutoScroll( int ) ) ); NStateButton * loopPoints = new NStateButton( _tool_bar ); loopPoints->setGeneralToolTip( tr( "Enable/disable loop-points" ) ); loopPoints->addState( embed::getIconPixmap( "loop_points_off" ) ); loopPoints->addState( embed::getIconPixmap( "loop_points_on" ) ); connect( loopPoints, SIGNAL( changedState( int ) ), this, SLOT( toggleLoopPoints( int ) ) ); connect( this, SIGNAL( loopPointStateLoaded( int ) ), loopPoints, SLOT( changeState( int ) ) ); NStateButton * behaviourAtStop = new NStateButton( _tool_bar ); behaviourAtStop->addState( embed::getIconPixmap( "back_to_zero" ), tr( "After stopping go back to begin" ) ); behaviourAtStop->addState( embed::getIconPixmap( "back_to_start" ), tr( "After stopping go back to " "position at which playing was " "started" ) ); behaviourAtStop->addState( embed::getIconPixmap( "keep_stop_position" ), tr( "After stopping keep position" ) ); connect( behaviourAtStop, SIGNAL( changedState( int ) ), this, SLOT( toggleBehaviourAtStop( int ) ) ); _tool_bar->addWidget( autoScroll ); _tool_bar->addWidget( loopPoints ); _tool_bar->addWidget( behaviourAtStop ); } void TimeLineWidget::saveSettings( QDomDocument & _doc, QDomElement & _this ) { _this.setAttribute( "lp0pos", (int) loopBegin() ); _this.setAttribute( "lp1pos", (int) loopEnd() ); _this.setAttribute( "lpstate", m_loopPoints ); } void TimeLineWidget::loadSettings( const QDomElement & _this ) { m_loopPos[0] = _this.attribute( "lp0pos" ).toInt(); m_loopPos[1] = _this.attribute( "lp1pos" ).toInt(); m_loopPoints = static_cast( _this.attribute( "lpstate" ).toInt() ); update(); emit loopPointStateLoaded( m_loopPoints ); } void TimeLineWidget::updatePosition( const MidiTime & ) { const int new_x = markerX( m_pos ); if( new_x != m_posMarkerX ) { m_posMarkerX = new_x; m_changedPosition = true; emit positionChanged( m_pos ); update(); } } void TimeLineWidget::toggleAutoScroll( int _n ) { m_autoScroll = static_cast( _n ); } void TimeLineWidget::toggleLoopPoints( int _n ) { m_loopPoints = static_cast( _n ); update(); } void TimeLineWidget::toggleBehaviourAtStop( int _n ) { m_behaviourAtStop = static_cast( _n ); } void TimeLineWidget::paintEvent( QPaintEvent * ) { QPainter p( this ); // Draw background 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() ); // Draw 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 loopRectWidth = loopEndR - loopStart; bool const loopPointsActive = loopPointsEnabled(); // Draw the main rectangle (inner fill only) QRect outerRectangle( loopStart, loopRectMargin, loopRectWidth - 1, loopRectHeight - 1 ); p.fillRect( outerRectangle, loopPointsActive ? getActiveLoopBrush() : getInactiveLoopBrush()); // Draw the bar lines and numbers // Activate hinting on the font QFont font = p.font(); font.setHintingPreference( QFont::PreferFullHinting ); p.setFont(font); int const fontAscent = p.fontMetrics().ascent(); int const fontHeight = p.fontMetrics().height(); QColor const & barLineColor = getBarLineColor(); QColor const & barNumberColor = getBarNumberColor(); tact_t barNumber = m_begin.getTact(); int const x = m_xOffset + s_posMarkerPixmap->width() / 2 - ( ( static_cast( m_begin * m_ppt ) / MidiTime::ticksPerTact() ) % static_cast( m_ppt ) ); for( int i = 0; x + i * m_ppt < width(); ++i ) { ++barNumber; if( ( barNumber - 1 ) % qMax( 1, qRound( 1.0f / 3.0f * MidiTime::ticksPerTact() / m_ppt ) ) == 0 ) { const int cx = x + qRound( i * m_ppt ); p.setPen( barLineColor ); p.drawLine( cx, 5, cx, height() - 6 ); const QString s = QString::number( barNumber ); p.setPen( barNumberColor ); p.drawText( cx + 5, ((height() - fontHeight) / 2) + fontAscent, s ); } } // Draw the main rectangle (outer border) p.setPen( loopPointsActive ? getActiveLoopColor() : getInactiveLoopColor() ); p.setBrush( Qt::NoBrush ); p.drawRect( outerRectangle ); // Draw the inner border outline (no fill) QRect innerRectangle = outerRectangle.adjusted( 1, 1, -1, -1 ); p.setPen( loopPointsActive ? getActiveLoopInnerColor() : getInactiveLoopInnerColor() ); p.setBrush( Qt::NoBrush ); p.drawRect( innerRectangle ); // Draw the position marker p.setOpacity( 0.6 ); p.drawPixmap( m_posMarkerX, height() - s_posMarkerPixmap->height(), *s_posMarkerPixmap ); } void TimeLineWidget::mousePressEvent( QMouseEvent* event ) { if( event->x() < m_xOffset ) { return; } if( event->button() == Qt::LeftButton && !(event->modifiers() & Qt::ShiftModifier) ) { 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::LeftButton && (event->modifiers() & Qt::ShiftModifier) ) { m_action = SelectSongTCO; m_initalXSelect = event->x(); } else if( event->button() == Qt::RightButton || event->button() == Qt::MiddleButton ) { m_moveXOff = s_posMarkerPixmap->width() / 2; const MidiTime t = m_begin + static_cast( event->x() * MidiTime::ticksPerTact() / m_ppt ); if( m_loopPos[0] > m_loopPos[1] ) { qSwap( m_loopPos[0], m_loopPos[1] ); } if( ( event->modifiers() & Qt::ShiftModifier ) || event->button() == Qt::MiddleButton ) { m_action = MoveLoopBegin; } else { m_action = MoveLoopEnd; } m_loopPos[( m_action == MoveLoopBegin ) ? 0 : 1] = t; } if( m_action == MoveLoopBegin ) { delete m_hint; m_hint = TextFloat::displayMessage( tr( "Hint" ), tr( "Press <%1> to disable magnetic loop points." ).arg( #ifdef LMMS_BUILD_APPLE "⌘"), #else "Ctrl"), #endif embed::getIconPixmap( "hint" ), 0 ); } else if( m_action == MoveLoopEnd ) { delete m_hint; m_hint = TextFloat::displayMessage( tr( "Hint" ), tr( "Hold to move the begin loop point; Press <%1> to disable magnetic loop points." ).arg( #ifdef LMMS_BUILD_APPLE "⌘"), #else "Ctrl"), #endif embed::getIconPixmap( "hint" ), 0 ); } mouseMoveEvent( event ); } void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) { const MidiTime t = m_begin + static_cast( qMax( event->x() - m_xOffset - m_moveXOff, 0 ) * MidiTime::ticksPerTact() / m_ppt ); switch( m_action ) { case MovePositionMarker: m_pos.setTicks( t.getTicks() ); Engine::getSong()->setMilliSeconds(((((t.getTicks()))*60*1000/48)/Engine::getSong()->getTempo())); m_pos.setCurrentFrame( 0 ); updatePosition(); positionMarkerMoved(); break; case MoveLoopBegin: case MoveLoopEnd: { const int i = m_action - MoveLoopBegin; if( event->modifiers() & Qt::ControlModifier ) { // no ctrl-press-hint when having ctrl pressed delete m_hint; m_hint = NULL; m_loopPos[i] = t; } else { m_loopPos[i] = t.toNearestTact(); } // Catch begin == end if( m_loopPos[0] == m_loopPos[1] ) { // Note, swap 1 and 0 below and the behavior "skips" the other // marking instead of pushing it. if( m_action == MoveLoopBegin ) m_loopPos[0] -= MidiTime::ticksPerTact(); else m_loopPos[1] += MidiTime::ticksPerTact(); } update(); break; } case SelectSongTCO: emit regionSelectedFromPixels( m_initalXSelect , event->x() ); break; default: break; } } void TimeLineWidget::mouseReleaseEvent( QMouseEvent* event ) { delete m_hint; m_hint = NULL; if ( m_action == SelectSongTCO ) { emit selectionFinished(); } m_action = NoAction; }