diff --git a/app/common/rational.cpp b/app/common/rational.cpp
index e7020f92b..d7d374230 100644
--- a/app/common/rational.cpp
+++ b/app/common/rational.cpp
@@ -16,7 +16,7 @@ rational rational::fromDouble(const double &flt, bool* ok)
}
// Use FFmpeg function for the time being
- AVRational r = av_d2q(flt, INT_MAX);
+ AVRational r = av_d2q(flt, 65535);
if (r.den == 0) {
// If den == 0, we were unable to convert to a rational
diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt
index a7ecf0f78..1f9df6e3d 100644
--- a/app/dialog/CMakeLists.txt
+++ b/app/dialog/CMakeLists.txt
@@ -24,6 +24,7 @@ add_subdirectory(export)
add_subdirectory(footageproperties)
add_subdirectory(footagerelink)
add_subdirectory(keyframeproperties)
+add_subdirectory(markerproperties)
if(OpenTimelineIO_FOUND)
add_subdirectory(otioproperties)
endif()
diff --git a/app/dialog/markerproperties/CMakeLists.txt b/app/dialog/markerproperties/CMakeLists.txt
new file mode 100644
index 000000000..222c9246e
--- /dev/null
+++ b/app/dialog/markerproperties/CMakeLists.txt
@@ -0,0 +1,22 @@
+# 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 .
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ dialog/markerproperties/markerpropertiesdialog.h
+ dialog/markerproperties/markerpropertiesdialog.cpp
+ PARENT_SCOPE
+)
diff --git a/app/dialog/markerproperties/markerpropertiesdialog.cpp b/app/dialog/markerproperties/markerpropertiesdialog.cpp
new file mode 100644
index 000000000..5473af3ab
--- /dev/null
+++ b/app/dialog/markerproperties/markerpropertiesdialog.cpp
@@ -0,0 +1,152 @@
+/***
+
+ 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 .
+
+***/
+
+#include "markerpropertiesdialog.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "core.h"
+
+namespace olive {
+
+#define super QDialog
+
+MarkerPropertiesDialog::MarkerPropertiesDialog(const QVector &markers, const rational &timebase, QWidget *parent) :
+ super(parent),
+ markers_(markers)
+{
+ QGridLayout *layout = new QGridLayout(this);
+
+ int row = 0;
+
+ QGroupBox *time_group = new QGroupBox(tr("Time"));
+ QGridLayout *time_layout = new QGridLayout(time_group);
+
+ {
+ int time_row = 0;
+
+ time_layout->addWidget(new QLabel(tr("In:")), time_row, 0);
+
+ in_slider_ = new RationalSlider();
+ time_layout->addWidget(in_slider_, time_row, 1);
+
+ time_row++;
+
+ time_layout->addWidget(new QLabel(tr("Out:")), time_row, 0);
+
+ out_slider_ = new RationalSlider();
+ time_layout->addWidget(out_slider_, time_row, 1);
+ }
+
+ if (markers.size() == 1) {
+ in_slider_->SetValue(markers.first()->time_range().in());
+ in_slider_->SetDisplayType(RationalSlider::kTime);
+ in_slider_->SetTimebase(timebase);
+ out_slider_->SetValue(markers.first()->time_range().out());
+ out_slider_->SetDisplayType(RationalSlider::kTime);
+ out_slider_->SetTimebase(timebase);
+ } else {
+ // Markers cannot be on the same time, so we disable setting time if multiple markers are selected
+ in_slider_->setEnabled(false);
+ in_slider_->SetTristate();
+ out_slider_->setEnabled(false);
+ out_slider_->SetTristate();
+ }
+
+ layout->addWidget(time_group, row, 0, 1, 2);
+
+ row++;
+
+ layout->addWidget(new QLabel(tr("Color:")), row, 0);
+
+ color_menu_ = new ColorCodingComboBox();
+ layout->addWidget(color_menu_, row, 1);
+
+ color_menu_->SetColor(markers.first()->color());
+ for (int i=1; icolor() != color_menu_->GetSelectedColor()) {
+ color_menu_->SetColor(-1);
+ break;
+ }
+ }
+
+ row++;
+
+ layout->addWidget(new QLabel(tr("Name:")), row, 0);
+
+ label_edit_ = new LineEditWithFocusSignal();
+ connect(label_edit_, &LineEditWithFocusSignal::Focused, this, [this]{
+ label_edit_->setPlaceholderText(QString());
+ });
+ layout->addWidget(label_edit_, row, 1);
+
+ // Determine what the startup label text should be
+ label_edit_->setText(markers.first()->name());
+ for (int i=1; iname() != label_edit_->text()) {
+ label_edit_->clear();
+ label_edit_->setPlaceholderText(tr("(multiple)"));
+ break;
+ }
+ }
+
+ row++;
+
+ QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ connect(buttons, &QDialogButtonBox::accepted, this, &MarkerPropertiesDialog::accept);
+ connect(buttons, &QDialogButtonBox::rejected, this, &MarkerPropertiesDialog::reject);
+ layout->addWidget(buttons, row, 0, 1, 2);
+}
+
+void MarkerPropertiesDialog::accept()
+{
+ if (in_slider_->isEnabled() && in_slider_->GetValue() > out_slider_->GetValue()) {
+ QMessageBox::critical(this, tr("Invalid Values"), tr("In point must be less than or equal to out point."));
+ return;
+ }
+
+ MultiUndoCommand *command = new MultiUndoCommand();
+
+ int color = color_menu_->GetSelectedColor();
+
+ foreach (TimelineMarker *m, markers_) {
+ if (color != -1) {
+ command->add_child(new MarkerChangeColorCommand(m, color));
+ }
+
+ if (label_edit_->placeholderText().isEmpty()) {
+ command->add_child(new MarkerChangeNameCommand(m, label_edit_->text()));
+ }
+ }
+
+ if (markers_.size() == 1) {
+ command->add_child(new MarkerChangeTimeCommand(markers_.first(), TimeRange(in_slider_->GetValue(), out_slider_->GetValue())));
+ }
+
+ Core::instance()->undo_stack()->pushIfHasChildren(command);
+
+ super::accept();
+}
+
+}
diff --git a/app/dialog/markerproperties/markerpropertiesdialog.h b/app/dialog/markerproperties/markerpropertiesdialog.h
new file mode 100644
index 000000000..218339af1
--- /dev/null
+++ b/app/dialog/markerproperties/markerpropertiesdialog.h
@@ -0,0 +1,78 @@
+/***
+
+ 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 .
+
+***/
+
+#ifndef MARKERPROPERTIESDIALOG_H
+#define MARKERPROPERTIESDIALOG_H
+
+#include
+#include
+
+#include "timeline/timelinemarker.h"
+#include "widget/colorlabelmenu/colorcodingcombobox.h"
+#include "widget/slider/rationalslider.h"
+
+namespace olive {
+
+class LineEditWithFocusSignal : public QLineEdit
+{
+ Q_OBJECT
+public:
+ LineEditWithFocusSignal(QWidget *parent = nullptr) :
+ QLineEdit(parent)
+ {
+ }
+
+protected:
+ virtual void focusInEvent(QFocusEvent *e) override
+ {
+ QLineEdit::focusInEvent(e);
+ emit Focused();
+ }
+
+signals:
+ void Focused();
+
+};
+
+class MarkerPropertiesDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ MarkerPropertiesDialog(const QVector &markers, const rational &timebase, QWidget *parent = nullptr);
+
+public slots:
+ virtual void accept() override;
+
+private:
+ QVector markers_;
+
+ LineEditWithFocusSignal *label_edit_;
+
+ ColorCodingComboBox *color_menu_;
+
+ RationalSlider *in_slider_;
+
+ RationalSlider *out_slider_;
+
+};
+
+}
+
+#endif // MARKERPROPERTIESDIALOG_H
diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp
index f5afb79fa..bb5d7188d 100644
--- a/app/node/block/clip/clip.cpp
+++ b/app/node/block/clip/clip.cpp
@@ -208,10 +208,22 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int
// Find connected viewer node
auto viewers = FindInputNodesConnectedToInput(NodeInput(this, kBufferIn));
- if (viewers.isEmpty()) {
- connected_viewer_ = nullptr;
- } else {
- connected_viewer_ = viewers.first();
+ ViewerOutput *new_connected_viewer = viewers.isEmpty() ? nullptr : viewers.first();
+
+ if (new_connected_viewer != connected_viewer_) {
+ if (connected_viewer_) {
+ disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged);
+ disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged);
+ disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged);
+ }
+
+ connected_viewer_ = new_connected_viewer;
+
+ if (connected_viewer_) {
+ connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged);
+ connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged);
+ connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged);
+ }
}
super::InvalidateCache(adj, from, element, options);
diff --git a/app/node/keyframe.cpp b/app/node/keyframe.cpp
index 30b6e2508..277a1ed61 100644
--- a/app/node/keyframe.cpp
+++ b/app/node/keyframe.cpp
@@ -195,4 +195,10 @@ NodeKeyframe::BezierType NodeKeyframe::get_opposing_bezier_type(NodeKeyframe::Be
}
}
+bool NodeKeyframe::has_sibling_at_time(const rational &t) const
+{
+ NodeKeyframe *k = parent()->GetKeyframeAtTimeOnTrack(input(), t, track(), element());
+ return k && k != this;
+}
+
}
diff --git a/app/node/keyframe.h b/app/node/keyframe.h
index a142c5015..b4c7f08e6 100644
--- a/app/node/keyframe.h
+++ b/app/node/keyframe.h
@@ -167,6 +167,8 @@ public:
next_ = keyframe;
}
+ bool has_sibling_at_time(const rational &t) const;
+
signals:
/**
* @brief Signal emitted when this keyframe's time is changed
diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp
index 9944a0e49..ee458d7d5 100644
--- a/app/node/output/viewer/viewer.cpp
+++ b/app/node/output/viewer/viewer.cpp
@@ -68,6 +68,8 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream
}
SetFlags(kDontShowInParamView);
+
+ timeline_points_ = new TimelinePoints(this);
}
Node *ViewerOutput::copy() const
diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h
index c16b86160..ee207c6ec 100644
--- a/app/node/output/viewer/viewer.h
+++ b/app/node/output/viewer/viewer.h
@@ -137,7 +137,7 @@ public:
TimelinePoints* GetTimelinePoints()
{
- return &timeline_points_;
+ return timeline_points_;
}
QVector GetEnabledStreamsAsReferences() const;
@@ -252,7 +252,7 @@ private:
AudioParams cached_audio_params_;
- TimelinePoints timeline_points_;
+ TimelinePoints *timeline_points_;
bool video_cache_enabled_;
bool audio_cache_enabled_;
diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp
index 31d3ea726..aa80921dd 100644
--- a/app/node/project/serializer/serializer220403.cpp
+++ b/app/node/project/serializer/serializer220403.cpp
@@ -991,6 +991,7 @@ void ProjectSerializer220403::LoadMarkerList(QXmlStreamReader *reader, TimelineM
if (reader->name() == QStringLiteral("marker")) {
QString name;
rational in, out;
+ int color = Config::Current()[QStringLiteral("MarkerColor")].toInt();
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("name")) {
@@ -999,10 +1000,12 @@ void ProjectSerializer220403::LoadMarkerList(QXmlStreamReader *reader, TimelineM
in = rational::fromString(attr.value().toString());
} else if (attr.name() == QStringLiteral("out")) {
out = rational::fromString(attr.value().toString());
+ } else if (attr.name() == QStringLiteral("color")) {
+ color = attr.value().toInt();
}
}
- new TimelineMarker(Config::Current()[QStringLiteral("MarkerColor")].toInt(), TimeRange(in, out), name, markers);
+ new TimelineMarker(color, TimeRange(in, out), name, markers);
}
reader->skipCurrentElement();
@@ -1011,13 +1014,16 @@ void ProjectSerializer220403::LoadMarkerList(QXmlStreamReader *reader, TimelineM
void ProjectSerializer220403::SaveMarkerList(QXmlStreamWriter *writer, TimelineMarkerList *markers) const
{
- foreach (TimelineMarker* marker, markers->list()) {
+ for (auto it=markers->cbegin(); it!=markers->cend(); it++) {
+ TimelineMarker* marker = *it;
+
writer->writeStartElement(QStringLiteral("marker"));
writer->writeAttribute(QStringLiteral("name"), marker->name());
- writer->writeAttribute(QStringLiteral("in"), marker->time().in().toString());
- writer->writeAttribute(QStringLiteral("out"), marker->time().out().toString());
+ writer->writeAttribute(QStringLiteral("in"), marker->time_range().in().toString());
+ writer->writeAttribute(QStringLiteral("out"), marker->time_range().out().toString());
+ writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color()));
writer->writeEndElement(); // marker
}
diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp
index 2251ab3a3..bb40b7790 100644
--- a/app/timeline/timelinemarker.cpp
+++ b/app/timeline/timelinemarker.cpp
@@ -20,18 +20,20 @@
#include "timelinemarker.h"
+#include "common/qtutils.h"
#include "common/xmlutils.h"
#include "config/config.h"
#include "core.h"
+#include "ui/colorcoding.h"
namespace olive {
TimelineMarker::TimelineMarker(int color, const TimeRange &time, const QString &name, QObject *parent) :
- QObject(parent),
time_(time),
name_(name),
color_(color)
{
+ setParent(parent);
}
void TimelineMarker::set_time(const TimeRange &time)
@@ -40,6 +42,17 @@ void TimelineMarker::set_time(const TimeRange &time)
emit TimeChanged(time_);
}
+void TimelineMarker::set_time(const rational &time)
+{
+ set_time(TimeRange(time, time + time_.length()));
+}
+
+bool TimelineMarker::has_sibling_at_time(const rational &t) const
+{
+ TimelineMarker *m = static_cast(parent())->GetMarkerAtTime(t);
+ return m && m != this;
+}
+
void TimelineMarker::set_name(const QString &name)
{
name_ = name;
@@ -49,13 +62,65 @@ void TimelineMarker::set_name(const QString &name)
void TimelineMarker::set_color(int c)
{
color_ = c;
-
emit ColorChanged(color_);
}
-const std::vector &TimelineMarkerList::list() const
+int TimelineMarker::GetMarkerHeight(const QFontMetrics &fm)
{
- return markers_;
+ return fm.height();
+}
+
+QRect TimelineMarker::Draw(QPainter *p, const QPoint &pt, double scale, bool selected)
+{
+ QFontMetrics fm = p->fontMetrics();
+
+ int marker_height = GetMarkerHeight(fm);
+ int marker_width = QtUtils::QFontMetricsWidth(fm, QStringLiteral("H"));
+
+ int half_width = marker_width / 2;
+
+ QColor c = ColorCoding::GetColor(color()).toQColor();
+ if (selected) {
+ p->setPen(Qt::white);
+ p->setBrush(c.lighter());
+ } else {
+ p->setPen(Qt::black);
+ p->setBrush(c);
+ }
+
+ int top = pt.y() - marker_height;
+
+ if (time_.out() != time_.in()) {
+ QRect marker_rect(pt.x(), top, time_.length().toDouble() * scale, marker_height);
+
+ p->drawRect(marker_rect);
+
+ if (!name_.isEmpty()) {
+ p->setPen(ColorCoding::GetUISelectorColor(ColorCoding::GetColor(color_)));
+ p->drawText(marker_rect.adjusted(marker_width/4, 0, 0, 0), name_, Qt::AlignLeft | Qt::AlignVCenter);
+ }
+
+ return marker_rect;
+ } else {
+ int half_marker_height = marker_height / 3;
+ int left = pt.x() - half_width;
+ int right = pt.x() + half_width;
+ int center_y = pt.y() - half_marker_height;
+
+ QPoint points[] = {
+ pt,
+ QPoint(left, center_y),
+ QPoint(left, top),
+ QPoint(right, top),
+ QPoint(right, center_y),
+ pt,
+ };
+
+ p->setRenderHint(QPainter::Antialiasing);
+ p->drawPolygon(points, 6);
+
+ return QRect(left, top, marker_width, marker_height);
+ }
}
void TimelineMarkerList::childEvent(QChildEvent *e)
@@ -64,20 +129,93 @@ void TimelineMarkerList::childEvent(QChildEvent *e)
if (TimelineMarker *marker = dynamic_cast(e->child())) {
if (e->type() == QChildEvent::ChildAdded) {
- markers_.push_back(marker);
+
+ connect(marker, &TimelineMarker::TimeChanged, this, &TimelineMarkerList::HandleMarkerTimeChange);
+ connect(marker, &TimelineMarker::TimeChanged, this, &TimelineMarkerList::HandleMarkerModification);
+ connect(marker, &TimelineMarker::NameChanged, this, &TimelineMarkerList::HandleMarkerModification);
+ connect(marker, &TimelineMarker::ColorChanged, this, &TimelineMarkerList::HandleMarkerModification);
+
+ InsertIntoList(marker);
+
+ emit MarkerAdded(marker);
+
} else if (e->type() == QChildEvent::ChildRemoved) {
- auto it = std::find(markers_.begin(), markers_.end(), marker);
- if (it != markers_.end()) {
- markers_.erase(it);
- }
+
+ RemoveFromList(marker);
+
+ disconnect(marker, &TimelineMarker::TimeChanged, this, &TimelineMarkerList::HandleMarkerTimeChange);
+ disconnect(marker, &TimelineMarker::TimeChanged, this, &TimelineMarkerList::HandleMarkerModification);
+ disconnect(marker, &TimelineMarker::NameChanged, this, &TimelineMarkerList::HandleMarkerModification);
+ disconnect(marker, &TimelineMarker::ColorChanged, this, &TimelineMarkerList::HandleMarkerModification);
+
+ emit MarkerRemoved(marker);
+
}
}
}
-MarkerAddCommand::MarkerAddCommand(TimelineMarkerList *marker_list, const TimeRange &range, const QString &name, int color) :
- marker_list_(marker_list)
+void TimelineMarkerList::InsertIntoList(TimelineMarker *marker)
{
- added_marker_ = new TimelineMarker(color, range, name, &memory_manager_);
+ // Insertion sort by time to allow some loop optimizations
+ bool found = false;
+ for (auto it=markers_.begin(); it!=markers_.end(); it++) {
+ TimelineMarker *m = *it;
+
+ Q_ASSERT(m->time() != marker->time());
+
+ if (m->time() > marker->time()) {
+ markers_.insert(it, marker);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ markers_.push_back(marker);
+ }
+}
+
+bool TimelineMarkerList::RemoveFromList(TimelineMarker *marker)
+{
+ auto it = std::find(markers_.begin(), markers_.end(), marker);
+
+ if (it != markers_.end()) {
+ markers_.erase(it);
+ return true;
+ }
+
+ return false;
+}
+
+void TimelineMarkerList::HandleMarkerModification()
+{
+ emit MarkerModified(static_cast(sender()));
+}
+
+void TimelineMarkerList::HandleMarkerTimeChange()
+{
+ TimelineMarker *m = static_cast(sender());
+
+ auto it = std::find(markers_.begin(), markers_.end(), m);
+
+ if ((it+1 != markers_.end() && (*(it+1))->time() < m->time())
+ || (it != markers_.begin() && (*(it-1))->time() > m->time())) {
+ // Re-sort into list
+ markers_.erase(it);
+ InsertIntoList(m);
+ }
+}
+
+MarkerAddCommand::MarkerAddCommand(TimelineMarkerList *marker_list, const TimeRange &range, const QString &name, int color) :
+ MarkerAddCommand(marker_list, new TimelineMarker(color, range, name, &memory_manager_))
+{
+}
+
+MarkerAddCommand::MarkerAddCommand(TimelineMarkerList *marker_list, TimelineMarker *marker) :
+ marker_list_(marker_list),
+ added_marker_(marker)
+{
+ added_marker_->setParent(&memory_manager_);
}
Project* MarkerAddCommand::GetRelevantProject() const
@@ -102,7 +240,7 @@ MarkerRemoveCommand::MarkerRemoveCommand(TimelineMarker *marker) :
Project* MarkerRemoveCommand::GetRelevantProject() const
{
- return Project::GetProjectFromObject(marker_list_);
+ return Project::GetProjectFromObject(marker_);
}
void MarkerRemoveCommand::redo()
@@ -173,7 +311,7 @@ Project* MarkerChangeTimeCommand::GetRelevantProject() const
void MarkerChangeTimeCommand::redo()
{
- old_time_ = marker_->time();
+ old_time_ = marker_->time_range();
marker_->set_time(new_time_);
}
diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h
index 61822859c..d55af84bf 100644
--- a/app/timeline/timelinemarker.h
+++ b/app/timeline/timelinemarker.h
@@ -21,6 +21,7 @@
#ifndef TIMELINEMARKER_H
#define TIMELINEMARKER_H
+#include
#include
#include
#include
@@ -36,8 +37,12 @@ class TimelineMarker : public QObject
public:
TimelineMarker(int color, const TimeRange& time, const QString& name = QString(), QObject* parent = nullptr);
- const TimeRange &time() const { return time_; }
+ const rational &time() const { return time_.in(); }
+ const TimeRange &time_range() const { return time_; }
void set_time(const TimeRange& time);
+ void set_time(const rational& time);
+
+ bool has_sibling_at_time(const rational &t) const;
const QString& name() const { return name_; }
void set_name(const QString& name);
@@ -45,6 +50,9 @@ public:
int color() const { return color_; }
void set_color(int c);
+ static int GetMarkerHeight(const QFontMetrics &fm);
+ QRect Draw(QPainter *p, const QPoint &pt, double scale, bool selected);
+
signals:
void TimeChanged(const TimeRange& time);
@@ -70,24 +78,79 @@ public:
{
}
- const std::vector &list() const;
+ inline bool empty() const { return markers_.empty(); }
+ inline std::vector::iterator begin() { return markers_.begin(); }
+ inline std::vector::iterator end() { return markers_.end(); }
+ inline std::vector::const_iterator cbegin() const { return markers_.cbegin(); }
+ inline std::vector::const_iterator cend() const { return markers_.cend(); }
+ inline TimelineMarker *back() const { return markers_.back(); }
+ inline TimelineMarker *front() const { return markers_.front(); }
+ inline size_t size() const { return markers_.size(); }
+
+ TimelineMarker *GetMarkerAtTime(const rational &t) const
+ {
+ for (auto it=markers_.cbegin(); it!=markers_.cend(); it++) {
+ TimelineMarker *m = *it;
+ if (m->time() == t) {
+ return m;
+ }
+ }
+
+ return nullptr;
+ }
+
+ TimelineMarker *GetClosestMarkerToTime(const rational &t) const
+ {
+ TimelineMarker *closest = nullptr;
+
+ for (auto it=markers_.cbegin(); it!=markers_.cend(); it++) {
+ TimelineMarker *m = *it;
+
+ rational this_diff = qAbs(m->time() - t);
+
+ if (closest) {
+ rational stored_diff = qAbs(closest->time() - t);
+
+ if (this_diff > stored_diff) {
+ // Since the list is organized by time, if the diff increases, assume we are only going
+ // to move further away from here and there's no need to check
+ break;
+ }
+ }
+
+ closest = m;
+ }
+
+ return closest;
+ }
signals:
void MarkerAdded(TimelineMarker* marker);
void MarkerRemoved(TimelineMarker* marker);
+ void MarkerModified(TimelineMarker* marker);
+
protected:
virtual void childEvent(QChildEvent *e) override;
private:
+ void InsertIntoList(TimelineMarker *m);
+ bool RemoveFromList(TimelineMarker *m);
+
std::vector markers_;
+private slots:
+ void HandleMarkerModification();
+
+ void HandleMarkerTimeChange();
+
};
class MarkerAddCommand : public UndoCommand {
public:
MarkerAddCommand(TimelineMarkerList* marker_list, const TimeRange& range, const QString& name, int color);
+ MarkerAddCommand(TimelineMarkerList* marker_list, TimelineMarker *marker);
virtual Project* GetRelevantProject() const override;
diff --git a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp
index 59d50d0f1..0b06ac1bc 100644
--- a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp
+++ b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp
@@ -50,6 +50,7 @@ void ResizableTimelineScrollBar::ConnectTimelinePoints(TimelinePoints *points)
disconnect(points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&ResizableTimelineScrollBar::update));
disconnect(points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&ResizableTimelineScrollBar::update));
disconnect(points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&ResizableTimelineScrollBar::update));
+ disconnect(points_->markers(), &TimelineMarkerList::MarkerModified, this, static_cast(&ResizableTimelineScrollBar::update));
}
points_ = points;
@@ -59,6 +60,7 @@ void ResizableTimelineScrollBar::ConnectTimelinePoints(TimelinePoints *points)
connect(points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&ResizableTimelineScrollBar::update));
connect(points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&ResizableTimelineScrollBar::update));
connect(points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&ResizableTimelineScrollBar::update));
+ connect(points_->markers(), &TimelineMarkerList::MarkerModified, this, static_cast(&ResizableTimelineScrollBar::update));
}
update();
@@ -77,7 +79,7 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event)
if (points_
&& !timebase().isNull()
- && (points_->workarea()->enabled() || !points_->markers()->list().empty())) {
+ && (points_->workarea()->enabled() || !points_->markers()->empty())) {
QStyleOptionSlider opt;
initStyleOption(&opt);
@@ -110,11 +112,13 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event)
workarea_color);
}
- if (!points_->markers()->list().empty()) {
- foreach (TimelineMarker* marker, points_->markers()->list()) {
+ if (!points_->markers()->empty()) {
+ for (auto it=points_->markers()->cbegin(); it!=points_->markers()->cend(); it++) {
+ TimelineMarker* marker = *it;
+
QColor marker_color = ColorCoding::GetColor(marker->color()).toQColor();
- int64_t in = qRound64(ratio * TimeToScene(marker->time().in()));
- int64_t out = qRound64(ratio * TimeToScene(marker->time().out()));
+ int64_t in = qRound64(ratio * TimeToScene(marker->time_range().in()));
+ int64_t out = qRound64(ratio * TimeToScene(marker->time_range().out()));
int64_t length = qMax(int64_t(1), out-in);
p.fillRect(gr.x() + in,
diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h
index ae5e32230..cc214e8dd 100644
--- a/app/widget/timebased/timebasedviewselectionmanager.h
+++ b/app/widget/timebased/timebasedviewselectionmanager.h
@@ -153,36 +153,54 @@ public:
for (int i=0; itime(), view_->TimeToScene(obj->time())};
+ dragging_[i] = obj->time();
}
drag_mouse_start_ = view_->mapToScene(event->pos());
}
- void DragMove(QMouseEvent *event, const QString &tip_format)
+ void DragMove(QMouseEvent *event, const QString &tip_format = QString())
{
- QPointF diff = view_->mapToScene(event->pos()) - drag_mouse_start_;
+ rational time_diff = view_->SceneToTimeNoGrid(view_->mapToScene(event->pos()).x() - drag_mouse_start_.x());
+ // Validate movement
for (int i=0; iSceneToTimeNoGrid(dragging_.at(i).x + diff.x());
+ rational proposed_time = dragging_.at(i) + time_diff;
T *sel = selected_.at(i);
// Magic number: use interval of 1ms to avoid collisions
rational adj(1, 1000);
- if (dragging_.at(i).time < proposed_time) {
+ if (dragging_.at(i) < proposed_time) {
+ // Negate adjustment value if origin is less than proposed time
adj = -adj;
}
- while (true) {
- NodeKeyframe *key_at_time = sel->parent()->GetKeyframeAtTimeOnTrack(sel->input(), proposed_time, sel->track(), sel->element());
- if (!key_at_time || key_at_time == sel) {
- break;
+ bool loop;
+ do {
+ loop = false;
+ while (sel->has_sibling_at_time(proposed_time)) {
+ proposed_time += adj;
}
- proposed_time += adj;
- }
+ if (proposed_time < 0) {
+ // Prevent any object from going below zero
+ proposed_time = 0;
- sel->set_time(proposed_time);
+ // Setting our proposed time to zero may (re)introduce a conflict that we just avoided
+ // with the sibling check above, so we request it to happen again. To avoid a negative
+ // adj bringing us back below zero, we force adj to positive so it'll only nudge higher
+ adj = qAbs(adj);
+
+ loop = true;
+ }
+ } while (loop);
+
+ time_diff = proposed_time - dragging_.at(i);
+ }
+
+ // Apply movement
+ for (int i=0; iset_time(dragging_.at(i) + time_diff);
}
// Show information about this keyframe
@@ -202,7 +220,7 @@ public:
QToolTip::hideText();
for (int i=0; iadd_child(new SetTimeCommand(selected_.at(i), selected_.at(i)->time(), dragging_.at(i).time));
+ command->add_child(new SetTimeCommand(selected_.at(i), selected_.at(i)->time(), dragging_.at(i)));
}
dragging_.clear();
@@ -271,7 +289,7 @@ private:
virtual Project* GetRelevantProject() const override
{
- return key_->parent()->project();
+ return Project::GetProjectFromObject(key_);
}
protected:
@@ -300,13 +318,7 @@ private:
QVector selected_;
- struct DragObject
- {
- rational time;
- double x;
- };
-
- QVector dragging_;
+ QVector dragging_;
T *initial_drag_item_;
diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp
index b28398b45..81197e616 100644
--- a/app/widget/timebased/timebasedwidget.cpp
+++ b/app/widget/timebased/timebasedwidget.cpp
@@ -26,6 +26,7 @@
#include "common/timecodefunctions.h"
#include "config/config.h"
#include "core.h"
+#include "dialog/markerproperties/markerpropertiesdialog.h"
#include "node/project/sequence/sequence.h"
#include "widget/timelinewidget/undo/timelineundoworkarea.h"
@@ -588,29 +589,36 @@ void TimeBasedWidget::SetMarker()
return;
}
- bool ok;
- QString marker_name;
+ TimelineMarkerList *markers = GetConnectedNode()->GetTimelinePoints()->markers();
- if (Config::Current()[QStringLiteral("SetNameWithMarker")].toBool()) {
- marker_name = QInputDialog::getText(this, tr("Add Marker"), tr("Name:"), QLineEdit::Normal, QString(), &ok);
+ if (TimelineMarker *existing = markers->GetMarkerAtTime(GetTime())) {
+ // We already have a marker here, so pop open the edit dialog
+ MarkerPropertiesDialog mpd({existing}, timebase(), this);
+ mpd.exec();
} else {
- ok = true;
- }
-
- if (ok) {
+ // Create a new marker and place it here
int color;
-
- TimelineMarkerList *markers = GetConnectedNode()->GetTimelinePoints()->markers();
-
- if (!markers->list().empty()) {
- // Use last invoked color if applicable
- color = markers->list().back()->color();
+ if (TimelineMarker *closest = markers->GetClosestMarkerToTime(GetTime())) {
+ // Copy color of closest marker to this time
+ color = closest->color();
} else {
// Fallback to default color in preferences
color = Config::Current()[QStringLiteral("MarkerColor")].toInt();
}
- Core::instance()->undo_stack()->push(new MarkerAddCommand(markers, TimeRange(GetTime(), GetTime()), marker_name, color));
+ TimelineMarker *marker = new TimelineMarker(color, TimeRange(GetTime(), GetTime()));
+
+ if (Config::Current()[QStringLiteral("SetNameWithMarker")].toBool()) {
+ MarkerPropertiesDialog mpd({marker}, timebase(), this);
+ if (mpd.exec() != QDialog::Accepted) {
+ delete marker;
+ marker = nullptr;
+ }
+ }
+
+ if (marker) {
+ Core::instance()->undo_stack()->push(new MarkerAddCommand(markers, marker));
+ }
}
}
diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp
index 0cb52f29a..273aa7446 100644
--- a/app/widget/timelinewidget/timelinewidget.cpp
+++ b/app/widget/timelinewidget/timelinewidget.cpp
@@ -1798,13 +1798,15 @@ bool TimelineWidget::SnapPoint(QVector start_times, rational* movement
}
if ((snap_points & kSnapToMarkers)) {
- foreach (TimelineMarker* m, GetConnectedNode()->GetTimelinePoints()->markers()->list()) {
- qreal marker_pos = TimeToScene(m->time().in());
- potential_snaps.append(AttemptSnap(screen_pt, marker_pos, start_times, m->time().in()));
+ for (auto it=GetConnectedNode()->GetTimelinePoints()->markers()->cbegin(); it!=GetConnectedNode()->GetTimelinePoints()->markers()->cend(); it++) {
+ TimelineMarker* m = *it;
- if (m->time().in() != m->time().out()) {
- marker_pos = TimeToScene(m->time().out());
- potential_snaps.append(AttemptSnap(screen_pt, marker_pos, start_times, m->time().out()));
+ qreal marker_pos = TimeToScene(m->time_range().in());
+ potential_snaps.append(AttemptSnap(screen_pt, marker_pos, start_times, m->time_range().in()));
+
+ if (m->time_range().in() != m->time_range().out()) {
+ marker_pos = TimeToScene(m->time_range().out());
+ potential_snaps.append(AttemptSnap(screen_pt, marker_pos, start_times, m->time_range().out()));
}
}
}
diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp
index d8b8a28c2..c00070b21 100644
--- a/app/widget/timelinewidget/view/timelineview.cpp
+++ b/app/widget/timelinewidget/view/timelineview.cpp
@@ -67,7 +67,7 @@ void TimelineView::mousePressEvent(QMouseEvent *event)
if (rect.contains(mapToScene(event->pos()))) {
TimelinePanel *timeline = PanelManager::instance()->MostRecentlyFocused();
if (timeline) {
- timeline->timeline_widget()->SetTime(clip_marker_positions_.key(rect)->time().in());
+ timeline->timeline_widget()->SetTime(clip_marker_positions_.key(rect)->time_range().in());
}
}
}
@@ -510,58 +510,43 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q
SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base()));
}
- // Draw zebra stripes
- if (clip->connected_viewer() && !clip->connected_viewer()->GetLength().isNull()) {
- if (clip->media_in() < 0) {
- // Draw stripes for sections of clip < 0
- qreal zebra_right = TimeToScene(-clip->media_in());
- if (zebra_right > GetTimelineLeftBound()) {
- DrawZebraStripes(painter, QRectF(block_left, block_top, zebra_right, block_height));
+ // Draw zebra stripes and markers
+ if (clip->connected_viewer()) {
+ if (!clip->connected_viewer()->GetLength().isNull()) {
+ if (clip->media_in() < 0) {
+ // Draw stripes for sections of clip < 0
+ qreal zebra_right = TimeToScene(-clip->media_in());
+ if (zebra_right > GetTimelineLeftBound()) {
+ DrawZebraStripes(painter, QRectF(block_left, block_top, zebra_right, block_height));
+ }
}
- }
- if (clip->length() + clip->media_in() > clip->connected_viewer()->GetLength()) {
- // Draw stripes for sections for clip > clip length
- qreal zebra_left = TimeToScene(clip->out() - (clip->media_in() + clip->length() - clip->connected_viewer()->GetLength()));
- if (zebra_left < GetTimelineRightBound()) {
- DrawZebraStripes(painter, QRectF(zebra_left, block_top, block_right - zebra_left, block_height));
- }
- }
- }
-
- // Draw markers
- qDebug() << "MARKERS ON CLIP STUB";
- /*if (clip->connected_viewer() && !clip->connected_viewer()->GetLength().isNull()) {
- if (clip->connected_viewer()->GetTimelinePoints()->markers()->list().size() > 0) {
- std::vector marker_list = clip->connected_viewer()->GetTimelinePoints()->markers()->list();
-
- int marker_width = QtUtils::QFontMetricsWidth(fm, "H");
- clip_marker_positions_.clear();
-
- // Only draw markers if the block UI is large enough to draw all the markers
- if (marker_list.size() * marker_width < block_right - block_left) {
-
- 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()) {
- // Only draw names that we have room for
- bool draw_name = true;
- if (!marker->name().isEmpty()) {
- int length = fm.horizontalAdvance(marker->name());
- if (iterator.hasNext()) {
- if (TimeToScene(iterator.peekNext()->time().in()) - TimeToScene(marker->time().out()) < (double)length) {
- draw_name = false;
- }
- }
- }
- DrawClipMarker(painter, TimeToScene(clip->in() - clip->media_in() + marker->time().in()),
- block_top + block_height, marker, draw_name);
- }
+ if (clip->length() + clip->media_in() > clip->connected_viewer()->GetLength()) {
+ // Draw stripes for sections for clip > clip length
+ qreal zebra_left = TimeToScene(clip->out() - (clip->media_in() + clip->length() - clip->connected_viewer()->GetLength()));
+ if (zebra_left < GetTimelineRightBound()) {
+ DrawZebraStripes(painter, QRectF(zebra_left, block_top, block_right - zebra_left, block_height));
}
}
}
- }*/
+
+ TimelineMarkerList *marker_list = clip->connected_viewer()->GetTimelinePoints()->markers();
+ if (!marker_list->empty()) {
+
+ clip_marker_positions_.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_range().in() >= clip->media_in() && marker->time_range().out() <= clip->media_in() + clip->length()) {
+ QPoint marker_pt(TimeToScene(clip->in() - clip->media_in() + marker->time_range().in()), block_top + block_height);
+ painter->setClipRect(r);
+ marker->Draw(painter, marker_pt, GetScale(), false);
+ painter->setClipping(false);
+ }
+ }
+ }
+ }
}
// For transitions, show lines representing a transition
@@ -631,46 +616,6 @@ void TimelineView::DrawZebraStripes(QPainter *painter, const QRectF &r)
painter->setClipping(false);
}
-void TimelineView::DrawClipMarker(QPainter* painter, double marker_x, qreal marker_y, TimelineMarker* marker, bool draw_name)
-{
- QFontMetrics fm = fontMetrics();
-
- int marker_height = fm.height();
- int marker_width = QtUtils::QFontMetricsWidth(fm, "H");
-
- int y = marker_y - 1;
-
- int half_width = marker_width / 2;
-
- int x = marker_x + half_width;
-
- painter->setPen(Qt::black);
-
- painter->setBrush(ColorCoding::GetColor(marker->color()).toQColor());
-
- painter->setRenderHint(QPainter::Antialiasing);
-
- int half_marker_height = marker_height / 3;
-
- QPoint points[] = {
- QPoint(x, y),
- QPoint(x - half_width, y - half_marker_height),
- QPoint(x - half_width, y - marker_height),
- QPoint(x + 1 + half_width, y - marker_height),
- QPoint(x + 1 + half_width, y - half_marker_height),
- QPoint(x + 1, y),
- };
-
- painter->drawPolygon(points, 6);
-
- if (!marker->name().isEmpty() && draw_name) {
- painter->drawText(x + marker_width, y - half_marker_height, marker->name());
- }
-
- QPointF scenePos = QPoint(x - half_width, y);
- clip_marker_positions_.insert(marker, (QRectF(scenePos, QSize(marker_width, -marker_height))));
-}
-
int TimelineView::GetHeightOfAllTracks() const
{
if (connected_track_list_) {
diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h
index c22c5caba..ff8368add 100644
--- a/app/widget/timelinewidget/view/timelineview.h
+++ b/app/widget/timelinewidget/view/timelineview.h
@@ -132,8 +132,6 @@ private:
void DrawZebraStripes(QPainter *painter, const QRectF &r);
- void DrawClipMarker(QPainter* painter, double marker_x, qreal marker_y, TimelineMarker* marker, bool draw_name);
-
int GetHeightOfAllTracks() const;
void UpdatePlayheadRect();
diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp
index 467a9f649..fbbe714b9 100644
--- a/app/widget/timeruler/seekablewidget.cpp
+++ b/app/widget/timeruler/seekablewidget.cpp
@@ -20,12 +20,16 @@
#include "seekablewidget.h"
+#include
#include
#include
#include
#include "common/qtutils.h"
#include "core.h"
+#include "dialog/markerproperties/markerpropertiesdialog.h"
+#include "widget/colorlabelmenu/colorlabelmenu.h"
+#include "widget/menu/menushared.h"
#include "widget/timebased/timebasedwidget.h"
namespace olive {
@@ -34,8 +38,10 @@ namespace olive {
SeekableWidget::SeekableWidget(QWidget* parent) :
super(parent),
+ NodeCopyPasteService(QStringLiteral("markers")),
timeline_points_(nullptr),
dragging_(false),
+ ignore_next_focus_out_(false),
selection_manager_(this)
{
QFontMetrics fm = fontMetrics();
@@ -54,19 +60,21 @@ void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points)
if (timeline_points_) {
selection_manager_.ClearSelection();
- disconnect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast(&SeekableWidget::update));
- disconnect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update));
- disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::update));
- disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update));
+ disconnect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, viewport(), static_cast(&QWidget::update));
+ disconnect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, viewport(), static_cast(&QWidget::update));
+ disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, viewport(), static_cast(&QWidget::update));
+ disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, viewport(), static_cast(&QWidget::update));
+ disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerModified, viewport(), static_cast(&QWidget::update));
}
timeline_points_ = points;
if (timeline_points_) {
- connect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast(&SeekableWidget::update));
- connect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update));
- connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::update));
- connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update));
+ connect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, viewport(), static_cast(&QWidget::update));
+ connect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, viewport(), static_cast(&QWidget::update));
+ connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, viewport(), static_cast(&QWidget::update));
+ connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, viewport(), static_cast(&QWidget::update));
+ connect(timeline_points_->markers(), &TimelineMarkerList::MarkerModified, viewport(), static_cast(&QWidget::update));
}
viewport()->update();
@@ -102,7 +110,9 @@ void SeekableWidget::PasteMarkers(bool insert, rational insert_time)
void SeekableWidget::mousePressEvent(QMouseEvent *event)
{
- if (event->button() == Qt::LeftButton) {
+ if (TimelineMarker *initial = selection_manager_.MousePress(event)) {
+ selection_manager_.DragStart(initial, event);
+ } else if (!selection_manager_.GetObjectAtPoint(event->pos()) && event->button() == Qt::LeftButton) {
SeekToScenePoint(mapToScene(event->pos()).x());
dragging_ = true;
@@ -112,14 +122,20 @@ void SeekableWidget::mousePressEvent(QMouseEvent *event)
void SeekableWidget::mouseMoveEvent(QMouseEvent *event)
{
- if (event->buttons() & Qt::LeftButton) {
+ if (selection_manager_.IsDragging()) {
+ selection_manager_.DragMove(event);
+ } else if (dragging_) {
SeekToScenePoint(mapToScene(event->pos()).x());
}
}
void SeekableWidget::mouseReleaseEvent(QMouseEvent *event)
{
- Q_UNUSED(event)
+ if (selection_manager_.IsDragging()) {
+ MultiUndoCommand *command = new MultiUndoCommand();
+ selection_manager_.DragStop(command);
+ Core::instance()->undo_stack()->pushIfHasChildren(command);
+ }
if (GetSnapService()) {
GetSnapService()->HideSnaps();
@@ -128,12 +144,25 @@ void SeekableWidget::mouseReleaseEvent(QMouseEvent *event)
dragging_ = false;
}
+void SeekableWidget::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ super::mouseDoubleClickEvent(event);
+
+ if (selection_manager_.GetObjectAtPoint(event->pos()) && !selection_manager_.GetSelectedObjects().isEmpty()) {
+ ShowMarkerProperties();
+ }
+}
+
void SeekableWidget::focusOutEvent(QFocusEvent *event)
{
super::focusOutEvent(event);
- // Deselect everything when we lose focus
- DeselectAllMarkers();
+ if (ignore_next_focus_out_) {
+ ignore_next_focus_out_ = false;
+ } else {
+ // Deselect everything when we lose focus
+ DeselectAllMarkers();
+ }
}
TimelinePoints *SeekableWidget::timeline_points() const
@@ -159,6 +188,20 @@ void SeekableWidget::SetMarkerColor(int c)
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
+void SeekableWidget::ShowMarkerProperties()
+{
+ MarkerPropertiesDialog mpd(selection_manager_.GetSelectedObjects(), timebase(), this);
+ ignore_next_focus_out_ = true;
+ mpd.exec();
+}
+
+void SeekableWidget::TimebaseChangedEvent(const rational &t)
+{
+ super::TimebaseChangedEvent(t);
+
+ selection_manager_.SetTimebase(t);
+}
+
void SeekableWidget::SeekToScenePoint(qreal scene)
{
if (timebase().isNull()) {
@@ -171,8 +214,8 @@ void SeekableWidget::SeekToScenePoint(qreal scene)
rational movement;
GetSnapService()->SnapPoint({playhead_time},
- &movement,
- SnapService::kSnapAll & ~SnapService::kSnapToPlayhead);
+ &movement,
+ SnapService::kSnapAll & ~SnapService::kSnapToPlayhead);
playhead_time += movement;
}
@@ -184,56 +227,62 @@ void SeekableWidget::SeekToScenePoint(qreal scene)
}
}
+void SeekableWidget::SelectionManagerSelectEvent(void *obj)
+{
+ super::SelectionManagerSelectEvent(obj);
+
+ viewport()->update();
+}
+
+void SeekableWidget::SelectionManagerDeselectEvent(void *obj)
+{
+ super::SelectionManagerDeselectEvent(obj);
+
+ viewport()->update();
+}
+
void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom)
{
if (!timeline_points()) {
return;
}
+ int lim_left = GetScroll();
+ int lim_right = lim_left + width();
+
+ selection_manager_.ClearDrawnObjects();
+
// Draw in/out workarea
if (timeline_points()->workarea()->enabled()) {
- int workarea_left = qMax(0.0, TimeToScene(timeline_points()->workarea()->in()));
+ int workarea_left = qMax(qreal(lim_left), TimeToScene(timeline_points()->workarea()->in()));
int workarea_right;
if (timeline_points()->workarea()->out() == TimelineWorkArea::kResetOut) {
- workarea_right = width();
+ workarea_right = lim_right;
} else {
- workarea_right = qMin(qreal(width()), TimeToScene(timeline_points()->workarea()->out()));
+ workarea_right = qMin(qreal(lim_right), TimeToScene(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().empty()) {
+ if (marker_bottom > 0 && !timeline_points()->markers()->empty()) {
+ for (auto it=timeline_points()->markers()->cbegin(); it!=timeline_points()->markers()->cend(); it++) {
+ TimelineMarker* marker = *it;
- 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 = TimeToScene(marker->time().in());
- int marker_right = TimeToScene(marker->time().out());
-
- if (marker_left >= width() || marker_right < 0) {
+ int marker_right = TimeToScene(marker->time_range().out());
+ if (marker_right < lim_left) {
continue;
}
- if (marker->time().length() != 0) {
- // 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());
- }
+ int marker_left = TimeToScene(marker->time_range().in());
+ if (marker_left >= lim_right) {
+ break;
}
+
+ QRect marker_rect = marker->Draw(p, QPoint(marker_left, marker_bottom), GetScale(), selection_manager_.IsSelected(marker));
+ selection_manager_.DeclareDrawnObject(marker, marker_rect);
}
}
}
@@ -264,4 +313,31 @@ void SeekableWidget::DrawPlayhead(QPainter *p, int x, int y)
p->setRenderHint(QPainter::Antialiasing, false);
}
+bool SeekableWidget::ShowContextMenu(const QPoint &p)
+{
+ if (selection_manager_.GetObjectAtPoint(p) && !selection_manager_.GetSelectedObjects().isEmpty()) {
+ // Show marker-specific menu
+ Menu m;
+
+ ColorLabelMenu color_coding_menu;
+ connect(&color_coding_menu, &ColorLabelMenu::ColorSelected, this, &SeekableWidget::SetMarkerColor);
+ m.addMenu(&color_coding_menu);
+
+ m.addSeparator();
+
+ MenuShared::instance()->AddItemsForEditMenu(&m, false);
+
+ m.addSeparator();
+
+ QAction *properties_action = m.addAction(tr("Properties"));
+ connect(properties_action, &QAction::triggered, this, &SeekableWidget::ShowMarkerProperties);
+
+ ignore_next_focus_out_ = true;
+ m.exec(mapToGlobal(p));
+ return true;
+ } else {
+ return false;
+ }
+}
+
}
diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h
index 56d6f2660..d06401989 100644
--- a/app/widget/timeruler/seekablewidget.h
+++ b/app/widget/timeruler/seekablewidget.h
@@ -25,14 +25,15 @@
#include
#include "common/rational.h"
+#include "node/nodecopypaste.h"
#include "timeline/timelinepoints.h"
+#include "widget/menu/menu.h"
#include "widget/snapservice/snapservice.h"
#include "widget/timebased/timebasedviewselectionmanager.h"
-//#include "widget/marker/markercopypaste.h"
namespace olive {
-class SeekableWidget : public TimeBasedView//, public MarkerCopyPasteService
+class SeekableWidget : public TimeBasedView, public NodeCopyPasteService
{
Q_OBJECT
public:
@@ -60,22 +61,22 @@ public:
void SeekToScenePoint(qreal scene);
+ virtual void SelectionManagerSelectEvent(void *obj) override;
+ virtual void SelectionManagerDeselectEvent(void *obj) override;
+
public slots:
void SetScroll(int i)
{
horizontalScrollBar()->setValue(i);
}
- void SetMarkerColor(int c);
-
- /*void SetMarkerTime();
-
- void SetMarkerName();*/
+ virtual void TimebaseChangedEvent(const rational &) override;
protected:
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent *event) override;
+ virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
virtual void focusOutEvent(QFocusEvent *event) override;
@@ -93,6 +94,9 @@ protected:
return playhead_width_;
}
+protected slots:
+ virtual bool ShowContextMenu(const QPoint &p);
+
private:
TimelinePoints* timeline_points_;
@@ -102,8 +106,15 @@ private:
bool dragging_;
+ bool ignore_next_focus_out_;
+
TimeBasedViewSelectionManager selection_manager_;
+private slots:
+ void SetMarkerColor(int c);
+
+ void ShowMarkerProperties();
+
};
}
diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp
index ff2b3281d..f50e67d36 100644
--- a/app/widget/timeruler/timeruler.cpp
+++ b/app/widget/timeruler/timeruler.cpp
@@ -32,8 +32,10 @@
namespace olive {
+#define super SeekableWidget
+
TimeRuler::TimeRuler(bool text_visible, bool cache_status_visible, QWidget* parent) :
- SeekableWidget(parent),
+ super(parent),
text_visible_(text_visible),
centered_text_(true),
show_cache_status_(cache_status_visible),
@@ -102,18 +104,9 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect)
}
// Draw timeline points if connected
+ int marker_height = TimelineMarker::GetMarkerHeight(p->fontMetrics());
if (timeline_points()) {
- int marker_bottom = height() - text_height();
-
- if (show_cache_status_) {
- marker_bottom -= cache_status_height_;
- }
-
- if (text_visible_) {
- marker_bottom -= cache_status_height_;
- }
-
- DrawTimelinePoints(p, marker_bottom);
+ DrawTimelinePoints(p, marker_height);
}
double width_of_frame = timebase_dbl() * GetScale();
@@ -222,11 +215,11 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect)
int timecode_left;
if (centered_text_) {
- text_rect = QRect(i - kAverageTextWidth/2, 0, kAverageTextWidth, fm.height());
+ text_rect = QRect(i - kAverageTextWidth/2, marker_height, kAverageTextWidth, fm.height());
text_align = Qt::AlignCenter;
timecode_left = i - timecode_width/2;
} else {
- text_rect = QRect(i, 0, kAverageTextWidth, fm.height());
+ text_rect = QRect(i, marker_height, kAverageTextWidth, fm.height());
text_align = Qt::AlignLeft | Qt::AlignVCenter;
timecode_left = i;
@@ -265,13 +258,15 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect)
if (show_cache_status_ && playback_cache_) {
// FIXME: Hardcoded to get video length, if we ever need audio length, this will have to change
rational len = playback_cache_->viewer_parent()->GetVideoLength();
+ int lim_left = GetScroll();
+ int lim_right = lim_left + width();
- int cache_screen_length = qMin(TimeToScene(len), qreal(width()));
+ int cache_screen_length = TimeToScene(len);
if (cache_screen_length > 0) {
int cache_y = height() - cache_status_height_;
- p->fillRect(0, cache_y, cache_screen_length , cache_status_height_, Qt::green);
+ p->fillRect(0, cache_y, cache_screen_length, cache_status_height_, Qt::green);
foreach (const TimeRange& range, playback_cache_->GetInvalidatedRanges(len)) {
int range_left = TimeToScene(range.in());
@@ -284,11 +279,11 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect)
continue;
}
- int adjusted_left = qMax(0, range_left);
+ int adjusted_left = qMax(lim_left, range_left);
p->fillRect(adjusted_left,
cache_y,
- qMin(width(), range_right) - adjusted_left,
+ qMin(lim_right, range_right) - adjusted_left,
cache_status_height_,
Qt::red);
}
@@ -304,6 +299,8 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect)
void TimeRuler::TimebaseChangedEvent(const rational &tb)
{
+ super::TimebaseChangedEvent(tb);
+
timebase_flipped_dbl_ = tb.flipped().toDouble();
update();
@@ -314,14 +311,20 @@ int TimeRuler::CacheStatusHeight() const
return fontMetrics().height() / 4;
}
-void TimeRuler::ShowContextMenu()
+bool TimeRuler::ShowContextMenu(const QPoint &p)
{
- Menu m(this);
+ if (super::ShowContextMenu(p)) {
+ return true;
+ } else {
+ Menu m(this);
- MenuShared::instance()->AddItemsForTimeRulerMenu(&m);
- MenuShared::instance()->AboutToShowTimeRulerActions(timebase());
+ MenuShared::instance()->AddItemsForTimeRulerMenu(&m);
+ MenuShared::instance()->AboutToShowTimeRulerActions(timebase());
- m.exec(QCursor::pos());
+ m.exec(mapToGlobal(p));
+
+ return true;
+ }
}
void TimeRuler::UpdateHeight()
@@ -339,7 +342,7 @@ void TimeRuler::UpdateHeight()
}
// Add marker height
- height += text_height();
+ height += TimelineMarker::GetMarkerHeight(fontMetrics());
setFixedHeight(height);
}
diff --git a/app/widget/timeruler/timeruler.h b/app/widget/timeruler/timeruler.h
index 3a475d398..b6bfc8858 100644
--- a/app/widget/timeruler/timeruler.h
+++ b/app/widget/timeruler/timeruler.h
@@ -45,6 +45,9 @@ protected:
virtual void TimebaseChangedEvent(const rational& tb) override;
+protected slots:
+ virtual bool ShowContextMenu(const QPoint &p) override;
+
private:
void UpdateHeight();
@@ -64,9 +67,6 @@ private:
PlaybackCache* playback_cache_;
-private slots:
- void ShowContextMenu();
-
};
}