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