diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 047afc779..1a0403568 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -78,19 +78,24 @@ void TimelineMarker::set_active(bool active) emit ActiveChanged(active_); } +void TimelineMarker::Save(QXmlStreamWriter* writer) const +{ + writer->writeStartElement(QStringLiteral("marker")); + + writer->writeAttribute(QStringLiteral("name"), name_); + + writer->writeAttribute(QStringLiteral("color"), QString::number(color_)); + + writer->writeAttribute(QStringLiteral("in"), time_.in().toString()); + writer->writeAttribute(QStringLiteral("out"), time_.out().toString()); + + writer->writeEndElement(); // marker +} + void TimelineMarkerList::Save(QXmlStreamWriter *writer) const { foreach (TimelineMarker* marker, markers_) { - writer->writeStartElement(QStringLiteral("marker")); - - writer->writeAttribute(QStringLiteral("name"), marker->name()); - - writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color())); - - writer->writeAttribute(QStringLiteral("in"), marker->time().in().toString()); - writer->writeAttribute(QStringLiteral("out"), marker->time().out().toString()); - - writer->writeEndElement(); // marker + marker->Save(writer); } } diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index 1c921a311..f63a1f3d3 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -47,6 +47,9 @@ public: bool active(); void set_active(bool active); + void Load(QXmlStreamReader* reader); + void Save(QXmlStreamWriter* writer) const; + signals: void TimeChanged(const TimeRange& time); diff --git a/app/widget/marker/CMakeLists.txt b/app/widget/marker/CMakeLists.txt index 0f49bae01..01ce2157d 100644 --- a/app/widget/marker/CMakeLists.txt +++ b/app/widget/marker/CMakeLists.txt @@ -18,5 +18,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} widget/marker/marker.h widget/marker/marker.cpp + widget/marker/markercopypaste.h + widget/marker/markercopypaste.cpp PARENT_SCOPE ) diff --git a/app/widget/marker/markercopypaste.cpp b/app/widget/marker/markercopypaste.cpp new file mode 100644 index 000000000..c5b48edb0 --- /dev/null +++ b/app/widget/marker/markercopypaste.cpp @@ -0,0 +1,124 @@ +/*** + 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 "markercopypaste.h" + +#include "core.h" +#include "widget/timebased/timebasedwidget.h" + +namespace olive { + +void MarkerCopyPasteService::CopyMarkersToClipboard(const QVector& markers, void* userdata) +{ + QString copy_str; + + QXmlStreamWriter writer(©_str); + writer.setAutoFormatting(true); + + writer.writeStartDocument(); + writer.writeStartElement(QStringLiteral("olive")); + + writer.writeTextElement(QStringLiteral("version"), QString::number(Core::kProjectVersion)); + + writer.writeStartElement(QStringLiteral("markers")); + + // Get earliest marker for offset + rational min_start = RATIONAL_MAX; + foreach (TimelineMarker* marker, markers) { + if (marker->time().in() < min_start) { + min_start = marker->time().in(); + } + } + + foreach(TimelineMarker * marker, markers) { + // Cache marker in/out points + rational in = marker->time().in(); + rational out = marker->time().out(); + + // Temporarily set in/out to be relative to the earliest selected marker + marker->set_time(TimeRange(in - min_start, marker->time().out() - min_start)); + marker->Save(&writer); + + // Reset in/out + marker->set_time(TimeRange(in, out)); + } + + + writer.writeEndElement(); // markers + writer.writeEndElement(); // olive + writer.writeEndDocument(); + + Core::CopyStringToClipboard(copy_str); +} + +void MarkerCopyPasteService::PasteMarkersFromClipboard(TimelineMarkerList* list, MultiUndoCommand* command, + rational offset) { + QString clipboard = Core::PasteStringFromClipboard(); + + if (clipboard.isEmpty()) { + return; + } + + QXmlStreamReader reader(clipboard); + uint data_version = 0; + + QVector pasted_markers; + + while (XMLReadNextStartElement(&reader)) { + if (reader.name() == QStringLiteral("olive")) { + while (XMLReadNextStartElement(&reader)) { + if (reader.name() == QStringLiteral("version")) { + data_version = reader.readElementText().toUInt(); + } else if (reader.name() == QStringLiteral("markers")) { + while (XMLReadNextStartElement(&reader)) { + if (data_version == 0) { + return; + } + QString name; + int color; + rational in, out; + + XMLAttributeLoop((&reader), attr) { + if (attr.name() == QStringLiteral("name")) { + name = attr.value().toString(); + } + + if (attr.name() == QStringLiteral("color")) { + color = attr.value().toInt(); + } + + if (attr.name() == QStringLiteral("in")) { + in = rational::fromString(attr.value().toString()); + } + + if (attr.name() == QStringLiteral("out")) { + out = rational::fromString(attr.value().toString()); + } + } + + command->add_child(new TimeBasedWidget::MarkerAddCommand(Core::instance()->GetActiveProject(), + list, + TimeRange(in + offset, out + offset), + name, + color)); + } + } + } + } + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); +} +} //namespace olive diff --git a/app/widget/marker/markercopypaste.h b/app/widget/marker/markercopypaste.h new file mode 100644 index 000000000..7566ad805 --- /dev/null +++ b/app/widget/marker/markercopypaste.h @@ -0,0 +1,40 @@ +/*** + 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 MARKERCOPYPASTEWIDGET_H +#define MARKERCOPYPASTEWIDGET_H + +#include + +#include "widget/marker/marker.h" +#include "timeline/timelinemarker.h" +#include "undo/undocommand.h" + +namespace olive { + +class MarkerCopyPasteService +{ +public: + MarkerCopyPasteService() = default; + +protected: + void CopyMarkersToClipboard(const QVector &markers, void* userdata = nullptr); + + void PasteMarkersFromClipboard(TimelineMarkerList* list, MultiUndoCommand *command, rational offset); +}; + +} + +#endif // MARKERCOPYPASTEWIDGET_H diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index fcc53161f..28e8a7eab 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -665,11 +665,12 @@ void TimeBasedWidget::DeleteSelected() } } -TimeBasedWidget::MarkerAddCommand::MarkerAddCommand(Project *project, TimelineMarkerList *marker_list, const TimeRange &range, const QString &name) : +TimeBasedWidget::MarkerAddCommand::MarkerAddCommand(Project *project, TimelineMarkerList *marker_list, const TimeRange &range, const QString &name, int color) : project_(project), marker_list_(marker_list), range_(range), - name_(name) + name_(name), + color_(color) { } @@ -680,7 +681,7 @@ Project *TimeBasedWidget::MarkerAddCommand::GetRelevantProject() const void TimeBasedWidget::MarkerAddCommand::redo() { - added_marker_ = marker_list_->AddMarker(range_, name_); + added_marker_ = marker_list_->AddMarker(range_, name_, color_); } void TimeBasedWidget::MarkerAddCommand::undo() diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index ae6db3b56..1e6fb87fe 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -55,6 +55,27 @@ public: virtual bool eventFilter(QObject* object, QEvent* event) override; // Temp file location + + class MarkerAddCommand : public UndoCommand { + public: + MarkerAddCommand(Project* project, TimelineMarkerList* marker_list, const TimeRange& range, const QString& name, int color = -1); + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo() override; + virtual void undo() override; + + private: + Project* project_; + TimelineMarkerList* marker_list_; + TimeRange range_; + QString name_; + int color_; + + TimelineMarker* added_marker_; + }; + class MarkerRemoveCommand : public UndoCommand { public: MarkerRemoveCommand(Project* project, TimelineMarker* marker, TimelineMarkerList* marker_list); @@ -163,26 +184,7 @@ signals: void ConnectedNodeChanged(ViewerOutput* old, ViewerOutput* now); private: - class MarkerAddCommand : public UndoCommand - { - public: - MarkerAddCommand(Project* project, TimelineMarkerList* marker_list, const TimeRange& range, const QString& name); - - virtual Project* GetRelevantProject() const override; - - protected: - virtual void redo() override; - virtual void undo() override; - - private: - Project* project_; - TimelineMarkerList* marker_list_; - TimeRange range_; - QString name_; - - TimelineMarker* added_marker_; - - }; + /** * @brief Set either in or out point to the current playhead diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index be6c68d1c..637203991 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -378,6 +378,15 @@ void TimelineWidget::DeselectAll() SignalDeselectedAllBlocks(); } +bool TimelineWidget::MarkersActive() +{ + if (ruler()->GetActiveTimelineMarkers().size() > 0) { + return true; + } else { + return false; + } +} + void TimelineWidget::RippleToIn() { RippleTo(Timeline::kTrimIn); @@ -475,7 +484,7 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QVector &blocks, void TimelineWidget::DeleteSelected(bool ripple) { - if (ruler()->GetActiveTimelineMarkers().size() > 0) { + if (MarkersActive()) { ruler()->DeleteSelected(); return; } @@ -598,6 +607,12 @@ void TimelineWidget::CopySelected(bool cut) return; } + if (MarkersActive()) { + ruler()->CopySelected(cut); + return; + } + + QVector selected = GetSelectedBlocks(); if (selected.isEmpty()) { @@ -631,6 +646,11 @@ void TimelineWidget::Paste(bool insert) return; } + if (ruler()->underMouse()) { + ruler()->PasteMarkers(insert, GetTime()); + return; + } + MultiUndoCommand* command = new MultiUndoCommand(); QVector paste_data; diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 6082e2e51..080aa9fd5 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -59,6 +59,8 @@ public: void DeselectAll(); + bool MarkersActive(); + void RippleToIn(); void RippleToOut(); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 0764ee80b..90fb36c79 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -106,6 +106,21 @@ 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) { diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 05d9a81bb..7438245a3 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -28,10 +28,11 @@ #include "widget/snapservice/snapservice.h" #include "widget/timebased/timescaledobject.h" #include "widget/marker/marker.h" +#include "widget/marker/markercopypaste.h" namespace olive { -class SeekableWidget : public TimelineScaledWidget +class SeekableWidget : public TimelineScaledWidget, public MarkerCopyPasteService { Q_OBJECT public: @@ -55,6 +56,10 @@ public: void DeleteSelected(); + void CopySelected(bool cut); + + void PasteMarkers(bool insert, rational insert_time); + QVector GetActiveTimelineMarkers(); void DeselectAllMarkers();