Files
oak-editor/app/widget/timelinewidget/view/timelineviewbase.cpp
T

244 lines
6.2 KiB
C++

#include "timelineviewbase.h"
#include <QGraphicsRectItem>
#include <QMouseEvent>
#include <QScrollBar>
#include <QTimer>
#include "common/autoscroll.h"
#include "common/timecodefunctions.h"
#include "config/config.h"
TimelineViewBase::TimelineViewBase(QWidget *parent) :
QGraphicsView(parent),
playhead_(0),
playhead_scene_left_(-1),
playhead_scene_right_(-1),
limit_y_axis_(false)
{
setScene(&scene_);
// Create end item
end_item_ = new TimelineViewEndItem();
scene_.addItem(end_item_);
// Set default scale
SetScale(1.0);
setViewportUpdateMode(FullViewportUpdate);
connect(&scene_, SIGNAL(changed(const QList<QRectF>&)), this, SLOT(UpdateSceneRect()));
}
void TimelineViewBase::SetScale(const double &scale)
{
scale_ = scale;
end_item_->SetScale(scale_);
ScaleChangedEvent(scale_);
// Force redraw for playhead
viewport()->update();
// Zoom towards the playhead
// (using a hacky singleShot so the scroll occurs after the scene and its scrollbars have updated)
QTimer::singleShot(0, this, &TimelineViewBase::CenterScrollOnPlayhead);
}
void TimelineViewBase::SetTimebase(const rational &timebase)
{
SetTimebaseInternal(timebase);
// Timebase influences position/visibility of playhead
viewport()->update();
}
void TimelineViewBase::SetTime(const int64_t time)
{
playhead_ = time;
switch (static_cast<AutoScroll::Method>(Config::Current()["Autoscroll"].toInt())) {
case AutoScroll::kNone:
// Do nothing
break;
case AutoScroll::kPage:
PageScrollToPlayhead();
break;
case AutoScroll::kSmooth:
CenterScrollOnPlayhead();
break;
}
// Force redraw for playhead
viewport()->update();
}
void TimelineViewBase::drawForeground(QPainter *painter, const QRectF &rect)
{
QGraphicsView::drawForeground(painter, rect);
if (!timebase().isNull()) {
double width = TimeToScene(timebase());
playhead_scene_left_ = GetPlayheadX();
playhead_scene_right_ = playhead_scene_left_ + width;
playhead_style_.Draw(painter, QRectF(playhead_scene_left_, rect.top(), width, rect.height()));
}
}
rational TimelineViewBase::GetPlayheadTime()
{
return rational(playhead_ * timebase().numerator(), timebase().denominator());
}
bool TimelineViewBase::PlayheadPress(QMouseEvent *event)
{
QPointF scene_pos = mapToScene(event->pos());
dragging_playhead_ = (scene_pos.x() >= playhead_scene_left_ && scene_pos.x() < playhead_scene_right_);
return dragging_playhead_;
}
bool TimelineViewBase::PlayheadMove(QMouseEvent *event)
{
if (!dragging_playhead_) {
return false;
}
QPointF scene_pos = mapToScene(event->pos());
rational mouse_time = SceneToTime(scene_pos.x());
int64_t target_ts = qMax(static_cast<int64_t>(0), Timecode::time_to_timestamp(mouse_time, timebase()));
SetTime(target_ts);
emit TimeChanged(target_ts);
return true;
}
bool TimelineViewBase::PlayheadRelease(QMouseEvent *event)
{
return dragging_playhead_;
}
qreal TimelineViewBase::GetPlayheadX()
{
return TimeToScene(rational(playhead_ * timebase().numerator(), timebase().denominator()));
}
void TimelineViewBase::SetEndTime(const rational &length)
{
end_item_->SetEndTime(length);
}
void TimelineViewBase::UpdateSceneRect()
{
QRectF bounding_rect = scene_.itemsBoundingRect();
if (limit_y_axis_) {
// Make a gap of half the viewport height
if (alignment() & Qt::AlignBottom) {
bounding_rect.setTop(bounding_rect.top() - height()/2);
} else {
bounding_rect.setBottom(bounding_rect.bottom() + height()/2);
}
// 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);
}
}
} else {
// We'll still limit the X to 0 since that behavior is desired by all TimelineViewBase derivatives
bounding_rect.setLeft(0);
}
// Ensure the scene is always the full length of the timeline with a gap at the end to work with
end_item_->SetEndPadding(width()/2);
// If the scene is already this rect, do nothing
if (scene_.sceneRect() == bounding_rect) {
return;
}
scene_.setSceneRect(bounding_rect);
}
void TimelineViewBase::CenterScrollOnPlayhead()
{
horizontalScrollBar()->setValue(qRound(GetPlayheadX()) - viewport()->width()/2);
}
void TimelineViewBase::PageScrollToPlayhead()
{
int playhead_pos = qRound(GetPlayheadX());
int viewport_padding = viewport()->width() / 16;
if (playhead_pos < horizontalScrollBar()->value()) {
// Anchor the playhead to the RIGHT of where we scroll to
horizontalScrollBar()->setValue(playhead_pos - viewport()->width() + viewport_padding);
} else if (playhead_pos > horizontalScrollBar()->value() + viewport()->width()) {
// Anchor the playhead to the LEFT of where we scroll to
horizontalScrollBar()->setValue(playhead_pos - viewport_padding);
}
}
void TimelineViewBase::resizeEvent(QResizeEvent *event)
{
QGraphicsView::resizeEvent(event);
UpdateSceneRect();
}
void TimelineViewBase::ScaleChangedEvent(double scale)
{
Q_UNUSED(scale)
}
bool TimelineViewBase::HandleZoomFromScroll(QWheelEvent *event)
{
if (WheelEventIsAZoomEvent(event)) {
// If CTRL is held (or a preference is set to swap CTRL behavior), we zoom instead of scrolling
if (event->delta() != 0) {
if (event->delta() > 0) {
emit ScaleChanged(scale_ * 2.0);
} else {
emit ScaleChanged(scale_ * 0.5);
}
}
return true;
}
return false;
}
bool TimelineViewBase::WheelEventIsAZoomEvent(QWheelEvent *event)
{
return (static_cast<bool>(event->modifiers() & Qt::ControlModifier) == !Config::Current()["ScrollZooms"].toBool());
}
void TimelineViewBase::SetLimitYAxis(bool e)
{
limit_y_axis_ = true;
UpdateSceneRect();
}