]> git.sesse.net Git - nageru/blob - futatabi/clip_list.cpp
Make the UI programmatically dependent on NUM_CAMERAS.
[nageru] / futatabi / clip_list.cpp
1 #include "clip_list.h"
2
3 #include "mainwindow.h"
4 #include "shared/timebase.h"
5 #include "ui_mainwindow.h"
6
7 #include <math.h>
8 #include <string>
9 #include <vector>
10
11 using namespace std;
12
13 string pts_to_string(int64_t pts)
14 {
15         int64_t t = lrint((pts / double(TIMEBASE)) * 1e3);  // In milliseconds.
16         int ms = t % 1000;
17         t /= 1000;
18         int sec = t % 60;
19         t /= 60;
20         int min = t % 60;
21         t /= 60;
22         int hour = t;
23
24         char buf[256];
25         snprintf(buf, sizeof(buf), "%d:%02d:%02d.%03d", hour, min, sec, ms);
26         return buf;
27 }
28
29 string duration_to_string(int64_t pts_diff)
30 {
31         int64_t t = lrint((pts_diff / double(TIMEBASE)) * 1e3);  // In milliseconds.
32         int ms = t % 1000;
33         t /= 1000;
34         int sec = t % 60;
35         t /= 60;
36         int min = t;
37
38         char buf[256];
39         snprintf(buf, sizeof(buf), "%d:%02d.%03d", min, sec, ms);
40         return buf;
41 }
42
43 int ClipList::rowCount(const QModelIndex &parent) const
44 {
45         if (parent.isValid())
46                 return 0;
47         return clips.size();
48 }
49
50 int PlayList::rowCount(const QModelIndex &parent) const
51 {
52         if (parent.isValid())
53                 return 0;
54         return clips.size();
55 }
56
57 int ClipList::columnCount(const QModelIndex &parent) const
58 {
59         if (parent.isValid())
60                 return 0;
61         return int(Column::NUM_NON_CAMERA_COLUMNS) + NUM_CAMERAS;
62 }
63
64 int PlayList::columnCount(const QModelIndex &parent) const
65 {
66         if (parent.isValid())
67                 return 0;
68         return int(Column::NUM_COLUMNS);
69 }
70
71 QVariant ClipList::data(const QModelIndex &parent, int role) const
72 {
73         if (!parent.isValid())
74                 return QVariant();
75         const int row = parent.row(), column = parent.column();
76         if (size_t(row) >= clips.size())
77                 return QVariant();
78
79         if (role == Qt::TextAlignmentRole) {
80                 switch (Column(column)) {
81                 case Column::IN:
82                 case Column::OUT:
83                 case Column::DURATION:
84                         return Qt::AlignRight + Qt::AlignVCenter;
85                 default:
86                         return Qt::AlignLeft + Qt::AlignVCenter;
87                 }
88         }
89
90         if (role != Qt::DisplayRole && role != Qt::EditRole)
91                 return QVariant();
92
93         switch (Column(column)) {
94         case Column::IN:
95                 return QString::fromStdString(pts_to_string(clips[row].pts_in));
96         case Column::OUT:
97                 if (clips[row].pts_out >= 0) {
98                         return QString::fromStdString(pts_to_string(clips[row].pts_out));
99                 } else {
100                         return QVariant();
101                 }
102         case Column::DURATION:
103                 if (clips[row].pts_out >= 0) {
104                         return QString::fromStdString(duration_to_string(clips[row].pts_out - clips[row].pts_in));
105                 } else {
106                         return QVariant();
107                 }
108         default:
109                 if (is_camera_column(column)) {
110                         unsigned stream_idx = column - int(Column::CAMERA_1);
111                         return QString::fromStdString(clips[row].descriptions[stream_idx]);
112                 } else {
113                         return "";
114                 }
115         }
116 }
117
118 QVariant PlayList::data(const QModelIndex &parent, int role) const
119 {
120         if (!parent.isValid())
121                 return QVariant();
122         const int row = parent.row(), column = parent.column();
123         if (size_t(row) >= clips.size())
124                 return QVariant();
125
126         if (role == Qt::TextAlignmentRole) {
127                 switch (Column(column)) {
128                 case Column::PLAYING:
129                         return Qt::AlignCenter;
130                 case Column::IN:
131                 case Column::OUT:
132                 case Column::DURATION:
133                 case Column::FADE_TIME:
134                         return Qt::AlignRight + Qt::AlignVCenter;
135                 case Column::CAMERA:
136                         return Qt::AlignCenter;
137                 default:
138                         return Qt::AlignLeft + Qt::AlignVCenter;
139                 }
140         }
141         if (role == Qt::BackgroundRole) {
142                 if (Column(column) == Column::PLAYING) {
143                         auto it = current_progress.find(row);
144                         if (it != current_progress.end()) {
145                                 double play_progress = it->second;
146
147                                 // This only really works well for the first column, for whatever odd Qt reason.
148                                 QLinearGradient grad(QPointF(0, 0), QPointF(1, 0));
149                                 grad.setCoordinateMode(grad.QGradient::ObjectBoundingMode);
150                                 grad.setColorAt(0.0f, QColor::fromRgbF(0.0f, 0.0f, 1.0f, 0.2f));
151                                 grad.setColorAt(play_progress, QColor::fromRgbF(0.0f, 0.0f, 1.0f, 0.2f));
152                                 if (play_progress + 0.01f <= 1.0f) {
153                                         grad.setColorAt(play_progress + 0.01f, QColor::fromRgbF(0.0f, 0.0f, 1.0f, 0.0f));
154                                 }
155                                 return QBrush(grad);
156                         } else {
157                                 return QVariant();
158                         }
159                 } else {
160                         return QVariant();
161                 }
162         }
163
164         if (role != Qt::DisplayRole && role != Qt::EditRole)
165                 return QVariant();
166
167         switch (Column(column)) {
168         case Column::PLAYING:
169                 return current_progress.count(row) ? "→" : "";
170         case Column::IN:
171                 return QString::fromStdString(pts_to_string(clips[row].pts_in));
172         case Column::OUT:
173                 if (clips[row].pts_out >= 0) {
174                         return QString::fromStdString(pts_to_string(clips[row].pts_out));
175                 } else {
176                         return QVariant();
177                 }
178         case Column::DURATION:
179                 if (clips[row].pts_out >= 0) {
180                         return QString::fromStdString(duration_to_string(clips[row].pts_out - clips[row].pts_in));
181                 } else {
182                         return QVariant();
183                 }
184         case Column::CAMERA:
185                 return qlonglong(clips[row].stream_idx + 1);
186         case Column::DESCRIPTION:
187                 return QString::fromStdString(clips[row].descriptions[clips[row].stream_idx]);
188         case Column::FADE_TIME: {
189                 stringstream ss;
190                 ss.imbue(locale("C"));
191                 ss.precision(3);
192                 ss << fixed << clips[row].fade_time_seconds;
193                 return QString::fromStdString(ss.str());
194         }
195         default:
196                 return "";
197         }
198 }
199
200 QVariant ClipList::headerData(int section, Qt::Orientation orientation, int role) const
201 {
202         if (role != Qt::DisplayRole)
203                 return QVariant();
204         if (orientation != Qt::Horizontal)
205                 return QVariant();
206
207         switch (Column(section)) {
208         case Column::IN:
209                 return "In";
210         case Column::OUT:
211                 return "Out";
212         case Column::DURATION:
213                 return "Duration";
214         default:
215                 if (section >= int(Column::CAMERA_1) && section < int(Column::CAMERA_1) + NUM_CAMERAS) {
216                         return QString::fromStdString("Camera " + to_string(section - int(Column::CAMERA_1) + 1));
217                 } else {
218                         return "";
219                 }
220         }
221 }
222
223 QVariant PlayList::headerData(int section, Qt::Orientation orientation, int role) const
224 {
225         if (role != Qt::DisplayRole)
226                 return QVariant();
227         if (orientation != Qt::Horizontal)
228                 return QVariant();
229
230         switch (Column(section)) {
231         case Column::PLAYING:
232                 return "";
233         case Column::IN:
234                 return "In";
235         case Column::OUT:
236                 return "Out";
237         case Column::DURATION:
238                 return "Duration";
239         case Column::CAMERA:
240                 return "Camera";
241         case Column::DESCRIPTION:
242                 return "Description";
243         case Column::FADE_TIME:
244                 return "Fade time";
245         default:
246                 return "";
247         }
248 }
249
250 Qt::ItemFlags ClipList::flags(const QModelIndex &index) const
251 {
252         if (!index.isValid())
253                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
254         const int row = index.row(), column = index.column();
255         if (size_t(row) >= clips.size())
256                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
257
258         if (is_camera_column(column)) {
259                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
260         } else {
261                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
262         }
263 }
264
265 Qt::ItemFlags PlayList::flags(const QModelIndex &index) const
266 {
267         if (!index.isValid())
268                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
269         const int row = index.row(), column = index.column();
270         if (size_t(row) >= clips.size())
271                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
272
273         switch (Column(column)) {
274         case Column::DESCRIPTION:
275         case Column::CAMERA:
276         case Column::FADE_TIME:
277                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
278                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
279         default:
280                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
281         }
282 }
283
284 bool ClipList::setData(const QModelIndex &index, const QVariant &value, int role)
285 {
286         if (!index.isValid() || role != Qt::EditRole) {
287                 return false;
288         }
289
290         const int row = index.row(), column = index.column();
291         if (size_t(row) >= clips.size())
292                 return false;
293
294         if (is_camera_column(column)) {
295                 unsigned stream_idx = column - int(Column::CAMERA_1);
296                 clips[row].descriptions[stream_idx] = value.toString().toStdString();
297                 emit_data_changed(row);
298                 return true;
299         } else {
300                 return false;
301         }
302 }
303
304 bool PlayList::setData(const QModelIndex &index, const QVariant &value, int role)
305 {
306         if (!index.isValid() || role != Qt::EditRole) {
307                 return false;
308         }
309
310         const int row = index.row(), column = index.column();
311         if (size_t(row) >= clips.size())
312                 return false;
313
314         switch (Column(column)) {
315         case Column::DESCRIPTION:
316                 clips[row].descriptions[clips[row].stream_idx] = value.toString().toStdString();
317                 emit_data_changed(row);
318                 return true;
319         case Column::CAMERA: {
320                 bool ok;
321                 int camera_idx = value.toInt(&ok);
322                 if (!ok || camera_idx < 1 || camera_idx > NUM_CAMERAS) {
323                         return false;
324                 }
325                 clips[row].stream_idx = camera_idx - 1;
326                 emit_data_changed(row);
327                 return true;
328         }
329         case Column::FADE_TIME: {
330                 bool ok;
331                 double val = value.toDouble(&ok);
332                 if (!ok || !(val >= 0.0)) {
333                         return false;
334                 }
335                 clips[row].fade_time_seconds = val;
336                 emit_data_changed(row);
337                 return true;
338         }
339         default:
340                 return false;
341         }
342 }
343
344 void ClipList::add_clip(const Clip &clip)
345 {
346         beginInsertRows(QModelIndex(), clips.size(), clips.size());
347         clips.push_back(clip);
348         endInsertRows();
349         emit any_content_changed();
350 }
351
352 void PlayList::add_clip(const Clip &clip)
353 {
354         beginInsertRows(QModelIndex(), clips.size(), clips.size());
355         clips.push_back(clip);
356         endInsertRows();
357         emit any_content_changed();
358 }
359
360 void PlayList::duplicate_clips(size_t first, size_t last)
361 {
362         beginInsertRows(QModelIndex(), first, last);
363         clips.insert(clips.begin() + first, clips.begin() + first, clips.begin() + last + 1);
364         endInsertRows();
365         emit any_content_changed();
366 }
367
368 void PlayList::erase_clips(size_t first, size_t last)
369 {
370         beginRemoveRows(QModelIndex(), first, last);
371         clips.erase(clips.begin() + first, clips.begin() + last + 1);
372         endRemoveRows();
373         emit any_content_changed();
374 }
375
376 void PlayList::move_clips(size_t first, size_t last, int delta)
377 {
378         if (delta == -1) {
379                 beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1);
380                 rotate(clips.begin() + first - 1, clips.begin() + first, clips.begin() + last + 1);
381         } else {
382                 beginMoveRows(QModelIndex(), first, last, QModelIndex(), first + (last - first + 1) + 1);
383                 first = clips.size() - first - 1;
384                 last = clips.size() - last - 1;
385                 rotate(clips.rbegin() + last - 1, clips.rbegin() + last, clips.rbegin() + first + 1);
386         }
387         endMoveRows();
388         emit any_content_changed();
389 }
390
391 void ClipList::emit_data_changed(size_t row)
392 {
393         emit dataChanged(index(row, 0), index(row, int(Column::NUM_NON_CAMERA_COLUMNS) + NUM_CAMERAS));
394         emit any_content_changed();
395 }
396
397 void PlayList::emit_data_changed(size_t row)
398 {
399         emit dataChanged(index(row, 0), index(row, int(Column::NUM_COLUMNS)));
400         emit any_content_changed();
401 }
402
403 void PlayList::set_currently_playing(int index, double progress)
404 {
405         int old_index = currently_playing_index;
406         int column = int(Column::PLAYING);
407         if (index != old_index) {
408                 currently_playing_index = index;
409                 play_progress = progress;
410                 if (old_index != -1) {
411                         emit dataChanged(this->index(old_index, column), this->index(old_index, column));
412                 }
413                 if (index != -1) {
414                         emit dataChanged(this->index(index, column), this->index(index, column));
415                 }
416         } else if (index != -1 && fabs(progress - play_progress) > 1e-3) {
417                 play_progress = progress;
418                 emit dataChanged(this->index(index, column), this->index(index, column));
419         }
420 }
421
422 void PlayList::set_progress(const map<size_t, double> &progress)
423 {
424         const int column = int(Column::PLAYING);
425         map<size_t, double> old_progress = move(this->current_progress);
426         this->current_progress = progress;
427
428         for (auto it : old_progress) {
429                 size_t index = it.first;
430                 if (current_progress.count(index) == 0) {
431                         emit dataChanged(this->index(index, column), this->index(index, column));
432                 }
433         }
434         for (auto it : current_progress) {
435                 size_t index = it.first;
436                 emit dataChanged(this->index(index, column), this->index(index, column));
437         }
438 }
439
440 namespace {
441
442 Clip deserialize_clip(const ClipProto &clip_proto)
443 {
444         Clip clip;
445         clip.pts_in = clip_proto.pts_in();
446         clip.pts_out = clip_proto.pts_out();
447         for (int camera_idx = 0; camera_idx < min(clip_proto.description_size(), NUM_CAMERAS); ++camera_idx) {
448                 clip.descriptions[camera_idx] = clip_proto.description(camera_idx);
449         }
450         clip.stream_idx = clip_proto.stream_idx();
451         clip.fade_time_seconds = clip_proto.fade_time_seconds();
452         return clip;
453 }
454
455 void serialize_clip(const Clip &clip, ClipProto *clip_proto)
456 {
457         clip_proto->set_pts_in(clip.pts_in);
458         clip_proto->set_pts_out(clip.pts_out);
459         for (int camera_idx = 0; camera_idx < NUM_CAMERAS; ++camera_idx) {
460                 *clip_proto->add_description() = clip.descriptions[camera_idx];
461         }
462         clip_proto->set_stream_idx(clip.stream_idx);
463         clip_proto->set_fade_time_seconds(clip.fade_time_seconds);
464 }
465
466 }  // namespace
467
468 ClipList::ClipList(const ClipListProto &serialized)
469 {
470         for (const ClipProto &clip_proto : serialized.clip()) {
471                 clips.push_back(deserialize_clip(clip_proto));
472         }
473 }
474
475 ClipListProto ClipList::serialize() const
476 {
477         ClipListProto ret;
478         for (const Clip &clip : clips) {
479                 serialize_clip(clip, ret.add_clip());
480         }
481         return ret;
482 }
483
484 PlayList::PlayList(const ClipListProto &serialized)
485 {
486         for (const ClipProto &clip_proto : serialized.clip()) {
487                 clips.push_back(deserialize_clip(clip_proto));
488         }
489 }
490
491 ClipListProto PlayList::serialize() const
492 {
493         ClipListProto ret;
494         for (const Clip &clip : clips) {
495                 serialize_clip(clip, ret.add_clip());
496         }
497         return ret;
498 }