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 "timelinewidget.h"
22 
23 #include <QPainter>
24 #include <QColor>
25 #include <QMouseEvent>
26 #include <QObject>
27 #include <QVariant>
28 #include <QPoint>
29 #include <QMessageBox>
30 #include <QtMath>
31 #include <QScrollBar>
32 #include <QMimeData>
33 #include <QToolTip>
34 #include <QInputDialog>
35 #include <QStatusBar>
36 
37 #include "global/global.h"
38 #include "panels/panels.h"
39 #include "project/projectelements.h"
40 #include "rendering/audio.h"
41 #include "global/config.h"
42 #include "ui/sourcetable.h"
43 #include "ui/sourceiconview.h"
44 #include "undo/undo.h"
45 #include "undo/undostack.h"
46 #include "ui/viewerwidget.h"
47 #include "ui/resizablescrollbar.h"
48 #include "dialogs/newsequencedialog.h"
49 #include "mainwindow.h"
50 #include "ui/rectangleselect.h"
51 #include "rendering/renderfunctions.h"
52 #include "ui/cursors.h"
53 #include "ui/menuhelper.h"
54 #include "ui/menu.h"
55 #include "ui/focusfilter.h"
56 #include "dialogs/clippropertiesdialog.h"
57 #include "global/debug.h"
58 #include "effects/effect.h"
59 #include "effects/internal/solideffect.h"
60 
61 #define MAX_TEXT_WIDTH 20
62 #define TRANSITION_BETWEEN_RANGE 40
63 
TimelineWidget(QWidget * parent)64 TimelineWidget::TimelineWidget(QWidget *parent) : QWidget(parent) {
65   selection_command = nullptr;
66   self_created_sequence = nullptr;
67   scroll = 0;
68 
69   bottom_align = false;
70   track_resizing = false;
71   setMouseTracking(true);
72 
73   setAcceptDrops(true);
74 
75   setContextMenuPolicy(Qt::CustomContextMenu);
76   connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&)));
77 
78   tooltip_timer.setInterval(500);
79   connect(&tooltip_timer, SIGNAL(timeout()), this, SLOT(tooltip_timer_timeout()));
80 }
81 
show_context_menu(const QPoint & pos)82 void TimelineWidget::show_context_menu(const QPoint& pos) {
83   if (olive::ActiveSequence != nullptr) {
84     // hack because sometimes right clicking doesn't trigger mouse release event
85     panel_timeline->rect_select_init = false;
86     panel_timeline->rect_select_proc = false;
87 
88     Menu menu(this);
89 
90     QAction* undoAction = menu.addAction(tr("&Undo"));
91     QAction* redoAction = menu.addAction(tr("&Redo"));
92     connect(undoAction, SIGNAL(triggered(bool)), olive::Global.get(), SLOT(undo()));
93     connect(redoAction, SIGNAL(triggered(bool)), olive::Global.get(), SLOT(redo()));
94     undoAction->setEnabled(olive::UndoStack.canUndo());
95     redoAction->setEnabled(olive::UndoStack.canRedo());
96     menu.addSeparator();
97 
98     // collect all the selected clips
99     QVector<Clip*> selected_clips = olive::ActiveSequence->SelectedClips();
100 
101     olive::MenuHelper.make_edit_functions_menu(&menu, !selected_clips.isEmpty());
102 
103     if (selected_clips.isEmpty()) {
104       // no clips are selected
105 
106       // determine if we can perform a ripple empty space
107       panel_timeline->cursor_frame = panel_timeline->getTimelineFrameFromScreenPoint(pos.x());
108       panel_timeline->cursor_track = getTrackFromScreenPoint(pos.y());
109 
110       if (panel_timeline->can_ripple_empty_space(panel_timeline->cursor_frame, panel_timeline->cursor_track)) {
111         QAction* ripple_delete_action = menu.addAction(tr("R&ipple Delete Empty Space"));
112         connect(ripple_delete_action, SIGNAL(triggered(bool)), panel_timeline, SLOT(ripple_delete_empty_space()));
113       }
114 
115       QAction* seq_settings = menu.addAction(tr("Sequence Settings"));
116       connect(seq_settings, SIGNAL(triggered(bool)), this, SLOT(open_sequence_properties()));
117     }
118 
119     if (!selected_clips.isEmpty()) {
120 
121       bool video_clips_are_selected = false;
122       bool audio_clips_are_selected = false;
123 
124       for (int i=0;i<selected_clips.size();i++) {
125         if (selected_clips.at(i)->track() < 0) {
126           video_clips_are_selected = true;
127         } else {
128           audio_clips_are_selected = true;
129         }
130       }
131 
132       menu.addSeparator();
133 
134       menu.addAction(tr("&Speed/Duration"), olive::Global.get(), SLOT(open_speed_dialog()));
135 
136       if (audio_clips_are_selected) {
137         menu.addAction(tr("Auto-Cut Silence"), olive::Global.get(), SLOT(open_autocut_silence_dialog()));
138       }
139 
140       QAction* autoscaleAction = menu.addAction(tr("Auto-S&cale"), this, SLOT(toggle_autoscale()));
141       autoscaleAction->setCheckable(true);
142       // set autoscale to the first selected clip
143       autoscaleAction->setChecked(selected_clips.at(0)->autoscaled());
144 
145       olive::MenuHelper.make_clip_functions_menu(&menu);
146 
147       // stabilizer option
148       /*int video_clip_count = 0;
149       bool all_video_is_footage = true;
150       for (int i=0;i<selected_clips.size();i++) {
151         if (selected_clips.at(i)->track() < 0) {
152           video_clip_count++;
153           if (selected_clips.at(i)->media() == nullptr
154               || selected_clips.at(i)->media()->get_type() != MEDIA_TYPE_FOOTAGE) {
155             all_video_is_footage = false;
156           }
157         }
158       }
159       if (video_clip_count == 1 && all_video_is_footage) {
160         QAction* stabilizerAction = menu.addAction("S&tabilizer");
161         connect(stabilizerAction, SIGNAL(triggered(bool)), this, SLOT(show_stabilizer_diag()));
162       }*/
163 
164       // check if all selected clips have the same media for a "Reveal In Project"
165       bool same_media = true;
166       rc_reveal_media = selected_clips.at(0)->media();
167       for (int i=1;i<selected_clips.size();i++) {
168         if (selected_clips.at(i)->media() != rc_reveal_media) {
169           same_media = false;
170           break;
171         }
172       }
173 
174       if (same_media) {
175         QAction* revealInProjectAction = menu.addAction(tr("&Reveal in Project"));
176         connect(revealInProjectAction, SIGNAL(triggered(bool)), this, SLOT(reveal_media()));
177       }
178 
179       menu.addAction(tr("Properties"), this, SLOT(show_clip_properties()));
180     }
181 
182     menu.exec(mapToGlobal(pos));
183   }
184 }
185 
toggle_autoscale()186 void TimelineWidget::toggle_autoscale() {
187   QVector<Clip*> selected_clips = olive::ActiveSequence->SelectedClips();
188 
189   if (!selected_clips.isEmpty()) {
190     SetClipProperty* action = new SetClipProperty(kSetClipPropertyAutoscale);
191 
192     for (int i=0;i<selected_clips.size();i++) {
193       Clip* c = selected_clips.at(i);
194       action->AddSetting(c, !c->autoscaled());
195     }
196 
197     olive::UndoStack.push(action);
198   }
199 }
200 
tooltip_timer_timeout()201 void TimelineWidget::tooltip_timer_timeout() {
202   if (olive::ActiveSequence != nullptr) {
203     if (tooltip_clip < olive::ActiveSequence->clips.size()) {
204       ClipPtr c = olive::ActiveSequence->clips.at(tooltip_clip);
205       if (c != nullptr) {
206         QToolTip::showText(QCursor::pos(),
207                            tr("%1\nStart: %2\nEnd: %3\nDuration: %4").arg(
208                              c->name(),
209                              frame_to_timecode(c->timeline_in(), olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate),
210                              frame_to_timecode(c->timeline_out(), olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate),
211                              frame_to_timecode(c->length(), olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate)
212                              ));
213       }
214     }
215   }
216   tooltip_timer.stop();
217 }
218 
open_sequence_properties()219 void TimelineWidget::open_sequence_properties() {
220   QList<Media*> sequence_items;
221   QList<Media*> all_top_level_items;
222   for (int i=0;i<olive::project_model.childCount();i++) {
223     all_top_level_items.append(olive::project_model.child(i));
224   }
225   panel_project->get_all_media_from_table(all_top_level_items, sequence_items, MEDIA_TYPE_SEQUENCE); // find all sequences in project
226   for (int i=0;i<sequence_items.size();i++) {
227     if (sequence_items.at(i)->to_sequence() == olive::ActiveSequence) {
228       NewSequenceDialog nsd(this, sequence_items.at(i));
229       nsd.exec();
230       return;
231     }
232   }
233   QMessageBox::critical(this, tr("Error"), tr("Couldn't locate media wrapper for sequence."));
234 }
235 
show_clip_properties()236 void TimelineWidget::show_clip_properties()
237 {
238   // get list of selected clips
239   QVector<Clip*> selected_clips = olive::ActiveSequence->SelectedClips();
240 
241   // if clips are selected, open the clip properties dialog
242   if (!selected_clips.isEmpty()) {
243     ClipPropertiesDialog cpd(this, selected_clips);
244     cpd.exec();
245   }
246 }
247 
same_sign(int a,int b)248 bool same_sign(int a, int b) {
249   return (a < 0) == (b < 0);
250 }
251 
dragEnterEvent(QDragEnterEvent * event)252 void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) {
253   bool import_init = false;
254 
255   QVector<olive::timeline::MediaImportData> media_list;
256   panel_timeline->importing_files = false;
257 
258   if (panel_project->IsProjectWidget(event->source())) {
259     QModelIndexList items = panel_project->get_current_selected();
260     media_list.resize(items.size());
261     for (int i=0;i<items.size();i++) {
262       media_list[i] = panel_project->item_to_media(items.at(i));
263     }
264     import_init = true;
265   }
266 
267   if (event->source() == panel_footage_viewer) {
268     if (panel_footage_viewer->seq != olive::ActiveSequence) { // don't allow nesting the same sequence
269 
270       media_list.append(olive::timeline::MediaImportData(panel_footage_viewer->media,
271                          static_cast<olive::timeline::MediaImportType>(event->mimeData()->text().toInt())));
272       import_init = true;
273 
274     }
275   }
276 
277   if (olive::CurrentConfig.enable_drag_files_to_timeline && event->mimeData()->hasUrls()) {
278     QList<QUrl> urls = event->mimeData()->urls();
279     if (!urls.isEmpty()) {
280       QStringList file_list;
281 
282       for (int i=0;i<urls.size();i++) {
283         file_list.append(urls.at(i).toLocalFile());
284       }
285 
286       panel_project->process_file_list(file_list);
287 
288       for (int i=0;i<panel_project->last_imported_media.size();i++) {
289         Footage* f = panel_project->last_imported_media.at(i)->to_footage();
290 
291         // waits for media to have a duration
292         // TODO would be much nicer if this was multithreaded
293         f->ready_lock.lock();
294         f->ready_lock.unlock();
295 
296         if (f->ready) {
297           media_list.append(panel_project->last_imported_media.at(i));
298         }
299       }
300 
301       if (media_list.isEmpty()) {
302         olive::UndoStack.undo();
303       } else {
304         import_init = true;
305         panel_timeline->importing_files = true;
306       }
307     }
308   }
309 
310   if (import_init) {
311     event->acceptProposedAction();
312 
313     long entry_point;
314     Sequence* seq = olive::ActiveSequence.get();
315 
316     if (seq == nullptr) {
317       // if no sequence, we're going to create a new one using the clips as a reference
318       entry_point = 0;
319 
320       self_created_sequence = create_sequence_from_media(media_list);
321       seq = self_created_sequence.get();
322     } else {
323       entry_point = panel_timeline->getTimelineFrameFromScreenPoint(event->pos().x());
324       panel_timeline->drag_frame_start = entry_point + getFrameFromScreenPoint(panel_timeline->zoom, 50);
325       panel_timeline->drag_track_start = (bottom_align) ? -1 : 0;
326     }
327 
328     panel_timeline->create_ghosts_from_media(seq, entry_point, media_list);
329 
330     panel_timeline->importing = true;
331   }
332 }
333 
dragMoveEvent(QDragMoveEvent * event)334 void TimelineWidget::dragMoveEvent(QDragMoveEvent *event) {
335   if (panel_timeline->importing) {
336     event->acceptProposedAction();
337 
338     if (olive::ActiveSequence != nullptr) {
339       QPoint pos = event->pos();
340       panel_timeline->scroll_to_frame(panel_timeline->getTimelineFrameFromScreenPoint(event->pos().x()));
341       update_ghosts(pos, event->keyboardModifiers() & Qt::ShiftModifier);
342       panel_timeline->move_insert = ((event->keyboardModifiers() & Qt::ControlModifier) && (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->importing));
343       update_ui(false);
344     }
345   }
346 }
347 
wheelEvent(QWheelEvent * event)348 void TimelineWidget::wheelEvent(QWheelEvent *event) {
349 
350   // TODO: implement pixel scrolling
351 
352   bool shift = (event->modifiers() & Qt::ShiftModifier);
353   bool ctrl = (event->modifiers() & Qt::ControlModifier);
354   bool alt = (event->modifiers() & Qt::AltModifier);
355 
356   // "Scroll Zooms" false + Control up  : not zooming
357   // "Scroll Zooms" false + Control down:     zooming
358   // "Scroll Zooms" true  + Control up  :     zooming
359   // "Scroll Zooms" true  + Control down: not zooming
360   bool zooming = (olive::CurrentConfig.scroll_zooms != ctrl);
361 
362   // Allow shift for axis swap, but don't swap on zoom... Unless
363   // we need to override Qt's axis swap via Alt
364   bool swap_hv = ((shift != olive::CurrentConfig.invert_timeline_scroll_axes) &
365                   !zooming) | (alt & !shift & zooming);
366 
367   int delta_h = swap_hv ? event->angleDelta().y() : event->angleDelta().x();
368   int delta_v = swap_hv ? event->angleDelta().x() : event->angleDelta().y();
369 
370   if (zooming) {
371 
372     // Zoom only uses vertical scrolling, to avoid glitches on touchpads.
373     // Don't do anything if not scrolling vertically.
374 
375     if (delta_v != 0) {
376 
377       // delta_v == 120 for one click of a mousewheel. Less or more for a
378       // touchpad gesture. Calculate speed to compensate.
379       // 120 = ratio of 4/3 (1.33), -120 = ratio of 3/4 (.75)
380 
381       double zoom_ratio = 1.0 + (abs(delta_v) * 0.33 / 120);
382 
383       if (delta_v < 0) {
384         zoom_ratio = 1.0 / zoom_ratio;
385       }
386 
387       panel_timeline->multiply_zoom(zoom_ratio);
388     }
389 
390   } else {
391 
392     // Use the Timeline's main scrollbar for horizontal scrolling, and this
393     // widget's scrollbar for vertical scrolling.
394 
395     QScrollBar* bar_v = scrollBar;
396     QScrollBar* bar_h = panel_timeline->horizontalScrollBar;
397 
398     // Match the wheel events to the size of a step as per
399     // https://doc.qt.io/qt-5/qwheelevent.html#angleDelta
400 
401     int step_h = bar_h->singleStep() * delta_h / -120;
402     int step_v = bar_v->singleStep() * delta_v / -120;
403 
404     // Apply to appropriate scrollbars
405 
406     bar_h->setValue(bar_h->value() + step_h);
407     bar_v->setValue(bar_v->value() + step_v);
408   }
409 }
410 
dragLeaveEvent(QDragLeaveEvent * event)411 void TimelineWidget::dragLeaveEvent(QDragLeaveEvent* event) {
412   event->accept();
413   if (panel_timeline->importing) {
414     if (panel_timeline->importing_files) {
415       olive::UndoStack.undo();
416     }
417     panel_timeline->importing_files = false;
418     panel_timeline->ghosts.clear();
419     panel_timeline->importing = false;
420     update_ui(false);
421   }
422   if (self_created_sequence != nullptr) {
423     self_created_sequence.reset();
424     self_created_sequence = nullptr;
425   }
426 }
427 
delete_area_under_ghosts(ComboAction * ca)428 void delete_area_under_ghosts(ComboAction* ca) {
429   // delete areas before adding
430   QVector<Selection> delete_areas;
431   for (int i=0;i<panel_timeline->ghosts.size();i++) {
432     const Ghost& g = panel_timeline->ghosts.at(i);
433     Selection sel;
434     sel.in = g.in;
435     sel.out = g.out;
436     sel.track = g.track;
437     delete_areas.append(sel);
438   }
439   panel_timeline->delete_areas_and_relink(ca, delete_areas, false);
440 }
441 
insert_clips(ComboAction * ca)442 void insert_clips(ComboAction* ca) {
443   bool ripple_old_point = true;
444 
445   long earliest_old_point = LONG_MAX;
446   long latest_old_point = LONG_MIN;
447 
448   long earliest_new_point = LONG_MAX;
449   long latest_new_point = LONG_MIN;
450 
451   QVector<int> ignore_clips;
452   for (int i=0;i<panel_timeline->ghosts.size();i++) {
453     const Ghost& g = panel_timeline->ghosts.at(i);
454 
455     earliest_old_point = qMin(earliest_old_point, g.old_in);
456     latest_old_point = qMax(latest_old_point, g.old_out);
457     earliest_new_point = qMin(earliest_new_point, g.in);
458     latest_new_point = qMax(latest_new_point, g.out);
459 
460     if (g.clip >= 0) {
461       ignore_clips.append(g.clip);
462     } else {
463       // don't try to close old gap if importing
464       ripple_old_point = false;
465     }
466   }
467 
468   panel_timeline->split_cache.clear();
469 
470   for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
471     ClipPtr c = olive::ActiveSequence->clips.at(i);
472     if (c != nullptr) {
473       // don't split any clips that are moving
474       bool found = false;
475       for (int j=0;j<panel_timeline->ghosts.size();j++) {
476         if (panel_timeline->ghosts.at(j).clip == i) {
477           found = true;
478           break;
479         }
480       }
481       if (!found) {
482         if (c->timeline_in() < earliest_new_point && c->timeline_out() > earliest_new_point) {
483           panel_timeline->split_clip_and_relink(ca, i, earliest_new_point, true);
484         }
485 
486         // determine if we should close the gap the old clips left behind
487         if (ripple_old_point
488             && !((c->timeline_in() < earliest_old_point && c->timeline_out() <= earliest_old_point) || (c->timeline_in() >= latest_old_point && c->timeline_out() > latest_old_point))
489             && !ignore_clips.contains(i)) {
490           ripple_old_point = false;
491         }
492       }
493     }
494   }
495 
496   long ripple_length = (latest_new_point - earliest_new_point);
497 
498   ripple_clips(ca, olive::ActiveSequence.get(), earliest_new_point, ripple_length, ignore_clips);
499 
500   if (ripple_old_point) {
501     // works for moving later clips earlier but not earlier to later
502     long second_ripple_length = (earliest_old_point - latest_old_point);
503 
504     ripple_clips(ca, olive::ActiveSequence.get(), latest_old_point, second_ripple_length, ignore_clips);
505 
506     if (earliest_old_point < earliest_new_point) {
507       for (int i=0;i<panel_timeline->ghosts.size();i++) {
508         Ghost& g = panel_timeline->ghosts[i];
509         g.in += second_ripple_length;
510         g.out += second_ripple_length;
511       }
512       for (int i=0;i<olive::ActiveSequence->selections.size();i++) {
513         Selection& s = olive::ActiveSequence->selections[i];
514         s.in += second_ripple_length;
515         s.out += second_ripple_length;
516       }
517     }
518   }
519 }
520 
dropEvent(QDropEvent * event)521 void TimelineWidget::dropEvent(QDropEvent* event) {
522   if (panel_timeline->importing && panel_timeline->ghosts.size() > 0) {
523     ComboAction* ca = new ComboAction();
524 
525     Sequence* s = olive::ActiveSequence.get();
526 
527     // if we're dropping into nothing, create a new sequences based on the clip being dragged
528     if (s == nullptr) {
529       QMessageBox mbox(this);
530 
531       mbox.setWindowTitle(tr("New Sequence"));
532       mbox.setText(tr("No sequence has been created yet. Would you like to make one based on this footage or set "
533                       "custom parameters?"));
534       mbox.addButton(tr("Use Footage Parameters"), QMessageBox::YesRole);
535       QAbstractButton* custom_param_btn = mbox.addButton(tr("Custom Parameters"), QMessageBox::NoRole);
536       QAbstractButton* cancel_btn = mbox.addButton(QMessageBox::Cancel);
537 
538       mbox.exec();
539 
540       s = self_created_sequence.get();
541       double old_fr = s->frame_rate;
542 
543       if (mbox.clickedButton() == cancel_btn
544           || (mbox.clickedButton() == custom_param_btn && NewSequenceDialog(this, nullptr, s).exec() == QDialog::Rejected)) {
545         delete ca;
546         self_created_sequence = nullptr;
547         event->ignore();
548         return;
549       }
550 
551       if (mbox.clickedButton() == custom_param_btn
552           && !qFuzzyCompare(old_fr, s->frame_rate)) {
553         // If we're here, the user changed the frame rate so all the ghosts will need adjustment
554         for (int i=0;i<panel_timeline->ghosts.size();i++) {
555           Ghost& g = panel_timeline->ghosts[i];
556 
557           g.in = rescale_frame_number(g.in, old_fr, s->frame_rate);
558           g.out = rescale_frame_number(g.out, old_fr, s->frame_rate);
559           g.clip_in = rescale_frame_number(g.clip_in, old_fr, s->frame_rate);
560         }
561       }
562 
563       panel_project->create_sequence_internal(ca, self_created_sequence, true, nullptr);
564       self_created_sequence = nullptr;
565 
566     } else if (event->keyboardModifiers() & Qt::ControlModifier) {
567       insert_clips(ca);
568     } else {
569       delete_area_under_ghosts(ca);
570     }
571 
572     panel_timeline->add_clips_from_ghosts(ca, s);
573 
574     olive::UndoStack.push(ca);
575 
576     setFocus();
577 
578     update_ui(true);
579 
580     event->acceptProposedAction();
581   }
582 }
583 
mouseDoubleClickEvent(QMouseEvent * event)584 void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *event) {
585   if (olive::ActiveSequence != nullptr) {
586     if (panel_timeline->tool == TIMELINE_TOOL_EDIT) {
587       int clip_index = getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track);
588       if (clip_index >= 0) {
589         ClipPtr clip = olive::ActiveSequence->clips.at(clip_index);
590         if (!(event->modifiers() & Qt::ShiftModifier)) olive::ActiveSequence->selections.clear();
591         Selection s;
592         s.in = clip->timeline_in();
593         s.out = clip->timeline_out();
594         s.track = clip->track();
595         olive::ActiveSequence->selections.append(s);
596         update_ui(false);
597       }
598     } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER) {
599       int clip_index = getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track);
600       if (clip_index >= 0) {
601         ClipPtr c = olive::ActiveSequence->clips.at(clip_index);
602         if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) {
603           olive::Global->set_sequence(c->media()->to_sequence());
604         }
605       }
606     }
607   }
608 }
609 
current_tool_shows_cursor()610 bool current_tool_shows_cursor() {
611   return (panel_timeline->tool == TIMELINE_TOOL_EDIT || panel_timeline->tool == TIMELINE_TOOL_RAZOR || panel_timeline->creating);
612 }
613 
mousePressEvent(QMouseEvent * event)614 void TimelineWidget::mousePressEvent(QMouseEvent *event) {
615   if (olive::ActiveSequence != nullptr) {
616 
617     int effective_tool = panel_timeline->tool;
618 
619     // some user actions will override which tool we'll be using
620     if (event->button() == Qt::MiddleButton) {
621       effective_tool = TIMELINE_TOOL_HAND;
622       panel_timeline->creating = false;
623     } else if (event->button() == Qt::RightButton) {
624       effective_tool = TIMELINE_TOOL_MENU;
625       panel_timeline->creating = false;
626     }
627 
628     // ensure cursor_frame and cursor_track are up to date
629     mouseMoveEvent(event);
630 
631     // store current cursor positions
632     panel_timeline->drag_x_start = event->pos().x();
633     panel_timeline->drag_y_start = event->pos().y();
634 
635     // store current frame/tracks as the values to start dragging from
636     panel_timeline->drag_frame_start = panel_timeline->cursor_frame;
637     panel_timeline->drag_track_start = panel_timeline->cursor_track;
638 
639     // get the clip the user is currently hovering over, priority to trim_target set from mouseMoveEvent
640     int hovered_clip = panel_timeline->trim_target == -1 ?
641           getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track)
642         : panel_timeline->trim_target;
643 
644     bool shift = (event->modifiers() & Qt::ShiftModifier);
645     bool alt = (event->modifiers() & Qt::AltModifier);
646 
647     // Normal behavior is to reset selections to zero when clicking, but if Shift is held, we add selections
648     // to the existing selections. `selection_offset` is the index to change selections from (and we don't touch
649     // any prior to that)
650     if (shift) {
651       panel_timeline->selection_offset = olive::ActiveSequence->selections.size();
652     } else {
653       panel_timeline->selection_offset = 0;
654     }
655 
656     // if the user is creating an object
657     if (panel_timeline->creating) {
658       int comp = 0;
659       switch (panel_timeline->creating_object) {
660       case ADD_OBJ_TITLE:
661       case ADD_OBJ_SOLID:
662       case ADD_OBJ_BARS:
663         comp = -1;
664         break;
665       case ADD_OBJ_TONE:
666       case ADD_OBJ_NOISE:
667       case ADD_OBJ_AUDIO:
668         comp = 1;
669         break;
670       }
671 
672       // if the track the user clicked is correct for the type of object we're adding
673 
674       if ((panel_timeline->drag_track_start < 0) == (comp < 0)) {
675         Ghost g;
676         g.in = g.old_in = g.out = g.old_out = panel_timeline->drag_frame_start;
677         g.track = g.old_track = panel_timeline->drag_track_start;
678         g.transition = nullptr;
679         g.clip = -1;
680         g.trim_type = TRIM_OUT;
681         panel_timeline->ghosts.append(g);
682 
683         panel_timeline->moving_init = true;
684         panel_timeline->moving_proc = true;
685       }
686     } else {
687 
688       // pass through tools to determine what action we'll be starting
689       switch (effective_tool) {
690 
691       // many tools share pointer-esque behavior
692       case TIMELINE_TOOL_POINTER:
693       case TIMELINE_TOOL_RIPPLE:
694       case TIMELINE_TOOL_SLIP:
695       case TIMELINE_TOOL_ROLLING:
696       case TIMELINE_TOOL_SLIDE:
697       case TIMELINE_TOOL_MENU:
698       {
699         if (track_resizing && effective_tool != TIMELINE_TOOL_MENU) {
700 
701           // if the cursor is currently hovering over a track, init track resizing
702           panel_timeline->moving_init = true;
703 
704         } else {
705 
706           // check if we're currently hovering over a clip or not
707           if (hovered_clip >= 0) {
708             Clip* clip = olive::ActiveSequence->clips.at(hovered_clip).get();
709 
710             if (clip->IsSelected()) {
711 
712               if (shift) {
713 
714                 // if the user clicks a selected clip while holding shift, deselect the clip
715                 panel_timeline->deselect_area(clip->timeline_in(), clip->timeline_out(), clip->track());
716 
717                 // if the user isn't holding alt, also deselect all of its links as well
718                 if (!alt) {
719                   for (int i=0;i<clip->linked.size();i++) {
720                     ClipPtr link = olive::ActiveSequence->clips.at(clip->linked.at(i));
721                     panel_timeline->deselect_area(link->timeline_in(), link->timeline_out(), link->track());
722                   }
723                 }
724 
725               } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER
726                           && panel_timeline->transition_select != kTransitionNone) {
727 
728                 // if the clip was selected by then the user clicked a transition, de-select the clip and its links
729                 // and select the transition only
730 
731                 panel_timeline->deselect_area(clip->timeline_in(), clip->timeline_out(), clip->track());
732 
733                 for (int i=0;i<clip->linked.size();i++) {
734                   ClipPtr link = olive::ActiveSequence->clips.at(clip->linked.at(i));
735                   panel_timeline->deselect_area(link->timeline_in(), link->timeline_out(), link->track());
736                 }
737 
738                 Selection s;
739                 s.track = clip->track();
740 
741                 // select the transition only
742                 if (panel_timeline->transition_select == kTransitionOpening && clip->opening_transition != nullptr) {
743                   s.in = clip->timeline_in();
744 
745                   if (clip->opening_transition->secondary_clip != nullptr) {
746                     s.in -= clip->opening_transition->get_true_length();
747                   }
748 
749                   s.out = clip->timeline_in() + clip->opening_transition->get_true_length();
750                 } else if (panel_timeline->transition_select == kTransitionClosing && clip->closing_transition != nullptr) {
751                   s.in = clip->timeline_out() - clip->closing_transition->get_true_length();
752                   s.out = clip->timeline_out();
753 
754                   if (clip->closing_transition->secondary_clip != nullptr) {
755                     s.out += clip->closing_transition->get_true_length();
756                   }
757                 }
758                 olive::ActiveSequence->selections.append(s);
759               }
760             } else {
761 
762               // if the clip is not already selected
763 
764               // if shift is NOT down, we change clear all current selections
765               if (!shift) {
766                 olive::ActiveSequence->selections.clear();
767               }
768 
769               Selection s;
770 
771               s.in = clip->timeline_in();
772               s.out = clip->timeline_out();
773               s.track = clip->track();
774 
775               // if user is using the pointer tool, they may be trying to select a transition
776               // check if the use is hovering over a transition
777               if (panel_timeline->tool == TIMELINE_TOOL_POINTER) {
778                 if (panel_timeline->transition_select == kTransitionOpening) {
779                   // move the selection to only select the transitoin
780                   s.out = clip->timeline_in() + clip->opening_transition->get_true_length();
781 
782                   // if the transition is a "shared" transition, adjust the selection to select both sides
783                   if (clip->opening_transition->secondary_clip != nullptr) {
784                     s.in -= clip->opening_transition->get_true_length();
785                   }
786                 } else if (panel_timeline->transition_select == kTransitionClosing) {
787                   // move the selection to only select the transitoin
788                   s.in = clip->timeline_out() - clip->closing_transition->get_true_length();
789 
790                   // if the transition is a "shared" transition, adjust the selection to select both sides
791                   if (clip->closing_transition->secondary_clip != nullptr) {
792                     s.out += clip->closing_transition->get_true_length();
793                   }
794                 }
795               }
796 
797               // add the selection to the array
798               olive::ActiveSequence->selections.append(s);
799 
800               // if the config is set to also seek with selections, do so now
801               if (olive::CurrentConfig.select_also_seeks) {
802                 panel_sequence_viewer->seek(clip->timeline_in());
803               }
804 
805               // if alt is not down, select links (provided we're not selecting transitions)
806               if (!alt && panel_timeline->transition_select == kTransitionNone) {
807 
808                 for (int i=0;i<clip->linked.size();i++) {
809 
810                   Clip* link = olive::ActiveSequence->clips.at(clip->linked.at(i)).get();
811 
812                   // check if the clip is already selected
813                   if (!link->IsSelected()) {
814                     Selection ss;
815                     ss.in = link->timeline_in();
816                     ss.out = link->timeline_out();
817                     ss.track = link->track();
818                     olive::ActiveSequence->selections.append(ss);
819                   }
820 
821                 }
822 
823               }
824             }
825 
826             // authorize the starting of a move action if the mouse moves after this
827             if (effective_tool != TIMELINE_TOOL_MENU) {
828               panel_timeline->moving_init = true;
829             }
830 
831           } else {
832 
833             // if the user did not click a clip at all, we start a rectangle selection
834 
835             if (!shift) {
836               olive::ActiveSequence->selections.clear();
837             }
838 
839             panel_timeline->rect_select_init = true;
840           }
841 
842           // update everything
843           update_ui(false);
844         }
845       }
846         break;
847       case TIMELINE_TOOL_HAND:
848 
849         // initiate moving with the hand tool
850         panel_timeline->hand_moving = true;
851 
852         break;
853       case TIMELINE_TOOL_EDIT:
854 
855         // if the config is set to seek with the edit tool, do so now
856         if (olive::CurrentConfig.edit_tool_also_seeks) {
857           panel_sequence_viewer->seek(panel_timeline->drag_frame_start);
858         }
859 
860         // initiate selecting
861         panel_timeline->selecting = true;
862 
863         break;
864       case TIMELINE_TOOL_RAZOR:
865       {
866 
867         // initiate razor tool
868         panel_timeline->splitting = true;
869 
870         // add this track as a track being split by the razor
871         panel_timeline->split_tracks.append(panel_timeline->drag_track_start);
872 
873         update_ui(false);
874       }
875         break;
876       case TIMELINE_TOOL_TRANSITION:
877       {
878 
879         // if there is a clip to run the transition tool on, initiate the transition tool
880         if (panel_timeline->transition_tool_open_clip > -1
881               || panel_timeline->transition_tool_close_clip > -1) {
882           panel_timeline->transition_tool_init = true;
883         }
884 
885       }
886         break;
887       }
888     }
889   }
890 }
891 
make_room_for_transition(ComboAction * ca,Clip * c,int type,long transition_start,long transition_end,bool delete_old_transitions,long timeline_in=-1,long timeline_out=-1)892 void make_room_for_transition(ComboAction* ca,
893                               Clip* c,
894                               int type,
895                               long transition_start,
896                               long transition_end,
897                               bool delete_old_transitions,
898                               long timeline_in = -1,
899                               long timeline_out = -1) {
900   // it's possible to specify other in/out points for the clip, but default behavior is to use the ones existing
901   if (timeline_in < 0) {
902     timeline_in = c->timeline_in();
903   }
904   if (timeline_out < 0) {
905     timeline_out = c->timeline_out();
906   }
907 
908   // make room for transition
909   if (type == kTransitionOpening) {
910     if (delete_old_transitions && c->opening_transition != nullptr) {
911       ca->append(new DeleteTransitionCommand(c->opening_transition));
912     }
913     if (c->closing_transition != nullptr) {
914       if (transition_end >= c->timeline_out()) {
915         ca->append(new DeleteTransitionCommand(c->closing_transition));
916       } else if (transition_end > c->timeline_out() - c->closing_transition->get_true_length()) {
917         ca->append(new ModifyTransitionCommand(c->closing_transition, c->timeline_out() - transition_end));
918       }
919     }
920   } else {
921     if (delete_old_transitions && c->closing_transition != nullptr) {
922       ca->append(new DeleteTransitionCommand(c->closing_transition));
923     }
924     if (c->opening_transition != nullptr) {
925       if (transition_start <= c->timeline_in()) {
926         ca->append(new DeleteTransitionCommand(c->opening_transition));
927       } else if (transition_start < c->timeline_in() + c->opening_transition->get_true_length()) {
928         ca->append(new ModifyTransitionCommand(c->opening_transition, transition_start - c->timeline_in()));
929       }
930     }
931   }
932 }
933 
VerifyTransitionsAfterCreating(ComboAction * ca,Clip * open,Clip * close,long transition_start,long transition_end)934 void VerifyTransitionsAfterCreating(ComboAction* ca, Clip* open, Clip* close, long transition_start, long transition_end) {
935   // in case the user made the transition larger than the clips, we're going to delete everything under
936   // the transition ghost and extend the clips to the transition's coordinates as necessary
937 
938   if (open == nullptr && close == nullptr) {
939     qWarning() << "VerifyTransitionsAfterCreating() called with two null clips";
940     return;
941   }
942 
943   // determine whether this is a "shared" transition between to clips or not
944   bool shared_transition = (open != nullptr && close != nullptr);
945 
946   int track = 0;
947 
948   // first we set the clips to "undeletable" so they aren't affected by delete_areas_and_relink()
949   if (open != nullptr) {
950     open->undeletable = true;
951     track = open->track();
952   }
953   if (close != nullptr) {
954     close->undeletable = true;
955     track = close->track();
956   }
957 
958   // set the area to delete to the transition's coordinates and clear it
959   QVector<Selection> areas;
960   Selection s;
961   s.in = transition_start;
962   s.out = transition_end;
963   s.track = track;
964   areas.append(s);
965   panel_timeline->delete_areas_and_relink(ca, areas, false);
966 
967   // set the clips back to undeletable now that we're done
968   if (open != nullptr) {
969     open->undeletable = false;
970   }
971   if (close != nullptr) {
972     close->undeletable = false;
973   }
974 
975   // loop through both kinds of transition
976   for (int t=kTransitionOpening;t<=kTransitionClosing;t++) {
977 
978     Clip* clip_ref = (t == kTransitionOpening) ? open : close;
979 
980     // if we have an opening transition:
981     if (clip_ref != nullptr) {
982 
983       // make_room_for_transition will adjust the opposite transition to make space for this one,
984       // for example if the user makes an opening transition that overlaps the closing transition, it'll resize
985       // or even delete the closing transition if necessary (and vice versa)
986 
987       make_room_for_transition(ca, clip_ref, t, transition_start, transition_end, true);
988 
989       // check if the transition coordinates require the clip to be resized
990       if (transition_start < clip_ref->timeline_in() || transition_end > clip_ref->timeline_out()) {
991 
992         long new_in, new_out;
993 
994         if (t == kTransitionOpening) {
995 
996           // if the transition is shared, it doesn't matter if the transition extend beyond the in point since
997           // that'll be "absorbed" by the other clip
998           new_in = (shared_transition) ? open->timeline_in() : qMin(transition_start, open->timeline_in());
999 
1000           new_out = qMax(transition_end, open->timeline_out());
1001 
1002         } else {
1003 
1004           new_in = qMin(transition_start, close->timeline_in());
1005 
1006           // if the transition is shared, it doesn't matter if the transition extend beyond the out point since
1007           // that'll be "absorbed" by the other clip
1008           new_out = (shared_transition) ? close->timeline_out() : qMax(transition_end, close->timeline_out());
1009 
1010         }
1011 
1012 
1013 
1014         clip_ref->move(ca,
1015                        new_in,
1016                        new_out,
1017                        clip_ref->clip_in() - (clip_ref->timeline_in() - new_in),
1018                        clip_ref->track());
1019       }
1020     }
1021   }
1022 }
1023 
mouseReleaseEvent(QMouseEvent * event)1024 void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) {
1025   QToolTip::hideText();
1026   if (olive::ActiveSequence != nullptr) {
1027     bool alt = (event->modifiers() & Qt::AltModifier);
1028     bool shift = (event->modifiers() & Qt::ShiftModifier);
1029     bool ctrl = (event->modifiers() & Qt::ControlModifier);
1030 
1031     if (event->button() == Qt::LeftButton) {
1032       ComboAction* ca = new ComboAction();
1033       bool push_undo = false;
1034 
1035       if (panel_timeline->creating) {
1036         if (panel_timeline->ghosts.size() > 0) {
1037           const Ghost& g = panel_timeline->ghosts.at(0);
1038 
1039           if (panel_timeline->creating_object == ADD_OBJ_AUDIO) {
1040             olive::MainWindow->statusBar()->clearMessage();
1041             panel_sequence_viewer->cue_recording(qMin(g.in, g.out), qMax(g.in, g.out), g.track);
1042             panel_timeline->creating = false;
1043           } else if (g.in != g.out) {
1044             ClipPtr c = std::make_shared<Clip>(olive::ActiveSequence.get());
1045             c->set_media(nullptr, 0);
1046             c->set_timeline_in(qMin(g.in, g.out));
1047             c->set_timeline_out(qMax(g.in, g.out));
1048             c->set_clip_in(0);
1049             c->set_color(192, 192, 64);
1050             c->set_track(g.track);
1051 
1052             if (ctrl) {
1053               insert_clips(ca);
1054             } else {
1055               Selection s;
1056               s.in = c->timeline_in();
1057               s.out = c->timeline_out();
1058               s.track = c->track();
1059               QVector<Selection> areas;
1060               areas.append(s);
1061               panel_timeline->delete_areas_and_relink(ca, areas, false);
1062             }
1063 
1064             QVector<ClipPtr> add;
1065             add.append(c);
1066             ca->append(new AddClipCommand(olive::ActiveSequence.get(), add));
1067 
1068             if (c->track() < 0 && olive::CurrentConfig.add_default_effects_to_clips) {
1069               // default video effects (before custom effects)
1070               c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_TRANSFORM, EFFECT_TYPE_EFFECT)));
1071             }
1072 
1073             switch (panel_timeline->creating_object) {
1074             case ADD_OBJ_TITLE:
1075               c->set_name(tr("Title"));
1076               c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_RICHTEXT, EFFECT_TYPE_EFFECT)));
1077               break;
1078             case ADD_OBJ_SOLID:
1079               c->set_name(tr("Solid Color"));
1080               c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_SOLID, EFFECT_TYPE_EFFECT)));
1081               break;
1082             case ADD_OBJ_BARS:
1083             {
1084               c->set_name(tr("Bars"));
1085               EffectPtr e = Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_SOLID, EFFECT_TYPE_EFFECT));
1086 
1087               // Auto-select bars
1088               SolidEffect* solid_effect = static_cast<SolidEffect*>(e.get());
1089               solid_effect->SetType(SolidEffect::SOLID_TYPE_BARS);
1090 
1091               c->effects.append(e);
1092             }
1093               break;
1094             case ADD_OBJ_TONE:
1095               c->set_name(tr("Tone"));
1096               c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_TONE, EFFECT_TYPE_EFFECT)));
1097               break;
1098             case ADD_OBJ_NOISE:
1099               c->set_name(tr("Noise"));
1100               c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_NOISE, EFFECT_TYPE_EFFECT)));
1101               break;
1102             }
1103 
1104             if (c->track() >= 0 && olive::CurrentConfig.add_default_effects_to_clips) {
1105               // default audio effects (after custom effects)
1106               c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_VOLUME, EFFECT_TYPE_EFFECT)));
1107               c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_PAN, EFFECT_TYPE_EFFECT)));
1108             }
1109 
1110             push_undo = true;
1111 
1112             if (!shift) {
1113               panel_timeline->creating = false;
1114             }
1115           }
1116         }
1117       } else if (panel_timeline->moving_proc) {
1118 
1119         // see if any clips actually moved, otherwise we don't need to do any processing
1120         // (perhaps this could be moved further up to cover more actions?)
1121 
1122         bool process_moving = false;
1123 
1124         for (int i=0;i<panel_timeline->ghosts.size();i++) {
1125           const Ghost& g = panel_timeline->ghosts.at(i);
1126           if (g.in != g.old_in
1127               || g.out != g.old_out
1128               || g.clip_in != g.old_clip_in
1129               || g.track != g.old_track) {
1130             process_moving = true;
1131             break;
1132           }
1133         }
1134 
1135         if (process_moving) {
1136           const Ghost& first_ghost = panel_timeline->ghosts.at(0);
1137 
1138           // start a ripple movement
1139           if (panel_timeline->tool == TIMELINE_TOOL_RIPPLE) {
1140 
1141             // ripple_length becomes the length/number of frames we trimmed
1142             // ripple_point is the "axis" around which we move all the clips, any clips after it get moved
1143             long ripple_length;
1144             long ripple_point = LONG_MAX;
1145 
1146             if (panel_timeline->trim_type == TRIM_IN) {
1147 
1148               // it's assumed that all the ghosts rippled by the same length, so we just take the difference of the
1149               // first ghost here
1150               ripple_length = first_ghost.old_in - first_ghost.in;
1151 
1152               // for in trimming movements we also move the selections forward (unnecessary for out trimming since
1153               // the selected clips more or less stay in the same place)
1154               for (int i=0;i<olive::ActiveSequence->selections.size();i++) {
1155                 olive::ActiveSequence->selections[i].in += ripple_length;
1156                 olive::ActiveSequence->selections[i].out += ripple_length;
1157               }
1158             } else {
1159 
1160               // use the out points for length if the user trimmed the out point
1161               ripple_length = first_ghost.old_out - panel_timeline->ghosts.at(0).out;
1162 
1163             }
1164 
1165             // build a list of "ignore clips" that won't get affected by ripple_clips() below
1166             QVector<int> ignore_clips;
1167             for (int i=0;i<panel_timeline->ghosts.size();i++) {
1168               const Ghost& g = panel_timeline->ghosts.at(i);
1169 
1170               // for the same reason that we pushed selections forward above, for in trimming,
1171               // we push the ghosts forward here
1172               if (panel_timeline->trim_type == TRIM_IN) {
1173                 ignore_clips.append(g.clip);
1174                 panel_timeline->ghosts[i].in += ripple_length;
1175                 panel_timeline->ghosts[i].out += ripple_length;
1176               }
1177 
1178               // find the earliest ripple point
1179               long comp_point = (panel_timeline->trim_type == TRIM_IN) ? g.old_in : g.old_out;
1180               ripple_point = qMin(ripple_point, comp_point);
1181             }
1182 
1183             // if this was out trimming, flip the direction of the ripple
1184             if (panel_timeline->trim_type == TRIM_OUT) ripple_length = -ripple_length;
1185 
1186             // finally, ripple everything
1187             ripple_clips(ca, olive::ActiveSequence.get(), ripple_point, ripple_length, ignore_clips);
1188           }
1189 
1190           if (panel_timeline->tool == TIMELINE_TOOL_POINTER
1191               && (event->modifiers() & Qt::AltModifier)
1192               && panel_timeline->trim_target == -1) {
1193 
1194             // if the user was holding alt (and not trimming), we duplicate clips rather than move them
1195             QVector<int> old_clips;
1196             QVector<ClipPtr> new_clips;
1197             QVector<Selection> delete_areas;
1198             for (int i=0;i<panel_timeline->ghosts.size();i++) {
1199               const Ghost& g = panel_timeline->ghosts.at(i);
1200               if (g.old_in != g.in || g.old_out != g.out || g.track != g.old_track || g.clip_in != g.old_clip_in) {
1201 
1202                 // create copy of clip
1203                 ClipPtr c = olive::ActiveSequence->clips.at(g.clip)->copy(olive::ActiveSequence.get());
1204 
1205                 c->set_timeline_in(g.in);
1206                 c->set_timeline_out(g.out);
1207                 c->set_track(g.track);
1208 
1209                 Selection s;
1210                 s.in = g.in;
1211                 s.out = g.out;
1212                 s.track = g.track;
1213                 delete_areas.append(s);
1214 
1215                 old_clips.append(g.clip);
1216                 new_clips.append(c);
1217 
1218               }
1219             }
1220 
1221             if (new_clips.size() > 0) {
1222 
1223               // delete anything under the new clips
1224               panel_timeline->delete_areas_and_relink(ca, delete_areas, false);
1225 
1226               // relink duplicated clips
1227               panel_timeline->relink_clips_using_ids(old_clips, new_clips);
1228 
1229               // add them
1230               ca->append(new AddClipCommand(olive::ActiveSequence.get(), new_clips));
1231 
1232             }
1233 
1234           } else {
1235 
1236             // if we're not holding alt, this will just be a move
1237 
1238             // if the user is holding ctrl, perform an insert rather than an overwrite
1239             if (panel_timeline->tool == TIMELINE_TOOL_POINTER && ctrl) {
1240 
1241               insert_clips(ca);
1242 
1243             } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->tool == TIMELINE_TOOL_SLIDE) {
1244 
1245               // if the user is not holding ctrl, we start standard clip movement
1246 
1247               // delete everything under the new clips
1248               QVector<Selection> delete_areas;
1249               for (int i=0;i<panel_timeline->ghosts.size();i++) {
1250                 // step 1 - set clips that are moving to "undeletable" (to avoid step 2 deleting any part of them)
1251                 const Ghost& g = panel_timeline->ghosts.at(i);
1252 
1253                 // set clip to undeletable so it's unaffected by delete_areas_and_relink() below
1254                 olive::ActiveSequence->clips.at(g.clip)->undeletable = true;
1255 
1256                 // if the user was moving a transition make sure they're undeletable too
1257                 if (g.transition != nullptr) {
1258                   g.transition->parent_clip->undeletable = true;
1259                   if (g.transition->secondary_clip != nullptr) {
1260                     g.transition->secondary_clip->undeletable = true;
1261                   }
1262                 }
1263 
1264                 // set area to delete
1265                 Selection s;
1266                 s.in = g.in;
1267                 s.out = g.out;
1268                 s.track = g.track;
1269                 delete_areas.append(s);
1270               }
1271 
1272               panel_timeline->delete_areas_and_relink(ca, delete_areas, false);
1273 
1274               // clean up, i.e. make everything not undeletable again
1275               for (int i=0;i<panel_timeline->ghosts.size();i++) {
1276                 const Ghost& g = panel_timeline->ghosts.at(i);
1277                 olive::ActiveSequence->clips.at(g.clip)->undeletable = false;
1278 
1279                 if (g.transition != nullptr) {
1280                   g.transition->parent_clip->undeletable = false;
1281                   if (g.transition->secondary_clip != nullptr) {
1282                     g.transition->secondary_clip->undeletable = false;
1283                   }
1284                 }
1285               }
1286             }
1287 
1288             // finally, perform actual movement of clips
1289             for (int i=0;i<panel_timeline->ghosts.size();i++) {
1290               Ghost& g = panel_timeline->ghosts[i];
1291 
1292               Clip* c = olive::ActiveSequence->clips.at(g.clip).get();
1293 
1294               if (g.transition == nullptr) {
1295 
1296                 // if this was a clip rather than a transition
1297 
1298                 c->move(ca, (g.in - g.old_in), (g.out - g.old_out), (g.clip_in - g.old_clip_in), (g.track - g.old_track), false, true);
1299 
1300               } else {
1301 
1302                 // if the user was moving a transition
1303 
1304                 bool is_opening_transition = (g.transition == c->opening_transition);
1305                 long new_transition_length = g.out - g.in;
1306                 if (g.transition->secondary_clip != nullptr) new_transition_length >>= 1;
1307                 ca->append(
1308                       new ModifyTransitionCommand(is_opening_transition ? c->opening_transition : c->closing_transition,
1309                                                        new_transition_length)
1310                       );
1311 
1312                 long clip_length = c->length();
1313 
1314                 if (g.transition->secondary_clip != nullptr) {
1315 
1316                   // if this is a shared transition
1317                   if (g.in != g.old_in && g.trim_type == TRIM_NONE) {
1318                     long movement = g.in - g.old_in;
1319 
1320                     // check if the transition is going to extend the out point (opening clip)
1321                     long timeline_out_movement = 0;
1322                     if (g.out > g.transition->parent_clip->timeline_out()) {
1323                       timeline_out_movement = g.out - g.transition->parent_clip->timeline_out();
1324                     }
1325 
1326                     // check if the transition is going to extend the in point (closing clip)
1327                     long timeline_in_movement = 0;
1328                     if (g.in < g.transition->secondary_clip->timeline_in()) {
1329                       timeline_in_movement = g.in - g.transition->secondary_clip->timeline_in();
1330                     }
1331 
1332                     g.transition->parent_clip->move(ca, movement, timeline_out_movement, movement, 0, false, true);
1333                     g.transition->secondary_clip->move(ca, timeline_in_movement, movement, timeline_in_movement, 0, false, true);
1334 
1335                     make_room_for_transition(ca, g.transition->parent_clip, kTransitionOpening, g.in, g.out, false);
1336                     make_room_for_transition(ca, g.transition->secondary_clip, kTransitionClosing, g.in, g.out, false);
1337 
1338                   }
1339 
1340                 } else if (is_opening_transition) {
1341 
1342                   if (g.in != g.old_in) {
1343                     // if transition is going to make the clip bigger, make the clip bigger
1344 
1345                     // check if the transition is going to extend the out point
1346                     long timeline_out_movement = 0;
1347                     if (g.out > g.transition->parent_clip->timeline_out()) {
1348                       timeline_out_movement = g.out - g.transition->parent_clip->timeline_out();
1349                     }
1350 
1351                     c->move(ca, (g.in - g.old_in), timeline_out_movement, (g.clip_in - g.old_clip_in), 0, false, true);
1352                     clip_length -= (g.in - g.old_in);
1353                   }
1354 
1355                   make_room_for_transition(ca, c, kTransitionOpening, g.in, g.out, false);
1356 
1357                 } else {
1358 
1359                   if (g.out != g.old_out) {
1360 
1361                     // check if the transition is going to extend the in point
1362                     long timeline_in_movement = 0;
1363                     if (g.in < g.transition->parent_clip->timeline_in()) {
1364                       timeline_in_movement = g.in - g.transition->parent_clip->timeline_in();
1365                     }
1366 
1367                     // if transition is going to make the clip bigger, make the clip bigger
1368                     c->move(ca, timeline_in_movement, (g.out - g.old_out), timeline_in_movement, 0, false, true);
1369                     clip_length += (g.out - g.old_out);
1370                   }
1371 
1372                   make_room_for_transition(ca, c, kTransitionClosing, g.in, g.out, false);
1373 
1374                 }
1375               }
1376             }
1377 
1378             // time to verify the transitions of moved clips
1379             for (int i=0;i<panel_timeline->ghosts.size();i++) {
1380               const Ghost& g = panel_timeline->ghosts.at(i);
1381 
1382               // only applies to moving clips, transitions are verified above instead
1383               if (g.transition == nullptr) {
1384                 ClipPtr c = olive::ActiveSequence->clips.at(g.clip);
1385 
1386                 long new_clip_length = g.out - g.in;
1387 
1388                 // using a for loop between constants to repeat the same steps for the opening and closing transitions
1389                 for (int t=kTransitionOpening;t<=kTransitionClosing;t++) {
1390 
1391                   TransitionPtr transition = (t == kTransitionOpening) ? c->opening_transition : c->closing_transition;
1392 
1393                   // check the whether the clip has a transition here
1394                   if (transition != nullptr) {
1395 
1396                     // if the new clip size exceeds the opening transition's length, resize the transition
1397                     if (new_clip_length < transition->get_true_length()) {
1398                       ca->append(new ModifyTransitionCommand(transition, new_clip_length));
1399                     }
1400 
1401                     // check if the transition is a shared transition (it'll never have a secondary clip if it isn't)
1402                     if (transition->secondary_clip != nullptr) {
1403 
1404                       // check if the transition's "edge" is going to move
1405                       if ((t == kTransitionOpening && g.in != g.old_in)
1406                           || (t == kTransitionClosing && g.out != g.old_out)) {
1407 
1408                         // if we're here, this clip shares its opening transition as the closing transition of another
1409                         // clip (or vice versa), and the in point is moving, so we may have to account for this
1410 
1411                         // the other clip sharing this transition may be moving as well, meaning we don't have to do
1412                         // anything
1413 
1414                         bool split = true;
1415 
1416                         // loop through ghosts to find out
1417 
1418                         // for a shared transition, the secondary_clip will always be the closing transition side and
1419                         // the parent_clip will always be the opening transition side
1420                         Clip* search_clip = (t == kTransitionOpening)
1421                                                       ? transition->secondary_clip : transition->parent_clip;
1422 
1423                         for (int j=0;j<panel_timeline->ghosts.size();j++) {
1424                           const Ghost& other_clip_ghost = panel_timeline->ghosts.at(j);
1425 
1426                           if (olive::ActiveSequence->clips.at(other_clip_ghost.clip).get() == search_clip) {
1427 
1428                             // we found the other clip in the current ghosts/selections
1429 
1430                             // see if it's destination edge will be equal to this ghost's edge (in which case the
1431                             // transition doesn't need to change)
1432                             //
1433                             // also only do this if j is less than i, because it only needs to happen once and chances are
1434                             // the other clip already
1435 
1436                             bool edges_still_touch;
1437                             if (t == kTransitionOpening) {
1438                               edges_still_touch = (other_clip_ghost.out == g.in);
1439                             } else {
1440                               edges_still_touch = (other_clip_ghost.in == g.out);
1441                             }
1442 
1443                             if (edges_still_touch || j < i) {
1444                               split = false;
1445                             }
1446 
1447                             break;
1448                           }
1449                         }
1450 
1451                         if (split) {
1452                           // separate shared transition into one transition for each clip
1453 
1454                           if (t == kTransitionOpening) {
1455 
1456                             // set transition to single-clip mode
1457                             ca->append(new SetPointer(reinterpret_cast<void**>(&transition->secondary_clip),
1458                                                       nullptr));
1459 
1460                             // create duplicate transition for other clip
1461                             ca->append(new AddTransitionCommand(nullptr,
1462                                                                 transition->secondary_clip,
1463                                                                 transition,
1464                                                                 nullptr,
1465                                                                 0));
1466 
1467                           } else {
1468 
1469                             // set transition to single-clip mode
1470                             ca->append(new SetPointer(reinterpret_cast<void**>(&transition->secondary_clip),
1471                                                       nullptr));
1472 
1473                             // that transition will now attach to the other clip, so we duplicate it for this one
1474 
1475                             // create duplicate transition for this clip
1476                             ca->append(new AddTransitionCommand(nullptr,
1477                                                                 transition->secondary_clip,
1478                                                                 transition,
1479                                                                 nullptr,
1480                                                                 0));
1481 
1482                           }
1483                         }
1484 
1485                       }
1486                     }
1487                   }
1488                 }
1489               }
1490             }
1491           }
1492           push_undo = true;
1493         }
1494       } else if (panel_timeline->selecting || panel_timeline->rect_select_proc) {
1495       } else if (panel_timeline->transition_tool_proc) {
1496         const Ghost& g = panel_timeline->ghosts.at(0);
1497 
1498         // if the transition is greater than 0 length (if it is 0, we make nothing)
1499         if (g.in != g.out) {
1500 
1501           // get transition coordinates on the timeline
1502           long transition_start = qMin(g.in, g.out);
1503           long transition_end = qMax(g.in, g.out);
1504 
1505           // get clip references from tool's cached data
1506           Clip* open = (panel_timeline->transition_tool_open_clip > -1)
1507               ? olive::ActiveSequence->clips.at(panel_timeline->transition_tool_open_clip).get()
1508               : nullptr;
1509 
1510           Clip* close = (panel_timeline->transition_tool_close_clip > -1)
1511               ? olive::ActiveSequence->clips.at(panel_timeline->transition_tool_close_clip).get()
1512               : nullptr;
1513 
1514 
1515 
1516           // if it's shared, the transition length is halved (one half for each clip will result in the full length)
1517           long transition_length = transition_end - transition_start;
1518           if (open != nullptr && close != nullptr) {
1519             transition_length /= 2;
1520           }
1521 
1522           VerifyTransitionsAfterCreating(ca, open, close, transition_start, transition_end);
1523 
1524           // finally, add the transition to these clips
1525           ca->append(new AddTransitionCommand(open,
1526                                               close,
1527                                               nullptr,
1528                                               panel_timeline->transition_tool_meta,
1529                                               transition_length));
1530 
1531           push_undo = true;
1532         }
1533       } else if (panel_timeline->splitting) {
1534         bool split = false;
1535         for (int i=0;i<panel_timeline->split_tracks.size();i++) {
1536           int split_index = getClipIndexFromCoords(panel_timeline->drag_frame_start, panel_timeline->split_tracks.at(i));
1537           if (split_index > -1 && panel_timeline->split_clip_and_relink(ca, split_index, panel_timeline->drag_frame_start, !alt)) {
1538             split = true;
1539           }
1540         }
1541         if (split) {
1542           push_undo = true;
1543         }
1544         panel_timeline->split_cache.clear();
1545       }
1546 
1547       // remove duplicate selections
1548       panel_timeline->clean_up_selections(olive::ActiveSequence->selections);
1549 
1550       if (selection_command != nullptr) {
1551         selection_command->new_data = olive::ActiveSequence->selections;
1552         ca->append(selection_command);
1553         selection_command = nullptr;
1554         push_undo = true;
1555       }
1556 
1557       if (push_undo) {
1558         olive::UndoStack.push(ca);
1559       } else {
1560         delete ca;
1561       }
1562 
1563       // destroy all ghosts
1564       panel_timeline->ghosts.clear();
1565 
1566       // clear split tracks
1567       panel_timeline->split_tracks.clear();
1568 
1569       panel_timeline->selecting = false;
1570       panel_timeline->moving_proc = false;
1571       panel_timeline->moving_init = false;
1572       panel_timeline->splitting = false;
1573       panel_timeline->snapped = false;
1574       panel_timeline->rect_select_init = false;
1575       panel_timeline->rect_select_proc = false;
1576       panel_timeline->transition_tool_init = false;
1577       panel_timeline->transition_tool_proc = false;
1578       pre_clips.clear();
1579       post_clips.clear();
1580 
1581       update_ui(true);
1582     }
1583     panel_timeline->hand_moving = false;
1584   }
1585 }
1586 
init_ghosts()1587 void TimelineWidget::init_ghosts() {
1588   for (int i=0;i<panel_timeline->ghosts.size();i++) {
1589     Ghost& g = panel_timeline->ghosts[i];
1590     ClipPtr c = olive::ActiveSequence->clips.at(g.clip);
1591 
1592     g.track = g.old_track = c->track();
1593     g.clip_in = g.old_clip_in = c->clip_in();
1594 
1595     if (panel_timeline->tool == TIMELINE_TOOL_SLIP) {
1596       g.clip_in = g.old_clip_in = c->clip_in(true);
1597       g.in = g.old_in = c->timeline_in(true);
1598       g.out = g.old_out = c->timeline_out(true);
1599       g.ghost_length = g.old_out - g.old_in;
1600     } else if (g.transition == nullptr) {
1601       // this ghost is for a clip
1602       g.in = g.old_in = c->timeline_in();
1603       g.out = g.old_out = c->timeline_out();
1604       g.ghost_length = g.old_out - g.old_in;
1605     } else if (g.transition == c->opening_transition) {
1606       g.in = g.old_in = c->timeline_in(true);
1607       g.ghost_length = c->opening_transition->get_length();
1608       g.out = g.old_out = g.in + g.ghost_length;
1609     } else if (g.transition == c->closing_transition) {
1610       g.out = g.old_out = c->timeline_out(true);
1611       g.ghost_length = c->closing_transition->get_length();
1612       g.in = g.old_in = g.out - g.ghost_length;
1613       g.clip_in = g.old_clip_in = c->clip_in() + c->length() - c->closing_transition->get_true_length();
1614     }
1615 
1616     // used for trim ops
1617     g.media_length = c->media_length();
1618   }
1619   for (int i=0;i<olive::ActiveSequence->selections.size();i++) {
1620     Selection& s = olive::ActiveSequence->selections[i];
1621     s.old_in = s.in;
1622     s.old_out = s.out;
1623     s.old_track = s.track;
1624   }
1625 }
1626 
validate_transitions(Clip * c,int transition_type,long & frame_diff)1627 void validate_transitions(Clip* c, int transition_type, long& frame_diff) {
1628   long validator;
1629 
1630   if (transition_type == kTransitionOpening) {
1631     // prevent from going below 0 on the timeline
1632     validator = c->timeline_in() + frame_diff;
1633     if (validator < 0) frame_diff -= validator;
1634 
1635     // prevent from going below 0 for the media
1636     validator = c->clip_in() + frame_diff;
1637     if (validator < 0) frame_diff -= validator;
1638 
1639     // prevent transition from exceeding media length
1640     validator -= c->media_length();
1641     if (validator > 0) frame_diff -= validator;
1642   } else {
1643     // prevent from going below 0 on the timeline
1644     validator = c->timeline_out() + frame_diff;
1645     if (validator < 0) frame_diff -= validator;
1646 
1647     // prevent from going below 0 for the media
1648     validator = c->clip_in() + c->length() + frame_diff;
1649     if (validator < 0) frame_diff -= validator;
1650 
1651     // prevent transition from exceeding media length
1652     validator -= c->media_length();
1653     if (validator > 0) frame_diff -= validator;
1654   }
1655 }
1656 
update_ghosts(const QPoint & mouse_pos,bool lock_frame)1657 void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) {
1658   int effective_tool = panel_timeline->tool;
1659   if (panel_timeline->importing || panel_timeline->creating) effective_tool = TIMELINE_TOOL_POINTER;
1660 
1661   int mouse_track = getTrackFromScreenPoint(mouse_pos.y());
1662   long frame_diff = (lock_frame) ? 0 : panel_timeline->getTimelineFrameFromScreenPoint(mouse_pos.x()) - panel_timeline->drag_frame_start;
1663   int track_diff = ((effective_tool == TIMELINE_TOOL_SLIDE || panel_timeline->transition_select != kTransitionNone) && !panel_timeline->importing) ? 0 : mouse_track - panel_timeline->drag_track_start;
1664   long validator;
1665   long earliest_in_point = LONG_MAX;
1666 
1667   // first try to snap
1668   long fm;
1669 
1670   if (effective_tool != TIMELINE_TOOL_SLIP) {
1671     // slipping doesn't move the clips so we don't bother snapping for it
1672     for (int i=0;i<panel_timeline->ghosts.size();i++) {
1673       const Ghost& g = panel_timeline->ghosts.at(i);
1674 
1675       // snap ghost's in point
1676       if ((panel_timeline->tool != TIMELINE_TOOL_TRANSITION && panel_timeline->trim_target == -1)
1677           || g.trim_type == TRIM_IN
1678           || panel_timeline->transition_tool_open_clip > -1) {
1679         fm = g.old_in + frame_diff;
1680         if (panel_timeline->snap_to_timeline(&fm, true, true, true)) {
1681           frame_diff = fm - g.old_in;
1682           break;
1683         }
1684       }
1685 
1686       // snap ghost's out point
1687       if ((panel_timeline->tool != TIMELINE_TOOL_TRANSITION && panel_timeline->trim_target == -1)
1688           || g.trim_type == TRIM_OUT
1689           || panel_timeline->transition_tool_close_clip > -1) {
1690         fm = g.old_out + frame_diff;
1691         if (panel_timeline->snap_to_timeline(&fm, true, true, true)) {
1692           frame_diff = fm - g.old_out;
1693           break;
1694         }
1695       }
1696 
1697       // if the ghost is attached to a clip, snap its markers too
1698       if (panel_timeline->trim_target == -1 && g.clip >= 0 && panel_timeline->tool != TIMELINE_TOOL_TRANSITION) {
1699         ClipPtr c = olive::ActiveSequence->clips.at(g.clip);
1700         for (int j=0;j<c->get_markers().size();j++) {
1701           long marker_real_time = c->get_markers().at(j).frame + c->timeline_in() - c->clip_in();
1702           fm = marker_real_time + frame_diff;
1703           if (panel_timeline->snap_to_timeline(&fm, true, true, true)) {
1704             frame_diff = fm - marker_real_time;
1705             break;
1706           }
1707         }
1708       }
1709     }
1710   }
1711 
1712   bool clips_are_movable = (effective_tool == TIMELINE_TOOL_POINTER || effective_tool == TIMELINE_TOOL_SLIDE);
1713 
1714   // validate ghosts
1715   long temp_frame_diff = frame_diff; // cache to see if we change it (thus cancelling any snap)
1716   for (int i=0;i<panel_timeline->ghosts.size();i++) {
1717     const Ghost& g = panel_timeline->ghosts.at(i);
1718     Clip* c = nullptr;
1719     if (g.clip != -1) {
1720       c = olive::ActiveSequence->clips.at(g.clip).get();
1721     }
1722 
1723     const FootageStream* ms = nullptr;
1724     if (g.clip != -1 && c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) {
1725       ms = c->media_stream();
1726     }
1727 
1728     // validate ghosts for trimming
1729     if (panel_timeline->creating) {
1730       // i feel like we might need something here but we haven't so far?
1731     } else if (effective_tool == TIMELINE_TOOL_SLIP) {
1732       if ((c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE)
1733           || (ms != nullptr && !ms->infinite_length)) {
1734         // prevent slip moving a clip below 0 clip_in
1735         validator = g.old_clip_in - frame_diff;
1736         if (validator < 0) frame_diff += validator;
1737 
1738         // prevent slip moving clip beyond media length
1739         validator += g.ghost_length;
1740         if (validator > g.media_length) frame_diff += validator - g.media_length;
1741       }
1742     } else if (g.trim_type != TRIM_NONE) {
1743       if (g.trim_type == TRIM_IN) {
1744         // prevent clip/transition length from being less than 1 frame long
1745         validator = g.ghost_length - frame_diff;
1746         if (validator < 1) frame_diff -= (1 - validator);
1747 
1748         // prevent timeline in from going below 0
1749         if (effective_tool != TIMELINE_TOOL_RIPPLE) {
1750           validator = g.old_in + frame_diff;
1751           if (validator < 0) frame_diff -= validator;
1752         }
1753 
1754         // prevent clip_in from going below 0
1755         if ((c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE)
1756             || (ms != nullptr && !ms->infinite_length)) {
1757           validator = g.old_clip_in + frame_diff;
1758           if (validator < 0) frame_diff -= validator;
1759         }
1760       } else {
1761         // prevent clip length from being less than 1 frame long
1762         validator = g.ghost_length + frame_diff;
1763         if (validator < 1) frame_diff += (1 - validator);
1764 
1765         // prevent clip length exceeding media length
1766         if ((c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE)
1767             || (ms != nullptr && !ms->infinite_length)) {
1768           validator = g.old_clip_in + g.ghost_length + frame_diff;
1769           if (validator > g.media_length) frame_diff -= validator - g.media_length;
1770         }
1771       }
1772 
1773       // prevent dual transition from going below 0 on the primary or media length on the secondary
1774       if (g.transition != nullptr && g.transition->secondary_clip != nullptr) {
1775         Clip* otc = g.transition->parent_clip;
1776         Clip* ctc = g.transition->secondary_clip;
1777 
1778         if (g.trim_type == TRIM_IN) {
1779           frame_diff -= g.transition->get_true_length();
1780         } else {
1781           frame_diff += g.transition->get_true_length();
1782         }
1783 
1784         validate_transitions(otc, kTransitionOpening, frame_diff);
1785         validate_transitions(ctc, kTransitionClosing, frame_diff);
1786 
1787         frame_diff = -frame_diff;
1788         validate_transitions(otc, kTransitionOpening, frame_diff);
1789         validate_transitions(ctc, kTransitionClosing, frame_diff);
1790         frame_diff = -frame_diff;
1791 
1792         if (g.trim_type == TRIM_IN) {
1793           frame_diff += g.transition->get_true_length();
1794         } else {
1795           frame_diff -= g.transition->get_true_length();
1796         }
1797       }
1798 
1799       // ripple ops
1800       if (effective_tool == TIMELINE_TOOL_RIPPLE) {
1801         for (int j=0;j<post_clips.size();j++) {
1802           ClipPtr post = post_clips.at(j);
1803 
1804           // prevent any rippled clip from going below 0
1805           if (panel_timeline->trim_type == TRIM_IN) {
1806             validator = post->timeline_in() - frame_diff;
1807             if (validator < 0) frame_diff += validator;
1808           }
1809 
1810           // prevent any post-clips colliding with pre-clips
1811           for (int k=0;k<pre_clips.size();k++) {
1812             ClipPtr pre = pre_clips.at(k);
1813             if (pre != post && pre->track() == post->track()) {
1814               if (panel_timeline->trim_type == TRIM_IN) {
1815                 validator = post->timeline_in() - frame_diff - pre->timeline_out();
1816                 if (validator < 0) frame_diff += validator;
1817               } else {
1818                 validator = post->timeline_in() + frame_diff - pre->timeline_out();
1819                 if (validator < 0) frame_diff -= validator;
1820               }
1821             }
1822           }
1823         }
1824       }
1825     } else if (clips_are_movable) { // validate ghosts for moving
1826       // prevent clips from moving below 0 on the timeline
1827       validator = g.old_in + frame_diff;
1828       if (validator < 0) frame_diff -= validator;
1829 
1830       if (g.transition != nullptr) {
1831         if (g.transition->secondary_clip != nullptr) {
1832           // prevent dual transitions from going below 0 on the primary or above media length on the secondary
1833 
1834           validator = g.transition->parent_clip->clip_in(true) + frame_diff;
1835           if (validator < 0) frame_diff -= validator;
1836 
1837           validator = g.transition->secondary_clip->timeline_out(true) - g.transition->secondary_clip->timeline_in(true) - g.transition->get_length() + g.transition->secondary_clip->clip_in(true) + frame_diff;
1838           if (validator < 0) frame_diff -= validator;
1839 
1840           validator = g.transition->parent_clip->clip_in() + frame_diff - g.transition->parent_clip->media_length() + g.transition->get_true_length();
1841           if (validator > 0) frame_diff -= validator;
1842 
1843           validator = g.transition->secondary_clip->timeline_out(true) - g.transition->secondary_clip->timeline_in(true) + g.transition->secondary_clip->clip_in(true) + frame_diff - g.transition->secondary_clip->media_length();
1844           if (validator > 0) frame_diff -= validator;
1845         } else {
1846           // prevent clip_in from going below 0
1847           if ((c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE)
1848               || (ms != nullptr && !ms->infinite_length)) {
1849             validator = g.old_clip_in + frame_diff;
1850             if (validator < 0) frame_diff -= validator;
1851           }
1852 
1853           // prevent clip length exceeding media length
1854           if ((c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE)
1855               || (ms != nullptr && !ms->infinite_length)) {
1856             validator = g.old_clip_in + g.ghost_length + frame_diff;
1857             if (validator > g.media_length) frame_diff -= validator - g.media_length;
1858           }
1859         }
1860       }
1861 
1862       // prevent clips from crossing tracks
1863       if (same_sign(g.old_track, panel_timeline->drag_track_start)) {
1864         while (!same_sign(g.old_track, g.old_track + track_diff)) {
1865           if (g.old_track < 0) {
1866             track_diff--;
1867           } else {
1868             track_diff++;
1869           }
1870         }
1871       }
1872     } else if (effective_tool == TIMELINE_TOOL_TRANSITION) {
1873       if (panel_timeline->transition_tool_open_clip == -1
1874           || panel_timeline->transition_tool_close_clip == -1) {
1875         validate_transitions(c, g.media_stream, frame_diff);
1876       } else {
1877         // open transition clip
1878         Clip* otc = olive::ActiveSequence->clips.at(panel_timeline->transition_tool_open_clip).get();
1879 
1880         // close transition clip
1881         Clip* ctc = olive::ActiveSequence->clips.at(panel_timeline->transition_tool_close_clip).get();
1882 
1883         if (g.media_stream == kTransitionClosing) {
1884           // swap
1885           Clip* temp = otc;
1886           otc = ctc;
1887           ctc = temp;
1888         }
1889 
1890         // always gets a positive frame_diff
1891         validate_transitions(otc, kTransitionOpening, frame_diff);
1892         validate_transitions(ctc, kTransitionClosing, frame_diff);
1893 
1894         // always gets a negative frame_diff
1895         frame_diff = -frame_diff;
1896         validate_transitions(otc, kTransitionOpening, frame_diff);
1897         validate_transitions(ctc, kTransitionClosing, frame_diff);
1898         frame_diff = -frame_diff;
1899       }
1900     }
1901   }
1902 
1903   // if the above validation changed the frame movement, it's unlikely we're still snapped
1904   if (temp_frame_diff != frame_diff) {
1905     panel_timeline->snapped = false;
1906   }
1907 
1908   // apply changes to ghosts
1909   for (int i=0;i<panel_timeline->ghosts.size();i++) {
1910     Ghost& g = panel_timeline->ghosts[i];
1911 
1912     if (effective_tool == TIMELINE_TOOL_SLIP) {
1913       g.clip_in = g.old_clip_in - frame_diff;
1914     } else if (g.trim_type != TRIM_NONE) {
1915       long ghost_diff = frame_diff;
1916 
1917       // prevent trimming clips from overlapping each other
1918       for (int j=0;j<panel_timeline->ghosts.size();j++) {
1919         const Ghost& comp = panel_timeline->ghosts.at(j);
1920         if (i != j && g.track == comp.track) {
1921           long validator;
1922           if (g.trim_type == TRIM_IN && comp.out < g.out) {
1923             validator = (g.old_in + ghost_diff) - comp.out;
1924             if (validator < 0) ghost_diff -= validator;
1925           } else if (comp.in > g.in) {
1926             validator = (g.old_out + ghost_diff) - comp.in;
1927             if (validator > 0) ghost_diff -= validator;
1928           }
1929         }
1930       }
1931 
1932       // apply changes
1933       if (g.transition != nullptr && g.transition->secondary_clip != nullptr) {
1934         if (g.trim_type == TRIM_IN) ghost_diff = -ghost_diff;
1935         g.in = g.old_in - ghost_diff;
1936         g.out = g.old_out + ghost_diff;
1937       } else if (g.trim_type == TRIM_IN) {
1938         g.in = g.old_in + ghost_diff;
1939         g.clip_in = g.old_clip_in + ghost_diff;
1940       } else {
1941         g.out = g.old_out + ghost_diff;
1942       }
1943     } else if (clips_are_movable) {
1944       g.track = g.old_track;
1945       g.in = g.old_in + frame_diff;
1946       g.out = g.old_out + frame_diff;
1947 
1948       if (g.transition != nullptr
1949           && g.transition == olive::ActiveSequence->clips.at(g.clip)->opening_transition) {
1950         g.clip_in = g.old_clip_in + frame_diff;
1951       }
1952 
1953       if (panel_timeline->importing) {
1954         if ((panel_timeline->video_ghosts && mouse_track < 0)
1955             || (panel_timeline->audio_ghosts && mouse_track >= 0)) {
1956           int abs_track_diff = abs(track_diff);
1957           if (g.old_track < 0) { // clip is video
1958             g.track -= abs_track_diff;
1959           } else { // clip is audio
1960             g.track += abs_track_diff;
1961           }
1962         }
1963       } else if (same_sign(g.old_track, panel_timeline->drag_track_start)) {
1964         g.track += track_diff;
1965       }
1966     } else if (effective_tool == TIMELINE_TOOL_TRANSITION) {
1967       if (panel_timeline->transition_tool_open_clip > -1
1968             && panel_timeline->transition_tool_close_clip > -1) {
1969         g.in = g.old_in - frame_diff;
1970         g.out = g.old_out + frame_diff;
1971       } else if (panel_timeline->transition_tool_open_clip == g.clip) {
1972         g.out = g.old_out + frame_diff;
1973       } else {
1974         g.in = g.old_in + frame_diff;
1975       }
1976     }
1977 
1978     earliest_in_point = qMin(earliest_in_point, g.in);
1979   }
1980 
1981   // apply changes to selections
1982   if (effective_tool != TIMELINE_TOOL_SLIP && !panel_timeline->importing && !panel_timeline->creating) {
1983     for (int i=0;i<olive::ActiveSequence->selections.size();i++) {
1984       Selection& s = olive::ActiveSequence->selections[i];
1985       if (panel_timeline->trim_target > -1) {
1986         if (panel_timeline->trim_type == TRIM_IN) {
1987           s.in = s.old_in + frame_diff;
1988         } else {
1989           s.out = s.old_out + frame_diff;
1990         }
1991       } else if (clips_are_movable) {
1992         for (int i=0;i<olive::ActiveSequence->selections.size();i++) {
1993           Selection& s = olive::ActiveSequence->selections[i];
1994           s.in = s.old_in + frame_diff;
1995           s.out = s.old_out + frame_diff;
1996           s.track = s.old_track;
1997 
1998           if (panel_timeline->importing) {
1999             int abs_track_diff = abs(track_diff);
2000             if (s.old_track < 0) {
2001               s.track -= abs_track_diff;
2002             } else {
2003               s.track += abs_track_diff;
2004             }
2005           } else {
2006             if (same_sign(s.track, panel_timeline->drag_track_start)) s.track += track_diff;
2007           }
2008         }
2009       }
2010     }
2011   }
2012 
2013   if (panel_timeline->importing) {
2014     QToolTip::showText(mapToGlobal(mouse_pos), frame_to_timecode(earliest_in_point, olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate));
2015   } else {
2016     QString tip = ((frame_diff < 0) ? "-" : "+") + frame_to_timecode(qAbs(frame_diff), olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate);
2017     if (panel_timeline->trim_target > -1) {
2018       // find which clip is being moved
2019       const Ghost* g = nullptr;
2020       for (int i=0;i<panel_timeline->ghosts.size();i++) {
2021         if (panel_timeline->ghosts.at(i).clip == panel_timeline->trim_target) {
2022           g = &panel_timeline->ghosts.at(i);
2023           break;
2024         }
2025       }
2026 
2027       if (g != nullptr) {
2028         tip += " " + tr("Duration:") + " ";
2029         long len = (g->old_out-g->old_in);
2030         if (panel_timeline->trim_type == TRIM_IN) {
2031           len -= frame_diff;
2032         } else {
2033           len += frame_diff;
2034         }
2035         tip += frame_to_timecode(len, olive::CurrentConfig.timecode_view, olive::ActiveSequence->frame_rate);
2036       }
2037     }
2038     QToolTip::showText(mapToGlobal(mouse_pos), tip);
2039   }
2040 }
2041 
mouseMoveEvent(QMouseEvent * event)2042 void TimelineWidget::mouseMoveEvent(QMouseEvent *event) {
2043   // interrupt any potential tooltip about to show
2044   tooltip_timer.stop();
2045 
2046   if (olive::ActiveSequence != nullptr) {
2047     bool alt = (event->modifiers() & Qt::AltModifier);
2048 
2049     // store current frame/track corresponding to the cursor
2050     panel_timeline->cursor_frame = panel_timeline->getTimelineFrameFromScreenPoint(event->pos().x());
2051     panel_timeline->cursor_track = getTrackFromScreenPoint(event->pos().y());
2052 
2053     // if holding the mouse button down, let's scroll to that location
2054     if (event->buttons() != 0 && panel_timeline->tool != TIMELINE_TOOL_HAND) {
2055       panel_timeline->scroll_to_frame(panel_timeline->cursor_frame);
2056     }
2057 
2058     // determine if the action should be "inserting" rather than "overwriting"
2059     // Default behavior is to replace/overwrite clips under any clips we're dropping over them. Inserting will
2060     // split and move existing clips at the drop point to make space for the drop
2061     panel_timeline->move_insert = ((event->modifiers() & Qt::ControlModifier)
2062                                    && (panel_timeline->tool == TIMELINE_TOOL_POINTER
2063                                        || panel_timeline->importing
2064                                        || panel_timeline->creating));
2065 
2066     // if we're not currently resizing already, default track resizing to false (we'll set it to true later if
2067     // the user is still hovering over a track line)
2068     if (!panel_timeline->moving_init) {
2069       track_resizing = false;
2070     }
2071 
2072     // if the current tool uses an on-screen visible cursor, we snap the cursor to the timeline
2073     if (current_tool_shows_cursor()) {
2074       panel_timeline->snap_to_timeline(&panel_timeline->cursor_frame,
2075 
2076                                        // only snap to the playhead if the edit tool doesn't force the playhead to
2077                                        // follow it (or if we're not selecting since that means the playhead is
2078                                        // static at the moment)
2079                                        !olive::CurrentConfig.edit_tool_also_seeks || !panel_timeline->selecting,
2080 
2081                                        true,
2082                                        true);
2083     }
2084 
2085     if (panel_timeline->selecting) {
2086 
2087       // get number of selections based on tracks in selection area
2088       int selection_tool_count = 1 + qMax(panel_timeline->cursor_track, panel_timeline->drag_track_start) - qMin(panel_timeline->cursor_track, panel_timeline->drag_track_start);
2089 
2090       // add count to selection offset for the total number of selection objects
2091       // (offset is usually 0, unless the user is holding shift in which case we add to existing selections)
2092       int selection_count = selection_tool_count + panel_timeline->selection_offset;
2093 
2094       // resize selection object array to new count
2095       if (olive::ActiveSequence->selections.size() != selection_count) {
2096         olive::ActiveSequence->selections.resize(selection_count);
2097       }
2098 
2099       // loop through tracks in selection area and adjust them accordingly
2100       int minimum_selection_track = qMin(panel_timeline->cursor_track, panel_timeline->drag_track_start);
2101       int maximum_selection_track = qMax(panel_timeline->cursor_track, panel_timeline->drag_track_start);
2102       long selection_in = qMin(panel_timeline->drag_frame_start, panel_timeline->cursor_frame);
2103       long selection_out = qMax(panel_timeline->drag_frame_start, panel_timeline->cursor_frame);
2104       for (int i=panel_timeline->selection_offset;i<selection_count;i++) {
2105         Selection& s = olive::ActiveSequence->selections[i];
2106         s.track = minimum_selection_track + i - panel_timeline->selection_offset;
2107         s.in = selection_in;
2108         s.out = selection_out;
2109       }
2110 
2111       // If the config is set to select links as well with the edit tool
2112       if (olive::CurrentConfig.edit_tool_selects_links) {
2113 
2114         // find which clips are selected
2115         for (int j=0;j<olive::ActiveSequence->clips.size();j++) {
2116 
2117           Clip* c = olive::ActiveSequence->clips.at(j).get();
2118 
2119           if (c != nullptr && c->IsSelected(false)) {
2120 
2121             // loop through linked clips
2122             for (int k=0;k<c->linked.size();k++) {
2123 
2124               ClipPtr link = olive::ActiveSequence->clips.at(c->linked.at(k));
2125 
2126               // see if one of the selections is already covering this track
2127               if (!(link->track() >= minimum_selection_track
2128                     && link->track() <= maximum_selection_track)) {
2129 
2130                 // clip is not in selection area, time to select it
2131                 Selection link_sel;
2132                 link_sel.in = selection_in;
2133                 link_sel.out = selection_out;
2134                 link_sel.track = link->track();
2135                 olive::ActiveSequence->selections.append(link_sel);
2136 
2137               }
2138 
2139             }
2140 
2141           }
2142         }
2143       }
2144 
2145       // if the config is set to seek with the edit too, do so now
2146       if (olive::CurrentConfig.edit_tool_also_seeks) {
2147         panel_sequence_viewer->seek(qMin(panel_timeline->drag_frame_start, panel_timeline->cursor_frame));
2148       } else {
2149         // if not, repaint (seeking will trigger a repaint)
2150         panel_timeline->repaint_timeline();
2151       }
2152 
2153     } else if (panel_timeline->hand_moving) {
2154 
2155       // if we're hand moving, we'll be adding values directly to the scrollbars
2156 
2157       // the scrollbars trigger repaints when they scroll, which is unnecessary here so we block them
2158       panel_timeline->block_repaints = true;
2159       panel_timeline->horizontalScrollBar->setValue(panel_timeline->horizontalScrollBar->value() + panel_timeline->drag_x_start - event->pos().x());
2160       scrollBar->setValue(scrollBar->value() + panel_timeline->drag_y_start - event->pos().y());
2161       panel_timeline->block_repaints = false;
2162 
2163       // finally repaint
2164       panel_timeline->repaint_timeline();
2165 
2166       // store current cursor position for next hand move event
2167       panel_timeline->drag_x_start = event->pos().x();
2168       panel_timeline->drag_y_start = event->pos().y();
2169 
2170     } else if (panel_timeline->moving_init) {
2171 
2172       if (track_resizing) {
2173 
2174         // get cursor movement
2175         int diff = (event->pos().y() - panel_timeline->drag_y_start);
2176 
2177         // add it to the current track height
2178         int new_height = panel_timeline->GetTrackHeight(track_target);
2179         if (bottom_align) {
2180           new_height -= diff;
2181         } else {
2182           new_height += diff;
2183         }
2184 
2185         // limit track height to track minimum height constant
2186         new_height = qMax(new_height, olive::timeline::kTrackMinHeight);
2187 
2188         // set the track height
2189         panel_timeline->SetTrackHeight(track_target, new_height);
2190 
2191         // store current cursor position for next track resize event
2192         panel_timeline->drag_y_start = event->pos().y();
2193 
2194         update();
2195       } else if (panel_timeline->moving_proc) {
2196 
2197         // we're currently dragging ghosts
2198         update_ghosts(event->pos(), event->modifiers() & Qt::ShiftModifier);
2199 
2200       } else {
2201 
2202         // Prepare to start moving clips in some capacity. We create Ghost objects to store movement data before we
2203         // actually apply it to the clips (in mouseReleaseEvent)
2204 
2205         // loop through clips for any currently selected
2206         for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
2207 
2208           Clip* c = olive::ActiveSequence->clips.at(i).get();
2209 
2210           if (c != nullptr) {
2211             Ghost g;
2212             g.transition = nullptr;
2213 
2214             // check if whole clip is added
2215             bool add = false;
2216 
2217             // check if a transition is selected (prioritize transition selection)
2218             // (only the pointer tool supports moving transitions)
2219             if (panel_timeline->tool == TIMELINE_TOOL_POINTER
2220                 && (c->opening_transition != nullptr || c->closing_transition != nullptr)) {
2221 
2222               // check if any selections contain a whole transition
2223               for (int j=0;j<olive::ActiveSequence->selections.size();j++) {
2224 
2225                 const Selection& s = olive::ActiveSequence->selections.at(j);
2226 
2227                 if (s.track == c->track()) {
2228                   if (selection_contains_transition(s, c, kTransitionOpening)) {
2229 
2230                     g.transition = c->opening_transition;
2231                     add = true;
2232                     break;
2233 
2234                   } else if (selection_contains_transition(s, c, kTransitionClosing)) {
2235 
2236                     g.transition = c->closing_transition;
2237                     add = true;
2238                     break;
2239 
2240                   }
2241                 }
2242 
2243               }
2244 
2245             }
2246 
2247             // if a transition isn't selected, check if the whole clip is
2248             if (!add) {
2249               add = c->IsSelected();
2250             }
2251 
2252             if (add) {
2253 
2254               if (g.transition != nullptr) {
2255 
2256                 // transition may be a dual transition, check if it's already been added elsewhere
2257                 for (int j=0;j<panel_timeline->ghosts.size();j++) {
2258                   if (panel_timeline->ghosts.at(j).transition == g.transition) {
2259                     add = false;
2260                     break;
2261                   }
2262                 }
2263 
2264               }
2265 
2266               if (add) {
2267                 g.clip = i;
2268                 g.trim_type = panel_timeline->trim_type;
2269                 panel_timeline->ghosts.append(g);
2270               }
2271 
2272             }
2273           }
2274         }
2275 
2276         if (panel_timeline->tool == TIMELINE_TOOL_SLIDE) {
2277 
2278           // for the slide tool, we add the surrounding clips as ghosts that are getting trimmed the opposite way
2279 
2280           // store original array size since we'll be adding to it
2281           int ghost_arr_size = panel_timeline->ghosts.size();
2282 
2283           // loop through clips for any that are "touching" the selected clips
2284           for (int j=0;j<olive::ActiveSequence->clips.size();j++) {
2285 
2286             ClipPtr c = olive::ActiveSequence->clips.at(j);
2287             if (c != nullptr) {
2288 
2289               for (int i=0;i<ghost_arr_size;i++) {
2290 
2291                 Ghost& g = panel_timeline->ghosts[i];
2292                 g.trim_type = TRIM_NONE; // the selected clips will be moving, not trimming
2293 
2294                 ClipPtr ghost_clip = olive::ActiveSequence->clips.at(g.clip);
2295 
2296                 if (c->track() == ghost_clip->track()) {
2297 
2298                   // see if this clip is currently selected, if so we won't add it as a "touching" clip
2299                   bool found = false;
2300                   for (int k=0;k<ghost_arr_size;k++) {
2301                     if (panel_timeline->ghosts.at(k).clip == j) {
2302                       found = true;
2303                       break;
2304                     }
2305                   }
2306 
2307                   if (!found) { // the clip is not currently selected
2308 
2309                     // check if this clip is indeed touching
2310                     bool is_in = (c->timeline_in() == ghost_clip->timeline_out());
2311                     if (is_in || c->timeline_out() == ghost_clip->timeline_in()) {
2312                       Ghost gh;
2313                       gh.transition = nullptr;
2314                       gh.clip = j;
2315                       gh.trim_type = is_in ? TRIM_IN : TRIM_OUT;
2316                       panel_timeline->ghosts.append(gh);
2317                     }
2318                   }
2319                 }
2320               }
2321             }
2322           }
2323         }
2324 
2325         // set up ghost defaults
2326         init_ghosts();
2327 
2328         // if the ripple tool is selected, prepare to ripple
2329         if (panel_timeline->tool == TIMELINE_TOOL_RIPPLE) {
2330 
2331           long axis = LONG_MAX;
2332 
2333           // find the earliest point within the selected clips which is the point we'll ripple around
2334           // also store the currently selected clips so we don't have to do it later
2335           QVector<ClipPtr> ghost_clips;
2336           ghost_clips.resize(panel_timeline->ghosts.size());
2337 
2338           for (int i=0;i<panel_timeline->ghosts.size();i++) {
2339             ClipPtr c = olive::ActiveSequence->clips.at(panel_timeline->ghosts.at(i).clip);
2340             if (panel_timeline->trim_type == TRIM_IN) {
2341               axis = qMin(axis, c->timeline_in());
2342             } else {
2343               axis = qMin(axis, c->timeline_out());
2344             }
2345 
2346             // store clip reference
2347             ghost_clips[i] = c;
2348           }
2349 
2350           // loop through clips and cache which are earlier than the axis and which after after
2351           for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
2352             ClipPtr c = olive::ActiveSequence->clips.at(i);
2353             if (c != nullptr && !ghost_clips.contains(c)) {
2354               bool clip_is_post = (c->timeline_in() >= axis);
2355 
2356               // construct the list of pre and post clips
2357               QVector<ClipPtr>& clip_list = (clip_is_post) ? post_clips : pre_clips;
2358 
2359               // check if there's already a clip in this list on this track, and if this clip is closer or not
2360               bool found = false;
2361               for (int j=0;j<clip_list.size();j++) {
2362 
2363                 ClipPtr compare = clip_list.at(j);
2364 
2365                 if (compare->track() == c->track()) {
2366 
2367                   // if the clip is closer, use this one instead of the current one in the list
2368                   if ((!clip_is_post && compare->timeline_out() < c->timeline_out())
2369                       || (clip_is_post && compare->timeline_in() > c->timeline_in())) {
2370                     clip_list[j] = c;
2371                   }
2372 
2373                   found = true;
2374                   break;
2375                 }
2376 
2377               }
2378 
2379               // if there is no clip on this track in the list, add it
2380               if (!found) {
2381                 clip_list.append(c);
2382               }
2383             }
2384           }
2385         }
2386 
2387         // store selections
2388         selection_command = new SetSelectionsCommand(olive::ActiveSequence.get());
2389         selection_command->old_data = olive::ActiveSequence->selections;
2390 
2391         // ready to start moving clips
2392         panel_timeline->moving_proc = true;
2393       }
2394 
2395       update_ui(false);
2396 
2397     } else if (panel_timeline->splitting) {
2398 
2399       // get the range of tracks currently dragged
2400       int track_start = qMin(panel_timeline->cursor_track, panel_timeline->drag_track_start);
2401       int track_end = qMax(panel_timeline->cursor_track, panel_timeline->drag_track_start);
2402       int track_size = 1 + track_end - track_start;
2403 
2404       // set tracks to be split
2405       panel_timeline->split_tracks.resize(track_size);
2406       for (int i=0;i<track_size;i++) {
2407         panel_timeline->split_tracks[i] = track_start + i;
2408       }
2409 
2410       // if alt isn't being held, also add the tracks of the clip's links
2411       if (!alt) {
2412         for (int i=0;i<track_size;i++) {
2413 
2414           // make sure there's a clip in this track
2415           int clip_index = getClipIndexFromCoords(panel_timeline->drag_frame_start, panel_timeline->split_tracks[i]);
2416 
2417           if (clip_index > -1) {
2418             ClipPtr clip = olive::ActiveSequence->clips.at(clip_index);
2419             for (int j=0;j<clip->linked.size();j++) {
2420 
2421               ClipPtr link = olive::ActiveSequence->clips.at(clip->linked.at(j));
2422 
2423               // if this clip isn't already in the list of tracks to split
2424               if (link->track() < track_start || link->track() > track_end) {
2425                 panel_timeline->split_tracks.append(link->track());
2426               }
2427 
2428             }
2429           }
2430         }
2431       }
2432 
2433       update_ui(false);
2434 
2435     } else if (panel_timeline->rect_select_init) {
2436 
2437       // set if the user started dragging at point where there was no clip
2438 
2439       if (panel_timeline->rect_select_proc) {
2440 
2441         // we're currently rectangle selecting
2442 
2443         // set the right/bottom coords to the current mouse position
2444         // (left/top were set to the starting drag position earlier)
2445         panel_timeline->rect_select_rect.setRight(event->pos().x());
2446 
2447         if (bottom_align) {
2448           panel_timeline->rect_select_rect.setBottom(event->pos().y() - height());
2449         } else {
2450           panel_timeline->rect_select_rect.setBottom(event->pos().y());
2451         }
2452 
2453         long frame_min = qMin(panel_timeline->drag_frame_start, panel_timeline->cursor_frame);
2454         long frame_max = qMax(panel_timeline->drag_frame_start, panel_timeline->cursor_frame);
2455 
2456         int track_min = qMin(panel_timeline->drag_track_start, panel_timeline->cursor_track);
2457         int track_max = qMax(panel_timeline->drag_track_start, panel_timeline->cursor_track);
2458 
2459         // determine which clips are in this rectangular selection
2460         QVector<ClipPtr> selected_clips;
2461         for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
2462           ClipPtr clip = olive::ActiveSequence->clips.at(i);
2463           if (clip != nullptr &&
2464               clip->track() >= track_min &&
2465               clip->track() <= track_max &&
2466               !(clip->timeline_in() < frame_min && clip->timeline_out() < frame_min) &&
2467               !(clip->timeline_in() > frame_max && clip->timeline_out() > frame_max)) {
2468 
2469             // create a group of the clip (and its links if alt is not pressed)
2470             QVector<ClipPtr> session_clips;
2471             session_clips.append(clip);
2472 
2473             if (!alt) {
2474               for (int j=0;j<clip->linked.size();j++) {
2475                 session_clips.append(olive::ActiveSequence->clips.at(clip->linked.at(j)));
2476               }
2477             }
2478 
2479             // for each of these clips, see if clip has already been added -
2480             // this can easily happen due to adding linked clips
2481             for (int j=0;j<session_clips.size();j++) {
2482               bool found = false;
2483 
2484               ClipPtr c = session_clips.at(j);
2485               for (int k=0;k<selected_clips.size();k++) {
2486                 if (selected_clips.at(k) == c) {
2487                   found = true;
2488                   break;
2489                 }
2490               }
2491 
2492               // if the clip isn't already in the selection add it
2493               if (!found) {
2494                 selected_clips.append(c);
2495               }
2496             }
2497           }
2498         }
2499 
2500         // add each of the selected clips to the main sequence's selections
2501         olive::ActiveSequence->selections.resize(selected_clips.size() + panel_timeline->selection_offset);
2502         for (int i=0;i<selected_clips.size();i++) {
2503           Selection& s = olive::ActiveSequence->selections[i+panel_timeline->selection_offset];
2504           ClipPtr clip = selected_clips.at(i);
2505           s.old_in = s.in = clip->timeline_in();
2506           s.old_out = s.out = clip->timeline_out();
2507           s.old_track = s.track = clip->track();
2508         }
2509 
2510         panel_timeline->repaint_timeline();
2511       } else {
2512 
2513         // set up rectangle selecting
2514         panel_timeline->rect_select_rect.setX(event->pos().x());
2515 
2516         if (bottom_align) {
2517           // bottom aligned widgets start with 0 at the bottom and go down to a negative number
2518           panel_timeline->rect_select_rect.setY(event->pos().y() - height());
2519         } else {
2520           panel_timeline->rect_select_rect.setY(event->pos().y());
2521         }
2522 
2523         panel_timeline->rect_select_rect.setWidth(0);
2524         panel_timeline->rect_select_rect.setHeight(0);
2525 
2526         panel_timeline->rect_select_proc = true;
2527 
2528       }
2529     } else if (current_tool_shows_cursor()) {
2530 
2531       // we're not currently performing an action (click is not pressed), but redraw because we have an on-screen cursor
2532       panel_timeline->repaint_timeline();
2533 
2534     } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER ||
2535                panel_timeline->tool == TIMELINE_TOOL_RIPPLE ||
2536                panel_timeline->tool == TIMELINE_TOOL_ROLLING) {
2537 
2538       // hide any tooltip that may be currently showing
2539       QToolTip::hideText();
2540 
2541       // cache cursor position
2542       QPoint pos = event->pos();
2543 
2544       //
2545       // check to see if the cursor is on a clip edge
2546       //
2547 
2548       // threshold around a trim point that the cursor can be within and still considered "trimming"
2549       int lim = 5;
2550       int mouse_frame_lower = pos.x() - lim;
2551       int mouse_frame_upper = pos.x() + lim;
2552 
2553       // used to determine whether we the cursor found a trim point or not
2554       bool found = false;
2555 
2556       // used to determine whether the cursor is within the rect of a clip
2557       bool cursor_contains_clip = false;
2558 
2559       // used to determine how close the cursor is to a trim point
2560       // (and more specifically, whether another point is closer or not)
2561       int closeness = INT_MAX;
2562 
2563       // while we loop through the clips, we cache the maximum/minimum tracks in this sequence
2564       int min_track = INT_MAX;
2565       int max_track = INT_MIN;
2566 
2567       // we default to selecting no transition, but set this accordingly if the cursor is on a transition
2568       panel_timeline->transition_select = kTransitionNone;
2569 
2570       // we also default to no trimming which may be changed later in this function
2571       panel_timeline->trim_type = TRIM_NONE;
2572 
2573       // set currently trimming clip to -1 (aka null)
2574       panel_timeline->trim_target = -1;
2575 
2576       // loop through current clips in the sequence
2577       for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
2578         ClipPtr c = olive::ActiveSequence->clips.at(i);
2579         if (c != nullptr) {
2580 
2581           // cache track range
2582           min_track = qMin(min_track, c->track());
2583           max_track = qMax(max_track, c->track());
2584 
2585           // if this clip is on the same track the mouse is
2586           if (c->track() == panel_timeline->cursor_track) {
2587 
2588             // if this cursor is inside the boundaries of this clip (hovering over the clip)
2589             if (panel_timeline->cursor_frame >= c->timeline_in() &&
2590                 panel_timeline->cursor_frame <= c->timeline_out()) {
2591 
2592               // acknowledge that we are hovering over a clip
2593               cursor_contains_clip = true;
2594 
2595               // start a timer to show a tooltip about this clip
2596               tooltip_timer.start();
2597               tooltip_clip = i;
2598 
2599               // check if the cursor is specifically hovering over one of the clip's transitions
2600               if (c->opening_transition != nullptr
2601                   && panel_timeline->cursor_frame <= c->timeline_in() + c->opening_transition->get_true_length()) {
2602 
2603                 panel_timeline->transition_select = kTransitionOpening;
2604 
2605               } else if (c->closing_transition != nullptr
2606                          && panel_timeline->cursor_frame >= c->timeline_out() - c->closing_transition->get_true_length()) {
2607 
2608                 panel_timeline->transition_select = kTransitionClosing;
2609 
2610               }
2611             }
2612 
2613             int visual_in_point = panel_timeline->getTimelineScreenPointFromFrame(c->timeline_in());
2614             int visual_out_point = panel_timeline->getTimelineScreenPointFromFrame(c->timeline_out());
2615 
2616             // is the cursor hovering around the clip's IN point?
2617             if (visual_in_point > mouse_frame_lower && visual_in_point < mouse_frame_upper) {
2618 
2619               // test how close this IN point is to the cursor
2620               int nc = qAbs(visual_in_point + 1 - pos.x());
2621 
2622               // and test whether it's closer than the last in/out point we found
2623               if (nc < closeness) {
2624 
2625                 // if so, this is the point we'll make active for now (unless we find a closer one later)
2626                 panel_timeline->trim_target = i;
2627                 panel_timeline->trim_type = TRIM_IN;
2628                 closeness = nc;
2629                 found = true;
2630 
2631               }
2632             }
2633 
2634             // is the cursor hovering around the clip's OUT point?
2635             if (visual_out_point > mouse_frame_lower && visual_out_point < mouse_frame_upper) {
2636 
2637               // test how close this OUT point is to the cursor
2638               int nc = qAbs(visual_out_point - 1 - pos.x());
2639 
2640               // and test whether it's closer than the last in/out point we found
2641               if (nc < closeness) {
2642 
2643                 // if so, this is the point we'll make active for now (unless we find a closer one later)
2644                 panel_timeline->trim_target = i;
2645                 panel_timeline->trim_type = TRIM_OUT;
2646                 closeness = nc;
2647                 found = true;
2648 
2649               }
2650             }
2651 
2652             // the pointer can be used to resize/trim transitions, here we test if the
2653             // cursor is within the trim point of one of the clip's transitions
2654             if (panel_timeline->tool == TIMELINE_TOOL_POINTER) {
2655 
2656               // if the clip has an opening transition
2657               if (c->opening_transition != nullptr) {
2658 
2659                 // cache the timeline frame where the transition ends
2660                 int transition_point = panel_timeline->getTimelineScreenPointFromFrame(c->timeline_in()
2661                                                                                        + c->opening_transition->get_true_length());
2662 
2663                 // check if the cursor is hovering around it (within the threshold)
2664                 if (transition_point > mouse_frame_lower && transition_point < mouse_frame_upper) {
2665 
2666                   // similar to above, test how close it is and if it's closer, make this active
2667                   int nc = qAbs(transition_point - 1 - pos.x());
2668                   if (nc < closeness) {
2669                     panel_timeline->trim_target = i;
2670                     panel_timeline->trim_type = TRIM_OUT;
2671                     panel_timeline->transition_select = kTransitionOpening;
2672                     closeness = nc;
2673                     found = true;
2674                   }
2675                 }
2676               }
2677 
2678               // if the clip has a closing transition
2679               if (c->closing_transition != nullptr) {
2680 
2681                 // cache the timeline frame where the transition starts
2682                 int transition_point = panel_timeline->getTimelineScreenPointFromFrame(c->timeline_out()
2683                                                                                         - c->closing_transition->get_true_length());
2684 
2685                 // check if the cursor is hovering around it (within the threshold)
2686                 if (transition_point > mouse_frame_lower && transition_point < mouse_frame_upper) {
2687 
2688                   // similar to above, test how close it is and if it's closer, make this active
2689                   int nc = qAbs(transition_point + 1 - pos.x());
2690                   if (nc < closeness) {
2691                     panel_timeline->trim_target = i;
2692                     panel_timeline->trim_type = TRIM_IN;
2693                     panel_timeline->transition_select = kTransitionClosing;
2694                     closeness = nc;
2695                     found = true;
2696                   }
2697                 }
2698               }
2699             }
2700           }
2701         }
2702       }
2703 
2704       // if the cursor is indeed on a clip edge, we set the cursor accordingly
2705       if (found) {
2706 
2707         if (panel_timeline->trim_type == TRIM_IN) { // if we're trimming an IN point
2708           setCursor(panel_timeline->tool == TIMELINE_TOOL_RIPPLE ? olive::cursor::LeftRipple : olive::cursor::LeftTrim);
2709         } else { // if we're trimming an OUT point
2710           setCursor(panel_timeline->tool == TIMELINE_TOOL_RIPPLE ? olive::cursor::RightRipple : olive::cursor::RightTrim);
2711         }
2712 
2713       } else {
2714         // we didn't find a trim target, so we must be doing something else
2715         // (e.g. dragging a clip or resizing the track heights)
2716 
2717         unsetCursor();
2718 
2719         // check to see if we're resizing a track height
2720         int test_range = 5;
2721         int mouse_pos = event->pos().y();
2722         int hover_track = getTrackFromScreenPoint(mouse_pos);
2723         int track_y_edge = getScreenPointFromTrack(hover_track);
2724 
2725         if (!bottom_align) {
2726           track_y_edge += panel_timeline->GetTrackHeight(hover_track);
2727         }
2728 
2729         if (mouse_pos > track_y_edge - test_range
2730             && mouse_pos < track_y_edge + test_range) {
2731           if (cursor_contains_clip
2732               || (olive::CurrentConfig.show_track_lines
2733                   && panel_timeline->cursor_track >= min_track
2734                   && panel_timeline->cursor_track <= max_track)) {
2735             track_resizing = true;
2736             track_target = hover_track;
2737             setCursor(Qt::SizeVerCursor);
2738           }
2739         }
2740       }
2741     } else if (panel_timeline->tool == TIMELINE_TOOL_SLIP) {
2742 
2743       // we're not currently performing any slipping, all we do here is set the cursor if mouse is hovering over a
2744       // cursor
2745       if (getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track) > -1) {
2746         setCursor(olive::cursor::Slip);
2747       } else {
2748         unsetCursor();
2749       }
2750 
2751     } else if (panel_timeline->tool == TIMELINE_TOOL_TRANSITION) {
2752 
2753       if (panel_timeline->transition_tool_init) {
2754 
2755         // the transition tool has started
2756 
2757         if (panel_timeline->transition_tool_proc) {
2758 
2759           // ghosts have been set up, so just run update
2760           update_ghosts(event->pos(), event->modifiers() & Qt::ShiftModifier);
2761 
2762         } else {
2763 
2764           // transition tool is being used but ghosts haven't been set up yet, set them up now
2765           int primary_type = kTransitionOpening;
2766           int primary = panel_timeline->transition_tool_open_clip;
2767           if (primary == -1) {
2768             primary_type = kTransitionClosing;
2769             primary = panel_timeline->transition_tool_close_clip;
2770           }
2771 
2772           ClipPtr c = olive::ActiveSequence->clips.at(primary);
2773 
2774           Ghost g;
2775 
2776           g.in = g.old_in = g.out = g.old_out = (primary_type == kTransitionOpening) ?
2777                                                   c->timeline_in()
2778                                                 : c->timeline_out();
2779 
2780           g.track = c->track();
2781           g.clip = primary;
2782           g.media_stream = primary_type;
2783           g.trim_type = TRIM_NONE;
2784 
2785           panel_timeline->ghosts.append(g);
2786 
2787           panel_timeline->transition_tool_proc = true;
2788 
2789         }
2790 
2791       } else {
2792 
2793         // transition tool has been selected but is not yet active, so we show screen feedback to the user on
2794         // possible transitions
2795 
2796         int mouse_clip = getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track);
2797 
2798         // set default transition tool references to no clip
2799         panel_timeline->transition_tool_open_clip = -1;
2800         panel_timeline->transition_tool_close_clip = -1;
2801 
2802         if (mouse_clip > -1) {
2803 
2804           // cursor is hovering over a clip
2805 
2806           ClipPtr c = olive::ActiveSequence->clips.at(mouse_clip);
2807 
2808           // check if the clip and transition are both the same sign (meaning video/audio are the same)
2809           if (same_sign(c->track(), panel_timeline->transition_tool_side)) {
2810 
2811             // the range within which the transition tool will assume the user wants to make a shared transition
2812             // between two clips rather than just one transition on one clip
2813             long between_range = getFrameFromScreenPoint(panel_timeline->zoom, TRANSITION_BETWEEN_RANGE) + 1;
2814 
2815             // set whether the transition is opening or closing based on whether the cursor is on the left half
2816             // or right half of the clip
2817             if (panel_timeline->cursor_frame > (c->timeline_in() + (c->length()/2))) {
2818               panel_timeline->transition_tool_close_clip = mouse_clip;
2819 
2820               // if the cursor is within this range, set the post_clip to be the next clip touching
2821               //
2822               // getClipIndexFromCoords() will automatically set to -1 if there's no clip there which means the
2823               // end result will be the same as not setting a clip here at all
2824               if (panel_timeline->cursor_frame > c->timeline_out() - between_range) {
2825                 panel_timeline->transition_tool_open_clip = getClipIndexFromCoords(c->timeline_out()+1, c->track());
2826               }
2827             } else {
2828               panel_timeline->transition_tool_open_clip = mouse_clip;
2829 
2830               if (panel_timeline->cursor_frame < c->timeline_in() + between_range) {
2831                 panel_timeline->transition_tool_close_clip = getClipIndexFromCoords(c->timeline_in()-1, c->track());
2832               }
2833             }
2834 
2835           }
2836         }
2837       }
2838 
2839       panel_timeline->repaint_timeline();
2840     }
2841   }
2842 }
2843 
leaveEvent(QEvent *)2844 void TimelineWidget::leaveEvent(QEvent*) {
2845   tooltip_timer.stop();
2846 }
2847 
draw_waveform(ClipPtr clip,const FootageStream * ms,long media_length,QPainter * p,const QRect & clip_rect,int waveform_start,int waveform_limit,double zoom)2848 void draw_waveform(ClipPtr clip, const FootageStream* ms, long media_length, QPainter *p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom) {
2849   // audio channels multiplied by the number of bytes in a 16-bit audio sample
2850   int divider = ms->audio_channels*2;
2851 
2852   int channel_height = clip_rect.height()/ms->audio_channels;
2853 
2854   int last_waveform_index = -1;
2855 
2856   for (int i=waveform_start;i<waveform_limit;i++) {
2857     int waveform_index = qFloor((((clip->clip_in() + (double(i)/zoom))/media_length) * ms->audio_preview.size())/divider)*divider;
2858 
2859     if (clip->reversed()) {
2860       waveform_index = ms->audio_preview.size() - waveform_index - (ms->audio_channels * 2);
2861     }
2862 
2863     if (last_waveform_index < 0) last_waveform_index = waveform_index;
2864 
2865     for (int j=0;j<ms->audio_channels;j++) {
2866       int mid = (olive::CurrentConfig.rectified_waveforms) ? clip_rect.top()+channel_height*(j+1) : clip_rect.top()+channel_height*j+(channel_height/2);
2867 
2868       int offset_range_start = last_waveform_index+(j*2);
2869       int offset_range_end = waveform_index+(j*2);
2870       int offset_range_min = qMin(offset_range_start, offset_range_end);
2871       int offset_range_max = qMax(offset_range_start, offset_range_end);
2872 
2873       qint8 min = qint8(qRound(double(ms->audio_preview.at(offset_range_min)) / 128.0 * (channel_height/2)));
2874       qint8 max = qint8(qRound(double(ms->audio_preview.at(offset_range_min+1)) / 128.0 * (channel_height/2)));
2875 
2876       if ((offset_range_max + 1) < ms->audio_preview.size()) {
2877 
2878         // for waveform drawings, we get the maximum below 0 and maximum above 0 for this waveform range
2879         for (int k=offset_range_min+2;k<=offset_range_max;k+=2) {
2880           min = qMin(min, qint8(qRound(double(ms->audio_preview.at(k)) / 128.0 * (channel_height/2))));
2881           max = qMax(max, qint8(qRound(double(ms->audio_preview.at(k+1)) / 128.0 * (channel_height/2))));
2882         }
2883 
2884         // draw waveforms
2885         if (olive::CurrentConfig.rectified_waveforms)  {
2886 
2887           // rectified waveforms start from the bottom and draw upwards
2888           p->drawLine(clip_rect.left()+i, mid, clip_rect.left()+i, mid - (max - min));
2889         } else {
2890 
2891           // non-rectified waveforms start from the center and draw outwards
2892           p->drawLine(clip_rect.left()+i, mid+min, clip_rect.left()+i, mid+max);
2893 
2894         }
2895       }
2896     }
2897     last_waveform_index = waveform_index;
2898   }
2899 }
2900 
draw_transition(QPainter & p,ClipPtr c,const QRect & clip_rect,QRect & text_rect,int transition_type)2901 void draw_transition(QPainter& p, ClipPtr c, const QRect& clip_rect, QRect& text_rect, int transition_type) {
2902   TransitionPtr t = (transition_type == kTransitionOpening) ? c->opening_transition : c->closing_transition;
2903   if (t != nullptr) {
2904     QColor transition_color(255, 0, 0, 16);
2905     int transition_width = getScreenPointFromFrame(panel_timeline->zoom, t->get_true_length());
2906     int transition_height = clip_rect.height();
2907     int tr_y = clip_rect.y();
2908     int tr_x = 0;
2909     if (transition_type == kTransitionOpening) {
2910       tr_x = clip_rect.x();
2911       text_rect.setX(text_rect.x()+transition_width);
2912     } else {
2913       tr_x = clip_rect.right()-transition_width;
2914       text_rect.setWidth(text_rect.width()-transition_width);
2915     }
2916     QRect transition_rect = QRect(tr_x, tr_y, transition_width, transition_height);
2917     p.fillRect(transition_rect, transition_color);
2918     QRect transition_text_rect(transition_rect.x() + olive::timeline::kClipTextPadding, transition_rect.y() + olive::timeline::kClipTextPadding, transition_rect.width() - olive::timeline::kClipTextPadding, transition_rect.height() - olive::timeline::kClipTextPadding);
2919     if (transition_text_rect.width() > MAX_TEXT_WIDTH) {
2920       bool draw_text = true;
2921 
2922       p.setPen(QColor(0, 0, 0, 96));
2923       if (t->secondary_clip == nullptr) {
2924         if (transition_type == kTransitionOpening) {
2925           p.drawLine(transition_rect.bottomLeft(), transition_rect.topRight());
2926         } else {
2927           p.drawLine(transition_rect.topLeft(), transition_rect.bottomRight());
2928         }
2929       } else {
2930         if (transition_type == kTransitionOpening) {
2931           p.drawLine(QPoint(transition_rect.left(), transition_rect.center().y()), transition_rect.topRight());
2932           p.drawLine(QPoint(transition_rect.left(), transition_rect.center().y()), transition_rect.bottomRight());
2933           draw_text = false;
2934         } else {
2935           p.drawLine(QPoint(transition_rect.right(), transition_rect.center().y()), transition_rect.topLeft());
2936           p.drawLine(QPoint(transition_rect.right(), transition_rect.center().y()), transition_rect.bottomLeft());
2937         }
2938       }
2939 
2940       if (draw_text) {
2941         p.setPen(Qt::white);
2942         p.drawText(transition_text_rect, 0, t->meta->name, &transition_text_rect);
2943       }
2944     }
2945     p.setPen(Qt::black);
2946     p.drawRect(transition_rect);
2947   }
2948 
2949 }
2950 
paintEvent(QPaintEvent *)2951 void TimelineWidget::paintEvent(QPaintEvent*) {
2952   // Draw clips
2953   if (olive::ActiveSequence != nullptr) {
2954     QPainter p(this);
2955 
2956     // get widget width and height
2957     int video_track_limit = 0;
2958     int audio_track_limit = 0;
2959     for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
2960       ClipPtr clip = olive::ActiveSequence->clips.at(i);
2961       if (clip != nullptr) {
2962         video_track_limit = qMin(video_track_limit, clip->track());
2963         audio_track_limit = qMax(audio_track_limit, clip->track());
2964       }
2965     }
2966 
2967     // start by adding a track height worth of padding
2968     int panel_height = olive::timeline::kTrackDefaultHeight;
2969 
2970     // loop through tracks for maximum panel height
2971     if (bottom_align) {
2972       for (int i=-1;i>=video_track_limit;i--) {
2973         panel_height += panel_timeline->GetTrackHeight(i);
2974       }
2975     } else {
2976       for (int i=0;i<=audio_track_limit;i++) {
2977         panel_height += panel_timeline->GetTrackHeight(i);
2978       }
2979     }
2980     if (bottom_align) {
2981       scrollBar->setMinimum(qMin(0, - panel_height + height()));
2982     } else {
2983       scrollBar->setMaximum(qMax(0, panel_height - height()));
2984     }
2985 
2986     for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
2987       ClipPtr clip = olive::ActiveSequence->clips.at(i);
2988       if (clip != nullptr && is_track_visible(clip->track())) {
2989         QRect clip_rect(panel_timeline->getTimelineScreenPointFromFrame(clip->timeline_in()), getScreenPointFromTrack(clip->track()), getScreenPointFromFrame(panel_timeline->zoom, clip->length()), panel_timeline->GetTrackHeight(clip->track()));
2990         QRect text_rect(clip_rect.left() + olive::timeline::kClipTextPadding, clip_rect.top() + olive::timeline::kClipTextPadding, clip_rect.width() - olive::timeline::kClipTextPadding - 1, clip_rect.height() - olive::timeline::kClipTextPadding - 1);
2991         if (clip_rect.left() < width() && clip_rect.right() >= 0 && clip_rect.top() < height() && clip_rect.bottom() >= 0) {
2992           QRect actual_clip_rect = clip_rect;
2993           if (actual_clip_rect.x() < 0) actual_clip_rect.setX(0);
2994           if (actual_clip_rect.right() > width()) actual_clip_rect.setRight(width());
2995           if (actual_clip_rect.y() < 0) actual_clip_rect.setY(0);
2996           if (actual_clip_rect.bottom() > height()) actual_clip_rect.setBottom(height());
2997           p.fillRect(actual_clip_rect, (clip->enabled()) ? clip->color() : QColor(96, 96, 96));
2998 
2999           int thumb_x = clip_rect.x() + 1;
3000 
3001           if (clip->media() != nullptr && clip->media()->get_type() == MEDIA_TYPE_FOOTAGE) {
3002             bool draw_checkerboard = false;
3003             QRect checkerboard_rect(clip_rect);
3004             FootageStream* ms = clip->media_stream();
3005             if (ms == nullptr) {
3006               draw_checkerboard = true;
3007             } else if (ms->preview_done) {
3008               // draw top and tail triangles
3009               int triangle_size = olive::timeline::kTrackMinHeight >> 2;
3010               if (!ms->infinite_length && clip_rect.width() > triangle_size) {
3011                 p.setPen(Qt::NoPen);
3012                 p.setBrush(QColor(80, 80, 80));
3013                 if (clip->clip_in() == 0
3014                     && clip_rect.x() + triangle_size > 0
3015                     && clip_rect.y() + triangle_size > 0
3016                     && clip_rect.x() < width()
3017                     && clip_rect.y() < height()) {
3018                   const QPoint points[3] = {
3019                     QPoint(clip_rect.x(), clip_rect.y()),
3020                     QPoint(clip_rect.x() + triangle_size, clip_rect.y()),
3021                     QPoint(clip_rect.x(), clip_rect.y() + triangle_size)
3022                   };
3023                   p.drawPolygon(points, 3);
3024                   text_rect.setLeft(text_rect.left() + (triangle_size >> 2));
3025                 }
3026                 if (clip->timeline_out() - clip->timeline_in() + clip->clip_in() == clip->media_length()
3027                     && clip_rect.right() - triangle_size < width()
3028                     && clip_rect.y() + triangle_size > 0
3029                     && clip_rect.right() > 0
3030                     && clip_rect.y() < height()) {
3031                   const QPoint points[3] = {
3032                     QPoint(clip_rect.right(), clip_rect.y()),
3033                     QPoint(clip_rect.right() - triangle_size, clip_rect.y()),
3034                     QPoint(clip_rect.right(), clip_rect.y() + triangle_size)
3035                   };
3036                   p.drawPolygon(points, 3);
3037                   text_rect.setRight(text_rect.right() - (triangle_size >> 2));
3038                 }
3039               }
3040 
3041               p.setBrush(Qt::NoBrush);
3042 
3043               // draw thumbnail/waveform
3044               long media_length = clip->media_length();
3045 
3046               if (clip->track() < 0) {
3047                 // draw thumbnail
3048                 int thumb_y = p.fontMetrics().height()+olive::timeline::kClipTextPadding+olive::timeline::kClipTextPadding;
3049                 if (thumb_x < width() && thumb_y < height()) {
3050                   int space_for_thumb = clip_rect.width()-1;
3051                   if (clip->opening_transition != nullptr) {
3052                     int ot_width = getScreenPointFromFrame(panel_timeline->zoom, clip->opening_transition->get_true_length());
3053                     thumb_x += ot_width;
3054                     space_for_thumb -= ot_width;
3055                   }
3056                   if (clip->closing_transition != nullptr) {
3057                     space_for_thumb -= getScreenPointFromFrame(panel_timeline->zoom, clip->closing_transition->get_true_length());
3058                   }
3059                   int thumb_height = clip_rect.height()-thumb_y;
3060                   int thumb_width = qRound(thumb_height*(double(ms->video_preview.width())/double(ms->video_preview.height())));
3061                   if (thumb_x + thumb_width >= 0
3062                       && thumb_height > thumb_y
3063                       && thumb_y + thumb_height >= 0
3064                       && space_for_thumb > MAX_TEXT_WIDTH) {
3065                     int thumb_clip_width = qMin(thumb_width, space_for_thumb);
3066                     p.drawImage(QRect(thumb_x,
3067                                       clip_rect.y()+thumb_y,
3068                                       thumb_clip_width,
3069                                       thumb_height),
3070                                 ms->video_preview,
3071                                 QRect(0,
3072                                       0,
3073                                       qRound(thumb_clip_width*(double(ms->video_preview.width())/double(thumb_width))),
3074                                       ms->video_preview.height()
3075                                       )
3076                                 );
3077                   }
3078                 }
3079                 if (clip->timeline_out() - clip->timeline_in() + clip->clip_in() > clip->media_length()) {
3080                   draw_checkerboard = true;
3081                   checkerboard_rect.setLeft(panel_timeline->getTimelineScreenPointFromFrame(clip->media_length() + clip->timeline_in() - clip->clip_in()));
3082                 }
3083               } else if (clip_rect.height() > olive::timeline::kTrackMinHeight) {
3084                 // draw waveform
3085                 p.setPen(QColor(80, 80, 80));
3086 
3087                 int waveform_start = -qMin(clip_rect.x(), 0);
3088                 int waveform_limit = qMin(clip_rect.width(), getScreenPointFromFrame(panel_timeline->zoom, media_length - clip->clip_in()));
3089 
3090                 if ((clip_rect.x() + waveform_limit) > width()) {
3091                   waveform_limit -= (clip_rect.x() + waveform_limit - width());
3092                 } else if (waveform_limit < clip_rect.width()) {
3093                   draw_checkerboard = true;
3094                   if (waveform_limit > 0) checkerboard_rect.setLeft(checkerboard_rect.left() + waveform_limit);
3095                 }
3096 
3097                 draw_waveform(clip, ms, media_length, &p, clip_rect, waveform_start, waveform_limit, panel_timeline->zoom);
3098               }
3099             }
3100             if (draw_checkerboard) {
3101               checkerboard_rect.setLeft(qMax(checkerboard_rect.left(), 0));
3102               checkerboard_rect.setRight(qMin(checkerboard_rect.right(), width()));
3103               checkerboard_rect.setTop(qMax(checkerboard_rect.top(), 0));
3104               checkerboard_rect.setBottom(qMin(checkerboard_rect.bottom(), height()));
3105 
3106               if (checkerboard_rect.left() < width()
3107                   && checkerboard_rect.right() >= 0
3108                   && checkerboard_rect.top() < height()
3109                   && checkerboard_rect.bottom() >= 0) {
3110                 // draw "error lines" if media stream is missing
3111                 p.setPen(QPen(QColor(64, 64, 64), 2));
3112                 int limit = checkerboard_rect.width();
3113                 int clip_height = checkerboard_rect.height();
3114                 for (int j=-clip_height;j<limit;j+=15) {
3115                   int lines_start_x = checkerboard_rect.left()+j;
3116                   int lines_start_y = checkerboard_rect.bottom();
3117                   int lines_end_x = lines_start_x + clip_height;
3118                   int lines_end_y = checkerboard_rect.top();
3119                   if (lines_start_x < checkerboard_rect.left()) {
3120                     lines_start_y -= (checkerboard_rect.left() - lines_start_x);
3121                     lines_start_x = checkerboard_rect.left();
3122                   }
3123                   if (lines_end_x > checkerboard_rect.right()) {
3124                     lines_end_y -= (checkerboard_rect.right() - lines_end_x);
3125                     lines_end_x = checkerboard_rect.right();
3126                   }
3127                   p.drawLine(lines_start_x, lines_start_y, lines_end_x, lines_end_y);
3128                 }
3129               }
3130             }
3131           }
3132 
3133           // draw clip markers
3134           for (int j=0;j<clip->get_markers().size();j++) {
3135             const Marker& m = clip->get_markers().at(j);
3136 
3137             // convert marker time (in clip time) to sequence time
3138             long marker_time = m.frame + clip->timeline_in() - clip->clip_in();
3139             int marker_x = panel_timeline->getTimelineScreenPointFromFrame(marker_time);
3140             if (marker_x > clip_rect.x() && marker_x < clip_rect.right()) {
3141               draw_marker(p, marker_x, clip_rect.bottom()-p.fontMetrics().height(), clip_rect.bottom(), false);
3142             }
3143           }
3144           p.setBrush(Qt::NoBrush);
3145 
3146           // draw clip transitions
3147           draw_transition(p, clip, clip_rect, text_rect, kTransitionOpening);
3148           draw_transition(p, clip, clip_rect, text_rect, kTransitionClosing);
3149 
3150           // top left bevel
3151           p.setPen(Qt::white);
3152           if (clip_rect.x() >= 0 && clip_rect.x() < width()) p.drawLine(clip_rect.bottomLeft(), clip_rect.topLeft());
3153           if (clip_rect.y() >= 0 && clip_rect.y() < height()) p.drawLine(QPoint(qMax(0, clip_rect.left()), clip_rect.top()), QPoint(qMin(width(), clip_rect.right()), clip_rect.top()));
3154 
3155           // draw text
3156           if (text_rect.width() > MAX_TEXT_WIDTH && text_rect.right() > 0 && text_rect.left() < width()) {
3157             if (!clip->enabled()) {
3158               p.setPen(Qt::gray);
3159             } else if (clip->color().lightness() > 160) {
3160               // set to black if color is bright
3161               p.setPen(Qt::black);
3162             }
3163             if (clip->linked.size() > 0) {
3164               int underline_y = olive::timeline::kClipTextPadding + p.fontMetrics().height() + clip_rect.top();
3165                 int underline_width = qMin(text_rect.width() - 1, p.fontMetrics().width(clip->name()));
3166               p.drawLine(text_rect.x(), underline_y, text_rect.x() + underline_width, underline_y);
3167             }
3168             QString name = clip->name();
3169             if (clip->speed().value != 1.0 || clip->reversed()) {
3170               name += " (";
3171               if (clip->reversed()) name += "-";
3172               name += QString::number(clip->speed().value*100) + "%)";
3173             }
3174             p.drawText(text_rect, 0, name, &text_rect);
3175           }
3176 
3177           // bottom right gray
3178           p.setPen(QColor(0, 0, 0, 128));
3179           if (clip_rect.right() >= 0 && clip_rect.right() < width()) p.drawLine(clip_rect.bottomRight(), clip_rect.topRight());
3180           if (clip_rect.bottom() >= 0 && clip_rect.bottom() < height()) p.drawLine(QPoint(qMax(0, clip_rect.left()), clip_rect.bottom()), QPoint(qMin(width(), clip_rect.right()), clip_rect.bottom()));
3181 
3182           // draw transition tool
3183           if (panel_timeline->tool == TIMELINE_TOOL_TRANSITION) {
3184 
3185             bool shared_transition = (panel_timeline->transition_tool_open_clip > -1
3186                                       && panel_timeline->transition_tool_close_clip > -1);
3187 
3188             QRect transition_tool_rect = clip_rect;
3189             bool draw_transition_tool_rect = false;
3190 
3191             if (panel_timeline->transition_tool_open_clip == i) {
3192               if (shared_transition) {
3193                 transition_tool_rect.setWidth(TRANSITION_BETWEEN_RANGE);
3194               } else {
3195                 transition_tool_rect.setWidth(transition_tool_rect.width()>>2);
3196               }
3197               draw_transition_tool_rect = true;
3198             } else if (panel_timeline->transition_tool_close_clip == i) {
3199               if (shared_transition) {
3200                 transition_tool_rect.setLeft(transition_tool_rect.right() - TRANSITION_BETWEEN_RANGE);
3201               } else {
3202                 transition_tool_rect.setLeft(transition_tool_rect.left() + (3*(transition_tool_rect.width()>>2)));
3203               }
3204               draw_transition_tool_rect = true;
3205             }
3206 
3207             if (draw_transition_tool_rect
3208                 && transition_tool_rect.left() < width()
3209                 && transition_tool_rect.right() > 0) {
3210               if (transition_tool_rect.left() < 0) {
3211                 transition_tool_rect.setLeft(0);
3212               }
3213               if (transition_tool_rect.right() > width()) {
3214                 transition_tool_rect.setRight(width());
3215               }
3216               p.fillRect(transition_tool_rect, QColor(0, 0, 0, 128));
3217             }
3218           }
3219         }
3220       }
3221     }
3222 
3223     // Draw recording clip if recording if valid
3224     if (panel_sequence_viewer->is_recording_cued() && is_track_visible(panel_sequence_viewer->recording_track)) {
3225       int rec_track_x = panel_timeline->getTimelineScreenPointFromFrame(panel_sequence_viewer->recording_start);
3226       int rec_track_y = getScreenPointFromTrack(panel_sequence_viewer->recording_track);
3227       int rec_track_height = panel_timeline->GetTrackHeight(panel_sequence_viewer->recording_track);
3228       if (panel_sequence_viewer->recording_start != panel_sequence_viewer->recording_end) {
3229         QRect rec_rect(
3230               rec_track_x,
3231               rec_track_y,
3232               getScreenPointFromFrame(panel_timeline->zoom, panel_sequence_viewer->recording_end - panel_sequence_viewer->recording_start),
3233               rec_track_height
3234               );
3235         p.setPen(QPen(QColor(96, 96, 96), 2));
3236         p.fillRect(rec_rect, QColor(192, 192, 192));
3237         p.drawRect(rec_rect);
3238       }
3239       QRect active_rec_rect(
3240             rec_track_x,
3241             rec_track_y,
3242             getScreenPointFromFrame(panel_timeline->zoom, panel_sequence_viewer->seq->playhead - panel_sequence_viewer->recording_start),
3243             rec_track_height
3244             );
3245       p.setPen(QPen(QColor(192, 0, 0), 2));
3246       p.fillRect(active_rec_rect, QColor(255, 96, 96));
3247       p.drawRect(active_rec_rect);
3248 
3249       p.setPen(Qt::NoPen);
3250 
3251       if (!panel_sequence_viewer->playing) {
3252         int rec_marker_size = 6;
3253         int rec_track_midY = rec_track_y + (rec_track_height >> 1);
3254         p.setBrush(Qt::white);
3255         QPoint cue_marker[3] = {
3256           QPoint(rec_track_x, rec_track_midY - rec_marker_size),
3257           QPoint(rec_track_x + rec_marker_size, rec_track_midY),
3258           QPoint(rec_track_x, rec_track_midY + rec_marker_size)
3259         };
3260         p.drawPolygon(cue_marker, 3);
3261       }
3262     }
3263 
3264     // Draw track lines
3265     if (olive::CurrentConfig.show_track_lines) {
3266       p.setPen(QColor(0, 0, 0, 96));
3267       audio_track_limit++;
3268       if (video_track_limit == 0) video_track_limit--;
3269 
3270       if (bottom_align) {
3271         // only draw lines for video tracks
3272         for (int i=video_track_limit;i<0;i++) {
3273           int line_y = getScreenPointFromTrack(i) - 1;
3274           p.drawLine(0, line_y, rect().width(), line_y);
3275         }
3276       } else {
3277         // only draw lines for audio tracks
3278         for (int i=0;i<audio_track_limit;i++) {
3279           int line_y = getScreenPointFromTrack(i) + panel_timeline->GetTrackHeight(i);
3280           p.drawLine(0, line_y, rect().width(), line_y);
3281         }
3282       }
3283     }
3284 
3285     // Draw selections
3286     for (int i=0;i<olive::ActiveSequence->selections.size();i++) {
3287       const Selection& s = olive::ActiveSequence->selections.at(i);
3288       if (is_track_visible(s.track)) {
3289         int selection_y = getScreenPointFromTrack(s.track);
3290         int selection_x = panel_timeline->getTimelineScreenPointFromFrame(s.in);
3291         p.setPen(Qt::NoPen);
3292         p.setBrush(Qt::NoBrush);
3293         p.fillRect(selection_x, selection_y, panel_timeline->getTimelineScreenPointFromFrame(s.out) - selection_x, panel_timeline->GetTrackHeight(s.track), QColor(0, 0, 0, 64));
3294       }
3295     }
3296 
3297     // draw rectangle select
3298     if (panel_timeline->rect_select_proc) {
3299       QRect rect_select = panel_timeline->rect_select_rect;
3300 
3301       if (bottom_align) {
3302         rect_select.translate(0, height());
3303       }
3304 
3305       draw_selection_rectangle(p, rect_select);
3306     }
3307 
3308     // Draw ghosts
3309     if (!panel_timeline->ghosts.isEmpty()) {
3310       QVector<int> insert_points;
3311       long first_ghost = LONG_MAX;
3312       for (int i=0;i<panel_timeline->ghosts.size();i++) {
3313         const Ghost& g = panel_timeline->ghosts.at(i);
3314         first_ghost = qMin(first_ghost, g.in);
3315         if (is_track_visible(g.track)) {
3316           int ghost_x = panel_timeline->getTimelineScreenPointFromFrame(g.in);
3317           int ghost_y = getScreenPointFromTrack(g.track);
3318           int ghost_width = panel_timeline->getTimelineScreenPointFromFrame(g.out) - ghost_x - 1;
3319           int ghost_height = panel_timeline->GetTrackHeight(g.track) - 1;
3320 
3321           insert_points.append(ghost_y + (ghost_height>>1));
3322 
3323           p.setPen(QColor(255, 255, 0));
3324           for (int j=0;j<olive::timeline::kGhostThickness;j++) {
3325             p.drawRect(ghost_x+j, ghost_y+j, ghost_width-j-j, ghost_height-j-j);
3326           }
3327         }
3328       }
3329 
3330       // draw insert indicator
3331       if (panel_timeline->move_insert && !insert_points.isEmpty()) {
3332         p.setBrush(Qt::white);
3333         p.setPen(Qt::NoPen);
3334         int insert_x = panel_timeline->getTimelineScreenPointFromFrame(first_ghost);
3335         int tri_size = olive::timeline::kTrackMinHeight>>2;
3336 
3337         for (int i=0;i<insert_points.size();i++) {
3338           QPoint points[3] = {
3339             QPoint(insert_x, insert_points.at(i) - tri_size),
3340             QPoint(insert_x + tri_size, insert_points.at(i)),
3341             QPoint(insert_x, insert_points.at(i) + tri_size)
3342           };
3343           p.drawPolygon(points, 3);
3344         }
3345       }
3346     }
3347 
3348 
3349     // Draw splitting cursor
3350     if (panel_timeline->splitting) {
3351       for (int i=0;i<panel_timeline->split_tracks.size();i++) {
3352         if (is_track_visible(panel_timeline->split_tracks.at(i))) {
3353           int cursor_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->drag_frame_start);
3354           int cursor_y = getScreenPointFromTrack(panel_timeline->split_tracks.at(i));
3355 
3356           p.setPen(QColor(64, 64, 64));
3357           p.drawLine(cursor_x, cursor_y, cursor_x, cursor_y + panel_timeline->GetTrackHeight(panel_timeline->split_tracks.at(i)));
3358         }
3359       }
3360     }
3361 
3362     // Draw playhead
3363     p.setPen(Qt::red);
3364     int playhead_x = panel_timeline->getTimelineScreenPointFromFrame(olive::ActiveSequence->playhead);
3365     p.drawLine(playhead_x, rect().top(), playhead_x, rect().bottom());
3366 
3367     // Draw single frame highlight
3368     int playhead_frame_width = panel_timeline->getTimelineScreenPointFromFrame(olive::ActiveSequence->playhead+1) - playhead_x;
3369     if (playhead_frame_width > 5){ //hardcoded for now, maybe better way to do this?
3370         QRectF singleFrameRect(playhead_x, rect().top(), playhead_frame_width, rect().bottom());
3371         p.fillRect(singleFrameRect, QColor(255,255,255,15));
3372     }
3373 
3374     // draw border
3375     p.setPen(QColor(0, 0, 0, 64));
3376     int edge_y = (bottom_align) ? rect().height()-1 : 0;
3377     p.drawLine(0, edge_y, rect().width(), edge_y);
3378 
3379     // draw snap point
3380     if (panel_timeline->snapped) {
3381       p.setPen(Qt::white);
3382       int snap_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->snap_point);
3383       p.drawLine(snap_x, 0, snap_x, height());
3384     }
3385 
3386     // Draw edit cursor
3387     if (current_tool_shows_cursor() && is_track_visible(panel_timeline->cursor_track)) {
3388       int cursor_x = panel_timeline->getTimelineScreenPointFromFrame(panel_timeline->cursor_frame);
3389       int cursor_y = getScreenPointFromTrack(panel_timeline->cursor_track);
3390 
3391       p.setPen(Qt::gray);
3392       p.drawLine(cursor_x, cursor_y, cursor_x, cursor_y + panel_timeline->GetTrackHeight(panel_timeline->cursor_track));
3393     }
3394   }
3395 }
3396 
resizeEvent(QResizeEvent *)3397 void TimelineWidget::resizeEvent(QResizeEvent *) {
3398   scrollBar->setPageStep(height());
3399 }
3400 
is_track_visible(int track)3401 bool TimelineWidget::is_track_visible(int track) {
3402   return (bottom_align == (track < 0));
3403 }
3404 
3405 // **************************************
3406 // screen point <-> frame/track functions
3407 // **************************************
3408 
getTrackFromScreenPoint(int y)3409 int TimelineWidget::getTrackFromScreenPoint(int y) {
3410   int track_candidate = 0;
3411 
3412   y += scroll;
3413 
3414   if (bottom_align) {
3415     y -= height();
3416   }
3417 
3418   if (y < 0) {
3419     track_candidate--;
3420   }
3421 
3422   int compounded_heights = 0;
3423 
3424   while (true) {
3425     int track_height = panel_timeline->GetTrackHeight(track_candidate);
3426     if (olive::CurrentConfig.show_track_lines) track_height++;
3427     if (y < 0) {
3428       track_height = -track_height;
3429     }
3430 
3431     int next_compounded_height = compounded_heights + track_height;
3432 
3433 
3434     if (y >= qMin(next_compounded_height, compounded_heights) && y < qMax(next_compounded_height, compounded_heights)) {
3435       return track_candidate;
3436     }
3437 
3438     compounded_heights = next_compounded_height;
3439 
3440     if (y < 0) {
3441       track_candidate--;
3442     } else {
3443       track_candidate++;
3444     }
3445   }
3446 }
3447 
getScreenPointFromTrack(int track)3448 int TimelineWidget::getScreenPointFromTrack(int track) {
3449   int point = 0;
3450 
3451   int start = (track < 0) ? -1 : 0;
3452   int interval = (track < 0) ? -1 : 1;
3453 
3454   if (track < 0) track--;
3455 
3456   for (int i=start;i!=track;i+=interval) {
3457     point += panel_timeline->GetTrackHeight(i);
3458     if (olive::CurrentConfig.show_track_lines) point++;
3459   }
3460 
3461   if (bottom_align) {
3462     return height() - point - scroll;
3463   } else {
3464     return point - scroll;
3465   }
3466 }
3467 
getClipIndexFromCoords(long frame,int track)3468 int TimelineWidget::getClipIndexFromCoords(long frame, int track) {
3469   for (int i=0;i<olive::ActiveSequence->clips.size();i++) {
3470     ClipPtr c = olive::ActiveSequence->clips.at(i);
3471     if (c != nullptr && c->track() == track && frame >= c->timeline_in() && frame < c->timeline_out()) {
3472       return i;
3473     }
3474   }
3475   return -1;
3476 }
3477 
setScroll(int s)3478 void TimelineWidget::setScroll(int s) {
3479   scroll = s;
3480   update();
3481 }
3482 
reveal_media()3483 void TimelineWidget::reveal_media() {
3484   panel_project->reveal_media(rc_reveal_media);
3485 }
3486