1 /*
2  * AutomationEditor.cpp - implementation of AutomationEditor which is used for
3  *						actual setting of dynamic values
4  *
5  * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
6  * Copyright (c) 2008-2013 Paul Giblock <pgib/at/users.sourceforge.net>
7  * Copyright (c) 2006-2008 Javier Serrano Polo <jasp00/at/users.sourceforge.net>
8  *
9  * This file is part of LMMS - https://lmms.io
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public
22  * License along with this program (see COPYING); if not, write to the
23  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24  * Boston, MA 02110-1301 USA.
25  *
26  */
27 
28 #include "AutomationEditor.h"
29 
30 #include <cmath>
31 
32 #include <QApplication>
33 #include <QKeyEvent>
34 #include <QLabel>
35 #include <QLayout>
36 #include <QMdiArea>
37 #include <QPainter>
38 #include <QPainterPath>
39 #include <QScrollBar>
40 #include <QStyleOption>
41 #include <QToolTip>
42 
43 #ifndef __USE_XOPEN
44 #define __USE_XOPEN
45 #endif
46 
47 #include "ActionGroup.h"
48 #include "SongEditor.h"
49 #include "MainWindow.h"
50 #include "GuiApplication.h"
51 #include "embed.h"
52 #include "Engine.h"
53 #include "gui_templates.h"
54 #include "TimeLineWidget.h"
55 #include "ToolTip.h"
56 #include "TextFloat.h"
57 #include "ComboBox.h"
58 #include "BBTrackContainer.h"
59 #include "PianoRoll.h"
60 #include "debug.h"
61 #include "StringPairDrag.h"
62 #include "ProjectJournal.h"
63 
64 
65 QPixmap * AutomationEditor::s_toolDraw = NULL;
66 QPixmap * AutomationEditor::s_toolErase = NULL;
67 QPixmap * AutomationEditor::s_toolSelect = NULL;
68 QPixmap * AutomationEditor::s_toolMove = NULL;
69 QPixmap * AutomationEditor::s_toolYFlip = NULL;
70 QPixmap * AutomationEditor::s_toolXFlip = NULL;
71 
72 const QVector<double> AutomationEditor::m_zoomXLevels =
73 		{ 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f };
74 
75 
76 
AutomationEditor()77 AutomationEditor::AutomationEditor() :
78 	QWidget(),
79 	m_zoomingXModel(),
80 	m_zoomingYModel(),
81 	m_quantizeModel(),
82 	m_patternMutex( QMutex::Recursive ),
83 	m_pattern( NULL ),
84 	m_minLevel( 0 ),
85 	m_maxLevel( 0 ),
86 	m_step( 1 ),
87 	m_scrollLevel( 0 ),
88 	m_bottomLevel( 0 ),
89 	m_topLevel( 0 ),
90 	m_currentPosition(),
91 	m_action( NONE ),
92 	m_moveStartLevel( 0 ),
93 	m_moveStartTick( 0 ),
94 	m_drawLastLevel( 0.0f ),
95 	m_drawLastTick( 0 ),
96 	m_ppt( DEFAULT_PPT ),
97 	m_y_delta( DEFAULT_Y_DELTA ),
98 	m_y_auto( true ),
99 	m_editMode( DRAW ),
100 	m_mouseDownRight( false ),
101 	m_scrollBack( false ),
102 	m_barLineColor( 0, 0, 0 ),
103 	m_beatLineColor( 0, 0, 0 ),
104 	m_lineColor( 0, 0, 0 ),
105 	m_graphColor( Qt::SolidPattern ),
106 	m_vertexColor( 0,0,0 ),
107 	m_scaleColor( Qt::SolidPattern ),
108 	m_crossColor( 0, 0, 0 ),
109 	m_backgroundShade( 0, 0, 0 )
110 {
111 	connect( this, SIGNAL( currentPatternChanged() ),
112 				this, SLOT( updateAfterPatternChange() ),
113 				Qt::QueuedConnection );
114 	connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int, int ) ),
115 						this, SLOT( update() ) );
116 
117 	setAttribute( Qt::WA_OpaquePaintEvent, true );
118 
119 	//keeps the direction of the widget, undepended on the locale
120 	setLayoutDirection( Qt::LeftToRight );
121 
122 	m_tensionModel = new FloatModel(1.0, 0.0, 1.0, 0.01);
123 	connect( m_tensionModel, SIGNAL( dataChanged() ),
124 				this, SLOT( setTension() ) );
125 
126 	for( int i = 0; i < 7; ++i )
127 	{
128 		m_quantizeModel.addItem( "1/" + QString::number( 1 << i ) );
129 	}
130 	for( int i = 0; i < 5; ++i )
131 	{
132 		m_quantizeModel.addItem( "1/" +
133 					QString::number( ( 1 << i ) * 3 ) );
134 	}
135 	m_quantizeModel.addItem( "1/192" );
136 
137 	connect( &m_quantizeModel, SIGNAL(dataChanged() ),
138 					this, SLOT( setQuantization() ) );
139 	m_quantizeModel.setValue( m_quantizeModel.findText( "1/8" ) );
140 
141 	if( s_toolYFlip == NULL )
142 	{
143 		s_toolYFlip = new QPixmap( embed::getIconPixmap(
144 							"flip_y" ) );
145 	}
146 	if( s_toolXFlip == NULL )
147 	{
148 		s_toolXFlip = new QPixmap( embed::getIconPixmap(
149 							"flip_x" ) );
150 	}
151 
152 	// add time-line
153 	m_timeLine = new TimeLineWidget( VALUES_WIDTH, 0, m_ppt,
154 				Engine::getSong()->getPlayPos(
155 					Song::Mode_PlayAutomationPattern ),
156 						m_currentPosition, this );
157 	connect( this, SIGNAL( positionChanged( const MidiTime & ) ),
158 		m_timeLine, SLOT( updatePosition( const MidiTime & ) ) );
159 	connect( m_timeLine, SIGNAL( positionChanged( const MidiTime & ) ),
160 			this, SLOT( updatePosition( const MidiTime & ) ) );
161 
162 	removeSelection();
163 
164 	// init scrollbars
165 	m_leftRightScroll = new QScrollBar( Qt::Horizontal, this );
166 	m_leftRightScroll->setSingleStep( 1 );
167 	connect( m_leftRightScroll, SIGNAL( valueChanged( int ) ), this,
168 						SLOT( horScrolled( int ) ) );
169 
170 	m_topBottomScroll = new QScrollBar( Qt::Vertical, this );
171 	m_topBottomScroll->setSingleStep( 1 );
172 	m_topBottomScroll->setPageStep( 20 );
173 	connect( m_topBottomScroll, SIGNAL( valueChanged( int ) ), this,
174 						SLOT( verScrolled( int ) ) );
175 
176 	// init pixmaps
177 	if( s_toolDraw == NULL )
178 	{
179 		s_toolDraw = new QPixmap( embed::getIconPixmap(
180 							"edit_draw" ) );
181 	}
182 	if( s_toolErase == NULL )
183 	{
184 		s_toolErase= new QPixmap( embed::getIconPixmap(
185 							"edit_erase" ) );
186 	}
187 	if( s_toolSelect == NULL )
188 	{
189 		s_toolSelect = new QPixmap( embed::getIconPixmap(
190 							"edit_select" ) );
191 	}
192 	if( s_toolMove == NULL )
193 	{
194 		s_toolMove = new QPixmap( embed::getIconPixmap(
195 							"edit_move" ) );
196 	}
197 
198 	setCurrentPattern( NULL );
199 
200 	setMouseTracking( true );
201 }
202 
203 
204 
205 
~AutomationEditor()206 AutomationEditor::~AutomationEditor()
207 {
208 	m_zoomingXModel.disconnect();
209 	m_zoomingYModel.disconnect();
210 	m_quantizeModel.disconnect();
211 	m_tensionModel->disconnect();
212 
213 	delete m_tensionModel;
214 }
215 
216 
217 
218 
setCurrentPattern(AutomationPattern * new_pattern)219 void AutomationEditor::setCurrentPattern(AutomationPattern * new_pattern )
220 {
221 	if (m_pattern)
222 	{
223 		m_pattern->disconnect(this);
224 	}
225 
226 	m_patternMutex.lock();
227 	m_pattern = new_pattern;
228 	m_patternMutex.unlock();
229 
230 	if (m_pattern != nullptr)
231 	{
232 		connect(m_pattern, SIGNAL(dataChanged()), this, SLOT(update()));
233 	}
234 
235 	emit currentPatternChanged();
236 }
237 
238 
239 
240 
saveSettings(QDomDocument & doc,QDomElement & dom_parent)241 void AutomationEditor::saveSettings(QDomDocument & doc, QDomElement & dom_parent)
242 {
243 	MainWindow::saveWidgetState( parentWidget(), dom_parent );
244 }
245 
246 
247 
248 
loadSettings(const QDomElement & dom_parent)249 void AutomationEditor::loadSettings( const QDomElement & dom_parent)
250 {
251 	MainWindow::restoreWidgetState(parentWidget(), dom_parent);
252 }
253 
254 
255 
256 // qproperty access methods
257 
barLineColor() const258 QColor AutomationEditor::barLineColor() const
259 { return m_barLineColor; }
260 
setBarLineColor(const QColor & c)261 void AutomationEditor::setBarLineColor( const QColor & c )
262 { m_barLineColor = c; }
263 
beatLineColor() const264 QColor AutomationEditor::beatLineColor() const
265 { return m_beatLineColor; }
266 
setBeatLineColor(const QColor & c)267 void AutomationEditor::setBeatLineColor( const QColor & c )
268 { m_beatLineColor = c; }
269 
lineColor() const270 QColor AutomationEditor::lineColor() const
271 { return m_lineColor; }
272 
setLineColor(const QColor & c)273 void AutomationEditor::setLineColor( const QColor & c )
274 { m_lineColor = c; }
275 
graphColor() const276 QBrush AutomationEditor::graphColor() const
277 { return m_graphColor; }
278 
setGraphColor(const QBrush & c)279 void AutomationEditor::setGraphColor( const QBrush & c )
280 { m_graphColor = c; }
281 
vertexColor() const282 QColor AutomationEditor::vertexColor() const
283 { return m_vertexColor; }
284 
setVertexColor(const QColor & c)285 void AutomationEditor::setVertexColor( const QColor & c )
286 { m_vertexColor = c; }
287 
scaleColor() const288 QBrush AutomationEditor::scaleColor() const
289 { return m_scaleColor; }
290 
setScaleColor(const QBrush & c)291 void AutomationEditor::setScaleColor( const QBrush & c )
292 { m_scaleColor = c; }
293 
crossColor() const294 QColor AutomationEditor::crossColor() const
295 { return m_crossColor; }
296 
setCrossColor(const QColor & c)297 void AutomationEditor::setCrossColor( const QColor & c )
298 { m_crossColor = c; }
299 
backgroundShade() const300 QColor AutomationEditor::backgroundShade() const
301 { return m_backgroundShade; }
302 
setBackgroundShade(const QColor & c)303 void AutomationEditor::setBackgroundShade( const QColor & c )
304 { m_backgroundShade = c; }
305 
306 
307 
308 
updateAfterPatternChange()309 void AutomationEditor::updateAfterPatternChange()
310 {
311 	QMutexLocker m( &m_patternMutex );
312 
313 	m_currentPosition = 0;
314 
315 	if( !validPattern() )
316 	{
317 		m_minLevel = m_maxLevel = m_scrollLevel = 0;
318 		m_step = 1;
319 		resizeEvent( NULL );
320 		return;
321 	}
322 
323 	m_minLevel = m_pattern->firstObject()->minValue<float>();
324 	m_maxLevel = m_pattern->firstObject()->maxValue<float>();
325 	m_step = m_pattern->firstObject()->step<float>();
326 	m_scrollLevel = ( m_minLevel + m_maxLevel ) / 2;
327 
328 	m_tensionModel->setValue( m_pattern->getTension() );
329 
330 	// resizeEvent() does the rest for us (scrolling, range-checking
331 	// of levels and so on...)
332 	resizeEvent( NULL );
333 
334 	update();
335 }
336 
337 
338 
339 
update()340 void AutomationEditor::update()
341 {
342 	QWidget::update();
343 
344 	QMutexLocker m( &m_patternMutex );
345 	// Note detuning?
346 	if( m_pattern && !m_pattern->getTrack() )
347 	{
348 		gui->pianoRoll()->update();
349 	}
350 }
351 
352 
353 
354 
removeSelection()355 void AutomationEditor::removeSelection()
356 {
357 	m_selectStartTick = 0;
358 	m_selectedTick = 0;
359 	m_selectStartLevel = 0;
360 	m_selectedLevels = 0;
361 }
362 
363 
364 
365 
keyPressEvent(QKeyEvent * ke)366 void AutomationEditor::keyPressEvent(QKeyEvent * ke )
367 {
368 	switch( ke->key() )
369 	{
370 		case Qt::Key_Up:
371 			m_topBottomScroll->setValue(
372 					m_topBottomScroll->value() - 1 );
373 			ke->accept();
374 			break;
375 
376 		case Qt::Key_Down:
377 			m_topBottomScroll->setValue(
378 					m_topBottomScroll->value() + 1 );
379 			ke->accept();
380 			break;
381 
382 		case Qt::Key_Left:
383 			if( ( m_timeLine->pos() -= 16 ) < 0 )
384 			{
385 				m_timeLine->pos().setTicks( 0 );
386 			}
387 			m_timeLine->updatePosition();
388 			ke->accept();
389 			break;
390 
391 		case Qt::Key_Right:
392 			m_timeLine->pos() += 16;
393 			m_timeLine->updatePosition();
394 			ke->accept();
395 			break;
396 
397 		//TODO: m_selectButton and m_moveButton are broken.
398 		/*case Qt::Key_A:
399 			if( ke->modifiers() & Qt::ControlModifier )
400 			{
401 				m_selectButton->setChecked( true );
402 				selectAll();
403 				update();
404 				ke->accept();
405 			}
406 			break;
407 
408 		case Qt::Key_Backspace:
409 		case Qt::Key_Delete:
410 			deleteSelectedValues();
411 			ke->accept();
412 			break;*/
413 
414 		case Qt::Key_Home:
415 			m_timeLine->pos().setTicks( 0 );
416 			m_timeLine->updatePosition();
417 			ke->accept();
418 			break;
419 
420 		default:
421 			break;
422 	}
423 }
424 
425 
426 
427 
leaveEvent(QEvent * e)428 void AutomationEditor::leaveEvent(QEvent * e )
429 {
430 	while( QApplication::overrideCursor() != NULL )
431 	{
432 		QApplication::restoreOverrideCursor();
433 	}
434 	QWidget::leaveEvent( e );
435 	update();
436 }
437 
438 
drawLine(int x0In,float y0,int x1In,float y1)439 void AutomationEditor::drawLine( int x0In, float y0, int x1In, float y1 )
440 {
441 	int x0 = Note::quantized( x0In, AutomationPattern::quantization() );
442 	int x1 = Note::quantized( x1In, AutomationPattern::quantization() );
443 	int deltax = qAbs( x1 - x0 );
444 	float deltay = qAbs<float>( y1 - y0 );
445 	int x = x0;
446 	float y = y0;
447 	int xstep;
448 	int ystep;
449 
450 	if( deltax < AutomationPattern::quantization() )
451 	{
452 		return;
453 	}
454 
455 	deltax /= AutomationPattern::quantization();
456 
457 	float yscale = deltay / ( deltax );
458 
459 	if( x0 < x1 )
460 	{
461 		xstep = AutomationPattern::quantization();
462 	}
463 	else
464 	{
465 		xstep = -( AutomationPattern::quantization() );
466 	}
467 
468 	float lineAdjust;
469 	if( y0 < y1 )
470 	{
471 		ystep = 1;
472 		lineAdjust = yscale;
473 	}
474 	else
475 	{
476 		ystep = -1;
477 		lineAdjust = -( yscale );
478 	}
479 
480 	int i = 0;
481 	while( i < deltax )
482 	{
483 		y = y0 + ( ystep * yscale * i ) + lineAdjust;
484 
485 		x += xstep;
486 		i += 1;
487 		m_pattern->removeValue( MidiTime( x ) );
488 		m_pattern->putValue( MidiTime( x ), y );
489 	}
490 }
491 
492 
493 
494 
mousePressEvent(QMouseEvent * mouseEvent)495 void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent )
496 {
497 	QMutexLocker m( &m_patternMutex );
498 	if( !validPattern() )
499 	{
500 		return;
501 	}
502 	if( mouseEvent->y() > TOP_MARGIN )
503 	{
504 		float level = getLevel( mouseEvent->y() );
505 
506 		int x = mouseEvent->x();
507 
508 		if( x > VALUES_WIDTH )
509 		{
510 			// set or move value
511 
512 			x -= VALUES_WIDTH;
513 
514 			// get tick in which the user clicked
515 			int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
516 							m_currentPosition;
517 
518 			// get time map of current pattern
519 			timeMap & time_map = m_pattern->getTimeMap();
520 
521 			// will be our iterator in the following loop
522 			timeMap::iterator it = time_map.begin();
523 
524 			// loop through whole time-map...
525 			while( it != time_map.end() )
526 			{
527 				// and check whether the user clicked on an
528 				// existing value
529 				if( pos_ticks >= it.key() &&
530 					( it+1==time_map.end() ||
531 						pos_ticks <= (it+1).key() ) &&
532 		( pos_ticks<= it.key() + MidiTime::ticksPerTact() *4 / m_ppt ) &&
533 		( level == it.value() || mouseEvent->button() == Qt::RightButton ) )
534 				{
535 					break;
536 				}
537 				++it;
538 			}
539 
540 			if( mouseEvent->button() == Qt::RightButton )
541 			{
542 				m_mouseDownRight = true;
543 			}
544 
545 			// left button??
546 			if( mouseEvent->button() == Qt::LeftButton &&
547 							m_editMode == DRAW )
548 			{
549 				m_pattern->addJournalCheckPoint();
550 				// Connect the dots
551 				if( mouseEvent->modifiers() & Qt::ShiftModifier )
552 				{
553 					drawLine( m_drawLastTick,
554 							m_drawLastLevel,
555 							pos_ticks, level );
556 				}
557 				m_drawLastTick = pos_ticks;
558 				m_drawLastLevel = level;
559 
560 				// did it reach end of map because
561 				// there's no value??
562 				if( it == time_map.end() )
563 				{
564 					// then set new value
565 					MidiTime value_pos( pos_ticks );
566 
567 					MidiTime new_time =
568 						m_pattern->setDragValue( value_pos,
569 									level, true,
570 							mouseEvent->modifiers() &
571 								Qt::ControlModifier );
572 
573 					// reset it so that it can be used for
574 					// ops (move, resize) after this
575 					// code-block
576 					it = time_map.find( new_time );
577 				}
578 
579 				// move it
580 				m_action = MOVE_VALUE;
581 				int aligned_x = (int)( (float)( (
582 						it.key() -
583 						m_currentPosition ) *
584 						m_ppt ) / MidiTime::ticksPerTact() );
585 				m_moveXOffset = x - aligned_x - 1;
586 				// set move-cursor
587 				QCursor c( Qt::SizeAllCursor );
588 				QApplication::setOverrideCursor( c );
589 
590 				Engine::getSong()->setModified();
591 			}
592 			else if( ( mouseEvent->button() == Qt::RightButton &&
593 							m_editMode == DRAW ) ||
594 					m_editMode == ERASE )
595 			{
596 				m_drawLastTick = pos_ticks;
597 				m_pattern->addJournalCheckPoint();
598 				// erase single value
599 				if( it != time_map.end() )
600 				{
601 					m_pattern->removeValue( it.key() );
602 					Engine::getSong()->setModified();
603 				}
604 				m_action = NONE;
605 			}
606 			else if( mouseEvent->button() == Qt::LeftButton &&
607 							m_editMode == SELECT )
608 			{
609 				// select an area of values
610 
611 				m_selectStartTick = pos_ticks;
612 				m_selectedTick = 0;
613 				m_selectStartLevel = level;
614 				m_selectedLevels = 1;
615 				m_action = SELECT_VALUES;
616 			}
617 			else if( mouseEvent->button() == Qt::RightButton &&
618 							m_editMode == SELECT )
619 			{
620 				// when clicking right in select-move, we
621 				// switch to move-mode
622 				//m_moveButton->setChecked( true );
623 			}
624 			else if( mouseEvent->button() == Qt::LeftButton &&
625 							m_editMode == MOVE )
626 			{
627 				m_pattern->addJournalCheckPoint();
628 				// move selection (including selected values)
629 
630 				// save position where move-process began
631 				m_moveStartTick = pos_ticks;
632 				m_moveStartLevel = level;
633 
634 				m_action = MOVE_SELECTION;
635 
636 				Engine::getSong()->setModified();
637 			}
638 			else if( mouseEvent->button() == Qt::RightButton &&
639 							m_editMode == MOVE )
640 			{
641 				// when clicking right in select-move, we
642 				// switch to draw-mode
643 				//m_drawButton->setChecked( true );
644 			}
645 
646 			update();
647 		}
648 	}
649 }
650 
651 
652 
653 
mouseReleaseEvent(QMouseEvent * mouseEvent)654 void AutomationEditor::mouseReleaseEvent(QMouseEvent * mouseEvent )
655 {
656 	bool mustRepaint = false;
657 
658 	if ( mouseEvent->button() == Qt::RightButton )
659 	{
660 		m_mouseDownRight = false;
661 		mustRepaint = true;
662 	}
663 
664 	if( mouseEvent->button() == Qt::LeftButton )
665 	{
666 		mustRepaint = true;
667 	}
668 
669 	if( m_editMode == DRAW )
670 	{
671 		if( m_action == MOVE_VALUE )
672 		{
673 			m_pattern->applyDragValue();
674 		}
675 		QApplication::restoreOverrideCursor();
676 	}
677 
678 	m_action = NONE;
679 
680 	if( mustRepaint )
681 	{
682 		repaint();
683 	}
684 }
685 
686 
687 
688 
removePoints(int x0,int x1)689 void AutomationEditor::removePoints( int x0, int x1 )
690 {
691 	int deltax = qAbs( x1 - x0 );
692 	int x = x0;
693 	int xstep;
694 
695 	if( deltax < 1 )
696 	{
697 		return;
698 	}
699 
700 	if( x0 < x1 )
701 	{
702 		xstep = 1;
703 	}
704 	else
705 	{
706 		xstep = -1;
707 	}
708 
709 	int i = 0;
710 	while( i <= deltax )
711 	{
712 		m_pattern->removeValue( MidiTime( x ) );
713 		x += xstep;
714 		i += 1;
715 	}
716 }
717 
718 
719 
720 
mouseMoveEvent(QMouseEvent * mouseEvent)721 void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent )
722 {
723 	QMutexLocker m( &m_patternMutex );
724 	if( !validPattern() )
725 	{
726 		update();
727 		return;
728 	}
729 
730 	if( mouseEvent->y() > TOP_MARGIN )
731 	{
732 		float level = getLevel( mouseEvent->y() );
733 		int x = mouseEvent->x();
734 
735 		x -= VALUES_WIDTH;
736 		if( m_action == MOVE_VALUE )
737 		{
738 			x -= m_moveXOffset;
739 		}
740 
741 		int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
742 							m_currentPosition;
743 		if( mouseEvent->buttons() & Qt::LeftButton && m_editMode == DRAW )
744 		{
745 			if( m_action == MOVE_VALUE )
746 			{
747 				// moving value
748 				if( pos_ticks < 0 )
749 				{
750 					pos_ticks = 0;
751 				}
752 
753 				drawLine( m_drawLastTick, m_drawLastLevel,
754 							pos_ticks, level );
755 
756 				m_drawLastTick = pos_ticks;
757 				m_drawLastLevel = level;
758 
759 				// we moved the value so the value has to be
760 				// moved properly according to new starting-
761 				// time in the time map of pattern
762 				m_pattern->setDragValue( MidiTime( pos_ticks ),
763 								level, true,
764 							mouseEvent->modifiers() &
765 								Qt::ControlModifier );
766 			}
767 
768 			Engine::getSong()->setModified();
769 
770 		}
771 		else if( ( mouseEvent->buttons() & Qt::RightButton &&
772 						m_editMode == DRAW ) ||
773 				( mouseEvent->buttons() & Qt::LeftButton &&
774 						m_editMode == ERASE ) )
775 		{
776 			// removing automation point
777 			if( pos_ticks < 0 )
778 			{
779 				pos_ticks = 0;
780 			}
781 			removePoints( m_drawLastTick, pos_ticks );
782 			Engine::getSong()->setModified();
783 		}
784 		else if( mouseEvent->buttons() & Qt::NoButton && m_editMode == DRAW )
785 		{
786 			// set move- or resize-cursor
787 
788 			// get time map of current pattern
789 			timeMap & time_map = m_pattern->getTimeMap();
790 
791 			// will be our iterator in the following loop
792 			timeMap::iterator it = time_map.begin();
793 			// loop through whole time map...
794 			for( ; it != time_map.end(); ++it )
795 			{
796 				// and check whether the cursor is over an
797 				// existing value
798 				if( pos_ticks >= it.key() &&
799 					( it+1==time_map.end() ||
800 						pos_ticks <= (it+1).key() ) &&
801 							level <= it.value() )
802 				{
803 					break;
804 				}
805 			}
806 
807 			// did it reach end of map because there's
808 			// no value??
809 			if( it != time_map.end() )
810 			{
811 				if( QApplication::overrideCursor() )
812 				{
813 					if( QApplication::overrideCursor()->shape() != Qt::SizeAllCursor )
814 					{
815 						while( QApplication::overrideCursor() != NULL )
816 						{
817 							QApplication::restoreOverrideCursor();
818 						}
819 
820 						QCursor c( Qt::SizeAllCursor );
821 						QApplication::setOverrideCursor(
822 									c );
823 					}
824 				}
825 				else
826 				{
827 					QCursor c( Qt::SizeAllCursor );
828 					QApplication::setOverrideCursor( c );
829 				}
830 			}
831 			else
832 			{
833 				// the cursor is over no value, so restore
834 				// cursor
835 				while( QApplication::overrideCursor() != NULL )
836 				{
837 					QApplication::restoreOverrideCursor();
838 				}
839 			}
840 		}
841 		else if( mouseEvent->buttons() & Qt::LeftButton &&
842 						m_editMode == SELECT &&
843 						m_action == SELECT_VALUES )
844 		{
845 
846 			// change size of selection
847 
848 			if( x < 0 && m_currentPosition > 0 )
849 			{
850 				x = 0;
851 				QCursor::setPos( mapToGlobal( QPoint(
852 						VALUES_WIDTH, mouseEvent->y() ) ) );
853 				if( m_currentPosition >= 4 )
854 				{
855 					m_leftRightScroll->setValue(
856 							m_currentPosition - 4 );
857 				}
858 				else
859 				{
860 					m_leftRightScroll->setValue( 0 );
861 				}
862 			}
863 			else if( x > width() - VALUES_WIDTH )
864 			{
865 				x = width() - VALUES_WIDTH;
866 				QCursor::setPos( mapToGlobal( QPoint( width(),
867 								mouseEvent->y() ) ) );
868 				m_leftRightScroll->setValue( m_currentPosition +
869 									4 );
870 			}
871 
872 			// get tick in which the cursor is posated
873 			int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
874 							m_currentPosition;
875 
876 			m_selectedTick = pos_ticks - m_selectStartTick;
877 			if( (int) m_selectStartTick + m_selectedTick < 0 )
878 			{
879 				m_selectedTick = -m_selectStartTick;
880 			}
881 			m_selectedLevels = level - m_selectStartLevel;
882 			if( level <= m_selectStartLevel )
883 			{
884 				--m_selectedLevels;
885 			}
886 		}
887 		else if( mouseEvent->buttons() & Qt::LeftButton &&
888 					m_editMode == MOVE &&
889 					m_action == MOVE_SELECTION )
890 		{
891 			// move selection + selected values
892 
893 			// do horizontal move-stuff
894 			int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
895 							m_currentPosition;
896 			int ticks_diff = pos_ticks -
897 							m_moveStartTick;
898 			if( m_selectedTick > 0 )
899 			{
900 				if( (int) m_selectStartTick +
901 							ticks_diff < 0 )
902 				{
903 					ticks_diff = -m_selectStartTick;
904 				}
905 			}
906 			else
907 			{
908 				if( (int) m_selectStartTick +
909 					m_selectedTick + ticks_diff <
910 									0 )
911 				{
912 					ticks_diff = -(
913 							m_selectStartTick +
914 							m_selectedTick );
915 				}
916 			}
917 			m_selectStartTick += ticks_diff;
918 
919 			int tact_diff = ticks_diff / MidiTime::ticksPerTact();
920 			ticks_diff = ticks_diff % MidiTime::ticksPerTact();
921 
922 
923 			// do vertical move-stuff
924 			float level_diff = level - m_moveStartLevel;
925 
926 			if( m_selectedLevels > 0 )
927 			{
928 				if( m_selectStartLevel + level_diff
929 								< m_minLevel )
930 				{
931 					level_diff = m_minLevel -
932 							m_selectStartLevel;
933 				}
934 				else if( m_selectStartLevel + m_selectedLevels +
935 						level_diff > m_maxLevel )
936 				{
937 					level_diff = m_maxLevel -
938 							m_selectStartLevel -
939 							m_selectedLevels;
940 				}
941 			}
942 			else
943 			{
944 				if( m_selectStartLevel + m_selectedLevels +
945 						level_diff < m_minLevel )
946 				{
947 					level_diff = m_minLevel -
948 							m_selectStartLevel -
949 							m_selectedLevels;
950 				}
951 				else if( m_selectStartLevel + level_diff >
952 								m_maxLevel )
953 				{
954 					level_diff = m_maxLevel -
955 							m_selectStartLevel;
956 				}
957 			}
958 			m_selectStartLevel += level_diff;
959 
960 
961 			timeMap new_selValuesForMove;
962 			for( timeMap::iterator it = m_selValuesForMove.begin();
963 					it != m_selValuesForMove.end(); ++it )
964 			{
965 				MidiTime new_value_pos;
966 				if( it.key() )
967 				{
968 					int value_tact =
969 						( it.key() /
970 							MidiTime::ticksPerTact() )
971 								+ tact_diff;
972 					int value_ticks =
973 						( it.key() %
974 							MidiTime::ticksPerTact() )
975 								+ ticks_diff;
976 					// ensure value_ticks range
977 					if( value_ticks / MidiTime::ticksPerTact() )
978 					{
979 						value_tact += value_ticks
980 							/ MidiTime::ticksPerTact();
981 						value_ticks %=
982 							MidiTime::ticksPerTact();
983 					}
984 					m_pattern->removeValue( it.key() );
985 					new_value_pos = MidiTime( value_tact,
986 							value_ticks );
987 				}
988 				new_selValuesForMove[
989 					m_pattern->putValue( new_value_pos,
990 						it.value () + level_diff,
991 									false )]
992 						= it.value() + level_diff;
993 			}
994 			m_selValuesForMove = new_selValuesForMove;
995 
996 			m_moveStartTick = pos_ticks;
997 			m_moveStartLevel = level;
998 		}
999 	}
1000 	else
1001 	{
1002 		if( mouseEvent->buttons() & Qt::LeftButton &&
1003 					m_editMode == SELECT &&
1004 					m_action == SELECT_VALUES )
1005 		{
1006 
1007 			int x = mouseEvent->x() - VALUES_WIDTH;
1008 			if( x < 0 && m_currentPosition > 0 )
1009 			{
1010 				x = 0;
1011 				QCursor::setPos( mapToGlobal( QPoint( VALUES_WIDTH,
1012 								mouseEvent->y() ) ) );
1013 				if( m_currentPosition >= 4 )
1014 				{
1015 					m_leftRightScroll->setValue(
1016 							m_currentPosition - 4 );
1017 				}
1018 				else
1019 				{
1020 					m_leftRightScroll->setValue( 0 );
1021 				}
1022 			}
1023 			else if( x > width() - VALUES_WIDTH )
1024 			{
1025 				x = width() - VALUES_WIDTH;
1026 				QCursor::setPos( mapToGlobal( QPoint( width(),
1027 							mouseEvent->y() ) ) );
1028 				m_leftRightScroll->setValue( m_currentPosition +
1029 									4 );
1030 			}
1031 
1032 			// get tick in which the cursor is posated
1033 			int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
1034 							m_currentPosition;
1035 
1036 			m_selectedTick = pos_ticks -
1037 							m_selectStartTick;
1038 			if( (int) m_selectStartTick + m_selectedTick <
1039 									0 )
1040 			{
1041 				m_selectedTick = -m_selectStartTick;
1042 			}
1043 
1044 			float level = getLevel( mouseEvent->y() );
1045 
1046 			if( level <= m_bottomLevel )
1047 			{
1048 				QCursor::setPos( mapToGlobal( QPoint( mouseEvent->x(),
1049 							height() -
1050 							SCROLLBAR_SIZE ) ) );
1051 				m_topBottomScroll->setValue(
1052 					m_topBottomScroll->value() + 1 );
1053 				level = m_bottomLevel;
1054 			}
1055 			else if( level >= m_topLevel )
1056 			{
1057 				QCursor::setPos( mapToGlobal( QPoint( mouseEvent->x(),
1058 							TOP_MARGIN ) ) );
1059 				m_topBottomScroll->setValue(
1060 					m_topBottomScroll->value() - 1 );
1061 				level = m_topLevel;
1062 			}
1063 			m_selectedLevels = level - m_selectStartLevel;
1064 			if( level <= m_selectStartLevel )
1065 			{
1066 				--m_selectedLevels;
1067 			}
1068 		}
1069 		QApplication::restoreOverrideCursor();
1070 	}
1071 
1072 	update();
1073 }
1074 
1075 
1076 
1077 
drawCross(QPainter & p)1078 inline void AutomationEditor::drawCross( QPainter & p )
1079 {
1080 	QPoint mouse_pos = mapFromGlobal( QCursor::pos() );
1081 	int grid_bottom = height() - SCROLLBAR_SIZE - 1;
1082 	float level = getLevel( mouse_pos.y() );
1083 	float cross_y = m_y_auto ?
1084 		grid_bottom - ( ( grid_bottom - TOP_MARGIN )
1085 				* ( level - m_minLevel )
1086 				/ (float)( m_maxLevel - m_minLevel ) ) :
1087 		grid_bottom - ( level - m_bottomLevel ) * m_y_delta;
1088 
1089 	p.setPen( crossColor() );
1090 	p.drawLine( VALUES_WIDTH, (int) cross_y, width(), (int) cross_y );
1091 	p.drawLine( mouse_pos.x(), TOP_MARGIN, mouse_pos.x(), height() - SCROLLBAR_SIZE );
1092 
1093 
1094 	QPoint tt_pos =  QCursor::pos();
1095 	tt_pos.ry() -= 51;
1096 	tt_pos.rx() += 26;
1097 
1098 	float scaledLevel = m_pattern->firstObject()->scaledValue( level );
1099 
1100 	// Limit the scaled-level tooltip to the grid
1101 	if( mouse_pos.x() >= 0 &&
1102 		mouse_pos.x() <= width() - SCROLLBAR_SIZE &&
1103 		mouse_pos.y() >= 0 &&
1104 		mouse_pos.y() <= height() - SCROLLBAR_SIZE )
1105 	{
1106 		QToolTip::showText( tt_pos, QString::number( scaledLevel ), this );
1107 	}
1108 }
1109 
1110 
1111 
1112 
drawAutomationPoint(QPainter & p,timeMap::iterator it)1113 inline void AutomationEditor::drawAutomationPoint( QPainter & p, timeMap::iterator it )
1114 {
1115 	int x = xCoordOfTick( it.key() );
1116 	int y = yCoordOfLevel( it.value() );
1117 	const int outerRadius = qBound( 3, ( m_ppt * AutomationPattern::quantization() ) / 576, 5 ); // man, getting this calculation right took forever
1118 	p.setPen( QPen( vertexColor().lighter( 200 ) ) );
1119 	p.setBrush( QBrush( vertexColor() ) );
1120 	p.drawEllipse( x - outerRadius, y - outerRadius, outerRadius * 2, outerRadius * 2 );
1121 }
1122 
1123 
1124 
1125 
paintEvent(QPaintEvent * pe)1126 void AutomationEditor::paintEvent(QPaintEvent * pe )
1127 {
1128 	QMutexLocker m( &m_patternMutex );
1129 
1130 	QStyleOption opt;
1131 	opt.initFrom( this );
1132 	QPainter p( this );
1133 	style()->drawPrimitive( QStyle::PE_Widget, &opt, &p, this );
1134 
1135 	// get foreground color
1136 	QColor fgColor = p.pen().brush().color();
1137 	// get background color and fill background
1138 	QBrush bgColor = p.background();
1139 	p.fillRect( 0, 0, width(), height(), bgColor );
1140 
1141 	// set font-size to 8
1142 	p.setFont( pointSize<8>( p.font() ) );
1143 
1144 	int grid_height = height() - TOP_MARGIN - SCROLLBAR_SIZE;
1145 
1146 	// start drawing at the bottom
1147 	int grid_bottom = height() - SCROLLBAR_SIZE - 1;
1148 
1149 	p.fillRect( 0, TOP_MARGIN, VALUES_WIDTH, height() - TOP_MARGIN,
1150 						scaleColor() );
1151 
1152 	// print value numbers
1153 	int font_height = p.fontMetrics().height();
1154 	Qt::Alignment text_flags =
1155 		(Qt::Alignment)( Qt::AlignRight | Qt::AlignVCenter );
1156 
1157 	if( validPattern() )
1158 	{
1159 		if( m_y_auto )
1160 		{
1161 			int y[] = { grid_bottom, TOP_MARGIN + font_height / 2 };
1162 			float level[] = { m_minLevel, m_maxLevel };
1163 			for( int i = 0; i < 2; ++i )
1164 			{
1165 				const QString & label = m_pattern->firstObject()
1166 						->displayValue( level[i] );
1167 				p.setPen( QApplication::palette().color( QPalette::Active,
1168 							QPalette::Shadow ) );
1169 				p.drawText( 1, y[i] - font_height + 1,
1170 					VALUES_WIDTH - 10, 2 * font_height,
1171 					text_flags, label );
1172 				p.setPen( fgColor );
1173 				p.drawText( 0, y[i] - font_height,
1174 					VALUES_WIDTH - 10, 2 * font_height,
1175 					text_flags, label );
1176 			}
1177 		}
1178 		else
1179 		{
1180 			int y;
1181 			int level = (int) m_bottomLevel;
1182 			int printable = qMax( 1, 5 * DEFAULT_Y_DELTA
1183 								/ m_y_delta );
1184 			int module = level % printable;
1185 			if( module )
1186 			{
1187 				int inv_module = ( printable - module )
1188 								% printable;
1189 				level += inv_module;
1190 			}
1191 			for( ; level <= m_topLevel; level += printable )
1192 			{
1193 				const QString & label = m_pattern->firstObject()
1194 							->displayValue( level );
1195 				y = yCoordOfLevel( level );
1196 				p.setPen( QApplication::palette().color( QPalette::Active,
1197 							QPalette::Shadow ) );
1198 				p.drawText( 1, y - font_height + 1,
1199 					VALUES_WIDTH - 10, 2 * font_height,
1200 					text_flags, label );
1201 				p.setPen( fgColor );
1202 				p.drawText( 0, y - font_height,
1203 					VALUES_WIDTH - 10, 2 * font_height,
1204 					text_flags, label );
1205 			}
1206 		}
1207 	}
1208 
1209 	// set clipping area, because we are not allowed to paint over
1210 	// keyboard...
1211 	p.setClipRect( VALUES_WIDTH, TOP_MARGIN, width() - VALUES_WIDTH,
1212 								grid_height  );
1213 
1214 	// draw vertical raster
1215 
1216 	if( m_pattern )
1217 	{
1218 		int tick, x, q;
1219 		int x_line_end = (int)( m_y_auto || m_topLevel < m_maxLevel ?
1220 			TOP_MARGIN :
1221 			grid_bottom - ( m_topLevel - m_bottomLevel ) * m_y_delta );
1222 
1223 		if( m_zoomingXModel.value() > 3 )
1224 		{
1225 			// If we're over 100% zoom, we allow all quantization level grids
1226 			q = AutomationPattern::quantization();
1227 		}
1228 		else if( AutomationPattern::quantization() % 3 != 0 )
1229 		{
1230 			// If we're under 100% zoom, we allow quantization grid up to 1/24 for triplets
1231 			// to ensure a dense doesn't fill out the background
1232 			q = AutomationPattern::quantization() < 8 ? 8 : AutomationPattern::quantization();
1233 		}
1234 		else {
1235 			// If we're under 100% zoom, we allow quantization grid up to 1/32 for normal notes
1236 			q = AutomationPattern::quantization() < 6 ? 6 : AutomationPattern::quantization();
1237 		}
1238 
1239 		// 3 independent loops, because quantization might not divide evenly into
1240 		// exotic denominators (e.g. 7/11 time), which are allowed ATM.
1241 		// First quantization grid...
1242 		for( tick = m_currentPosition - m_currentPosition % q,
1243 				 x = xCoordOfTick( tick );
1244 			 x<=width();
1245 			 tick += q, x = xCoordOfTick( tick ) )
1246 		{
1247 			p.setPen( lineColor() );
1248 			p.drawLine( x, grid_bottom, x, x_line_end );
1249 		}
1250 
1251 		/// \todo move this horizontal line drawing code into the same loop as the value ticks?
1252 		if( m_y_auto )
1253 		{
1254 			QPen pen( beatLineColor() );
1255 			pen.setStyle( Qt::DotLine );
1256 			p.setPen( pen );
1257 			float y_delta = ( grid_bottom - TOP_MARGIN ) / 8.0f;
1258 			for( int i = 1; i < 8; ++i )
1259 			{
1260 				int y = (int)( grid_bottom - i * y_delta );
1261 				p.drawLine( VALUES_WIDTH, y, width(), y );
1262 			}
1263 		}
1264 		else
1265 		{
1266 			float y;
1267 			for( int level = (int)m_bottomLevel; level <= m_topLevel; level++)
1268 			{
1269 				y =  yCoordOfLevel( (float)level );
1270 				if( level % 10 == 0 )
1271 				{
1272 					p.setPen( beatLineColor() );
1273 				}
1274 				else
1275 				{
1276 					p.setPen( lineColor() );
1277 				}
1278 				// draw level line
1279 				p.drawLine( VALUES_WIDTH, (int) y, width(), (int) y );
1280 			}
1281 		}
1282 
1283 
1284 		// alternating shades for better contrast
1285 		float timeSignature = static_cast<float>( Engine::getSong()->getTimeSigModel().getNumerator() )
1286 				/ static_cast<float>( Engine::getSong()->getTimeSigModel().getDenominator() );
1287 		float zoomFactor = m_zoomXLevels[m_zoomingXModel.value()];
1288 		//the bars which disappears at the left side by scrolling
1289 		int leftBars = m_currentPosition * zoomFactor / MidiTime::ticksPerTact();
1290 
1291 		//iterates the visible bars and draw the shading on uneven bars
1292 		for( int x = VALUES_WIDTH, barCount = leftBars; x < width() + m_currentPosition * zoomFactor / timeSignature; x += m_ppt, ++barCount )
1293 		{
1294 			if( ( barCount + leftBars )  % 2 != 0 )
1295 			{
1296 				p.fillRect( x - m_currentPosition * zoomFactor / timeSignature, TOP_MARGIN, m_ppt,
1297 					height() - ( SCROLLBAR_SIZE + TOP_MARGIN ), backgroundShade() );
1298 			}
1299 		}
1300 
1301 		// Draw the beat grid
1302 		int ticksPerBeat = DefaultTicksPerTact /
1303 			Engine::getSong()->getTimeSigModel().getDenominator();
1304 
1305 		for( tick = m_currentPosition - m_currentPosition % ticksPerBeat,
1306 				 x = xCoordOfTick( tick );
1307 			 x<=width();
1308 			 tick += ticksPerBeat, x = xCoordOfTick( tick ) )
1309 		{
1310 			p.setPen( beatLineColor() );
1311 			p.drawLine( x, grid_bottom, x, x_line_end );
1312 		}
1313 
1314 		// and finally bars
1315 		for( tick = m_currentPosition - m_currentPosition % MidiTime::ticksPerTact(),
1316 				 x = xCoordOfTick( tick );
1317 			 x<=width();
1318 			 tick += MidiTime::ticksPerTact(), x = xCoordOfTick( tick ) )
1319 		{
1320 			p.setPen( barLineColor() );
1321 			p.drawLine( x, grid_bottom, x, x_line_end );
1322 		}
1323 	}
1324 
1325 
1326 
1327 	// following code draws all visible values
1328 
1329 	// setup selection-vars
1330 	int sel_pos_start = m_selectStartTick;
1331 	int sel_pos_end = m_selectStartTick + m_selectedTick;
1332 	if( sel_pos_start > sel_pos_end )
1333 	{
1334 		qSwap<int>( sel_pos_start, sel_pos_end );
1335 	}
1336 
1337 	float selLevel_start = m_selectStartLevel;
1338 	float selLevel_end = selLevel_start + m_selectedLevels;
1339 	if( selLevel_start > selLevel_end )
1340 	{
1341 		qSwap<float>( selLevel_start, selLevel_end );
1342 	}
1343 
1344 	if( validPattern() )
1345 	{
1346 		//NEEDS Change in CSS
1347 		//int len_ticks = 4;
1348 		timeMap & time_map = m_pattern->getTimeMap();
1349 
1350 		//Don't bother doing/rendering anything if there is no automation points
1351 		if( time_map.size() > 0 )
1352 		{
1353 			timeMap::iterator it = time_map.begin();
1354 			while( it+1 != time_map.end() )
1355 			{
1356 				// skip this section if it occurs completely before the
1357 				// visible area
1358 				int next_x = xCoordOfTick( (it+1).key() );
1359 				if( next_x < 0 )
1360 				{
1361 					++it;
1362 					continue;
1363 				}
1364 
1365 				int x = xCoordOfTick( it.key() );
1366 				if( x > width() )
1367 				{
1368 					break;
1369 				}
1370 
1371 				//NEEDS Change in CSS
1372 				/*bool is_selected = false;
1373 				// if we're in move-mode, we may only draw
1374 				// values in selected area, that have originally
1375 				// been selected and not values that are now in
1376 				// selection because the user moved it...
1377 				if( m_editMode == MOVE )
1378 				{
1379 					if( m_selValuesForMove.contains( it.key() ) )
1380 					{
1381 						is_selected = true;
1382 					}
1383 				}
1384 				else if( it.value() >= selLevel_start &&
1385 					it.value() <= selLevel_end &&
1386 					it.key() >= sel_pos_start &&
1387 					it.key() + len_ticks <= sel_pos_end )
1388 				{
1389 					is_selected = true;
1390 				}*/
1391 
1392 				float *values = m_pattern->valuesAfter( it.key() );
1393 
1394 				float nextValue;
1395 				if( m_pattern->progressionType() == AutomationPattern::DiscreteProgression )
1396 				{
1397 					nextValue = it.value();
1398 				}
1399 				else
1400 				{
1401 					nextValue = ( it + 1 ).value();
1402 				}
1403 
1404 				p.setRenderHints( QPainter::Antialiasing, true );
1405 				QPainterPath path;
1406 				path.moveTo( QPointF( xCoordOfTick( it.key() ), yCoordOfLevel( 0 ) ) );
1407 				for( int i = 0; i < ( it + 1 ).key() - it.key(); i++ )
1408 				{	path.lineTo( QPointF( xCoordOfTick( it.key() + i ), yCoordOfLevel( values[i] ) ) );
1409 					//NEEDS Change in CSS
1410 					//drawLevelTick( p, it.key() + i, values[i], is_selected );
1411 
1412 				}
1413 				path.lineTo( QPointF( xCoordOfTick( ( it + 1 ).key() ), yCoordOfLevel( nextValue ) ) );
1414 				path.lineTo( QPointF( xCoordOfTick( ( it + 1 ).key() ), yCoordOfLevel( 0 ) ) );
1415 				path.lineTo( QPointF( xCoordOfTick( it.key() ), yCoordOfLevel( 0 ) ) );
1416 				p.fillPath( path, graphColor() );
1417 				p.setRenderHints( QPainter::Antialiasing, false );
1418 				delete [] values;
1419 
1420 				// Draw circle
1421 				drawAutomationPoint(p, it);
1422 
1423 				++it;
1424 			}
1425 
1426 			for( int i = it.key(), x = xCoordOfTick( i ); x <= width();
1427 							i++, x = xCoordOfTick( i ) )
1428 			{
1429 				// TODO: Find out if the section after the last control
1430 				// point is able to be selected and if so set this
1431 				// boolean correctly
1432 				drawLevelTick( p, i, it.value()); ////NEEDS Change in CSS:, false );
1433 			}
1434 			// Draw circle(the last one)
1435 			drawAutomationPoint(p, it);
1436 		}
1437 	}
1438 	else
1439 	{
1440 		QFont f = p.font();
1441 		f.setBold( true );
1442 		p.setFont( pointSize<14>( f ) );
1443 		p.setPen( QApplication::palette().color( QPalette::Active,
1444 							QPalette::BrightText ) );
1445 		p.drawText( VALUES_WIDTH + 20, TOP_MARGIN + 40,
1446 				width() - VALUES_WIDTH - 20 - SCROLLBAR_SIZE,
1447 				grid_height - 40, Qt::TextWordWrap,
1448 				tr( "Please open an automation pattern with "
1449 					"the context menu of a control!" ) );
1450 	}
1451 
1452 	// now draw selection-frame
1453 	int x = ( sel_pos_start - m_currentPosition ) * m_ppt /
1454 							MidiTime::ticksPerTact();
1455 	int w = ( sel_pos_end - sel_pos_start ) * m_ppt / MidiTime::ticksPerTact();
1456 	int y, h;
1457 	if( m_y_auto )
1458 	{
1459 		y = (int)( grid_bottom - ( ( grid_bottom - TOP_MARGIN )
1460 				* ( selLevel_start - m_minLevel )
1461 				/ (float)( m_maxLevel - m_minLevel ) ) );
1462 		h = (int)( grid_bottom - ( ( grid_bottom - TOP_MARGIN )
1463 				* ( selLevel_end - m_minLevel )
1464 				/ (float)( m_maxLevel - m_minLevel ) ) - y );
1465 	}
1466 	else
1467 	{
1468 		y = (int)( grid_bottom - ( selLevel_start - m_bottomLevel )
1469 								* m_y_delta );
1470 		h = (int)( ( selLevel_start - selLevel_end ) * m_y_delta );
1471 	}
1472 	p.setPen( QColor( 0, 64, 192 ) );
1473 	p.drawRect( x + VALUES_WIDTH, y, w, h );
1474 
1475 	// TODO: Get this out of paint event
1476 	int l = validPattern() ? (int) m_pattern->length() : 0;
1477 
1478 	// reset scroll-range
1479 	if( m_leftRightScroll->maximum() != l )
1480 	{
1481 		m_leftRightScroll->setRange( 0, l );
1482 		m_leftRightScroll->setPageStep( l );
1483 	}
1484 
1485 	if( validPattern() && GuiApplication::instance()->automationEditor()->hasFocus() )
1486 	{
1487 
1488 		drawCross( p );
1489 	}
1490 
1491 	const QPixmap * cursor = NULL;
1492 	// draw current edit-mode-icon below the cursor
1493 	switch( m_editMode )
1494 	{
1495 		case DRAW:
1496 			if( m_mouseDownRight )
1497 			{
1498 				cursor = s_toolErase;
1499 			}
1500 			else if( m_action == MOVE_VALUE )
1501 			{
1502 				cursor = s_toolMove;
1503 			}
1504 			else
1505 			{
1506 				cursor = s_toolDraw;
1507 			}
1508 
1509 			break;
1510 		case ERASE: cursor = s_toolErase; break;
1511 		case SELECT: cursor = s_toolSelect; break;
1512 		case MOVE: cursor = s_toolMove; break;
1513 	}
1514 	QPoint mousePosition = mapFromGlobal( QCursor::pos() );
1515 	if( cursor != NULL && mousePosition.y() > TOP_MARGIN + SCROLLBAR_SIZE)
1516 	{
1517 		p.drawPixmap( mousePosition + QPoint( 8, 8 ), *cursor );
1518 	}
1519 }
1520 
1521 
1522 
1523 
xCoordOfTick(int tick)1524 int AutomationEditor::xCoordOfTick(int tick )
1525 {
1526 	return VALUES_WIDTH + ( ( tick - m_currentPosition )
1527 		* m_ppt / MidiTime::ticksPerTact() );
1528 }
1529 
1530 
1531 
1532 
yCoordOfLevel(float level)1533 float AutomationEditor::yCoordOfLevel(float level )
1534 {
1535 	int grid_bottom = height() - SCROLLBAR_SIZE - 1;
1536 	if( m_y_auto )
1537 	{
1538 		return ( grid_bottom - ( grid_bottom - TOP_MARGIN ) * ( level - m_minLevel ) / ( m_maxLevel - m_minLevel ) );
1539 	}
1540 	else
1541 	{
1542 		return ( grid_bottom - ( level - m_bottomLevel ) * m_y_delta );
1543 	}
1544 }
1545 
1546 
1547 
1548 
1549 				//NEEDS Change in CSS
drawLevelTick(QPainter & p,int tick,float value)1550 void AutomationEditor::drawLevelTick(QPainter & p, int tick, float value)
1551 				//			bool is_selected )
1552 {
1553 	int grid_bottom = height() - SCROLLBAR_SIZE - 1;
1554 	const int x = xCoordOfTick( tick );
1555 	int rect_width = xCoordOfTick( tick+1 ) - x;
1556 
1557 	// is the level in visible area?
1558 	if( ( value >= m_bottomLevel && value <= m_topLevel )
1559 			|| ( value > m_topLevel && m_topLevel >= 0 )
1560 			|| ( value < m_bottomLevel && m_bottomLevel <= 0 ) )
1561 	{
1562 		int y_start = yCoordOfLevel( value );
1563 		int rect_height;
1564 
1565 		if( m_y_auto )
1566 		{
1567 			int y_end = (int)( grid_bottom
1568 						+ ( grid_bottom - TOP_MARGIN )
1569 						* m_minLevel
1570 						/ ( m_maxLevel - m_minLevel ) );
1571 
1572 			rect_height = y_end - y_start;
1573 		}
1574 		else
1575 		{
1576 			rect_height = (int)( value * m_y_delta );
1577 		}
1578 
1579 		//NEEDS Change in CSS
1580 		/*QBrush currentColor = is_selected
1581 			? QBrush( QColor( 0x00, 0x40, 0xC0 ) )
1582 			: graphColor();
1583 
1584 		*/
1585 
1586 		QBrush currentColor = graphColor();
1587 
1588 		p.fillRect( x, y_start, rect_width, rect_height, currentColor );
1589 	}
1590 
1591 	else
1592 	{
1593 		printf("not in range\n");
1594 	}
1595 
1596 }
1597 
1598 
1599 
1600 
1601 // responsible for moving/resizing scrollbars after window-resizing
resizeEvent(QResizeEvent * re)1602 void AutomationEditor::resizeEvent(QResizeEvent * re)
1603 {
1604 	m_leftRightScroll->setGeometry( VALUES_WIDTH, height() - SCROLLBAR_SIZE,
1605 							width() - VALUES_WIDTH,
1606 							SCROLLBAR_SIZE );
1607 
1608 	int grid_height = height() - TOP_MARGIN - SCROLLBAR_SIZE;
1609 	m_topBottomScroll->setGeometry( width() - SCROLLBAR_SIZE, TOP_MARGIN,
1610 						SCROLLBAR_SIZE, grid_height );
1611 
1612 	int half_grid = grid_height / 2;
1613 	int total_pixels = (int)( ( m_maxLevel - m_minLevel ) * m_y_delta + 1 );
1614 	if( !m_y_auto && grid_height < total_pixels )
1615 	{
1616 		int min_scroll = (int)( m_minLevel + floorf( half_grid
1617 							/ (float)m_y_delta ) );
1618 		int max_scroll = (int)( m_maxLevel - (int)floorf( ( grid_height
1619 					- half_grid ) / (float)m_y_delta ) );
1620 		m_topBottomScroll->setRange( min_scroll, max_scroll );
1621 	}
1622 	else
1623 	{
1624 		m_topBottomScroll->setRange( (int) m_scrollLevel,
1625 							(int) m_scrollLevel );
1626 	}
1627 
1628 	m_topBottomScroll->setValue( (int) m_scrollLevel );
1629 
1630 	if( Engine::getSong() )
1631 	{
1632 		Engine::getSong()->getPlayPos( Song::Mode_PlayAutomationPattern
1633 					).m_timeLine->setFixedWidth( width() );
1634 	}
1635 
1636 	updateTopBottomLevels();
1637 	update();
1638 }
1639 
1640 
1641 
1642 
wheelEvent(QWheelEvent * we)1643 void AutomationEditor::wheelEvent(QWheelEvent * we )
1644 {
1645 	we->accept();
1646 	if( we->modifiers() & Qt::ControlModifier && we->modifiers() & Qt::ShiftModifier )
1647 	{
1648 		int y = m_zoomingYModel.value();
1649 		if( we->delta() > 0 )
1650 		{
1651 			y++;
1652 		}
1653 		else if( we->delta() < 0 )
1654 		{
1655 			y--;
1656 		}
1657 		y = qBound( 0, y, m_zoomingYModel.size() - 1 );
1658 		m_zoomingYModel.setValue( y );
1659 	}
1660 	else if( we->modifiers() & Qt::ControlModifier && we->modifiers() & Qt::AltModifier )
1661 	{
1662 		int q = m_quantizeModel.value();
1663 		if( we->delta() > 0 )
1664 		{
1665 			q--;
1666 		}
1667 		else if( we->delta() < 0 )
1668 		{
1669 			q++;
1670 		}
1671 		q = qBound( 0, q, m_quantizeModel.size() - 1 );
1672 		m_quantizeModel.setValue( q );
1673 		update();
1674 	}
1675 	else if( we->modifiers() & Qt::ControlModifier )
1676 	{
1677 		int x = m_zoomingXModel.value();
1678 		if( we->delta() > 0 )
1679 		{
1680 			x++;
1681 		}
1682 		else if( we->delta() < 0 )
1683 		{
1684 			x--;
1685 		}
1686 		x = qBound( 0, x, m_zoomingXModel.size() - 1 );
1687 		m_zoomingXModel.setValue( x );
1688 	}
1689 	else if( we->modifiers() & Qt::ShiftModifier
1690 			|| we->orientation() == Qt::Horizontal )
1691 	{
1692 		m_leftRightScroll->setValue( m_leftRightScroll->value() -
1693 							we->delta() * 2 / 15 );
1694 	}
1695 	else
1696 	{
1697 		m_topBottomScroll->setValue( m_topBottomScroll->value() -
1698 							we->delta() / 30 );
1699 	}
1700 }
1701 
1702 
1703 
1704 
getLevel(int y)1705 float AutomationEditor::getLevel(int y )
1706 {
1707 	int level_line_y = height() - SCROLLBAR_SIZE - 1;
1708 	// pressed level
1709 	float level = roundf( ( m_bottomLevel + ( m_y_auto ?
1710 			( m_maxLevel - m_minLevel ) * ( level_line_y - y )
1711 					/ (float)( level_line_y - ( TOP_MARGIN + 2 ) ) :
1712 			( level_line_y - y ) / (float)m_y_delta ) ) / m_step ) * m_step;
1713 	// some range-checking-stuff
1714 	level = qBound( m_bottomLevel, level, m_topLevel );
1715 
1716 	return( level );
1717 }
1718 
1719 
1720 
1721 
inBBEditor()1722 inline bool AutomationEditor::inBBEditor()
1723 {
1724 	QMutexLocker m( &m_patternMutex );
1725 	return( validPattern() &&
1726 				m_pattern->getTrack()->trackContainer() == Engine::getBBTrackContainer() );
1727 }
1728 
1729 
1730 
1731 
play()1732 void AutomationEditor::play()
1733 {
1734 	QMutexLocker m( &m_patternMutex );
1735 
1736 	if( !validPattern() )
1737 	{
1738 		return;
1739 	}
1740 
1741 	if( !m_pattern->getTrack() )
1742 	{
1743 		if( Engine::getSong()->playMode() != Song::Mode_PlayPattern )
1744 		{
1745 			Engine::getSong()->stop();
1746 			Engine::getSong()->playPattern( gui->pianoRoll()->currentPattern() );
1747 		}
1748 		else if( Engine::getSong()->isStopped() == false )
1749 		{
1750 			Engine::getSong()->togglePause();
1751 		}
1752 		else
1753 		{
1754 			Engine::getSong()->playPattern( gui->pianoRoll()->currentPattern() );
1755 		}
1756 	}
1757 	else if( inBBEditor() )
1758 	{
1759 		Engine::getBBTrackContainer()->play();
1760 	}
1761 	else
1762 	{
1763 		if( Engine::getSong()->isStopped() == true )
1764 		{
1765 			Engine::getSong()->playSong();
1766 		}
1767 		else
1768 		{
1769 			Engine::getSong()->togglePause();
1770 		}
1771 	}
1772 }
1773 
1774 
1775 
1776 
stop()1777 void AutomationEditor::stop()
1778 {
1779 	QMutexLocker m( &m_patternMutex );
1780 
1781 	if( !validPattern() )
1782 	{
1783 		return;
1784 	}
1785 	if( m_pattern->getTrack() && inBBEditor() )
1786 	{
1787 		Engine::getBBTrackContainer()->stop();
1788 	}
1789 	else
1790 	{
1791 		Engine::getSong()->stop();
1792 	}
1793 	m_scrollBack = true;
1794 }
1795 
1796 
1797 
1798 
horScrolled(int new_pos)1799 void AutomationEditor::horScrolled(int new_pos )
1800 {
1801 	m_currentPosition = new_pos;
1802 	emit positionChanged( m_currentPosition );
1803 	update();
1804 }
1805 
1806 
1807 
1808 
verScrolled(int new_pos)1809 void AutomationEditor::verScrolled(int new_pos )
1810 {
1811 	m_scrollLevel = new_pos;
1812 	updateTopBottomLevels();
1813 	update();
1814 }
1815 
1816 
1817 
1818 
setEditMode(AutomationEditor::EditModes mode)1819 void AutomationEditor::setEditMode(AutomationEditor::EditModes mode)
1820 {
1821 	if (m_editMode == mode)
1822 		return;
1823 
1824 	m_editMode = mode;
1825 	switch (mode)
1826 	{
1827 	case DRAW:
1828 	case ERASE:
1829 	case SELECT:
1830 		removeSelection();
1831 		break;
1832 	case MOVE:
1833 		m_selValuesForMove.clear();
1834 		getSelectedValues(m_selValuesForMove);
1835 	}
1836 	update();
1837 }
1838 
1839 
1840 
1841 
setEditMode(int mode)1842 void AutomationEditor::setEditMode(int mode)
1843 {
1844 	setEditMode((AutomationEditor::EditModes) mode);
1845 }
1846 
1847 
1848 
1849 
setProgressionType(AutomationPattern::ProgressionTypes type)1850 void AutomationEditor::setProgressionType(AutomationPattern::ProgressionTypes type)
1851 {
1852 	if (validPattern())
1853 	{
1854 		m_pattern->addJournalCheckPoint();
1855 		QMutexLocker m(&m_patternMutex);
1856 		m_pattern->setProgressionType(type);
1857 		Engine::getSong()->setModified();
1858 		update();
1859 	}
1860 }
1861 
setProgressionType(int type)1862 void AutomationEditor::setProgressionType(int type)
1863 {
1864 	setProgressionType((AutomationPattern::ProgressionTypes) type);
1865 }
1866 
1867 
1868 
1869 
setTension()1870 void AutomationEditor::setTension()
1871 {
1872 	if ( m_pattern )
1873 	{
1874 		m_pattern->setTension( QString::number( m_tensionModel->value() ) );
1875 		update();
1876 	}
1877 }
1878 
1879 
1880 
selectAll()1881 void AutomationEditor::selectAll()
1882 {
1883 	QMutexLocker m( &m_patternMutex );
1884 	if( !validPattern() )
1885 	{
1886 		return;
1887 	}
1888 
1889 	timeMap & time_map = m_pattern->getTimeMap();
1890 
1891 	timeMap::iterator it = time_map.begin();
1892 	m_selectStartTick = 0;
1893 	m_selectedTick = m_pattern->length();
1894 	m_selectStartLevel = it.value();
1895 	m_selectedLevels = 1;
1896 
1897 	while( ++it != time_map.end() )
1898 	{
1899 		const float level = it.value();
1900 		if( level < m_selectStartLevel )
1901 		{
1902 			// if we move start-level down, we have to add
1903 			// the difference between old and new start-level
1904 			// to m_selectedLevels, otherwise the selection
1905 			// is just moved down...
1906 			m_selectedLevels += m_selectStartLevel - level;
1907 			m_selectStartLevel = level;
1908 		}
1909 		else if( level >= m_selectStartLevel + m_selectedLevels )
1910 		{
1911 			m_selectedLevels = level - m_selectStartLevel + 1;
1912 		}
1913 	}
1914 }
1915 
1916 
1917 
1918 
1919 // returns vector with pointers to all selected values
getSelectedValues(timeMap & selected_values)1920 void AutomationEditor::getSelectedValues( timeMap & selected_values )
1921 {
1922 	QMutexLocker m( &m_patternMutex );
1923 	if( !validPattern() )
1924 	{
1925 		return;
1926 	}
1927 
1928 	int sel_pos_start = m_selectStartTick;
1929 	int sel_pos_end = sel_pos_start + m_selectedTick;
1930 	if( sel_pos_start > sel_pos_end )
1931 	{
1932 		qSwap<int>( sel_pos_start, sel_pos_end );
1933 	}
1934 
1935 	float selLevel_start = m_selectStartLevel;
1936 	float selLevel_end = selLevel_start + m_selectedLevels;
1937 	if( selLevel_start > selLevel_end )
1938 	{
1939 		qSwap<float>( selLevel_start, selLevel_end );
1940 	}
1941 
1942 	timeMap & time_map = m_pattern->getTimeMap();
1943 
1944 	for( timeMap::iterator it = time_map.begin(); it != time_map.end();
1945 									++it )
1946 	{
1947 		//TODO: Add constant
1948 		tick_t len_ticks = MidiTime::ticksPerTact() / 16;
1949 
1950 		float level = it.value();
1951 		tick_t pos_ticks = it.key();
1952 
1953 		if( level >= selLevel_start && level <= selLevel_end &&
1954 				pos_ticks >= sel_pos_start &&
1955 				pos_ticks + len_ticks <= sel_pos_end )
1956 		{
1957 			selected_values[it.key()] = level;
1958 		}
1959 	}
1960 }
1961 
1962 
1963 
1964 
copySelectedValues()1965 void AutomationEditor::copySelectedValues()
1966 {
1967 	m_valuesToCopy.clear();
1968 
1969 	timeMap selected_values;
1970 	getSelectedValues( selected_values );
1971 
1972 	if( !selected_values.isEmpty() )
1973 	{
1974 		for( timeMap::iterator it = selected_values.begin();
1975 			it != selected_values.end(); ++it )
1976 		{
1977 			m_valuesToCopy[it.key()] = it.value();
1978 		}
1979 		TextFloat::displayMessage( tr( "Values copied" ),
1980 				tr( "All selected values were copied to the "
1981 								"clipboard." ),
1982 				embed::getIconPixmap( "edit_copy" ), 2000 );
1983 	}
1984 }
1985 
1986 
1987 
1988 
cutSelectedValues()1989 void AutomationEditor::cutSelectedValues()
1990 {
1991 	QMutexLocker m( &m_patternMutex );
1992 	if( !validPattern() )
1993 	{
1994 		return;
1995 	}
1996 
1997 	m_pattern->addJournalCheckPoint();
1998 	m_valuesToCopy.clear();
1999 
2000 	timeMap selected_values;
2001 	getSelectedValues( selected_values );
2002 
2003 	if( !selected_values.isEmpty() )
2004 	{
2005 		Engine::getSong()->setModified();
2006 
2007 		for( timeMap::iterator it = selected_values.begin();
2008 					it != selected_values.end(); ++it )
2009 		{
2010 			m_valuesToCopy[it.key()] = it.value();
2011 			m_pattern->removeValue( it.key() );
2012 		}
2013 	}
2014 
2015 	update();
2016 	gui->songEditor()->update();
2017 }
2018 
2019 
2020 
2021 
pasteValues()2022 void AutomationEditor::pasteValues()
2023 {
2024 	QMutexLocker m( &m_patternMutex );
2025 	if( validPattern() && !m_valuesToCopy.isEmpty() )
2026 	{
2027 		m_pattern->addJournalCheckPoint();
2028 		for( timeMap::iterator it = m_valuesToCopy.begin();
2029 					it != m_valuesToCopy.end(); ++it )
2030 		{
2031 			m_pattern->putValue( it.key() + m_currentPosition,
2032 								it.value() );
2033 		}
2034 
2035 		// we only have to do the following lines if we pasted at
2036 		// least one value...
2037 		Engine::getSong()->setModified();
2038 		update();
2039 		gui->songEditor()->update();
2040 	}
2041 }
2042 
2043 
2044 
2045 
deleteSelectedValues()2046 void AutomationEditor::deleteSelectedValues()
2047 {
2048 	QMutexLocker m( &m_patternMutex );
2049 	if( !validPattern() )
2050 	{
2051 		return;
2052 	}
2053 
2054 	m_pattern->addJournalCheckPoint();
2055 	timeMap selected_values;
2056 	getSelectedValues( selected_values );
2057 
2058 	const bool update_after_delete = !selected_values.empty();
2059 
2060 	for( timeMap::iterator it = selected_values.begin();
2061 					it != selected_values.end(); ++it )
2062 	{
2063 		m_pattern->removeValue( it.key() );
2064 	}
2065 
2066 	if( update_after_delete == true )
2067 	{
2068 		Engine::getSong()->setModified();
2069 		update();
2070 		gui->songEditor()->update();
2071 	}
2072 }
2073 
2074 
2075 
2076 
updatePosition(const MidiTime & t)2077 void AutomationEditor::updatePosition(const MidiTime & t )
2078 {
2079 	if( ( Engine::getSong()->isPlaying() &&
2080 			Engine::getSong()->playMode() ==
2081 					Song::Mode_PlayAutomationPattern ) ||
2082 							m_scrollBack == true )
2083 	{
2084 		const int w = width() - VALUES_WIDTH;
2085 		if( t > m_currentPosition + w * MidiTime::ticksPerTact() / m_ppt )
2086 		{
2087 			m_leftRightScroll->setValue( t.getTact() *
2088 							MidiTime::ticksPerTact() );
2089 		}
2090 		else if( t < m_currentPosition )
2091 		{
2092 			MidiTime t_ = qMax( t - w * MidiTime::ticksPerTact() *
2093 					MidiTime::ticksPerTact() / m_ppt, 0 );
2094 			m_leftRightScroll->setValue( t_.getTact() *
2095 							MidiTime::ticksPerTact() );
2096 		}
2097 		m_scrollBack = false;
2098 	}
2099 }
2100 
2101 
2102 
2103 
zoomingXChanged()2104 void AutomationEditor::zoomingXChanged()
2105 {
2106 	m_ppt = m_zoomXLevels[m_zoomingXModel.value()] * DEFAULT_PPT;
2107 
2108 	assert( m_ppt > 0 );
2109 
2110 	m_timeLine->setPixelsPerTact( m_ppt );
2111 	update();
2112 }
2113 
2114 
2115 
2116 
zoomingYChanged()2117 void AutomationEditor::zoomingYChanged()
2118 {
2119 	const QString & zfac = m_zoomingYModel.currentText();
2120 	m_y_auto = zfac == "Auto";
2121 	if( !m_y_auto )
2122 	{
2123 		m_y_delta = zfac.left( zfac.length() - 1 ).toInt()
2124 							* DEFAULT_Y_DELTA / 100;
2125 	}
2126 #ifdef LMMS_DEBUG
2127 	assert( m_y_delta > 0 );
2128 #endif
2129 	resizeEvent( NULL );
2130 }
2131 
2132 
2133 
2134 
setQuantization()2135 void AutomationEditor::setQuantization()
2136 {
2137 	int quantization = m_quantizeModel.value();
2138 	if( quantization < 7 )
2139 	{
2140 		quantization = 1 << quantization;
2141 	}
2142 	else if( quantization < 12 )
2143 	{
2144 		quantization = 1 << ( quantization - 7 );
2145 		quantization *= 3;
2146 	}
2147 	else
2148 	{
2149 		quantization = DefaultTicksPerTact;
2150 	}
2151 	quantization = DefaultTicksPerTact / quantization;
2152 	AutomationPattern::setQuantization( quantization );
2153 
2154 	update();
2155 }
2156 
2157 
2158 
2159 
updateTopBottomLevels()2160 void AutomationEditor::updateTopBottomLevels()
2161 {
2162 	if( m_y_auto )
2163 	{
2164 		m_bottomLevel = m_minLevel;
2165 		m_topLevel = m_maxLevel;
2166 		return;
2167 	}
2168 
2169 	int total_pixels = (int)( ( m_maxLevel - m_minLevel ) * m_y_delta + 1 );
2170 	int grid_height = height() - TOP_MARGIN - SCROLLBAR_SIZE;
2171 	int half_grid = grid_height / 2;
2172 
2173 	if( total_pixels > grid_height )
2174 	{
2175 		int centralLevel = (int)( m_minLevel + m_maxLevel - m_scrollLevel );
2176 
2177 		m_bottomLevel = centralLevel - ( half_grid
2178 							/ (float)m_y_delta );
2179 		if( m_bottomLevel < m_minLevel )
2180 		{
2181 			m_bottomLevel = m_minLevel;
2182 			m_topLevel = m_minLevel + (int)floorf( grid_height
2183 							/ (float)m_y_delta );
2184 		}
2185 		else
2186 		{
2187 			m_topLevel = m_bottomLevel + (int)floorf( grid_height
2188 							/ (float)m_y_delta );
2189 			if( m_topLevel > m_maxLevel )
2190 			{
2191 				m_topLevel = m_maxLevel;
2192 				m_bottomLevel = m_maxLevel - (int)floorf(
2193 					grid_height / (float)m_y_delta );
2194 			}
2195 		}
2196 	}
2197 	else
2198 	{
2199 		m_bottomLevel = m_minLevel;
2200 		m_topLevel = m_maxLevel;
2201 	}
2202 }
2203 
2204 
2205 
2206 
2207 
2208 
2209 
2210 
2211 
AutomationEditorWindow()2212 AutomationEditorWindow::AutomationEditorWindow() :
2213 	Editor(),
2214 	m_editor(new AutomationEditor())
2215 {
2216 	setCentralWidget(m_editor);
2217 
2218 
2219 
2220 	// Play/stop buttons
2221 	m_playAction->setToolTip(tr( "Play/pause current pattern (Space)" ));
2222 	m_playAction->setWhatsThis(
2223 		tr( "Click here if you want to play the current pattern. "
2224 			"This is useful while editing it.  The pattern is "
2225 			"automatically looped when the end is reached." ) );
2226 
2227 	m_stopAction->setToolTip(tr("Stop playing of current pattern (Space)"));
2228 	m_stopAction->setWhatsThis(
2229 		tr( "Click here if you want to stop playing of the "
2230 			"current pattern." ) );
2231 
2232 	// Edit mode buttons
2233 	DropToolBar *editActionsToolBar = addDropToolBarToTop(tr("Edit actions"));
2234 
2235 	ActionGroup* editModeGroup = new ActionGroup(this);
2236 	QAction* drawAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw"), tr("Draw mode (Shift+D)"));
2237 	drawAction->setShortcut(Qt::SHIFT | Qt::Key_D);
2238 	drawAction->setChecked(true);
2239 
2240 	QAction* eraseAction = editModeGroup->addAction(embed::getIconPixmap("edit_erase"), tr("Erase mode (Shift+E)"));
2241 	eraseAction->setShortcut(Qt::SHIFT | Qt::Key_E);
2242 
2243 	m_flipYAction = new QAction(embed::getIconPixmap("flip_y"), tr("Flip vertically"), this);
2244 	m_flipXAction = new QAction(embed::getIconPixmap("flip_x"), tr("Flip horizontally"), this);
2245 
2246 	m_flipYAction->setWhatsThis(
2247 				tr( "Click here and the pattern will be inverted."
2248 					"The points are flipped in the y direction. " ) );
2249 	m_flipXAction->setWhatsThis(
2250 				tr( "Click here and the pattern will be reversed. "
2251 					"The points are flipped in the x direction." ) );
2252 
2253 //	TODO: m_selectButton and m_moveButton are broken.
2254 //	m_selectButton = new QAction(embed::getIconPixmap("edit_select"), tr("Select mode (Shift+S)"), editModeGroup);
2255 //	m_moveButton = new QAction(embed::getIconPixmap("edit_move"), tr("Move selection mode (Shift+M)"), editModeGroup);
2256 
2257 	drawAction->setWhatsThis(
2258 		tr( "Click here and draw-mode will be activated. In this "
2259 			"mode you can add and move single values.  This "
2260 			"is the default mode which is used most of the time.  "
2261 			"You can also press 'Shift+D' on your keyboard to "
2262 			"activate this mode." ) );
2263 	eraseAction->setWhatsThis(
2264 		tr( "Click here and erase-mode will be activated. In this "
2265 			"mode you can erase single values. You can also press "
2266 			"'Shift+E' on your keyboard to activate this mode." ) );
2267 	/*m_selectButton->setWhatsThis(
2268 		tr( "Click here and select-mode will be activated. In this "
2269 			"mode you can select values. This is necessary "
2270 			"if you want to cut, copy, paste, delete, or move "
2271 			"values. You can also press 'Shift+S' on your keyboard "
2272 			"to activate this mode." ) );
2273 	m_moveButton->setWhatsThis(
2274 		tr( "If you click here, move-mode will be activated. In this "
2275 			"mode you can move the values you selected in select-"
2276 			"mode. You can also press 'Shift+M' on your keyboard "
2277 			"to activate this mode." ) );*/
2278 
2279 	connect(editModeGroup, SIGNAL(triggered(int)), m_editor, SLOT(setEditMode(int)));
2280 
2281 	editActionsToolBar->addAction(drawAction);
2282 	editActionsToolBar->addAction(eraseAction);
2283 //	editActionsToolBar->addAction(m_selectButton);
2284 //	editActionsToolBar->addAction(m_moveButton);
2285 	editActionsToolBar->addAction(m_flipXAction);
2286 	editActionsToolBar->addAction(m_flipYAction);
2287 
2288 
2289 	// Interpolation actions
2290 	DropToolBar *interpolationActionsToolBar = addDropToolBarToTop(tr("Interpolation controls"));
2291 
2292 	ActionGroup* progression_type_group = new ActionGroup(this);
2293 
2294 	m_discreteAction = progression_type_group->addAction(
2295 				embed::getIconPixmap("progression_discrete"), tr("Discrete progression"));
2296 	m_discreteAction->setChecked(true);
2297 
2298 	m_linearAction = progression_type_group->addAction(
2299 				embed::getIconPixmap("progression_linear"), tr("Linear progression"));
2300 	m_cubicHermiteAction = progression_type_group->addAction(
2301 				embed::getIconPixmap("progression_cubic_hermite"), tr( "Cubic Hermite progression"));
2302 
2303 	connect(progression_type_group, SIGNAL(triggered(int)), m_editor, SLOT(setProgressionType(int)));
2304 
2305 	// setup tension-stuff
2306 	m_tensionKnob = new Knob( knobSmall_17, this, "Tension" );
2307 	m_tensionKnob->setModel(m_editor->m_tensionModel);
2308 	ToolTip::add(m_tensionKnob, tr("Tension value for spline"));
2309 	m_tensionKnob->setWhatsThis(
2310 				tr("A higher tension value may make a smoother curve "
2311 				   "but overshoot some values. A low tension "
2312 				   "value will cause the slope of the curve to "
2313 				   "level off at each control point."));
2314 
2315 	connect(m_cubicHermiteAction, SIGNAL(toggled(bool)), m_tensionKnob, SLOT(setEnabled(bool)));
2316 
2317 	m_discreteAction->setWhatsThis(
2318 		tr( "Click here to choose discrete progressions for this "
2319 			"automation pattern.  The value of the connected "
2320 			"object will remain constant between control points "
2321 			"and be set immediately to the new value when each "
2322 			"control point is reached." ) );
2323 	m_linearAction->setWhatsThis(
2324 		tr( "Click here to choose linear progressions for this "
2325 			"automation pattern.  The value of the connected "
2326 			"object will change at a steady rate over time "
2327 			"between control points to reach the correct value at "
2328 			"each control point without a sudden change." ) );
2329 	m_cubicHermiteAction->setWhatsThis(
2330 		tr( "Click here to choose cubic hermite progressions for this "
2331 			"automation pattern.  The value of the connected "
2332 			"object will change in a smooth curve and ease in to "
2333 			"the peaks and valleys." ) );
2334 
2335 	interpolationActionsToolBar->addSeparator();
2336 	interpolationActionsToolBar->addAction(m_discreteAction);
2337 	interpolationActionsToolBar->addAction(m_linearAction);
2338 	interpolationActionsToolBar->addAction(m_cubicHermiteAction);
2339 	interpolationActionsToolBar->addSeparator();
2340 	interpolationActionsToolBar->addWidget( new QLabel( tr("Tension: "), interpolationActionsToolBar ));
2341 	interpolationActionsToolBar->addWidget( m_tensionKnob );
2342 
2343 
2344 
2345 	// Copy paste buttons
2346 	/*DropToolBar *copyPasteActionsToolBar = addDropToolBarToTop(tr("Copy paste actions"));*/
2347 
2348 	QAction* cutAction = new QAction(embed::getIconPixmap("edit_cut"),
2349 					tr("Cut selected values (%1+X)").arg(
2350 						#ifdef LMMS_BUILD_APPLE
2351 						"⌘"), this);
2352 						#else
2353 						"Ctrl"), this);
2354 						#endif
2355 	QAction* copyAction = new QAction(embed::getIconPixmap("edit_copy"),
2356 					tr("Copy selected values (%1+C)").arg(
2357 						#ifdef LMMS_BUILD_APPLE
2358 						"⌘"), this);
2359 						#else
2360 						"Ctrl"), this);
2361 						#endif
2362 	QAction* pasteAction = new QAction(embed::getIconPixmap("edit_paste"),
2363 					tr("Paste values from clipboard (%1+V)").arg(
2364 						#ifdef LMMS_BUILD_APPLE
2365 						"⌘"), this);
2366 						#else
2367 						"Ctrl"), this);
2368 						#endif
2369 
2370 	cutAction->setWhatsThis(
2371 		tr( "Click here and selected values will be cut into the "
2372 			"clipboard.  You can paste them anywhere in any pattern "
2373 			"by clicking on the paste button." ) );
2374 	copyAction->setWhatsThis(
2375 		tr( "Click here and selected values will be copied into "
2376 			"the clipboard.  You can paste them anywhere in any "
2377 			"pattern by clicking on the paste button." ) );
2378 	pasteAction->setWhatsThis(
2379 		tr( "Click here and the values from the clipboard will be "
2380 			"pasted at the first visible measure." ) );
2381 
2382 	cutAction->setShortcut(Qt::CTRL | Qt::Key_X);
2383 	copyAction->setShortcut(Qt::CTRL | Qt::Key_C);
2384 	pasteAction->setShortcut(Qt::CTRL | Qt::Key_V);
2385 
2386 	connect(cutAction,   SIGNAL(triggered()), m_editor, SLOT(cutSelectedValues()));
2387 	connect(copyAction,  SIGNAL(triggered()), m_editor, SLOT(copySelectedValues()));
2388 	connect(pasteAction, SIGNAL(triggered()), m_editor, SLOT(pasteValues()));
2389 
2390 	//	Select is broken
2391 	//	copyPasteActionsToolBar->addAction( cutAction );
2392 	//	copyPasteActionsToolBar->addAction( copyAction );
2393 	//	copyPasteActionsToolBar->addAction( pasteAction );
2394 
2395 
2396 	// Not implemented.
2397 	//DropToolBar *timeLineToolBar = addDropToolBarToTop(tr("Timeline controls"));
2398 	//m_editor->m_timeLine->addToolButtons(timeLineToolBar);
2399 
2400 
2401 	addToolBarBreak();
2402 
2403 
2404 	// Zoom controls
2405 	DropToolBar *zoomToolBar = addDropToolBarToTop(tr("Zoom controls"));
2406 
2407 	QLabel * zoom_x_label = new QLabel( zoomToolBar );
2408 	zoom_x_label->setPixmap( embed::getIconPixmap( "zoom_x" ) );
2409 
2410 	m_zoomingXComboBox = new ComboBox( zoomToolBar );
2411 	m_zoomingXComboBox->setFixedSize( 80, 22 );
2412 
2413 	for( float const & zoomLevel : m_editor->m_zoomXLevels )
2414 	{
2415 		m_editor->m_zoomingXModel.addItem( QString( "%1\%" ).arg( zoomLevel * 100 ) );
2416 	}
2417 	m_editor->m_zoomingXModel.setValue( m_editor->m_zoomingXModel.findText( "100%" ) );
2418 
2419 	m_zoomingXComboBox->setModel( &m_editor->m_zoomingXModel );
2420 
2421 	connect( &m_editor->m_zoomingXModel, SIGNAL( dataChanged() ),
2422 			m_editor, SLOT( zoomingXChanged() ) );
2423 
2424 
2425 	QLabel * zoom_y_label = new QLabel( zoomToolBar );
2426 	zoom_y_label->setPixmap( embed::getIconPixmap( "zoom_y" ) );
2427 
2428 	m_zoomingYComboBox = new ComboBox( zoomToolBar );
2429 	m_zoomingYComboBox->setFixedSize( 80, 22 );
2430 
2431 	m_editor->m_zoomingYModel.addItem( "Auto" );
2432 	for( int i = 0; i < 7; ++i )
2433 	{
2434 		m_editor->m_zoomingYModel.addItem( QString::number( 25 << i ) + "%" );
2435 	}
2436 	m_editor->m_zoomingYModel.setValue( m_editor->m_zoomingYModel.findText( "Auto" ) );
2437 
2438 	m_zoomingYComboBox->setModel( &m_editor->m_zoomingYModel );
2439 
2440 	connect( &m_editor->m_zoomingYModel, SIGNAL( dataChanged() ),
2441 			m_editor, SLOT( zoomingYChanged() ) );
2442 
2443 	zoomToolBar->addWidget( zoom_x_label );
2444 	zoomToolBar->addWidget( m_zoomingXComboBox );
2445 	zoomToolBar->addSeparator();
2446 	zoomToolBar->addWidget( zoom_y_label );
2447 	zoomToolBar->addWidget( m_zoomingYComboBox );
2448 
2449 
2450 
2451 	// Quantization controls
2452 	DropToolBar *quantizationActionsToolBar = addDropToolBarToTop(tr("Quantization controls"));
2453 
2454 	QLabel * quantize_lbl = new QLabel( m_toolBar );
2455 	quantize_lbl->setPixmap( embed::getIconPixmap( "quantize" ) );
2456 
2457 	m_quantizeComboBox = new ComboBox( m_toolBar );
2458 	m_quantizeComboBox->setFixedSize( 60, 22 );
2459 
2460 	m_quantizeComboBox->setModel( &m_editor->m_quantizeModel );
2461 
2462 	quantizationActionsToolBar->addWidget( quantize_lbl );
2463 	quantizationActionsToolBar->addWidget( m_quantizeComboBox );
2464 	m_quantizeComboBox->setToolTip( tr( "Quantization" ) );
2465 	m_quantizeComboBox->setWhatsThis( tr( "Quantization. Sets the smallest "
2466 				"step size for the Automation Point. By default "
2467 				"this also sets the length, clearing out other "
2468 				"points in the range. Press <Ctrl> to override "
2469 				"this behaviour." ) );
2470 
2471 	// Setup our actual window
2472 	setFocusPolicy( Qt::StrongFocus );
2473 	setFocus();
2474 	setWindowIcon( embed::getIconPixmap( "automation" ) );
2475 	setAcceptDrops( true );
2476 	m_toolBar->setAcceptDrops( true );
2477 }
2478 
2479 
~AutomationEditorWindow()2480 AutomationEditorWindow::~AutomationEditorWindow()
2481 {
2482 }
2483 
2484 
setCurrentPattern(AutomationPattern * pattern)2485 void AutomationEditorWindow::setCurrentPattern(AutomationPattern* pattern)
2486 {
2487 	// Disconnect our old pattern
2488 	if (currentPattern() != nullptr)
2489 	{
2490 		m_editor->m_pattern->disconnect(this);
2491 		m_flipXAction->disconnect();
2492 		m_flipYAction->disconnect();
2493 	}
2494 
2495 	m_editor->setCurrentPattern(pattern);
2496 
2497 	// Set our window's title
2498 	if (pattern == nullptr)
2499 	{
2500 		setWindowTitle( tr( "Automation Editor - no pattern" ) );
2501 		return;
2502 	}
2503 
2504 	setWindowTitle( tr( "Automation Editor - %1" ).arg( m_editor->m_pattern->name() ) );
2505 
2506 
2507 	switch(m_editor->m_pattern->progressionType())
2508 	{
2509 	case AutomationPattern::DiscreteProgression:
2510 		m_discreteAction->setChecked(true);
2511 		m_tensionKnob->setEnabled(false);
2512 		break;
2513 	case AutomationPattern::LinearProgression:
2514 		m_linearAction->setChecked(true);
2515 		m_tensionKnob->setEnabled(false);
2516 		break;
2517 	case AutomationPattern::CubicHermiteProgression:
2518 		m_cubicHermiteAction->setChecked(true);
2519 		m_tensionKnob->setEnabled(true);
2520 		break;
2521 	}
2522 
2523 	// Connect new pattern
2524 	if (pattern)
2525 	{
2526 		connect(pattern, SIGNAL(dataChanged()), this, SLOT(update()));
2527 		connect( pattern, SIGNAL( dataChanged() ), this, SLOT( updateWindowTitle() ) );
2528 		connect(pattern, SIGNAL(destroyed()), this, SLOT(clearCurrentPattern()));
2529 
2530 		connect(m_flipXAction, SIGNAL(triggered()), pattern, SLOT(flipX()));
2531 		connect(m_flipYAction, SIGNAL(triggered()), pattern, SLOT(flipY()));
2532 	}
2533 
2534 	emit currentPatternChanged();
2535 }
2536 
2537 
currentPattern()2538 const AutomationPattern* AutomationEditorWindow::currentPattern()
2539 {
2540 	return m_editor->currentPattern();
2541 }
2542 
dropEvent(QDropEvent * _de)2543 void AutomationEditorWindow::dropEvent( QDropEvent *_de )
2544 {
2545 	QString type = StringPairDrag::decodeKey( _de );
2546 	QString val = StringPairDrag::decodeValue( _de );
2547 	if( type == "automatable_model" )
2548 	{
2549 		AutomatableModel * mod = dynamic_cast<AutomatableModel *>(
2550 				Engine::projectJournal()->
2551 					journallingObject( val.toInt() ) );
2552 		if( mod != NULL )
2553 		{
2554 			bool added = m_editor->m_pattern->addObject( mod );
2555 			if ( !added )
2556 			{
2557 				TextFloat::displayMessage( mod->displayName(),
2558 							   tr( "Model is already connected "
2559 							   "to this pattern." ),
2560 							   embed::getIconPixmap( "automation" ),
2561 							   2000 );
2562 			}
2563 			setCurrentPattern( m_editor->m_pattern );
2564 		}
2565 	}
2566 
2567 	update();
2568 }
2569 
dragEnterEvent(QDragEnterEvent * _dee)2570 void AutomationEditorWindow::dragEnterEvent( QDragEnterEvent *_dee )
2571 {
2572 	if (! m_editor->validPattern() ) {
2573 		return;
2574 	}
2575 	StringPairDrag::processDragEnterEvent( _dee, "automatable_model" );
2576 }
2577 
open(AutomationPattern * pattern)2578 void AutomationEditorWindow::open(AutomationPattern* pattern)
2579 {
2580 	setCurrentPattern(pattern);
2581 	parentWidget()->show();
2582 	show();
2583 	setFocus();
2584 }
2585 
sizeHint() const2586 QSize AutomationEditorWindow::sizeHint() const
2587 {
2588 	return {INITIAL_WIDTH, INITIAL_HEIGHT};
2589 }
2590 
clearCurrentPattern()2591 void AutomationEditorWindow::clearCurrentPattern()
2592 {
2593 	m_editor->m_pattern = nullptr;
2594 	setCurrentPattern(nullptr);
2595 }
2596 
play()2597 void AutomationEditorWindow::play()
2598 {
2599 	m_editor->play();
2600 	setPauseIcon(Engine::getSong()->isPlaying());
2601 }
2602 
stop()2603 void AutomationEditorWindow::stop()
2604 {
2605 	m_editor->stop();
2606 }
2607 
updateWindowTitle()2608 void AutomationEditorWindow::updateWindowTitle()
2609 {
2610 	if ( m_editor->m_pattern == nullptr)
2611 	{
2612 		setWindowTitle( tr( "Automation Editor - no pattern" ) );
2613 		return;
2614 	}
2615 
2616 	setWindowTitle( tr( "Automation Editor - %1" ).arg( m_editor->m_pattern->name() ) );
2617 }
2618