]> git.sesse.net Git - nageru/blob - mainwindow.cpp
Make it possible to switch camera angles for previews with the 1–4 keys.
[nageru] / mainwindow.cpp
1 #include "mainwindow.h"
2
3 #include "clip_list.h"
4 #include "player.h"
5 #include "post_to_main_thread.h"
6 #include "ui_mainwindow.h"
7
8 #include <string>
9 #include <vector>
10
11 #include <QMouseEvent>
12 #include <QWheelEvent>
13 #include <QShortcut>
14
15 using namespace std;
16
17 MainWindow *global_mainwindow = nullptr;
18 ClipList *cliplist_clips;
19 PlayList *playlist_clips;
20
21 extern int64_t current_pts;
22 extern mutex frame_mu;
23 extern vector<int64_t> frames[MAX_STREAMS];
24
25 MainWindow::MainWindow()
26         : ui(new Ui::MainWindow)
27 {
28         global_mainwindow = this;
29         ui->setupUi(this);
30
31         cliplist_clips = new ClipList();
32         ui->clip_list->setModel(cliplist_clips);
33
34         playlist_clips = new PlayList();
35         ui->playlist->setModel(playlist_clips);
36
37         // For scrubbing in the pts columns.
38         ui->clip_list->viewport()->installEventFilter(this);
39         ui->playlist->viewport()->installEventFilter(this);
40
41         QShortcut *cue_in = new QShortcut(QKeySequence(Qt::Key_A), this);
42         connect(cue_in, &QShortcut::activated, ui->cue_in_btn, &QPushButton::click);
43         connect(ui->cue_in_btn, &QPushButton::clicked, this, &MainWindow::cue_in_clicked);
44
45         QShortcut *cue_out = new QShortcut(QKeySequence(Qt::Key_S), this);
46         connect(cue_out, &QShortcut::activated, ui->cue_out_btn, &QPushButton::click);
47         connect(ui->cue_out_btn, &QPushButton::clicked, this, &MainWindow::cue_out_clicked);
48
49         QShortcut *queue = new QShortcut(QKeySequence(Qt::Key_Q), this);
50         connect(queue, &QShortcut::activated, ui->queue_btn, &QPushButton::click);
51         connect(ui->queue_btn, &QPushButton::clicked, this, &MainWindow::queue_clicked);
52
53         QShortcut *preview = new QShortcut(QKeySequence(Qt::Key_W), this);
54         connect(preview, &QShortcut::activated, ui->preview_btn, &QPushButton::click);
55         connect(ui->preview_btn, &QPushButton::clicked, this, &MainWindow::preview_clicked);
56
57         QShortcut *play = new QShortcut(QKeySequence(Qt::Key_Space), this);
58         connect(play, &QShortcut::activated, ui->play_btn, &QPushButton::click);
59         connect(ui->play_btn, &QPushButton::clicked, this, &MainWindow::play_clicked);
60
61         QShortcut *preview_1 = new QShortcut(QKeySequence(Qt::Key_1), this);
62         connect(preview_1, &QShortcut::activated, ui->preview_1_btn, &QPushButton::click);
63         connect(ui->preview_1_btn, &QPushButton::clicked, [this]{ preview_angle_clicked(0); });
64
65         QShortcut *preview_2 = new QShortcut(QKeySequence(Qt::Key_2), this);
66         connect(preview_2, &QShortcut::activated, ui->preview_2_btn, &QPushButton::click);
67         connect(ui->preview_2_btn, &QPushButton::clicked, [this]{ preview_angle_clicked(1); });
68
69         QShortcut *preview_3 = new QShortcut(QKeySequence(Qt::Key_3), this);
70         connect(preview_3, &QShortcut::activated, ui->preview_3_btn, &QPushButton::click);
71         connect(ui->preview_3_btn, &QPushButton::clicked, [this]{ preview_angle_clicked(2); });
72
73         QShortcut *preview_4 = new QShortcut(QKeySequence(Qt::Key_4), this);
74         connect(preview_4, &QShortcut::activated, ui->preview_4_btn, &QPushButton::click);
75         connect(ui->preview_4_btn, &QPushButton::clicked, [this]{ preview_angle_clicked(3); });
76
77         preview_player = new Player(ui->preview_display);
78         live_player = new Player(ui->live_display);
79         live_player->set_done_callback([this]{
80                 post_to_main_thread([this]{
81                         live_player_clip_done();
82                 });
83         });
84 }
85
86 void MainWindow::cue_in_clicked()
87 {
88         if (!cliplist_clips->empty() && cliplist_clips->back()->pts_out < 0) {
89                 cliplist_clips->back()->pts_in = current_pts;
90                 return;
91         }
92         Clip clip;
93         clip.pts_in = current_pts;
94         cliplist_clips->add_clip(clip);
95 }
96
97 void MainWindow::cue_out_clicked()
98 {
99         if (!cliplist_clips->empty()) {
100                 cliplist_clips->back()->pts_out = current_pts;
101                 // TODO: select the row in the clip list?
102         }
103 }
104
105 void MainWindow::queue_clicked()
106 {
107         QItemSelectionModel *selected = ui->clip_list->selectionModel();
108         if (!selected->hasSelection()) {
109                 Clip clip = *cliplist_clips->back();
110                 clip.stream_idx = 0;
111                 playlist_clips->add_clip(clip);
112                 return;
113         }
114
115         QModelIndex index = selected->currentIndex();
116         if (index.column() >= int(ClipList::Column::CAMERA_1) &&
117             index.column() <= int(ClipList::Column::CAMERA_4)) {
118                 Clip clip = *cliplist_clips->clip(index.row());
119                 clip.stream_idx = index.column() - int(ClipList::Column::CAMERA_1);
120                 playlist_clips->add_clip(clip);
121         }
122 }
123
124 void MainWindow::preview_clicked()
125 {
126         if (cliplist_clips->empty()) return;
127
128         QItemSelectionModel *selected = ui->clip_list->selectionModel();
129         if (!selected->hasSelection()) {
130                 preview_player->play_clip(*cliplist_clips->back(), 0);
131                 return;
132         }
133
134         QModelIndex index = selected->currentIndex();
135         if (index.column() >= int(ClipList::Column::CAMERA_1) &&
136             index.column() <= int(ClipList::Column::CAMERA_4)) {
137                 unsigned stream_idx = index.column() - int(ClipList::Column::CAMERA_1);
138                 preview_player->play_clip(*cliplist_clips->clip(index.row()), stream_idx);
139         }
140 }
141
142 void MainWindow::preview_angle_clicked(unsigned stream_idx)
143 {
144         preview_player->override_angle(stream_idx);
145
146         // Change the selection if we were previewing a clip from the clip list.
147         // (The only other thing we could be showing is a pts scrub, and if so,
148         // that would be selected.)
149         QItemSelectionModel *selected = ui->clip_list->selectionModel();
150         if (selected->hasSelection()) {
151                 QModelIndex cell = selected->selectedIndexes()[0];
152                 int column = int(ClipList::Column::CAMERA_1) + stream_idx;
153                 selected->setCurrentIndex(cell.sibling(cell.row(), column), QItemSelectionModel::ClearAndSelect);
154         }
155 }
156
157 void MainWindow::play_clicked()
158 {
159         if (playlist_clips->empty()) return;
160
161         QItemSelectionModel *selected = ui->playlist->selectionModel();
162         int row;
163         if (!selected->hasSelection()) {
164                 row = 0;
165         } else {
166                 row = selected->selectedRows(0)[0].row();
167         }
168
169         const Clip &clip = *playlist_clips->clip(row);
170         live_player->play_clip(clip, clip.stream_idx);
171         playlist_clips->set_currently_playing(row);
172 }
173
174 void MainWindow::live_player_clip_done()
175 {
176         int row = playlist_clips->get_currently_playing();
177         if (row != -1 && row < int(playlist_clips->size()) - 1) {
178                 ++row;
179                 const Clip &clip = *playlist_clips->clip(row);
180                 live_player->play_clip(clip, clip.stream_idx);
181                 playlist_clips->set_currently_playing(row);
182         } else {
183                 playlist_clips->set_currently_playing(-1);
184         }
185 }
186
187 void MainWindow::resizeEvent(QResizeEvent *event)
188 {
189         QMainWindow::resizeEvent(event);
190
191         // Ask for a relayout, but only after the event loop is done doing relayout
192         // on everything else.
193         QMetaObject::invokeMethod(this, "relayout", Qt::QueuedConnection);
194 }
195
196 void MainWindow::relayout()
197 {
198         ui->live_display->setMinimumHeight(ui->live_display->width() * 9 / 16);
199 }
200
201 bool MainWindow::eventFilter(QObject *watched, QEvent *event)
202 {
203         constexpr int dead_zone_pixels = 3;  // To avoid that simple clicks get misinterpreted.
204         constexpr int scrub_sensitivity = 100;  // pts units per pixel.
205         constexpr int wheel_sensitivity = 100;  // pts units per degree.
206
207         unsigned stream_idx = ui->preview_display->get_stream_idx();
208
209         if (event->type() == QEvent::MouseButtonPress) {
210                 QMouseEvent *mouse = (QMouseEvent *)event;
211
212                 QTableView *destination;
213                 ScrubType type;
214
215                 if (watched == ui->clip_list->viewport()) {
216                         destination = ui->clip_list;
217                         type = SCRUBBING_CLIP_LIST;
218                 } else if (watched == ui->playlist->viewport()) {
219                         destination = ui->playlist;
220                         type = SCRUBBING_PLAYLIST;
221                 } else {
222                         return false;
223                 }
224                 int column = destination->columnAt(mouse->x());
225                 int row = destination->rowAt(mouse->y());
226                 if (column == -1 || row == -1) return false;
227
228                 if (type == SCRUBBING_CLIP_LIST) {
229                         if (ClipList::Column(column) == ClipList::Column::IN) {
230                                 scrub_pts_origin = cliplist_clips->clip(row)->pts_in;
231                                 preview_single_frame(scrub_pts_origin, stream_idx, FIRST_AT_OR_AFTER);
232                         } else if (ClipList::Column(column) == ClipList::Column::OUT) {
233                                 scrub_pts_origin = cliplist_clips->clip(row)->pts_out;
234                                 preview_single_frame(scrub_pts_origin, stream_idx, LAST_BEFORE);
235                         } else {
236                                 return false;
237                         }
238                 } else {
239                         if (PlayList::Column(column) == PlayList::Column::IN) {
240                                 scrub_pts_origin = playlist_clips->clip(row)->pts_in;
241                                 preview_single_frame(scrub_pts_origin, stream_idx, FIRST_AT_OR_AFTER);
242                         } else if (PlayList::Column(column) == PlayList::Column::OUT) {
243                                 scrub_pts_origin = playlist_clips->clip(row)->pts_out;
244                                 preview_single_frame(scrub_pts_origin, stream_idx, LAST_BEFORE);
245                         } else {
246                                 return false;
247                         }
248                 }
249
250                 scrubbing = true;
251                 scrub_row = row;
252                 scrub_column = column;
253                 scrub_x_origin = mouse->x();
254                 scrub_type = type;
255         } else if (event->type() == QEvent::MouseMove) {
256                 if (scrubbing) {
257                         QMouseEvent *mouse = (QMouseEvent *)event;
258                         int offset = mouse->x() - scrub_x_origin;
259                         int adjusted_offset;
260                         if (offset >= dead_zone_pixels) {
261                                 adjusted_offset = offset - dead_zone_pixels;
262                         } else if (offset < -dead_zone_pixels) {
263                                 adjusted_offset = offset + dead_zone_pixels;
264                         } else {
265                                 adjusted_offset = 0;
266                         }
267
268                         int64_t pts = scrub_pts_origin + adjusted_offset * scrub_sensitivity;
269
270                         if (scrub_type == SCRUBBING_CLIP_LIST) {
271                                 if (scrub_column == int(ClipList::Column::IN)) {
272                                         pts = std::max<int64_t>(pts, 0);
273                                         pts = std::min(pts, cliplist_clips->clip(scrub_row)->pts_out);
274                                         cliplist_clips->clip(scrub_row)->pts_in = pts;
275                                         preview_single_frame(pts, stream_idx, FIRST_AT_OR_AFTER);
276                                 } else {
277                                         pts = std::max(pts, cliplist_clips->clip(scrub_row)->pts_in);
278                                         pts = std::min(pts, current_pts);
279                                         cliplist_clips->clip(scrub_row)->pts_out = pts;
280                                         preview_single_frame(pts, stream_idx, LAST_BEFORE);
281                                 }
282                         } else {
283                                 if (scrub_column == int(PlayList::Column::IN)) {
284                                         pts = std::max<int64_t>(pts, 0);
285                                         pts = std::min(pts, playlist_clips->clip(scrub_row)->pts_out);
286                                         playlist_clips->clip(scrub_row)->pts_in = pts;
287                                         preview_single_frame(pts, stream_idx, FIRST_AT_OR_AFTER);
288                                 } else {
289                                         pts = std::max(pts, playlist_clips->clip(scrub_row)->pts_in);
290                                         pts = std::min(pts, current_pts);
291                                         playlist_clips->clip(scrub_row)->pts_out = pts;
292                                         preview_single_frame(pts, stream_idx, LAST_BEFORE);
293                                 }
294                         }
295
296                         return true;  // Don't use this mouse movement for selecting things.
297                 }
298         } else if (event->type() == QEvent::Wheel) {
299                 QWheelEvent *wheel = (QWheelEvent *)event;
300
301                 QTableView *destination;
302                 int in_column, out_column;
303                 if (watched == ui->clip_list->viewport()) {
304                         destination = ui->clip_list;
305                         in_column = int(ClipList::Column::IN);
306                         out_column = int(ClipList::Column::OUT);
307                 } else if (watched == ui->playlist->viewport()) {
308                         destination = ui->playlist;
309                         in_column = int(PlayList::Column::IN);
310                         out_column = int(PlayList::Column::OUT);
311                 } else {
312                         return false;
313                 }
314                 int column = destination->columnAt(wheel->x());
315                 int row = destination->rowAt(wheel->y());
316                 if (column == -1 || row == -1) return false;
317
318                 ClipProxy clip = (watched == ui->clip_list->viewport()) ?
319                         cliplist_clips->clip(row) : playlist_clips->clip(row);
320
321                 if (column == in_column) {
322                         int64_t pts = clip->pts_in + wheel->angleDelta().y() * wheel_sensitivity;
323                         pts = std::max<int64_t>(pts, 0);
324                         pts = std::min(pts, clip->pts_out);
325                         clip->pts_in = pts;
326                         preview_single_frame(pts, stream_idx, FIRST_AT_OR_AFTER);
327                 } else if (column == out_column) {
328                         int64_t pts = clip->pts_out + wheel->angleDelta().y() * wheel_sensitivity;
329                         pts = std::max(pts, clip->pts_in);
330                         pts = std::min(pts, current_pts);
331                         clip->pts_out = pts;
332                         preview_single_frame(pts, stream_idx, LAST_BEFORE);
333                 }
334         } else if (event->type() == QEvent::MouseButtonRelease) {
335                 scrubbing = false;
336         }
337         return false;
338 }
339
340 void MainWindow::preview_single_frame(int64_t pts, unsigned stream_idx, MainWindow::Rounding rounding)
341 {
342         if (rounding == LAST_BEFORE) {
343                 lock_guard<mutex> lock(frame_mu);
344                 if (frames[stream_idx].empty()) return;
345                 auto it = lower_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts);
346                 if (it != frames[stream_idx].end()) {
347                         pts = *it;
348                 }
349         } else {
350                 assert(rounding == FIRST_AT_OR_AFTER);
351                 lock_guard<mutex> lock(frame_mu);
352                 if (frames[stream_idx].empty()) return;
353                 auto it = upper_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts - 1);
354                 if (it != frames[stream_idx].end()) {
355                         pts = *it;
356                 }
357         }
358
359         Clip fake_clip;
360         fake_clip.pts_in = pts;
361         fake_clip.pts_out = pts + 1;
362         preview_player->play_clip(fake_clip, stream_idx);
363 }