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