/* * AutomationEditor.cpp - implementation of AutomationEditor which is used for * actual setting of dynamic values * * Copyright (c) 2008-2013 Tobias Doerffel * Copyright (c) 2008-2013 Paul Giblock * Copyright (c) 2006-2008 Javier Serrano Polo * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * 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 "AutomationEditor.h" #include #include #include #include #include #include #include #include #include #include #include #ifndef __USE_XOPEN #define __USE_XOPEN #endif #include #include "song_editor.h" #include "MainWindow.h" #include "embed.h" #include "engine.h" #include "pixmap_button.h" #include "templates.h" #include "gui_templates.h" #include "timeline.h" #include "tooltip.h" #include "midi.h" #include "tool_button.h" #include "text_float.h" #include "combobox.h" #include "bb_track_container.h" #include "piano_roll.h" #include "debug.h" QPixmap * AutomationEditor::s_toolDraw = NULL; QPixmap * AutomationEditor::s_toolErase = NULL; QPixmap * AutomationEditor::s_toolSelect = NULL; QPixmap * AutomationEditor::s_toolMove = NULL; AutomationEditor::AutomationEditor() : QWidget(), m_zoomingXModel(), m_zoomingYModel(), m_quantizeModel(), m_patternMutex( QMutex::Recursive ), m_pattern( NULL ), m_minLevel( 0 ), m_maxLevel( 0 ), m_step( 1 ), m_scrollLevel( 0 ), m_bottomLevel( 0 ), m_topLevel( 0 ), m_currentPosition(), m_action( NONE ), m_moveStartLevel( 0 ), m_moveStartTick( 0 ), m_drawLastLevel( 0.0f ), m_drawLastTick( 0 ), m_ppt( DEFAULT_PPT ), m_y_delta( DEFAULT_Y_DELTA ), m_y_auto( TRUE ), m_editMode( DRAW ), m_scrollBack( FALSE ) { connect( this, SIGNAL( currentPatternChanged() ), this, SLOT( updateAfterPatternChange() ), Qt::QueuedConnection ); // init pixmaps if( s_toolDraw == NULL ) { s_toolDraw = new QPixmap( embed::getIconPixmap( "edit_draw" ) ); } if( s_toolErase == NULL ) { s_toolErase= new QPixmap( embed::getIconPixmap( "edit_erase" ) ); } if( s_toolSelect == NULL ) { s_toolSelect = new QPixmap( embed::getIconPixmap( "edit_select" ) ); } if( s_toolMove == NULL ) { s_toolMove = new QPixmap( embed::getIconPixmap( "edit_move" ) ); } setAttribute( Qt::WA_OpaquePaintEvent, true ); // add time-line m_timeLine = new timeLine( VALUES_WIDTH, 32, m_ppt, engine::getSong()->getPlayPos( song::Mode_PlayAutomationPattern ), m_currentPosition, this ); connect( this, SIGNAL( positionChanged( const midiTime & ) ), m_timeLine, SLOT( updatePosition( const midiTime & ) ) ); connect( m_timeLine, SIGNAL( positionChanged( const midiTime & ) ), this, SLOT( updatePosition( const midiTime & ) ) ); m_toolBar = new QWidget( this ); m_toolBar->setFixedHeight( 32 ); m_toolBar->move( 0, 0 ); m_toolBar->setAutoFillBackground( TRUE ); QPalette pal; pal.setBrush( m_toolBar->backgroundRole(), embed::getIconPixmap( "toolbar_bg" ) ); m_toolBar->setPalette( pal ); QHBoxLayout * tb_layout = new QHBoxLayout( m_toolBar ); tb_layout->setMargin( 0 ); tb_layout->setSpacing( 0 ); // init control-buttons at the top m_playButton = new toolButton( embed::getIconPixmap( "play" ), tr( "Play/pause current pattern (Space)" ), this, SLOT( play() ), m_toolBar ); m_stopButton = new toolButton( embed::getIconPixmap( "stop" ), tr( "Stop playing of current pattern (Space)" ), this, SLOT( stop() ), m_toolBar ); m_playButton->setWhatsThis( tr( "Click here if you want to play the current pattern. " "This is useful while editing it. The pattern is " "automatically looped when the end is reached." ) ); m_stopButton->setWhatsThis( tr( "Click here if you want to stop playing of the " "current pattern." ) ); removeSelection(); // init scrollbars m_leftRightScroll = new QScrollBar( Qt::Horizontal, this ); m_leftRightScroll->setSingleStep( 1 ); connect( m_leftRightScroll, SIGNAL( valueChanged( int ) ), this, SLOT( horScrolled( int ) ) ); m_topBottomScroll = new QScrollBar( Qt::Vertical, this ); m_topBottomScroll->setSingleStep( 1 ); m_topBottomScroll->setPageStep( 20 ); connect( m_topBottomScroll, SIGNAL( valueChanged( int ) ), this, SLOT( verScrolled( int ) ) ); // init edit-buttons at the top m_drawButton = new toolButton( embed::getIconPixmap( "edit_draw" ), tr( "Draw mode (Shift+D)" ), this, SLOT( drawButtonToggled() ), m_toolBar ); m_drawButton->setCheckable( TRUE ); m_drawButton->setChecked( TRUE ); m_eraseButton = new toolButton( embed::getIconPixmap( "edit_erase" ), tr( "Erase mode (Shift+E)" ), this, SLOT( eraseButtonToggled() ), m_toolBar ); m_eraseButton->setCheckable( TRUE ); m_selectButton = new toolButton( embed::getIconPixmap( "edit_select" ), tr( "Select mode (Shift+S)" ), this, SLOT( selectButtonToggled() ), m_toolBar ); m_selectButton->setCheckable( TRUE ); m_moveButton = new toolButton( embed::getIconPixmap( "edit_move" ), tr( "Move selection mode (Shift+M)" ), this, SLOT( moveButtonToggled() ), m_toolBar ); m_moveButton->setCheckable( TRUE ); QButtonGroup * tool_button_group = new QButtonGroup( this ); tool_button_group->addButton( m_drawButton ); tool_button_group->addButton( m_eraseButton ); tool_button_group->addButton( m_selectButton ); tool_button_group->addButton( m_moveButton ); tool_button_group->setExclusive( TRUE ); m_drawButton->setWhatsThis( tr( "Click here and draw-mode will be activated. In this " "mode you can add and move single values. This " "is the default mode which is used most of the time. " "You can also press 'Shift+D' on your keyboard to " "activate this mode." ) ); m_eraseButton->setWhatsThis( tr( "Click here and erase-mode will be activated. In this " "mode you can erase single values. You can also press " "'Shift+E' on your keyboard to activate this mode." ) ); m_selectButton->setWhatsThis( tr( "Click here and select-mode will be activated. In this " "mode you can select values. This is necessary " "if you want to cut, copy, paste, delete, or move " "values. You can also press 'Shift+S' on your keyboard " "to activate this mode." ) ); m_moveButton->setWhatsThis( tr( "If you click here, move-mode will be activated. In this " "mode you can move the values you selected in select-" "mode. You can also press 'Shift+M' on your keyboard " "to activate this mode." ) ); m_discreteButton = new toolButton( embed::getIconPixmap( "progression_discrete" ), tr( "Discrete progression" ), this, SLOT( discreteButtonToggled() ), m_toolBar ); m_discreteButton->setCheckable( true ); m_discreteButton->setChecked( true ); m_linearButton = new toolButton( embed::getIconPixmap( "progression_linear" ), tr( "Linear progression" ), this, SLOT( linearButtonToggled() ), m_toolBar ); m_linearButton->setCheckable( true ); m_cubicHermiteButton = new toolButton( embed::getIconPixmap( "progression_cubic_hermite" ), tr( "Cubic Hermite progression" ), this, SLOT( cubicHermiteButtonToggled() ), m_toolBar ); m_cubicHermiteButton->setCheckable( true ); // setup tension-stuff m_tensionComboBox = new comboBox( m_toolBar ); m_tensionComboBox->setFixedSize( 60, 22 ); for( int i = 0; i < 4; ++i ) { m_tensionModel.addItem( "0." + QString::number( 25 * i ) ); } m_tensionModel.addItem( "1.0" ); m_tensionModel.setValue( m_tensionModel.findText( "1.0" ) ); m_tensionDisabledModel.addItem( "-----" ); disableTensionComboBox(); connect( &m_tensionModel, SIGNAL( dataChanged() ), this, SLOT( tensionChanged() ) ); tool_button_group = new QButtonGroup( this ); tool_button_group->addButton( m_discreteButton ); tool_button_group->addButton( m_linearButton ); tool_button_group->addButton( m_cubicHermiteButton ); tool_button_group->setExclusive( true ); m_discreteButton->setWhatsThis( tr( "Click here to choose discrete progressions for this " "automation pattern. The value of the connected " "object will remain constant between control points " "and be set immediately to the new value when each " "control point is reached." ) ); m_linearButton->setWhatsThis( tr( "Click here to choose linear progressions for this " "automation pattern. The value of the connected " "object will change at a steady rate over time " "between control points to reach the correct value at " "each control point without a sudden change." ) ); m_cubicHermiteButton->setWhatsThis( tr( "Click here to choose cubic hermite progressions for this " "automation pattern. The value of the connected " "object will change in a smooth curve and ease in to " "the peaks and valleys." ) ); m_cutButton = new toolButton( embed::getIconPixmap( "edit_cut" ), tr( "Cut selected values (Ctrl+X)" ), this, SLOT( cutSelectedValues() ), m_toolBar ); m_copyButton = new toolButton( embed::getIconPixmap( "edit_copy" ), tr( "Copy selected values (Ctrl+C)" ), this, SLOT( copySelectedValues() ), m_toolBar ); m_pasteButton = new toolButton( embed::getIconPixmap( "edit_paste" ), tr( "Paste values from clipboard " "(Ctrl+V)" ), this, SLOT( pasteValues() ), m_toolBar ); m_cutButton->setWhatsThis( tr( "Click here and selected values will be cut into the " "clipboard. You can paste them anywhere in any pattern " "by clicking on the paste button." ) ); m_copyButton->setWhatsThis( tr( "Click here and selected values will be copied into " "the clipboard. You can paste them anywhere in any " "pattern by clicking on the paste button." ) ); m_pasteButton->setWhatsThis( tr( "Click here and the values from the clipboard will be " "pasted at the first visible measure." ) ); // setup zooming-stuff QLabel * zoom_x_lbl = new QLabel( m_toolBar ); zoom_x_lbl->setPixmap( embed::getIconPixmap( "zoom_x" ) ); m_zoomingXComboBox = new comboBox( m_toolBar ); m_zoomingXComboBox->setFixedSize( 80, 22 ); for( int i = 0; i < 6; ++i ) { m_zoomingXModel.addItem( QString::number( 25 << i ) + "%" ); } m_zoomingXModel.setValue( m_zoomingXModel.findText( "100%" ) ); m_zoomingXComboBox->setModel( &m_zoomingXModel ); connect( &m_zoomingXModel, SIGNAL( dataChanged() ), this, SLOT( zoomingXChanged() ) ); QLabel * zoom_y_lbl = new QLabel( m_toolBar ); zoom_y_lbl->setPixmap( embed::getIconPixmap( "zoom_y" ) ); m_zoomingYComboBox = new comboBox( m_toolBar ); m_zoomingYComboBox->setFixedSize( 80, 22 ); m_zoomingYModel.addItem( "Auto" ); for( int i = 0; i < 6; ++i ) { m_zoomingYModel.addItem( QString::number( 25 << i ) + "%" ); } m_zoomingYModel.setValue( m_zoomingYModel.findText( "Auto" ) ); m_zoomingYComboBox->setModel( &m_zoomingYModel ); connect( &m_zoomingYModel, SIGNAL( dataChanged() ), this, SLOT( zoomingYChanged() ) ); // setup quantize-stuff QLabel * quantize_lbl = new QLabel( m_toolBar ); quantize_lbl->setPixmap( embed::getIconPixmap( "quantize" ) ); m_quantizeComboBox = new comboBox( m_toolBar ); m_quantizeComboBox->setFixedSize( 60, 22 ); // TODO: leak ComboBoxModel * quantize_model = new ComboBoxModel( /* this */ ); for( int i = 0; i < 7; ++i ) { quantize_model->addItem( "1/" + QString::number( 1 << i ) ); } quantize_model->setValue( quantize_model->findText( "1/16" ) ); m_quantizeComboBox->setModel( quantize_model ); tb_layout->addSpacing( 5 ); tb_layout->addWidget( m_playButton ); tb_layout->addWidget( m_stopButton ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( m_drawButton ); tb_layout->addWidget( m_eraseButton ); tb_layout->addWidget( m_selectButton ); tb_layout->addWidget( m_moveButton ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( m_discreteButton ); tb_layout->addWidget( m_linearButton ); tb_layout->addWidget( m_cubicHermiteButton ); tb_layout->addSpacing( 5 ); tb_layout->addWidget( m_tensionComboBox ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( m_cutButton ); tb_layout->addWidget( m_copyButton ); tb_layout->addWidget( m_pasteButton ); tb_layout->addSpacing( 10 ); m_timeLine->addToolButtons( m_toolBar ); tb_layout->addSpacing( 15 ); tb_layout->addWidget( zoom_x_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_zoomingXComboBox ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( zoom_y_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_zoomingYComboBox ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( quantize_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_quantizeComboBox ); tb_layout->addStretch(); // setup our actual window setFocusPolicy( Qt::StrongFocus ); setFocus(); setWindowIcon( embed::getIconPixmap( "automation" ) ); setCurrentPattern( NULL ); setMouseTracking( TRUE ); setMinimumSize( tb_layout->minimumSize().width(), 128 ); // add us to workspace if( engine::mainWindow()->workspace() ) { engine::mainWindow()->workspace()->addSubWindow( this ); parentWidget()->resize( INITIAL_WIDTH, INITIAL_HEIGHT ); parentWidget()->hide(); } else { resize( INITIAL_WIDTH, INITIAL_HEIGHT ); hide(); } } AutomationEditor::~AutomationEditor() { m_zoomingXModel.disconnect(); m_zoomingYModel.disconnect(); m_tensionModel.disconnect(); } void AutomationEditor::setCurrentPattern( AutomationPattern * _new_pattern ) { m_patternMutex.lock(); m_pattern = _new_pattern; m_patternMutex.unlock(); emit currentPatternChanged(); } void AutomationEditor::saveSettings( QDomDocument & _doc, QDomElement & _this ) { MainWindow::saveWidgetState( this, _this ); } void AutomationEditor::loadSettings( const QDomElement & _this ) { MainWindow::restoreWidgetState( this, _this ); } void AutomationEditor::updatePlayPauseIcon() { if( engine::getSong()->playMode() != song::Mode_PlayPattern ) { m_playButton->setIcon( embed::getIconPixmap( "play" ) ); } else { if( engine::getSong()->isPlaying() ) { m_playButton->setIcon( embed::getIconPixmap( "pause" ) ); } else { m_playButton->setIcon( embed::getIconPixmap( "play" ) ); } } } void AutomationEditor::updateAfterPatternChange() { QMutexLocker m( &m_patternMutex ); m_currentPosition = 0; if( !validPattern() ) { setWindowTitle( tr( "Automation Editor - no pattern" ) ); m_minLevel = m_maxLevel = m_scrollLevel = 0; m_step = 1; resizeEvent( NULL ); return; } if( m_pattern->progressionType() == AutomationPattern::DiscreteProgression && !m_discreteButton->isChecked() ) { m_discreteButton->setChecked( true ); } if( m_pattern->progressionType() == AutomationPattern::LinearProgression && !m_linearButton->isChecked() ) { m_linearButton->setChecked( true ); } if( m_pattern->progressionType() == AutomationPattern::CubicHermiteProgression && !m_cubicHermiteButton->isChecked() ) { m_cubicHermiteButton->setChecked( true ); } m_tensionModel.setValue( m_tensionModel.findText( m_pattern->getTension() ) ); m_minLevel = m_pattern->firstObject()->minValue(); m_maxLevel = m_pattern->firstObject()->maxValue(); m_step = m_pattern->firstObject()->step(); m_scrollLevel = ( m_minLevel + m_maxLevel ) / 2; // resizeEvent() does the rest for us (scrolling, range-checking // of levels and so on...) resizeEvent( NULL ); setWindowTitle( tr( "Automation Editor - %1" ).arg( m_pattern->name() ) ); update(); } void AutomationEditor::update() { QWidget::update(); QMutexLocker m( &m_patternMutex ); // Note detuning? if( m_pattern && !m_pattern->getTrack() ) { engine::getPianoRoll()->update(); } } void AutomationEditor::removeSelection() { m_selectStartTick = 0; m_selectedTick = 0; m_selectStartLevel = 0; m_selectedLevels = 0; } void AutomationEditor::closeEvent( QCloseEvent * _ce ) { QApplication::restoreOverrideCursor(); if( parentWidget() ) { parentWidget()->hide(); } else { hide(); } _ce->ignore(); } void AutomationEditor::keyPressEvent( QKeyEvent * _ke ) { switch( _ke->key() ) { case Qt::Key_Up: m_topBottomScroll->setValue( m_topBottomScroll->value() - 1 ); _ke->accept(); break; case Qt::Key_Down: m_topBottomScroll->setValue( m_topBottomScroll->value() + 1 ); _ke->accept(); break; case Qt::Key_Left: if( ( m_timeLine->pos() -= 16 ) < 0 ) { m_timeLine->pos().setTicks( 0 ); } m_timeLine->updatePosition(); _ke->accept(); break; case Qt::Key_Right: m_timeLine->pos() += 16; m_timeLine->updatePosition(); _ke->accept(); break; case Qt::Key_C: if( _ke->modifiers() & Qt::ControlModifier ) { copySelectedValues(); _ke->accept(); } break; case Qt::Key_X: if( _ke->modifiers() & Qt::ControlModifier ) { cutSelectedValues(); _ke->accept(); } break; case Qt::Key_V: if( _ke->modifiers() & Qt::ControlModifier ) { pasteValues(); _ke->accept(); } break; case Qt::Key_A: if( _ke->modifiers() & Qt::ControlModifier ) { m_selectButton->setChecked( TRUE ); selectAll(); update(); _ke->accept(); } break; case Qt::Key_D: if( _ke->modifiers() & Qt::ShiftModifier ) { m_drawButton->setChecked( TRUE ); _ke->accept(); } break; case Qt::Key_E: if( _ke->modifiers() & Qt::ShiftModifier ) { m_eraseButton->setChecked( TRUE ); _ke->accept(); } break; case Qt::Key_S: if( _ke->modifiers() & Qt::ShiftModifier ) { m_selectButton->setChecked( TRUE ); _ke->accept(); } break; case Qt::Key_M: if( _ke->modifiers() & Qt::ShiftModifier ) { m_moveButton->setChecked( TRUE ); _ke->accept(); } break; case Qt::Key_Delete: deleteSelectedValues(); _ke->accept(); break; case Qt::Key_Space: if( engine::getSong()->isPlaying() ) { stop(); } else { play(); } _ke->accept(); break; case Qt::Key_Home: m_timeLine->pos().setTicks( 0 ); m_timeLine->updatePosition(); _ke->accept(); break; default: break; } } void AutomationEditor::leaveEvent( QEvent * _e ) { while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } QWidget::leaveEvent( _e ); } void AutomationEditor::drawLine( int _x0, float _y0, int _x1, float _y1 ) { int deltax = qRound( qAbs( _x1 - _x0 ) ); float deltay = qAbs( _y1 - _y0 ); int x = _x0; float y = _y0; int xstep; int ystep; if( deltax < quantization() ) { return; } deltax /= quantization(); float yscale = deltay / ( deltax ); if( _x0 < _x1) { xstep = quantization(); } else { xstep = -( quantization() ); } if( _y0 < _y1 ) { ystep = 1; } else { ystep = -1; } int i = 0; while( i < deltax ) { y = _y0 + ( ystep * yscale * i ); x += xstep; i += 1; m_pattern->removeValue( midiTime( x ) ); m_pattern->putValue( midiTime( x ), y ); } } void AutomationEditor::disableTensionComboBox() { m_tensionComboBox->setEnabled( false ); m_tensionComboBox->setModel( &m_tensionDisabledModel ); m_tensionComboBox->setToolTip( tr( "Choose Cubic Hermite progression to enable" ) ); m_tensionComboBox->setWhatsThis( tr( "Choose Cubic Hermite progression to enable" ) ); } void AutomationEditor::mousePressEvent( QMouseEvent * _me ) { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } if( _me->y() > TOP_MARGIN ) { float level = getLevel( _me->y() ); int x = _me->x(); if( x > VALUES_WIDTH ) { // set or move value x -= VALUES_WIDTH; // get tick in which the user clicked int pos_ticks = x * DefaultTicksPerTact / m_ppt + m_currentPosition; // get time map of current pattern timeMap & time_map = m_pattern->getTimeMap(); // will be our iterator in the following loop timeMap::iterator it = time_map.begin(); // loop through whole time-map... while( it != time_map.end() ) { midiTime len = 4; // and check whether the user clicked on an // existing value if( pos_ticks >= it.key() && len > 0 && ( it+1==time_map.end() || pos_ticks <= (it+1).key() ) && ( pos_ticks<= it.key() + DefaultTicksPerTact *4 / m_ppt ) && level <= it.value() ) { break; } ++it; } // left button?? if( _me->button() == Qt::LeftButton && m_editMode == DRAW ) { // Connect the dots if( _me->modifiers() & Qt::ShiftModifier ) { drawLine( m_drawLastTick, m_drawLastLevel, pos_ticks, level ); } m_drawLastTick = pos_ticks; m_drawLastLevel = level; // did it reach end of map because // there's no value?? if( it == time_map.end() ) { // then set new value midiTime value_pos( pos_ticks ); midiTime new_time = m_pattern->putValue( value_pos, level ); // reset it so that it can be used for // ops (move, resize) after this // code-block it = time_map.find( new_time ); } // move it m_action = MOVE_VALUE; int aligned_x = (int)( (float)( ( it.key() - m_currentPosition ) * m_ppt ) / DefaultTicksPerTact ); m_moveXOffset = x - aligned_x - 1; // set move-cursor QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); engine::getSong()->setModified(); } else if( ( _me->button() == Qt::RightButton && m_editMode == DRAW ) || m_editMode == ERASE ) { // erase single value if( it != time_map.end() ) { m_pattern->removeValue( it.key() ); engine::getSong()->setModified(); } } else if( _me->button() == Qt::LeftButton && m_editMode == SELECT ) { // select an area of values m_selectStartTick = pos_ticks; m_selectedTick = 0; m_selectStartLevel = level; m_selectedLevels = 1; m_action = SELECT_VALUES; } else if( _me->button() == Qt::RightButton && m_editMode == SELECT ) { // when clicking right in select-move, we // switch to move-mode m_moveButton->setChecked( TRUE ); } else if( _me->button() == Qt::LeftButton && m_editMode == MOVE ) { // move selection (including selected values) // save position where move-process began m_moveStartTick = pos_ticks; m_moveStartLevel = level; m_action = MOVE_SELECTION; engine::getSong()->setModified(); } else if( _me->button() == Qt::RightButton && m_editMode == MOVE ) { // when clicking right in select-move, we // switch to draw-mode m_drawButton->setChecked( TRUE ); } update(); } } } void AutomationEditor::mouseReleaseEvent( QMouseEvent * _me ) { m_action = NONE; if( m_editMode == DRAW ) { QApplication::restoreOverrideCursor(); } } void AutomationEditor::mouseMoveEvent( QMouseEvent * _me ) { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { update(); return; } if( _me->y() > TOP_MARGIN ) { float level = getLevel( _me->y() ); int x = _me->x(); if( _me->x() <= VALUES_WIDTH ) { update(); return; } x -= VALUES_WIDTH; if( m_action == MOVE_VALUE ) { x -= m_moveXOffset; } int pos_ticks = x * DefaultTicksPerTact / m_ppt + m_currentPosition; if( _me->buttons() & Qt::LeftButton && m_editMode == DRAW ) { if( m_action == MOVE_VALUE ) { // moving value if( pos_ticks < 0 ) { pos_ticks = 0; } drawLine( m_drawLastTick, m_drawLastLevel, pos_ticks, level ); m_drawLastTick = pos_ticks; m_drawLastLevel = level; // we moved the value so the value has to be // moved properly according to new starting- // time in the time map of pattern m_pattern->removeValue( midiTime( pos_ticks ) ); m_pattern->putValue( midiTime( pos_ticks ), level ); } engine::getSong()->setModified(); } else if( ( _me->buttons() & Qt::RightButton && m_editMode == DRAW ) || ( _me->buttons() & Qt::LeftButton && m_editMode == ERASE ) ) { m_pattern->removeValue( midiTime( pos_ticks ) ); } else if( _me->buttons() & Qt::NoButton && m_editMode == DRAW ) { // set move- or resize-cursor // get time map of current pattern timeMap & time_map = m_pattern->getTimeMap(); // will be our iterator in the following loop timeMap::iterator it = time_map.begin(); // loop through whole time map... for( ; it != time_map.end(); ++it ) { // and check whether the cursor is over an // existing value if( pos_ticks >= it.key() && ( it+1==time_map.end() || pos_ticks <= (it+1).key() ) && level <= it.value() ) { break; } } // did it reach end of map because there's // no value?? if( it != time_map.end() ) { if( QApplication::overrideCursor() ) { if( QApplication::overrideCursor()->shape() != Qt::SizeAllCursor ) { while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); } } else { QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); } } else { // the cursor is over no value, so restore // cursor while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } } } else if( _me->buttons() & Qt::LeftButton && m_editMode == SELECT && m_action == SELECT_VALUES ) { // change size of selection if( x < 0 && m_currentPosition > 0 ) { x = 0; QCursor::setPos( mapToGlobal( QPoint( VALUES_WIDTH, _me->y() ) ) ); if( m_currentPosition >= 4 ) { m_leftRightScroll->setValue( m_currentPosition - 4 ); } else { m_leftRightScroll->setValue( 0 ); } } else if( x > width() - VALUES_WIDTH ) { x = width() - VALUES_WIDTH; QCursor::setPos( mapToGlobal( QPoint( width(), _me->y() ) ) ); m_leftRightScroll->setValue( m_currentPosition + 4 ); } // get tick in which the cursor is posated int pos_ticks = x * DefaultTicksPerTact / m_ppt + m_currentPosition; m_selectedTick = pos_ticks - m_selectStartTick; if( (int) m_selectStartTick + m_selectedTick < 0 ) { m_selectedTick = -qRound( m_selectStartTick ); } m_selectedLevels = level - m_selectStartLevel; if( level <= m_selectStartLevel ) { --m_selectedLevels; } } else if( _me->buttons() & Qt::LeftButton && m_editMode == MOVE && m_action == MOVE_SELECTION ) { // move selection + selected values // do horizontal move-stuff int pos_ticks = x * DefaultTicksPerTact / m_ppt + m_currentPosition; int ticks_diff = pos_ticks - m_moveStartTick; if( m_selectedTick > 0 ) { if( (int) m_selectStartTick + ticks_diff < 0 ) { ticks_diff = -m_selectStartTick; } } else { if( (int) m_selectStartTick + m_selectedTick + ticks_diff < 0 ) { ticks_diff = -( m_selectStartTick + m_selectedTick ); } } m_selectStartTick += ticks_diff; int tact_diff = ticks_diff / DefaultTicksPerTact; ticks_diff = ticks_diff % DefaultTicksPerTact; // do vertical move-stuff float level_diff = level - m_moveStartLevel; if( m_selectedLevels > 0 ) { if( m_selectStartLevel + level_diff < m_minLevel ) { level_diff = m_minLevel - m_selectStartLevel; } else if( m_selectStartLevel + m_selectedLevels + level_diff > m_maxLevel ) { level_diff = m_maxLevel - m_selectStartLevel - m_selectedLevels; } } else { if( m_selectStartLevel + m_selectedLevels + level_diff < m_minLevel ) { level_diff = m_minLevel - m_selectStartLevel - m_selectedLevels; } else if( m_selectStartLevel + level_diff > m_maxLevel ) { level_diff = m_maxLevel - m_selectStartLevel; } } m_selectStartLevel += level_diff; timeMap new_selValuesForMove; for( timeMap::iterator it = m_selValuesForMove.begin(); it != m_selValuesForMove.end(); ++it ) { midiTime new_value_pos; if( it.key() ) { int value_tact = ( it.key() / DefaultTicksPerTact ) + tact_diff; int value_ticks = ( it.key() % DefaultTicksPerTact ) + ticks_diff; // ensure value_ticks range if( value_ticks / DefaultTicksPerTact ) { value_tact += value_ticks / DefaultTicksPerTact; value_ticks %= DefaultTicksPerTact; } m_pattern->removeValue( it.key() ); new_value_pos = midiTime( value_tact, value_ticks ); } new_selValuesForMove[ m_pattern->putValue( new_value_pos, it.value () + level_diff, FALSE )] = it.value() + level_diff; } m_selValuesForMove = new_selValuesForMove; m_moveStartTick = pos_ticks; m_moveStartLevel = level; } } else { if( _me->buttons() & Qt::LeftButton && m_editMode == SELECT && m_action == SELECT_VALUES ) { int x = _me->x() - VALUES_WIDTH; if( x < 0 && m_currentPosition > 0 ) { x = 0; QCursor::setPos( mapToGlobal( QPoint( VALUES_WIDTH, _me->y() ) ) ); if( m_currentPosition >= 4 ) { m_leftRightScroll->setValue( m_currentPosition - 4 ); } else { m_leftRightScroll->setValue( 0 ); } } else if( x > width() - VALUES_WIDTH ) { x = width() - VALUES_WIDTH; QCursor::setPos( mapToGlobal( QPoint( width(), _me->y() ) ) ); m_leftRightScroll->setValue( m_currentPosition + 4 ); } // get tick in which the cursor is posated int pos_ticks = x * DefaultTicksPerTact / m_ppt + m_currentPosition; m_selectedTick = pos_ticks - m_selectStartTick; if( (int) m_selectStartTick + m_selectedTick < 0 ) { m_selectedTick = -qRound( m_selectStartTick ); } float level = getLevel( _me->y() ); if( level <= m_bottomLevel ) { QCursor::setPos( mapToGlobal( QPoint( _me->x(), height() - SCROLLBAR_SIZE ) ) ); m_topBottomScroll->setValue( m_topBottomScroll->value() + 1 ); level = m_bottomLevel; } else if( level >= m_topLevel ) { QCursor::setPos( mapToGlobal( QPoint( _me->x(), TOP_MARGIN ) ) ); m_topBottomScroll->setValue( m_topBottomScroll->value() - 1 ); level = m_topLevel; } m_selectedLevels = level - m_selectStartLevel; if( level <= m_selectStartLevel ) { --m_selectedLevels; } } QApplication::restoreOverrideCursor(); } update(); } inline void AutomationEditor::drawCross( QPainter & _p ) { QPoint mouse_pos = mapFromGlobal( QCursor::pos() ); float level = getLevel( mouse_pos.y() ); int grid_bottom = height() - SCROLLBAR_SIZE - 1; float cross_y = m_y_auto ? grid_bottom - ( ( grid_bottom - TOP_MARGIN ) * ( level - m_minLevel ) / (float)( m_maxLevel - m_minLevel ) ) : grid_bottom - ( level - m_bottomLevel ) * m_y_delta; _p.setPen( QColor( 0xFF, 0x33, 0x33 ) ); _p.drawLine( VALUES_WIDTH, (int) cross_y, width(), (int) cross_y ); _p.drawLine( mouse_pos.x(), TOP_MARGIN, mouse_pos.x(), height() - SCROLLBAR_SIZE ); QPoint tt_pos = QCursor::pos(); tt_pos.ry() -= 64; tt_pos.rx() += 32; QToolTip::showText( tt_pos,QString::number( level ),this); } void AutomationEditor::paintEvent( QPaintEvent * _pe ) { QMutexLocker m( &m_patternMutex ); QStyleOption opt; opt.initFrom( this ); QPainter p( this ); style()->drawPrimitive( QStyle::PE_Widget, &opt, &p, this ); // set font-size to 8 p.setFont( pointSize<8>( p.font() ) ); int grid_height = height() - TOP_MARGIN - SCROLLBAR_SIZE; // start drawing at the bottom int grid_bottom = height() - SCROLLBAR_SIZE - 1; p.fillRect( 0, TOP_MARGIN, VALUES_WIDTH, height() - TOP_MARGIN, QColor( 0x33, 0x33, 0x33 ) ); // print value numbers int font_height = p.fontMetrics().height(); Qt::Alignment text_flags = (Qt::Alignment)( Qt::AlignRight | Qt::AlignVCenter ); if( validPattern() ) { if( m_y_auto ) { int y[] = { grid_bottom, TOP_MARGIN + font_height / 2 }; float level[] = { m_minLevel, m_maxLevel }; for( int i = 0; i < 2; ++i ) { const QString & label = m_pattern->firstObject() ->displayValue( level[i] ); p.setPen( QColor( 240, 240, 240 ) ); p.drawText( 1, y[i] - font_height + 1, VALUES_WIDTH - 10, 2 * font_height, text_flags, label ); p.setPen( QColor( 0, 0, 0 ) ); p.drawText( 0, y[i] - font_height, VALUES_WIDTH - 10, 2 * font_height, text_flags, label ); } } else { int y = grid_bottom; int level = (int) m_bottomLevel; int printable = qMax( 1, 5 * DEFAULT_Y_DELTA / m_y_delta ); int module = level % printable; if( module ) { int inv_module = ( printable - module ) % printable; y -= inv_module * m_y_delta; level += inv_module; } for( ; y >= TOP_MARGIN && level <= m_topLevel; y -= printable * m_y_delta, level += printable ) { const QString & label = m_pattern->firstObject() ->displayValue( level ); p.setPen( QColor( 240, 240, 240 ) ); p.drawText( 1, y - font_height + 1, VALUES_WIDTH - 10, 2 * font_height, text_flags, label ); p.setPen( QColor( 0, 0, 0 ) ); p.drawText( 0, y - font_height, VALUES_WIDTH - 10, 2 * font_height, text_flags, label ); } } } // set clipping area, because we are not allowed to paint over // keyboard... p.setClipRect( VALUES_WIDTH, TOP_MARGIN, width() - VALUES_WIDTH, grid_height ); // draw vertical raster int tact_16th = m_currentPosition / ( DefaultTicksPerTact / 16 ); const int offset = ( m_currentPosition % (DefaultTicksPerTact/16) ) * m_ppt / DEFAULT_STEPS_PER_TACT / 8; if( m_pattern ) { int x_line_end = (int)( m_y_auto || m_topLevel < m_maxLevel ? TOP_MARGIN : grid_bottom - ( m_topLevel - m_bottomLevel ) * m_y_delta ); for( int x = VALUES_WIDTH - offset; x < width(); x += m_ppt / DEFAULT_STEPS_PER_TACT, ++tact_16th ) { if( x >= VALUES_WIDTH ) { // every tact-start needs to be a bright line if( tact_16th % 16 == 0 ) { p.setPen( QColor( 0x7F, 0x7F, 0x7F ) ); } // normal line else if( tact_16th % 4 == 0 ) { p.setPen( QColor( 0x5F, 0x5F, 0x5F ) ); } // weak line else { p.setPen( QColor( 0x3F, 0x3F, 0x3F ) ); } p.drawLine( x, grid_bottom, x, x_line_end ); } } if( m_y_auto ) { QPen pen( QColor( 0x4F, 0x4F, 0x4F ) ); p.setPen( pen ); p.drawLine( VALUES_WIDTH, grid_bottom, width(), grid_bottom ); pen.setStyle( Qt::DotLine ); p.setPen( pen ); float y_delta = ( grid_bottom - TOP_MARGIN ) / 8.0f; for( int i = 1; i < 8; ++i ) { int y = (int)( grid_bottom - i * y_delta ); p.drawLine( VALUES_WIDTH, y, width(), y ); } } else { for( float y = grid_bottom, level = m_bottomLevel; y >= TOP_MARGIN && level <= m_topLevel; y -= m_y_delta, ++level ) { if( (int)level % 5 == 0 ) { p.setPen( QColor( 0x4F, 0x4F, 0x4F ) ); } else { p.setPen( QColor( 0x3F, 0x3F, 0x3F ) ); } // draw level line p.drawLine( VALUES_WIDTH, (int) y, width(), (int) y ); } } } // following code draws all visible values // setup selection-vars int sel_pos_start = m_selectStartTick; int sel_pos_end = m_selectStartTick + m_selectedTick; if( sel_pos_start > sel_pos_end ) { qSwap( sel_pos_start, sel_pos_end ); } float selLevel_start = m_selectStartLevel; float selLevel_end = selLevel_start + m_selectedLevels; if( selLevel_start > selLevel_end ) { qSwap( selLevel_start, selLevel_end ); } if( validPattern() ) { Sint32 len_ticks = 4; timeMap & time_map = m_pattern->getTimeMap(); timeMap::iterator it = time_map.begin(); p.setPen( QColor( 0xFF, 0xDF, 0x20 ) ); while( it+1 != time_map.end() ) { // skip this section if it occurs completely before the // visible area int next_x = xCoordOfTick( (it+1).key() ); if( next_x < 0 ) { ++it; continue; } int x = xCoordOfTick( it.key() ); if( x > width() ) { break; } bool is_selected = FALSE; // if we're in move-mode, we may only draw // values in selected area, that have originally // been selected and not values that are now in // selection because the user moved it... if( m_editMode == MOVE ) { if( m_selValuesForMove.contains( it.key() ) ) { is_selected = TRUE; } } else if( it.value() >= selLevel_start && it.value() <= selLevel_end && it.key() >= sel_pos_start && it.key() + len_ticks <= sel_pos_end ) { is_selected = TRUE; } float *values = m_pattern->valuesAfter( it.key() ); for( int i = 0; i < (it+1).key() - it.key(); i++ ) { drawLevelTick( p, it.key() + i, values[i], is_selected ); } delete [] values; // Draw cross int y = yCoordOfLevel( it.value() ); p.drawLine( x - 1, y, x + 1, y ); p.drawLine( x, y - 1, x, y + 1 ); // _p.setPen( QColor( 0xFF, 0x9F, 0x00 ) ); // _p.setPen( QColor( 0xFF, 0xFF, 0x40 ) ); ++it; } for( int i = it.key(), x = xCoordOfTick( i ); x <= width(); i++, x = xCoordOfTick( i ) ) { // TODO: Find out if the section after the last control // point is able to be selected and if so set this // boolean correctly drawLevelTick( p, i, it.value(), false ); } } else { QFont f = p.font(); f.setBold( TRUE ); p.setFont( pointSize<14>( f ) ); p.setPen( QColor( 0, 255, 0 ) ); p.drawText( VALUES_WIDTH + 20, TOP_MARGIN + 40, width() - VALUES_WIDTH - 20 - SCROLLBAR_SIZE, grid_height - 40, Qt::TextWordWrap, tr( "Please open an automation pattern with " "the context menu of a control!" ) ); } // now draw selection-frame int x = ( sel_pos_start - m_currentPosition ) * m_ppt / DefaultTicksPerTact; int w = ( sel_pos_end - sel_pos_start ) * m_ppt / DefaultTicksPerTact; int y, h; if( m_y_auto ) { y = (int)( grid_bottom - ( ( grid_bottom - TOP_MARGIN ) * ( selLevel_start - m_minLevel ) / (float)( m_maxLevel - m_minLevel ) ) ); h = (int)( grid_bottom - ( ( grid_bottom - TOP_MARGIN ) * ( selLevel_end - m_minLevel ) / (float)( m_maxLevel - m_minLevel ) ) - y ); } else { y = (int)( grid_bottom - ( selLevel_start - m_bottomLevel ) * m_y_delta ); h = (int)( ( selLevel_start - selLevel_end ) * m_y_delta ); } p.setPen( QColor( 0, 64, 192 ) ); p.drawRect( x + VALUES_WIDTH, y, w, h ); // TODO: Get this out of paint event int l = validPattern() ? (int) m_pattern->length() : 0; // reset scroll-range if( m_leftRightScroll->maximum() != l ) { m_leftRightScroll->setRange( 0, l ); m_leftRightScroll->setPageStep( l ); } if( validPattern() ) { drawCross( p ); } const QPixmap * cursor = NULL; // draw current edit-mode-icon below the cursor switch( m_editMode ) { case DRAW: cursor = s_toolDraw; break; case ERASE: cursor = s_toolErase; break; case SELECT: cursor = s_toolSelect; break; case MOVE: cursor = s_toolMove; break; } p.drawPixmap( mapFromGlobal( QCursor::pos() ) + QPoint( 8, 8 ), *cursor ); } int AutomationEditor::xCoordOfTick( int _tick ) { return VALUES_WIDTH + ( ( _tick - m_currentPosition ) * m_ppt / DefaultTicksPerTact ); } int AutomationEditor::yCoordOfLevel( float _level ) { int grid_bottom = height() - SCROLLBAR_SIZE - 1; if( m_y_auto ) { return (int)( grid_bottom - ( grid_bottom - TOP_MARGIN ) * ( _level - m_minLevel ) / ( m_maxLevel - m_minLevel ) ); } else { return (int)( grid_bottom - ( _level - m_bottomLevel ) * m_y_delta ); } } void AutomationEditor::drawLevelTick( QPainter & _p, int _tick, float _level, bool _is_selected ) { int grid_bottom = height() - SCROLLBAR_SIZE - 1; const int x = xCoordOfTick( _tick ); int rect_width = xCoordOfTick( _tick+1 ) - x; // is the level in visible area? if( ( _level >= m_bottomLevel && _level <= m_topLevel ) || ( _level > m_topLevel && m_topLevel >= 0 ) || ( _level < m_bottomLevel && m_bottomLevel <= 0 ) ) { int y_start = yCoordOfLevel( _level ); int rect_height; if( m_y_auto ) { int y_end = (int)( grid_bottom + ( grid_bottom - TOP_MARGIN ) * m_minLevel / ( m_maxLevel - m_minLevel ) ); rect_height = y_end - y_start; } else { rect_height = (int)( _level * m_y_delta ); } QColor current_color( 0xFF, 0xB0, 0x00 ); if( _is_selected == TRUE ) { current_color.setRgb( 0x00, 0x40, 0xC0 ); } _p.fillRect( x, y_start, rect_width, rect_height, current_color ); } else { printf("not in range\n"); } } // responsible for moving/resizing scrollbars after window-resizing void AutomationEditor::resizeEvent( QResizeEvent * ) { m_leftRightScroll->setGeometry( VALUES_WIDTH, height() - SCROLLBAR_SIZE, width() - VALUES_WIDTH, SCROLLBAR_SIZE ); int grid_height = height() - TOP_MARGIN - SCROLLBAR_SIZE; m_topBottomScroll->setGeometry( width() - SCROLLBAR_SIZE, TOP_MARGIN, SCROLLBAR_SIZE, grid_height ); int half_grid = grid_height / 2; int total_pixels = (int)( ( m_maxLevel - m_minLevel ) * m_y_delta + 1 ); if( !m_y_auto && grid_height < total_pixels ) { int min_scroll = (int)( m_minLevel + floorf( half_grid / (float)m_y_delta ) ); int max_scroll = (int)( m_maxLevel - (int)floorf( ( grid_height - half_grid ) / (float)m_y_delta ) ); m_topBottomScroll->setRange( min_scroll, max_scroll ); } else { m_topBottomScroll->setRange( (int) m_scrollLevel, (int) m_scrollLevel ); } m_topBottomScroll->setValue( (int) m_scrollLevel ); if( engine::getSong() ) { engine::getSong()->getPlayPos( song::Mode_PlayAutomationPattern ).m_timeLine->setFixedWidth( width() ); } m_toolBar->setFixedWidth( width() ); updateTopBottomLevels(); update(); } void AutomationEditor::wheelEvent( QWheelEvent * _we ) { _we->accept(); if( _we->modifiers() & Qt::ControlModifier ) { if( _we->delta() > 0 ) { m_ppt = qMin( m_ppt * 2, m_y_delta * DEFAULT_STEPS_PER_TACT * 8 ); } else if( m_ppt >= 72 ) { m_ppt /= 2; } // update combobox with zooming-factor m_zoomingXComboBox->model()->setValue( m_zoomingXComboBox->model()->findText( QString::number( qRound( m_ppt * 100 / DEFAULT_PPT ) ) +"%" ) ); // update timeline m_timeLine->setPixelsPerTact( m_ppt ); update(); } else if( _we->modifiers() & Qt::ShiftModifier || _we->orientation() == Qt::Horizontal ) { m_leftRightScroll->setValue( m_leftRightScroll->value() - _we->delta() * 2 / 15 ); } else { m_topBottomScroll->setValue( m_topBottomScroll->value() - _we->delta() / 30 ); } } float AutomationEditor::getLevel( int _y ) { int level_line_y = height() - SCROLLBAR_SIZE - 1; // pressed level float level = roundf( ( m_bottomLevel + ( m_y_auto ? ( m_maxLevel - m_minLevel ) * ( level_line_y - _y ) / (float)( level_line_y - TOP_MARGIN ) : ( level_line_y - _y ) / (float)m_y_delta ) ) / m_step ) * m_step; // some range-checking-stuff if( level < m_bottomLevel ) { level = m_bottomLevel; } else if( level > m_topLevel ) { level = m_topLevel; } return( level ); } inline bool AutomationEditor::inBBEditor() { QMutexLocker m( &m_patternMutex ); return( validPattern() && m_pattern->getTrack()->getTrackContainer() == engine::getBBTrackContainer() ); } void AutomationEditor::play() { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } if( !m_pattern->getTrack() ) { if( engine::getSong()->playMode() != song::Mode_PlayPattern ) { engine::getSong()->stop(); engine::getSong()->playPattern( (pattern *) engine::getPianoRoll()->currentPattern() ); } else if( engine::getSong()->isStopped() == false ) { engine::getSong()->togglePause(); } else { engine::getSong()->playPattern( (pattern *) engine::getPianoRoll()->currentPattern() ); } } else if( inBBEditor() ) { engine::getBBTrackContainer()->play(); } else { if( engine::getSong()->isStopped() == true ) { engine::getSong()->playSong(); } else { engine::getSong()->togglePause(); } } } void AutomationEditor::stop() { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } if( m_pattern->getTrack() && inBBEditor() ) { engine::getBBTrackContainer()->stop(); } else { engine::getSong()->stop(); } m_scrollBack = TRUE; } void AutomationEditor::horScrolled( int _new_pos ) { m_currentPosition = _new_pos; emit positionChanged( m_currentPosition ); update(); } void AutomationEditor::verScrolled( int _new_pos ) { m_scrollLevel = _new_pos; updateTopBottomLevels(); update(); } void AutomationEditor::drawButtonToggled() { m_editMode = DRAW; removeSelection(); update(); } void AutomationEditor::eraseButtonToggled() { m_editMode = ERASE; removeSelection(); update(); } void AutomationEditor::selectButtonToggled() { m_editMode = SELECT; removeSelection(); update(); } void AutomationEditor::moveButtonToggled() { m_editMode = MOVE; m_selValuesForMove.clear(); getSelectedValues( m_selValuesForMove ); update(); } void AutomationEditor::discreteButtonToggled() { if ( validPattern() ) { QMutexLocker m( &m_patternMutex ); disableTensionComboBox(); m_pattern->setProgressionType( AutomationPattern::DiscreteProgression ); engine::getSong()->setModified(); update(); } } void AutomationEditor::linearButtonToggled() { if ( validPattern() ) { QMutexLocker m( &m_patternMutex ); disableTensionComboBox(); m_pattern->setProgressionType( AutomationPattern::LinearProgression ); engine::getSong()->setModified(); update(); } } void AutomationEditor::cubicHermiteButtonToggled() { if ( validPattern() ) { QMutexLocker m( &m_patternMutex ); m_tensionComboBox->setEnabled( true ); m_tensionComboBox->setModel( &m_tensionModel ); m_tensionComboBox->setToolTip( tr( "Tension value for spline" ) ); m_tensionComboBox->setWhatsThis( tr( "A higher tension value may make a smoother curve " "but overshoot some values. A low tension " "value will cause the slope of the curve to " "level off at each control point." ) ); m_pattern->setProgressionType( AutomationPattern::CubicHermiteProgression ); engine::getSong()->setModified(); update(); } } void AutomationEditor::tensionChanged() { m_pattern->setTension( m_tensionModel.currentText() ); update(); } void AutomationEditor::selectAll() { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } timeMap & time_map = m_pattern->getTimeMap(); timeMap::iterator it = time_map.begin(); m_selectStartTick = 0; m_selectedTick = m_pattern->length(); m_selectStartLevel = it.value(); m_selectedLevels = 1; while( ++it != time_map.end() ) { const float level = it.value(); if( level < m_selectStartLevel ) { // if we move start-level down, we have to add // the difference between old and new start-level // to m_selectedLevels, otherwise the selection // is just moved down... m_selectedLevels += m_selectStartLevel - level; m_selectStartLevel = level; } else if( level >= m_selectStartLevel + m_selectedLevels ) { m_selectedLevels = level - m_selectStartLevel + 1; } } } // returns vector with pointers to all selected values void AutomationEditor::getSelectedValues( timeMap & _selected_values ) { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } int sel_pos_start = m_selectStartTick; int sel_pos_end = sel_pos_start + m_selectedTick; if( sel_pos_start > sel_pos_end ) { qSwap( sel_pos_start, sel_pos_end ); } float selLevel_start = m_selectStartLevel; float selLevel_end = selLevel_start + m_selectedLevels; if( selLevel_start > selLevel_end ) { qSwap( selLevel_start, selLevel_end ); } timeMap & time_map = m_pattern->getTimeMap(); for( timeMap::iterator it = time_map.begin(); it != time_map.end(); ++it ) { //TODO: Add constant tick_t len_ticks = DefaultTicksPerTact / 16; float level = it.value(); tick_t pos_ticks = it.key(); if( level >= selLevel_start && level <= selLevel_end && pos_ticks >= sel_pos_start && pos_ticks + len_ticks <= sel_pos_end ) { _selected_values[it.key()] = level; } } } void AutomationEditor::copySelectedValues() { m_valuesToCopy.clear(); timeMap selected_values; getSelectedValues( selected_values ); if( !selected_values.isEmpty() ) { for( timeMap::iterator it = selected_values.begin(); it != selected_values.end(); ++it ) { m_valuesToCopy[it.key()] = it.value(); } textFloat::displayMessage( tr( "Values copied" ), tr( "All selected values were copied to the " "clipboard." ), embed::getIconPixmap( "edit_copy" ), 2000 ); } } void AutomationEditor::cutSelectedValues() { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } m_valuesToCopy.clear(); timeMap selected_values; getSelectedValues( selected_values ); if( !selected_values.isEmpty() ) { engine::getSong()->setModified(); for( timeMap::iterator it = selected_values.begin(); it != selected_values.end(); ++it ) { m_valuesToCopy[it.key()] = it.value(); m_pattern->removeValue( it.key() ); } } update(); engine::getSongEditor()->update(); } void AutomationEditor::pasteValues() { QMutexLocker m( &m_patternMutex ); if( validPattern() && !m_valuesToCopy.isEmpty() ) { for( timeMap::iterator it = m_valuesToCopy.begin(); it != m_valuesToCopy.end(); ++it ) { m_pattern->putValue( it.key() + m_currentPosition, it.value() ); } // we only have to do the following lines if we pasted at // least one value... engine::getSong()->setModified(); update(); engine::getSongEditor()->update(); } } void AutomationEditor::deleteSelectedValues() { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } timeMap selected_values; getSelectedValues( selected_values ); const bool update_after_delete = !selected_values.empty(); for( timeMap::iterator it = selected_values.begin(); it != selected_values.end(); ++it ) { m_pattern->removeValue( it.key() ); } if( update_after_delete == TRUE ) { engine::getSong()->setModified(); update(); engine::getSongEditor()->update(); } } void AutomationEditor::updatePosition( const midiTime & _t ) { if( ( engine::getSong()->isPlaying() && engine::getSong()->playMode() == song::Mode_PlayAutomationPattern ) || m_scrollBack == TRUE ) { const int w = width() - VALUES_WIDTH; if( _t > m_currentPosition + w * DefaultTicksPerTact / m_ppt ) { m_leftRightScroll->setValue( _t.getTact() * DefaultTicksPerTact ); } else if( _t < m_currentPosition ) { midiTime t = qMax( _t - w * DefaultTicksPerTact * DefaultTicksPerTact / m_ppt, 0 ); m_leftRightScroll->setValue( t.getTact() * DefaultTicksPerTact ); } m_scrollBack = FALSE; } } void AutomationEditor::zoomingXChanged() { const QString & zfac = m_zoomingXModel.currentText(); m_ppt = zfac.left( zfac.length() - 1 ).toInt() * DEFAULT_PPT / 100; #ifdef LMMS_DEBUG assert( m_ppt > 0 ); #endif m_timeLine->setPixelsPerTact( m_ppt ); update(); } void AutomationEditor::zoomingYChanged() { const QString & zfac = m_zoomingYModel.currentText(); m_y_auto = zfac == "Auto"; if( !m_y_auto ) { m_y_delta = zfac.left( zfac.length() - 1 ).toInt() * DEFAULT_Y_DELTA / 100; } #ifdef LMMS_DEBUG assert( m_y_delta > 0 ); #endif resizeEvent( NULL ); } int AutomationEditor::quantization() const { return( DefaultTicksPerTact / m_quantizeComboBox->model()->currentText().right( m_quantizeComboBox->model()->currentText().length() - 2 ).toInt() ); } void AutomationEditor::updateTopBottomLevels() { if( m_y_auto ) { m_bottomLevel = m_minLevel; m_topLevel = m_maxLevel; return; } int total_pixels = (int)( ( m_maxLevel - m_minLevel ) * m_y_delta + 1 ); int grid_height = height() - TOP_MARGIN - SCROLLBAR_SIZE; int half_grid = grid_height / 2; if( total_pixels > grid_height ) { int centralLevel = (int)( m_minLevel + m_maxLevel - m_scrollLevel ); m_bottomLevel = centralLevel - ( half_grid / (float)m_y_delta ); if( m_bottomLevel < m_minLevel ) { m_bottomLevel = m_minLevel; m_topLevel = m_minLevel + (int)floorf( grid_height / (float)m_y_delta ); } else { m_topLevel = m_bottomLevel + (int)floorf( grid_height / (float)m_y_delta ); if( m_topLevel > m_maxLevel ) { m_topLevel = m_maxLevel; m_bottomLevel = m_maxLevel - (int)floorf( grid_height / (float)m_y_delta ); } } } else { m_bottomLevel = m_minLevel; m_topLevel = m_maxLevel; } } #include "moc_AutomationEditor.cxx"