]> git.sesse.net Git - nageru/blobdiff - clip_list.cpp
Allow symlinked frame files. Useful for testing.
[nageru] / clip_list.cpp
index 62434a6fb4d0fcca4ca86d404c8dc89cd60e7d48..14f083e91268984c043ac652cd74ff19b39cccc9 100644 (file)
@@ -1,18 +1,18 @@
+#include "clip_list.h"
+
 #include "mainwindow.h"
+#include "timebase.h"
+#include "ui_mainwindow.h"
 
 #include <math.h>
 #include <string>
 #include <vector>
 
-#include "clip_list.h"
-#include "ui_mainwindow.h"
-
 using namespace std;
 
 string pts_to_string(int64_t pts)
 {
-       // FIXME: This depends on a fixed timebase.
-       int64_t t = lrint((pts / 12800.0) * 1e3);  // In milliseconds.
+       int64_t t = lrint((pts / double(TIMEBASE)) * 1e3);  // In milliseconds.
        int ms = t % 1000;
        t /= 1000;
        int sec = t % 60;
@@ -28,8 +28,7 @@ string pts_to_string(int64_t pts)
 
 string duration_to_string(int64_t pts_diff)
 {
-       // FIXME: This depends on a fixed timebase.
-       int64_t t = lrint((pts_diff / 12800.0) * 1e3);  // In milliseconds.
+       int64_t t = lrint((pts_diff / double(TIMEBASE)) * 1e3);  // In milliseconds.
        int ms = t % 1000;
        t /= 1000;
        int sec = t % 60;
@@ -41,21 +40,36 @@ string duration_to_string(int64_t pts_diff)
        return buf;
 }
 
-int ClipList::rowCount(const QModelIndex &parent) const {
-       if (parent.isValid()) return 0;
+int ClipList::rowCount(const QModelIndex &parent) const
+{
+       if (parent.isValid())
+               return 0;
        return clips.size();
 }
 
-int ClipList::columnCount(const QModelIndex &parent) const {
-       if (parent.isValid()) return 0;
-       if (display_type == ListDisplay::CLIP_LIST) {
-               return int(ClipListColumn::NUM_COLUMNS);
-       } else {
-               return int(PlayListColumn::NUM_COLUMNS);
-       }
+int PlayList::rowCount(const QModelIndex &parent) const
+{
+       if (parent.isValid())
+               return 0;
+       return clips.size();
+}
+
+int ClipList::columnCount(const QModelIndex &parent) const
+{
+       if (parent.isValid())
+               return 0;
+       return int(Column::NUM_COLUMNS);
+}
+
+int PlayList::columnCount(const QModelIndex &parent) const
+{
+       if (parent.isValid())
+               return 0;
+       return int(Column::NUM_COLUMNS);
 }
 
-QVariant ClipList::data(const QModelIndex &parent, int role) const {
+QVariant ClipList::data(const QModelIndex &parent, int role) const
+{
        if (!parent.isValid())
                return QVariant();
        const int row = parent.row(), column = parent.column();
@@ -63,121 +77,282 @@ QVariant ClipList::data(const QModelIndex &parent, int role) const {
                return QVariant();
 
        if (role == Qt::TextAlignmentRole) {
-               if (display_type == ListDisplay::CLIP_LIST) {
-                       switch (ClipListColumn(column)) {
-                       case ClipListColumn::IN:
-                       case ClipListColumn::OUT:
-                       case ClipListColumn::DURATION:
-                               return Qt::AlignRight + Qt::AlignVCenter;
-                       default:
-                               return Qt::AlignLeft + Qt::AlignVCenter;
-                       }
+               switch (Column(column)) {
+               case Column::IN:
+               case Column::OUT:
+               case Column::DURATION:
+                       return Qt::AlignRight + Qt::AlignVCenter;
+               default:
+                       return Qt::AlignLeft + Qt::AlignVCenter;
+               }
+       }
+
+       if (role != Qt::DisplayRole && role != Qt::EditRole)
+               return QVariant();
+
+       switch (Column(column)) {
+       case Column::IN:
+               return QString::fromStdString(pts_to_string(clips[row].pts_in));
+       case Column::OUT:
+               if (clips[row].pts_out >= 0) {
+                       return QString::fromStdString(pts_to_string(clips[row].pts_out));
                } else {
-                       switch (PlayListColumn(column)) {
-                       case PlayListColumn::PLAYING:
-                               return Qt::AlignCenter;
-                       case PlayListColumn::IN:
-                       case PlayListColumn::OUT:
-                       case PlayListColumn::DURATION:
-                               return Qt::AlignRight + Qt::AlignVCenter;
-                       case PlayListColumn::CAMERA:
-                               return Qt::AlignCenter;
-                       default:
-                               return Qt::AlignLeft + Qt::AlignVCenter;
-                       }
+                       return QVariant();
+               }
+       case Column::DURATION:
+               if (clips[row].pts_out >= 0) {
+                       return QString::fromStdString(duration_to_string(clips[row].pts_out - clips[row].pts_in));
+               } else {
+                       return QVariant();
                }
+       case Column::CAMERA_1:
+       case Column::CAMERA_2:
+       case Column::CAMERA_3:
+       case Column::CAMERA_4: {
+               unsigned stream_idx = column - int(Column::CAMERA_1);
+               return QString::fromStdString(clips[row].descriptions[stream_idx]);
        }
+       default:
+               return "";
+       }
+}
 
-       if (role != Qt::DisplayRole)
+QVariant PlayList::data(const QModelIndex &parent, int role) const
+{
+       if (!parent.isValid())
+               return QVariant();
+       const int row = parent.row(), column = parent.column();
+       if (size_t(row) >= clips.size())
                return QVariant();
 
-       if (display_type == ListDisplay::CLIP_LIST) {
-               switch (ClipListColumn(column)) {
-               case ClipListColumn::IN:
-                       return QString::fromStdString(pts_to_string(clips[row].pts_in));
-               case ClipListColumn::OUT:
-                       if (clips[row].pts_out >= 0) {
-                               return QString::fromStdString(pts_to_string(clips[row].pts_out));
-                       } else {
-                               return QVariant();
-                       }
-               case ClipListColumn::DURATION:
-                       if (clips[row].pts_out >= 0) {
-                               return QString::fromStdString(duration_to_string(clips[row].pts_out - clips[row].pts_in));
-                       } else {
-                               return QVariant();
-                       }
+       if (role == Qt::TextAlignmentRole) {
+               switch (Column(column)) {
+               case Column::PLAYING:
+                       return Qt::AlignCenter;
+               case Column::IN:
+               case Column::OUT:
+               case Column::DURATION:
+               case Column::FADE_TIME:
+                       return Qt::AlignRight + Qt::AlignVCenter;
+               case Column::CAMERA:
+                       return Qt::AlignCenter;
                default:
-                       return "";
+                       return Qt::AlignLeft + Qt::AlignVCenter;
                }
-       } else {
-               switch (PlayListColumn(column)) {
-               case PlayListColumn::PLAYING:
-                       return (row == currently_playing_index) ? "→" : "";
-               case PlayListColumn::IN:
-                       return QString::fromStdString(pts_to_string(clips[row].pts_in));
-               case PlayListColumn::OUT:
-                       if (clips[row].pts_out >= 0) {
-                               return QString::fromStdString(pts_to_string(clips[row].pts_out));
-                       } else {
-                               return QVariant();
-                       }
-               case PlayListColumn::DURATION:
-                       if (clips[row].pts_out >= 0) {
-                               return QString::fromStdString(duration_to_string(clips[row].pts_out - clips[row].pts_in));
+       }
+       if (role == Qt::BackgroundRole) {
+               if (Column(column) == Column::PLAYING) {
+                       auto it = current_progress.find(row);
+                       if (it != current_progress.end()) {
+                               double play_progress = it->second;
+
+                               // This only really works well for the first column, for whatever odd Qt reason.
+                               QLinearGradient grad(QPointF(0, 0), QPointF(1, 0));
+                               grad.setCoordinateMode(grad.QGradient::ObjectBoundingMode);
+                               grad.setColorAt(0.0f, QColor::fromRgbF(0.0f, 0.0f, 1.0f, 0.2f));
+                               grad.setColorAt(play_progress, QColor::fromRgbF(0.0f, 0.0f, 1.0f, 0.2f));
+                               if (play_progress + 0.01f <= 1.0f) {
+                                       grad.setColorAt(play_progress + 0.01f, QColor::fromRgbF(0.0f, 0.0f, 1.0f, 0.0f));
+                               }
+                               return QBrush(grad);
                        } else {
                                return QVariant();
                        }
-               case PlayListColumn::CAMERA:
-                       return qlonglong(clips[row].stream_idx + 1);
-               default:
-                       return "";
+               } else {
+                       return QVariant();
+               }
+       }
+
+       if (role != Qt::DisplayRole && role != Qt::EditRole)
+               return QVariant();
+
+       switch (Column(column)) {
+       case Column::PLAYING:
+               return current_progress.count(row) ? "→" : "";
+       case Column::IN:
+               return QString::fromStdString(pts_to_string(clips[row].pts_in));
+       case Column::OUT:
+               if (clips[row].pts_out >= 0) {
+                       return QString::fromStdString(pts_to_string(clips[row].pts_out));
+               } else {
+                       return QVariant();
                }
+       case Column::DURATION:
+               if (clips[row].pts_out >= 0) {
+                       return QString::fromStdString(duration_to_string(clips[row].pts_out - clips[row].pts_in));
+               } else {
+                       return QVariant();
+               }
+       case Column::CAMERA:
+               return qlonglong(clips[row].stream_idx + 1);
+       case Column::DESCRIPTION:
+               return QString::fromStdString(clips[row].descriptions[clips[row].stream_idx]);
+       case Column::FADE_TIME: {
+               stringstream ss;
+               ss.imbue(locale("C"));
+               ss.precision(3);
+               ss << fixed << clips[row].fade_time_seconds;
+               return QString::fromStdString(ss.str());
+       }
+       default:
+               return "";
        }
 }
 
-QVariant ClipList::headerData(int section, Qt::Orientation orientation, int role) const {
+QVariant ClipList::headerData(int section, Qt::Orientation orientation, int role) const
+{
        if (role != Qt::DisplayRole)
                return QVariant();
        if (orientation != Qt::Horizontal)
                return QVariant();
 
-       if (display_type == ListDisplay::CLIP_LIST) {
-               switch (ClipListColumn(section)) {
-               case ClipListColumn::IN:
-                       return "In";
-               case ClipListColumn::OUT:
-                       return "Out";
-               case ClipListColumn::DURATION:
-                       return "Duration";
-               case ClipListColumn::CAMERA_1:
-                       return "Camera 1";
-               case ClipListColumn::CAMERA_2:
-                       return "Camera 2";
-               case ClipListColumn::CAMERA_3:
-                       return "Camera 3";
-               case ClipListColumn::CAMERA_4:
-                       return "Camera 4";
-               default:
-                       return "";
+       switch (Column(section)) {
+       case Column::IN:
+               return "In";
+       case Column::OUT:
+               return "Out";
+       case Column::DURATION:
+               return "Duration";
+       case Column::CAMERA_1:
+               return "Camera 1";
+       case Column::CAMERA_2:
+               return "Camera 2";
+       case Column::CAMERA_3:
+               return "Camera 3";
+       case Column::CAMERA_4:
+               return "Camera 4";
+       default:
+               return "";
+       }
+}
+
+QVariant PlayList::headerData(int section, Qt::Orientation orientation, int role) const
+{
+       if (role != Qt::DisplayRole)
+               return QVariant();
+       if (orientation != Qt::Horizontal)
+               return QVariant();
+
+       switch (Column(section)) {
+       case Column::PLAYING:
+               return "";
+       case Column::IN:
+               return "In";
+       case Column::OUT:
+               return "Out";
+       case Column::DURATION:
+               return "Duration";
+       case Column::CAMERA:
+               return "Camera";
+       case Column::DESCRIPTION:
+               return "Description";
+       case Column::FADE_TIME:
+               return "Fade time";
+       default:
+               return "";
+       }
+}
+
+Qt::ItemFlags ClipList::flags(const QModelIndex &index) const
+{
+       if (!index.isValid())
+               return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+       const int row = index.row(), column = index.column();
+       if (size_t(row) >= clips.size())
+               return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+
+       switch (Column(column)) {
+       case Column::CAMERA_1:
+       case Column::CAMERA_2:
+       case Column::CAMERA_3:
+       case Column::CAMERA_4:
+               return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
+       default:
+               return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+       }
+}
+
+Qt::ItemFlags PlayList::flags(const QModelIndex &index) const
+{
+       if (!index.isValid())
+               return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+       const int row = index.row(), column = index.column();
+       if (size_t(row) >= clips.size())
+               return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+
+       switch (Column(column)) {
+       case Column::DESCRIPTION:
+       case Column::CAMERA:
+       case Column::FADE_TIME:
+               return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
+               return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
+       default:
+               return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+       }
+}
+
+bool ClipList::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+       if (!index.isValid() || role != Qt::EditRole) {
+               return false;
+       }
+
+       const int row = index.row(), column = index.column();
+       if (size_t(row) >= clips.size())
+               return false;
+
+       switch (Column(column)) {
+       case Column::CAMERA_1:
+       case Column::CAMERA_2:
+       case Column::CAMERA_3:
+       case Column::CAMERA_4: {
+               unsigned stream_idx = column - int(Column::CAMERA_1);
+               clips[row].descriptions[stream_idx] = value.toString().toStdString();
+               emit_data_changed(row);
+               return true;
+       }
+       default:
+               return false;
+       }
+}
+
+bool PlayList::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+       if (!index.isValid() || role != Qt::EditRole) {
+               return false;
+       }
+
+       const int row = index.row(), column = index.column();
+       if (size_t(row) >= clips.size())
+               return false;
+
+       switch (Column(column)) {
+       case Column::DESCRIPTION:
+               clips[row].descriptions[clips[row].stream_idx] = value.toString().toStdString();
+               emit_data_changed(row);
+               return true;
+       case Column::CAMERA: {
+               bool ok;
+               int camera_idx = value.toInt(&ok);
+               if (!ok || camera_idx < 1 || camera_idx > NUM_CAMERAS) {
+                       return false;
                }
-       } else {
-               switch (PlayListColumn(section)) {
-               case PlayListColumn::PLAYING:
-                       return "";
-               case PlayListColumn::IN:
-                       return "In";
-               case PlayListColumn::OUT:
-                       return "Out";
-               case PlayListColumn::DURATION:
-                       return "Duration";
-               case PlayListColumn::CAMERA:
-                       return "Camera";
-               case PlayListColumn::DESCRIPTION:
-                       return "Description";
-               default:
-                       return "";
+               clips[row].stream_idx = camera_idx - 1;
+               emit_data_changed(row);
+               return true;
+       }
+       case Column::FADE_TIME: {
+               bool ok;
+               double val = value.toDouble(&ok);
+               if (!ok || !(val >= 0.0)) {
+                       return false;
                }
+               clips[row].fade_time_seconds = val;
+               emit_data_changed(row);
+               return true;
+       }
+       default:
+               return false;
        }
 }
 
@@ -186,27 +361,153 @@ void ClipList::add_clip(const Clip &clip)
        beginInsertRows(QModelIndex(), clips.size(), clips.size());
        clips.push_back(clip);
        endInsertRows();
+       emit any_content_changed();
 }
 
-void ClipList::emit_data_changed(size_t row)
+void PlayList::add_clip(const Clip &clip)
+{
+       beginInsertRows(QModelIndex(), clips.size(), clips.size());
+       clips.push_back(clip);
+       endInsertRows();
+       emit any_content_changed();
+}
+
+void PlayList::duplicate_clips(size_t first, size_t last)
+{
+       beginInsertRows(QModelIndex(), first, last);
+       clips.insert(clips.begin() + first, clips.begin() + first, clips.begin() + last + 1);
+       endInsertRows();
+       emit any_content_changed();
+}
+
+void PlayList::erase_clips(size_t first, size_t last)
 {
-       if (display_type == ListDisplay::CLIP_LIST) {
-               emit dataChanged(index(row, 0), index(row, int(ClipListColumn::NUM_COLUMNS)));
+       beginRemoveRows(QModelIndex(), first, last);
+       clips.erase(clips.begin() + first, clips.begin() + last + 1);
+       endRemoveRows();
+       emit any_content_changed();
+}
+
+void PlayList::move_clips(size_t first, size_t last, int delta)
+{
+       if (delta == -1) {
+               beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1);
+               rotate(clips.begin() + first - 1, clips.begin() + first, clips.begin() + last + 1);
        } else {
-               emit dataChanged(index(row, 0), index(row, int(PlayListColumn::NUM_COLUMNS)));
+               beginMoveRows(QModelIndex(), first, last, QModelIndex(), first + (last - first + 1) + 1);
+               first = clips.size() - first - 1;
+               last = clips.size() - last - 1;
+               rotate(clips.rbegin() + last - 1, clips.rbegin() + last, clips.rbegin() + first + 1);
        }
+       endMoveRows();
+       emit any_content_changed();
 }
 
-void ClipList::set_currently_playing(int index)
+void ClipList::emit_data_changed(size_t row)
+{
+       emit dataChanged(index(row, 0), index(row, int(Column::NUM_COLUMNS)));
+       emit any_content_changed();
+}
+
+void PlayList::emit_data_changed(size_t row)
+{
+       emit dataChanged(index(row, 0), index(row, int(Column::NUM_COLUMNS)));
+       emit any_content_changed();
+}
+
+void PlayList::set_currently_playing(int index, double progress)
 {
        int old_index = currently_playing_index;
+       int column = int(Column::PLAYING);
        if (index != old_index) {
                currently_playing_index = index;
+               play_progress = progress;
                if (old_index != -1) {
-                       emit_data_changed(old_index);
+                       emit dataChanged(this->index(old_index, column), this->index(old_index, column));
                }
                if (index != -1) {
-                       emit_data_changed(index);
+                       emit dataChanged(this->index(index, column), this->index(index, column));
+               }
+       } else if (index != -1 && fabs(progress - play_progress) > 1e-3) {
+               play_progress = progress;
+               emit dataChanged(this->index(index, column), this->index(index, column));
+       }
+}
+
+void PlayList::set_progress(const map<size_t, double> &progress)
+{
+       const int column = int(Column::PLAYING);
+       map<size_t, double> old_progress = move(this->current_progress);
+       this->current_progress = progress;
+
+       for (auto it : old_progress) {
+               size_t index = it.first;
+               if (current_progress.count(index) == 0) {
+                       emit dataChanged(this->index(index, column), this->index(index, column));
                }
        }
+       for (auto it : current_progress) {
+               size_t index = it.first;
+               emit dataChanged(this->index(index, column), this->index(index, column));
+       }
+}
+
+namespace {
+
+Clip deserialize_clip(const ClipProto &clip_proto)
+{
+       Clip clip;
+       clip.pts_in = clip_proto.pts_in();
+       clip.pts_out = clip_proto.pts_out();
+       for (int camera_idx = 0; camera_idx < min(clip_proto.description_size(), NUM_CAMERAS); ++camera_idx) {
+               clip.descriptions[camera_idx] = clip_proto.description(camera_idx);
+       }
+       clip.stream_idx = clip_proto.stream_idx();
+       clip.fade_time_seconds = clip_proto.fade_time_seconds();
+       return clip;
+}
+
+void serialize_clip(const Clip &clip, ClipProto *clip_proto)
+{
+       clip_proto->set_pts_in(clip.pts_in);
+       clip_proto->set_pts_out(clip.pts_out);
+       for (int camera_idx = 0; camera_idx < NUM_CAMERAS; ++camera_idx) {
+               *clip_proto->add_description() = clip.descriptions[camera_idx];
+       }
+       clip_proto->set_stream_idx(clip.stream_idx);
+       clip_proto->set_fade_time_seconds(clip.fade_time_seconds);
+}
+
+}  // namespace
+
+ClipList::ClipList(const ClipListProto &serialized)
+{
+       for (const ClipProto &clip_proto : serialized.clip()) {
+               clips.push_back(deserialize_clip(clip_proto));
+       }
+}
+
+ClipListProto ClipList::serialize() const
+{
+       ClipListProto ret;
+       for (const Clip &clip : clips) {
+               serialize_clip(clip, ret.add_clip());
+       }
+       return ret;
+}
+
+PlayList::PlayList(const ClipListProto &serialized)
+{
+       for (const ClipProto &clip_proto : serialized.clip()) {
+               clips.push_back(deserialize_clip(clip_proto));
+       }
+}
+
+ClipListProto PlayList::serialize() const
+{
+       ClipListProto ret;
+       for (const Clip &clip : clips) {
+               serialize_clip(clip, ret.add_clip());
+       }
+       return ret;
 }