1056 lines
28 KiB
C++
1056 lines
28 KiB
C++
/*
|
|
* Olive Community Edition - Non-Linear Video Editor
|
|
* Copyright (C) 2025 Olive CE 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "timelineview.h"
|
|
|
|
#include <QDebug>
|
|
#include <QMimeData>
|
|
#include <QMouseEvent>
|
|
#include <QScrollBar>
|
|
#include <QtMath>
|
|
#include <QPen>
|
|
|
|
#include "config/config.h"
|
|
#include "common/qtutils.h"
|
|
#include "node/project/footage/footage.h"
|
|
#include "panel/panelmanager.h"
|
|
#include "panel/timeline/timeline.h"
|
|
#include "ui/colorcoding.h"
|
|
#include "widget/timelinewidget/timelinewidget.h"
|
|
|
|
namespace olive
|
|
{
|
|
|
|
#define super TimeBasedView
|
|
|
|
TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent)
|
|
: super(parent)
|
|
, selections_(nullptr)
|
|
, ghosts_(nullptr)
|
|
, show_beam_cursor_(false)
|
|
, connected_track_list_(nullptr)
|
|
, transition_overlay_out_(nullptr)
|
|
, transition_overlay_in_(nullptr)
|
|
{
|
|
Q_ASSERT(vertical_alignment == Qt::AlignTop ||
|
|
vertical_alignment == Qt::AlignBottom);
|
|
setAlignment(Qt::AlignLeft | vertical_alignment);
|
|
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
setBackgroundRole(QPalette::Window);
|
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
|
viewport()->setMouseTracking(true);
|
|
|
|
SetIsTimelineAxes(true);
|
|
}
|
|
|
|
void TimelineView::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
// If we click on marker, jump to that point in the timeline
|
|
QPointF scene_pos = mapToScene(event->pos());
|
|
for (auto it = clip_marker_rects_.cbegin(); it != clip_marker_rects_.cend();
|
|
it++) {
|
|
if (it.value().contains(scene_pos)) {
|
|
GetViewerNode()->SetPlayhead(it.key()->time().in());
|
|
break;
|
|
}
|
|
}
|
|
|
|
TimelineViewMouseEvent timeline_event = CreateMouseEvent(event);
|
|
|
|
if (HandPress(event) ||
|
|
(!GetItemAtScenePos(timeline_event.GetFrame(),
|
|
timeline_event.GetTrack().index()) &&
|
|
Core::instance()->tool() != Tool::kAdd && PlayheadPress(event))) {
|
|
// Let the parent handle this
|
|
return;
|
|
}
|
|
|
|
if (dragMode() != GetDefaultDragMode()) {
|
|
// Use default behavior when hand dragging for instance
|
|
super::mousePressEvent(event);
|
|
return;
|
|
}
|
|
|
|
emit MousePressed(&timeline_event);
|
|
}
|
|
|
|
void TimelineView::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
TimelineViewMouseEvent timeline_event = CreateMouseEvent(event);
|
|
|
|
if (HandMove(event) || PlayheadMove(event)) {
|
|
// Let the parent handle this
|
|
return;
|
|
}
|
|
|
|
if (dragMode() != GetDefaultDragMode()) {
|
|
super::mouseMoveEvent(event);
|
|
return;
|
|
}
|
|
|
|
if (event->buttons() == Qt::NoButton) {
|
|
Block *b = GetItemAtScenePos(timeline_event.GetFrame(),
|
|
timeline_event.GetTrack().index());
|
|
if (b) {
|
|
setToolTip(
|
|
tr("In: %1\nOut: %2\nDuration: %3")
|
|
.arg(QString::fromStdString(Timecode::time_to_timecode(
|
|
b->in(), timebase(),
|
|
Core::instance()->GetTimecodeDisplay())),
|
|
QString::fromStdString(Timecode::time_to_timecode(
|
|
b->out(), timebase(),
|
|
Core::instance()->GetTimecodeDisplay())),
|
|
QString::fromStdString(Timecode::time_to_timecode(
|
|
b->length(), timebase(),
|
|
Core::instance()->GetTimecodeDisplay()))));
|
|
} else {
|
|
setToolTip(QString());
|
|
}
|
|
}
|
|
|
|
emit MouseMoved(&timeline_event);
|
|
}
|
|
|
|
void TimelineView::mouseReleaseEvent(QMouseEvent *event)
|
|
{
|
|
if (HandRelease(event) || PlayheadRelease(event)) {
|
|
// Let the parent handle this
|
|
return;
|
|
}
|
|
|
|
if (dragMode() != GetDefaultDragMode()) {
|
|
super::mouseReleaseEvent(event);
|
|
return;
|
|
}
|
|
|
|
TimelineViewMouseEvent timeline_event = CreateMouseEvent(event);
|
|
|
|
emit MouseReleased(&timeline_event);
|
|
}
|
|
|
|
void TimelineView::mouseDoubleClickEvent(QMouseEvent *event)
|
|
{
|
|
TimelineViewMouseEvent timeline_event = CreateMouseEvent(event);
|
|
|
|
emit MouseDoubleClicked(&timeline_event);
|
|
}
|
|
|
|
void TimelineView::dragEnterEvent(QDragEnterEvent *event)
|
|
{
|
|
TimelineViewMouseEvent timeline_event = CreateMouseEvent(
|
|
event->pos(), Qt::NoButton, event->keyboardModifiers());
|
|
|
|
timeline_event.SetMimeData(event->mimeData());
|
|
timeline_event.SetEvent(event);
|
|
|
|
emit DragEntered(&timeline_event);
|
|
}
|
|
|
|
void TimelineView::dragMoveEvent(QDragMoveEvent *event)
|
|
{
|
|
TimelineViewMouseEvent timeline_event = CreateMouseEvent(
|
|
event->pos(), Qt::NoButton, 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 = CreateMouseEvent(
|
|
event->pos(), Qt::NoButton, event->keyboardModifiers());
|
|
|
|
timeline_event.SetMimeData(event->mimeData());
|
|
timeline_event.SetEvent(event);
|
|
|
|
emit DragDropped(&timeline_event);
|
|
}
|
|
|
|
void TimelineView::drawBackground(QPainter *painter, const QRectF &rect)
|
|
{
|
|
if (!connected_track_list_) {
|
|
return;
|
|
}
|
|
|
|
painter->setPen(palette().base().color());
|
|
|
|
int line_y = 0;
|
|
|
|
foreach (Track *track, connected_track_list_->GetTracks()) {
|
|
line_y += track->GetTrackHeightInPixels();
|
|
|
|
// One px gap between tracks
|
|
line_y++;
|
|
|
|
int this_line_y;
|
|
|
|
if (alignment() & Qt::AlignTop) {
|
|
this_line_y = line_y;
|
|
} else {
|
|
this_line_y = -line_y;
|
|
}
|
|
|
|
painter->drawLine(qRound(rect.left()), this_line_y,
|
|
qRound(rect.right()), this_line_y);
|
|
}
|
|
}
|
|
|
|
void TimelineView::drawForeground(QPainter *painter, const QRectF &rect)
|
|
{
|
|
if (!connected_track_list_) {
|
|
return;
|
|
}
|
|
|
|
// Draw block backgrounds
|
|
DrawBlocks(painter, false);
|
|
|
|
// Draw selections
|
|
if (selections_ && !selections_->isEmpty()) {
|
|
painter->setPen(Qt::NoPen);
|
|
painter->setBrush(QColor(0, 0, 0, 64));
|
|
|
|
for (auto it = selections_->cbegin(); it != selections_->cend(); it++) {
|
|
if (it.key().type() == connected_track_list_->type()) {
|
|
int track_index = it.key().index();
|
|
|
|
foreach (const TimeRange &range, it.value()) {
|
|
painter->drawRect(TimeToScene(range.in()),
|
|
GetTrackY(track_index),
|
|
TimeToScene(range.length()),
|
|
GetTrackHeight(track_index));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw block foregrounds
|
|
DrawBlocks(painter, true);
|
|
|
|
// Draw ghosts
|
|
if (ghosts_ && !ghosts_->isEmpty()) {
|
|
foreach (TimelineViewGhostItem *ghost, (*ghosts_)) {
|
|
if (ghost->GetTrack().type() == connected_track_list_->type() &&
|
|
!ghost->IsInvisible()) {
|
|
int track_index = ghost->GetAdjustedTrack().index();
|
|
|
|
Block *attached = QtUtils::ValueToPtr<Block>(
|
|
ghost->GetData(TimelineViewGhostItem::kAttachedBlock));
|
|
|
|
if (attached &&
|
|
OLIVE_CONFIG("ShowClipWhileDragging").toBool()) {
|
|
int adj_track = ghost->GetAdjustedTrack().index();
|
|
qreal track_top = GetTrackY(adj_track);
|
|
qreal track_height = GetTrackHeight(adj_track);
|
|
|
|
qreal old_opacity = painter->opacity();
|
|
painter->setOpacity(0.5);
|
|
|
|
rational in = ghost->GetAdjustedIn(),
|
|
out = ghost->GetAdjustedOut(),
|
|
media_in = ghost->GetAdjustedMediaIn();
|
|
DrawBlock(painter, false, attached, track_top, track_height,
|
|
in, out, media_in);
|
|
DrawBlock(painter, true, attached, track_top, track_height,
|
|
in, out, media_in);
|
|
|
|
painter->setOpacity(old_opacity);
|
|
}
|
|
|
|
painter->setPen(QPen(Qt::yellow, 2));
|
|
painter->setBrush(Qt::NoBrush);
|
|
|
|
painter->drawRect(TimeToScene(ghost->GetAdjustedIn()),
|
|
GetTrackY(track_index),
|
|
TimeToScene(ghost->GetAdjustedLength()),
|
|
GetTrackHeight(track_index));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw beam cursor
|
|
if (show_beam_cursor_ &&
|
|
cursor_coord_.GetTrack().type() == connected_track_list_->type()) {
|
|
painter->setPen(Qt::gray);
|
|
|
|
double cursor_x = TimeToScene(cursor_coord_.GetFrame());
|
|
int track_index = cursor_coord_.GetTrack().index();
|
|
int track_y = GetTrackY(track_index);
|
|
|
|
painter->drawLine(cursor_x, track_y, cursor_x,
|
|
track_y + GetTrackHeight(track_index));
|
|
}
|
|
|
|
// Draw recording overlay
|
|
if (recording_overlay_ &&
|
|
recording_coord_.GetTrack().type() == connected_track_list_->type()) {
|
|
painter->setPen(QPen(Qt::red, 2));
|
|
painter->setBrush(QColor(255, 128, 128));
|
|
|
|
int x = TimeToScene(recording_coord_.GetFrame());
|
|
painter->drawRect(x, GetTrackY(recording_coord_.GetTrack().index()),
|
|
TimeToScene(GetViewerNode()->GetPlayhead()) - x,
|
|
GetTrackHeight(recording_coord_.GetTrack().index()));
|
|
}
|
|
|
|
// Draw standard TimelineViewBase things (such as playhead)
|
|
super::drawForeground(painter, rect);
|
|
}
|
|
|
|
void TimelineView::ToolChangedEvent(Tool::Item tool)
|
|
{
|
|
switch (tool) {
|
|
case Tool::kRazor:
|
|
setCursor(Qt::SplitHCursor);
|
|
break;
|
|
case Tool::kEdit:
|
|
setCursor(Qt::IBeamCursor);
|
|
break;
|
|
case Tool::kAdd:
|
|
case Tool::kTransition:
|
|
case Tool::kZoom:
|
|
case Tool::kRecord:
|
|
setCursor(Qt::CrossCursor);
|
|
break;
|
|
case Tool::kTrackSelect:
|
|
setCursor(Qt::SizeHorCursor); // FIXME: Not the ideal cursor
|
|
break;
|
|
default:
|
|
unsetCursor();
|
|
}
|
|
|
|
// Hide/show cursor if necessary
|
|
if (show_beam_cursor_) {
|
|
show_beam_cursor_ = false;
|
|
viewport()->update();
|
|
}
|
|
}
|
|
|
|
void TimelineView::SceneRectUpdateEvent(QRectF &rect)
|
|
{
|
|
if (alignment() & Qt::AlignTop) {
|
|
rect.setTop(0);
|
|
rect.setBottom(GetHeightOfAllTracks() + height() / 2);
|
|
} else if (alignment() & Qt::AlignBottom) {
|
|
rect.setBottom(0);
|
|
rect.setTop(GetHeightOfAllTracks() - height() / 2);
|
|
}
|
|
}
|
|
|
|
Track::Type TimelineView::ConnectedTrackType()
|
|
{
|
|
if (connected_track_list_) {
|
|
return connected_track_list_->type();
|
|
}
|
|
|
|
return Track::kNone;
|
|
}
|
|
|
|
TimelineCoordinate TimelineView::ScreenToCoordinate(const QPoint &pt)
|
|
{
|
|
return SceneToCoordinate(mapToScene(pt));
|
|
}
|
|
|
|
TimelineCoordinate TimelineView::SceneToCoordinate(const QPointF &pt)
|
|
{
|
|
return TimelineCoordinate(SceneToTime(pt.x()),
|
|
Track::Reference(ConnectedTrackType(),
|
|
SceneToTrack(pt.y())));
|
|
}
|
|
|
|
TimelineViewMouseEvent TimelineView::CreateMouseEvent(QMouseEvent *event)
|
|
{
|
|
return CreateMouseEvent(event->pos(), event->button(), event->modifiers());
|
|
}
|
|
|
|
TimelineViewMouseEvent
|
|
TimelineView::CreateMouseEvent(const QPoint &pos, Qt::MouseButton button,
|
|
Qt::KeyboardModifiers modifiers)
|
|
{
|
|
QPointF scene_pt = mapToScene(pos);
|
|
|
|
return TimelineViewMouseEvent(scene_pt, pos, GetScale(), timebase(),
|
|
Track::Reference(ConnectedTrackType(),
|
|
SceneToTrack(scene_pt.y())),
|
|
button, modifiers);
|
|
}
|
|
|
|
void TimelineView::DrawBlocks(QPainter *painter, bool foreground)
|
|
{
|
|
rational start_time = SceneToTime(GetTimelineLeftBound());
|
|
rational end_time = SceneToTime(GetTimelineRightBound());
|
|
|
|
foreach (Track *track, connected_track_list_->GetTracks()) {
|
|
// Get first visible block in this track
|
|
Block *block = track->NearestBlockBeforeOrAt(start_time);
|
|
|
|
qreal track_top = GetTrackY(track->Index());
|
|
qreal track_height = GetTrackHeight(track->Index());
|
|
|
|
while (block) {
|
|
DrawBlock(painter, foreground, block, track_top, track_height);
|
|
|
|
if (block->out() >= end_time) {
|
|
// Rest of the clips are offscreen, can break loop now
|
|
break;
|
|
}
|
|
|
|
block = block->next();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block,
|
|
qreal block_top, qreal block_height,
|
|
const rational &in, const rational &out,
|
|
const rational &media_in)
|
|
{
|
|
if (dynamic_cast<ClipBlock *>(block) ||
|
|
dynamic_cast<TransitionBlock *>(block)) {
|
|
qreal block_in = TimeToScene(in);
|
|
|
|
qreal block_left = qMax(GetTimelineLeftBound(), block_in);
|
|
qreal block_right = qMin(GetTimelineRightBound(), TimeToScene(out)) - 1;
|
|
|
|
QRectF r(block_left, block_top, block_right - block_left, block_height);
|
|
|
|
QColor shadow_color = block->is_enabled() ?
|
|
QtUtils::toQColor(block->color()).darker() :
|
|
QColor(Qt::darkGray).darker();
|
|
|
|
const qreal MINIMUM_RECT_WIDTH = 2;
|
|
const qreal MINIMUM_DETAIL_WIDTH = 8;
|
|
|
|
if (r.width() <= MINIMUM_RECT_WIDTH) {
|
|
if (!foreground) {
|
|
// Just draw a green background
|
|
// Width is likely fractional, so we ceil it and add 1 to ensure the entire width of the
|
|
// rect is painted
|
|
r.setWidth(std::ceil(r.width()) + 1);
|
|
painter->fillRect(r, shadow_color);
|
|
}
|
|
} else {
|
|
QFontMetrics fm = fontMetrics();
|
|
int text_height = fm.height();
|
|
int text_padding =
|
|
text_height /
|
|
4; // This ties into the track minimum height being 1.5
|
|
int text_total_height = text_height + text_padding + text_padding;
|
|
|
|
if (foreground) {
|
|
painter->setBrush(Qt::NoBrush);
|
|
|
|
if (r.width() > MINIMUM_DETAIL_WIDTH) {
|
|
QString using_label = block->GetLabelOrName();
|
|
|
|
QRectF text_rect = r.adjusted(text_padding, text_padding,
|
|
-text_padding, -text_padding);
|
|
painter->setPen(
|
|
block->is_enabled() ?
|
|
ColorCoding::GetUISelectorColor(block->color()) :
|
|
Qt::lightGray);
|
|
painter->drawText(text_rect, Qt::AlignLeft | Qt::AlignTop,
|
|
using_label);
|
|
|
|
if (block->HasLinks()) {
|
|
int text_width =
|
|
qMin(qRound(text_rect.width()),
|
|
QtUtils::QFontMetricsWidth(fm, using_label));
|
|
|
|
int underline_y = text_rect.y() + text_height;
|
|
|
|
painter->drawLine(text_rect.x(), underline_y,
|
|
text_width + text_rect.x(),
|
|
underline_y);
|
|
}
|
|
}
|
|
|
|
qreal line_bottom = block_top + block_height - 1;
|
|
|
|
painter->setPen(Qt::white);
|
|
painter->drawLine(block_left, block_top, block_right,
|
|
block_top);
|
|
painter->drawLine(block_left, block_top, block_left,
|
|
line_bottom);
|
|
|
|
painter->setPen(shadow_color);
|
|
painter->drawLine(block_left, line_bottom, block_right,
|
|
line_bottom);
|
|
painter->drawLine(block_right, line_bottom, block_right,
|
|
block_top);
|
|
} else {
|
|
painter->setPen(Qt::NoPen);
|
|
painter->setBrush(
|
|
block->is_enabled() ?
|
|
block->brush(block_top, block_top + block_height) :
|
|
Qt::gray);
|
|
painter->drawRect(r);
|
|
|
|
if (r.width() > MINIMUM_DETAIL_WIDTH) {
|
|
if (ClipBlock *clip = dynamic_cast<ClipBlock *>(block)) {
|
|
QRect preview_rect = r.toRect();
|
|
|
|
// Draw clip thumbnails
|
|
if (clip->GetTrackType() == Track::kVideo &&
|
|
OLIVE_CONFIG("TimelineThumbnailMode").toInt() !=
|
|
Timeline::kThumbnailOff) {
|
|
// Start thumbnails underneath clip name
|
|
preview_rect.adjust(0, text_total_height, 0, 0);
|
|
|
|
if (preview_rect.height() > r.height() / 3) {
|
|
if (const FrameHashCache *thumbs =
|
|
clip->thumbnails()) {
|
|
QRect thumb_rect;
|
|
painter->setRenderHint(
|
|
QPainter::SmoothPixmapTransform);
|
|
painter->setClipRect(preview_rect);
|
|
|
|
if (OLIVE_CONFIG("TimelineThumbnailMode") ==
|
|
Timeline::kThumbnailOn) {
|
|
Sequence *s = clip->track()->sequence();
|
|
int width = s->GetVideoParams().width();
|
|
int height =
|
|
s->GetVideoParams().height();
|
|
int start;
|
|
if (height >
|
|
0) { // Prevent divide by zero/invalid params
|
|
double scale =
|
|
double(preview_rect.height()) /
|
|
double(height);
|
|
thumb_rect.setWidth(width * scale);
|
|
start = (((preview_rect.left() -
|
|
int(qFloor(block_in))) /
|
|
thumb_rect.width()) *
|
|
thumb_rect.width()) +
|
|
qFloor(block_in);
|
|
} else {
|
|
start = preview_rect.left();
|
|
}
|
|
|
|
for (int i = start;
|
|
i < preview_rect.right();
|
|
i += thumb_rect.width() + 1) {
|
|
rational time_here =
|
|
SceneToTime(
|
|
i - block_in, GetScale(),
|
|
connected_track_list_
|
|
->parent()
|
|
->GetVideoParams()
|
|
.frame_rate_as_time_base()) +
|
|
media_in;
|
|
DrawThumbnail(painter, thumbs,
|
|
time_here, i,
|
|
preview_rect,
|
|
&thumb_rect);
|
|
}
|
|
|
|
} else {
|
|
rational time =
|
|
clip->media_range().in();
|
|
time = Timecode::snap_time_to_timebase(
|
|
time, thumbs->GetTimebase(),
|
|
Timecode::kFloor);
|
|
DrawThumbnail(painter, thumbs, time,
|
|
block_left, preview_rect,
|
|
&thumb_rect);
|
|
}
|
|
|
|
painter->setClipping(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw waveform
|
|
if (clip->GetTrackType() == Track::kAudio &&
|
|
OLIVE_CONFIG("TimelineWaveformMode").toInt() ==
|
|
Timeline::kWaveformsEnabled) {
|
|
if (const AudioWaveformCache *wave =
|
|
clip->waveform()) {
|
|
rational waveform_start =
|
|
SceneToTime(
|
|
block_left - block_in, GetScale(),
|
|
connected_track_list_->parent()
|
|
->GetAudioParams()
|
|
.sample_rate_as_time_base()) +
|
|
media_in;
|
|
painter->setPen(shadow_color);
|
|
|
|
wave->Draw(painter, preview_rect,
|
|
this->GetScale(), waveform_start);
|
|
}
|
|
}
|
|
|
|
// Draw zebra stripes and markers
|
|
if (clip->connected_viewer()) {
|
|
if (!clip->connected_viewer()->GetLength().isNull()) {
|
|
painter->setPen(shadow_color);
|
|
|
|
if (clip->media_in() < 0) {
|
|
qreal zebra_right = TimeToScene(
|
|
clip->in() - clip->media_in());
|
|
|
|
switch (clip->loop_mode()) {
|
|
case LoopMode::kLoopModeOff:
|
|
// Draw stripes for sections of clip < 0
|
|
if (zebra_right >
|
|
GetTimelineLeftBound()) {
|
|
DrawZebraStripes(
|
|
painter,
|
|
QRectF(block_left, block_top,
|
|
zebra_right - block_left,
|
|
block_height));
|
|
}
|
|
break;
|
|
case LoopMode::kLoopModeLoop:
|
|
for (qreal i = zebra_right;
|
|
i > block_left;
|
|
i -= TimeToScene(
|
|
clip->connected_viewer()
|
|
->GetLength())) {
|
|
painter->drawLine(i, block_top, i,
|
|
block_top +
|
|
block_height);
|
|
}
|
|
break;
|
|
case LoopMode::kLoopModeClamp:
|
|
painter->drawLine(
|
|
zebra_right, block_top, zebra_right,
|
|
block_top + block_height);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (clip->length() + clip->media_in() >
|
|
clip->connected_viewer()->GetLength()) {
|
|
qreal zebra_left = TimeToScene(
|
|
clip->out() -
|
|
(clip->media_in() + clip->length() -
|
|
clip->connected_viewer()->GetLength()));
|
|
switch (clip->loop_mode()) {
|
|
case LoopMode::kLoopModeOff:
|
|
// Draw stripes for sections for clip > clip length
|
|
if (zebra_left <
|
|
GetTimelineRightBound()) {
|
|
DrawZebraStripes(
|
|
painter,
|
|
QRectF(zebra_left, block_top,
|
|
block_right - zebra_left,
|
|
block_height));
|
|
}
|
|
break;
|
|
case LoopMode::kLoopModeLoop:
|
|
for (qreal i = zebra_left;
|
|
i < block_right;
|
|
i += TimeToScene(
|
|
clip->connected_viewer()
|
|
->GetLength())) {
|
|
painter->drawLine(i, block_top, i,
|
|
block_top +
|
|
block_height);
|
|
}
|
|
break;
|
|
case LoopMode::kLoopModeClamp:
|
|
painter->drawLine(
|
|
zebra_left, block_top, zebra_left,
|
|
block_top + block_height);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TimelineMarkerList *marker_list =
|
|
clip->connected_viewer()->GetMarkers();
|
|
if (!marker_list->empty()) {
|
|
clip_marker_rects_.clear();
|
|
|
|
for (auto it = marker_list->cbegin();
|
|
it != marker_list->cend(); it++) {
|
|
TimelineMarker *marker = *it;
|
|
// Make sure marker is within In/Out points of the clip
|
|
if (marker->time().in() >=
|
|
clip->media_in() &&
|
|
marker->time().out() <=
|
|
clip->media_in() + clip->length()) {
|
|
QPoint marker_pt(
|
|
TimeToScene(clip->in() -
|
|
clip->media_in() +
|
|
marker->time().in()),
|
|
block_top + block_height);
|
|
painter->setClipRect(r);
|
|
QRect marker_rect =
|
|
marker->Draw(painter, marker_pt, -1,
|
|
GetScale(), false);
|
|
clip_marker_rects_.insert(marker,
|
|
marker_rect);
|
|
painter->setClipping(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (const FrameHashCache *cache =
|
|
clip->connected_video_cache()) {
|
|
if (cache->HasValidatedRanges()) {
|
|
QRect cache_rect =
|
|
r.adjusted(
|
|
0,
|
|
r.height() -
|
|
PlaybackCache::
|
|
GetCacheIndicatorHeight(),
|
|
0, 0)
|
|
.toRect();
|
|
cache->Draw(painter, clip->media_in(),
|
|
GetScale(), cache_rect);
|
|
}
|
|
}
|
|
}
|
|
|
|
// For transitions, show lines representing a transition
|
|
if (TransitionBlock *transition =
|
|
dynamic_cast<TransitionBlock *>(block)) {
|
|
QVector<QLineF> lines;
|
|
|
|
if (transition->connected_in_block()) {
|
|
lines.append(QLineF(r.bottomLeft(), r.topRight()));
|
|
}
|
|
|
|
if (transition->connected_out_block()) {
|
|
lines.append(QLineF(r.topLeft(), r.bottomRight()));
|
|
}
|
|
|
|
painter->setPen(shadow_color);
|
|
painter->drawLines(lines);
|
|
}
|
|
|
|
if (transition_overlay_out_ == block ||
|
|
transition_overlay_in_ == block) {
|
|
QRectF transition_overlay_rect = r;
|
|
|
|
qreal transition_overlay_width =
|
|
TimeToScene(block->length()) * 0.5;
|
|
if (transition_overlay_out_ && transition_overlay_in_) {
|
|
// This is a dual transition, use the smallest width
|
|
Block *other_block =
|
|
(transition_overlay_out_ == block) ?
|
|
transition_overlay_in_ :
|
|
transition_overlay_out_;
|
|
|
|
qreal other_width =
|
|
TimeToScene(other_block->length()) * 0.5;
|
|
|
|
transition_overlay_width =
|
|
qMin(transition_overlay_width, other_width);
|
|
}
|
|
|
|
if (transition_overlay_out_ == block) {
|
|
transition_overlay_rect.setLeft(
|
|
transition_overlay_rect.right() -
|
|
transition_overlay_width);
|
|
} else {
|
|
transition_overlay_rect.setRight(
|
|
transition_overlay_rect.left() +
|
|
transition_overlay_width);
|
|
}
|
|
|
|
painter->setPen(Qt::NoPen);
|
|
painter->setBrush(QColor(0, 0, 0, 64));
|
|
|
|
painter->drawRect(transition_overlay_rect);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TimelineView::DrawZebraStripes(QPainter *painter, const QRectF &r)
|
|
{
|
|
int zebra_interval = fontMetrics().height();
|
|
|
|
painter->setPen(QPen(QColor(0, 0, 0, 128), zebra_interval / 4));
|
|
painter->setBrush(Qt::NoBrush);
|
|
|
|
QVector<QLineF> lines;
|
|
lines.reserve(qCeil(r.width() / zebra_interval));
|
|
|
|
qreal left = r.left() - r.height();
|
|
qreal right = r.right() + r.height();
|
|
|
|
for (qreal i = left; i < right; i += zebra_interval) {
|
|
lines.append(QLineF(i, r.top(), i - r.height(), r.bottom()));
|
|
}
|
|
|
|
painter->setClipRect(r);
|
|
painter->drawLines(lines);
|
|
painter->setClipping(false);
|
|
}
|
|
|
|
int TimelineView::GetHeightOfAllTracks() const
|
|
{
|
|
if (connected_track_list_) {
|
|
if (alignment() & Qt::AlignTop) {
|
|
return GetTrackY(connected_track_list_->GetTrackCount());
|
|
} else {
|
|
return GetTrackY(connected_track_list_->GetTrackCount() - 1);
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
qreal TimelineView::GetTimelineLeftBound() const
|
|
{
|
|
return horizontalScrollBar()->value();
|
|
}
|
|
|
|
qreal TimelineView::GetTimelineRightBound() const
|
|
{
|
|
return GetTimelineLeftBound() + viewport()->width();
|
|
}
|
|
|
|
void TimelineView::DrawThumbnail(QPainter *painter,
|
|
const FrameHashCache *thumbs,
|
|
const rational &time, int x,
|
|
const QRect &preview_rect,
|
|
QRect *thumb_rect) const
|
|
{
|
|
QString thumbnail = thumbs->GetValidCacheFilename(time);
|
|
|
|
if (!thumbnail.isEmpty()) {
|
|
QImage img;
|
|
if (img.load(thumbnail, "jpg")) {
|
|
double scale = double(preview_rect.height()) / double(img.height());
|
|
*thumb_rect = QRect(x, preview_rect.top(), img.width() * scale,
|
|
preview_rect.height());
|
|
painter->drawImage(*thumb_rect, img);
|
|
}
|
|
}
|
|
}
|
|
|
|
int TimelineView::GetTrackY(int track_index) const
|
|
{
|
|
if (!connected_track_list_ || !connected_track_list_->GetTrackCount()) {
|
|
return 0;
|
|
}
|
|
|
|
int y = 0;
|
|
|
|
if (alignment() & Qt::AlignBottom) {
|
|
track_index++;
|
|
}
|
|
|
|
for (int i = 0; i < track_index; i++) {
|
|
y += GetTrackHeight(i);
|
|
|
|
// One px line between each track
|
|
y++;
|
|
}
|
|
|
|
if (alignment() & Qt::AlignBottom) {
|
|
y = -y + 1;
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
int TimelineView::GetTrackHeight(int track_index) const
|
|
{
|
|
if (!connected_track_list_ || connected_track_list_->GetTrackCount() == 0) {
|
|
// Handle null or empty track list
|
|
return Track::GetDefaultTrackHeightInPixels();
|
|
}
|
|
|
|
if (track_index >= connected_track_list_->GetTrackCount()) {
|
|
// Handle new track at the end of the list
|
|
return connected_track_list_
|
|
->GetTrackAt(connected_track_list_->GetTrackCount() - 1)
|
|
->GetTrackHeightInPixels();
|
|
}
|
|
|
|
if (track_index < 0) {
|
|
// Handle new track at the beginning of the list
|
|
return connected_track_list_->GetTrackAt(0)->GetTrackHeightInPixels();
|
|
}
|
|
|
|
// Track definitely exists, return its actual height
|
|
return connected_track_list_->GetTrackAt(track_index)
|
|
->GetTrackHeightInPixels();
|
|
}
|
|
|
|
QPoint TimelineView::GetScrollCoordinates() const
|
|
{
|
|
return QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value());
|
|
}
|
|
|
|
void TimelineView::SetScrollCoordinates(const QPoint &pt)
|
|
{
|
|
horizontalScrollBar()->setValue(pt.x());
|
|
verticalScrollBar()->setValue(pt.y());
|
|
}
|
|
|
|
void TimelineView::ConnectTrackList(TrackList *list)
|
|
{
|
|
if (connected_track_list_) {
|
|
disconnect(connected_track_list_, &TrackList::TrackListChanged, this,
|
|
&TimelineView::TrackListChanged);
|
|
disconnect(connected_track_list_, &TrackList::TrackHeightChanged, this,
|
|
&TimelineView::TrackListChanged);
|
|
}
|
|
|
|
connected_track_list_ = list;
|
|
|
|
if (connected_track_list_) {
|
|
connect(connected_track_list_, &TrackList::TrackListChanged, this,
|
|
&TimelineView::TrackListChanged);
|
|
connect(connected_track_list_, &TrackList::TrackHeightChanged, this,
|
|
&TimelineView::TrackListChanged);
|
|
}
|
|
}
|
|
|
|
void TimelineView::SetBeamCursor(const TimelineCoordinate &coord)
|
|
{
|
|
if (!connected_track_list_) {
|
|
return;
|
|
}
|
|
|
|
bool update_required =
|
|
coord.GetTrack().type() == connected_track_list_->type() ||
|
|
cursor_coord_.GetTrack().type() == connected_track_list_->type();
|
|
|
|
show_beam_cursor_ = true;
|
|
cursor_coord_ = coord;
|
|
|
|
if (update_required) {
|
|
viewport()->update();
|
|
}
|
|
}
|
|
|
|
void TimelineView::SetTransitionOverlay(ClipBlock *out, ClipBlock *in)
|
|
{
|
|
if (transition_overlay_out_ != out || transition_overlay_in_ != in) {
|
|
Track::Type type = Track::kNone;
|
|
|
|
if (out) {
|
|
type = out->track()->type();
|
|
} else if (in) {
|
|
type = in->track()->type();
|
|
}
|
|
|
|
if (type == this->connected_track_list_->type()) {
|
|
transition_overlay_out_ = out;
|
|
transition_overlay_in_ = in;
|
|
} else {
|
|
transition_overlay_out_ = nullptr;
|
|
transition_overlay_in_ = nullptr;
|
|
}
|
|
|
|
viewport()->update();
|
|
}
|
|
}
|
|
|
|
void TimelineView::EnableRecordingOverlay(const TimelineCoordinate &coord)
|
|
{
|
|
recording_overlay_ = true;
|
|
recording_coord_ = coord;
|
|
viewport()->update();
|
|
}
|
|
|
|
void TimelineView::DisableRecordingOverlay()
|
|
{
|
|
recording_overlay_ = false;
|
|
viewport()->update();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
Block *TimelineView::GetItemAtScenePos(const rational &time,
|
|
int track_index) const
|
|
{
|
|
if (connected_track_list_) {
|
|
Track *track = connected_track_list_->GetTrackAt(track_index);
|
|
|
|
if (track) {
|
|
foreach (Block *b, track->Blocks()) {
|
|
if (b->in() <= time && b->out() > time) {
|
|
return b;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
QVector<Block *> TimelineView::GetItemsAtSceneRect(const QRectF &rect) const
|
|
{
|
|
QVector<Block *> list;
|
|
|
|
if (connected_track_list_) {
|
|
rational start = this->SceneToTime(rect.left());
|
|
rational end = this->SceneToTime(rect.right());
|
|
|
|
for (int i = 0; i < connected_track_list_->GetTrackCount(); i++) {
|
|
Track *track = connected_track_list_->GetTrackAt(i);
|
|
int track_top = GetTrackY(i);
|
|
int track_bottom = track_top + GetTrackHeight(i);
|
|
|
|
if (track) {
|
|
if (!(track_bottom < rect.top() || track_top > rect.bottom())) {
|
|
Block *b = track->NearestBlockBeforeOrAt(start);
|
|
while (b && b->in() < end) {
|
|
list.append(b);
|
|
b = b->next();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
void TimelineView::TrackListChanged()
|
|
{
|
|
UpdateSceneRect();
|
|
viewport()->update();
|
|
}
|
|
|
|
}
|