From fac423880b5682f9fa36c28c2fbb78ef9a811f77 Mon Sep 17 00:00:00 2001
From: Elliu <elliu@hashi.re>
Date: Wed, 2 Feb 2022 21:49:38 +0100
Subject: [PATCH] Working drag-and-drop timing bars on audio view

Except for the starting line bar
---
 src/Lib/Ass/Line.cc                           |   6 +
 src/Lib/Ass/Line.hh                           |   2 +
 src/Lib/Ass/Syl.cc                            |   6 +
 src/Lib/Ass/Syl.hh                            |   2 +
 .../AudioVisualizer/TimingLine.cc             | 124 +++++++++++++++---
 .../AudioVisualizer/TimingLine.hh             |  12 ++
 .../AudioVisualizer/TimingSeparator.cc        |  26 +++-
 .../AudioVisualizer/TimingSeparator.hh        |  14 +-
 .../AudioVisualizer/TimingSyl.cc              |   7 +
 .../AudioVisualizer/TimingSyl.hh              |   3 +
 10 files changed, 184 insertions(+), 18 deletions(-)

diff --git a/src/Lib/Ass/Line.cc b/src/Lib/Ass/Line.cc
index 71eafe81..27a2c35e 100644
--- a/src/Lib/Ass/Line.cc
+++ b/src/Lib/Ass/Line.cc
@@ -129,6 +129,12 @@ Line::getContent() const noexcept
     return content;
 }
 
+QVector<Syl> *
+Line::getContentPtr() noexcept
+{
+    return &content;
+}
+
 bool
 Line::getIsComment() const noexcept
 {
diff --git a/src/Lib/Ass/Line.hh b/src/Lib/Ass/Line.hh
index f24b2e9e..a5fffb0d 100644
--- a/src/Lib/Ass/Line.hh
+++ b/src/Lib/Ass/Line.hh
@@ -44,6 +44,8 @@ public:
     StyleWeakPtr getStyle() const noexcept;
     const QVector<Syl> &getContent() const noexcept;
 
+    QVector<Syl> *getContentPtr() noexcept; // FIXME: remove me
+
     quint64 getStart() const noexcept;
     quint64 getEnd() const noexcept;
     QString getContentAsText() const noexcept;
diff --git a/src/Lib/Ass/Syl.cc b/src/Lib/Ass/Syl.cc
index 2c5a3d21..91c49b84 100644
--- a/src/Lib/Ass/Syl.cc
+++ b/src/Lib/Ass/Syl.cc
@@ -41,3 +41,9 @@ Syl::getDuration() const noexcept
 {
     return duration;
 }
+
+void
+Syl::setDuration(quint64 x) noexcept
+{
+    duration = x;
+}
diff --git a/src/Lib/Ass/Syl.hh b/src/Lib/Ass/Syl.hh
index e112eef3..7c603cea 100644
--- a/src/Lib/Ass/Syl.hh
+++ b/src/Lib/Ass/Syl.hh
@@ -31,6 +31,8 @@ public:
     QString getContent() const noexcept;
     quint64 getDuration() const noexcept;
 
+    void setDuration(quint64) noexcept;
+
 private:
     static quint64 getDurationFromString(const QString &) noexcept;
 };
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingLine.cc b/src/UI/DocumentViews/AudioVisualizer/TimingLine.cc
index e4316254..26f920c1 100644
--- a/src/UI/DocumentViews/AudioVisualizer/TimingLine.cc
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingLine.cc
@@ -4,8 +4,6 @@
 #include <QGraphicsScene>
 
 #include "TimingUtils.hh"
-#include "TimingSyl.hh"
-#include "TimingSeparator.hh"
 
 using namespace Vivy;
 
