#include "timelinewidget.h" #include "panels/panels.h" #include "io/config.h" #include "project/sequence.h" #include "project/clip.h" #include "panels/project.h" #include "panels/timeline.h" #include "io/media.h" #include "ui/sourcetable.h" #include "panels/effectcontrols.h" #include "effects/effects.h" #include #include #include #include #include #include #include #include #include TimelineWidget::TimelineWidget(QWidget *parent) : QWidget(parent) { bottom_align = false; track_resizing = false; setMouseTracking(true); setAcceptDrops(true); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); } void TimelineWidget::show_context_menu(const QPoint& pos) { QMenu menu(this); menu.addAction("heck!"); menu.exec(mapToGlobal(pos)); } bool same_sign(int a, int b) { return (a < 0) == (b < 0); } void TimelineWidget::resizeEvent(QResizeEvent*) { if (sequence != NULL) redraw_clips(); } void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { if (static_cast(event->source()) == panel_project->source_table) { QPoint pos = event->pos(); event->accept(); QList items = panel_project->source_table->selectedItems(); long entry_point = panel_timeline->getFrameFromScreenPoint(pos.x()); panel_timeline->drag_frame_start = entry_point + panel_timeline->getFrameFromScreenPoint(50); panel_timeline->drag_track_start = (bottom_align) ? -1 : 0; for (int i=0;iget_media_from_tree(items.at(i)); long duration = m->get_length_in_frames(sequence->frame_rate); Ghost g = {NULL, entry_point, entry_point + duration}; g.media = m; g.clip_in = 0; for (int j=0;jaudio_tracks.size();j++) { g.track = j; g.media_stream = m->audio_tracks.at(j); ignore_infinite_length = true; panel_timeline->ghosts.append(g); } for (int j=0;jvideo_tracks.size();j++) { g.track = -1-j; g.media_stream = m->video_tracks.at(j); if (m->video_tracks[j]->infinite_length && !ignore_infinite_length) g.out = g.in + 100; panel_timeline->ghosts.append(g); } entry_point += duration; } init_ghosts(); panel_timeline->importing = true; } } void TimelineWidget::dragMoveEvent(QDragMoveEvent *event) { if (panel_timeline->importing) { QPoint pos = event->pos(); update_ghosts(pos); panel_timeline->repaint_timeline(); } } void TimelineWidget::dragLeaveEvent(QDragLeaveEvent*) { if (panel_timeline->importing) { panel_timeline->ghosts.clear(); panel_timeline->importing = false; panel_timeline->repaint_timeline(); } } void TimelineWidget::dropEvent(QDropEvent* event) { if (panel_timeline->importing) { event->accept(); QVector added_clips; // delete areas before adding QVector delete_areas; for (int i=0;ighosts.size();i++) { const Ghost& g = panel_timeline->ghosts.at(i); delete_areas.append({g.in, g.out, g.track}); } panel_timeline->delete_areas_and_relink(delete_areas); // add clips for (int i=0;ighosts.size();i++) { const Ghost& g = panel_timeline->ghosts.at(i); Clip* c = new Clip(); c->media = g.media; c->media_stream = g.media_stream; c->timeline_in = g.in; c->timeline_out = g.out; c->clip_in = g.clip_in; c->color_r = 128; c->color_g = 128; c->color_b = 192; c->sequence = sequence; c->track = g.track; c->name = c->media->name; if (c->track < 0) { // add default video effects c->effects.append(create_effect(VIDEO_TRANSFORM_EFFECT, c)); } else { // add default audio effects c->effects.append(create_effect(AUDIO_VOLUME_EFFECT, c)); c->effects.append(create_effect(AUDIO_PAN_EFFECT, c)); } sequence->add_clip(c); added_clips.append(c); } // link clips from the same media for (int i=0;imedia == cc->media) { c->linked.append(cc->id); } } } panel_timeline->ghosts.clear(); panel_timeline->importing = false; panel_timeline->snapped = false; setFocus(); panel_timeline->redraw_all_clips(true); } } void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *event) { if (panel_timeline->tool == TIMELINE_TOOL_EDIT) { int clip_index = getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track); if (clip_index >= 0) { Clip* clip = sequence->get_clip(clip_index); if (!(event->modifiers() & Qt::ShiftModifier)) panel_timeline->selections.clear(); panel_timeline->selections.append({clip->timeline_in, clip->timeline_out, clip->track}); panel_timeline->repaint_timeline(); } } } void TimelineWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { if (panel_timeline->tool == TIMELINE_TOOL_EDIT || panel_timeline->tool == TIMELINE_TOOL_RAZOR) { panel_timeline->drag_frame_start = panel_timeline->cursor_frame; panel_timeline->drag_track_start = panel_timeline->cursor_track; } else { QPoint pos = event->pos(); panel_timeline->drag_frame_start = panel_timeline->getFrameFromScreenPoint(pos.x()); panel_timeline->drag_track_start = getTrackFromScreenPoint(pos.y()); } int clip_index = panel_timeline->trim_target; if (clip_index == -1) clip_index = getClipIndexFromCoords(panel_timeline->drag_frame_start, panel_timeline->drag_track_start); switch (panel_timeline->tool) { case TIMELINE_TOOL_POINTER: case TIMELINE_TOOL_RIPPLE: case TIMELINE_TOOL_SLIP: { if (track_resizing) { track_resize_mouse_cache = event->pos().y(); panel_timeline->moving_init = true; } else if (clip_index >= 0) { Clip* c = sequence->get_clip(clip_index); if (c != NULL && panel_timeline->is_clip_selected(c)) { // TODO if shift is down, deselect it } else { // if "shift" is not down if (!(event->modifiers() & Qt::ShiftModifier)) { panel_timeline->selections.clear(); } Clip* clip = sequence->get_clip(clip_index); if (clip != NULL) { panel_timeline->selections.append({clip->timeline_in, clip->timeline_out, clip->track}); // if alt is not down, select links if (!(event->modifiers() & Qt::AltModifier)) { for (int i=0;ilinked.size();i++) { Clip* link = sequence->get_clip(c->linked.at(i)); if (!panel_timeline->is_clip_selected(link)) { panel_timeline->selections.append({link->timeline_in, link->timeline_out, link->track}); } } } } } panel_timeline->moving_init = true; } else { panel_timeline->selections.clear(); } panel_timeline->repaint_timeline(); } break; case TIMELINE_TOOL_EDIT: if (panel_timeline->edit_tool_also_seeks) panel_timeline->seek(panel_timeline->drag_frame_start); if ((event->modifiers() & Qt::ShiftModifier)) { panel_timeline->selection_offset = panel_timeline->selections.size(); } else { panel_timeline->selection_offset = 0; } panel_timeline->selecting = true; break; case TIMELINE_TOOL_RAZOR: { if (clip_index >= 0) { Clip* clip = sequence->get_clip(clip_index); if (clip != NULL) { panel_timeline->split_clip_and_relink(clip, panel_timeline->drag_frame_start, !(event->modifiers() & Qt::AltModifier)); } } panel_timeline->splitting = true; panel_timeline->redraw_all_clips(true); } break; } } } void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { bool repaint = false; if (panel_timeline->moving_proc) { if (event->modifiers() & Qt::AltModifier) { // if holding alt, duplicate rather than move // duplicate clips QVector old_clips; QVector copy_clips; QVector delete_areas; for (int i=0;ighosts.size();i++) { const Ghost& g = panel_timeline->ghosts.at(i); if (g.old_in != g.in || g.old_out != g.out || g.track != g.old_track || g.clip_in != g.old_clip_in) { Clip* c = g.clip->copy(); c->timeline_in = g.in; c->timeline_out = g.out; c->track = g.track; delete_areas.append({g.in, g.out, g.track}); old_clips.append(g.clip); copy_clips.append(c); } } if (copy_clips.size() > 0) { panel_timeline->delete_areas_and_relink(delete_areas); for (int i=0;iadd_clip(copy_clips.at(i)); } // relink duplicated clips panel_timeline->relink_clips_using_ids(old_clips, copy_clips); } } else { // move clips // TODO can we do this better than 3 consecutive for loops? for (int i=0;ighosts.size();i++) { // step 1 - set clips that are moving to "undeletable" (to avoid step 2 deleting any part of them) panel_timeline->ghosts[i].clip->undeletable = true; } // step 2 - delete areas QVector delete_areas; for (int i=0;ighosts.size();i++) { const Ghost& g = panel_timeline->ghosts.at(i); if (panel_timeline->tool == TIMELINE_TOOL_POINTER) { // step 2 - delete anything that exists in area that clip is moving to // note: ripples are non-destructive so this is pointer-tool exclusive delete_areas.append({g.in, g.out, g.track}); } } panel_timeline->delete_areas_and_relink(delete_areas); for (int i=0;ighosts.size();i++) { Ghost& g = panel_timeline->ghosts[i]; // step 3 - move clips g.clip->timeline_in = g.in; g.clip->timeline_out = g.out; g.clip->track = g.track; g.clip->clip_in = g.clip_in; } for (int i=0;ighosts.size();i++) { // step 4 - set clips back to deletable panel_timeline->ghosts[i].clip->undeletable = false; } } // ripple ops if (panel_timeline->tool == TIMELINE_TOOL_RIPPLE) { long ripple_length, ripple_point; if (panel_timeline->trim_in) { ripple_length = panel_timeline->ghosts.at(0).old_in - panel_timeline->ghosts.at(0).in; ripple_point = (ripple_length < 0) ? panel_timeline->ghosts.at(0).old_in : panel_timeline->ghosts.at(0).in; } else { ripple_length = panel_timeline->ghosts.at(0).old_out - panel_timeline->ghosts.at(0).out; ripple_point = (ripple_length < 0) ? panel_timeline->ghosts.at(0).old_out : panel_timeline->ghosts.at(0).out; } for (int i=0;ighosts.size();i++) { long comp_point; if (panel_timeline->trim_in) { comp_point = (ripple_length < 0) ? panel_timeline->ghosts.at(i).old_in : panel_timeline->ghosts.at(i).in; } else { comp_point = (ripple_length < 0) ? panel_timeline->ghosts.at(i).old_out : panel_timeline->ghosts.at(i).out; } if (ripple_point > comp_point) ripple_point = comp_point; } if (panel_timeline->trim_in) { panel_timeline->ripple(ripple_point, ripple_length); } else { panel_timeline->ripple(ripple_point, -ripple_length); } } panel_timeline->redraw_all_clips(true); } else if (panel_timeline->selecting) { // remove duplicate selections panel_timeline->clean_up_selections(panel_timeline->selections); repaint = true; } // destroy all ghosts panel_timeline->ghosts.clear(); panel_timeline->selecting = false; panel_timeline->moving_proc = false; panel_timeline->moving_init = false; panel_timeline->splitting = false; panel_timeline->snapped = false; pre_clips.clear(); post_clips.clear(); if (repaint) panel_timeline->repaint_timeline(); // SEND CLIPS TO EFFECT CONTROLS // find out how many clips are selected // limits to one video clip and one audio clip and only if they're linked // one of these days it might be nice to have multiple clips in the effects panel bool got_vclip = false; bool got_aclip = false; Clip* vclip = NULL; Clip* aclip = NULL; for (int i=0;iclip_count();i++) { Clip* clip = sequence->get_clip(i); if (clip != NULL && panel_timeline->is_clip_selected(clip)) { if (clip->track < 0) { if (got_vclip) { vclip = NULL; } else { vclip = clip; got_vclip = true; } } else { if (got_aclip) { aclip = NULL; } else { aclip = clip; got_aclip = true; } } } } // check if aclip is linked to vclip QVector selected_clips; if (vclip != NULL) selected_clips.append(vclip); if (aclip != NULL) selected_clips.append(aclip); if (vclip != NULL && aclip != NULL) { bool found = false; for (int i=0;ilinked.size();i++) { if (vclip->linked.at(i) == aclip->id) { found = true; break; } } if (!found) { // only display multiple clips if they're linked selected_clips.clear(); } } panel_effect_controls->set_clips(selected_clips); } } void TimelineWidget::init_ghosts() { for (int i=0;ighosts.size();i++) { Ghost& g = panel_timeline->ghosts[i]; g.old_in = g.in; g.old_out = g.out; g.old_track = g.track; g.old_clip_in = g.clip_in; if (panel_timeline->trim_target > -1 || panel_timeline->tool == TIMELINE_TOOL_SLIP) { // used for trim ops g.ghost_length = g.old_out - g.old_in; g.media_length = g.clip->media->get_length_in_frames(sequence->frame_rate); } } for (int i=0;iselections.size();i++) { Selection& s = panel_timeline->selections[i]; s.old_in = s.in; s.old_out = s.out; s.old_track = s.track; } } bool subvalidate_snapping(Ghost& g, long* frame_diff, long snap_point) { int snap_range = panel_timeline->get_snap_range(); long in_validator = g.old_in + *frame_diff - snap_point; long out_validator = g.old_out + *frame_diff - snap_point; if ((panel_timeline->trim_target == -1 || panel_timeline->trim_in) && in_validator > -snap_range && in_validator < snap_range) { *frame_diff -= in_validator; panel_timeline->snap_point = snap_point; panel_timeline->snapped = true; return true; } else if (!panel_timeline->trim_in && out_validator > -snap_range && out_validator < snap_range) { *frame_diff -= out_validator; panel_timeline->snap_point = snap_point; panel_timeline->snapped = true; return true; } return false; } void validate_snapping(Ghost& g, long* frame_diff) { panel_timeline->snapped = false; if (panel_timeline->snapping) { if (!subvalidate_snapping(g, frame_diff, panel_timeline->playhead)) { for (int j=0;jclip_count();j++) { Clip* c = sequence->get_clip(j); if (c != NULL) { if (!subvalidate_snapping(g, frame_diff, c->timeline_in)) { subvalidate_snapping(g, frame_diff, c->timeline_out); } if (panel_timeline->snapped) break; } } } } } void TimelineWidget::update_ghosts(QPoint& mouse_pos) { int mouse_track = getTrackFromScreenPoint(mouse_pos.y()); long frame_diff = panel_timeline->getFrameFromScreenPoint(mouse_pos.x()) - panel_timeline->drag_frame_start; int track_diff = mouse_track - panel_timeline->drag_track_start; long validator; if (panel_timeline->trim_target > -1) { // if trimming // trim ops // validate ghosts for (int i=0;ighosts.size();i++) { Ghost& g = panel_timeline->ghosts[i]; if (panel_timeline->trim_in) { // prevent clip length from being less than 1 frame long validator = g.ghost_length - frame_diff; if (validator < 1) frame_diff -= (1 - validator); // prevent timeline in from going below 0 validator = g.old_in + frame_diff; if (validator < 0) frame_diff -= validator; if (!g.clip->media_stream->infinite_length) { // prevent clip_in from going below 0 validator = g.old_clip_in + frame_diff; if (validator < 0) frame_diff -= validator; } // ripple ops if (panel_timeline->tool == TIMELINE_TOOL_RIPPLE) { for (int j=0;jtimeline_in - frame_diff; if (validator < 0) frame_diff += validator; // prevent any post-clips colliding with pre-clips for (int k=0;ktrack == post->track) { validator = post->timeline_in - frame_diff - pre->timeline_out; if (validator < 0) frame_diff += validator; } } } } } else { // prevent clip length from being less than 1 frame long validator = g.ghost_length + frame_diff; if (validator < 1) frame_diff += (1 - validator); if (!g.clip->media_stream->infinite_length) { // prevent clip length exceeding media length validator = g.ghost_length + frame_diff; if (validator > g.media_length) frame_diff -= validator - g.media_length; } // ripple ops if (panel_timeline->tool == TIMELINE_TOOL_RIPPLE) { for (int j=0;jtrack == post->track) { validator = post->timeline_in + frame_diff - pre->timeline_out; if (validator < 0) frame_diff -= validator; } } } } } validate_snapping(g, &frame_diff); } // resize ghosts for (int i=0;ighosts.size();i++) { Ghost& g = panel_timeline->ghosts[i]; if (panel_timeline->trim_in) { g.in = g.old_in + frame_diff; g.clip_in = g.old_clip_in + frame_diff; } else { g.out = g.old_out + frame_diff; } } // resize selections for (int i=0;iselections.size();i++) { Selection& s = panel_timeline->selections[i]; if (panel_timeline->trim_in) { s.in = s.old_in + frame_diff; } else { s.out = s.old_out + frame_diff; } } } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->importing) { // only move clips on pointer (not ripple or rolling) // validate ghosts for (int i=0;ighosts.size();i++) { Ghost& g = panel_timeline->ghosts[i]; // prevent clips from moving below 0 on the timeline validator = g.old_in + frame_diff; if (validator < 0) frame_diff -= validator; // prevent clips from crossing tracks if (same_sign(g.old_track, panel_timeline->drag_track_start)) { while (!same_sign(g.old_track, g.old_track + track_diff)) { if (g.old_track < 0) { track_diff--; } else { track_diff++; } } } validate_snapping(g, &frame_diff); } // move ghosts for (int i=0;ighosts.size();i++) { Ghost& g = panel_timeline->ghosts[i]; g.in = g.old_in + frame_diff; g.out = g.old_out + frame_diff; g.track = g.old_track; if (panel_timeline->importing) { int abs_track_diff = abs(track_diff); if (g.old_track < 0) { // clip is video g.track -= abs_track_diff; } else { // clip is audio g.track += abs_track_diff; } } else { if (same_sign(g.old_track, panel_timeline->drag_track_start)) g.track += track_diff; } } // move selections if (!panel_timeline->importing) { for (int i=0;iselections.size();i++) { Selection& s = panel_timeline->selections[i]; s.in = s.old_in + frame_diff; s.out = s.old_out + frame_diff; s.track = s.old_track; if (panel_timeline->importing) { int abs_track_diff = abs(track_diff); if (s.old_track < 0) { s.track -= abs_track_diff; } else { s.track += abs_track_diff; } } else { if (same_sign(s.track, panel_timeline->drag_track_start)) s.track += track_diff; } } } } else if (panel_timeline->tool == TIMELINE_TOOL_SLIP) { // validate ghosts for (int i=0;ighosts.size();i++) { Ghost& g = panel_timeline->ghosts[i]; if (!g.clip->media_stream->infinite_length) { // prevent slip moving a clip below 0 clip_in validator = g.old_clip_in - frame_diff; if (validator < 0) frame_diff += validator; // prevent slip moving clip beyond media length validator += g.ghost_length; if (validator > g.media_length) frame_diff += validator - g.media_length; } } // slip ghosts for (int i=0;ighosts.size();i++) { Ghost& g = panel_timeline->ghosts[i]; g.clip_in = g.old_clip_in - frame_diff; } } } void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { panel_timeline->cursor_frame = panel_timeline->getFrameFromScreenPoint(event->pos().x()); panel_timeline->cursor_track = getTrackFromScreenPoint(event->pos().y()); if (panel_timeline->tool == TIMELINE_TOOL_EDIT || panel_timeline->tool == TIMELINE_TOOL_RAZOR) { panel_timeline->snap_to_clip(&panel_timeline->cursor_frame, !panel_timeline->edit_tool_also_seeks || !panel_timeline->selecting); } if (panel_timeline->selecting) { int selection_count = 1 + qMax(panel_timeline->cursor_track, panel_timeline->drag_track_start) - qMin(panel_timeline->cursor_track, panel_timeline->drag_track_start) + panel_timeline->selection_offset; if (panel_timeline->selections.size() != selection_count) { panel_timeline->selections.resize(selection_count); } int minimum_selection_track = qMin(panel_timeline->cursor_track, panel_timeline->drag_track_start); for (int i=panel_timeline->selection_offset;iselections[i]; s.track = minimum_selection_track + i - panel_timeline->selection_offset; long in = panel_timeline->drag_frame_start; long out = panel_timeline->cursor_frame; s.in = qMin(in, out); s.out = qMax(in, out); } if (panel_timeline->edit_tool_also_seeks) { panel_timeline->seek(qMin(panel_timeline->drag_frame_start, panel_timeline->cursor_frame)); } else { panel_timeline->repaint_timeline(); } } else if (panel_timeline->moving_init) { if (track_resizing) { int diff = track_resize_mouse_cache - event->pos().y(); int new_height = track_resize_old_value; if (bottom_align) { new_height += diff; } else { new_height -= diff; } if (new_height < TRACK_MIN_HEIGHT) new_height = TRACK_MIN_HEIGHT; panel_timeline->calculate_track_height(track_target, new_height); redraw_clips(); } else if (panel_timeline->moving_proc) { QPoint pos = event->pos(); update_ghosts(pos); } else { // set up movement // create ghosts for (int i=0;iclip_count();i++) { Clip* c = sequence->get_clip(i); if (c != NULL && panel_timeline->is_clip_selected(c)) { panel_timeline->ghosts.append({c, c->timeline_in, c->timeline_out, c->track, c->clip_in}); } } // ripple edit prep if (panel_timeline->tool == TIMELINE_TOOL_RIPPLE) { for (int i=0;ighosts.size();i++) { // get clips before and after ripple point for (int j=0;jclip_count();j++) { Clip* cc = sequence->get_clip(j); if (cc != NULL) { // don't cache any currently selected clips Clip* c = panel_timeline->ghosts.at(i).clip; bool is_selected = false; for (int k=0;kghosts.size();k++) { if (panel_timeline->ghosts.at(k).clip == cc) { is_selected = true; break; } } if (!is_selected) { if (cc->timeline_in < c->timeline_in) { // add clip to pre-cache UNLESS there is already a clip on that track closer to the ripple point bool found = false; for (int k=0;ktrack == cc->track) { if (ccc->timeline_in < cc->timeline_in) { // clip is closer to ripple point than the one in cache, replace it ccc = cc; } found = true; } } if (!found) { // no clip from that track in the cache, add it pre_clips.append(cc); } } else { // add clip to post-cache UNLESS there is already a clip on that track closer to the ripple point bool found = false; for (int k=0;ktrack == cc->track) { if (ccc->timeline_in > cc->timeline_in) { // clip is closer to ripple point than the one in cache, replace it ccc = cc; } found = true; } } if (!found) { // no clip from that track in the cache, add it post_clips.append(cc); } } } } } } } init_ghosts(); panel_timeline->moving_proc = true; } panel_timeline->repaint_timeline(); } else if (panel_timeline->splitting) { int track = panel_timeline->cursor_track; bool repaint = false; for (int i=0;iclip_count();i++) { Clip* clip = sequence->get_clip(i); if (clip != NULL && clip->track == track) { panel_timeline->split_clip_and_relink(clip, panel_timeline->drag_frame_start, !(event->modifiers() & Qt::AltModifier)); repaint = true; } } // redraw clips since we changed them if (repaint) panel_timeline->redraw_all_clips(true); } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->tool == TIMELINE_TOOL_RIPPLE) { track_resizing = false; QPoint pos = event->pos(); int lim = 5; int mouse_track = getTrackFromScreenPoint(pos.y()); long mouse_frame_lower = panel_timeline->getFrameFromScreenPoint(pos.x()-lim)-1; long mouse_frame_upper = panel_timeline->getFrameFromScreenPoint(pos.x()+lim)+1; bool found = false; int closeness = INT_MAX; for (int i=0;iclip_count();i++) { Clip* c = sequence->get_clip(i); if (c != NULL && c->track == mouse_track) { if (c->timeline_in > mouse_frame_lower && c->timeline_in < mouse_frame_upper) { int nc = abs(c->timeline_in + 1 - panel_timeline->cursor_frame); if (nc < closeness) { panel_timeline->trim_target = i; panel_timeline->trim_in = true; closeness = nc; found = true; } } if (c->timeline_out > mouse_frame_lower && c->timeline_out < mouse_frame_upper) { int nc = abs(c->timeline_out - 1 - panel_timeline->cursor_frame); if (nc < closeness) { panel_timeline->trim_target = i; panel_timeline->trim_in = false; closeness = nc; found = true; } } } } if (found) { setCursor(Qt::SizeHorCursor); } else { panel_timeline->trim_target = -1; // look for track heights found = false; int track_y = 0; for (int i=0;iget_track_height_size(bottom_align);i++) { int track = (bottom_align) ? -1-i : i; int track_height = panel_timeline->calculate_track_height(track, -1); track_y += track_height; int y_test_value = (bottom_align) ? rect().bottom() - track_y : track_y; int test_range = 5; if (pos.y() > y_test_value-test_range && pos.y() < y_test_value+test_range) { found = true; track_resizing = true; track_target = track; track_resize_old_value = track_height; } } if (found) { setCursor(Qt::SizeVerCursor); } else { unsetCursor(); } } } else if (panel_timeline->tool == TIMELINE_TOOL_EDIT || panel_timeline->tool == TIMELINE_TOOL_RAZOR) { // redraw because we have a cursor panel_timeline->repaint_timeline(); } else if (panel_timeline->tool == TIMELINE_TOOL_SLIP) { if (getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track) > -1) { setCursor(Qt::SizeHorCursor); } else { unsetCursor(); } } } int color_brightness(int r, int g, int b) { return (0.2126*r + 0.7152*g + 0.0722*b); } void TimelineWidget::redraw_clips() { // Draw clips int panel_width = panel_timeline->getScreenPointFromFrame(sequence->getEndFrame()) + 100; if (minimumWidth() != panel_width || clip_pixmap.height() != height()) { setMinimumWidth(panel_width); clip_pixmap = QPixmap(panel_width, height()); } clip_pixmap.fill(Qt::transparent); QPainter clip_painter(&clip_pixmap); int video_track_limit = 0; int audio_track_limit = 0; for (int i=0;iclip_count();i++) { Clip* clip = sequence->get_clip(i); if (clip != NULL && is_track_visible(clip->track)) { if (clip->track < 0 && clip->track < video_track_limit) { // video clip video_track_limit = clip->track; } else if (clip->track > audio_track_limit) { audio_track_limit = clip->track; } QRect clip_rect(panel_timeline->getScreenPointFromFrame(clip->timeline_in), getScreenPointFromTrack(clip->track), clip->getLength() * panel_timeline->zoom, panel_timeline->calculate_track_height(clip->track, -1)); clip_painter.fillRect(clip_rect, QColor(clip->color_r, clip->color_g, clip->color_b)); // draw thumbnail/waveform if (clip->media_stream->preview_done) { if (clip->track < 0) { int thumb_y = clip_painter.fontMetrics().height()+CLIP_TEXT_PADDING+CLIP_TEXT_PADDING; int thumb_height = clip_rect.height()-thumb_y; int thumb_width = (thumb_height*((float)clip->media_stream->video_preview.width()/(float)clip->media_stream->video_preview.height())); if (thumb_height > thumb_y && clip_rect.width() > thumb_width) { // at small clip heights, don't even draw it QRect thumb_rect(clip_rect.x(), clip_rect.y()+thumb_y, thumb_width, thumb_height); clip_painter.drawImage(thumb_rect, clip->media_stream->video_preview); } } else { int divider = clip->media_stream->audio_channels*2; clip_painter.setPen(QColor(80, 80, 80)); int channel_height = clip_rect.height()/clip->media_stream->audio_channels; long media_length = clip->media->get_length_in_frames(clip->sequence->frame_rate); for (int i=0;iclip_in + ((float) i/panel_timeline->zoom))/media_length) * clip->media_stream->audio_preview.size())/divider)*divider; for (int j=0;jmedia_stream->audio_channels;j++) { int mid = clip_rect.top()+channel_height*j+(channel_height/2); int offset = waveform_index+(j*2); qint8 min = (float)clip->media_stream->audio_preview[offset] / 128.0f * (channel_height/2); qint8 max = (float)clip->media_stream->audio_preview[offset+1] / 128.0f * (channel_height/2); clip_painter.drawLine(clip_rect.left()+i, mid+min, clip_rect.left()+i, mid+max); } } } } // top left bevel clip_painter.setPen(Qt::white); clip_painter.drawLine(clip_rect.bottomLeft(), clip_rect.topLeft()); clip_painter.drawLine(clip_rect.topLeft(), clip_rect.topRight()); // draw text if (color_brightness(clip->color_r, clip->color_g, clip->color_b) > 160) { // set to black if color is bright clip_painter.setPen(Qt::black); } QRect text_rect(clip_rect.left() + CLIP_TEXT_PADDING, clip_rect.top() + CLIP_TEXT_PADDING, clip_rect.width() - CLIP_TEXT_PADDING - CLIP_TEXT_PADDING, clip_rect.height() - CLIP_TEXT_PADDING - CLIP_TEXT_PADDING); clip_painter.drawText(text_rect, 0, clip->name, &text_rect); if (clip->linked.size() > 0) { int underline_y = CLIP_TEXT_PADDING + clip_painter.fontMetrics().height() + clip_rect.top(); int underline_width = qMin(clip_rect.left() + clip_rect.width() - CLIP_TEXT_PADDING, clip_rect.left() + CLIP_TEXT_PADDING + clip_painter.fontMetrics().width(clip->name)); clip_painter.drawLine(clip_rect.left() + CLIP_TEXT_PADDING, underline_y, underline_width, underline_y); } // bottom right gray clip_painter.setPen(Qt::gray); clip_painter.drawLine(clip_rect.bottomLeft(), clip_rect.bottomRight()); clip_painter.drawLine(clip_rect.bottomRight(), clip_rect.topRight()); } } // Draw track lines if (show_track_lines) { clip_painter.setPen(QColor(0, 0, 0, 96)); audio_track_limit++; if (video_track_limit == 0) video_track_limit--; if (bottom_align) { // only draw lines for video tracks for (int i=video_track_limit;i<0;i++) { int line_y = getScreenPointFromTrack(i) - 1; clip_painter.drawLine(0, line_y, rect().width(), line_y); } } else { // only draw lines for audio tracks for (int i=0;icalculate_track_height(i, -1); clip_painter.drawLine(0, line_y, rect().width(), line_y); } } } update(); } void TimelineWidget::paintEvent(QPaintEvent*) { if (sequence != NULL) { QPainter p(this); p.drawPixmap(0, 0, minimumWidth(), height(), clip_pixmap); // Draw selections for (int i=0;iselections.size();i++) { const Selection& s = panel_timeline->selections.at(i); if (is_track_visible(s.track)) { int selection_y = getScreenPointFromTrack(s.track); int selection_x = panel_timeline->getScreenPointFromFrame(s.in); p.fillRect(selection_x, selection_y, panel_timeline->getScreenPointFromFrame(s.out) - selection_x, panel_timeline->calculate_track_height(s.track, -1), QColor(0, 0, 0, 64)); } } // Draw ghosts for (int i=0;ighosts.size();i++) { const Ghost& g = panel_timeline->ghosts.at(i); if (is_track_visible(g.track)) { int ghost_x = panel_timeline->getScreenPointFromFrame(g.in); int ghost_y = getScreenPointFromTrack(g.track); int ghost_width = panel_timeline->getScreenPointFromFrame(g.out - g.in) - 1; int ghost_height = panel_timeline->calculate_track_height(g.track, -1) - 1; p.setPen(QColor(255, 255, 0)); for (int j=0;jgetScreenPointFromFrame(panel_timeline->playhead); p.drawLine(playhead_x, rect().top(), playhead_x, rect().bottom()); p.setPen(QColor(0, 0, 0, 64)); int edge_y = (bottom_align) ? rect().height()-1 : 0; p.drawLine(0, edge_y, rect().width(), edge_y); // draw snap point if (panel_timeline->snapping && panel_timeline->snapped) { p.setPen(Qt::white); int snap_x = panel_timeline->getScreenPointFromFrame(panel_timeline->snap_point); p.drawLine(snap_x, 0, snap_x, height()); } // Draw edit cursor if (panel_timeline->tool == TIMELINE_TOOL_EDIT || panel_timeline->tool == TIMELINE_TOOL_RAZOR) { if (is_track_visible(panel_timeline->cursor_track)) { int cursor_x = panel_timeline->getScreenPointFromFrame(panel_timeline->cursor_frame); int cursor_y = getScreenPointFromTrack(panel_timeline->cursor_track); p.setPen(Qt::gray); p.drawLine(cursor_x, cursor_y, cursor_x, cursor_y + panel_timeline->calculate_track_height(panel_timeline->cursor_track, -1)); } } } } bool TimelineWidget::is_track_visible(int track) { return (bottom_align == (track < 0)); } // ************************************** // screen point <-> frame/track functions // ************************************** int TimelineWidget::getTrackFromScreenPoint(int y) { // if (bottom_align) { // y = -(y - rect().height()); // } // y--; // int height_measure = 0; // int counter = (bottom_align && y > 0) ? -1 : 0; // int track_height = panel_timeline->calculate_track_height(counter, -1); // while (y > height_measure+track_height) { // if (show_track_lines && counter != -1) y--; // height_measure += track_height; // if (bottom_align && y > 0) { // counter--; // } else { // counter++; // } // track_height = panel_timeline->calculate_track_height(counter, -1); // } // return counter; if (bottom_align) { y = -(y - rect().height()); } y--; int height_measure = 0; int counter = ((!bottom_align && y > 0) || (bottom_align && y < 0)) ? 0 : -1; int track_height = panel_timeline->calculate_track_height(counter, -1); while (qAbs(y) > height_measure+track_height) { if (show_track_lines && counter != -1) y--; height_measure += track_height; if ((!bottom_align && y > 0) || (bottom_align && y < 0)) { counter++; } else { counter--; } track_height = panel_timeline->calculate_track_height(counter, -1); } return counter; } int TimelineWidget::getScreenPointFromTrack(int track) { int y = 0; int counter = 0; while (counter != track) { if (bottom_align) counter--; y += panel_timeline->calculate_track_height(counter, -1); if (!bottom_align) counter++; if (show_track_lines && counter != -1) y++; } y++; return (bottom_align) ? rect().height() - y : y; } int TimelineWidget::getClipIndexFromCoords(long frame, int track) { for (int i=0;iclip_count();i++) { Clip* c = sequence->get_clip(i); if (c != NULL && c->track == track) { if (frame >= c->timeline_in && frame < c->timeline_out) { return i; } } } return -1; }