1 /***
2
3 Olive - Non-Linear Video Editor
4 Copyright (C) 2019 Olive Team
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
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 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 ***/
20
21 #include "timeline.h"
22
23 #include <QTime>
24 #include <QScrollBar>
25 #include <QtMath>
26 #include <QGuiApplication>
27 #include <QScreen>
28 #include <QPainter>
29 #include <QInputDialog>
30 #include <QMessageBox>
31 #include <QCheckBox>
32 #include <QPushButton>
33 #include <QHBoxLayout>
34 #include <QSplitter>
35 #include <QStatusBar>
36
37 #include "global/global.h"
38 #include "panels/panels.h"
39 #include "project/projectelements.h"
40 #include "ui/timelinewidget.h"
41 #include "ui/icons.h"
42 #include "ui/viewerwidget.h"
43 #include "rendering/audio.h"
44 #include "rendering/cacher.h"
45 #include "rendering/renderfunctions.h"
46 #include "global/config.h"
47 #include "project/clipboard.h"
48 #include "ui/timelineheader.h"
49 #include "ui/resizablescrollbar.h"
50 #include "ui/audiomonitor.h"
51 #include "ui/flowlayout.h"
52 #include "ui/cursors.h"
53 #include "ui/mainwindow.h"
54 #include "undo/undostack.h"
55 #include "global/debug.h"
56 #include "ui/menu.h"
57
58 int olive::timeline::kTrackDefaultHeight = 40;
59 int olive::timeline::kTrackMinHeight = 30;
60 int olive::timeline::kTrackHeightIncrement = 10;
61
Timeline(QWidget * parent)62 Timeline::Timeline(QWidget *parent) :
63 Panel(parent),
64 cursor_frame(0),
65 cursor_track(0),
66 zoom(1.0),
67 zoom_just_changed(false),
68 showing_all(false),
69 snapping(true),
70 snapped(false),
71 snap_point(0),
72 selecting(false),
73 rect_select_init(false),
74 rect_select_proc(false),
75 moving_init(false),
76 moving_proc(false),
77 move_insert(false),
78 trim_target(-1),
79 trim_type(TRIM_NONE),
80 splitting(false),
81 importing(false),
82 importing_files(false),
83 creating(false),
84 transition_tool_init(false),
85 transition_tool_proc(false),
86 transition_tool_open_clip(-1),
87 transition_tool_close_clip(-1),
88 hand_moving(false),
89 block_repaints(false),
90 scroll(0)
91 {
92 setup_ui();
93
94 headers->viewer = panel_sequence_viewer;
95
96 video_area->bottom_align = true;
97 video_area->scrollBar = videoScrollbar;
98 audio_area->scrollBar = audioScrollbar;
99
100 tool_buttons.append(toolArrowButton);
101 tool_buttons.append(toolEditButton);
102 tool_buttons.append(toolRippleButton);
103 tool_buttons.append(toolRazorButton);
104 tool_buttons.append(toolSlipButton);
105 tool_buttons.append(toolSlideButton);
106 tool_buttons.append(toolTransitionButton);
107 tool_buttons.append(toolHandButton);
108
109 toolArrowButton->click();
110
111 connect(horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(setScroll(int)));
112 connect(videoScrollbar, SIGNAL(valueChanged(int)), video_area, SLOT(setScroll(int)));
113 connect(audioScrollbar, SIGNAL(valueChanged(int)), audio_area, SLOT(setScroll(int)));
114 connect(horizontalScrollBar, SIGNAL(resize_move(double)), this, SLOT(resize_move(double)));
115
116 update_sequence();
117
118 Retranslate();
119 }
120
~Timeline()121 Timeline::~Timeline() {}
122
Retranslate()123 void Timeline::Retranslate() {
124 toolArrowButton->setToolTip(tr("Pointer Tool") + " (V)");
125 toolEditButton->setToolTip(tr("Edit Tool") + " (X)");
126 toolRippleButton->setToolTip(tr("Ripple Tool") + " (B)");
127 toolRazorButton->setToolTip(tr("Razor Tool") + " (C)");
128 toolSlipButton->setToolTip(tr("Slip Tool") + " (Y)");
129 toolSlideButton->setToolTip(tr("Slide Tool") + " (U)");
130 toolHandButton->setToolTip(tr("Hand Tool") + " (H)");
131 toolTransitionButton->setToolTip(tr("Transition Tool") + " (T)");
132 snappingButton->setToolTip(tr("Snapping") + " (S)");
133 zoomInButton->setToolTip(tr("Zoom In") + " (=)");
134 zoomOutButton->setToolTip(tr("Zoom Out") + " (-)");
135 recordButton->setToolTip(tr("Record audio"));
136 addButton->setToolTip(tr("Add title, solid, bars, etc."));
137
138 UpdateTitle();
139 }
140
split_clip_at_positions(ComboAction * ca,int clip_index,QVector<long> positions)141 void Timeline::split_clip_at_positions(ComboAction* ca, int clip_index, QVector<long> positions) {
142
143 QVector<int> pre_splits;
144
145 // Add the clip and each of its links to the pre_splits array
146 Clip* clip = olive::ActiveSequence->clips.at(clip_index).get();
147 pre_splits.append(clip_index);
148 for (int i=0;i<clip->linked.size();i++) {
149 pre_splits.append(clip->linked.at(i));
150 }
151
152 std::sort(positions.begin(), positions.end());
153
154 // Remove any duplicate positions
155 for (int i=1;i<positions.size();i++) {
156 if (positions.at(i-1) == positions.at(i)) {
157 positions.removeAt(i);
158 i--;
159 }
160 }
161
162 for (int i=1;i<positions.size();i++) {
163 Q_ASSERT(positions.at(i-1) < positions.at(i));
164 }
165
166 QVector< QVector<ClipPtr> > post_splits(positions.size());
167
168 for (int i=positions.size()-1;i>=0;i--) {
169
170 post_splits[i].resize(pre_splits.size());
171
172 for (int j=0;j<pre_splits.size();j++) {
173 post_splits[i][j] = split_clip(ca, true, pre_splits.at(j), positions.at(i));
174
175 if (post_splits[i][j] != nullptr && i + 1 < positions.size()) {
176 post_splits[i][j]->set_timeline_out(positions.at(i+1));
177 }
178 }
179 }
180
181 for (int i=0;i<post_splits.size();i++) {
182 relink_clips_using_ids(pre_splits, post_splits[i]);
183 ca->append(new AddClipCommand(olive::ActiveSequence.get(), post_splits[i]));
184 }
185
186 }
187
previous_cut()188 void Timeline::previous_cut() {
189 if (olive::ActiveSequence != nullptr
190 && olive::ActiveSequence->playhead > 0) {
191 long p_cut = 0;
192 for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
193 ClipPtr c = olive::ActiveSequence->clips.at(i);
194 if (c != nullptr) {
195 if (c->timeline_out() > p_cut && c->timeline_out() < olive::ActiveSequence->playhead) {
196 p_cut = c->timeline_out();
197 } else if (c->timeline_in() > p_cut && c->timeline_in() < olive::ActiveSequence->playhead) {
198 p_cut = c->timeline_in();
199 }
200 }
201 }
202 panel_sequence_viewer->seek(p_cut);
203 }
204 }
205
next_cut()206 void Timeline::next_cut() {
207 if (olive::ActiveSequence != nullptr) {
208 bool seek_enabled = false;
209 long n_cut = LONG_MAX;
210 for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
211 ClipPtr c = olive::ActiveSequence->clips.at(i);
212 if (c != nullptr) {
213 if (c->timeline_in() < n_cut && c->timeline_in() > olive::ActiveSequence->playhead) {
214 n_cut = c->timeline_in();
215 seek_enabled = true;
216 } else if (c->timeline_out() < n_cut && c->timeline_out() > olive::ActiveSequence->playhead) {
217 n_cut = c->timeline_out();
218 seek_enabled = true;
219 }
220 }
221 }
222 if (seek_enabled) panel_sequence_viewer->seek(n_cut);
223 }
224 }
225
ripple_clips(ComboAction * ca,Sequence * s,long point,long length,const QVector<int> & ignore)226 void ripple_clips(ComboAction* ca, Sequence* s, long point, long length, const QVector<int>& ignore) {
227 ca->append(new RippleAction(s, point, length, ignore));
228 }
229
toggle_show_all()230 void Timeline::toggle_show_all() {
231 if (olive::ActiveSequence != nullptr) {
232 showing_all = !showing_all;
233 if (showing_all) {
234 old_zoom = zoom;
235 set_zoom_value(double(timeline_area->width() - 200) / double(olive::ActiveSequence->getEndFrame()));
236 } else {
237 set_zoom_value(old_zoom);
238 }
239 }
240 }
241
create_ghosts_from_media(Sequence * seq,long entry_point,QVector<olive::timeline::MediaImportData> & media_list)242 void Timeline::create_ghosts_from_media(Sequence* seq, long entry_point, QVector<olive::timeline::MediaImportData>& media_list) {
243 video_ghosts = false;
244 audio_ghosts = false;
245
246 for (int i=0;i<media_list.size();i++) {
247 bool can_import = true;
248
249 const olive::timeline::MediaImportData import_data = media_list.at(i);
250 Media* medium = import_data.media();
251 Footage* m = nullptr;
252 Sequence* s = nullptr;
253 long sequence_length = 0;
254 long default_clip_in = 0;
255 long default_clip_out = 0;
256
257 switch (medium->get_type()) {
258 case MEDIA_TYPE_FOOTAGE:
259 m = medium->to_footage();
260 can_import = m->ready;
261 if (m->using_inout) {
262 double source_fr = 30;
263 if (m->video_tracks.size() > 0 && !qIsNull(m->video_tracks.at(0).video_frame_rate)) {
264 source_fr = m->video_tracks.at(0).video_frame_rate * m->speed;
265 }
266 default_clip_in = rescale_frame_number(m->in, source_fr, seq->frame_rate);
267 default_clip_out = rescale_frame_number(m->out, source_fr, seq->frame_rate);
268 }
269 break;
270 case MEDIA_TYPE_SEQUENCE:
271 s = medium->to_sequence().get();
272 sequence_length = s->getEndFrame();
273 if (seq != nullptr) sequence_length = rescale_frame_number(sequence_length, s->frame_rate, seq->frame_rate);
274 can_import = (s != seq && sequence_length != 0);
275 if (s->using_workarea) {
276 default_clip_in = rescale_frame_number(s->workarea_in, s->frame_rate, seq->frame_rate);
277 default_clip_out = rescale_frame_number(s->workarea_out, s->frame_rate, seq->frame_rate);
278 }
279 break;
280 default:
281 can_import = false;
282 }
283
284 if (can_import) {
285 Ghost g;
286 g.clip = -1;
287 g.trim_type = TRIM_NONE;
288 g.old_clip_in = g.clip_in = default_clip_in;
289 g.media = medium;
290 g.in = entry_point;
291 g.transition = nullptr;
292
293 switch (medium->get_type()) {
294 case MEDIA_TYPE_FOOTAGE:
295 // is video source a still image?
296 if (m->video_tracks.size() > 0 && m->video_tracks.at(0).infinite_length && m->audio_tracks.size() == 0) {
297 g.out = g.in + 100;
298 } else {
299 long length = m->get_length_in_frames(seq->frame_rate);
300 g.out = entry_point + length - default_clip_in;
301 if (m->using_inout) {
302 g.out -= (length - default_clip_out);
303 }
304 }
305
306 if (import_data.type() == olive::timeline::kImportAudioOnly
307 || import_data.type() == olive::timeline::kImportBoth) {
308 for (int j=0;j<m->audio_tracks.size();j++) {
309 if (m->audio_tracks.at(j).enabled) {
310 g.track = j;
311 g.media_stream = m->audio_tracks.at(j).file_index;
312 ghosts.append(g);
313 audio_ghosts = true;
314 }
315 }
316 }
317
318 if (import_data.type() == olive::timeline::kImportVideoOnly
319 || import_data.type() == olive::timeline::kImportBoth) {
320 for (int j=0;j<m->video_tracks.size();j++) {
321 if (m->video_tracks.at(j).enabled) {
322 g.track = -1-j;
323 g.media_stream = m->video_tracks.at(j).file_index;
324 ghosts.append(g);
325 video_ghosts = true;
326 }
327 }
328 }
329 break;
330 case MEDIA_TYPE_SEQUENCE:
331 g.out = entry_point + sequence_length - default_clip_in;
332
333 if (s->using_workarea) {
334 g.out -= (sequence_length - default_clip_out);
335 }
336
337 if (import_data.type() == olive::timeline::kImportVideoOnly
338 || import_data.type() == olive::timeline::kImportBoth) {
339 g.track = -1;
340 ghosts.append(g);
341 }
342
343 if (import_data.type() == olive::timeline::kImportAudioOnly
344 || import_data.type() == olive::timeline::kImportBoth) {
345 g.track = 0;
346 ghosts.append(g);
347 }
348
349 video_ghosts = true;
350 audio_ghosts = true;
351 break;
352 }
353 entry_point = g.out;
354 }
355 }
356 for (int i=0;i<ghosts.size();i++) {
357 Ghost& g = ghosts[i];
358 g.old_in = g.in;
359 g.old_out = g.out;
360 g.old_track = g.track;
361 }
362 }
363
add_clips_from_ghosts(ComboAction * ca,Sequence * s)364 void Timeline::add_clips_from_ghosts(ComboAction* ca, Sequence* s) {
365 // add clips
366 long earliest_point = LONG_MAX;
367 QVector<ClipPtr> added_clips;
368 for (int i=0;i<ghosts.size();i++) {
369 const Ghost& g = ghosts.at(i);
370
371 earliest_point = qMin(earliest_point, g.in);
372
373 ClipPtr c = std::make_shared<Clip>(s);
374 c->set_media(g.media, g.media_stream);
375 c->set_timeline_in(g.in);
376 c->set_timeline_out(g.out);
377 c->set_clip_in(g.clip_in);
378 c->set_track(g.track);
379 if (c->media()->get_type() == MEDIA_TYPE_FOOTAGE) {
380 Footage* m = c->media()->to_footage();
381 if (m->video_tracks.size() == 0) {
382 // audio only (greenish)
383 c->set_color(128, 192, 128);
384 } else if (m->audio_tracks.size() == 0) {
385 // video only (orangeish)
386 c->set_color(192, 160, 128);
387 } else {
388 // video and audio (blueish)
389 c->set_color(128, 128, 192);
390 }
391 c->set_name(m->name);
392 } else if (c->media()->get_type() == MEDIA_TYPE_SEQUENCE) {
393 // sequence (red?ish?)
394 c->set_color(192, 128, 128);
395
396 c->set_name(c->media()->to_sequence()->name);
397 }
398 c->refresh();
399 added_clips.append(c);
400 }
401 ca->append(new AddClipCommand(s, added_clips));
402
403 // link clips from the same media
404 for (int i=0;i<added_clips.size();i++) {
405 ClipPtr c = added_clips.at(i);
406 for (int j=0;j<added_clips.size();j++) {
407 ClipPtr cc = added_clips.at(j);
408 if (c != cc && c->media() == cc->media()) {
409 c->linked.append(j);
410 }
411 }
412
413 if (olive::CurrentConfig.add_default_effects_to_clips) {
414 if (c->track() < 0) {
415 // add default video effects
416 c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_TRANSFORM, EFFECT_TYPE_EFFECT)));
417 } else {
418 // add default audio effects
419 c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_VOLUME, EFFECT_TYPE_EFFECT)));
420 c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_PAN, EFFECT_TYPE_EFFECT)));
421 }
422 }
423 }
424 if (olive::CurrentConfig.enable_seek_to_import) {
425 panel_sequence_viewer->seek(earliest_point);
426 }
427 ghosts.clear();
428 importing = false;
429 snapped = false;
430 }
431
add_transition()432 void Timeline::add_transition() {
433 ComboAction* ca = new ComboAction();
434 bool adding = false;
435
436 for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
437 Clip* c = olive::ActiveSequence->clips.at(i).get();
438 if (c != nullptr && c->IsSelected()) {
439 int transition_to_add = (c->track() < 0) ? TRANSITION_INTERNAL_CROSSDISSOLVE : TRANSITION_INTERNAL_LINEARFADE;
440 if (c->opening_transition == nullptr) {
441 ca->append(new AddTransitionCommand(c,
442 nullptr,
443 nullptr,
444 Effect::GetInternalMeta(transition_to_add, EFFECT_TYPE_TRANSITION),
445 olive::CurrentConfig.default_transition_length));
446 adding = true;
447 }
448 if (c->closing_transition == nullptr) {
449 ca->append(new AddTransitionCommand(nullptr,
450 c,
451 nullptr,
452 Effect::GetInternalMeta(transition_to_add, EFFECT_TYPE_TRANSITION),
453 olive::CurrentConfig.default_transition_length));
454 adding = true;
455 }
456 }
457 }
458
459 if (adding) {
460 olive::UndoStack.push(ca);
461 } else {
462 delete ca;
463 }
464
465 update_ui(true);
466 }
467
nest()468 void Timeline::nest() {
469 if (olive::ActiveSequence != nullptr) {
470 // get selected clips
471 QVector<int> selected_clips = olive::ActiveSequence->SelectedClipIndexes();
472
473 // nest them
474 if (!selected_clips.isEmpty()) {
475
476 // get earliest point in selected clips
477 long earliest_point = LONG_MAX;
478 for (int i=0;i<selected_clips.size();i++) {
479 earliest_point = qMin(olive::ActiveSequence->clips.at(selected_clips.at(i))->timeline_in(), earliest_point);
480 }
481
482 ComboAction* ca = new ComboAction();
483
484 // create "nest" sequence with the same attributes as the current sequence
485 SequencePtr s = std::make_shared<Sequence>();
486
487 s->name = panel_project->get_next_sequence_name(tr("Nested Sequence"));
488 s->width = olive::ActiveSequence->width;
489 s->height = olive::ActiveSequence->height;
490 s->frame_rate = olive::ActiveSequence->frame_rate;
491 s->audio_frequency = olive::ActiveSequence->audio_frequency;
492 s->audio_layout = olive::ActiveSequence->audio_layout;
493
494 // copy all selected clips to the nest
495 for (int i=0;i<selected_clips.size();i++) {
496 // delete clip from old sequence
497 ca->append(new DeleteClipAction(olive::ActiveSequence.get(), selected_clips.at(i)));
498
499 // copy to new
500 ClipPtr copy(olive::ActiveSequence->clips.at(selected_clips.at(i))->copy(s.get()));
501 copy->set_timeline_in(copy->timeline_in() - earliest_point);
502 copy->set_timeline_out(copy->timeline_out() - earliest_point);
503 s->clips.append(copy);
504 }
505
506 // relink clips in new nested sequences
507 relink_clips_using_ids(selected_clips, s->clips);
508
509 // add sequence to project
510 MediaPtr m = panel_project->create_sequence_internal(ca, s, false, nullptr);
511
512 // add nested sequence to active sequence
513 QVector<olive::timeline::MediaImportData> media_list;
514 media_list.append(m.get());
515 create_ghosts_from_media(olive::ActiveSequence.get(), earliest_point, media_list);
516
517 // ensure ghosts won't overlap anything
518 for (int j=0;j<olive::ActiveSequence->clips.size();j++) {
519 Clip* c = olive::ActiveSequence->clips.at(j).get();
520 if (c != nullptr && !selected_clips.contains(j)) {
521 for (int i=0;i<ghosts.size();i++) {
522 Ghost& g = ghosts[i];
523 if (c->track() == g.track
524 && !((c->timeline_in() < g.in
525 && c->timeline_out() < g.in)
526 || (c->timeline_in() > g.out
527 && c->timeline_out() > g.out))) {
528 // There's a clip occupied by the space taken up by this ghost. Move up/down a track, and seek again
529 if (g.track < 0) {
530 g.track--;
531 } else {
532 g.track++;
533 }
534 j = -1;
535 break;
536 }
537 }
538 }
539 }
540
541
542 add_clips_from_ghosts(ca, olive::ActiveSequence.get());
543
544 panel_graph_editor->set_row(nullptr);
545 panel_effect_controls->Clear(true);
546 olive::ActiveSequence->selections.clear();
547
548 olive::UndoStack.push(ca);
549
550 update_ui(true);
551 }
552 }
553 }
554
update_sequence()555 void Timeline::update_sequence() {
556 bool null_sequence = (olive::ActiveSequence == nullptr);
557
558 for (int i=0;i<tool_buttons.count();i++) {
559 tool_buttons[i]->setEnabled(!null_sequence);
560 }
561 snappingButton->setEnabled(!null_sequence);
562 zoomInButton->setEnabled(!null_sequence);
563 zoomOutButton->setEnabled(!null_sequence);
564 recordButton->setEnabled(!null_sequence);
565 addButton->setEnabled(!null_sequence);
566 headers->setEnabled(!null_sequence);
567
568 UpdateTitle();
569 }
570
get_snap_range()571 int Timeline::get_snap_range() {
572 return getFrameFromScreenPoint(zoom, 10);
573 }
574
focused()575 bool Timeline::focused() {
576 return (olive::ActiveSequence != nullptr && (headers->hasFocus() || video_area->hasFocus() || audio_area->hasFocus()));
577 }
578
repaint_timeline()579 void Timeline::repaint_timeline() {
580 if (!block_repaints) {
581 bool draw = true;
582
583 if (olive::ActiveSequence != nullptr
584 && !horizontalScrollBar->isSliderDown()
585 && !horizontalScrollBar->is_resizing()
586 && panel_sequence_viewer->playing
587 && !zoom_just_changed) {
588 // auto scroll
589 if (olive::CurrentConfig.autoscroll == olive::AUTOSCROLL_PAGE_SCROLL) {
590 int playhead_x = getTimelineScreenPointFromFrame(olive::ActiveSequence->playhead);
591 if (playhead_x < 0 || playhead_x > (editAreas->width() - videoScrollbar->width())) {
592 horizontalScrollBar->setValue(getScreenPointFromFrame(zoom, olive::ActiveSequence->playhead));
593 draw = false;
594 }
595 } else if (olive::CurrentConfig.autoscroll == olive::AUTOSCROLL_SMOOTH_SCROLL) {
596 if (center_scroll_to_playhead(horizontalScrollBar, zoom, olive::ActiveSequence->playhead)) {
597 draw = false;
598 }
599 }
600 }
601
602 if (draw) {
603 headers->update();
604 video_area->update();
605 audio_area->update();
606
607 if (olive::ActiveSequence != nullptr
608 && !zoom_just_changed) {
609 set_sb_max();
610 }
611 }
612
613 zoom_just_changed = false;
614 }
615 }
616
select_all()617 void Timeline::select_all() {
618 if (olive::ActiveSequence != nullptr) {
619 olive::ActiveSequence->selections.clear();
620 for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
621 ClipPtr c = olive::ActiveSequence->clips.at(i);
622 if (c != nullptr) {
623 Selection s;
624 s.in = c->timeline_in();
625 s.out = c->timeline_out();
626 s.track = c->track();
627 olive::ActiveSequence->selections.append(s);
628 }
629 }
630 repaint_timeline();
631 }
632 }
633
scroll_to_frame(long frame)634 void Timeline::scroll_to_frame(long frame) {
635 scroll_to_frame_internal(horizontalScrollBar, frame, zoom, timeline_area->width());
636 }
637
select_from_playhead()638 void Timeline::select_from_playhead() {
639 olive::ActiveSequence->selections.clear();
640 for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
641 ClipPtr c = olive::ActiveSequence->clips.at(i);
642 if (c != nullptr
643 && c->timeline_in() <= olive::ActiveSequence->playhead
644 && c->timeline_out() > olive::ActiveSequence->playhead) {
645 Selection s;
646 s.in = c->timeline_in();
647 s.out = c->timeline_out();
648 s.track = c->track();
649 olive::ActiveSequence->selections.append(s);
650 }
651 }
652 }
653
can_ripple_empty_space(long frame,int track)654 bool Timeline::can_ripple_empty_space(long frame, int track) {
655 bool can_ripple_delete = true;
656 bool at_end_of_sequence = true;
657 rc_ripple_min = 0;
658 rc_ripple_max = LONG_MAX;
659
660 for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
661 ClipPtr c = olive::ActiveSequence->clips.at(i);
662 if (c != nullptr) {
663 if (c->timeline_in() > frame || c->timeline_out() > frame) {
664 at_end_of_sequence = false;
665 }
666 if (c->track() == track) {
667 if (c->timeline_in() <= frame && c->timeline_out() >= frame) {
668 can_ripple_delete = false;
669 break;
670 } else if (c->timeline_out() < frame) {
671 rc_ripple_min = qMax(rc_ripple_min, c->timeline_out());
672 } else if (c->timeline_in() > frame) {
673 rc_ripple_max = qMin(rc_ripple_max, c->timeline_in());
674 }
675 }
676 }
677 }
678
679 return (can_ripple_delete && !at_end_of_sequence);
680 }
681
ripple_delete_empty_space()682 void Timeline::ripple_delete_empty_space() {
683 QVector<Selection> sels;
684
685 Selection s;
686 s.in = rc_ripple_min;
687 s.out = rc_ripple_max;
688 s.track = cursor_track;
689
690 sels.append(s);
691
692 delete_selection(sels, true);
693 }
694
resizeEvent(QResizeEvent *)695 void Timeline::resizeEvent(QResizeEvent *) {
696 // adjust maximum scrollbar
697 if (olive::ActiveSequence != nullptr) set_sb_max();
698
699
700 // resize tool button widget to its contents
701 QList<QWidget*> tool_button_children = tool_button_widget->findChildren<QWidget*>();
702
703 int horizontal_spacing = static_cast<FlowLayout*>(tool_button_widget->layout())->horizontalSpacing();
704 int vertical_spacing = static_cast<FlowLayout*>(tool_button_widget->layout())->verticalSpacing();
705 int total_area = tool_button_widget->height();
706
707 int button_count = tool_button_children.size();
708 int button_height = tool_button_children.at(0)->sizeHint().height() + vertical_spacing;
709
710 int cols = 0;
711
712 int col_height;
713
714 if (button_height < total_area) {
715 do {
716 cols++;
717 col_height = (qCeil(double(button_count)/double(cols))*button_height)-vertical_spacing;
718 } while (col_height > total_area);
719 } else {
720 cols = button_count;
721 }
722
723 tool_button_widget->setFixedWidth((tool_button_children.at(0)->sizeHint().width())*cols + horizontal_spacing*(cols-1) + 1);
724 }
725
delete_in_out_internal(bool ripple)726 void Timeline::delete_in_out_internal(bool ripple) {
727 if (olive::ActiveSequence != nullptr && olive::ActiveSequence->using_workarea) {
728 QVector<Selection> areas;
729 int video_tracks = 0, audio_tracks = 0;
730 olive::ActiveSequence->getTrackLimits(&video_tracks, &audio_tracks);
731 for (int i=video_tracks;i<=audio_tracks;i++) {
732 Selection s;
733 s.in = olive::ActiveSequence->workarea_in;
734 s.out = olive::ActiveSequence->workarea_out;
735 s.track = i;
736 areas.append(s);
737 }
738 ComboAction* ca = new ComboAction();
739 delete_areas_and_relink(ca, areas, true);
740 if (ripple) ripple_clips(ca,
741 olive::ActiveSequence.get(),
742 olive::ActiveSequence->workarea_in,
743 olive::ActiveSequence->workarea_in - olive::ActiveSequence->workarea_out);
744 ca->append(new SetTimelineInOutCommand(olive::ActiveSequence.get(), false, 0, 0));
745 olive::UndoStack.push(ca);
746 update_ui(true);
747 }
748 }
749
toggle_enable_on_selected_clips()750 void Timeline::toggle_enable_on_selected_clips() {
751 if (olive::ActiveSequence != nullptr) {
752
753 // get currently selected clips
754 QVector<Clip*> selected_clips = olive::ActiveSequence->SelectedClips();
755
756 if (!selected_clips.isEmpty()) {
757 // if clips are selected, create an undoable action
758 SetClipProperty* set_action = new SetClipProperty(kSetClipPropertyEnabled);
759
760 // add each selected clip to the action
761 for (int i=0;i<selected_clips.size();i++) {
762 Clip* c = selected_clips.at(i);
763 set_action->AddSetting(c, !c->enabled());
764 }
765
766 // push the action
767 olive::UndoStack.push(set_action);
768 update_ui(false);
769 }
770 }
771 }
772
delete_selection(QVector<Selection> & selections,bool ripple_delete)773 void Timeline::delete_selection(QVector<Selection>& selections, bool ripple_delete) {
774 if (selections.size() > 0) {
775 panel_graph_editor->set_row(nullptr);
776 panel_effect_controls->Clear(true);
777
778 ComboAction* ca = new ComboAction();
779
780 // delete the areas currently selected by `selections`
781 // if we're ripple deleting, we don't want to delete the selections since we still need them for the ripple
782 delete_areas_and_relink(ca, selections, !ripple_delete);
783
784 if (ripple_delete) {
785 long ripple_point = selections.at(0).in;
786 long ripple_length = selections.at(0).out - selections.at(0).in;
787
788 // retrieve ripple_point and ripple_length from current selection
789 for (int i=1;i<selections.size();i++) {
790 const Selection& s = selections.at(i);
791 ripple_point = qMin(ripple_point, s.in);
792 ripple_length = qMin(ripple_length, s.out - s.in);
793 }
794
795 // it feels a little more intuitive with this here
796 ripple_point++;
797
798 bool can_ripple = true;
799 for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
800 ClipPtr c = olive::ActiveSequence->clips.at(i);
801 if (c != nullptr && c->timeline_in() < ripple_point && c->timeline_out() > ripple_point) {
802 // conflict detected, but this clip may be getting deleted so let's check
803 bool deleted = false;
804 for (int j=0;j<selections.size();j++) {
805 const Selection& s = selections.at(j);
806 if (s.track == c->track()
807 && !(c->timeline_in() < s.in && c->timeline_out() < s.in)
808 && !(c->timeline_in() > s.out && c->timeline_out() > s.out)) {
809 deleted = true;
810 break;
811 }
812 }
813 if (!deleted) {
814 for (int j=0;j<olive::ActiveSequence->clips.size();j++) {
815 ClipPtr cc = olive::ActiveSequence->clips.at(j);
816 if (cc != nullptr
817 && cc->track() == c->track()
818 && cc->timeline_in() > c->timeline_out()
819 && cc->timeline_in() < c->timeline_out() + ripple_length) {
820 ripple_length = cc->timeline_in() - c->timeline_out();
821 }
822 }
823 }
824 }
825 }
826
827 if (can_ripple) {
828 ripple_clips(ca, olive::ActiveSequence.get(), ripple_point, -ripple_length);
829 panel_sequence_viewer->seek(ripple_point-1);
830 }
831
832 // if we're rippling, we can clear the selections here - if we're not rippling, delete_areas_and_relink() will
833 // clear the selections for us
834 selections.clear();
835 }
836
837 olive::UndoStack.push(ca);
838
839 update_ui(true);
840 }
841 }
842
set_zoom_value(double v)843 void Timeline::set_zoom_value(double v) {
844 // set zoom value
845 zoom = v;
846
847 // update header zoom to match
848 headers->update_zoom(zoom);
849
850 // set flag that zoom has just changed to prevent auto-scrolling since we change the scroll below
851 zoom_just_changed = true;
852
853 // set scrollbar to center the playhead
854 if (olive::ActiveSequence != nullptr) {
855 // update scrollbar maximum value for new zoom
856 set_sb_max();
857
858 if (!horizontalScrollBar->is_resizing()) {
859 center_scroll_to_playhead(horizontalScrollBar, zoom, olive::ActiveSequence->playhead);
860 }
861 }
862
863 // repaint the timeline for the new zoom/location
864 repaint_timeline();
865 }
866
multiply_zoom(double m)867 void Timeline::multiply_zoom(double m) {
868 showing_all = false;
869 set_zoom_value(zoom * m);
870 }
871
decheck_tool_buttons(QObject * sender)872 void Timeline::decheck_tool_buttons(QObject* sender) {
873 for (int i=0;i<tool_buttons.count();i++) {
874 tool_buttons[i]->setChecked(tool_buttons.at(i) == sender);
875 }
876 }
877
zoom_in()878 void Timeline::zoom_in() {
879 multiply_zoom(2.0);
880 }
881
zoom_out()882 void Timeline::zoom_out() {
883 multiply_zoom(0.5);
884 }
885
GetTrackHeight(int track)886 int Timeline::GetTrackHeight(int track) {
887 for (int i=0;i<track_heights.size();i++) {
888 if (track_heights.at(i).index == track) {
889 return track_heights.at(i).height;
890 }
891 }
892 return olive::timeline::kTrackDefaultHeight;
893 }
894
SetTrackHeight(int track,int height)895 void Timeline::SetTrackHeight(int track, int height) {
896 for (int i=0;i<track_heights.size();i++) {
897 if (track_heights.at(i).index == track) {
898 track_heights[i].height = height;
899 return;
900 }
901 }
902
903 // we don't have a track height, so set a new one
904 TimelineTrackHeight t;
905 t.index = track;
906 t.height = height;
907 track_heights.append(t);
908 }
909
ChangeTrackHeightUniformly(int diff)910 void Timeline::ChangeTrackHeightUniformly(int diff) {
911 // get range of tracks currently active
912 int min_track, max_track;
913 olive::ActiveSequence->getTrackLimits(&min_track, &max_track);
914
915 // for each active track, set the track to increase/decrease based on `diff`
916 for (int i=min_track;i<=max_track;i++) {
917 SetTrackHeight(i, qMax(GetTrackHeight(i) + diff, olive::timeline::kTrackMinHeight));
918 }
919
920 // update the timeline
921 repaint_timeline();
922 }
923
IncreaseTrackHeight()924 void Timeline::IncreaseTrackHeight() {
925 ChangeTrackHeightUniformly(olive::timeline::kTrackHeightIncrement);
926 }
927
DecreaseTrackHeight()928 void Timeline::DecreaseTrackHeight() {
929 ChangeTrackHeightUniformly(-olive::timeline::kTrackHeightIncrement);
930 }
931
snapping_clicked(bool checked)932 void Timeline::snapping_clicked(bool checked) {
933 snapping = checked;
934 }
935
split_clip(ComboAction * ca,bool transitions,int p,long frame)936 ClipPtr Timeline::split_clip(ComboAction* ca, bool transitions, int p, long frame) {
937 return split_clip(ca, transitions, p, frame, frame);
938 }
939
split_clip(ComboAction * ca,bool transitions,int p,long frame,long post_in)940 ClipPtr Timeline::split_clip(ComboAction* ca, bool transitions, int p, long frame, long post_in) {
941 Clip* pre = olive::ActiveSequence->clips.at(p).get();
942 if (pre != nullptr) {
943
944 if (pre->timeline_in() < frame && pre->timeline_out() > frame) {
945 // duplicate clip without duplicating its transitions, we'll restore them later
946
947 ClipPtr post = pre->copy(olive::ActiveSequence.get());
948
949 long new_clip_length = frame - pre->timeline_in();
950
951 post->set_timeline_in(post_in);
952 post->set_clip_in(pre->clip_in() + (post->timeline_in() - pre->timeline_in()));
953
954 pre->move(ca, pre->timeline_in(), frame, pre->clip_in(), pre->track(), false);
955
956 if (transitions) {
957
958 // check if this clip has a closing transition
959 if (pre->closing_transition != nullptr) {
960
961 // if so, move closing transition to the post clip
962 post->closing_transition = pre->closing_transition;
963
964 // and set the original clip's closing transition to nothing
965 ca->append(new SetPointer(reinterpret_cast<void**>(&pre->closing_transition), nullptr));
966
967 // and set the transition's reference to the post clip
968 if (post->closing_transition->parent_clip == pre) {
969 ca->append(new SetPointer(reinterpret_cast<void**>(&post->closing_transition->parent_clip), post.get()));
970 }
971 if (post->closing_transition->secondary_clip == pre) {
972 ca->append(new SetPointer(reinterpret_cast<void**>(&post->closing_transition->secondary_clip), post.get()));
973 }
974
975 // and make sure it's at the correct size to the closing clip
976 if (post->closing_transition != nullptr && post->closing_transition->get_true_length() > post->length()) {
977 ca->append(new ModifyTransitionCommand(post->closing_transition, post->length()));
978 post->closing_transition->set_length(post->length());
979 }
980
981 }
982
983 // we're keeping the opening clip, so ensure that's a correct size too
984 if (pre->opening_transition != nullptr && pre->opening_transition->get_true_length() > new_clip_length) {
985 ca->append(new ModifyTransitionCommand(pre->opening_transition, new_clip_length));
986 }
987 }
988
989 return post;
990
991 } else if (frame == pre->timeline_in()
992 && pre->opening_transition != nullptr
993 && pre->opening_transition->secondary_clip != nullptr) {
994 // special case for shared transitions to split it into two
995
996 // set transition to single-clip mode
997 ca->append(new SetPointer(reinterpret_cast<void**>(&pre->opening_transition->secondary_clip), nullptr));
998
999 // clone transition for other clip
1000 ca->append(new AddTransitionCommand(nullptr,
1001 pre->opening_transition->secondary_clip,
1002 pre->opening_transition,
1003 nullptr,
1004 0)
1005 );
1006
1007 }
1008
1009 }
1010 return nullptr;
1011 }
1012
split_clip_and_relink(ComboAction * ca,int clip,long frame,bool relink)1013 bool Timeline::split_clip_and_relink(ComboAction *ca, int clip, long frame, bool relink) {
1014 // see if we split this clip before
1015 if (split_cache.contains(clip)) {
1016 return false;
1017 }
1018
1019 split_cache.append(clip);
1020
1021 Clip* c = olive::ActiveSequence->clips.at(clip).get();
1022 if (c != nullptr) {
1023 QVector<int> pre_clips;
1024 QVector<ClipPtr> post_clips;
1025
1026 ClipPtr post = split_clip(ca, true, clip, frame);
1027
1028 if (post == nullptr) {
1029 return false;
1030 } else {
1031 post_clips.append(post);
1032
1033 // if alt is not down, split clips links too
1034 if (relink) {
1035 pre_clips.append(clip);
1036
1037 bool original_clip_is_selected = c->IsSelected();
1038
1039 // find linked clips of old clip
1040 for (int i=0;i<c->linked.size();i++) {
1041 int l = c->linked.at(i);
1042 if (!split_cache.contains(l)) {
1043 Clip* link = olive::ActiveSequence->clips.at(l).get();
1044 if ((original_clip_is_selected && link->IsSelected()) || !original_clip_is_selected) {
1045 split_cache.append(l);
1046 ClipPtr s = split_clip(ca, true, l, frame);
1047 if (s != nullptr) {
1048 pre_clips.append(l);
1049 post_clips.append(s);
1050 }
1051 }
1052 }
1053 }
1054
1055 relink_clips_using_ids(pre_clips, post_clips);
1056 }
1057 ca->append(new AddClipCommand(olive::ActiveSequence.get(), post_clips));
1058 return true;
1059 }
1060 }
1061 return false;
1062 }
1063
clean_up_selections(QVector<Selection> & areas)1064 void Timeline::clean_up_selections(QVector<Selection>& areas) {
1065 for (int i=0;i<areas.size();i++) {
1066 Selection& s = areas[i];
1067 for (int j=0;j<areas.size();j++) {
1068 if (i != j) {
1069 Selection& ss = areas[j];
1070 if (s.track == ss.track) {
1071 bool remove = false;
1072 if (s.in < ss.in && s.out > ss.out) {
1073 // do nothing
1074 } else if (s.in >= ss.in && s.out <= ss.out) {
1075 remove = true;
1076 } else if (s.in <= ss.out && s.out > ss.out) {
1077 ss.out = s.out;
1078 remove = true;
1079 } else if (s.out >= ss.in && s.in < ss.in) {
1080 ss.in = s.in;
1081 remove = true;
1082 }
1083 if (remove) {
1084 areas.removeAt(i);
1085 i--;
1086 break;
1087 }
1088 }
1089 }
1090 }
1091 }
1092 }
1093
selection_contains_transition(const Selection & s,Clip * c,int type)1094 bool selection_contains_transition(const Selection& s, Clip* c, int type) {
1095 if (type == kTransitionOpening) {
1096 return c->opening_transition != nullptr
1097 && s.out == c->timeline_in() + c->opening_transition->get_true_length()
1098 && ((c->opening_transition->secondary_clip == nullptr && s.in == c->timeline_in())
1099 || (c->opening_transition->secondary_clip != nullptr && s.in == c->timeline_in() - c->opening_transition->get_true_length()));
1100 } else {
1101 return c->closing_transition != nullptr
1102 && s.in == c->timeline_out() - c->closing_transition->get_true_length()
1103 && ((c->closing_transition->secondary_clip == nullptr && s.out == c->timeline_out())
1104 || (c->closing_transition->secondary_clip != nullptr && s.out == c->timeline_out() + c->closing_transition->get_true_length()));
1105 }
1106 }
1107
delete_areas_and_relink(ComboAction * ca,QVector<Selection> & areas,bool deselect_areas)1108 void Timeline::delete_areas_and_relink(ComboAction* ca, QVector<Selection>& areas, bool deselect_areas) {
1109 clean_up_selections(areas);
1110
1111 panel_graph_editor->set_row(nullptr);
1112 panel_effect_controls->Clear(true);
1113
1114 QVector<int> pre_clips;
1115 QVector<ClipPtr> post_clips;
1116
1117 for (int i=0;i<areas.size();i++) {
1118 const Selection& s = areas.at(i);
1119 for (int j=0;j<olive::ActiveSequence->clips.size();j++) {
1120 Clip* c = olive::ActiveSequence->clips.at(j).get();
1121 if (c != nullptr && c->track() == s.track && !c->undeletable) {
1122 if (selection_contains_transition(s, c, kTransitionOpening)) {
1123 // delete opening transition
1124 ca->append(new DeleteTransitionCommand(c->opening_transition));
1125 } else if (selection_contains_transition(s, c, kTransitionClosing)) {
1126 // delete closing transition
1127 ca->append(new DeleteTransitionCommand(c->closing_transition));
1128 } else if (c->timeline_in() >= s.in && c->timeline_out() <= s.out) {
1129 // clips falls entirely within deletion area
1130 ca->append(new DeleteClipAction(olive::ActiveSequence.get(), j));
1131 } else if (c->timeline_in() < s.in && c->timeline_out() > s.out) {
1132 // middle of clip is within deletion area
1133
1134 // duplicate clip
1135 ClipPtr post = split_clip(ca, true, j, s.in, s.out);
1136
1137 pre_clips.append(j);
1138 post_clips.append(post);
1139 } else if (c->timeline_in() < s.in && c->timeline_out() > s.in) {
1140 // only out point is in deletion area
1141 c->move(ca, c->timeline_in(), s.in, c->clip_in(), c->track());
1142
1143 if (c->closing_transition != nullptr) {
1144 if (s.in < c->timeline_out() - c->closing_transition->get_true_length()) {
1145 ca->append(new DeleteTransitionCommand(c->closing_transition));
1146 } else {
1147 ca->append(new ModifyTransitionCommand(c->closing_transition, c->closing_transition->get_true_length() - (c->timeline_out() - s.in)));
1148 }
1149 }
1150 } else if (c->timeline_in() < s.out && c->timeline_out() > s.out) {
1151 // only in point is in deletion area
1152 c->move(ca, s.out, c->timeline_out(), c->clip_in() + (s.out - c->timeline_in()), c->track());
1153
1154 if (c->opening_transition != nullptr) {
1155 if (s.out > c->timeline_in() + c->opening_transition->get_true_length()) {
1156 ca->append(new DeleteTransitionCommand(c->opening_transition));
1157 } else {
1158 ca->append(new ModifyTransitionCommand(c->opening_transition, c->opening_transition->get_true_length() - (s.out - c->timeline_in())));
1159 }
1160 }
1161 }
1162 }
1163 }
1164 }
1165
1166 // deselect selected clip areas
1167 if (deselect_areas) {
1168 QVector<Selection> area_copy = areas;
1169 for (int i=0;i<area_copy.size();i++) {
1170 const Selection& s = area_copy.at(i);
1171 deselect_area(s.in, s.out, s.track);
1172 }
1173 }
1174
1175 relink_clips_using_ids(pre_clips, post_clips);
1176 ca->append(new AddClipCommand(olive::ActiveSequence.get(), post_clips));
1177 }
1178
copy(bool del)1179 void Timeline::copy(bool del) {
1180 bool cleared = false;
1181 bool copied = false;
1182
1183 long min_in = 0;
1184
1185 for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
1186 ClipPtr c = olive::ActiveSequence->clips.at(i);
1187 if (c != nullptr) {
1188 for (int j=0;j<olive::ActiveSequence->selections.size();j++) {
1189 const Selection& s = olive::ActiveSequence->selections.at(j);
1190 if (s.track == c->track() && !((c->timeline_in() <= s.in && c->timeline_out() <= s.in) || (c->timeline_in() >= s.out && c->timeline_out() >= s.out))) {
1191 if (!cleared) {
1192 clear_clipboard();
1193 cleared = true;
1194 clipboard_type = CLIPBOARD_TYPE_CLIP;
1195 }
1196
1197 ClipPtr copied_clip = c->copy(nullptr);
1198
1199 // copy linked IDs (we correct these later in paste())
1200 copied_clip->linked = c->linked;
1201
1202 if (copied_clip->timeline_in() < s.in) {
1203 copied_clip->set_clip_in(copied_clip->clip_in() + (s.in - copied_clip->timeline_in()));
1204 copied_clip->set_timeline_in(s.in);
1205 }
1206
1207 if (copied_clip->timeline_out() > s.out) {
1208 copied_clip->set_timeline_out(s.out);
1209 }
1210
1211 if (copied) {
1212 min_in = qMin(min_in, s.in);
1213 } else {
1214 min_in = s.in;
1215 copied = true;
1216 }
1217
1218 copied_clip->load_id = i;
1219
1220 clipboard.append(copied_clip);
1221 }
1222 }
1223 }
1224 }
1225
1226 for (int i=0;i<clipboard.size();i++) {
1227 // initialize all timeline_ins to 0 or offsets of
1228 ClipPtr c = std::static_pointer_cast<Clip>(clipboard.at(i));
1229 c->set_timeline_in(c->timeline_in() - min_in);
1230 c->set_timeline_out(c->timeline_out() - min_in);
1231 }
1232
1233 if (del && copied) {
1234 delete_selection(olive::ActiveSequence->selections, false);
1235 }
1236 }
1237
relink_clips_using_ids(QVector<int> & old_clips,QVector<ClipPtr> & new_clips)1238 void Timeline::relink_clips_using_ids(QVector<int>& old_clips, QVector<ClipPtr>& new_clips) {
1239 // relink pasted clips
1240 for (int i=0;i<old_clips.size();i++) {
1241 // these indices should correspond
1242 ClipPtr oc = olive::ActiveSequence->clips.at(old_clips.at(i));
1243 for (int j=0;j<oc->linked.size();j++) {
1244 for (int k=0;k<old_clips.size();k++) { // find clip with that ID
1245 if (oc->linked.at(j) == old_clips.at(k)) {
1246 if (new_clips.at(i) != nullptr) {
1247 new_clips.at(i)->linked.append(k);
1248 }
1249 }
1250 }
1251 }
1252 }
1253 }
1254
paste(bool insert)1255 void Timeline::paste(bool insert) {
1256 if (clipboard.size() > 0) {
1257 if (clipboard_type == CLIPBOARD_TYPE_CLIP) {
1258 ComboAction* ca = new ComboAction();
1259
1260 // create copies and delete areas that we'll be pasting to
1261 QVector<Selection> delete_areas;
1262 QVector<ClipPtr> pasted_clips;
1263 long paste_start = LONG_MAX;
1264 long paste_end = LONG_MIN;
1265 for (int i=0;i<clipboard.size();i++) {
1266 ClipPtr c = std::static_pointer_cast<Clip>(clipboard.at(i));
1267
1268 // create copy of clip and offset by playhead
1269 ClipPtr cc = c->copy(olive::ActiveSequence.get());
1270
1271 // convert frame rates
1272 cc->set_timeline_in(rescale_frame_number(cc->timeline_in(), c->cached_frame_rate(), olive::ActiveSequence->frame_rate));
1273 cc->set_timeline_out(rescale_frame_number(cc->timeline_out(), c->cached_frame_rate(), olive::ActiveSequence->frame_rate));
1274 cc->set_clip_in(rescale_frame_number(cc->clip_in(), c->cached_frame_rate(), olive::ActiveSequence->frame_rate));
1275
1276 cc->set_timeline_in(cc->timeline_in() + olive::ActiveSequence->playhead);
1277 cc->set_timeline_out(cc->timeline_out() + olive::ActiveSequence->playhead);
1278 cc->set_track(c->track());
1279
1280 paste_start = qMin(paste_start, cc->timeline_in());
1281 paste_end = qMax(paste_end, cc->timeline_out());
1282
1283 pasted_clips.append(cc);
1284
1285 if (!insert) {
1286 Selection s;
1287 s.in = cc->timeline_in();
1288 s.out = cc->timeline_out();
1289 s.track = c->track();
1290 delete_areas.append(s);
1291 }
1292 }
1293 if (insert) {
1294 split_cache.clear();
1295 split_all_clips_at_point(ca, olive::ActiveSequence->playhead);
1296 ripple_clips(ca, olive::ActiveSequence.get(), paste_start, paste_end - paste_start);
1297 } else {
1298 delete_areas_and_relink(ca, delete_areas, false);
1299 }
1300
1301 // correct linked clips
1302 for (int i=0;i<clipboard.size();i++) {
1303 // these indices should correspond
1304 ClipPtr oc = std::static_pointer_cast<Clip>(clipboard.at(i));
1305
1306 for (int j=0;j<oc->linked.size();j++) {
1307 for (int k=0;k<clipboard.size();k++) { // find clip with that ID
1308 ClipPtr comp = std::static_pointer_cast<Clip>(clipboard.at(k));
1309 if (comp->load_id == oc->linked.at(j)) {
1310 pasted_clips.at(i)->linked.append(k);
1311 }
1312 }
1313 }
1314 }
1315
1316 ca->append(new AddClipCommand(olive::ActiveSequence.get(), pasted_clips));
1317
1318 olive::UndoStack.push(ca);
1319
1320 update_ui(true);
1321
1322 if (olive::CurrentConfig.paste_seeks) {
1323 panel_sequence_viewer->seek(paste_end);
1324 }
1325 } else if (clipboard_type == CLIPBOARD_TYPE_EFFECT) {
1326 ComboAction* ca = new ComboAction();
1327
1328 bool replace = false;
1329 bool skip = false;
1330 bool ask_conflict = true;
1331
1332 QVector<Clip*> selected_clips = olive::ActiveSequence->SelectedClips();
1333
1334 for (int i=0;i<selected_clips.size();i++) {
1335 Clip* c = selected_clips.at(i);
1336
1337 for (int j=0;j<clipboard.size();j++) {
1338 EffectPtr e = std::static_pointer_cast<Effect>(clipboard.at(j));
1339 if ((c->track() < 0) == (e->meta->subtype == EFFECT_TYPE_VIDEO)) {
1340 int found = -1;
1341 if (ask_conflict) {
1342 replace = false;
1343 skip = false;
1344 }
1345 for (int k=0;k<c->effects.size();k++) {
1346 if (c->effects.at(k)->meta == e->meta) {
1347 found = k;
1348 break;
1349 }
1350 }
1351 if (found >= 0 && ask_conflict) {
1352 QMessageBox box(this);
1353 box.setWindowTitle(tr("Effect already exists"));
1354 box.setText(tr("Clip '%1' already contains a '%2' effect. "
1355 "Would you like to replace it with the pasted one or add it as a separate effect?")
1356 .arg(c->name(), e->meta->name));
1357 box.setIcon(QMessageBox::Icon::Question);
1358
1359 box.addButton(tr("Add"), QMessageBox::YesRole);
1360 QPushButton* replace_button = box.addButton(tr("Replace"), QMessageBox::NoRole);
1361 QPushButton* skip_button = box.addButton(tr("Skip"), QMessageBox::RejectRole);
1362
1363 QCheckBox* future_box = new QCheckBox(tr("Do this for all conflicts found"), &box);
1364 box.setCheckBox(future_box);
1365
1366 box.exec();
1367
1368 if (box.clickedButton() == replace_button) {
1369 replace = true;
1370 } else if (box.clickedButton() == skip_button) {
1371 skip = true;
1372 }
1373 ask_conflict = !future_box->isChecked();
1374 }
1375
1376 if (found >= 0 && skip) {
1377 // do nothing
1378 } else if (found >= 0 && replace) {
1379 ca->append(new EffectDeleteCommand(c->effects.at(found).get()));
1380
1381 ca->append(new AddEffectCommand(c, e->copy(c), nullptr, found));
1382 } else {
1383 ca->append(new AddEffectCommand(c, e->copy(c), nullptr));
1384 }
1385 }
1386 }
1387 }
1388 if (ca->hasActions()) {
1389 ca->appendPost(new ReloadEffectsCommand());
1390 olive::UndoStack.push(ca);
1391 } else {
1392 delete ca;
1393 }
1394 update_ui(true);
1395 }
1396 }
1397 }
1398
edit_to_point_internal(bool in,bool ripple)1399 void Timeline::edit_to_point_internal(bool in, bool ripple) {
1400 if (olive::ActiveSequence != nullptr) {
1401 if (olive::ActiveSequence->clips.size() > 0) {
1402 // get track count
1403 int track_min = INT_MAX;
1404 int track_max = INT_MIN;
1405 long sequence_end = 0;
1406
1407 bool playhead_falls_on_in = false;
1408 bool playhead_falls_on_out = false;
1409 long next_cut = LONG_MAX;
1410 long prev_cut = 0;
1411
1412 // find closest in point to playhead
1413 for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
1414 ClipPtr c = olive::ActiveSequence->clips.at(i);
1415 if (c != nullptr) {
1416 track_min = qMin(track_min, c->track());
1417 track_max = qMax(track_max, c->track());
1418
1419 sequence_end = qMax(c->timeline_out(), sequence_end);
1420
1421 if (c->timeline_in() == olive::ActiveSequence->playhead)
1422 playhead_falls_on_in = true;
1423 if (c->timeline_out() == olive::ActiveSequence->playhead)
1424 playhead_falls_on_out = true;
1425 if (c->timeline_in() > olive::ActiveSequence->playhead)
1426 next_cut = qMin(c->timeline_in(), next_cut);
1427 if (c->timeline_out() > olive::ActiveSequence->playhead)
1428 next_cut = qMin(c->timeline_out(), next_cut);
1429 if (c->timeline_in() < olive::ActiveSequence->playhead)
1430 prev_cut = qMax(c->timeline_in(), prev_cut);
1431 if (c->timeline_out() < olive::ActiveSequence->playhead)
1432 prev_cut = qMax(c->timeline_out(), prev_cut);
1433 }
1434 }
1435
1436 next_cut = qMin(sequence_end, next_cut);
1437
1438 QVector<Selection> areas;
1439 ComboAction* ca = new ComboAction();
1440 bool push_undo = true;
1441 long seek = olive::ActiveSequence->playhead;
1442
1443 if ((in && (playhead_falls_on_out || (playhead_falls_on_in && olive::ActiveSequence->playhead == 0)))
1444 || (!in && (playhead_falls_on_in || (playhead_falls_on_out && olive::ActiveSequence->playhead == sequence_end)))) { // one frame mode
1445 if (ripple) {
1446 // set up deletion areas based on track count
1447 long in_point = olive::ActiveSequence->playhead;
1448 if (!in) {
1449 in_point--;
1450 seek--;
1451 }
1452
1453 if (in_point >= 0) {
1454 Selection s;
1455 s.in = in_point;
1456 s.out = in_point + 1;
1457 for (int i=track_min;i<=track_max;i++) {
1458 s.track = i;
1459 areas.append(s);
1460 }
1461
1462 // trim and move clips around the in point
1463 delete_areas_and_relink(ca, areas, true);
1464
1465 if (ripple) ripple_clips(ca, olive::ActiveSequence.get(), in_point, -1);
1466 } else {
1467 push_undo = false;
1468 }
1469 } else {
1470 push_undo = false;
1471 }
1472 } else {
1473 // set up deletion areas based on track count
1474 Selection s;
1475 if (in) seek = prev_cut;
1476 s.in = in ? prev_cut : olive::ActiveSequence->playhead;
1477 s.out = in ? olive::ActiveSequence->playhead : next_cut;
1478
1479 if (s.in == s.out) {
1480 push_undo = false;
1481 } else {
1482 for (int i=track_min;i<=track_max;i++) {
1483 s.track = i;
1484 areas.append(s);
1485 }
1486
1487 // trim and move clips around the in point
1488 delete_areas_and_relink(ca, areas, true);
1489 if (ripple) ripple_clips(ca, olive::ActiveSequence.get(), s.in, s.in - s.out);
1490 }
1491 }
1492
1493 if (push_undo) {
1494 olive::UndoStack.push(ca);
1495
1496 update_ui(true);
1497
1498 if (seek != olive::ActiveSequence->playhead && ripple) {
1499 panel_sequence_viewer->seek(seek);
1500 }
1501 } else {
1502 delete ca;
1503 }
1504 } else {
1505 panel_sequence_viewer->seek(0);
1506 }
1507 }
1508 }
1509
split_selection(ComboAction * ca)1510 bool Timeline::split_selection(ComboAction* ca) {
1511 bool split = false;
1512
1513 // temporary relinking vectors
1514 QVector<int> pre_splits;
1515 QVector<ClipPtr> post_splits;
1516 QVector<ClipPtr> secondary_post_splits;
1517
1518 // find clips within selection and split
1519 for (int j=0;j<olive::ActiveSequence->clips.size();j++) {
1520 ClipPtr clip = olive::ActiveSequence->clips.at(j);
1521 if (clip != nullptr) {
1522 for (int i=0;i<olive::ActiveSequence->selections.size();i++) {
1523 const Selection& s = olive::ActiveSequence->selections.at(i);
1524 if (s.track == clip->track()) {
1525 ClipPtr post_b = split_clip(ca, true, j, s.out);
1526 ClipPtr post_a = split_clip(ca, true, j, s.in);
1527
1528 pre_splits.append(j);
1529 post_splits.append(post_a);
1530 secondary_post_splits.append(post_b);
1531
1532 if (post_a != nullptr) {
1533 post_a->set_timeline_out(qMin(post_a->timeline_out(), s.out));
1534 }
1535
1536 split = true;
1537 }
1538 }
1539 }
1540 }
1541
1542 if (split) {
1543 // relink after splitting
1544 relink_clips_using_ids(pre_splits, post_splits);
1545 relink_clips_using_ids(pre_splits, secondary_post_splits);
1546
1547 ca->append(new AddClipCommand(olive::ActiveSequence.get(), post_splits));
1548 ca->append(new AddClipCommand(olive::ActiveSequence.get(), secondary_post_splits));
1549
1550 return true;
1551 }
1552 return false;
1553 }
1554
split_all_clips_at_point(ComboAction * ca,long point)1555 bool Timeline::split_all_clips_at_point(ComboAction* ca, long point) {
1556 bool split = false;
1557 for (int j=0;j<olive::ActiveSequence->clips.size();j++) {
1558 ClipPtr c = olive::ActiveSequence->clips.at(j);
1559 if (c != nullptr) {
1560 // always relinks
1561 if (split_clip_and_relink(ca, j, point, true)) {
1562 split = true;
1563 }
1564 }
1565 }
1566 return split;
1567 }
1568
split_at_playhead()1569 void Timeline::split_at_playhead() {
1570 ComboAction* ca = new ComboAction();
1571 bool split_selected = false;
1572 split_cache.clear();
1573
1574 if (olive::ActiveSequence->selections.size() > 0) {
1575 // see if whole clips are selected
1576 QVector<int> pre_clips;
1577 QVector<ClipPtr> post_clips;
1578 for (int j=0;j<olive::ActiveSequence->clips.size();j++) {
1579 Clip* clip = olive::ActiveSequence->clips.at(j).get();
1580 if (clip != nullptr && clip->IsSelected()) {
1581 ClipPtr s = split_clip(ca, true, j, olive::ActiveSequence->playhead);
1582 if (s != nullptr) {
1583 pre_clips.append(j);
1584 post_clips.append(s);
1585 split_selected = true;
1586 }
1587 }
1588 }
1589
1590 if (split_selected) {
1591 // relink clips if we split
1592 relink_clips_using_ids(pre_clips, post_clips);
1593 ca->append(new AddClipCommand(olive::ActiveSequence.get(), post_clips));
1594 } else {
1595 // split a selection if not
1596 split_selected = split_selection(ca);
1597 }
1598 }
1599
1600 // if nothing was selected or no selections fell within playhead, simply split at playhead
1601 if (!split_selected) {
1602 split_selected = split_all_clips_at_point(ca, olive::ActiveSequence->playhead);
1603 }
1604
1605 if (split_selected) {
1606 olive::UndoStack.push(ca);
1607 update_ui(true);
1608 } else {
1609 delete ca;
1610 }
1611 }
1612
ripple_delete()1613 void Timeline::ripple_delete() {
1614 if (olive::ActiveSequence != nullptr) {
1615 if (olive::ActiveSequence->selections.size() > 0) {
1616 panel_timeline->delete_selection(olive::ActiveSequence->selections, true);
1617 } else if (olive::CurrentConfig.hover_focus && get_focused_panel() == panel_timeline) {
1618 if (panel_timeline->can_ripple_empty_space(panel_timeline->cursor_frame, panel_timeline->cursor_track)) {
1619 panel_timeline->ripple_delete_empty_space();
1620 }
1621 }
1622 }
1623 }
1624
deselect_area(long in,long out,int track)1625 void Timeline::deselect_area(long in, long out, int track) {
1626 int len = olive::ActiveSequence->selections.size();
1627 for (int i=0;i<len;i++) {
1628 Selection& s = olive::ActiveSequence->selections[i];
1629 if (s.track == track) {
1630 if (s.in >= in && s.out <= out) {
1631 // whole selection is in deselect area
1632 olive::ActiveSequence->selections.removeAt(i);
1633 i--;
1634 len--;
1635 } else if (s.in < in && s.out > out) {
1636 // middle of selection is in deselect area
1637 Selection new_sel;
1638 new_sel.in = out;
1639 new_sel.out = s.out;
1640 new_sel.track = s.track;
1641 olive::ActiveSequence->selections.append(new_sel);
1642
1643 s.out = in;
1644 } else if (s.in < in && s.out > in) {
1645 // only out point is in deselect area
1646 s.out = in;
1647 } else if (s.in < out && s.out > out) {
1648 // only in point is in deselect area
1649 s.in = out;
1650 }
1651 }
1652 }
1653 }
1654
snap_to_point(long point,long * l)1655 bool Timeline::snap_to_point(long point, long* l) {
1656 int limit = get_snap_range();
1657 if (*l > point-limit-1 && *l < point+limit+1) {
1658 snap_point = point;
1659 *l = point;
1660 snapped = true;
1661 return true;
1662 }
1663 return false;
1664 }
1665
snap_to_timeline(long * l,bool use_playhead,bool use_markers,bool use_workarea)1666 bool Timeline::snap_to_timeline(long* l, bool use_playhead, bool use_markers, bool use_workarea) {
1667 snapped = false;
1668 if (snapping) {
1669 if (use_playhead && !panel_sequence_viewer->playing) {
1670 // snap to playhead
1671 if (snap_to_point(olive::ActiveSequence->playhead, l)) return true;
1672 }
1673
1674 // snap to marker
1675 if (use_markers) {
1676 for (int i=0;i<olive::ActiveSequence->markers.size();i++) {
1677 if (snap_to_point(olive::ActiveSequence->markers.at(i).frame, l)) return true;
1678 }
1679 }
1680
1681 // snap to in/out
1682 if (use_workarea && olive::ActiveSequence->using_workarea) {
1683 if (snap_to_point(olive::ActiveSequence->workarea_in, l)) return true;
1684 if (snap_to_point(olive::ActiveSequence->workarea_out, l)) return true;
1685 }
1686
1687 // snap to clip/transition
1688 for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
1689 ClipPtr c = olive::ActiveSequence->clips.at(i);
1690 if (c != nullptr) {
1691 if (snap_to_point(c->timeline_in(), l)) {
1692 return true;
1693 } else if (snap_to_point(c->timeline_out(), l)) {
1694 return true;
1695 } else if (c->opening_transition != nullptr
1696 && snap_to_point(c->timeline_in() + c->opening_transition->get_true_length(), l)) {
1697 return true;
1698 } else if (c->closing_transition != nullptr
1699 && snap_to_point(c->timeline_out() - c->closing_transition->get_true_length(), l)) {
1700 return true;
1701 } else {
1702 // try to snap to clip markers
1703 for (int j=0;j<c->get_markers().size();j++) {
1704 if (snap_to_point(c->get_markers().at(j).frame + c->timeline_in() - c->clip_in(), l)) {
1705 return true;
1706 }
1707 }
1708 }
1709 }
1710 }
1711 }
1712 return false;
1713 }
1714
set_marker()1715 void Timeline::set_marker() {
1716 // determine if any clips are selected, and if so add markers to clips rather than the sequence
1717 QVector<int> clips_selected;
1718 bool clip_mode = false;
1719
1720 for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
1721 Clip* c = olive::ActiveSequence->clips.at(i).get();
1722 if (c != nullptr
1723 && c->IsSelected()) {
1724
1725 // only add markers if the playhead is inside the clip
1726 if (olive::ActiveSequence->playhead >= c->timeline_in()
1727 && olive::ActiveSequence->playhead <= c->timeline_out()) {
1728 clips_selected.append(i);
1729 }
1730
1731 // we are definitely adding markers to clips though
1732 clip_mode = true;
1733
1734 }
1735 }
1736
1737 // if we've selected clips but none of the clips are within the playhead,
1738 // nothing to do here
1739 if (clip_mode && clips_selected.size() == 0) {
1740 return;
1741 }
1742
1743 // pass off to internal set marker function
1744 set_marker_internal(olive::ActiveSequence.get(), clips_selected);
1745
1746 }
1747
delete_inout()1748 void Timeline::delete_inout() {
1749 panel_timeline->delete_in_out_internal(false);
1750 }
1751
ripple_delete_inout()1752 void Timeline::ripple_delete_inout() {
1753 panel_timeline->delete_in_out_internal(true);
1754 }
1755
ripple_to_in_point()1756 void Timeline::ripple_to_in_point() {
1757 panel_timeline->edit_to_point_internal(true, true);
1758 }
1759
ripple_to_out_point()1760 void Timeline::ripple_to_out_point() {
1761 panel_timeline->edit_to_point_internal(false, true);
1762 }
1763
edit_to_in_point()1764 void Timeline::edit_to_in_point() {
1765 panel_timeline->edit_to_point_internal(true, false);
1766 }
1767
edit_to_out_point()1768 void Timeline::edit_to_out_point() {
1769 panel_timeline->edit_to_point_internal(false, false);
1770 }
1771
toggle_links()1772 void Timeline::toggle_links() {
1773 LinkCommand* command = new LinkCommand();
1774 command->s = olive::ActiveSequence.get();
1775 for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
1776 Clip* c = olive::ActiveSequence->clips.at(i).get();
1777 if (c != nullptr && c->IsSelected()) {
1778 if (!command->clips.contains(i)) command->clips.append(i);
1779
1780 if (c->linked.size() > 0) {
1781 command->link = false; // prioritize unlinking
1782
1783 for (int j=0;j<c->linked.size();j++) { // add links to the command
1784 if (!command->clips.contains(c->linked.at(j))) command->clips.append(c->linked.at(j));
1785 }
1786 }
1787 }
1788 }
1789 if (command->clips.size() > 0) {
1790 olive::UndoStack.push(command);
1791 repaint_timeline();
1792 } else {
1793 delete command;
1794 }
1795 }
1796
deselect()1797 void Timeline::deselect() {
1798 olive::ActiveSequence->selections.clear();
1799 repaint_timeline();
1800 }
1801
getFrameFromScreenPoint(double zoom,int x)1802 long getFrameFromScreenPoint(double zoom, int x) {
1803 long f = qRound(double(x) / zoom);
1804 if (f < 0) {
1805 return 0;
1806 }
1807 return f;
1808 }
1809
getScreenPointFromFrame(double zoom,long frame)1810 int getScreenPointFromFrame(double zoom, long frame) {
1811 return qRound(double(frame)*zoom);
1812 }
1813
getTimelineFrameFromScreenPoint(int x)1814 long Timeline::getTimelineFrameFromScreenPoint(int x) {
1815 return getFrameFromScreenPoint(zoom, x + scroll);
1816 }
1817
getTimelineScreenPointFromFrame(long frame)1818 int Timeline::getTimelineScreenPointFromFrame(long frame) {
1819 return getScreenPointFromFrame(zoom, frame) - scroll;
1820 }
1821
add_btn_click()1822 void Timeline::add_btn_click() {
1823 Menu add_menu(this);
1824
1825 QAction* titleMenuItem = new QAction(&add_menu);
1826 titleMenuItem->setText(tr("Title..."));
1827 titleMenuItem->setData(ADD_OBJ_TITLE);
1828 add_menu.addAction(titleMenuItem);
1829
1830 QAction* solidMenuItem = new QAction(&add_menu);
1831 solidMenuItem->setText(tr("Solid Color..."));
1832 solidMenuItem->setData(ADD_OBJ_SOLID);
1833 add_menu.addAction(solidMenuItem);
1834
1835 QAction* barsMenuItem = new QAction(&add_menu);
1836 barsMenuItem->setText(tr("Bars..."));
1837 barsMenuItem->setData(ADD_OBJ_BARS);
1838 add_menu.addAction(barsMenuItem);
1839
1840 add_menu.addSeparator();
1841
1842 QAction* toneMenuItem = new QAction(&add_menu);
1843 toneMenuItem->setText(tr("Tone..."));
1844 toneMenuItem->setData(ADD_OBJ_TONE);
1845 add_menu.addAction(toneMenuItem);
1846
1847 QAction* noiseMenuItem = new QAction(&add_menu);
1848 noiseMenuItem->setText(tr("Noise..."));
1849 noiseMenuItem->setData(ADD_OBJ_NOISE);
1850 add_menu.addAction(noiseMenuItem);
1851
1852 connect(&add_menu, SIGNAL(triggered(QAction*)), this, SLOT(add_menu_item(QAction*)));
1853
1854 add_menu.exec(QCursor::pos());
1855 }
1856
add_menu_item(QAction * action)1857 void Timeline::add_menu_item(QAction* action) {
1858 creating = true;
1859 creating_object = action->data().toInt();
1860 }
1861
setScroll(int s)1862 void Timeline::setScroll(int s) {
1863 scroll = s;
1864 headers->set_scroll(s);
1865 repaint_timeline();
1866 }
1867
record_btn_click()1868 void Timeline::record_btn_click() {
1869 if (olive::ActiveProjectFilename.isEmpty()) {
1870 QMessageBox::critical(this,
1871 tr("Unsaved Project"),
1872 tr("You must save this project before you can record audio in it."),
1873 QMessageBox::Ok);
1874 } else {
1875 creating = true;
1876 creating_object = ADD_OBJ_AUDIO;
1877 olive::MainWindow->statusBar()->showMessage(
1878 tr("Click on the timeline where you want to start recording (drag to limit the recording to a certain timeframe)"),
1879 10000);
1880 }
1881 }
1882
transition_tool_click()1883 void Timeline::transition_tool_click() {
1884 creating = false;
1885
1886 Menu transition_menu(this);
1887
1888 for (int i=0;i<effects.size();i++) {
1889 const EffectMeta& em = effects.at(i);
1890 if (em.type == EFFECT_TYPE_TRANSITION && em.subtype == EFFECT_TYPE_VIDEO) {
1891 QAction* a = transition_menu.addAction(em.name);
1892 a->setObjectName("v");
1893 a->setData(reinterpret_cast<quintptr>(&em));
1894 }
1895 }
1896
1897 transition_menu.addSeparator();
1898
1899 for (int i=0;i<effects.size();i++) {
1900 const EffectMeta& em = effects.at(i);
1901 if (em.type == EFFECT_TYPE_TRANSITION && em.subtype == EFFECT_TYPE_AUDIO) {
1902 QAction* a = transition_menu.addAction(em.name);
1903 a->setObjectName("a");
1904 a->setData(reinterpret_cast<quintptr>(&em));
1905 }
1906 }
1907
1908 connect(&transition_menu, SIGNAL(triggered(QAction*)), this, SLOT(transition_menu_select(QAction*)));
1909
1910 toolTransitionButton->setChecked(false);
1911
1912 transition_menu.exec(QCursor::pos());
1913 }
1914
transition_menu_select(QAction * a)1915 void Timeline::transition_menu_select(QAction* a) {
1916 transition_tool_meta = reinterpret_cast<const EffectMeta*>(a->data().value<quintptr>());
1917
1918 if (a->objectName() == "v") {
1919 transition_tool_side = -1;
1920 } else {
1921 transition_tool_side = 1;
1922 }
1923
1924 decheck_tool_buttons(sender());
1925 timeline_area->setCursor(Qt::CrossCursor);
1926 tool = TIMELINE_TOOL_TRANSITION;
1927 toolTransitionButton->setChecked(true);
1928 }
1929
resize_move(double z)1930 void Timeline::resize_move(double z) {
1931 set_zoom_value(zoom * z);
1932 }
1933
set_sb_max()1934 void Timeline::set_sb_max() {
1935 headers->set_scrollbar_max(horizontalScrollBar, olive::ActiveSequence->getEndFrame(), editAreas->width() - getScreenPointFromFrame(zoom, 200));
1936 }
1937
UpdateTitle()1938 void Timeline::UpdateTitle() {
1939 QString title = tr("Timeline: ");
1940 if (olive::ActiveSequence == nullptr) {
1941 setWindowTitle(title + tr("(none)"));
1942 } else {
1943 setWindowTitle(title + olive::ActiveSequence->name);
1944 update_ui(false);
1945 }
1946 }
1947
setup_ui()1948 void Timeline::setup_ui() {
1949 QWidget* dockWidgetContents = new QWidget();
1950
1951 QHBoxLayout* horizontalLayout = new QHBoxLayout(dockWidgetContents);
1952 horizontalLayout->setSpacing(0);
1953 horizontalLayout->setMargin(0);
1954
1955 setWidget(dockWidgetContents);
1956
1957 tool_button_widget = new QWidget();
1958 tool_button_widget->setObjectName("timeline_toolbar");
1959 tool_button_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1960
1961 FlowLayout* tool_buttons_layout = new FlowLayout(tool_button_widget);
1962 tool_buttons_layout->setSpacing(4);
1963 tool_buttons_layout->setMargin(0);
1964
1965 toolArrowButton = new QPushButton();
1966 toolArrowButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/arrow.svg")));
1967 toolArrowButton->setCheckable(true);
1968 toolArrowButton->setProperty("tool", TIMELINE_TOOL_POINTER);
1969 connect(toolArrowButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
1970 tool_buttons_layout->addWidget(toolArrowButton);
1971
1972 toolEditButton = new QPushButton();
1973 toolEditButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/beam.svg")));
1974 toolEditButton->setCheckable(true);
1975 toolEditButton->setProperty("tool", TIMELINE_TOOL_EDIT);
1976 connect(toolEditButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
1977 tool_buttons_layout->addWidget(toolEditButton);
1978
1979 toolRippleButton = new QPushButton();
1980 toolRippleButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/ripple.svg")));
1981 toolRippleButton->setCheckable(true);
1982 toolRippleButton->setProperty("tool", TIMELINE_TOOL_RIPPLE);
1983 connect(toolRippleButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
1984 tool_buttons_layout->addWidget(toolRippleButton);
1985
1986 toolRazorButton = new QPushButton();
1987 toolRazorButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/razor.svg")));
1988 toolRazorButton->setCheckable(true);
1989 toolRazorButton->setProperty("tool", TIMELINE_TOOL_RAZOR);
1990 connect(toolRazorButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
1991 tool_buttons_layout->addWidget(toolRazorButton);
1992
1993 toolSlipButton = new QPushButton();
1994 toolSlipButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/slip.svg")));
1995 toolSlipButton->setCheckable(true);
1996 toolSlipButton->setProperty("tool", TIMELINE_TOOL_SLIP);
1997 connect(toolSlipButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
1998 tool_buttons_layout->addWidget(toolSlipButton);
1999
2000 toolSlideButton = new QPushButton();
2001 toolSlideButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/slide.svg")));
2002 toolSlideButton->setCheckable(true);
2003 toolSlideButton->setProperty("tool", TIMELINE_TOOL_SLIDE);
2004 connect(toolSlideButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
2005 tool_buttons_layout->addWidget(toolSlideButton);
2006
2007 toolHandButton = new QPushButton();
2008 toolHandButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/hand.svg")));
2009 toolHandButton->setCheckable(true);
2010
2011 toolHandButton->setProperty("tool", TIMELINE_TOOL_HAND);
2012 connect(toolHandButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
2013 tool_buttons_layout->addWidget(toolHandButton);
2014 toolTransitionButton = new QPushButton();
2015 toolTransitionButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/transition-tool.svg")));
2016 toolTransitionButton->setCheckable(true);
2017 connect(toolTransitionButton, SIGNAL(clicked(bool)), this, SLOT(transition_tool_click()));
2018 tool_buttons_layout->addWidget(toolTransitionButton);
2019
2020 snappingButton = new QPushButton();
2021 snappingButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/magnet.svg")));
2022 snappingButton->setCheckable(true);
2023 snappingButton->setChecked(true);
2024 connect(snappingButton, SIGNAL(toggled(bool)), this, SLOT(snapping_clicked(bool)));
2025 tool_buttons_layout->addWidget(snappingButton);
2026
2027 zoomInButton = new QPushButton();
2028 zoomInButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/zoomin.svg")));
2029 connect(zoomInButton, SIGNAL(clicked(bool)), this, SLOT(zoom_in()));
2030 tool_buttons_layout->addWidget(zoomInButton);
2031
2032 zoomOutButton = new QPushButton();
2033 zoomOutButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/zoomout.svg")));
2034 connect(zoomOutButton, SIGNAL(clicked(bool)), this, SLOT(zoom_out()));
2035 tool_buttons_layout->addWidget(zoomOutButton);
2036
2037 recordButton = new QPushButton();
2038 recordButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/record.svg")));
2039 connect(recordButton, SIGNAL(clicked(bool)), this, SLOT(record_btn_click()));
2040 tool_buttons_layout->addWidget(recordButton);
2041
2042 addButton = new QPushButton();
2043 addButton->setIcon(olive::icon::CreateIconFromSVG(QStringLiteral(":/icons/add-button.svg")));
2044 connect(addButton, SIGNAL(clicked()), this, SLOT(add_btn_click()));
2045 tool_buttons_layout->addWidget(addButton);
2046
2047 horizontalLayout->addWidget(tool_button_widget);
2048
2049 timeline_area = new QWidget();
2050 QSizePolicy timeline_area_policy(QSizePolicy::Minimum, QSizePolicy::Minimum);
2051 timeline_area_policy.setHorizontalStretch(1);
2052 timeline_area_policy.setVerticalStretch(0);
2053 timeline_area_policy.setHeightForWidth(timeline_area->sizePolicy().hasHeightForWidth());
2054 timeline_area->setSizePolicy(timeline_area_policy);
2055
2056 QVBoxLayout* timeline_area_layout = new QVBoxLayout(timeline_area);
2057 timeline_area_layout->setSpacing(0);
2058 timeline_area_layout->setContentsMargins(0, 0, 0, 0);
2059
2060 headers = new TimelineHeader();
2061 timeline_area_layout->addWidget(headers);
2062
2063 editAreas = new QWidget();
2064 QHBoxLayout* editAreaLayout = new QHBoxLayout(editAreas);
2065 editAreaLayout->setSpacing(0);
2066 editAreaLayout->setContentsMargins(0, 0, 0, 0);
2067
2068 QSplitter* splitter = new QSplitter();
2069 splitter->setChildrenCollapsible(false);
2070 splitter->setOrientation(Qt::Vertical);
2071
2072 QWidget* videoContainer = new QWidget();
2073
2074 QHBoxLayout* videoContainerLayout = new QHBoxLayout(videoContainer);
2075 videoContainerLayout->setSpacing(0);
2076 videoContainerLayout->setContentsMargins(0, 0, 0, 0);
2077
2078 video_area = new TimelineWidget();
2079 video_area->setFocusPolicy(Qt::ClickFocus);
2080 videoContainerLayout->addWidget(video_area);
2081
2082 videoScrollbar = new QScrollBar();
2083 videoScrollbar->setMaximum(0);
2084 videoScrollbar->setSingleStep(20);
2085 videoScrollbar->setOrientation(Qt::Vertical);
2086 videoContainerLayout->addWidget(videoScrollbar);
2087
2088 splitter->addWidget(videoContainer);
2089
2090 QWidget* audioContainer = new QWidget();
2091 QHBoxLayout* audioContainerLayout = new QHBoxLayout(audioContainer);
2092 audioContainerLayout->setSpacing(0);
2093 audioContainerLayout->setContentsMargins(0, 0, 0, 0);
2094
2095 audio_area = new TimelineWidget();
2096 audio_area->setFocusPolicy(Qt::ClickFocus);
2097
2098 audioContainerLayout->addWidget(audio_area);
2099
2100 audioScrollbar = new QScrollBar();
2101 audioScrollbar->setMaximum(0);
2102 audioScrollbar->setOrientation(Qt::Vertical);
2103
2104 audioContainerLayout->addWidget(audioScrollbar);
2105
2106 splitter->addWidget(audioContainer);
2107
2108 editAreaLayout->addWidget(splitter);
2109
2110 timeline_area_layout->addWidget(editAreas);
2111
2112 horizontalScrollBar = new ResizableScrollBar();
2113 horizontalScrollBar->setMaximum(0);
2114 horizontalScrollBar->setSingleStep(20);
2115 horizontalScrollBar->setOrientation(Qt::Horizontal);
2116
2117 timeline_area_layout->addWidget(horizontalScrollBar);
2118
2119 horizontalLayout->addWidget(timeline_area);
2120
2121 audio_monitor = new AudioMonitor();
2122 audio_monitor->setMinimumSize(QSize(50, 0));
2123
2124 horizontalLayout->addWidget(audio_monitor);
2125
2126 setWidget(dockWidgetContents);
2127 }
2128
set_tool()2129 void Timeline::set_tool() {
2130 QPushButton* button = static_cast<QPushButton*>(sender());
2131 decheck_tool_buttons(button);
2132 tool = button->property("tool").toInt();
2133 creating = false;
2134 switch (tool) {
2135 case TIMELINE_TOOL_EDIT:
2136 timeline_area->setCursor(Qt::IBeamCursor);
2137 break;
2138 case TIMELINE_TOOL_RAZOR:
2139 timeline_area->setCursor(olive::cursor::Razor);
2140 break;
2141 case TIMELINE_TOOL_HAND:
2142 timeline_area->setCursor(Qt::OpenHandCursor);
2143 break;
2144 default:
2145 timeline_area->setCursor(Qt::ArrowCursor);
2146 }
2147 }
2148
MultiplyTrackSizesByDPI()2149 void olive::timeline::MultiplyTrackSizesByDPI()
2150 {
2151 kTrackDefaultHeight *= QApplication::desktop()->devicePixelRatio();
2152 kTrackMinHeight *= QApplication::desktop()->devicePixelRatio();
2153 kTrackHeightIncrement *= QApplication::desktop()->devicePixelRatio();
2154 }
2155