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