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