1 // Copyright (C) 2012-2019 The VPaint Developers.
2 // See the COPYRIGHT file at the top-level directory of this distribution
3 // and at https://github.com/dalboris/vpaint/blob/master/COPYRIGHT
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //     http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 
17 #include "Timeline.h"
18 
19 #include <QSpinBox>
20 #include <QFormLayout>
21 #include <QGridLayout>
22 #include <QVBoxLayout>
23 #include <QHBoxLayout>
24 #include <QPushButton>
25 #include <QCheckBox>
26 #include <QComboBox>
27 #include <QPainter>
28 #include <QTimer>
29 #include <QElapsedTimer>
30 #include <QDialogButtonBox>
31 
32 #include <QMouseEvent>
33 #include <QtDebug>
34 
35 #include "Scene.h"
36 #include "View.h"
37 #include "Global.h"
38 
39 #include "VectorAnimationComplex/VAC.h"
40 #include "VectorAnimationComplex/Cell.h"
41 #include "VectorAnimationComplex/KeyCell.h"
42 #include "VectorAnimationComplex/InbetweenCell.h"
43 
44 #include "XmlStreamReader.h"
45 #include "XmlStreamWriter.h"
46 
47 using VectorAnimationComplex::VAC;
48 using VectorAnimationComplex::Cell;
49 using VectorAnimationComplex::KeyCell;
50 using VectorAnimationComplex::InbetweenCell;
51 using VectorAnimationComplex::CellSet;
52 using VectorAnimationComplex::KeyCellSet;
53 using VectorAnimationComplex::InbetweenCellSet;
54 
Timeline_HBar(Timeline * w)55 Timeline_HBar::Timeline_HBar(Timeline * w) :
56     QWidget(w),
57     w_(w),
58     isScrolling_(false),
59     hasHighlightedFrame_(false)
60 {
61     // colors
62     colors_ << Qt::red << Qt::blue;
63 
64     // set the recommended size
65     setMinimumSize(500, 20);
66     setMaximumSize(5000, 20);
67 
68     // set the background color
69     setAutoFillBackground(true);
70     QPalette palette(Qt::white, Qt::white);
71     setPalette(palette);
72 
73     // track the mouse for cell highlighting
74     setMouseTracking(true);
75 }
76 
paintEvent(QPaintEvent *)77 void Timeline_HBar::paintEvent (QPaintEvent * /*event*/)
78 {
79     // compute frame range to display
80     w_->firstVisibleFrame_ = (w_->totalPixelOffset_ / 10);
81     w_->lastVisibleFrame_ = ((w_->totalPixelOffset_ + width())/ 10);
82 
83     // initialize painter
84     QPainter painter(this);
85 
86     // draw grey background for cell out of the playing window
87     painter.setBrush(QColor(200,200,200));
88     painter.setPen(Qt::NoPen);
89     if(w_->firstVisibleFrame_<=w_->firstFrame())
90         painter.drawRect(
91             0, 1,
92             10*(w_->firstFrame())-w_->totalPixelOffset_,height()-2);
93     if(w_->lastVisibleFrame_>=w_->lastFrame())
94         painter.drawRect(
95             10*(w_->lastFrame()+1)-w_->totalPixelOffset_, 1,
96             width()-1-10*(w_->lastFrame() - w_->firstVisibleFrame_),
97             height()-2);
98 
99     // highlighted frame
100     //if(w_->isInOneTimeMode_ || w_->activeTime() == 1)
101         painter.setBrush(QColor(255,150,150));
102     //else
103     //    painter.setBrush(QColor(150,150,255));
104     painter.setPen(Qt::NoPen);
105     if(hasHighlightedFrame_/* && highlightedFrame_!=w_->currentFrame1_ && highlightedFrame_!=w_->currentFrame2_*/)
106         painter.drawRect(10*(highlightedFrame_) - w_->totalPixelOffset_ + 1, 1, 9, height()-2);
107 
108 
109 
110     // current frames
111     painter.setBrush(Qt::red);
112     painter.setPen(Qt::NoPen);
113     foreach(View * view, w_->views_)
114     {
115         painter.drawRect(10*(view->activeTime().floatTime()) - w_->totalPixelOffset_ + 1, 1, 9, height()-2);
116     }
117     painter.setBrush(QColor(200,0,0));
118     painter.drawRect(10*(global()->activeTime().floatTime()) - w_->totalPixelOffset_ + 1, 1, 9, height()-2);
119 
120     /*
121     painter.drawRect(10*(w_->currentFrame1_) - w_->totalPixelOffset_ + 1, 1, 9, height()-2);
122 
123     // current frame 2
124     if(!w_->isInOneTimeMode_)
125     {
126         painter.setBrush(Qt::blue);
127         painter.setPen(Qt::NoPen);
128         painter.drawRect(10*(w_->currentFrame2_) - w_->totalPixelOffset_ + 1, 1, 9, height()-2);
129     }
130     */
131 
132     // vertical bar between frames
133     painter.setPen(QColor(150,150,200));
134     for(int i=w_->firstVisibleFrame_; i<=w_->lastVisibleFrame_; i++)
135         painter.drawLine(10*i - w_->totalPixelOffset_, 1, 10*i - w_->totalPixelOffset_, height()-2);
136 
137 
138     // border
139     painter.setPen(QColor(50,50,50));
140     painter.drawLine(0, 0, width() - 1, 0);
141     painter.drawLine(0, height()-1, width() - 1, height()-1);
142     painter.drawLine(0, 1, 0, height()-2);
143     painter.drawLine(width()-1, 1, width()-1, height()-2);
144 
145     // Get cells
146     VAC * vac = w_->scene_->activeVAC();
147     if (!vac) {
148         return;
149     }
150     CellSet cells = vac->cells();
151     KeyCellSet keyCells = cells;
152     InbetweenCellSet inbetweenCells = cells;
153     CellSet selectedCells = vac->selectedCells();
154     KeyCellSet selectedKeyCells = selectedCells;
155     InbetweenCellSet selectedInbetweenCells = selectedCells;
156 
157     // Draw inbetween cells
158     painter.setPen(QColor(0,0,0));
159     painter.setBrush(QColor(0,0,0));
160     foreach(InbetweenCell * inbetweenCell, inbetweenCells)
161     {
162         double t1 = inbetweenCell->beforeTime().floatTime();
163         double t2 = inbetweenCell->afterTime().floatTime();
164         painter.drawRect(10*t1 - w_->totalPixelOffset_ + 5, 4,
165                          10*(t2-t1), 2);
166         //painter.drawLine(10*t1 - w_->totalPixelOffset_ + 5, 5,
167         //                 10*t2 - w_->totalPixelOffset_ + 5, 5);
168     }
169     painter.setBrush(QColor(255,0,0));
170     foreach(InbetweenCell * inbetweenCell, selectedInbetweenCells)
171     {
172         double t1 = inbetweenCell->beforeTime().floatTime();
173         double t2 = inbetweenCell->afterTime().floatTime();
174         painter.drawRect(10*t1 - w_->totalPixelOffset_ + 5, 4,
175                          10*(t2-t1), 2);
176         //painter.drawLine(10*t1 - w_->totalPixelOffset_ + 5, 5,
177         //                 10*t2 - w_->totalPixelOffset_ + 5, 5);
178     }
179 
180     // Draw key cells
181     painter.setPen(QColor(0,0,0));
182     painter.setBrush(QColor(0,0,0));
183     foreach(KeyCell * keyCell, keyCells)
184     {
185         double t = keyCell->time().floatTime();
186         painter.drawEllipse(10*t - w_->totalPixelOffset_ + 2, 2, 6, 6);
187     }
188     painter.setBrush(QColor(255,0,0));
189     foreach(KeyCell * keyCell, selectedKeyCells)
190     {
191         double t = keyCell->time().floatTime();
192         painter.drawEllipse(10*t - w_->totalPixelOffset_ + 2, 2, 6, 6);
193     }
194 
195 
196 
197     // Selection info
198     /*
199     if(w_->selectionType_ == 1)
200     {
201         painter.setPen(QColor(0,0,0));
202         painter.setBrush(QColor(0,0,0));
203 
204         // draw rect at current time
205         painter.drawRect(10*(w_->t_) - w_->totalPixelOffset_ + 1, 1, 8, 8);
206     }
207 
208     if(w_->selectionType_ == 1 || w_->selectionType_ == 2)
209     {
210         painter.setPen(QColor(0,0,0));
211         painter.setBrush(QColor(0,0,0));
212 
213         // draw disc at t1 and t2
214         painter.drawEllipse(10*(w_->t1_) - w_->totalPixelOffset_ + 2, 2, 6, 6);
215         painter.drawEllipse(10*(w_->t2_) - w_->totalPixelOffset_ + 2, 2, 6, 6);
216 
217         // link disks
218         painter.drawLine(10*(w_->t1_) - w_->totalPixelOffset_ + 5, 5,
219                          10*(w_->t2_) - w_->totalPixelOffset_ + 5, 5);
220 
221     }
222     */
223 
224 }
225 
mousePressEvent(QMouseEvent * event)226 void Timeline_HBar::mousePressEvent (QMouseEvent * event)
227 {
228     // Pan the timeline
229     if(event->button() == Qt::MidButton)
230     {
231         hasHighlightedFrame_ = false;
232         scrollingInitialX_ = event->x();
233         scrollingInitialOffset_ = w_->totalPixelOffset_;
234         isScrolling_ = true;
235         setCursor(QCursor(Qt::ClosedHandCursor));
236     }
237 
238     // Select time
239     else if(!isScrolling_ &&
240           event->button() == Qt::LeftButton &&
241           hasHighlightedFrame_ /*&&
242           highlightedFrame_!=w_->currentFrame_()*/)
243     {
244         w_->goToFrame(global()->activeView(), highlightedFrame_);
245     }
246 
247     // Temporal Drag and drop
248     else if(!isScrolling_ &&
249           event->button() == Qt::RightButton &&
250           hasHighlightedFrame_)
251     {
252         setCursor(QCursor(Qt::ClosedHandCursor));
253         VectorAnimationComplex::VAC * vac = w_->scene_->activeVAC();
254         if (vac)
255         {
256              vac->prepareTemporalDragAndDrop(Time(highlightedFrame_));
257         }
258     }
259 
260 
261     /*
262     else if(!isScrolling_ &&
263           !w_->isInOneTimeMode_ &&
264           event->button() == Qt::RightButton &&
265           hasHighlightedFrame_ &&
266           highlightedFrame_!=w_->otherFrame_())
267         w_->goToFrame(highlightedFrame_, true);
268         */
269 }
270 
mouseReleaseEvent(QMouseEvent * event)271 void Timeline_HBar::mouseReleaseEvent (QMouseEvent * event)
272 {
273     if(event->button() == Qt::MidButton)
274     {
275         isScrolling_ = false;
276         if(event->y() >=0 && event->y()<height() && event->x()>0 && event->x()<width())
277         {
278             hasHighlightedFrame_ = true;
279             int pos = (event->x() + w_->totalPixelOffset_);
280             if(pos>=0)
281                 highlightedFrame_ = (event->x() + w_->totalPixelOffset_)/10;
282             else
283                 highlightedFrame_ = (event->x() + w_->totalPixelOffset_)/10-1;
284         }
285         else
286             hasHighlightedFrame_ = false;
287 
288         setCursor(QCursor(Qt::ArrowCursor));
289     }
290     else if(event->button() == Qt::RightButton)
291     {
292         VectorAnimationComplex::VAC * vac = w_->scene_->activeVAC();
293         if (vac)
294         {
295             vac->completeTemporalDragAndDrop();
296             setCursor(QCursor(Qt::ArrowCursor));
297         }
298     }
299     repaint();
300 }
301 
mouseMoveEvent(QMouseEvent * event)302 void Timeline_HBar::mouseMoveEvent (QMouseEvent * event)
303 {
304     if(isScrolling_)
305     {
306         w_->totalPixelOffset_ = scrollingInitialOffset_ -
307             event->x() + scrollingInitialX_ ;
308     }
309     else
310     {
311         hasHighlightedFrame_ = true;
312         int pos = (event->x() + w_->totalPixelOffset_);
313         if(pos>=0)
314             highlightedFrame_ = (event->x() + w_->totalPixelOffset_)/10;
315         else
316             highlightedFrame_ = (event->x() + w_->totalPixelOffset_)/10-1;
317 
318         // Select time
319         if(event->buttons() & Qt::LeftButton &&
320            hasHighlightedFrame_ )
321         {
322             w_->goToFrame(global()->activeView(), highlightedFrame_);
323         }
324 
325         // Temporal Drag and drop
326         else if(event->buttons() & Qt::RightButton &&
327                 hasHighlightedFrame_ )
328         {
329             VectorAnimationComplex::VAC * vac = w_->scene_->activeVAC();
330             if (vac)
331             {
332                 vac->performTemporalDragAndDrop(Time(highlightedFrame_));
333             }
334         }
335     }
336 
337     repaint();
338 }
339 
leaveEvent(QEvent *)340 void Timeline_HBar::leaveEvent (QEvent * /*event*/)
341 {
342     hasHighlightedFrame_ = false;
343     repaint();
344 }
345 
346 
PlaybackSettings()347 PlaybackSettings::PlaybackSettings()
348 {
349     setDefaultValues();
350 }
351 
setDefaultValues()352 void PlaybackSettings::setDefaultValues()
353 {
354     setFirstFrame(0);
355     setLastFrame(47);
356     setFps(24) ;
357     setPlayMode(NORMAL);
358     setSubframeInbetweening(false);
359 }
360 
playModeToString(PlayMode mode)361 QString PlaybackSettings::playModeToString(PlayMode mode)
362 {
363     switch(mode)
364     {
365     case NORMAL:
366         return "normal";
367     case LOOP:
368         return "loop";
369     case BOUNCE:
370         return "bounce";
371     }
372 
373     return "normal";
374 }
375 
stringToPlayMode(const QString & str)376 PlaybackSettings::PlayMode PlaybackSettings::stringToPlayMode(const QString & str)
377 {
378     if(str == "normal")
379         return NORMAL;
380     else if(str == "loop")
381         return LOOP;
382     else if(str == "bounce")
383         return BOUNCE;
384     else
385         return NORMAL;
386 }
387 
firstFrame() const388 int PlaybackSettings::firstFrame() const { return firstFrame_; }
lastFrame() const389 int PlaybackSettings::lastFrame() const { return lastFrame_; }
fps() const390 int PlaybackSettings::fps() const { return fps_; }
playMode() const391 PlaybackSettings::PlayMode PlaybackSettings::playMode() const { return playMode_; }
subframeInbetweening() const392 bool PlaybackSettings::subframeInbetweening() const { return subframeInbetweening_; }
393 
setFirstFrame(int f)394 void PlaybackSettings::setFirstFrame(int f) { firstFrame_ = f; }
setLastFrame(int f)395 void PlaybackSettings::setLastFrame(int f) { lastFrame_ = f; }
setFps(int n)396 void PlaybackSettings::setFps(int n)  { fps_ = n; }
setPlayMode(PlayMode mode)397 void PlaybackSettings::setPlayMode(PlayMode mode) { playMode_ = mode; }
setSubframeInbetweening(bool b)398 void PlaybackSettings::setSubframeInbetweening(bool b) { subframeInbetweening_ = b; }
399 
read(XmlStreamReader & xml)400 void PlaybackSettings::read(XmlStreamReader & xml)
401 {
402     setDefaultValues();
403 
404     if(xml.attributes().hasAttribute("framerange"))
405     {
406         QString stringRange = xml.attributes().value("framerange").toString();
407         QStringList list = stringRange.split(" ");
408         setFirstFrame(list[0].toInt());
409         setLastFrame(list[1].toInt());
410     }
411     if(xml.attributes().hasAttribute("fps"))
412         setFps(xml.attributes().value("fps").toInt());
413     if(xml.attributes().hasAttribute("playmode"))
414         setPlayMode(stringToPlayMode(xml.attributes().value("playmode").toString()));
415     if(xml.attributes().hasAttribute("subframeinbetweening"))
416         setSubframeInbetweening((xml.attributes().value("subframeinbetweening") == "on") ? true : false);
417 
418     xml.skipCurrentElement();
419 }
420 
write(XmlStreamWriter & xml) const421 void PlaybackSettings::write(XmlStreamWriter & xml) const
422 {
423     xml.writeAttribute("framerange", QString().setNum(firstFrame()) + " " + QString().setNum(lastFrame()));
424     xml.writeAttribute("fps", QString().setNum(fps()));
425     xml.writeAttribute("subframeinbetweening", subframeInbetweening() ? "on" : "off");
426     xml.writeAttribute("playmode", playModeToString(playMode()));
427 }
428 
429 
430 
PlaybackSettingsDialog(const PlaybackSettings & settings)431 PlaybackSettingsDialog::PlaybackSettingsDialog(const PlaybackSettings & settings) :
432     QDialog()
433 {
434     // Window title
435     setWindowTitle(tr("Playback Settings"));
436 
437     // Create widgets holding settings values
438     //   FPS
439     fpsSpinBox_ = new QSpinBox();
440     fpsSpinBox_->setRange(1,200);
441     //   Playback Mode
442     playModeSpinBox_ = new QComboBox();
443     playModeSpinBox_->addItem("Normal");
444     playModeSpinBox_->addItem("Loop");
445     playModeSpinBox_->addItem("Bounce");
446     //   Subframe Inbetweening
447     subframeCheckBox_ = new QCheckBox();
448 
449     // Init values of widgets
450     setPlaybackSettings(settings);
451 
452     // Organize widgets into a form layout
453     QFormLayout * formLayout = new QFormLayout();
454     formLayout->addRow(tr("FPS"), fpsSpinBox_);
455     formLayout->addRow(tr("Play Mode"), playModeSpinBox_);
456     formLayout->addRow(tr("Subframe Inbetweening"), subframeCheckBox_);
457 
458     // Create OK/Cancel buttons
459     QDialogButtonBox * buttonBox = new QDialogButtonBox(
460                 QDialogButtonBox::Ok |
461                 QDialogButtonBox::Cancel);
462     connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
463     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
464 
465     // Create and set dialog layout
466     QVBoxLayout * layout = new QVBoxLayout();
467     layout->addLayout(formLayout);
468     layout->addStretch();
469     layout->addWidget(buttonBox);
470     setLayout(layout);
471 }
472 
playbackSettings() const473 PlaybackSettings PlaybackSettingsDialog::playbackSettings() const
474 {
475     settings_.setFps(fpsSpinBox_->value());
476     settings_.setSubframeInbetweening(subframeCheckBox_->isChecked());
477     settings_.setPlayMode(static_cast<PlaybackSettings::PlayMode>(playModeSpinBox_->currentIndex()));
478 
479     return settings_;
480 }
481 
setPlaybackSettings(const PlaybackSettings & settings)482 void PlaybackSettingsDialog::setPlaybackSettings(const PlaybackSettings & settings)
483 {
484     settings_ = settings;
485 
486     fpsSpinBox_->setValue(settings_.fps());
487     subframeCheckBox_->setChecked(settings_.subframeInbetweening());
488     playModeSpinBox_->setCurrentIndex(static_cast<int>(settings_.playMode()));
489 }
490 
read(XmlStreamReader & xml)491 void Timeline::read(XmlStreamReader & xml)
492 {
493     settings_.read(xml);
494 
495     setFirstFrame(settings_.firstFrame());
496     setLastFrame(settings_.lastFrame());
497     setFps(settings_.fps());
498 }
499 
write(XmlStreamWriter & xml) const500 void Timeline::write(XmlStreamWriter & xml) const
501 {
502     settings_.write(xml);
503 }
504 
505 namespace
506 {
507 
makeButton_(const QString & iconPath,QAction * action)508 QPushButton * makeButton_(const QString & iconPath, QAction * action)
509 {
510     QPushButton * button = new QPushButton(QIcon(iconPath), "");
511 #ifdef Q_OS_MAC
512     button->setMaximumWidth(50);
513 #else
514     button->setMaximumSize(32,32);
515 #endif
516     button->setToolTip(action->toolTip());
517     button->setStatusTip(action->statusTip());
518     QObject::connect(button, SIGNAL(clicked()), action, SLOT(trigger()));
519     return button;
520 }
521 
522 }
523 
Timeline(Scene * scene,QWidget * parent)524 Timeline::Timeline(Scene *scene, QWidget *parent) :
525     QWidget(parent),
526     scene_(scene)
527 {
528     // initialisations
529     totalPixelOffset_ = 0;
530     selectionType_ = 0;
531 
532     // Horizontal bar (must be first cause some setValue() call hbar_->update())
533     hbar_ = new Timeline_HBar(this);
534 
535     // Open settings
536     QPushButton * settingsButton = new QPushButton(tr("Settings"));
537 #ifdef Q_OS_MAC
538     settingsButton->setMaximumWidth(80);
539 #else
540     settingsButton->setMaximumSize(64,32);
541 #endif
542     connect(settingsButton, SIGNAL(clicked()),
543           this, SLOT(openPlaybackSettingsDialog()));
544 
545     // ----- Create actions -----
546 
547     actionGoToFirstFrame_ = new QAction(tr("Go to first frame"), this);
548     actionGoToFirstFrame_->setStatusTip(tr("Set frame of active view to be the first frame in playback range."));
549     actionGoToFirstFrame_->setToolTip(QString(ACTION_MODIFIER_NAME_SHORT).toUpper() + tr(" + Left"));
550     actionGoToFirstFrame_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Left));
551     actionGoToFirstFrame_->setShortcutContext(Qt::ApplicationShortcut);
552     connect(actionGoToFirstFrame_, SIGNAL(triggered()), this, SLOT(goToFirstFrame()));
553 
554     actionGoToPreviousFrame_ = new QAction(tr("Go to previous frame"), this);
555     actionGoToPreviousFrame_->setStatusTip(tr("Set frame of active view to be the previous frame."));
556     actionGoToPreviousFrame_->setToolTip(tr("Left"));
557     actionGoToPreviousFrame_->setShortcut(QKeySequence(Qt::Key_Left));
558     actionGoToPreviousFrame_->setShortcutContext(Qt::ApplicationShortcut);
559     connect(actionGoToPreviousFrame_, SIGNAL(triggered()), this, SLOT(goToPreviousFrame()));
560 
561     actionPlayPause_ = new QAction(tr("Play/Pause"), this);
562     actionPlayPause_->setStatusTip(tr("Toggle between play and pause"));
563     actionPlayPause_->setToolTip(tr("Space"));
564     actionPlayPause_->setShortcut(QKeySequence(Qt::Key_Space));
565     actionPlayPause_->setShortcutContext(Qt::ApplicationShortcut);
566     connect(actionPlayPause_, SIGNAL(triggered()), this, SLOT(playPause()));
567 
568     actionGoToNextFrame_ = new QAction(tr("Go to next frame"), this);
569     actionGoToNextFrame_->setStatusTip(tr("Set frame of active view to be the next frame."));
570     actionGoToNextFrame_->setToolTip(tr("Right"));
571     actionGoToNextFrame_->setShortcut(QKeySequence(Qt::Key_Right));
572     actionGoToNextFrame_->setShortcutContext(Qt::ApplicationShortcut);
573     connect(actionGoToNextFrame_, SIGNAL(triggered()), this, SLOT(goToNextFrame()));
574 
575     actionGoToLastFrame_ = new QAction(tr("Go to last frame"), this);
576     actionGoToLastFrame_->setStatusTip(tr("Set frame of active view to be the last frame in playback range."));
577     actionGoToLastFrame_->setToolTip(QString(ACTION_MODIFIER_NAME_SHORT).toUpper() + tr(" + Right"));
578     actionGoToLastFrame_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Right));
579     actionGoToLastFrame_->setShortcutContext(Qt::WindowShortcut);
580     connect(actionGoToLastFrame_, SIGNAL(triggered()), this, SLOT(goToLastFrame()));
581 
582     // ----- Create buttons -----
583 
584     // Go to first frame
585     firstFrameButton_ = makeButton_(":/images/go-previous.png", actionGoToFirstFrame_);
586 
587     // Go to previous frame
588     previousFrameButton_ = makeButton_(":/images/go-first-view.png", actionGoToPreviousFrame_);
589 
590     // Play/pause
591     playPauseButton_ = makeButton_(":/images/go-play.png", actionPlayPause_);
592 
593     // Go to next frame
594     nextFrameButton_ = makeButton_(":/images/go-last-view.png", actionGoToNextFrame_);
595 
596     // Go to last frame
597     lastFrameButton_ = makeButton_(":/images/go-next.png", actionGoToLastFrame_);
598 
599     // Set first frame
600     firstFrameSpinBox_ = new QSpinBox();
601 #ifdef Q_OS_MAC
602     firstFrameSpinBox_->setMaximumWidth(48);
603 #else
604     firstFrameSpinBox_->setMaximumSize(48,32);
605 #endif
606     firstFrameSpinBox_->setMinimum(-100000); // 100.000 frames = about 1h at 24fps
607     firstFrameSpinBox_->setMaximum(100000); // 100.000 frames = about 1h at 24fps
608     setFirstFrame(0);
609     connect(firstFrameSpinBox_, SIGNAL(valueChanged(int)), this, SLOT(setFirstFrame(int)));
610 
611     // Set last Frame
612     lastFrameSpinBox_ = new QSpinBox();
613 #ifdef Q_OS_MAC
614     lastFrameSpinBox_->setMaximumWidth(48);
615 #else
616     lastFrameSpinBox_->setMaximumSize(48,32);
617 #endif
618     lastFrameSpinBox_->setMinimum(-100000); // 100.000 frames = about 1h at 24fps
619     lastFrameSpinBox_->setMaximum(100000); // 100.000 frames = about 1h at 24fps
620     setLastFrame(47);
621     connect(lastFrameSpinBox_, SIGNAL(valueChanged(int)), this, SLOT(setLastFrame(int)));
622 
623     // Set FPS
624     timer_ = new QTimer();
625     setFps(24);
626     connect(timer_, SIGNAL(timeout()), this, SLOT(timerTimeout()));
627 
628     // Global layout
629     QHBoxLayout * layout = new QHBoxLayout();
630     layout->setSpacing(0);
631     layout->setMargin(0);
632     layout->setContentsMargins(0, 8, 5, 0);
633     layout->addWidget(settingsButton);
634     layout->addSpacing(5);
635     layout->addWidget(firstFrameButton_);
636     layout->addWidget(previousFrameButton_);
637     layout->addWidget(playPauseButton_);
638     layout->addWidget(nextFrameButton_);
639     layout->addWidget(lastFrameButton_);
640     layout->addSpacing(5);
641     layout->addWidget(firstFrameSpinBox_);
642     layout->addSpacing(5);
643     layout->addWidget(hbar_);
644     layout->addSpacing(5);
645     layout->addWidget(lastFrameSpinBox_);
646     setLayout(layout);
647 }
648 
~Timeline()649 Timeline::~Timeline()
650 {
651     delete timer_;
652 }
653 
actionGoToFirstFrame() const654 QAction * Timeline::actionGoToFirstFrame() const
655 {
656     return actionGoToFirstFrame_;
657 }
658 
actionGoToPreviousFrame() const659 QAction * Timeline::actionGoToPreviousFrame() const
660 {
661     return actionGoToPreviousFrame_;
662 }
663 
actionPlayPause() const664 QAction * Timeline::actionPlayPause() const
665 {
666     return actionPlayPause_;
667 }
668 
actionGoToNextFrame() const669 QAction * Timeline::actionGoToNextFrame() const
670 {
671     return actionGoToNextFrame_;
672 }
673 
actionGoToLastFrame() const674 QAction * Timeline::actionGoToLastFrame() const
675 {
676     return actionGoToLastFrame_;
677 }
678 
679 
setSelectionType(int type)680 void Timeline::setSelectionType(int type)
681 {
682     selectionType_ = type;
683     update();
684 }
685 
setT(double t)686 void Timeline::setT(double t)
687 {
688     t_ = t;
689     update();
690 }
691 
setT1(double t1)692 void Timeline::setT1(double t1)
693 {
694     t1_ = t1;
695     update();
696 }
697 
setT2(double t2)698 void Timeline::setT2(double t2)
699 {
700     t2_ = t2;
701     update();
702 }
703 
paintEvent(QPaintEvent * event)704 void Timeline::paintEvent(QPaintEvent * event)
705 {
706     hbar_->update();
707     QWidget::paintEvent(event);
708 }
709 
710 
firstFrame() const711 int Timeline::firstFrame() const
712 {
713     return settings_.firstFrame();
714 }
715 
lastFrame() const716 int Timeline::lastFrame() const
717 {
718     return settings_.lastFrame();
719 }
720 
fps() const721 int Timeline::fps() const
722 {
723     return settings_.fps();
724 }
725 
subframeInbetweening() const726 bool Timeline::subframeInbetweening() const
727 {
728     return settings_.subframeInbetweening();
729 }
730 
playMode() const731 PlaybackSettings::PlayMode Timeline::playMode() const
732 {
733     return settings_.playMode();
734 }
735 
firstVisibleFrame() const736 int Timeline::firstVisibleFrame() const
737 {
738     return firstVisibleFrame_;
739 }
740 
lastVisibleFrame() const741 int Timeline::lastVisibleFrame() const
742 {
743     return lastVisibleFrame_;
744 }
745 
play()746 void Timeline::play()
747 {
748     if(playMode() != PlaybackSettings::BOUNCE)
749         playingDirection_ = true;
750 
751     playedViews_.clear();
752     View * view = global()->activeView();
753     if(view)
754     {
755         playedViews_ << global()->activeView();
756         foreach(View * view, playedViews())
757             view->disablePicking();
758         elapsedTimer_.start();
759         timer_->start();
760         playPauseButton_->setIcon(QIcon(":/images/go-pause.png"));
761     }
762 }
763 
pause()764 void Timeline::pause()
765 {
766     timer_->stop();
767     foreach(View * view, playedViews())
768         view->enablePicking();
769     roundPlayedViews();
770     playPauseButton_->setIcon(QIcon(":/images/go-play.png"));
771 }
772 
playPause()773 void Timeline::playPause()
774 {
775     if(isPlaying())
776         pause();
777     else
778         play();
779 }
780 
781 
roundPlayedViews()782 void Timeline::roundPlayedViews()
783 {
784     foreach(View * view, playedViews())
785     {
786         Time t = view->activeTime();
787         double floatFrame = t.floatTime();
788         int intFrame = std::floor(floatFrame+0.5);
789         goToFrame(view, intFrame);
790     }
791 }
792 
openPlaybackSettingsDialog()793 void Timeline::openPlaybackSettingsDialog()
794 {
795     PlaybackSettingsDialog * dialog = new PlaybackSettingsDialog(settings_);
796     int accepted = dialog->exec();
797     if(accepted)
798     {
799         settings_ = dialog->playbackSettings();
800         setFps(fps());
801     }
802     delete dialog;
803 }
804 
goToFirstFrame()805 void Timeline::goToFirstFrame()
806 {
807     goToFirstFrame(global()->activeView());
808 }
809 
goToFirstFrame(View * view)810 void Timeline::goToFirstFrame(View * view)
811 {
812     goToFrame(view, firstFrame());
813 }
814 
goToLastFrame()815 void Timeline::goToLastFrame()
816 {
817     goToLastFrame(global()->activeView());
818 }
819 
goToLastFrame(View * view)820 void Timeline::goToLastFrame(View * view)
821 {
822     goToFrame(view, lastFrame());
823 }
824 
setFirstFrame(int firstFrame)825 void Timeline::setFirstFrame(int firstFrame)
826 {
827     if(firstFrame > lastFrame())
828     {
829         firstFrame = lastFrame();
830     }
831     if(firstFrameSpinBox_->value() != firstFrame)
832     {
833         firstFrameSpinBox_->setValue(firstFrame);
834         lastFrameSpinBox_->setMinimum(firstFrame);
835     }
836     settings_.setFirstFrame(firstFrame);
837     hbar_->update();
838     emit playingWindowChanged();
839 }
840 
setLastFrame(int lastFrame)841 void Timeline::setLastFrame(int lastFrame)
842 {
843     if(lastFrame < firstFrame()) {
844         lastFrame = firstFrame();
845     }
846     if(lastFrameSpinBox_->value() != lastFrame)
847     {
848         lastFrameSpinBox_->setValue(lastFrame);
849         firstFrameSpinBox_->setMaximum(lastFrame);
850     }
851     settings_.setLastFrame(lastFrame);
852     hbar_->update();
853     emit playingWindowChanged();
854 }
855 
setFps(int fps)856 void Timeline::setFps(int fps)
857 {
858     if(subframeInbetweening())
859     {
860         timer_->setInterval(0);
861     }
862     else
863     {
864         int msec = 1000 / fps;
865         timer_->setInterval(msec);
866     }
867 }
868 
realTimePlayingChanged()869 void Timeline::realTimePlayingChanged()
870 {
871     setFps(fps());
872 }
873 
timerTimeout()874 void Timeline::timerTimeout()
875 {
876     int elapsedMsec = elapsedTimer_.elapsed();
877     if(elapsedMsec == 0)
878         return;
879 
880     elapsedTimer_.restart();
881 
882     foreach(View * view, playedViews())
883     {
884         if(isPlaying() && subframeInbetweening())
885         {
886             double nextFrame = view->activeTime().floatTime();
887 
888             if(playingDirection_)
889                 nextFrame += 0.001 * elapsedMsec * fps();
890             else
891                 nextFrame -= 0.001 * elapsedMsec * fps();
892 
893             switch(playMode())
894             {
895             case PlaybackSettings::NORMAL:
896                 if(nextFrame > lastFrame())
897                    pause();
898                 else if(nextFrame < firstFrame())
899                     goToFrame(view, firstFrame());
900                 else
901                     goToFrame(view, nextFrame);
902                 break;
903 
904             case PlaybackSettings::LOOP:
905                 if(nextFrame > lastFrame())
906                     goToFrame(view, firstFrame());
907                 else if(nextFrame < firstFrame())
908                     goToFrame(view, firstFrame());
909                 else
910                     goToFrame(view, nextFrame);
911                 break;
912 
913             case PlaybackSettings::BOUNCE:
914                 if(nextFrame > lastFrame())
915                 {
916                     playingDirection_ = false;
917                     goToFrame(view, lastFrame());
918                 }
919                 else if(nextFrame < firstFrame())
920                 {
921                     playingDirection_ = true;
922                     goToFrame(view, firstFrame());
923                 }
924                 else
925                     goToFrame(view, nextFrame);
926                 break;
927             }
928         }
929         else
930         {
931             switch(playMode())
932             {
933             case PlaybackSettings::NORMAL:
934             case PlaybackSettings::LOOP:
935                 if(playingDirection_)
936                     goToNextFrame(view);
937                 else
938                     goToPreviousFrame(view);
939                 break;
940 
941             case PlaybackSettings::BOUNCE:
942                 if(view->activeTime() >= lastFrame())
943                 {
944                     playingDirection_ = false;
945                     goToFrame(view, lastFrame()-1);
946                 }
947                 else if(view->activeTime() <= firstFrame())
948                 {
949                     playingDirection_ = true;
950                     goToFrame(view, firstFrame()+1);
951                 }
952                 else
953                 {
954                     if(playingDirection_)
955                         goToNextFrame(view);
956                     else
957                         goToPreviousFrame(view);
958                 }
959                 break;
960             }
961         }
962     }
963 }
964 
goToNextFrame()965 void Timeline::goToNextFrame()
966 {
967     goToNextFrame(global()->activeView());
968 }
969 
970 // There are multiple implementations of goToNextFrame and goToPreviousFrame
971 // See https://github.com/dalboris/vpaint/pull/4#issuecomment-130426290 for more details
972 // Will likely be configurable through preferences one day
973 // Implementation 1
goToNextFrame(View * view)974 void Timeline::goToNextFrame(View * view)
975 {
976     int currentFrame = view->activeTime().floatTime();
977 
978     if(isPlaying()) {
979         if(currentFrame < firstFrame())
980         {
981             goToFrame(view, firstFrame());
982         }
983         else if(currentFrame >= lastFrame())
984         {
985             if(playMode() == PlaybackSettings::LOOP)
986             {
987                 goToFrame(view, firstFrame());
988             }
989             else
990             {
991                 pause();
992             }
993         }
994         else
995         {
996             goToFrame(view, currentFrame+1);
997         }
998     }
999     else
1000     {
1001         goToFrame(view, currentFrame+1);
1002     }
1003 }
1004 
1005 // Implementation 2
1006 /*void Timeline::goToNextFrame(View * view)
1007 {
1008     int currentFrame = view->activeTime().floatTime();
1009 
1010     if(currentFrame < firstFrame())
1011     {
1012         goToFrame(view, firstFrame());
1013     }
1014     else if(currentFrame >= lastFrame())
1015     {
1016         if(playMode() == PlaybackSettings::LOOP)
1017         {
1018             goToFrame(view, firstFrame());
1019         }
1020         else
1021         {
1022             pause();
1023         }
1024     }
1025     else
1026     {
1027         goToFrame(view, currentFrame+1);
1028     }
1029 }*/
1030 
1031 // Implemenation 3
1032 /*void Timeline::goToNextFrame(View * view)
1033 {
1034     int currentFrame = view->activeTime().floatTime();
1035 
1036     if(currentFrame < firstFrame())
1037     {
1038         goToFrame(view, firstFrame());
1039     }
1040     else if(currentFrame >= lastFrame())
1041     {
1042         if(playMode() == PlaybackSettings::LOOP && isPlaying())
1043         {
1044             goToFrame(view, firstFrame());
1045         }
1046         else
1047         {
1048             pause();
1049         }
1050     }
1051     else
1052     {
1053         goToFrame(view, currentFrame+1);
1054     }
1055 }*/
1056 
goToPreviousFrame()1057 void Timeline::goToPreviousFrame()
1058 {
1059     goToPreviousFrame(global()->activeView());
1060 }
1061 
1062 // See comment above goToNextFrame
1063 // Implementation 1
goToPreviousFrame(View * view)1064 void Timeline::goToPreviousFrame(View * view)
1065 {
1066     int currentFrame = view->activeTime().floatTime();
1067 
1068     if(isPlaying()) {
1069         if(currentFrame > lastFrame())
1070         {
1071             goToFrame(view, lastFrame());
1072         }
1073         else if(currentFrame <= firstFrame())
1074         {
1075             if(playMode() == PlaybackSettings::LOOP)
1076             {
1077                 goToFrame(view, lastFrame());
1078             }
1079             else
1080             {
1081                 pause();
1082             }
1083         }
1084         else
1085         {
1086             goToFrame(view, currentFrame-1);
1087         }
1088     }
1089     else
1090     {
1091         goToFrame(view, currentFrame-1);
1092     }
1093 }
1094 
1095 // Implementation 2
1096 /*void Timeline::goToPreviousFrame(View * view)
1097 {
1098     int currentFrame = view->activeTime().floatTime();
1099 
1100     if(currentFrame > lastFrame())
1101     {
1102         goToFrame(view, lastFrame());
1103     }
1104     else if(currentFrame <= firstFrame())
1105     {
1106         if(playMode() == PlaybackSettings::LOOP)
1107         {
1108             goToFrame(view, lastFrame());
1109         }
1110         else
1111         {
1112             pause();
1113         }
1114     }
1115     else
1116     {
1117         goToFrame(view, currentFrame-1);
1118     }
1119 }*/
1120 
1121 // Implemenation 3
1122 /*void Timeline::goToPreviousFrame(View * view)
1123 {
1124     int currentFrame = view->activeTime().floatTime();
1125 
1126     if(currentFrame > lastFrame())
1127     {
1128         goToFrame(view, lastFrame());
1129     }
1130     else if(currentFrame <= firstFrame())
1131     {
1132         if(playMode() == PlaybackSettings::LOOP && isPlaying())
1133         {
1134             goToFrame(view, lastFrame());
1135         }
1136         else
1137         {
1138             pause();
1139         }
1140     }
1141     else
1142     {
1143         goToFrame(view, currentFrame-1);
1144     }
1145 }*/
1146 
goToFrame(View * view,double frame)1147 void Timeline::goToFrame(View * view, double frame)
1148 {
1149     view->setActiveTime(Time(frame)); // float time
1150     hbar_->repaint();
1151     emit timeChanged();
1152 }
1153 
goToFrame(View * view,int frame)1154 void Timeline::goToFrame(View * view, int frame)
1155 {
1156     view->setActiveTime(Time(frame)); // exact frame
1157     hbar_->repaint();
1158     emit timeChanged();
1159 }
1160 
addView(View * view)1161 void Timeline::addView(View * view)
1162 {
1163     views_ << view;
1164     connect(view, SIGNAL(settingsChanged()), this, SLOT(update()));
1165     hbar_->update();
1166 }
1167 
removeView(View * view)1168 void Timeline::removeView(View * view)
1169 {
1170     views_.removeAll(view);
1171     hbar_->update();
1172 }
1173 
isPlaying() const1174 bool Timeline::isPlaying() const
1175 {
1176     return timer_->isActive();
1177 }
1178 
playedViews() const1179 QSet<View*> Timeline::playedViews() const
1180 {
1181     return playedViews_;
1182 }
1183