@@ -14,27 +12,42 @@ TimingLine::TimingLine(Ass::Line *lineptr, QGraphicsItem *parent)
     , line(*lineptr)
 {
     setPos(TimingUtils::posFromMs(int(line.getStart()) * 10), TimingUtils::axisHeight());
-    int currentTime        = 0;
+    originalX       = pos().x();
+    originalY       = pos().y();
+    int currentTime = 0;
+    int endSyl      = 0;
+    int i;
+
+    TimingSeparator *timingSeparatorStart =
+        new TimingSeparator(currentTime, 0, TimingSeparator::SeparatorStyle::Start, this);
+    seps.append(timingSeparatorStart);
+    connect(timingSeparatorStart, &TimingSeparator::positionChanged, this,
+            &TimingLine::timingSeparatorHasChanged);
+
     QVector<Ass::Syl> syls = line.getContent();
-    for (int i = 0; i < syls.size(); ++i) {
-        int endSyl = currentTime + 10 * int(syls.at(i).getDuration());
+    for (i = 0; i < syls.size(); ++i) {
+        endSyl = currentTime + 10 * int(syls.at(i).getDuration());
 
         TimingSyl *timingSyl = new TimingSyl(syls.at(i), currentTime, this);
+        timingSyls.append(timingSyl);
 
         // TODO: Here create the TimingSeparator and connect
-        TimingSeparator *timingSeparatorStart =
-            new TimingSeparator(currentTime,
-                                i != 0 ? TimingSeparator::SeparatorStyle::Middle
-                                       : TimingSeparator::SeparatorStyle::Start,
-                                this);
-        TimingSeparator *timingSeparatorEnd =
-            new TimingSeparator(i != syls.size() - 1 ? endSyl : int(10 * line.getDuration()),
-                                i != syls.size() - 1 ? TimingSeparator::SeparatorStyle::Middle
-                                                     : TimingSeparator::SeparatorStyle::End,
-                                this);
+        if (i != 0) {
+            TimingSeparator *timingSeparator =
+                new TimingSeparator(currentTime, i, TimingSeparator::SeparatorStyle::Middle, this);
+            seps.append(timingSeparator);
+            connect(timingSeparator, &TimingSeparator::positionChanged, this,
+                    &TimingLine::timingSeparatorHasChanged);
+        }
 
         currentTime = endSyl;
     }
+
+    TimingSeparator *timingSeparatorEnd =
+        new TimingSeparator(currentTime, i, TimingSeparator::SeparatorStyle::End, this);
+    seps.append(timingSeparatorEnd);
+    connect(timingSeparatorEnd, &TimingSeparator::positionChanged, this,
+            &TimingLine::timingSeparatorHasChanged);
 }
 
 QRectF
@@ -52,3 +65,84 @@ TimingLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWi
                              TimingUtils::audioHeight()),
                       QColor(0, 255, 255, 50));
 }
+
+void
+TimingLine::timingSeparatorHasChanged(int sylIndex, qreal x)
+{
+}
+
+qreal
+TimingLine::requestMove(int sepIndex, qreal x)
+{
+    QRectF sceneRect        = mapRectFromScene(scene()->sceneRect());
+    QVector<Ass::Syl> *syls = line.getContentPtr();
+
+    qreal ret = x;
+    qreal mini, maxi;
+
+    if (sepIndex <= 0) {
+        prepareGeometryChange();
+
+        mini = sceneRect.left();
+        maxi = sepIndex < syls->size() - 1 ? timingSyls[sepIndex + 1]->pos().x()
+                                           : TimingUtils::posFromMs(int(line.getDuration()) * 10);
+
+        if (ret < mini)
+            ret = mini;
+        if (ret > maxi)
+            ret = maxi;
+
+        quint64 dur1 = quint64(TimingUtils::msFromPos(int(seps[1]->pos().x() - ret)) / 10);
+        (*syls)[0].setDuration(dur1);
+        timingSyls[0]->setLen(dur1);
+        setPos(originalX + ret, originalY);
+        line.setStart(line.getStart() +
+                      quint64(TimingUtils::msFromPos(int(originalX) + int(ret) / 10)));
+    }
+
+    else if (sepIndex >= syls->size()) {
+        prepareGeometryChange();
+
+        mini = timingSyls[sepIndex - 1]->pos().x();
+        maxi = sceneRect.right();
+
+        if (ret < mini)
+            ret = mini;
+        if (ret > maxi)
+            ret = maxi;
+
+        quint64 dur2 =
+            quint64(TimingUtils::msFromPos(int(ret - timingSyls[sepIndex - 1]->pos().x())) / 10);
+        (*syls)[sepIndex - 1].setDuration(dur2);
+        timingSyls[sepIndex - 1]->setLen(dur2);
+        line.setEnd(line.getStart() + quint64(TimingUtils::msFromPos(int(ret)) / 10));
+    }
+
+    else {
+        mini = timingSyls[sepIndex - 1]->pos().x();
+        maxi = sepIndex < syls->size() - 1 ? timingSyls[sepIndex + 1]->pos().x()
+                                           : TimingUtils::posFromMs(int(line.getDuration() * 10));
+
+        if (ret < mini)
+            ret = mini;
+        if (ret > maxi)
+            ret = maxi;
+
+        quint64 sumDur = (*syls)[sepIndex].getDuration() + (*syls)[sepIndex - 1].getDuration();
+        quint64 dur1   = quint64(
+              TimingUtils::msFromPos(int(ret) - int(timingSyls[sepIndex - 1]->pos().x())) / 10);
+        if (dur1 > sumDur) {
+            dur1 = sumDur;
+        }
+        quint64 dur2 = sumDur - dur1;
+
+        (*syls)[sepIndex - 1].setDuration(dur1);
+        (*syls)[sepIndex].setDuration(dur2);
+
+        timingSyls[sepIndex - 1]->setLen(dur1);
+        timingSyls[sepIndex]->setPos(ret, 0);
+        timingSyls[sepIndex]->setLen(dur2);
+    }
+
+    return ret;
+}
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingLine.hh b/src/UI/DocumentViews/AudioVisualizer/TimingLine.hh
index ea7c7f86..a6116316 100644
--- a/src/UI/DocumentViews/AudioVisualizer/TimingLine.hh
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingLine.hh
@@ -3,6 +3,8 @@
 #include <QGraphicsObject>
 
 #include "../../../Lib/Ass/Ass.hh"
