363 lines
9.8 KiB
C++
363 lines
9.8 KiB
C++
/***
|
|
|
|
Olive - Non-Linear Video Editor
|
|
Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
|
|
|
***/
|
|
|
|
#include "seekablewidget.h"
|
|
|
|
#include <QMouseEvent>
|
|
#include <QPainter>
|
|
#include <QtMath>
|
|
|
|
#include "common/qtutils.h"
|
|
#include "core.h"
|
|
#include "widget/timebased/timebasedwidget.h"
|
|
|
|
namespace olive {
|
|
|
|
SeekableWidget::SeekableWidget(QWidget* parent) :
|
|
TimelineScaledWidget(parent),
|
|
timeline_points_(nullptr),
|
|
scroll_(0),
|
|
snap_service_(nullptr),
|
|
dragging_(false)
|
|
{
|
|
QFontMetrics fm = fontMetrics();
|
|
|
|
text_height_ = fm.height();
|
|
|
|
// Set width of playhead marker
|
|
playhead_width_ = QtUtils::QFontMetricsWidth(fm, "H");
|
|
|
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
}
|
|
|
|
void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points)
|
|
{
|
|
if (timeline_points_) {
|
|
disconnect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast<void (SeekableWidget::*)()>(&SeekableWidget::update));
|
|
disconnect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast<void (SeekableWidget::*)()>(&SeekableWidget::update));
|
|
disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast<void (SeekableWidget::*)()>(&SeekableWidget::update));
|
|
disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast<void (SeekableWidget::*)()>(&SeekableWidget::update));
|
|
|
|
foreach(Marker* marker_widget, marker_map_.values()) {
|
|
marker_widget->deleteLater();
|
|
}
|
|
}
|
|
|
|
timeline_points_ = points;
|
|
|
|
if (timeline_points_) {
|
|
connect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast<void (SeekableWidget::*)()>(&SeekableWidget::update));
|
|
connect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast<void (SeekableWidget::*)()>(&SeekableWidget::update));
|
|
connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast<void (SeekableWidget::*)()>(&SeekableWidget::update));
|
|
connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast<void (SeekableWidget::*)()>(&SeekableWidget::update));
|
|
|
|
connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, &SeekableWidget::addMarker);
|
|
connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, &SeekableWidget::removeMarker);
|
|
}
|
|
|
|
if (timeline_points() && !timeline_points()->markers()->list().isEmpty()) {
|
|
foreach (TimelineMarker *marker, timeline_points()->markers()->list()) {
|
|
if (!marker_map_.keys().contains(marker)) {
|
|
addMarker(marker);
|
|
}
|
|
}
|
|
}
|
|
|
|
updateMarkerPositions();
|
|
|
|
update();
|
|
}
|
|
|
|
void SeekableWidget::SetSnapService(SnapService *service)
|
|
{
|
|
snap_service_ = service;
|
|
}
|
|
|
|
void SeekableWidget::DeleteSelected() {
|
|
MultiUndoCommand* command = new MultiUndoCommand();
|
|
|
|
foreach (TimelineMarker *marker, GetActiveTimelineMarkers()) {
|
|
command->add_child(new TimeBasedWidget::MarkerRemoveCommand(Core::instance()->GetActiveProject(), marker, timeline_points_->markers()));
|
|
}
|
|
|
|
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
|
}
|
|
|
|
const int &SeekableWidget::GetScroll() const
|
|
{
|
|
return scroll_;
|
|
}
|
|
|
|
void SeekableWidget::CopySelected(bool cut)
|
|
{
|
|
CopyMarkersToClipboard(GetActiveTimelineMarkers());
|
|
|
|
if (cut) {
|
|
DeleteSelected();
|
|
}
|
|
}
|
|
|
|
void SeekableWidget::PasteMarkers(bool insert, rational insert_time)
|
|
{
|
|
MultiUndoCommand *command = new MultiUndoCommand();
|
|
PasteMarkersFromClipboard(timeline_points()->markers(), command, insert_time);
|
|
}
|
|
|
|
void SeekableWidget::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
if (event->button() == Qt::LeftButton) {
|
|
SeekToScreenPoint(event->pos().x());
|
|
dragging_ = true;
|
|
|
|
DeselectAllMarkers();
|
|
}
|
|
}
|
|
|
|
void SeekableWidget::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
if (event->buttons() & Qt::LeftButton) {
|
|
SeekToScreenPoint(event->pos().x());
|
|
}
|
|
}
|
|
|
|
void SeekableWidget::mouseReleaseEvent(QMouseEvent *event)
|
|
{
|
|
Q_UNUSED(event)
|
|
|
|
if (snap_service_) {
|
|
snap_service_->HideSnaps();
|
|
}
|
|
|
|
dragging_ = false;
|
|
}
|
|
|
|
void SeekableWidget::ScaleChangedEvent(const double &)
|
|
{
|
|
updateMarkerPositions();
|
|
|
|
update();
|
|
}
|
|
|
|
TimelinePoints *SeekableWidget::timeline_points() const
|
|
{
|
|
return timeline_points_;
|
|
}
|
|
|
|
void SeekableWidget::SetTime(const rational &r)
|
|
{
|
|
time_ = r;
|
|
|
|
updateMarkerPositions();
|
|
|
|
update();
|
|
}
|
|
|
|
void SeekableWidget::SetScroll(int s)
|
|
{
|
|
scroll_ = s;
|
|
|
|
updateMarkerPositions();
|
|
|
|
update();
|
|
}
|
|
|
|
QVector<TimelineMarker *> SeekableWidget::GetActiveTimelineMarkers() {
|
|
QVector<TimelineMarker*> active_timelineMarkers;
|
|
foreach (TimelineMarker *marker, timeline_points()->markers()->list()) {
|
|
if (marker->active()) {
|
|
active_timelineMarkers.append(marker);
|
|
}
|
|
}
|
|
|
|
return active_timelineMarkers;
|
|
}
|
|
|
|
|
|
void SeekableWidget::DeselectAllMarkers()
|
|
{
|
|
foreach(TimelineMarker* marker, timeline_points()->markers()->list()) {
|
|
marker->set_active(false);
|
|
}
|
|
}
|
|
|
|
void SeekableWidget::addMarker(TimelineMarker* marker)
|
|
{
|
|
if (!marker_map_.contains(marker)) {
|
|
Marker *marker_widget = new Marker(this);
|
|
marker_map_.insert(marker, marker_widget);
|
|
|
|
connect(marker_widget, &Marker::ColorChanged, this, &SeekableWidget::SetMarkerColor);
|
|
connect(marker, &TimelineMarker::ColorChanged, marker_widget, &Marker::SetColor);
|
|
|
|
connect(marker_widget, &Marker::ActiveChanged, marker, &TimelineMarker::set_active);
|
|
connect(marker, &TimelineMarker::ActiveChanged, marker_widget, &Marker::SetActive);
|
|
|
|
connect(marker_widget, &Marker::NameChanged, marker, &TimelineMarker::set_name);
|
|
connect(marker, &TimelineMarker::NameChanged, marker_widget, &Marker::SetName);
|
|
|
|
marker_widget->move(TimeToScreen(marker->time().in())-2, text_height_);
|
|
marker_widget->SetColor(marker->color());
|
|
marker_widget->SetName(marker->name());
|
|
marker_widget->show();
|
|
}
|
|
}
|
|
|
|
void SeekableWidget::removeMarker(TimelineMarker *marker)
|
|
{
|
|
marker_map_.value(marker)->deleteLater();
|
|
marker_map_.take(marker);
|
|
}
|
|
|
|
void SeekableWidget::SetMarkerColor(int c)
|
|
{
|
|
foreach(TimelineMarker* marker, GetActiveTimelineMarkers()) {
|
|
marker->set_color(c);
|
|
}
|
|
}
|
|
|
|
void SeekableWidget::updateMarkerPositions()
|
|
{
|
|
foreach (TimelineMarker* marker, marker_map_.keys()) {
|
|
Marker *m = marker_map_.value(marker);
|
|
m->move(TimeToScreen(marker->time().in()), text_height_);
|
|
}
|
|
}
|
|
|
|
int SeekableWidget::TimeToScreen(const rational &time) const
|
|
{
|
|
return qFloor(TimeToScene(time)) - scroll_;
|
|
}
|
|
|
|
rational SeekableWidget::ScreenToTime(int x) const
|
|
{
|
|
return qMax(rational(0), SceneToTime(x + scroll_));
|
|
}
|
|
|
|
void SeekableWidget::SeekToScreenPoint(int screen)
|
|
{
|
|
if (timebase().isNull()) {
|
|
return;
|
|
}
|
|
|
|
rational playhead_time = ScreenToTime(screen);
|
|
|
|
if (Core::instance()->snapping() && snap_service_) {
|
|
rational movement;
|
|
|
|
snap_service_->SnapPoint({playhead_time},
|
|
&movement,
|
|
SnapService::kSnapAll & ~SnapService::kSnapToPlayhead);
|
|
|
|
playhead_time += movement;
|
|
}
|
|
|
|
if (playhead_time != GetTime()) {
|
|
SetTime(playhead_time);
|
|
|
|
emit TimeChanged(playhead_time);
|
|
}
|
|
}
|
|
|
|
void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom)
|
|
{
|
|
if (!timeline_points()) {
|
|
return;
|
|
}
|
|
|
|
// Draw in/out workarea
|
|
if (timeline_points()->workarea()->enabled()) {
|
|
int workarea_left = qMax(0, TimeToScreen(timeline_points()->workarea()->in()));
|
|
int workarea_right;
|
|
|
|
if (timeline_points()->workarea()->out() == TimelineWorkArea::kResetOut) {
|
|
workarea_right = width();
|
|
} else {
|
|
workarea_right = qMin(width(), TimeToScreen(timeline_points()->workarea()->out()));
|
|
}
|
|
|
|
p->fillRect(workarea_left, 0, workarea_right - workarea_left, height(), palette().highlight());
|
|
}
|
|
|
|
// Draw markers
|
|
if (marker_bottom > 0 && !timeline_points()->markers()->list().isEmpty()) {
|
|
|
|
int marker_top = marker_bottom - text_height_;
|
|
|
|
// FIXME: Hardcoded marker colors
|
|
p->setPen(Qt::black);
|
|
p->setBrush(Qt::green);
|
|
|
|
foreach (TimelineMarker* marker, timeline_points()->markers()->list()) {
|
|
int marker_left = TimeToScreen(marker->time().in());
|
|
int marker_right = TimeToScreen(marker->time().out());
|
|
|
|
if (marker_left >= width() || marker_right < 0) {
|
|
continue;
|
|
}
|
|
|
|
if (marker->time().length() == 0) {
|
|
// Single point in time marker
|
|
//DrawPlayhead(p, marker_left, marker_bottom);
|
|
} else {
|
|
// Marker range
|
|
int rect_left = qMax(0, marker_left);
|
|
int rect_right = qMin(width(), marker_right);
|
|
|
|
QRect marker_rect(rect_left, marker_top, rect_right - rect_left, marker_bottom - marker_top);
|
|
|
|
p->drawRect(marker_rect);
|
|
|
|
if (!marker->name().isEmpty()) {
|
|
p->drawText(marker_rect, marker->name());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SeekableWidget::DrawPlayhead(QPainter *p, int x, int y)
|
|
{
|
|
int half_width = playhead_width_ / 2;
|
|
|
|
if (x + half_width < 0 || x - half_width > width()) {
|
|
return;
|
|
}
|
|
|
|
p->setRenderHint(QPainter::Antialiasing);
|
|
|
|
int half_text_height = text_height() / 3;
|
|
|
|
QPoint points[] = {
|
|
QPoint(x, y),
|
|
QPoint(x - half_width, y - half_text_height),
|
|
QPoint(x - half_width, y - text_height()),
|
|
QPoint(x + 1 + half_width, y - text_height()),
|
|
QPoint(x + 1 + half_width, y - half_text_height),
|
|
QPoint(x + 1, y),
|
|
};
|
|
|
|
p->drawPolygon(points, 6);
|
|
|
|
p->setRenderHint(QPainter::Antialiasing, false);
|
|
}
|
|
|
|
}
|