/*** 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 "timelineview.h" #include #include #include #include #include #include #include "common/flipmodifiers.h" #include "common/timecodefunctions.h" #include "core.h" #include "node/input/media/media.h" #include "project/item/footage/footage.h" TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) : QGraphicsView(parent), timeline_node_(nullptr), playhead_(0), use_tracklist_length_directly_(true) { Q_ASSERT(vertical_alignment == Qt::AlignTop || vertical_alignment == Qt::AlignBottom); setAlignment(Qt::AlignLeft | vertical_alignment); setScene(&scene_); setDragMode(RubberBandDrag); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setBackgroundRole(QPalette::Window); connect(&scene_, SIGNAL(changed(const QList&)), this, SLOT(UpdateSceneRect())); // Create end item end_item_ = new TimelineViewEndItem(); scene_.addItem(end_item_); // Set default scale SetScale(1.0); } void TimelineView::AddTrack(TrackOutput *track) { foreach (Block* b, track->Blocks()) { AddBlock(b, track->Index()); } } void TimelineView::RemoveTrack(TrackOutput *track) { foreach (Block* b, track->Blocks()) { RemoveBlock(b); } } void TimelineView::SetScale(const double &scale) { scale_ = scale; // Force redraw for playhead viewport()->update(); end_item_->SetScale(scale_); } void TimelineView::SetTimebase(const rational &timebase) { SetTimebaseInternal(timebase); // Timebase influences position/visibility of playhead viewport()->update(); } void TimelineView::ConnectTimelineNode(TrackList *node) { if (timeline_node_ != nullptr) { disconnect(timeline_node_, SIGNAL(TimebaseChanged(const rational&)), this, SIGNAL(TimebaseChanged(const rational&))); disconnect(timeline_node_, SIGNAL(TimebaseChanged(const rational&)), this, SLOT(SetTimebase(const rational&))); disconnect(timeline_node_, SIGNAL(TimelineCleared()), this, SLOT(Clear())); disconnect(timeline_node_, SIGNAL(TrackAdded(TrackOutput*)), this, SLOT(AddTrack(TrackOutput*))); disconnect(timeline_node_, SIGNAL(TrackRemoved(TrackOutput*)), this, SLOT(RemoveTrack(TrackOutput*))); disconnect(timeline_node_, SIGNAL(LengthChanged(const rational&)), this, SLOT(UpdateEndTimeFromTrackList(const rational&))); Clear(); } timeline_node_ = node; if (timeline_node_ != nullptr) { SetTimebase(timeline_node_->Timebase()); emit TimebaseChanged(timeline_node_->Timebase()); connect(timeline_node_, SIGNAL(TimebaseChanged(const rational&)), this, SIGNAL(TimebaseChanged(const rational&))); connect(timeline_node_, SIGNAL(TimebaseChanged(const rational&)), this, SLOT(SetTimebase(const rational&))); connect(timeline_node_, SIGNAL(TimelineCleared()), this, SLOT(Clear())); connect(timeline_node_, SIGNAL(TrackAdded(TrackOutput*)), this, SLOT(AddTrack(TrackOutput*))); connect(timeline_node_, SIGNAL(TrackRemoved(TrackOutput*)), this, SLOT(RemoveTrack(TrackOutput*))); connect(timeline_node_, SIGNAL(LengthChanged(const rational&)), this, SLOT(UpdateEndTimeFromTrackList(const rational&))); foreach (TrackOutput* track, timeline_node_->Tracks()) { // Defer to the track to make all the block UI items necessary AddTrack(track); } } } void TimelineView::DisconnectTimelineNode() { ConnectTimelineNode(nullptr); } void TimelineView::SelectAll() { QList all_items = items(); foreach (QGraphicsItem* i, all_items) { i->setSelected(true); } } void TimelineView::DeselectAll() { QList all_items = items(); foreach (QGraphicsItem* i, all_items) { i->setSelected(false); } } void TimelineView::SetUseTrackListLengthDirectly(bool use) { use_tracklist_length_directly_ = use; } void TimelineView::SetTime(const int64_t time) { playhead_ = time; // Force redraw for playhead viewport()->update(); } void TimelineView::mousePressEvent(QMouseEvent *event) { TimelineViewMouseEvent timeline_event(ScreenToCoordinate(event->pos()), event->modifiers()); emit MousePressed(&timeline_event); } void TimelineView::mouseMoveEvent(QMouseEvent *event) { TimelineViewMouseEvent timeline_event(ScreenToCoordinate(event->pos()), event->modifiers()); emit MouseMoved(&timeline_event); } void TimelineView::mouseReleaseEvent(QMouseEvent *event) { TimelineViewMouseEvent timeline_event(ScreenToCoordinate(event->pos()), event->modifiers()); emit MouseReleased(&timeline_event); } void TimelineView::mouseDoubleClickEvent(QMouseEvent *event) { TimelineViewMouseEvent timeline_event(ScreenToCoordinate(event->pos()), event->modifiers()); emit MouseDoubleClicked(&timeline_event); } void TimelineView::dragEnterEvent(QDragEnterEvent *event) { TimelineViewMouseEvent timeline_event(ScreenToCoordinate(event->pos()), event->keyboardModifiers()); timeline_event.SetMimeData(event->mimeData()); timeline_event.SetEvent(event); emit DragEntered(&timeline_event); } void TimelineView::dragMoveEvent(QDragMoveEvent *event) { TimelineViewMouseEvent timeline_event(ScreenToCoordinate(event->pos()), event->keyboardModifiers()); timeline_event.SetMimeData(event->mimeData()); timeline_event.SetEvent(event); emit DragMoved(&timeline_event); } void TimelineView::dragLeaveEvent(QDragLeaveEvent *event) { emit DragLeft(event); } void TimelineView::dropEvent(QDropEvent *event) { TimelineViewMouseEvent timeline_event(ScreenToCoordinate(event->pos()), event->keyboardModifiers()); timeline_event.SetMimeData(event->mimeData()); timeline_event.SetEvent(event); emit DragDropped(&timeline_event); } void TimelineView::resizeEvent(QResizeEvent *event) { QGraphicsView::resizeEvent(event); UpdateSceneRect(); } void TimelineView::drawForeground(QPainter *painter, const QRectF &rect) { QGraphicsView::drawForeground(painter, rect); if (!timebase().isNull()) { double x = TimeToScene(rational(playhead_ * timebase().numerator(), timebase().denominator())); double width = TimeToScene(timebase()); QRectF playhead_rect(x, rect.top(), width, rect.height()); painter->setPen(Qt::NoPen); painter->setBrush(playhead_style_.PlayheadHighlightColor()); painter->drawRect(playhead_rect); painter->setPen(playhead_style_.PlayheadColor()); painter->setBrush(Qt::NoBrush); painter->drawLine(QLineF(playhead_rect.topLeft(), playhead_rect.bottomLeft())); } } TrackType TimelineView::ConnectedTrackType() { if (timeline_node_ != nullptr) { return timeline_node_->TrackType(); } return kTrackTypeNone; } Stream::Type TimelineView::TrackTypeToStreamType(TrackType track_type) { switch (track_type) { case kTrackTypeNone: case kTrackTypeCount: break; case kTrackTypeVideo: return Stream::kVideo; case kTrackTypeAudio: return Stream::kAudio; case kTrackTypeSubtitle: return Stream::kSubtitle; } return Stream::kUnknown; } TimelineCoordinate TimelineView::ScreenToCoordinate(const QPoint& pt) { return SceneToCoordinate(mapToScene(pt)); } TimelineCoordinate TimelineView::SceneToCoordinate(const QPointF& pt) { return TimelineCoordinate(SceneToTime(pt.x()), SceneToTrack(pt.y())); } int TimelineView::GetTrackY(int track_index) { int y = 0; for (int i=0;iSetScale(scale_); ghost_items_.append(ghost); scene_.addItem(ghost); } int TimelineView::SceneToTrack(double y) { int track = -1; int heights = 0; if (alignment() & Qt::AlignBottom) { y = -y; } do { track++; heights += GetTrackHeight(track); } while (y > heights); return track; } void TimelineView::UserSetTime(const int64_t &time) { SetTime(time); emit TimeChanged(time); } rational TimelineView::GetPlayheadTime() { return rational(playhead_ * timebase().numerator(), timebase().denominator()); } void TimelineView::BlockChanged() { TimelineViewRect* rect = block_items_[static_cast(sender())]; if (rect != nullptr) { rect->UpdateRect(); } } void TimelineView::UpdateSceneRect() { QRectF bounding_rect = scene_.itemsBoundingRect(); // Ensure the scene height is always AT LEAST the height of the view // The scrollbar appears to have a 1px margin on the top and bottom, hence the -2 int minimum_height = height() - horizontalScrollBar()->height() - 2; if (alignment() & Qt::AlignBottom) { // Ensure the scene left and bottom are always 0 bounding_rect.setBottomLeft(QPointF(0, 0)); if (bounding_rect.top() > minimum_height) { bounding_rect.setTop(-minimum_height); } } else { // Ensure the scene left and top are always 0 bounding_rect.setTopLeft(QPointF(0, 0)); if (bounding_rect.height() < minimum_height) { bounding_rect.setHeight(minimum_height); } } // Ensure the scene is always the full length of the timeline with a gap at the end to work with end_item_->SetEndPadding(width()/4); // If the scene is already this rect, do nothing if (scene_.sceneRect() == bounding_rect) { return; } scene_.setSceneRect(bounding_rect); } void TimelineView::UpdateEndTimeFromTrackList(const rational &length) { if (use_tracklist_length_directly_) { SetEndTime(length); } } void TimelineView::SetEndTime(const rational &length) { end_item_->SetEndTime(length); }