All Classes Namespaces Functions Variables Enumerations Properties Pages
timecontrols.cpp
1 /*
2 
3 Pencil2D - Traditional Animation Software
4 Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5 Copyright (C) 2012-2020 Matthew Chiawen Chang
6 
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; version 2 of the License.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15 
16 */
17 
18 #include "timecontrols.h"
19 
20 #include <QLabel>
21 #include <QSettings>
22 #include <QMenu>
23 #include <QDebug>
24 #include "editor.h"
25 #include "playbackmanager.h"
26 #include "layermanager.h"
27 #include "pencildef.h"
28 #include "util.h"
29 #include "preferencemanager.h"
30 #include "timeline.h"
31 #include "pencildef.h"
32 
33 TimeControls::TimeControls(TimeLine* parent) : QToolBar(parent)
34 {
35  mTimeline = parent;
36 }
37 
38 void TimeControls::initUI()
39 {
40  QSettings settings(PENCIL2D, PENCIL2D);
41 
42  mFpsBox = new QSpinBox(this);
43  mFpsBox->setFixedHeight(24);
44  mFpsBox->setValue(settings.value("Fps").toInt());
45  mFpsBox->setMinimum(1);
46  mFpsBox->setMaximum(90);
47  mFpsBox->setSuffix(tr(" fps"));
48  mFpsBox->setToolTip(tr("Frames per second"));
50 
51  mFps = mFpsBox->value();
52  mTimecodeSelect = new QToolButton(this);
53  mTimecodeSelect->setIcon(QIcon(":app/icons/new/svg/more_options.svg"));
54  mTimecodeSelect->setPopupMode(QToolButton::InstantPopup);
55  mTimecodeSelect->addAction(mNoTimecodeAction = new QAction(tr("No text"), this));
56  mTimecodeSelect->addAction(mOnlyFramesAction = new QAction(tr("Frames"), this));
57  mTimecodeSelect->addAction(mSmpteAction = new QAction(tr("SMPTE Timecode"), this));
58  mTimecodeSelect->addAction(mSffAction = new QAction(tr("SFF Timecode"), this));
59  mTimecodeLabelEnum = mEditor->preference()->getInt(SETTING::TIMECODE_TEXT);
60  mTimecodeLabel = new QLabel(this);
61  mTimecodeLabel->setContentsMargins(2, 0, 0, 0);
62  mTimecodeLabel->setText("");
63 
64  switch (mTimecodeLabelEnum)
65  {
66  case NOTEXT:
67  mTimecodeLabel->setToolTip("");
68  break;
69  case FRAMES:
70  mTimecodeLabel->setToolTip(tr("Actual frame number"));
71  break;
72  case SMPTE:
73  mTimecodeLabel->setToolTip(tr("Timecode format MM:SS:FF"));
74  break;
75  case SFF:
76  mTimecodeLabel->setToolTip(tr("Timecode format S:FF"));
77  break;
78  default:
79  mTimecodeLabel->setToolTip("");
80  }
81 
82  mLoopStartSpinBox = new QSpinBox(this);
83  mLoopStartSpinBox->setFixedHeight(24);
84  mLoopStartSpinBox->setValue(settings.value("loopStart").toInt());
85  mLoopStartSpinBox->setMinimum(1);
86  mLoopStartSpinBox->setEnabled(false);
87  mLoopStartSpinBox->setToolTip(tr("Start of playback loop"));
88  mLoopStartSpinBox->setFocusPolicy(Qt::WheelFocus);
89 
90  mLoopEndSpinBox = new QSpinBox(this);
91  mLoopEndSpinBox->setFixedHeight(24);
92  mLoopEndSpinBox->setValue(settings.value("loopEnd").toInt());
93  mLoopEndSpinBox->setMinimum(2);
94  mLoopEndSpinBox->setEnabled(false);
95  mLoopEndSpinBox->setToolTip(tr("End of playback loop"));
96  mLoopEndSpinBox->setFocusPolicy(Qt::WheelFocus);
97 
98  mPlaybackRangeCheckBox = new QCheckBox(tr("Range"));
99  mPlaybackRangeCheckBox->setFixedHeight(24);
100  mPlaybackRangeCheckBox->setToolTip(tr("Playback range"));
101 
102  mPlayButton = new QPushButton(this);
103  mLoopButton = new QPushButton(this);
104  mSoundButton = new QPushButton(this);
105  mSoundScrubButton = new QPushButton(this);
106  mJumpToEndButton = new QPushButton(this);
107  mJumpToStartButton = new QPushButton(this);
108 
109  mLoopIcon = QIcon(":icons/controls/loop.png");
110  mSoundIcon = QIcon(":icons/controls/sound.png");
111  if (mEditor->preference()->isOn(SETTING::SOUND_SCRUB_ACTIVE))
112  mSoundScrubIcon = QIcon(":icons/controls/soundscrub.png");
113  else
114  mSoundScrubIcon = QIcon(":icons/controls/soundscrub-disabled.png");
115  mJumpToEndIcon = QIcon(":icons/controls/endplay.png");
116  mJumpToStartIcon = QIcon(":icons/controls/startplay.png");
117  mStartIcon = QIcon(":icons/controls/play.png");
118  mStopIcon = QIcon(":icons/controls/stop.png");
119  mPlayButton->setIcon(mStartIcon);
120  mLoopButton->setIcon(mLoopIcon);
121  mSoundButton->setIcon(mSoundIcon);
122  mSoundScrubButton->setIcon(mSoundScrubIcon);
123  mJumpToEndButton->setIcon(mJumpToEndIcon);
124  mJumpToStartButton->setIcon(mJumpToStartIcon);
125 
126  mPlayButton->setToolTip(tr("Play"));
127  mLoopButton->setToolTip(tr("Loop"));
128  mSoundButton->setToolTip(tr("Sound on/off"));
129  mSoundScrubButton->setToolTip(tr("Sound scrub on/off"));
130  mJumpToEndButton->setToolTip(tr("Jump to the End", "Tooltip of the jump to end button"));
131  mJumpToStartButton->setToolTip(tr("Jump to the Start", "Tooltip of the jump to start button"));
132 
133  mLoopButton->setCheckable(true);
134  mSoundButton->setCheckable(true);
135  mSoundButton->setChecked(true);
136  mSoundScrubButton->setCheckable(true);
137  mSoundScrubButton->setChecked(mEditor->preference()->isOn(SETTING::SOUND_SCRUB_ACTIVE));
138 
139 
140  addWidget(mJumpToStartButton);
141  addWidget(mPlayButton);
142  addWidget(mJumpToEndButton);
143  addWidget(mLoopButton);
144  addWidget(mFpsBox);
145  addWidget(mPlaybackRangeCheckBox);
146  addWidget(mLoopStartSpinBox);
147  addWidget(mLoopEndSpinBox);
148  addWidget(mSoundButton);
149  addWidget(mSoundScrubButton);
150  addWidget(mTimecodeSelect);
151  mTimecodeLabelAction = addWidget(mTimecodeLabel);
152 
153  makeConnections();
154 
155  updateUI();
156 }
157 
158 void TimeControls::updateUI()
159 {
160  PlaybackManager* playback = mEditor->playback();
161 
162  mPlaybackRangeCheckBox->setChecked(playback->isRangedPlaybackOn()); // don't block this signal since it enables start/end range spinboxes.
163 
164  QSignalBlocker b1(mLoopStartSpinBox);
165  mLoopStartSpinBox->setValue(playback->markInFrame());
166 
167  QSignalBlocker b2(mLoopEndSpinBox);
168  mLoopEndSpinBox->setValue(playback->markOutFrame());
169 
170  QSignalBlocker b3(mFpsBox);
171  mFpsBox->setValue(playback->fps());
172 
173  QSignalBlocker b4(mLoopButton);
174  mLoopButton->setChecked(playback->isLooping());
175 }
176 
177 void TimeControls::setEditor(Editor* editor)
178 {
179  Q_ASSERT(editor != nullptr);
180  mEditor = editor;
181 }
182 
183 void TimeControls::setFps(int value)
184 {
185  QSignalBlocker blocker(mFpsBox);
186  mFpsBox->setValue(value);
187  mFps = value;
188  updateTimecodeLabel(mEditor->currentFrame());
189 }
190 
191 void TimeControls::setLoop(bool checked)
192 {
193  mLoopButton->setChecked(checked);
194 }
195 
196 void TimeControls::setRangeState(bool checked)
197 {
198  mPlaybackRangeCheckBox->setChecked(checked);
199  mTimeline->updateLength();
200 }
201 
202 void TimeControls::makeConnections()
203 {
204  connect(mPlayButton, &QPushButton::clicked, this, &TimeControls::playButtonClicked);
205  connect(mJumpToEndButton, &QPushButton::clicked, this, &TimeControls::jumpToEndButtonClicked);
206  connect(mJumpToStartButton, &QPushButton::clicked, this, &TimeControls::jumpToStartButtonClicked);
207  connect(mLoopButton, &QPushButton::clicked, this, &TimeControls::loopButtonClicked);
208  connect(mPlaybackRangeCheckBox, &QCheckBox::clicked, this, &TimeControls::playbackRangeClicked);
209 
210  auto spinBoxValueChanged = static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged);
211  connect(mLoopStartSpinBox, spinBoxValueChanged, this, &TimeControls::loopStartValueChanged);
212  clearFocusOnFinished(mLoopStartSpinBox);
213  connect(mLoopEndSpinBox, spinBoxValueChanged, this, &TimeControls::loopEndValueChanged);
214  clearFocusOnFinished(mLoopEndSpinBox);
215 
216  connect(mPlaybackRangeCheckBox, &QCheckBox::toggled, mLoopStartSpinBox, &QSpinBox::setEnabled);
217  connect(mPlaybackRangeCheckBox, &QCheckBox::toggled, mLoopEndSpinBox, &QSpinBox::setEnabled);
218 
219  connect(mSoundButton, &QPushButton::clicked, this, &TimeControls::soundToggled);
220  connect(mSoundButton, &QPushButton::clicked, this, &TimeControls::updateSoundIcon);
221 
222  connect(mSoundScrubButton, &QPushButton::clicked, this, &TimeControls::soundScrubToggled);
223  connect(mSoundScrubButton, &QPushButton::clicked, this, &TimeControls::updateSoundScrubIcon);
224 
225  connect(mFpsBox, spinBoxValueChanged, this, &TimeControls::fpsChanged);
227 
228  connect(mFpsBox, spinBoxValueChanged, this, &TimeControls::setFps);
229  connect(mEditor, &Editor::fpsChanged, this, &TimeControls::setFps);
230  connect(mNoTimecodeAction, &QAction::triggered, this, &TimeControls::noTimecodeText);
231  connect(mOnlyFramesAction, &QAction::triggered, this, &TimeControls::onlyFramesText);
232  connect(mSmpteAction, &QAction::triggered, this, &TimeControls::smpteText);
233  connect(mSffAction, &QAction::triggered, this, &TimeControls::sffText);
234 }
235 
236 void TimeControls::playButtonClicked()
237 {
238  emit playButtonTriggered();
239 }
240 
241 void TimeControls::updatePlayState()
242 {
243  if (mEditor->playback()->isPlaying())
244  {
245  mPlayButton->setIcon(mStopIcon);
246  mPlayButton->setToolTip(tr("Stop"));
247  }
248  else
249  {
250  mPlayButton->setIcon(mStartIcon);
251  mPlayButton->setToolTip(tr("Play"));
252  }
253 }
254 
255 void TimeControls::jumpToStartButtonClicked()
256 {
257  if (mPlaybackRangeCheckBox->isChecked())
258  {
259  mEditor->scrubTo(mLoopStartSpinBox->value());
260  mEditor->playback()->setCheckForSoundsHalfway(true);
261  }
262  else
263  {
264  mEditor->scrubTo(mEditor->layers()->firstKeyFrameIndex());
265  }
266  mEditor->playback()->stopSounds();
267 }
268 
269 void TimeControls::jumpToEndButtonClicked()
270 {
271  if (mPlaybackRangeCheckBox->isChecked())
272  {
273  mEditor->scrubTo(mLoopEndSpinBox->value());
274  }
275  else
276  {
277  mEditor->scrubTo(mEditor->layers()->lastKeyFrameIndex());
278  }
279 }
280 
281 void TimeControls::loopButtonClicked(bool bChecked)
282 {
283  mEditor->playback()->setLooping(bChecked);
284 }
285 
286 void TimeControls::playbackRangeClicked(bool bChecked)
287 {
288  mEditor->playback()->enableRangedPlayback(bChecked);
289 }
290 
291 void TimeControls::loopStartValueChanged(int i)
292 {
293  if (i >= mLoopEndSpinBox->value())
294  {
295  mLoopEndSpinBox->setValue(i + 1);
296  }
297  mLoopEndSpinBox->setMinimum(i + 1);
298 
299  mEditor->playback()->setRangedStartFrame(i);
300  mTimeline->updateLength();
301 }
302 
303 void TimeControls::loopEndValueChanged(int i)
304 {
305  mEditor->playback()->setRangedEndFrame(i);
306  mTimeline->updateLength();
307 }
308 
309 void TimeControls::updateSoundIcon(bool soundEnabled)
310 {
311  if (soundEnabled)
312  {
313  mSoundButton->setIcon(QIcon(":icons/controls/sound.png"));
314  }
315  else
316  {
317  mSoundButton->setIcon(QIcon(":icons/controls/sound-disabled.png"));
318  }
319 }
320 
321 void TimeControls::updateSoundScrubIcon(bool soundScrubEnabled)
322 {
323  if (soundScrubEnabled)
324  {
325  mSoundScrubButton->setIcon(QIcon(":icons/controls/soundscrub.png"));
326  mEditor->playback()->setSoundScrubActive(true);
327  mEditor->preference()->set(SETTING::SOUND_SCRUB_ACTIVE, true);
328  }
329  else
330  {
331  mSoundScrubButton->setIcon(QIcon(":icons/controls/soundscrub-disabled.png"));
332  mEditor->playback()->setSoundScrubActive(false);
333  mEditor->preference()->set(SETTING::SOUND_SCRUB_ACTIVE, false);
334  }
335 }
336 
337 void TimeControls::noTimecodeText()
338 {
339  QSettings settings(PENCIL2D, PENCIL2D);
340  settings.setValue(SETTING_TIMECODE_TEXT, NOTEXT);
341  mTimecodeLabelEnum = NOTEXT;
342  mTimecodeLabel->setToolTip(tr(""));
343  updateTimecodeLabel(mEditor->currentFrame());
344 }
345 
346 void TimeControls::onlyFramesText()
347 {
348  QSettings settings(PENCIL2D, PENCIL2D);
349  settings.setValue(SETTING_TIMECODE_TEXT, FRAMES);
350  mTimecodeLabelEnum = FRAMES;
351  mTimecodeLabel->setToolTip(tr("Actual frame number"));
352  updateTimecodeLabel(mEditor->currentFrame());
353 }
354 
355 void TimeControls::sffText()
356 {
357  QSettings settings(PENCIL2D, PENCIL2D);
358  settings.setValue(SETTING_TIMECODE_TEXT, SFF);
359  mTimecodeLabelEnum = SFF;
360  mTimecodeLabel->setToolTip(tr("Timecode format S:FF"));
361  updateTimecodeLabel(mEditor->currentFrame());
362 }
363 
364 void TimeControls::smpteText()
365 {
366  QSettings settings(PENCIL2D, PENCIL2D);
367  settings.setValue(SETTING_TIMECODE_TEXT, SMPTE);
368  mTimecodeLabelEnum = SMPTE;
369  mTimecodeLabel->setToolTip(tr("Timecode format MM:SS:FF"));
370  updateTimecodeLabel(mEditor->currentFrame());
371 }
372 
374 {
375  mFpsBox->clearFocus();
376  emit fpsChanged(mFpsBox->value());
377  mFps = mFpsBox->value();
378 }
379 
380 void TimeControls::updateTimecodeLabel(int frame)
381 {
382  mTimecodeLabelAction->setVisible(true);
383 
384  switch (mTimecodeLabelEnum)
385  {
386  case TimecodeTextLevel::SMPTE:
387  mTimecodeLabel->setText(QString("%1:%2:%3")
388  .arg(QString::number(frame / (60 * mFps) % 60).rightJustified(2,'0'))
389  .arg(QString::number(frame / mFps % 60).rightJustified(2, '0'))
390  .arg(QString::number(frame % mFps).rightJustified(2, '0')));
391  break;
392  case TimecodeTextLevel::SFF:
393  mTimecodeLabel->setText(QString("%1:%2")
394  .arg(QString::number(frame / mFps))
395  .arg(QString::number(frame % mFps).rightJustified(2, '0')));
396  break;
397  case TimecodeTextLevel::FRAMES:
398  mTimecodeLabel->setText(tr("%1").arg(QString::number(frame).rightJustified(4, '0')));
399  break;
400  case TimecodeTextLevel::NOTEXT:
401  default:
402  mTimecodeLabelAction->setVisible(false);
403  break;
404  }
405 
406 }
407 
408 void TimeControls::updateLength(int frameLength)
409 {
410  mLoopStartSpinBox->setMaximum(frameLength - 1);
411  mLoopEndSpinBox->setMaximum(frameLength);
412 }
void setMinimum(int min)
void triggered(bool checked)
WheelFocus
void setSuffix(const QString &suffix)
void addAction(QAction *action)
void setFocusPolicy(Qt::FocusPolicy policy)
void setVisible(bool)
QAction * addWidget(QWidget *widget)
void onFpsEditingFinished()
Work-around in case the FPS spin-box "valueChanged" signal doesn't work.
void setIcon(const QIcon &icon)
QString tr(const char *sourceText, const char *disambiguation, int n)
void valueChanged(int i)
void setEnabled(bool)
QString number(int n, int base)
void clicked(bool checked)
void setText(const QString &)
void setCheckable(bool)
void setMaximum(int max)
void setChecked(bool)
void clearFocus()
void editingFinished()
void setFixedHeight(int h)
void setValue(int val)
void toggled(bool checked)
void setPopupMode(QToolButton::ToolButtonPopupMode mode)
void setToolTip(const QString &)
Definition: editor.h:51
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
void setContentsMargins(int left, int top, int right, int bottom)