/*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "timeline.h" #include "oliveglobal.h" #include "panels/panels.h" #include "project/projectelements.h" #include "ui/timelinewidget.h" #include "ui/viewerwidget.h" #include "rendering/audio.h" #include "rendering/cacher.h" #include "rendering/renderfunctions.h" #include "io/config.h" #include "io/clipboard.h" #include "ui/timelineheader.h" #include "ui/resizablescrollbar.h" #include "ui/audiomonitor.h" #include "ui/flowlayout.h" #include "mainwindow.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include int olive::timeline::kTrackDefaultHeight = 40; int olive::timeline::kTrackMinHeight = 30; int olive::timeline::kTrackHeightIncrement = 10; Timeline::Timeline(QWidget *parent) : Panel(parent), cursor_frame(0), cursor_track(0), zoom(1.0), zoom_just_changed(false), showing_all(false), snapping(true), snapped(false), snap_point(0), selecting(false), rect_select_init(false), rect_select_proc(false), moving_init(false), moving_proc(false), move_insert(false), trim_target(-1), trim_type(TRIM_NONE), splitting(false), importing(false), importing_files(false), creating(false), transition_tool_init(false), transition_tool_proc(false), transition_tool_open_clip(-1), transition_tool_close_clip(-1), hand_moving(false), block_repaints(false), scroll(0) { setup_ui(); headers->viewer = panel_sequence_viewer; video_area->bottom_align = true; video_area->scrollBar = videoScrollbar; audio_area->scrollBar = audioScrollbar; tool_buttons.append(toolArrowButton); tool_buttons.append(toolEditButton); tool_buttons.append(toolRippleButton); tool_buttons.append(toolRazorButton); tool_buttons.append(toolSlipButton); tool_buttons.append(toolSlideButton); tool_buttons.append(toolTransitionButton); tool_buttons.append(toolHandButton); toolArrowButton->click(); connect(horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(setScroll(int))); connect(videoScrollbar, SIGNAL(valueChanged(int)), video_area, SLOT(setScroll(int))); connect(audioScrollbar, SIGNAL(valueChanged(int)), audio_area, SLOT(setScroll(int))); connect(horizontalScrollBar, SIGNAL(resize_move(double)), this, SLOT(resize_move(double))); update_sequence(); Retranslate(); } Timeline::~Timeline() {} void Timeline::Retranslate() { toolArrowButton->setToolTip(tr("Pointer Tool") + " (V)"); toolEditButton->setToolTip(tr("Edit Tool") + " (X)"); toolRippleButton->setToolTip(tr("Ripple Tool") + " (B)"); toolRazorButton->setToolTip(tr("Razor Tool") + " (C)"); toolSlipButton->setToolTip(tr("Slip Tool") + " (Y)"); toolSlideButton->setToolTip(tr("Slide Tool") + " (U)"); toolHandButton->setToolTip(tr("Hand Tool") + " (H)"); toolTransitionButton->setToolTip(tr("Transition Tool") + " (T)"); snappingButton->setToolTip(tr("Snapping") + " (S)"); zoomInButton->setToolTip(tr("Zoom In") + " (=)"); zoomOutButton->setToolTip(tr("Zoom Out") + " (-)"); recordButton->setToolTip(tr("Record audio")); addButton->setToolTip(tr("Add title, solid, bars, etc.")); UpdateTitle(); } void Timeline::previous_cut() { if (olive::ActiveSequence != nullptr && olive::ActiveSequence->playhead > 0) { long p_cut = 0; for (int i=0;iclips.size();i++) { ClipPtr c = olive::ActiveSequence->clips.at(i); if (c != nullptr) { if (c->timeline_out() > p_cut && c->timeline_out() < olive::ActiveSequence->playhead) { p_cut = c->timeline_out(); } else if (c->timeline_in() > p_cut && c->timeline_in() < olive::ActiveSequence->playhead) { p_cut = c->timeline_in(); } } } panel_sequence_viewer->seek(p_cut); } } void Timeline::next_cut() { if (olive::ActiveSequence != nullptr) { bool seek_enabled = false; long n_cut = LONG_MAX; for (int i=0;iclips.size();i++) { ClipPtr c = olive::ActiveSequence->clips.at(i); if (c != nullptr) { if (c->timeline_in() < n_cut && c->timeline_in() > olive::ActiveSequence->playhead) { n_cut = c->timeline_in(); seek_enabled = true; } else if (c->timeline_out() < n_cut && c->timeline_out() > olive::ActiveSequence->playhead) { n_cut = c->timeline_out(); seek_enabled = true; } } } if (seek_enabled) panel_sequence_viewer->seek(n_cut); } } void ripple_clips(ComboAction* ca, SequencePtr s, long point, long length, const QVector& ignore) { ca->append(new RippleAction(s, point, length, ignore)); } void Timeline::toggle_show_all() { if (olive::ActiveSequence != nullptr) { showing_all = !showing_all; if (showing_all) { old_zoom = zoom; set_zoom_value(double(timeline_area->width() - 200) / double(olive::ActiveSequence->getEndFrame())); } else { set_zoom_value(old_zoom); } } } void Timeline::create_ghosts_from_media(SequencePtr seq, long entry_point, QVector& media_list) { video_ghosts = false; audio_ghosts = false; for (int i=0;iget_type()) { case MEDIA_TYPE_FOOTAGE: m = medium->to_footage(); can_import = m->ready; if (m->using_inout) { double source_fr = 30; if (m->video_tracks.size() > 0 && !qIsNull(m->video_tracks.at(0).video_frame_rate)) source_fr = m->video_tracks.at(0).video_frame_rate * m->speed; default_clip_in = rescale_frame_number(m->in, source_fr, seq->frame_rate); default_clip_out = rescale_frame_number(m->out, source_fr, seq->frame_rate); } break; case MEDIA_TYPE_SEQUENCE: s = medium->to_sequence(); sequence_length = s->getEndFrame(); if (seq != nullptr) sequence_length = rescale_frame_number(sequence_length, s->frame_rate, seq->frame_rate); can_import = (s != seq && sequence_length != 0); if (s->using_workarea) { default_clip_in = rescale_frame_number(s->workarea_in, s->frame_rate, seq->frame_rate); default_clip_out = rescale_frame_number(s->workarea_out, s->frame_rate, seq->frame_rate); } break; default: can_import = false; } if (can_import) { Ghost g; g.clip = -1; g.trim_type = TRIM_NONE; g.old_clip_in = g.clip_in = default_clip_in; g.media = medium; g.in = entry_point; g.transition = nullptr; switch (medium->get_type()) { case MEDIA_TYPE_FOOTAGE: // is video source a still image? if (m->video_tracks.size() > 0 && m->video_tracks.at(0).infinite_length && m->audio_tracks.size() == 0) { g.out = g.in + 100; } else { long length = m->get_length_in_frames(seq->frame_rate); g.out = entry_point + length - default_clip_in; if (m->using_inout) { g.out -= (length - default_clip_out); } } for (int j=0;jaudio_tracks.size();j++) { if (m->audio_tracks.at(j).enabled) { g.track = j; g.media_stream = m->audio_tracks.at(j).file_index; ghosts.append(g); audio_ghosts = true; } } for (int j=0;jvideo_tracks.size();j++) { if (m->video_tracks.at(j).enabled) { g.track = -1-j; g.media_stream = m->video_tracks.at(j).file_index; ghosts.append(g); video_ghosts = true; } } break; case MEDIA_TYPE_SEQUENCE: g.out = entry_point + sequence_length - default_clip_in; if (s->using_workarea) { g.out -= (sequence_length - default_clip_out); } g.track = -1; ghosts.append(g); g.track = 0; ghosts.append(g); video_ghosts = true; audio_ghosts = true; break; } entry_point = g.out; } } for (int i=0;i added_clips; for (int i=0;i(s); c->set_media(g.media, g.media_stream); c->set_timeline_in(g.in); c->set_timeline_out(g.out); c->set_clip_in(g.clip_in); c->set_track(g.track); if (c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { FootagePtr m = c->media()->to_footage(); if (m->video_tracks.size() == 0) { // audio only (greenish) c->set_color(128, 192, 128); } else if (m->audio_tracks.size() == 0) { // video only (orangeish) c->set_color(192, 160, 128); } else { // video and audio (blueish) c->set_color(128, 128, 192); } c->set_name(m->name); } else if (c->media()->get_type() == MEDIA_TYPE_SEQUENCE) { // sequence (red?ish?) c->set_color(192, 128, 128); SequencePtr media = c->media()->to_sequence(); c->set_name(media->name); } c->refresh(); added_clips.append(c); } ca->append(new AddClipCommand(s, added_clips)); // link clips from the same media for (int i=0;imedia() == cc->media()) { c->linked.append(j); } } if (olive::CurrentConfig.add_default_effects_to_clips) { if (c->track() < 0) { // add default video effects c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_TRANSFORM, EFFECT_TYPE_EFFECT))); } else { // add default audio effects c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_VOLUME, EFFECT_TYPE_EFFECT))); c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_PAN, EFFECT_TYPE_EFFECT))); } } } if (olive::CurrentConfig.enable_seek_to_import) { panel_sequence_viewer->seek(earliest_point); } ghosts.clear(); importing = false; snapped = false; } void Timeline::add_transition() { ComboAction* ca = new ComboAction(); bool adding = false; for (int i=0;iclips.size();i++) { Clip* c = olive::ActiveSequence->clips.at(i).get(); if (c != nullptr && olive::ActiveSequence->IsClipSelected(c, true)) { int transition_to_add = (c->track() < 0) ? TRANSITION_INTERNAL_CROSSDISSOLVE : TRANSITION_INTERNAL_LINEARFADE; if (c->opening_transition == nullptr) { ca->append(new AddTransitionCommand(c, nullptr, nullptr, Effect::GetInternalMeta(transition_to_add, EFFECT_TYPE_TRANSITION), olive::CurrentConfig.default_transition_length)); adding = true; } if (c->closing_transition == nullptr) { ca->append(new AddTransitionCommand(nullptr, c, nullptr, Effect::GetInternalMeta(transition_to_add, EFFECT_TYPE_TRANSITION), olive::CurrentConfig.default_transition_length)); adding = true; } } } if (adding) { olive::UndoStack.push(ca); } else { delete ca; } update_ui(true); } void Timeline::nest() { if (olive::ActiveSequence != nullptr) { // get selected clips QVector selected_clips = olive::ActiveSequence->SelectedClipIndexes(); // nest them if (!selected_clips.isEmpty()) { // get earliest point in selected clips long earliest_point = LONG_MAX; for (int i=0;iclips.at(selected_clips.at(i))->timeline_in(), earliest_point); } ComboAction* ca = new ComboAction(); // create "nest" sequence with the same attributes as the current sequence SequencePtr s(new Sequence()); s->name = panel_project->get_next_sequence_name(tr("Nested Sequence")); s->width = olive::ActiveSequence->width; s->height = olive::ActiveSequence->height; s->frame_rate = olive::ActiveSequence->frame_rate; s->audio_frequency = olive::ActiveSequence->audio_frequency; s->audio_layout = olive::ActiveSequence->audio_layout; // copy all selected clips to the nest for (int i=0;iappend(new DeleteClipAction(olive::ActiveSequence, selected_clips.at(i))); // copy to new ClipPtr copy(olive::ActiveSequence->clips.at(selected_clips.at(i))->copy(s)); copy->set_timeline_in(copy->timeline_in() - earliest_point); copy->set_timeline_out(copy->timeline_out() - earliest_point); s->clips.append(copy); } // relink clips in new nested sequences relink_clips_using_ids(selected_clips, s->clips); // add sequence to project Media* m = panel_project->create_sequence_internal(ca, s, false, nullptr); // add nested sequence to active sequence QVector media_list; media_list.append(m); create_ghosts_from_media(olive::ActiveSequence, earliest_point, media_list); add_clips_from_ghosts(ca, olive::ActiveSequence); panel_effect_controls->clear_effects(true); olive::ActiveSequence->selections.clear(); olive::UndoStack.push(ca); update_ui(true); } } } void Timeline::update_sequence() { bool null_sequence = (olive::ActiveSequence == nullptr); for (int i=0;isetEnabled(!null_sequence); } snappingButton->setEnabled(!null_sequence); zoomInButton->setEnabled(!null_sequence); zoomOutButton->setEnabled(!null_sequence); recordButton->setEnabled(!null_sequence); addButton->setEnabled(!null_sequence); headers->setEnabled(!null_sequence); UpdateTitle(); } int Timeline::get_snap_range() { return getFrameFromScreenPoint(zoom, 10); } bool Timeline::focused() { return (olive::ActiveSequence != nullptr && (headers->hasFocus() || video_area->hasFocus() || audio_area->hasFocus())); } void Timeline::repaint_timeline() { if (!block_repaints) { bool draw = true; if (olive::ActiveSequence != nullptr && !horizontalScrollBar->isSliderDown() && !horizontalScrollBar->is_resizing() && panel_sequence_viewer->playing && !zoom_just_changed) { // auto scroll if (olive::CurrentConfig.autoscroll == olive::AUTOSCROLL_PAGE_SCROLL) { int playhead_x = getTimelineScreenPointFromFrame(olive::ActiveSequence->playhead); if (playhead_x < 0 || playhead_x > (editAreas->width() - videoScrollbar->width())) { horizontalScrollBar->setValue(getScreenPointFromFrame(zoom, olive::ActiveSequence->playhead)); draw = false; } } else if (olive::CurrentConfig.autoscroll == olive::AUTOSCROLL_SMOOTH_SCROLL) { if (center_scroll_to_playhead(horizontalScrollBar, zoom, olive::ActiveSequence->playhead)) { draw = false; } } } if (draw) { headers->update(); video_area->update(); audio_area->update(); if (olive::ActiveSequence != nullptr && !zoom_just_changed) { set_sb_max(); } } zoom_just_changed = false; } } void Timeline::select_all() { if (olive::ActiveSequence != nullptr) { olive::ActiveSequence->selections.clear(); for (int i=0;iclips.size();i++) { ClipPtr c = olive::ActiveSequence->clips.at(i); if (c != nullptr) { Selection s; s.in = c->timeline_in(); s.out = c->timeline_out(); s.track = c->track(); olive::ActiveSequence->selections.append(s); } } repaint_timeline(); } } void Timeline::scroll_to_frame(long frame) { scroll_to_frame_internal(horizontalScrollBar, frame, zoom, timeline_area->width()); } void Timeline::select_from_playhead() { olive::ActiveSequence->selections.clear(); for (int i=0;iclips.size();i++) { ClipPtr c = olive::ActiveSequence->clips.at(i); if (c != nullptr && c->timeline_in() <= olive::ActiveSequence->playhead && c->timeline_out() > olive::ActiveSequence->playhead) { Selection s; s.in = c->timeline_in(); s.out = c->timeline_out(); s.track = c->track(); olive::ActiveSequence->selections.append(s); } } } bool Timeline::can_ripple_empty_space(long frame, int track) { bool can_ripple_delete = true; bool at_end_of_sequence = true; rc_ripple_min = 0; rc_ripple_max = LONG_MAX; for (int i=0;iclips.size();i++) { ClipPtr c = olive::ActiveSequence->clips.at(i); if (c != nullptr) { if (c->timeline_in() > frame || c->timeline_out() > frame) { at_end_of_sequence = false; } if (c->track() == track) { if (c->timeline_in() <= frame && c->timeline_out() >= frame) { can_ripple_delete = false; break; } else if (c->timeline_out() < frame) { rc_ripple_min = qMax(rc_ripple_min, c->timeline_out()); } else if (c->timeline_in() > frame) { rc_ripple_max = qMin(rc_ripple_max, c->timeline_in()); } } } } return (can_ripple_delete && !at_end_of_sequence); } void Timeline::ripple_delete_empty_space() { QVector sels; Selection s; s.in = rc_ripple_min; s.out = rc_ripple_max; s.track = cursor_track; sels.append(s); delete_selection(sels, true); } void Timeline::resizeEvent(QResizeEvent *) { // adjust maximum scrollbar if (olive::ActiveSequence != nullptr) set_sb_max(); // resize tool button widget to its contents QList tool_button_children = tool_button_widget->findChildren(); int horizontal_spacing = static_cast(tool_button_widget->layout())->horizontalSpacing(); int vertical_spacing = static_cast(tool_button_widget->layout())->verticalSpacing(); int total_area = tool_button_widget->height(); int button_count = tool_button_children.size(); int button_height = tool_button_children.at(0)->sizeHint().height() + vertical_spacing; int cols = 0; int col_height; if (button_height < total_area) { do { cols++; col_height = (qCeil(double(button_count)/double(cols))*button_height)-vertical_spacing; } while (col_height > total_area); } else { cols = button_count; } tool_button_widget->setFixedWidth((tool_button_children.at(0)->sizeHint().width())*cols + horizontal_spacing*(cols-1) + 1); } void Timeline::delete_in_out_internal(bool ripple) { if (olive::ActiveSequence != nullptr && olive::ActiveSequence->using_workarea) { QVector areas; int video_tracks = 0, audio_tracks = 0; olive::ActiveSequence->getTrackLimits(&video_tracks, &audio_tracks); for (int i=video_tracks;i<=audio_tracks;i++) { Selection s; s.in = olive::ActiveSequence->workarea_in; s.out = olive::ActiveSequence->workarea_out; s.track = i; areas.append(s); } ComboAction* ca = new ComboAction(); delete_areas_and_relink(ca, areas, true); if (ripple) ripple_clips(ca, olive::ActiveSequence, olive::ActiveSequence->workarea_in, olive::ActiveSequence->workarea_in - olive::ActiveSequence->workarea_out); ca->append(new SetTimelineInOutCommand(olive::ActiveSequence, false, 0, 0)); olive::UndoStack.push(ca); update_ui(true); } } void Timeline::toggle_enable_on_selected_clips() { if (olive::ActiveSequence != nullptr) { // get currently selected clips QVector selected_clips = olive::ActiveSequence->SelectedClips(); if (!selected_clips.isEmpty()) { // if clips are selected, create an undoable action SetClipProperty* set_action = new SetClipProperty(kSetClipPropertyEnabled); // add each selected clip to the action for (int i=0;iAddSetting(c, !c->enabled()); } // push the action olive::UndoStack.push(set_action); update_ui(false); } } } void Timeline::delete_selection(QVector& selections, bool ripple_delete) { if (selections.size() > 0) { panel_effect_controls->clear_effects(true); ComboAction* ca = new ComboAction(); // delete the areas currently selected by `selections` // if we're ripple deleting, we don't want to delete the selections since we still need them for the ripple delete_areas_and_relink(ca, selections, !ripple_delete); if (ripple_delete) { long ripple_point = selections.at(0).in; long ripple_length = selections.at(0).out - selections.at(0).in; // retrieve ripple_point and ripple_length from current selection for (int i=1;iclips.size();i++) { ClipPtr c = olive::ActiveSequence->clips.at(i); if (c != nullptr && c->timeline_in() < ripple_point && c->timeline_out() > ripple_point) { // conflict detected, but this clip may be getting deleted so let's check bool deleted = false; for (int j=0;jtrack() && !(c->timeline_in() < s.in && c->timeline_out() < s.in) && !(c->timeline_in() > s.out && c->timeline_out() > s.out)) { deleted = true; break; } } if (!deleted) { for (int j=0;jclips.size();j++) { ClipPtr cc = olive::ActiveSequence->clips.at(j); if (cc != nullptr && cc->track() == c->track() && cc->timeline_in() > c->timeline_out() && cc->timeline_in() < c->timeline_out() + ripple_length) { ripple_length = cc->timeline_in() - c->timeline_out(); } } } } } if (can_ripple) { ripple_clips(ca, olive::ActiveSequence, ripple_point, -ripple_length); panel_sequence_viewer->seek(ripple_point-1); } // if we're rippling, we can clear the selections here - if we're not rippling, delete_areas_and_relink() will // clear the selections for us selections.clear(); } olive::UndoStack.push(ca); update_ui(true); } } void Timeline::set_zoom_value(double v) { // set zoom value zoom = v; // update header zoom to match headers->update_zoom(zoom); // set flag that zoom has just changed to prevent auto-scrolling since we change the scroll below zoom_just_changed = true; // set scrollbar to center the playhead if (olive::ActiveSequence != nullptr) { // update scrollbar maximum value for new zoom set_sb_max(); if (!horizontalScrollBar->is_resizing()) { center_scroll_to_playhead(horizontalScrollBar, zoom, olive::ActiveSequence->playhead); } } // repaint the timeline for the new zoom/location repaint_timeline(); } void Timeline::multiply_zoom(double m) { showing_all = false; set_zoom_value(zoom * m); } void Timeline::decheck_tool_buttons(QObject* sender) { for (int i=0;isetChecked(tool_buttons.at(i) == sender); } } void Timeline::zoom_in() { multiply_zoom(2.0); } void Timeline::zoom_out() { multiply_zoom(0.5); } int Timeline::GetTrackHeight(int track) { for (int i=0;iclips.at(p).get(); if (pre != nullptr) { if (pre->timeline_in() < frame && pre->timeline_out() > frame) { // duplicate clip without duplicating its transitions, we'll restore them later ClipPtr post = ClipPtr(pre->copy(olive::ActiveSequence)); long new_clip_length = frame - pre->timeline_in(); post->set_timeline_in(post_in); post->set_clip_in(pre->clip_in() + (post->timeline_in() - pre->timeline_in())); pre->move(ca, pre->timeline_in(), frame, pre->clip_in(), pre->track(), false); if (transitions) { // check if this clip has a closing transition if (pre->closing_transition != nullptr) { // if so, move closing transition to the post clip post->closing_transition = pre->closing_transition; // and set the original clip's closing transition to nothing ca->append(new SetPointer(reinterpret_cast(&pre->closing_transition), nullptr)); // and set the transition's reference to the post clip if (post->closing_transition->parent_clip == pre) { ca->append(new SetPointer(reinterpret_cast(&post->closing_transition->parent_clip), post.get())); } if (post->closing_transition->secondary_clip == pre) { ca->append(new SetPointer(reinterpret_cast(&post->closing_transition->secondary_clip), post.get())); } // and make sure it's at the correct size to the closing clip if (post->closing_transition != nullptr && post->closing_transition->get_true_length() > post->length()) { ca->append(new ModifyTransitionCommand(post->closing_transition, post->length())); post->closing_transition->set_length(post->length()); } } // we're keeping the opening clip, so ensure that's a correct size too if (pre->opening_transition != nullptr && pre->opening_transition->get_true_length() > new_clip_length) { ca->append(new ModifyTransitionCommand(pre->opening_transition, new_clip_length)); } } return post; } else if (frame == pre->timeline_in() && pre->opening_transition != nullptr && pre->opening_transition->secondary_clip != nullptr) { // special case for shared transitions to split it into two // set transition to single-clip mode ca->append(new SetPointer(reinterpret_cast(&pre->opening_transition->secondary_clip), nullptr)); // clone transition for other clip ca->append(new AddTransitionCommand(nullptr, pre->opening_transition->secondary_clip, pre->opening_transition, nullptr, 0) ); } } return nullptr; } bool Timeline::split_clip_and_relink(ComboAction *ca, int clip, long frame, bool relink) { // see if we split this clip before if (split_cache.contains(clip)) { return false; } split_cache.append(clip); Clip* c = olive::ActiveSequence->clips.at(clip).get(); if (c != nullptr) { QVector pre_clips; QVector post_clips; ClipPtr post = split_clip(ca, true, clip, frame); if (post == nullptr) { return false; } else { post_clips.append(post); // if alt is not down, split clips links too if (relink) { pre_clips.append(clip); bool original_clip_is_selected = olive::ActiveSequence->IsClipSelected(c, true); // find linked clips of old clip for (int i=0;ilinked.size();i++) { int l = c->linked.at(i); if (!split_cache.contains(l)) { Clip* link = olive::ActiveSequence->clips.at(l).get(); if ((original_clip_is_selected && olive::ActiveSequence->IsClipSelected(link, true)) || !original_clip_is_selected) { split_cache.append(l); ClipPtr s = split_clip(ca, true, l, frame); if (s != nullptr) { pre_clips.append(l); post_clips.append(s); } } } } relink_clips_using_ids(pre_clips, post_clips); } ca->append(new AddClipCommand(olive::ActiveSequence, post_clips)); return true; } } return false; } void Timeline::clean_up_selections(QVector& areas) { for (int i=0;i ss.out) { // do nothing } else if (s.in >= ss.in && s.out <= ss.out) { remove = true; } else if (s.in <= ss.out && s.out > ss.out) { ss.out = s.out; remove = true; } else if (s.out >= ss.in && s.in < ss.in) { ss.in = s.in; remove = true; } if (remove) { areas.removeAt(i); i--; break; } } } } } } bool selection_contains_transition(const Selection& s, Clip* c, int type) { if (type == kTransitionOpening) { return c->opening_transition != nullptr && s.out == c->timeline_in() + c->opening_transition->get_true_length() && ((c->opening_transition->secondary_clip == nullptr && s.in == c->timeline_in()) || (c->opening_transition->secondary_clip != nullptr && s.in == c->timeline_in() - c->opening_transition->get_true_length())); } else { return c->closing_transition != nullptr && s.in == c->timeline_out() - c->closing_transition->get_true_length() && ((c->closing_transition->secondary_clip == nullptr && s.out == c->timeline_out()) || (c->closing_transition->secondary_clip != nullptr && s.out == c->timeline_out() + c->closing_transition->get_true_length())); } } void Timeline::delete_areas_and_relink(ComboAction* ca, QVector& areas, bool deselect_areas) { clean_up_selections(areas); panel_effect_controls->clear_effects(true); QVector pre_clips; QVector post_clips; for (int i=0;iclips.size();j++) { Clip* c = olive::ActiveSequence->clips.at(j).get(); if (c != nullptr && c->track() == s.track && !c->undeletable) { if (selection_contains_transition(s, c, kTransitionOpening)) { // delete opening transition ca->append(new DeleteTransitionCommand(c->opening_transition)); } else if (selection_contains_transition(s, c, kTransitionClosing)) { // delete closing transition ca->append(new DeleteTransitionCommand(c->closing_transition)); } else if (c->timeline_in() >= s.in && c->timeline_out() <= s.out) { // clips falls entirely within deletion area ca->append(new DeleteClipAction(olive::ActiveSequence, j)); } else if (c->timeline_in() < s.in && c->timeline_out() > s.out) { // middle of clip is within deletion area // duplicate clip ClipPtr post = split_clip(ca, true, j, s.in, s.out); pre_clips.append(j); post_clips.append(post); } else if (c->timeline_in() < s.in && c->timeline_out() > s.in) { // only out point is in deletion area c->move(ca, c->timeline_in(), s.in, c->clip_in(), c->track()); if (c->closing_transition != nullptr) { if (s.in < c->timeline_out() - c->closing_transition->get_true_length()) { ca->append(new DeleteTransitionCommand(c->closing_transition)); } else { ca->append(new ModifyTransitionCommand(c->closing_transition, c->closing_transition->get_true_length() - (c->timeline_out() - s.in))); } } } else if (c->timeline_in() < s.out && c->timeline_out() > s.out) { // only in point is in deletion area c->move(ca, s.out, c->timeline_out(), c->clip_in() + (s.out - c->timeline_in()), c->track()); if (c->opening_transition != nullptr) { if (s.out > c->timeline_in() + c->opening_transition->get_true_length()) { ca->append(new DeleteTransitionCommand(c->opening_transition)); } else { ca->append(new ModifyTransitionCommand(c->opening_transition, c->opening_transition->get_true_length() - (s.out - c->timeline_in()))); } } } } } } // deselect selected clip areas if (deselect_areas) { QVector area_copy = areas; for (int i=0;iappend(new AddClipCommand(olive::ActiveSequence, post_clips)); } void Timeline::copy(bool del) { bool cleared = false; bool copied = false; long min_in = 0; for (int i=0;iclips.size();i++) { ClipPtr c = olive::ActiveSequence->clips.at(i); if (c != nullptr) { for (int j=0;jselections.size();j++) { const Selection& s = olive::ActiveSequence->selections.at(j); if (s.track == c->track() && !((c->timeline_in() <= s.in && c->timeline_out() <= s.in) || (c->timeline_in() >= s.out && c->timeline_out() >= s.out))) { if (!cleared) { clear_clipboard(); cleared = true; clipboard_type = CLIPBOARD_TYPE_CLIP; } ClipPtr copied_clip = c->copy(nullptr); // copy linked IDs (we correct these later in paste()) copied_clip->linked = c->linked; if (copied_clip->timeline_in() < s.in) { copied_clip->set_clip_in(copied_clip->clip_in() + (s.in - copied_clip->timeline_in())); copied_clip->set_timeline_in(s.in); } if (copied_clip->timeline_out() > s.out) { copied_clip->set_timeline_out(s.out); } if (copied) { min_in = qMin(min_in, s.in); } else { min_in = s.in; copied = true; } copied_clip->load_id = i; clipboard.append(copied_clip); } } } } for (int i=0;i(clipboard.at(i)); c->set_timeline_in(c->timeline_in() - min_in); c->set_timeline_out(c->timeline_out() - min_in); } if (del && copied) { delete_selection(olive::ActiveSequence->selections, false); } } void Timeline::relink_clips_using_ids(QVector& old_clips, QVector& new_clips) { // relink pasted clips for (int i=0;iclips.at(old_clips.at(i)); for (int j=0;jlinked.size();j++) { for (int k=0;klinked.at(j) == old_clips.at(k)) { if (new_clips.at(i) != nullptr) { new_clips.at(i)->linked.append(k); } } } } } } void Timeline::paste(bool insert) { if (clipboard.size() > 0) { if (clipboard_type == CLIPBOARD_TYPE_CLIP) { ComboAction* ca = new ComboAction(); // create copies and delete areas that we'll be pasting to QVector delete_areas; QVector pasted_clips; long paste_start = LONG_MAX; long paste_end = LONG_MIN; for (int i=0;i(clipboard.at(i)); // create copy of clip and offset by playhead ClipPtr cc(c->copy(olive::ActiveSequence)); // convert frame rates cc->set_timeline_in(rescale_frame_number(cc->timeline_in(), c->cached_frame_rate(), olive::ActiveSequence->frame_rate)); cc->set_timeline_out(rescale_frame_number(cc->timeline_out(), c->cached_frame_rate(), olive::ActiveSequence->frame_rate)); cc->set_clip_in(rescale_frame_number(cc->clip_in(), c->cached_frame_rate(), olive::ActiveSequence->frame_rate)); cc->set_timeline_in(cc->timeline_in() + olive::ActiveSequence->playhead); cc->set_timeline_out(cc->timeline_out() + olive::ActiveSequence->playhead); cc->set_track(c->track()); paste_start = qMin(paste_start, cc->timeline_in()); paste_end = qMax(paste_end, cc->timeline_out()); pasted_clips.append(cc); if (!insert) { Selection s; s.in = cc->timeline_in(); s.out = cc->timeline_out(); s.track = c->track(); delete_areas.append(s); } } if (insert) { split_cache.clear(); split_all_clips_at_point(ca, olive::ActiveSequence->playhead); ripple_clips(ca, olive::ActiveSequence, paste_start, paste_end - paste_start); } else { delete_areas_and_relink(ca, delete_areas, false); } // correct linked clips for (int i=0;i(clipboard.at(i)); for (int j=0;jlinked.size();j++) { for (int k=0;k(clipboard.at(k)); if (comp->load_id == oc->linked.at(j)) { pasted_clips.at(i)->linked.append(k); } } } } ca->append(new AddClipCommand(olive::ActiveSequence, pasted_clips)); olive::UndoStack.push(ca); update_ui(true); if (olive::CurrentConfig.paste_seeks) { panel_sequence_viewer->seek(paste_end); } } else if (clipboard_type == CLIPBOARD_TYPE_EFFECT) { ComboAction* ca = new ComboAction(); bool replace = false; bool skip = false; bool ask_conflict = true; QVector selected_clips = olive::ActiveSequence->SelectedClips(); for (int i=0;i(clipboard.at(j)); if ((c->track() < 0) == (e->meta->subtype == EFFECT_TYPE_VIDEO)) { int found = -1; if (ask_conflict) { replace = false; skip = false; } for (int k=0;keffects.size();k++) { if (c->effects.at(k)->meta == e->meta) { found = k; break; } } if (found >= 0 && ask_conflict) { QMessageBox box(this); box.setWindowTitle(tr("Effect already exists")); box.setText(tr("Clip '%1' already contains a '%2' effect. " "Would you like to replace it with the pasted one or add it as a separate effect?") .arg(c->name(), e->meta->name)); box.setIcon(QMessageBox::Icon::Question); box.addButton(tr("Add"), QMessageBox::YesRole); QPushButton* replace_button = box.addButton(tr("Replace"), QMessageBox::NoRole); QPushButton* skip_button = box.addButton(tr("Skip"), QMessageBox::RejectRole); QCheckBox* future_box = new QCheckBox(tr("Do this for all conflicts found"), &box); box.setCheckBox(future_box); box.exec(); if (box.clickedButton() == replace_button) { replace = true; } else if (box.clickedButton() == skip_button) { skip = true; } ask_conflict = !future_box->isChecked(); } if (found >= 0 && skip) { // do nothing } else if (found >= 0 && replace) { EffectDeleteCommand* delcom = new EffectDeleteCommand(); delcom->clips.append(c); delcom->fx.append(found); ca->append(delcom); ca->append(new AddEffectCommand(c, e->copy(c), nullptr, found)); } else { ca->append(new AddEffectCommand(c, e->copy(c), nullptr)); } } } } if (ca->hasActions()) { ca->appendPost(new ReloadEffectsCommand()); olive::UndoStack.push(ca); } else { delete ca; } update_ui(true); } } } void Timeline::edit_to_point_internal(bool in, bool ripple) { if (olive::ActiveSequence != nullptr) { if (olive::ActiveSequence->clips.size() > 0) { // get track count int track_min = INT_MAX; int track_max = INT_MIN; long sequence_end = 0; bool playhead_falls_on_in = false; bool playhead_falls_on_out = false; long next_cut = LONG_MAX; long prev_cut = 0; // find closest in point to playhead for (int i=0;iclips.size();i++) { ClipPtr c = olive::ActiveSequence->clips.at(i); if (c != nullptr) { track_min = qMin(track_min, c->track()); track_max = qMax(track_max, c->track()); sequence_end = qMax(c->timeline_out(), sequence_end); if (c->timeline_in() == olive::ActiveSequence->playhead) playhead_falls_on_in = true; if (c->timeline_out() == olive::ActiveSequence->playhead) playhead_falls_on_out = true; if (c->timeline_in() > olive::ActiveSequence->playhead) next_cut = qMin(c->timeline_in(), next_cut); if (c->timeline_out() > olive::ActiveSequence->playhead) next_cut = qMin(c->timeline_out(), next_cut); if (c->timeline_in() < olive::ActiveSequence->playhead) prev_cut = qMax(c->timeline_in(), prev_cut); if (c->timeline_out() < olive::ActiveSequence->playhead) prev_cut = qMax(c->timeline_out(), prev_cut); } } next_cut = qMin(sequence_end, next_cut); QVector areas; ComboAction* ca = new ComboAction(); bool push_undo = true; long seek = olive::ActiveSequence->playhead; if ((in && (playhead_falls_on_out || (playhead_falls_on_in && olive::ActiveSequence->playhead == 0))) || (!in && (playhead_falls_on_in || (playhead_falls_on_out && olive::ActiveSequence->playhead == sequence_end)))) { // one frame mode if (ripple) { // set up deletion areas based on track count long in_point = olive::ActiveSequence->playhead; if (!in) { in_point--; seek--; } if (in_point >= 0) { Selection s; s.in = in_point; s.out = in_point + 1; for (int i=track_min;i<=track_max;i++) { s.track = i; areas.append(s); } // trim and move clips around the in point delete_areas_and_relink(ca, areas, true); if (ripple) ripple_clips(ca, olive::ActiveSequence, in_point, -1); } else { push_undo = false; } } else { push_undo = false; } } else { // set up deletion areas based on track count Selection s; if (in) seek = prev_cut; s.in = in ? prev_cut : olive::ActiveSequence->playhead; s.out = in ? olive::ActiveSequence->playhead : next_cut; if (s.in == s.out) { push_undo = false; } else { for (int i=track_min;i<=track_max;i++) { s.track = i; areas.append(s); } // trim and move clips around the in point delete_areas_and_relink(ca, areas, true); if (ripple) ripple_clips(ca, olive::ActiveSequence, s.in, s.in - s.out); } } if (push_undo) { olive::UndoStack.push(ca); update_ui(true); if (seek != olive::ActiveSequence->playhead && ripple) { panel_sequence_viewer->seek(seek); } } else { delete ca; } } else { panel_sequence_viewer->seek(0); } } } bool Timeline::split_selection(ComboAction* ca) { bool split = false; // temporary relinking vectors QVector pre_splits; QVector post_splits; QVector secondary_post_splits; // find clips within selection and split for (int j=0;jclips.size();j++) { ClipPtr clip = olive::ActiveSequence->clips.at(j); if (clip != nullptr) { for (int i=0;iselections.size();i++) { const Selection& s = olive::ActiveSequence->selections.at(i); if (s.track == clip->track()) { ClipPtr post_b = split_clip(ca, true, j, s.out); ClipPtr post_a = split_clip(ca, true, j, s.in); pre_splits.append(j); post_splits.append(post_a); secondary_post_splits.append(post_b); if (post_a != nullptr) { post_a->set_timeline_out(qMin(post_a->timeline_out(), s.out)); } split = true; } } } } if (split) { // relink after splitting relink_clips_using_ids(pre_splits, post_splits); relink_clips_using_ids(pre_splits, secondary_post_splits); ca->append(new AddClipCommand(olive::ActiveSequence, post_splits)); ca->append(new AddClipCommand(olive::ActiveSequence, secondary_post_splits)); return true; } return false; } bool Timeline::split_all_clips_at_point(ComboAction* ca, long point) { bool split = false; for (int j=0;jclips.size();j++) { ClipPtr c = olive::ActiveSequence->clips.at(j); if (c != nullptr) { // always relinks if (split_clip_and_relink(ca, j, point, true)) { split = true; } } } return split; } void Timeline::split_at_playhead() { ComboAction* ca = new ComboAction(); bool split_selected = false; split_cache.clear(); if (olive::ActiveSequence->selections.size() > 0) { // see if whole clips are selected QVector pre_clips; QVector post_clips; for (int j=0;jclips.size();j++) { Clip* clip = olive::ActiveSequence->clips.at(j).get(); if (clip != nullptr && olive::ActiveSequence->IsClipSelected(clip, true)) { ClipPtr s = split_clip(ca, true, j, olive::ActiveSequence->playhead); if (s != nullptr) { pre_clips.append(j); post_clips.append(s); split_selected = true; } } } if (split_selected) { // relink clips if we split relink_clips_using_ids(pre_clips, post_clips); ca->append(new AddClipCommand(olive::ActiveSequence, post_clips)); } else { // split a selection if not split_selected = split_selection(ca); } } // if nothing was selected or no selections fell within playhead, simply split at playhead if (!split_selected) { split_selected = split_all_clips_at_point(ca, olive::ActiveSequence->playhead); } if (split_selected) { olive::UndoStack.push(ca); update_ui(true); } else { delete ca; } } void Timeline::ripple_delete() { if (olive::ActiveSequence != nullptr) { if (olive::ActiveSequence->selections.size() > 0) { panel_timeline->delete_selection(olive::ActiveSequence->selections, true); } else if (olive::CurrentConfig.hover_focus && get_focused_panel() == panel_timeline) { if (panel_timeline->can_ripple_empty_space(panel_timeline->cursor_frame, panel_timeline->cursor_track)) { panel_timeline->ripple_delete_empty_space(); } } } } void Timeline::deselect_area(long in, long out, int track) { int len = olive::ActiveSequence->selections.size(); for (int i=0;iselections[i]; if (s.track == track) { if (s.in >= in && s.out <= out) { // whole selection is in deselect area olive::ActiveSequence->selections.removeAt(i); i--; len--; } else if (s.in < in && s.out > out) { // middle of selection is in deselect area Selection new_sel; new_sel.in = out; new_sel.out = s.out; new_sel.track = s.track; olive::ActiveSequence->selections.append(new_sel); s.out = in; } else if (s.in < in && s.out > in) { // only out point is in deselect area s.out = in; } else if (s.in < out && s.out > out) { // only in point is in deselect area s.in = out; } } } } bool Timeline::snap_to_point(long point, long* l) { int limit = get_snap_range(); if (*l > point-limit-1 && *l < point+limit+1) { snap_point = point; *l = point; snapped = true; return true; } return false; } bool Timeline::snap_to_timeline(long* l, bool use_playhead, bool use_markers, bool use_workarea) { snapped = false; if (snapping) { if (use_playhead && !panel_sequence_viewer->playing) { // snap to playhead if (snap_to_point(olive::ActiveSequence->playhead, l)) return true; } // snap to marker if (use_markers) { for (int i=0;imarkers.size();i++) { if (snap_to_point(olive::ActiveSequence->markers.at(i).frame, l)) return true; } } // snap to in/out if (use_workarea && olive::ActiveSequence->using_workarea) { if (snap_to_point(olive::ActiveSequence->workarea_in, l)) return true; if (snap_to_point(olive::ActiveSequence->workarea_out, l)) return true; } // snap to clip/transition for (int i=0;iclips.size();i++) { ClipPtr c = olive::ActiveSequence->clips.at(i); if (c != nullptr) { if (snap_to_point(c->timeline_in(), l)) { return true; } else if (snap_to_point(c->timeline_out(), l)) { return true; } else if (c->opening_transition != nullptr && snap_to_point(c->timeline_in() + c->opening_transition->get_true_length(), l)) { return true; } else if (c->closing_transition != nullptr && snap_to_point(c->timeline_out() - c->closing_transition->get_true_length(), l)) { return true; } else { // try to snap to clip markers for (int j=0;jget_markers().size();j++) { if (snap_to_point(c->get_markers().at(j).frame + c->timeline_in() - c->clip_in(), l)) { return true; } } } } } } return false; } void Timeline::set_marker() { // determine if any clips are selected, and if so add markers to clips rather than the sequence QVector clips_selected; bool clip_mode = false; for (int i=0;iclips.size();i++) { Clip* c = olive::ActiveSequence->clips.at(i).get(); if (c != nullptr && olive::ActiveSequence->IsClipSelected(c, true)) { // only add markers if the playhead is inside the clip if (olive::ActiveSequence->playhead >= c->timeline_in() && olive::ActiveSequence->playhead <= c->timeline_out()) { clips_selected.append(i); } // we are definitely adding markers to clips though clip_mode = true; } } // if we've selected clips but none of the clips are within the playhead, // nothing to do here if (clip_mode && clips_selected.size() == 0) { return; } // pass off to internal set marker function set_marker_internal(olive::ActiveSequence, clips_selected); } void Timeline::delete_inout() { panel_timeline->delete_in_out_internal(false); } void Timeline::ripple_delete_inout() { panel_timeline->delete_in_out_internal(true); } void Timeline::ripple_to_in_point() { panel_timeline->edit_to_point_internal(true, true); } void Timeline::ripple_to_out_point() { panel_timeline->edit_to_point_internal(false, true); } void Timeline::edit_to_in_point() { panel_timeline->edit_to_point_internal(true, false); } void Timeline::edit_to_out_point() { panel_timeline->edit_to_point_internal(false, false); } void Timeline::toggle_links() { LinkCommand* command = new LinkCommand(); command->s = olive::ActiveSequence; for (int i=0;iclips.size();i++) { Clip* c = olive::ActiveSequence->clips.at(i).get(); if (c != nullptr && olive::ActiveSequence->IsClipSelected(c, true)) { if (!command->clips.contains(i)) command->clips.append(i); if (c->linked.size() > 0) { command->link = false; // prioritize unlinking for (int j=0;jlinked.size();j++) { // add links to the command if (!command->clips.contains(c->linked.at(j))) command->clips.append(c->linked.at(j)); } } } } if (command->clips.size() > 0) { olive::UndoStack.push(command); repaint_timeline(); } else { delete command; } } void Timeline::deselect() { olive::ActiveSequence->selections.clear(); repaint_timeline(); } long getFrameFromScreenPoint(double zoom, int x) { long f = qCeil(double(x) / zoom); if (f < 0) { return 0; } return f; } int getScreenPointFromFrame(double zoom, long frame) { return qFloor(double(frame)*zoom); } long Timeline::getTimelineFrameFromScreenPoint(int x) { return getFrameFromScreenPoint(zoom, x + scroll); } int Timeline::getTimelineScreenPointFromFrame(long frame) { return getScreenPointFromFrame(zoom, frame) - scroll; } void Timeline::add_btn_click() { QMenu add_menu(this); QAction* titleMenuItem = new QAction(&add_menu); titleMenuItem->setText(tr("Title...")); titleMenuItem->setData(ADD_OBJ_TITLE); add_menu.addAction(titleMenuItem); QAction* solidMenuItem = new QAction(&add_menu); solidMenuItem->setText(tr("Solid Color...")); solidMenuItem->setData(ADD_OBJ_SOLID); add_menu.addAction(solidMenuItem); QAction* barsMenuItem = new QAction(&add_menu); barsMenuItem->setText(tr("Bars...")); barsMenuItem->setData(ADD_OBJ_BARS); add_menu.addAction(barsMenuItem); add_menu.addSeparator(); QAction* toneMenuItem = new QAction(&add_menu); toneMenuItem->setText(tr("Tone...")); toneMenuItem->setData(ADD_OBJ_TONE); add_menu.addAction(toneMenuItem); QAction* noiseMenuItem = new QAction(&add_menu); noiseMenuItem->setText(tr("Noise...")); noiseMenuItem->setData(ADD_OBJ_NOISE); add_menu.addAction(noiseMenuItem); connect(&add_menu, SIGNAL(triggered(QAction*)), this, SLOT(add_menu_item(QAction*))); add_menu.exec(QCursor::pos()); } void Timeline::add_menu_item(QAction* action) { creating = true; creating_object = action->data().toInt(); } void Timeline::setScroll(int s) { scroll = s; headers->set_scroll(s); repaint_timeline(); } void Timeline::record_btn_click() { if (olive::ActiveProjectFilename.isEmpty()) { QMessageBox::critical(this, tr("Unsaved Project"), tr("You must save this project before you can record audio in it."), QMessageBox::Ok); } else { creating = true; creating_object = ADD_OBJ_AUDIO; olive::MainWindow->statusBar()->showMessage( tr("Click on the timeline where you want to start recording (drag to limit the recording to a certain timeframe)"), 10000); } } void Timeline::transition_tool_click() { creating = false; QMenu transition_menu(this); for (int i=0;isetObjectName("v"); a->setData(reinterpret_cast(&em)); } } transition_menu.addSeparator(); for (int i=0;isetObjectName("a"); a->setData(reinterpret_cast(&em)); } } connect(&transition_menu, SIGNAL(triggered(QAction*)), this, SLOT(transition_menu_select(QAction*))); toolTransitionButton->setChecked(false); transition_menu.exec(QCursor::pos()); } void Timeline::transition_menu_select(QAction* a) { transition_tool_meta = reinterpret_cast(a->data().value()); if (a->objectName() == "v") { transition_tool_side = -1; } else { transition_tool_side = 1; } decheck_tool_buttons(sender()); timeline_area->setCursor(Qt::CrossCursor); tool = TIMELINE_TOOL_TRANSITION; toolTransitionButton->setChecked(true); } void Timeline::resize_move(double z) { set_zoom_value(zoom * z); } void Timeline::set_sb_max() { headers->set_scrollbar_max(horizontalScrollBar, olive::ActiveSequence->getEndFrame(), editAreas->width() - getScreenPointFromFrame(zoom, 200)); } void Timeline::UpdateTitle() { QString title = tr("Timeline: "); if (olive::ActiveSequence == nullptr) { setWindowTitle(title + tr("(none)")); } else { setWindowTitle(title + olive::ActiveSequence->name); update_ui(false); } } void Timeline::setup_ui() { QWidget* dockWidgetContents = new QWidget(); QHBoxLayout* horizontalLayout = new QHBoxLayout(dockWidgetContents); horizontalLayout->setSpacing(0); horizontalLayout->setMargin(0); setWidget(dockWidgetContents); tool_button_widget = new QWidget(); tool_button_widget->setObjectName("timeline_toolbar"); tool_button_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); FlowLayout* tool_buttons_layout = new FlowLayout(tool_button_widget); tool_buttons_layout->setSpacing(4); tool_buttons_layout->setMargin(0); toolArrowButton = new QPushButton(); toolArrowButton->setIcon(OliveGlobal::CreateIconFromSVG(QStringLiteral(":/icons/arrow.svg"))); toolArrowButton->setCheckable(true); toolArrowButton->setProperty("tool", TIMELINE_TOOL_POINTER); connect(toolArrowButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolArrowButton); toolEditButton = new QPushButton(); toolEditButton->setIcon(OliveGlobal::CreateIconFromSVG(QStringLiteral(":/icons/beam.svg"))); toolEditButton->setCheckable(true); toolEditButton->setProperty("tool", TIMELINE_TOOL_EDIT); connect(toolEditButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolEditButton); toolRippleButton = new QPushButton(); toolRippleButton->setIcon(OliveGlobal::CreateIconFromSVG(QStringLiteral(":/icons/ripple.svg"))); toolRippleButton->setCheckable(true); toolRippleButton->setProperty("tool", TIMELINE_TOOL_RIPPLE); connect(toolRippleButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolRippleButton); toolRazorButton = new QPushButton(); toolRazorButton->setIcon(OliveGlobal::CreateIconFromSVG(QStringLiteral(":/icons/razor.svg"))); toolRazorButton->setCheckable(true); toolRazorButton->setProperty("tool", TIMELINE_TOOL_RAZOR); connect(toolRazorButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolRazorButton); toolSlipButton = new QPushButton(); toolSlipButton->setIcon(OliveGlobal::CreateIconFromSVG(QStringLiteral(":/icons/slip.svg"))); toolSlipButton->setCheckable(true); toolSlipButton->setProperty("tool", TIMELINE_TOOL_SLIP); connect(toolSlipButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolSlipButton); toolSlideButton = new QPushButton(); toolSlideButton->setIcon(OliveGlobal::CreateIconFromSVG(QStringLiteral(":/icons/slide.svg"))); toolSlideButton->setCheckable(true); toolSlideButton->setProperty("tool", TIMELINE_TOOL_SLIDE); connect(toolSlideButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolSlideButton); toolHandButton = new QPushButton(); toolHandButton->setIcon(OliveGlobal::CreateIconFromSVG(QStringLiteral(":/icons/hand.svg"))); toolHandButton->setCheckable(true); toolHandButton->setProperty("tool", TIMELINE_TOOL_HAND); connect(toolHandButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolHandButton); toolTransitionButton = new QPushButton(); toolTransitionButton->setIcon(OliveGlobal::CreateIconFromSVG(QStringLiteral(":/icons/transition-tool.svg"))); toolTransitionButton->setCheckable(true); connect(toolTransitionButton, SIGNAL(clicked(bool)), this, SLOT(transition_tool_click())); tool_buttons_layout->addWidget(toolTransitionButton); snappingButton = new QPushButton(); snappingButton->setIcon(OliveGlobal::CreateIconFromSVG(QStringLiteral(":/icons/magnet.svg"))); snappingButton->setCheckable(true); snappingButton->setChecked(true); connect(snappingButton, SIGNAL(toggled(bool)), this, SLOT(snapping_clicked(bool))); tool_buttons_layout->addWidget(snappingButton); zoomInButton = new QPushButton(); zoomInButton->setIcon(OliveGlobal::CreateIconFromSVG(QStringLiteral(":/icons/zoomin.svg"))); connect(zoomInButton, SIGNAL(clicked(bool)), this, SLOT(zoom_in())); tool_buttons_layout->addWidget(zoomInButton); zoomOutButton = new QPushButton(); zoomOutButton->setIcon(OliveGlobal::CreateIconFromSVG(QStringLiteral(":/icons/zoomout.svg"))); connect(zoomOutButton, SIGNAL(clicked(bool)), this, SLOT(zoom_out())); tool_buttons_layout->addWidget(zoomOutButton); recordButton = new QPushButton(); recordButton->setIcon(OliveGlobal::CreateIconFromSVG(QStringLiteral(":/icons/record.svg"))); connect(recordButton, SIGNAL(clicked(bool)), this, SLOT(record_btn_click())); tool_buttons_layout->addWidget(recordButton); addButton = new QPushButton(); addButton->setIcon(OliveGlobal::CreateIconFromSVG(QStringLiteral(":/icons/add-button.svg"))); connect(addButton, SIGNAL(clicked()), this, SLOT(add_btn_click())); tool_buttons_layout->addWidget(addButton); horizontalLayout->addWidget(tool_button_widget); timeline_area = new QWidget(); QSizePolicy timeline_area_policy(QSizePolicy::Minimum, QSizePolicy::Minimum); timeline_area_policy.setHorizontalStretch(1); timeline_area_policy.setVerticalStretch(0); timeline_area_policy.setHeightForWidth(timeline_area->sizePolicy().hasHeightForWidth()); timeline_area->setSizePolicy(timeline_area_policy); QVBoxLayout* timeline_area_layout = new QVBoxLayout(timeline_area); timeline_area_layout->setSpacing(0); timeline_area_layout->setContentsMargins(0, 0, 0, 0); headers = new TimelineHeader(); timeline_area_layout->addWidget(headers); editAreas = new QWidget(); QHBoxLayout* editAreaLayout = new QHBoxLayout(editAreas); editAreaLayout->setSpacing(0); editAreaLayout->setContentsMargins(0, 0, 0, 0); QSplitter* splitter = new QSplitter(); splitter->setChildrenCollapsible(false); splitter->setOrientation(Qt::Vertical); QWidget* videoContainer = new QWidget(); QHBoxLayout* videoContainerLayout = new QHBoxLayout(videoContainer); videoContainerLayout->setSpacing(0); videoContainerLayout->setContentsMargins(0, 0, 0, 0); video_area = new TimelineWidget(); video_area->setFocusPolicy(Qt::ClickFocus); videoContainerLayout->addWidget(video_area); videoScrollbar = new QScrollBar(); videoScrollbar->setMaximum(0); videoScrollbar->setSingleStep(20); videoScrollbar->setOrientation(Qt::Vertical); videoContainerLayout->addWidget(videoScrollbar); splitter->addWidget(videoContainer); QWidget* audioContainer = new QWidget(); QHBoxLayout* audioContainerLayout = new QHBoxLayout(audioContainer); audioContainerLayout->setSpacing(0); audioContainerLayout->setContentsMargins(0, 0, 0, 0); audio_area = new TimelineWidget(); audio_area->setFocusPolicy(Qt::ClickFocus); audioContainerLayout->addWidget(audio_area); audioScrollbar = new QScrollBar(); audioScrollbar->setMaximum(0); audioScrollbar->setOrientation(Qt::Vertical); audioContainerLayout->addWidget(audioScrollbar); splitter->addWidget(audioContainer); editAreaLayout->addWidget(splitter); timeline_area_layout->addWidget(editAreas); horizontalScrollBar = new ResizableScrollBar(); horizontalScrollBar->setMaximum(0); horizontalScrollBar->setSingleStep(20); horizontalScrollBar->setOrientation(Qt::Horizontal); timeline_area_layout->addWidget(horizontalScrollBar); horizontalLayout->addWidget(timeline_area); audio_monitor = new AudioMonitor(); audio_monitor->setMinimumSize(QSize(50, 0)); horizontalLayout->addWidget(audio_monitor); setWidget(dockWidgetContents); } void Timeline::set_tool() { QPushButton* button = static_cast(sender()); decheck_tool_buttons(button); tool = button->property("tool").toInt(); creating = false; switch (tool) { case TIMELINE_TOOL_EDIT: case TIMELINE_TOOL_RAZOR: timeline_area->setCursor(Qt::IBeamCursor); break; case TIMELINE_TOOL_HAND: timeline_area->setCursor(Qt::OpenHandCursor); break; default: timeline_area->setCursor(Qt::ArrowCursor); } } void olive::timeline::MultiplyTrackSizesByDPI() { kTrackDefaultHeight *= QApplication::desktop()->devicePixelRatio(); kTrackMinHeight *= QApplication::desktop()->devicePixelRatio(); kTrackHeightIncrement *= QApplication::desktop()->devicePixelRatio(); }