+#include "TimingSeparator.hh"
+#include "TimingSyl.hh"
 
 namespace Vivy
 {
@@ -16,6 +18,16 @@ public:
 
 private:
     Ass::Line line;
+    QVector<TimingSeparator *> seps;
+    QVector<TimingSyl *> timingSyls;
+    int originalX;
+    int originalY;
+
+public:
+    qreal requestMove(int, qreal);
+
+public slots:
+    void timingSeparatorHasChanged(int, qreal);
 };
 
 }
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingSeparator.cc b/src/UI/DocumentViews/AudioVisualizer/TimingSeparator.cc
index e8811d4d..cc0ac33f 100644
--- a/src/UI/DocumentViews/AudioVisualizer/TimingSeparator.cc
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingSeparator.cc
@@ -2,17 +2,25 @@
 
 #include <QPainter>
 #include <QGraphicsScene>
+#include <QDrag>
 
 #include "TimingUtils.hh"
+#include "TimingLine.hh"
 
 using namespace Vivy;
 
-TimingSeparator::TimingSeparator(int time, SeparatorStyle style_, QGraphicsItem *parent)
+TimingSeparator::TimingSeparator(int time, int index, SeparatorStyle style_, TimingLine *parent)
     : QGraphicsObject(parent)
     , style(style_)
+    , sepIndex(index)
+    , parentTimingLine(parent)
 {
     setPos(TimingUtils::posFromMs(time), 0);
 
+    setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges);
+    setAcceptHoverEvents(true);
+    setCursor(Qt::PointingHandCursor);
+
     switch (style) {
     case SeparatorStyle::Start: pen = QPen(QColor(0, 0, 255)); break;
     case SeparatorStyle::Middle: pen = QPen(QColor(180, 0, 180)); break;
@@ -54,3 +62,19 @@ TimingSeparator::paint(QPainter *painter, const QStyleOptionGraphicsItem *option
         break;
     }
 }
+
+QVariant
+TimingSeparator::itemChange(GraphicsItemChange change, const QVariant &value) noexcept
+{
+    if (change == ItemPositionChange) {
+        qreal destination = value.toPointF().x();
+        destination       = parentTimingLine->requestMove(sepIndex, destination);
+
+        if (style == SeparatorStyle::Start)
+            destination = 0;
+
+        emit positionChanged(sepIndex, destination);
+        return QPointF(destination, 0);
+    }
+    return QGraphicsObject::itemChange(change, value);
+}
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingSeparator.hh b/src/UI/DocumentViews/AudioVisualizer/TimingSeparator.hh
index 61fdd95b..4d90df97 100644
--- a/src/UI/DocumentViews/AudioVisualizer/TimingSeparator.hh
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingSeparator.hh
@@ -1,17 +1,19 @@
 #pragma once
 
 #include <QGraphicsObject>
+#include <QGraphicsSceneDragDropEvent>
 
 namespace Vivy
 {
+class TimingLine;
+
 class TimingSeparator final : public QGraphicsObject {
     Q_OBJECT
 public:
     enum class SeparatorStyle { Start, Middle, End };
 
 public:
-    explicit TimingSeparator(int, SeparatorStyle style = SeparatorStyle::Middle,
-                             QGraphicsItem *parent = nullptr);
+    explicit TimingSeparator(int, int, SeparatorStyle, TimingLine *);
 
     QRectF boundingRect() const override;
     void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
@@ -20,6 +22,14 @@ private:
     SeparatorStyle style;
     int widthPaw = 5;
     QPen pen;
+    int sepIndex;
+    TimingLine *parentTimingLine;
+
+signals:
+    void positionChanged(int, qreal);
+
+protected:
+    QVariant itemChange(GraphicsItemChange change, const QVariant &value) noexcept override;
 };
 
 }
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingSyl.cc b/src/UI/DocumentViews/AudioVisualizer/TimingSyl.cc
index 548324fa..291804cb 100644
--- a/src/UI/DocumentViews/AudioVisualizer/TimingSyl.cc
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingSyl.cc
@@ -26,3 +26,10 @@ TimingSyl::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWid
 {
     painter->drawText(boundingRect(), Qt::AlignCenter, syl.getContent());
 }
+
+void
+TimingSyl::setLen(quint64 len)
+{
+    prepareGeometryChange();
+    syl.setDuration(len);
+}
diff --git a/src/UI/DocumentViews/AudioVisualizer/TimingSyl.hh b/src/UI/DocumentViews/AudioVisualizer/TimingSyl.hh
index fcf74aea..26a46ae5 100644
--- a/src/UI/DocumentViews/AudioVisualizer/TimingSyl.hh
+++ b/src/UI/DocumentViews/AudioVisualizer/TimingSyl.hh
@@ -16,6 +16,9 @@ public:
 
 private:
     Ass::Syl syl;
+
+public:
+    void setLen(quint64 len);
 };
 
 }
-- 
GitLab