Files
lmms/src/gui/editors/AutomationEditor.cpp
Dave French 51fa26ecb1 Fixed bug where cross hairs were incrorrectly draw in Automation Editor
The cross hairs in the automation editor were incorrectly drawn
when moving another window infront.
This change only draws the cross hairs when the window has focus.
2015-03-09 22:43:44 +00:00

2349 lines
55 KiB
C++

/*
* AutomationEditor.cpp - implementation of AutomationEditor which is used for
* actual setting of dynamic values
*
* Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2008-2013 Paul Giblock <pgib/at/users.sourceforge.net>
* Copyright (c) 2006-2008 Javier Serrano Polo <jasp00/at/users.sourceforge.net>
*
* This file is part of LMMS - http://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 "AutomationEditor.h"
#include <QApplication>
#include <QButtonGroup>
#include <QKeyEvent>
#include <QLabel>
#include <QLayout>
#include <QMdiArea>
#include <QPainter>
#include <QScrollBar>
#include <QStyleOption>
#include <QWheelEvent>
#include <QToolTip>
#include <QSignalMapper>
#ifndef __USE_XOPEN
#define __USE_XOPEN
#endif
#include <math.h>
#include "ActionGroup.h"
#include "SongEditor.h"
#include "MainWindow.h"
#include "GuiApplication.h"
#include "embed.h"
#include "Engine.h"
#include "PixmapButton.h"
#include "templates.h"
#include "gui_templates.h"
#include "TimeLineWidget.h"
#include "ToolTip.h"
#include "TextFloat.h"
#include "ComboBox.h"
#include "BBTrackContainer.h"
#include "PianoRoll.h"
#include "debug.h"
#include "MeterModel.h"
#include "StringPairDrag.h"
#include "ProjectJournal.h"
QPixmap * AutomationEditor::s_toolDraw = NULL;
QPixmap * AutomationEditor::s_toolErase = NULL;
QPixmap * AutomationEditor::s_toolSelect = NULL;
QPixmap * AutomationEditor::s_toolMove = NULL;
QPixmap * AutomationEditor::s_toolYFlip = NULL;
QPixmap * AutomationEditor::s_toolXFlip = 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 ),
m_gridColor( 0,0,0 ),
m_graphColor( Qt::SolidPattern ),
m_vertexColor( 0,0,0 ),
m_scaleColor( Qt::SolidPattern )
{
connect( this, SIGNAL( currentPatternChanged() ),
this, SLOT( updateAfterPatternChange() ),
Qt::QueuedConnection );
connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int, int ) ),
this, SLOT( update() ) );
setAttribute( Qt::WA_OpaquePaintEvent, true );
m_tensionModel = new FloatModel(1.0, 0.0, 1.0, 0.01);
connect( m_tensionModel, SIGNAL( dataChanged() ),
this, SLOT( setTension() ) );
for( int i = 0; i < 7; ++i )
{
m_quantizeModel.addItem( "1/" + QString::number( 1 << i ) );
}
if( s_toolYFlip == NULL )
{
s_toolYFlip = new QPixmap( embed::getIconPixmap(
"flip_y" ) );
}
if( s_toolXFlip == NULL )
{
s_toolXFlip = new QPixmap( embed::getIconPixmap(
"flip_x" ) );
}
connect(&m_quantizeModel, SIGNAL(dataChanged()), this, SLOT(setQuantization()));
m_quantizeModel.setValue( m_quantizeModel.findText( "1/16" ) );
// add time-line
m_timeLine = new TimeLineWidget( VALUES_WIDTH, 0, 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 & ) ) );
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 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" ) );
}
setCurrentPattern( NULL );
setMouseTracking( true );
}
AutomationEditor::~AutomationEditor()
{
m_zoomingXModel.disconnect();
m_zoomingYModel.disconnect();
m_quantizeModel.disconnect();
m_tensionModel->disconnect();
delete m_tensionModel;
}
void AutomationEditor::setCurrentPattern(AutomationPattern * new_pattern )
{
if (m_pattern)
{
m_pattern->disconnect(this);
}
m_patternMutex.lock();
m_pattern = new_pattern;
m_patternMutex.unlock();
if (m_pattern != nullptr)
{
connect(m_pattern, SIGNAL(dataChanged()), this, SLOT(update()));
}
emit currentPatternChanged();
}
void AutomationEditor::saveSettings(QDomDocument & doc, QDomElement & dom_parent)
{
MainWindow::saveWidgetState(parentWidget(), dom_parent);
}
void AutomationEditor::loadSettings( const QDomElement & dom_parent)
{
MainWindow::restoreWidgetState(parentWidget(), dom_parent);
}
// qproperty access methods
QColor AutomationEditor::gridColor() const
{ return m_gridColor; }
QBrush AutomationEditor::graphColor() const
{ return m_graphColor; }
QColor AutomationEditor::vertexColor() const
{ return m_vertexColor; }
QBrush AutomationEditor::scaleColor() const
{ return m_scaleColor; }
void AutomationEditor::setGridColor( const QColor & c )
{ m_gridColor = c; }
void AutomationEditor::setGraphColor( const QBrush & c )
{ m_graphColor = c; }
void AutomationEditor::setVertexColor( const QColor & c )
{ m_vertexColor = c; }
void AutomationEditor::setScaleColor( const QBrush & c )
{ m_scaleColor = c; }
void AutomationEditor::updateAfterPatternChange()
{
QMutexLocker m( &m_patternMutex );
m_currentPosition = 0;
if( !validPattern() )
{
m_minLevel = m_maxLevel = m_scrollLevel = 0;
m_step = 1;
resizeEvent( NULL );
return;
}
m_minLevel = m_pattern->firstObject()->minValue<float>();
m_maxLevel = m_pattern->firstObject()->maxValue<float>();
m_step = m_pattern->firstObject()->step<float>();
m_scrollLevel = ( m_minLevel + m_maxLevel ) / 2;
// resizeEvent() does the rest for us (scrolling, range-checking
// of levels and so on...)
resizeEvent( NULL );
update();
}
void AutomationEditor::update()
{
QWidget::update();
QMutexLocker m( &m_patternMutex );
// Note detuning?
if( m_pattern && !m_pattern->getTrack() )
{
gui->pianoRoll()->update();
}
}
void AutomationEditor::removeSelection()
{
m_selectStartTick = 0;
m_selectedTick = 0;
m_selectStartLevel = 0;
m_selectedLevels = 0;
}
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;
//TODO: m_selectButton and m_moveButton are broken.
/*case Qt::Key_A:
if( ke->modifiers() & Qt::ControlModifier )
{
m_selectButton->setChecked( true );
selectAll();
update();
ke->accept();
}
break;
case Qt::Key_Delete:
deleteSelectedValues();
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<float>( x1 - x0 ) );
float deltay = qAbs<float>( y1 - y0 );
int x = x0;
float y = y0;
int xstep;
int ystep;
if( deltax < AutomationPattern::quantization() )
{
return;
}
deltax /= AutomationPattern::quantization();
float yscale = deltay / ( deltax );
if( x0 < x1)
{
xstep = AutomationPattern::quantization();
}
else
{
xstep = -( AutomationPattern::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::mousePressEvent( QMouseEvent* mouseEvent )
{
QMutexLocker m( &m_patternMutex );
if( !validPattern() )
{
return;
}
if( mouseEvent->y() > TOP_MARGIN )
{
float level = getLevel( mouseEvent->y() );
int x = mouseEvent->x();
if( x > VALUES_WIDTH )
{
// set or move value
x -= VALUES_WIDTH;
// get tick in which the user clicked
int pos_ticks = x * MidiTime::ticksPerTact() / 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() + MidiTime::ticksPerTact() *4 / m_ppt ) &&
level <= it.value() )
{
break;
}
++it;
}
// left button??
if( mouseEvent->button() == Qt::LeftButton &&
m_editMode == DRAW )
{
m_pattern->addJournalCheckPoint();
// Connect the dots
if( mouseEvent->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->setDragValue( 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 ) / MidiTime::ticksPerTact() );
m_moveXOffset = x - aligned_x - 1;
// set move-cursor
QCursor c( Qt::SizeAllCursor );
QApplication::setOverrideCursor( c );
Engine::getSong()->setModified();
}
else if( ( mouseEvent->button() == Qt::RightButton &&
m_editMode == DRAW ) ||
m_editMode == ERASE )
{
m_pattern->addJournalCheckPoint();
// erase single value
if( it != time_map.end() )
{
m_pattern->removeValue( it.key() );
Engine::getSong()->setModified();
}
m_action = NONE;
}
else if( mouseEvent->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( mouseEvent->button() == Qt::RightButton &&
m_editMode == SELECT )
{
// when clicking right in select-move, we
// switch to move-mode
//m_moveButton->setChecked( true );
}
else if( mouseEvent->button() == Qt::LeftButton &&
m_editMode == MOVE )
{
m_pattern->addJournalCheckPoint();
// 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( mouseEvent->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 * mouseEvent )
{
if( m_editMode == DRAW )
{
if( m_action == MOVE_VALUE )
{
m_pattern->applyDragValue();
}
QApplication::restoreOverrideCursor();
}
m_action = NONE;
}
void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent )
{
QMutexLocker m( &m_patternMutex );
if( !validPattern() )
{
update();
return;
}
if( mouseEvent->y() > TOP_MARGIN )
{
float level = getLevel( mouseEvent->y() );
int x = mouseEvent->x();
if( mouseEvent->x() <= VALUES_WIDTH )
{
update();
return;
}
x -= VALUES_WIDTH;
if( m_action == MOVE_VALUE )
{
x -= m_moveXOffset;
}
int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
m_currentPosition;
if( mouseEvent->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->setDragValue( MidiTime( pos_ticks ),
level );
}
Engine::getSong()->setModified();
}
else if( ( mouseEvent->buttons() & Qt::RightButton &&
m_editMode == DRAW ) ||
( mouseEvent->buttons() & Qt::LeftButton &&
m_editMode == ERASE ) )
{
m_pattern->removeValue( MidiTime( pos_ticks ) );
}
else if( mouseEvent->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( mouseEvent->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, mouseEvent->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(),
mouseEvent->y() ) ) );
m_leftRightScroll->setValue( m_currentPosition +
4 );
}
// get tick in which the cursor is posated
int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
m_currentPosition;
m_selectedTick = pos_ticks - m_selectStartTick;
if( (int) m_selectStartTick + m_selectedTick < 0 )
{
m_selectedTick = -m_selectStartTick;
}
m_selectedLevels = level - m_selectStartLevel;
if( level <= m_selectStartLevel )
{
--m_selectedLevels;
}
}
else if( mouseEvent->buttons() & Qt::LeftButton &&
m_editMode == MOVE &&
m_action == MOVE_SELECTION )
{
// move selection + selected values
// do horizontal move-stuff
int pos_ticks = x * MidiTime::ticksPerTact() / 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 / MidiTime::ticksPerTact();
ticks_diff = ticks_diff % MidiTime::ticksPerTact();
// 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() /
MidiTime::ticksPerTact() )
+ tact_diff;
int value_ticks =
( it.key() %
MidiTime::ticksPerTact() )
+ ticks_diff;
// ensure value_ticks range
if( value_ticks / MidiTime::ticksPerTact() )
{
value_tact += value_ticks
/ MidiTime::ticksPerTact();
value_ticks %=
MidiTime::ticksPerTact();
}
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( mouseEvent->buttons() & Qt::LeftButton &&
m_editMode == SELECT &&
m_action == SELECT_VALUES )
{
int x = mouseEvent->x() - VALUES_WIDTH;
if( x < 0 && m_currentPosition > 0 )
{
x = 0;
QCursor::setPos( mapToGlobal( QPoint( VALUES_WIDTH,
mouseEvent->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(),
mouseEvent->y() ) ) );
m_leftRightScroll->setValue( m_currentPosition +
4 );
}
// get tick in which the cursor is posated
int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
m_currentPosition;
m_selectedTick = pos_ticks -
m_selectStartTick;
if( (int) m_selectStartTick + m_selectedTick <
0 )
{
m_selectedTick = -m_selectStartTick;
}
float level = getLevel( mouseEvent->y() );
if( level <= m_bottomLevel )
{
QCursor::setPos( mapToGlobal( QPoint( mouseEvent->x(),
height() -
SCROLLBAR_SIZE ) ) );
m_topBottomScroll->setValue(
m_topBottomScroll->value() + 1 );
level = m_bottomLevel;
}
else if( level >= m_topLevel )
{
QCursor::setPos( mapToGlobal( QPoint( mouseEvent->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;
float scaledLevel = m_pattern->firstObject()->scaledValue( level );
QToolTip::showText( tt_pos, QString::number( scaledLevel ), this );
}
inline void AutomationEditor::drawAutomationPoint( QPainter & p, timeMap::iterator it )
{
int x = xCoordOfTick( it.key() );
int y = yCoordOfLevel( it.value() );
const int outerRadius = qBound( 2, ( m_ppt * AutomationPattern::quantization() ) / 576, 5 ); // man, getting this calculation right took forever
p.setPen( QPen( vertexColor().lighter( 200 ) ) );
p.setBrush( QBrush( vertexColor() ) );
p.drawEllipse( x - outerRadius, y - outerRadius, outerRadius * 2, outerRadius * 2 );
}
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 );
// get foreground color
QColor fgColor = p.pen().brush().color();
// get background color and fill background
QBrush bgColor = p.background();
p.fillRect( 0, 0, width(), height(), bgColor );
// 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,
scaleColor() );
// 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( QApplication::palette().color( QPalette::Active,
QPalette::Shadow ) );
p.drawText( 1, y[i] - font_height + 1,
VALUES_WIDTH - 10, 2 * font_height,
text_flags, label );
p.setPen( fgColor );
p.drawText( 0, y[i] - font_height,
VALUES_WIDTH - 10, 2 * font_height,
text_flags, label );
}
}
else
{
int y;
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;
level += inv_module;
}
for( ; level <= m_topLevel; level += printable )
{
const QString & label = m_pattern->firstObject()
->displayValue( level );
y = yCoordOfLevel( level );
p.setPen( QApplication::palette().color( QPalette::Active,
QPalette::Shadow ) );
p.drawText( 1, y - font_height + 1,
VALUES_WIDTH - 10, 2 * font_height,
text_flags, label );
p.setPen( fgColor );
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
QColor lineColor = QColor( gridColor() );
if( m_pattern )
{
int tick, x;
int x_line_end = (int)( m_y_auto || m_topLevel < m_maxLevel ?
TOP_MARGIN :
grid_bottom - ( m_topLevel - m_bottomLevel )
* m_y_delta );
// 3 independent loops, because quantization might not divide evenly into
// exotic denominators (e.g. 7/11 time), which are allowed ATM.
// First quantization grid...
for( tick = m_currentPosition - m_currentPosition % AutomationPattern::quantization(),
x = xCoordOfTick( tick );
x<=width();
tick += AutomationPattern::quantization(), x = xCoordOfTick( tick ) )
{
lineColor.setAlpha( 80 );
p.setPen( lineColor );
p.drawLine( x, grid_bottom, x, x_line_end );
}
// Then beat grid
int ticksPerBeat = DefaultTicksPerTact /
Engine::getSong()->getTimeSigModel().getDenominator();
for( tick = m_currentPosition - m_currentPosition % ticksPerBeat,
x = xCoordOfTick( tick );
x<=width();
tick += ticksPerBeat, x = xCoordOfTick( tick ) )
{
lineColor.setAlpha( 160 );
p.setPen( lineColor );
p.drawLine( x, grid_bottom, x, x_line_end );
}
// and finally bars
for( tick = m_currentPosition - m_currentPosition % MidiTime::ticksPerTact(),
x = xCoordOfTick( tick );
x<=width();
tick += MidiTime::ticksPerTact(), x = xCoordOfTick( tick ) )
{
lineColor.setAlpha( 255 );
p.setPen( lineColor );
p.drawLine( x, grid_bottom, x, x_line_end );
}
/// \todo move this horizontal line drawing code into the same loop as the value ticks?
if( m_y_auto )
{
lineColor.setAlpha( 160 );
QPen pen( lineColor );
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
{
float y;
for( int level = (int)m_bottomLevel; level <= m_topLevel; level++)
{
y = yCoordOfLevel( (float)level );
if( level % 5 == 0 )
{
lineColor.setAlpha( 160 );
p.setPen( lineColor );
}
else
{
lineColor.setAlpha( 80 );
p.setPen( lineColor );
}
// 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<int>( sel_pos_start, sel_pos_end );
}
float selLevel_start = m_selectStartLevel;
float selLevel_end = selLevel_start + m_selectedLevels;
if( selLevel_start > selLevel_end )
{
qSwap<float>( selLevel_start, selLevel_end );
}
if( validPattern() )
{
int len_ticks = 4;
timeMap & time_map = m_pattern->getTimeMap();
//Don't bother doing/rendering anything if there is no automation points
if( time_map.size() > 0 )
{
timeMap::iterator it = time_map.begin();
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 circle
drawAutomationPoint(p, it);
++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 );
}
// Draw circle(the last one)
drawAutomationPoint(p, it);
}
}
else
{
QFont f = p.font();
f.setBold( true );
p.setFont( pointSize<14>( f ) );
p.setPen( QApplication::palette().color( QPalette::Active,
QPalette::BrightText ) );
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 /
MidiTime::ticksPerTact();
int w = ( sel_pos_end - sel_pos_start ) * m_ppt / MidiTime::ticksPerTact();
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() && GuiApplication::instance()->automationEditor()->hasFocus() )
{
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 / MidiTime::ticksPerTact() );
}
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 value,
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( ( value >= m_bottomLevel && value <= m_topLevel )
|| ( value > m_topLevel && m_topLevel >= 0 )
|| ( value < m_bottomLevel && m_bottomLevel <= 0 ) )
{
int y_start = yCoordOfLevel( value );
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)( value * m_y_delta );
}
QBrush currentColor = is_selected
? QBrush( QColor( 0x00, 0x40, 0xC0 ) )
: graphColor();
p.fillRect( x, y_start, rect_width, rect_height, currentColor );
}
else
{
printf("not in range\n");
}
}
// responsible for moving/resizing scrollbars after window-resizing
void AutomationEditor::resizeEvent(QResizeEvent * re)
{
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() );
}
updateTopBottomLevels();
update();
}
void AutomationEditor::wheelEvent(QWheelEvent * we )
{
we->accept();
if( we->modifiers() & Qt::ControlModifier && we->modifiers() & Qt::ShiftModifier )
{
int y = m_zoomingYModel.value();
if( we->delta() > 0 )
{
y++;
}
if( we->delta() < 0 )
{
y--;
}
y = qBound( 0, y, m_zoomingYModel.size() - 1 );
m_zoomingYModel.setValue( y );
}
else if( we->modifiers() & Qt::ControlModifier && we->modifiers() & Qt::AltModifier )
{
int q = m_quantizeModel.value();
if( we->delta() > 0 )
{
q--;
}
if( we->delta() < 0 )
{
q++;
}
q = qBound( 0, q, m_quantizeModel.size() - 1 );
m_quantizeModel.setValue( q );
update();
}
else if( we->modifiers() & Qt::ControlModifier )
{
int x = m_zoomingXModel.value();
if( we->delta() > 0 )
{
x++;
}
if( we->delta() < 0 )
{
x--;
}
x = qBound( 0, x, m_zoomingXModel.size() - 1 );
m_zoomingXModel.setValue( x );
}
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 + 2 ) ) :
( level_line_y - y ) / (float)m_y_delta ) ) / m_step ) * m_step;
// some range-checking-stuff
level = qBound( m_bottomLevel, level, m_topLevel );
return( level );
}
inline bool AutomationEditor::inBBEditor()
{
QMutexLocker m( &m_patternMutex );
return( validPattern() &&
m_pattern->getTrack()->trackContainer() == 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( gui->pianoRoll()->currentPattern() );
}
else if( Engine::getSong()->isStopped() == false )
{
Engine::getSong()->togglePause();
}
else
{
Engine::getSong()->playPattern( gui->pianoRoll()->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::setEditMode(AutomationEditor::EditModes mode)
{
if (m_editMode == mode)
return;
m_editMode = mode;
switch (mode)
{
case DRAW:
case ERASE:
case SELECT:
removeSelection();
break;
case MOVE:
m_selValuesForMove.clear();
getSelectedValues(m_selValuesForMove);
}
update();
}
void AutomationEditor::setEditMode(int mode)
{
setEditMode((AutomationEditor::EditModes) mode);
}
void AutomationEditor::setProgressionType(AutomationPattern::ProgressionTypes type)
{
if (validPattern())
{
m_pattern->addJournalCheckPoint();
QMutexLocker m(&m_patternMutex);
m_pattern->setProgressionType(type);
Engine::getSong()->setModified();
update();
}
}
void AutomationEditor::setProgressionType(int type)
{
setProgressionType((AutomationPattern::ProgressionTypes) type);
}
void AutomationEditor::setTension()
{
m_pattern->setTension( QString::number( m_tensionModel->value() ) );
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<int>( sel_pos_start, sel_pos_end );
}
float selLevel_start = m_selectStartLevel;
float selLevel_end = selLevel_start + m_selectedLevels;
if( selLevel_start > selLevel_end )
{
qSwap<float>( 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 = MidiTime::ticksPerTact() / 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_pattern->addJournalCheckPoint();
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();
gui->songEditor()->update();
}
void AutomationEditor::pasteValues()
{
QMutexLocker m( &m_patternMutex );
if( validPattern() && !m_valuesToCopy.isEmpty() )
{
m_pattern->addJournalCheckPoint();
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();
gui->songEditor()->update();
}
}
void AutomationEditor::deleteSelectedValues()
{
QMutexLocker m( &m_patternMutex );
if( !validPattern() )
{
return;
}
m_pattern->addJournalCheckPoint();
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();
gui->songEditor()->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 * MidiTime::ticksPerTact() / m_ppt )
{
m_leftRightScroll->setValue( t.getTact() *
MidiTime::ticksPerTact() );
}
else if( t < m_currentPosition )
{
MidiTime t_ = qMax( t - w * MidiTime::ticksPerTact() *
MidiTime::ticksPerTact() / m_ppt, 0 );
m_leftRightScroll->setValue( t_.getTact() *
MidiTime::ticksPerTact() );
}
m_scrollBack = false;
}
}
void AutomationEditor::zoomingXChanged()
{
const QString & zfac = m_zoomingXModel.currentText();
m_ppt = zfac.left( zfac.length() - 1 ).toInt() * DEFAULT_PPT / 100;
assert( m_ppt > 0 );
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 );
}
void AutomationEditor::setQuantization()
{
int quantization = DefaultTicksPerTact / (1 << m_quantizeModel.value());;
AutomationPattern::setQuantization(quantization);
}
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;
}
}
AutomationEditorWindow::AutomationEditorWindow() :
Editor(),
m_editor(new AutomationEditor())
{
setCentralWidget(m_editor);
// Play/stop buttons
m_playAction->setToolTip(tr( "Play/pause current pattern (Space)" ));
m_playAction->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_stopAction->setToolTip(tr("Stop playing of current pattern (Space)"));
m_stopAction->setWhatsThis(
tr( "Click here if you want to stop playing of the "
"current pattern." ) );
// Edit mode buttons
ActionGroup* editModeGroup = new ActionGroup(this);
QAction* drawAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw"), tr("Draw mode (Shift+D)"));
drawAction->setShortcut(Qt::SHIFT | Qt::Key_D);
QAction* eraseAction = editModeGroup->addAction(embed::getIconPixmap("edit_erase"), tr("Erase mode (Shift+E)"));
eraseAction->setShortcut(Qt::SHIFT | Qt::Key_E);
m_flipYAction = new QAction(embed::getIconPixmap("flip_y"), tr("Flip vertically"), this);
m_flipXAction = new QAction(embed::getIconPixmap("flip_x"), tr("Flip horizontally"), this);
m_flipYAction->setWhatsThis(
tr( "Click here and the pattern will be inverted."
"The points are flipped in the y direction. " ) );
m_flipXAction->setWhatsThis(
tr( "Click here and the pattern will be reversed. "
"The points are flipped in the x direction." ) );
// TODO: m_selectButton and m_moveButton are broken.
// m_selectButton = new QAction(embed::getIconPixmap("edit_select"), tr("Select mode (Shift+S)"), editModeGroup);
// m_moveButton = new QAction(embed::getIconPixmap("edit_move"), tr("Move selection mode (Shift+M)"), editModeGroup);
drawAction->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." ) );
eraseAction->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." ) );*/
connect(editModeGroup, SIGNAL(triggered(int)), m_editor, SLOT(setEditMode(int)));
// Progression type buttons
ActionGroup* progression_type_group = new ActionGroup(this);
m_discreteAction = progression_type_group->addAction(
embed::getIconPixmap("progression_discrete"), tr("Discrete progression"));
m_linearAction = progression_type_group->addAction(
embed::getIconPixmap("progression_linear"), tr("Linear progression"));
m_cubicHermiteAction = progression_type_group->addAction(
embed::getIconPixmap("progression_cubic_hermite"), tr( "Cubic Hermite progression"));
connect(progression_type_group, SIGNAL(triggered(int)), m_editor, SLOT(setProgressionType(int)));
// setup tension-stuff
m_tensionKnob = new Knob( knobSmall_17, this, "Tension" );
m_tensionKnob->setModel(m_editor->m_tensionModel);
ToolTip::add(m_tensionKnob, tr("Tension value for spline"));
m_tensionKnob->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."));
connect(m_cubicHermiteAction, SIGNAL(toggled(bool)), m_tensionKnob, SLOT(setEnabled(bool)));
m_discreteAction->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_linearAction->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_cubicHermiteAction->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." ) );
// Copy paste buttons
QAction* cutAction = new QAction(embed::getIconPixmap("edit_cut"),
tr("Cut selected values (Ctrl+X)"), this);
QAction* copyAction = new QAction(embed::getIconPixmap("edit_copy"),
tr("Copy selected values (Ctrl+C)"), this);
QAction* pasteAction = new QAction(embed::getIconPixmap("edit_paste"),
tr("Paste values from clipboard Ctrl+V)"), this);
cutAction->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." ) );
copyAction->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." ) );
pasteAction->setWhatsThis(
tr( "Click here and the values from the clipboard will be "
"pasted at the first visible measure." ) );
cutAction->setShortcut(Qt::CTRL | Qt::Key_X);
copyAction->setShortcut(Qt::CTRL | Qt::Key_C);
pasteAction->setShortcut(Qt::CTRL | Qt::Key_V);
connect(cutAction, SIGNAL(triggered()), m_editor, SLOT(cutSelectedValues()));
connect(copyAction, SIGNAL(triggered()), m_editor, SLOT(copySelectedValues()));
connect(pasteAction, SIGNAL(triggered()), m_editor, SLOT(pasteValues()));
// Zoom controls
QLabel * zoom_x_label = new QLabel( m_toolBar );
zoom_x_label->setPixmap( embed::getIconPixmap( "zoom_x" ) );
m_zoomingXComboBox = new ComboBox( m_toolBar );
m_zoomingXComboBox->setFixedSize( 80, 22 );
for( int i = 0; i < 6; ++i )
{
m_editor->m_zoomingXModel.addItem( QString::number( 25 << i ) + "%" );
}
m_editor->m_zoomingXModel.setValue( m_editor->m_zoomingXModel.findText( "100%" ) );
m_zoomingXComboBox->setModel( &m_editor->m_zoomingXModel );
connect( &m_editor->m_zoomingXModel, SIGNAL( dataChanged() ),
m_editor, SLOT( zoomingXChanged() ) );
QLabel * zoom_y_label = new QLabel( m_toolBar );
zoom_y_label->setPixmap( embed::getIconPixmap( "zoom_y" ) );
m_zoomingYComboBox = new ComboBox( m_toolBar );
m_zoomingYComboBox->setFixedSize( 80, 22 );
m_editor->m_zoomingYModel.addItem( "Auto" );
for( int i = 0; i < 7; ++i )
{
m_editor->m_zoomingYModel.addItem( QString::number( 25 << i ) + "%" );
}
m_editor->m_zoomingYModel.setValue( m_editor->m_zoomingYModel.findText( "Auto" ) );
m_zoomingYComboBox->setModel( &m_editor->m_zoomingYModel );
connect( &m_editor->m_zoomingYModel, SIGNAL( dataChanged() ),
m_editor, SLOT( zoomingYChanged() ) );
// Quantization controls
QLabel * quantize_lbl = new QLabel( m_toolBar );
quantize_lbl->setPixmap( embed::getIconPixmap( "quantize" ) );
m_quantizeComboBox = new ComboBox( m_toolBar );
m_quantizeComboBox->setFixedSize( 60, 22 );
m_quantizeComboBox->setModel( &m_editor->m_quantizeModel );
m_toolBar->addSeparator();;
m_toolBar->addAction(drawAction);
m_toolBar->addAction(eraseAction);
// m_toolBar->addAction(m_selectButton);
// m_toolBar->addAction(m_moveButton);
m_toolBar->addAction(m_flipXAction);
m_toolBar->addAction(m_flipYAction);
m_toolBar->addSeparator();
m_toolBar->addAction(m_discreteAction);
m_toolBar->addAction(m_linearAction);
m_toolBar->addAction(m_cubicHermiteAction);
m_toolBar->addSeparator();
m_toolBar->addWidget( new QLabel( tr("Tension: "), m_toolBar ));
m_toolBar->addWidget( m_tensionKnob );
m_toolBar->addSeparator();
// Select is broken
// m_toolBar->addAction( cutAction );
// m_toolBar->addAction( copyAction );
// m_toolBar->addAction( pasteAction );
m_toolBar->addSeparator();
m_editor->m_timeLine->addToolButtons(m_toolBar);
m_toolBar->addSeparator();
m_toolBar->addWidget( zoom_x_label );
m_toolBar->addWidget( m_zoomingXComboBox );
m_toolBar->addSeparator();
m_toolBar->addWidget( zoom_y_label );
m_toolBar->addWidget( m_zoomingYComboBox );
m_toolBar->addSeparator();
m_toolBar->addWidget( quantize_lbl );
m_toolBar->addWidget( m_quantizeComboBox );
drawAction->setChecked(true);
m_discreteAction->setChecked(true);
// Setup our actual window
setFocusPolicy( Qt::StrongFocus );
setFocus();
setWindowIcon( embed::getIconPixmap( "automation" ) );
setAcceptDrops( true );
m_toolBar->setAcceptDrops( true );
}
AutomationEditorWindow::~AutomationEditorWindow()
{
}
void AutomationEditorWindow::setCurrentPattern(AutomationPattern* pattern)
{
// Disconnect our old pattern
if (currentPattern() != nullptr)
{
m_editor->m_pattern->disconnect(this);
m_flipXAction->disconnect();
m_flipYAction->disconnect();
}
m_editor->setCurrentPattern(pattern);
// Set our window's title
if (pattern == nullptr)
{
setWindowTitle( tr( "Automation Editor - no pattern" ) );
return;
}
setWindowTitle( tr( "Automation Editor - %1" ).arg( m_editor->m_pattern->name() ) );
switch(m_editor->m_pattern->progressionType())
{
case AutomationPattern::DiscreteProgression:
m_discreteAction->setChecked(true);
break;
case AutomationPattern::LinearProgression:
m_linearAction->setChecked(true);
break;
case AutomationPattern::CubicHermiteProgression:
m_cubicHermiteAction->setChecked(true);
break;
}
// Connect new pattern
if (pattern)
{
connect(pattern, SIGNAL(dataChanged()), this, SLOT(update()));
connect(pattern, SIGNAL(destroyed()), this, SLOT(clearCurrentPattern()));
connect(m_flipXAction, SIGNAL(triggered()), pattern, SLOT(flipX()));
connect(m_flipYAction, SIGNAL(triggered()), pattern, SLOT(flipY()));
}
emit currentPatternChanged();
}
const AutomationPattern* AutomationEditorWindow::currentPattern()
{
return m_editor->currentPattern();
}
void AutomationEditorWindow::dropEvent( QDropEvent *_de )
{
QString type = StringPairDrag::decodeKey( _de );
QString val = StringPairDrag::decodeValue( _de );
if( type == "automatable_model" )
{
AutomatableModel * mod = dynamic_cast<AutomatableModel *>(
Engine::projectJournal()->
journallingObject( val.toInt() ) );
if( mod != NULL )
{
m_editor->m_pattern->addObject( mod );
setCurrentPattern( m_editor->m_pattern );
}
}
update();
}
void AutomationEditorWindow::dragEnterEvent( QDragEnterEvent *_dee )
{
StringPairDrag::processDragEnterEvent( _dee, "automatable_model" );
}
void AutomationEditorWindow::open(AutomationPattern* pattern)
{
setCurrentPattern(pattern);
parentWidget()->show();
show();
setFocus();
}
QSize AutomationEditorWindow::sizeHint() const
{
return {INITIAL_WIDTH, INITIAL_HEIGHT};
}
void AutomationEditorWindow::clearCurrentPattern()
{
m_editor->m_pattern = nullptr;
setCurrentPattern(nullptr);
}
void AutomationEditorWindow::play()
{
m_editor->play();
setPauseIcon(Engine::getSong()->isPlaying());
}
void AutomationEditorWindow::stop()
{
m_editor->stop();
}