]> git.sesse.net Git - nageru/blob - mainwindow.cpp
Fix a crash.
[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         if (cliplist_clips->empty()) {
108                 return;
109         }
110
111         QItemSelectionModel *selected = ui->clip_list->selectionModel();
112         if (!selected->hasSelection()) {
113                 Clip clip = *cliplist_clips->back();
114                 clip.stream_idx = 0;
115                 playlist_clips->add_clip(clip);
116                 return;
117         }
118
119         QModelIndex index = selected->currentIndex();
120         if (index.column() >= int(ClipList::Column::CAMERA_1) &&
121             index.column() <= int(ClipList::Column::CAMERA_4)) {
122                 Clip clip = *cliplist_clips->clip(index.row());
123                 clip.stream_idx = index.column() - int(ClipList::Column::CAMERA_1);
124                 playlist_clips->add_clip(clip);
125         }
126 }
127
128 void MainWindow::preview_clicked()
129 {
130         if (cliplist_clips->empty()) return;
131
132         QItemSelectionModel *selected = ui->clip_list->selectionModel();
133         if (!selected->hasSelection()) {
134                 preview_player->play_clip(*cliplist_clips->back(), 0);
135                 return;
136         }
137
138         QModelIndex index = selected->currentIndex();
139         if (index.column() >= int(ClipList::Column::CAMERA_1) &&
140             index.column() <= int(ClipList::Column::CAMERA_4)) {
141                 unsigned stream_idx = index.column() - int(ClipList::Column::CAMERA_1);
142                 preview_player->play_clip(*cliplist_clips->clip(index.row()), stream_idx);
143         }
144 }
145
146 void MainWindow::preview_angle_clicked(unsigned stream_idx)
147 {
148         preview_player->override_angle(stream_idx);
149
150         // Change the selection if we were previewing a clip from the clip list.
151         // (The only other thing we could be showing is a pts scrub, and if so,
152         // that would be selected.)
153         QItemSelectionModel *selected = ui->clip_list->selectionModel();
154         if (selected->hasSelection()) {
155                 QModelIndex cell = selected->selectedIndexes()[0];
156                 int column = int(ClipList::Column::CAMERA_1) + stream_idx;
157                 selected->setCurrentIndex(cell.sibling(cell.row(), column), QItemSelectionModel::ClearAndSelect);
158         }
159 }
160
161 void MainWindow::play_clicked()
162 {
163         if (playlist_clips->empty()) return;
164
165         QItemSelectionModel *selected = ui->playlist->selectionModel();
166         int row;
167         if (!selected->hasSelection()) {
168                 row = 0;
169         } else {
170                 row = selected->selectedRows(0)[0].row();
171         }
172
173         const Clip &clip = *playlist_clips->clip(row);
174         live_player->play_clip(clip, clip.stream_idx);
175         playlist_clips->set_currently_playing(row);
176 }
177
178 void MainWindow::live_player_clip_done()
179 {
180         int row = playlist_clips->get_currently_playing();
181         if (row != -1 && row < int(playlist_clips->size()) - 1) {
182                 ++row;
183                 const Clip &clip = *playlist_clips->clip(row);
184                 live_player->play_clip(clip, clip.stream_idx);
185                 playlist_clips->set_currently_playing(row);
186         } else {
187                 playlist_clips->set_currently_playing(-1);
188         }
189 }
190
191 void MainWindow::resizeEvent(QResizeEvent *event)
192 {
193         QMainWindow::resizeEvent(event);
194
195         // Ask for a relayout, but only after the event loop is done doing relayout
196         // on everything else.
197         QMetaObject::invokeMethod(this, "relayout", Qt::QueuedConnection);
198 }
199
200 void MainWindow::relayout()
201 {
202         ui->live_display->setMinimumHeight(ui->live_display->width() * 9 / 16);
203 }
204
205 bool MainWindow::eventFilter(QObject *watched, QEvent *event)
206 {
207         constexpr int dead_zone_pixels = 3;  // To avoid that simple clicks get misinterpreted.
208         constexpr int scrub_sensitivity = 100;  // pts units per pixel.
209         constexpr int wheel_sensitivity = 100;  // pts units per degree.
210
211         unsigned stream_idx = ui->preview_display->get_stream_idx();
212
213         if (event->type() == QEvent::MouseButtonPress) {
214                 QMouseEvent *mouse = (QMouseEvent *)event;
215
216                 QTableView *destination;
217                 ScrubType type;
218
219                 if (watched == ui->clip_list->viewport()) {
220                         destination = ui->clip_list;
221                         type = SCRUBBING_CLIP_LIST;
222                 } else if (watched == ui->playlist->viewport()) {
223                         destination = ui->playlist;
224                         type = SCRUBBING_PLAYLIST;
225                 } else {
226                         return false;
227                 }
228                 int column = destination->columnAt(mouse->x());
229                 int row = destination->rowAt(mouse->y());
230                 if (column == -1 || row == -1) return false;
231
232                 if (type == SCRUBBING_CLIP_LIST) {
233                         if (ClipList::Column(column) == ClipList::Column::IN) {
234                                 scrub_pts_origin = cliplist_clips->clip(row)->pts_in;
235                                 preview_single_frame(scrub_pts_origin, stream_idx, FIRST_AT_OR_AFTER);
236                         } else if (ClipList::Column(column) == ClipList::Column::OUT) {
237                                 scrub_pts_origin = cliplist_clips->clip(row)->pts_out;
238                                 preview_single_frame(scrub_pts_origin, stream_idx, LAST_BEFORE);
239                         } else {
240                                 return false;
241                         }
242                 } else {
243                         if (PlayList::Column(column) == PlayList::Column::IN) {
244                                 scrub_pts_origin = playlist_clips->clip(row)->pts_in;
245                                 preview_single_frame(scrub_pts_origin, stream_idx, FIRST_AT_OR_AFTER);
246                         } else if (PlayList::Column(column) == PlayList::Column::OUT) {
247                                 scrub_pts_origin = playlist_clips->clip(row)->pts_out;
248                                 preview_single_frame(scrub_pts_origin, stream_idx, LAST_BEFORE);
249                         } else {
250                                 return false;
251                         }
252                 }
253
254                 scrubbing = true;
255                 scrub_row = row;
256                 scrub_column = column;
257                 scrub_x_origin = mouse->x();
258                 scrub_type = type;
259         } else if (event->type() == QEvent::MouseMove) {
260                 if (scrubbing) {
261                         QMouseEvent *mouse = (QMouseEvent *)event;
262                         int offset = mouse->x() - scrub_x_origin;
263                         int adjusted_offset;
264                         if (offset >= dead_zone_pixels) {
265                                 adjusted_offset = offset - dead_zone_pixels;
266                         } else if (offset < -dead_zone_pixels) {
267                                 adjusted_offset = offset + dead_zone_pixels;
268                         } else {
269                                 adjusted_offset = 0;
270                         }
271
272                         int64_t pts = scrub_pts_origin + adjusted_offset * scrub_sensitivity;
273
274                         if (scrub_type == SCRUBBING_CLIP_LIST) {
275                                 if (scrub_column == int(ClipList::Column::IN)) {
276                                         pts = std::max<int64_t>(pts, 0);
277                                         pts = std::min(pts, cliplist_clips->clip(scrub_row)->pts_out);
278                                         cliplist_clips->clip(scrub_row)->pts_in = pts;
279                                         preview_single_frame(pts, stream_idx, FIRST_AT_OR_AFTER);
280                                 } else {
281                                         pts = std::max(pts, cliplist_clips->clip(scrub_row)->pts_in);
282                                         pts = std::min(pts, current_pts);
283                                         cliplist_clips->clip(scrub_row)->pts_out = pts;
284                                         preview_single_frame(pts, stream_idx, LAST_BEFORE);
285                                 }
286                         } else {
287                                 if (scrub_column == int(PlayList::Column::IN)) {
288                                         pts = std::max<int64_t>(pts, 0);
289                                         pts = std::min(pts, playlist_clips->clip(scrub_row)->pts_out);
290                                         playlist_clips->clip(scrub_row)->pts_in = pts;
291                                         preview_single_frame(pts, stream_idx, FIRST_AT_OR_AFTER);
292                                 } else {
293                                         pts = std::max(pts, playlist_clips->clip(scrub_row)->pts_in);
294                                         pts = std::min(pts, current_pts);
295                                         playlist_clips->clip(scrub_row)->pts_out = pts;
296                                         preview_single_frame(pts, stream_idx, LAST_BEFORE);
297                                 }
298                         }
299
300                         return true;  // Don't use this mouse movement for selecting things.
301                 }
302         } else if (event->type() == QEvent::Wheel) {
303                 QWheelEvent *wheel = (QWheelEvent *)event;
304
305                 QTableView *destination;
306                 int in_column, out_column;
307                 if (watched == ui->clip_list->viewport()) {
308                         destination = ui->clip_list;
309                         in_column = int(ClipList::Column::IN);
310                         out_column = int(ClipList::Column::OUT);
311                 } else if (watched == ui->playlist->viewport()) {
312                         destination = ui->playlist;
313                         in_column = int(PlayList::Column::IN);
314                         out_column = int(PlayList::Column::OUT);
315                 } else {
316                         return false;
317                 }
318                 int column = destination->columnAt(wheel->x());
319                 int row = destination->rowAt(wheel->y());
320                 if (column == -1 || row == -1) return false;
321
322                 ClipProxy clip = (watched == ui->clip_list->viewport()) ?
323                         cliplist_clips->clip(row) : playlist_clips->clip(row);
324
325                 if (column == in_column) {
326                         int64_t pts = clip->pts_in + wheel->angleDelta().y() * wheel_sensitivity;
327                         pts = std::max<int64_t>(pts, 0);
328                         pts = std::min(pts, clip->pts_out);
329                         clip->pts_in = pts;
330                         preview_single_frame(pts, stream_idx, FIRST_AT_OR_AFTER);
331                 } else if (column == out_column) {
332                         int64_t pts = clip->pts_out + wheel->angleDelta().y() * wheel_sensitivity;
333                         pts = std::max(pts, clip->pts_in);
334                         pts = std::min(pts, current_pts);
335                         clip->pts_out = pts;
336                         preview_single_frame(pts, stream_idx, LAST_BEFORE);
337                 }
338         } else if (event->type() == QEvent::MouseButtonRelease) {
339                 scrubbing = false;
340         }
341         return false;
342 }
343
344 void MainWindow::preview_single_frame(int64_t pts, unsigned stream_idx, MainWindow::Rounding rounding)
345 {
346         if (rounding == LAST_BEFORE) {
347                 lock_guard<mutex> lock(frame_mu);
348                 if (frames[stream_idx].empty()) return;
349                 auto it = lower_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts);
350                 if (it != frames[stream_idx].end()) {
351                         pts = *it;
352                 }
353         } else {
354                 assert(rounding == FIRST_AT_OR_AFTER);
355                 lock_guard<mutex> lock(frame_mu);
356                 if (frames[stream_idx].empty()) return;
357                 auto it = upper_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts - 1);
358                 if (it != frames[stream_idx].end()) {
359                         pts = *it;
360                 }
361         }
362
363         Clip fake_clip;
364         fake_clip.pts_in = pts;
365         fake_clip.pts_out = pts + 1;
366         preview_player->play_clip(fake_clip, stream_idx);
367 }