1 #include "mainwindow.h"
5 #include "post_to_main_thread.h"
6 #include "ui_mainwindow.h"
11 #include <QMouseEvent>
12 #include <QWheelEvent>
17 MainWindow *global_mainwindow = nullptr;
18 ClipList *cliplist_clips;
19 PlayList *playlist_clips;
21 extern int64_t current_pts;
22 extern mutex frame_mu;
23 extern vector<int64_t> frames[MAX_STREAMS];
25 MainWindow::MainWindow()
26 : ui(new Ui::MainWindow)
28 global_mainwindow = this;
31 cliplist_clips = new ClipList();
32 ui->clip_list->setModel(cliplist_clips);
34 playlist_clips = new PlayList();
35 ui->playlist->setModel(playlist_clips);
37 // For scrubbing in the pts columns.
38 ui->clip_list->viewport()->installEventFilter(this);
39 ui->playlist->viewport()->installEventFilter(this);
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);
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);
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);
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);
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);
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); });
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); });
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); });
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); });
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();
86 void MainWindow::cue_in_clicked()
88 if (!cliplist_clips->empty() && cliplist_clips->back()->pts_out < 0) {
89 cliplist_clips->back()->pts_in = current_pts;
93 clip.pts_in = current_pts;
94 cliplist_clips->add_clip(clip);
97 void MainWindow::cue_out_clicked()
99 if (!cliplist_clips->empty()) {
100 cliplist_clips->back()->pts_out = current_pts;
101 // TODO: select the row in the clip list?
105 void MainWindow::queue_clicked()
107 if (cliplist_clips->empty()) {
111 QItemSelectionModel *selected = ui->clip_list->selectionModel();
112 if (!selected->hasSelection()) {
113 Clip clip = *cliplist_clips->back();
115 playlist_clips->add_clip(clip);
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);
128 void MainWindow::preview_clicked()
130 if (cliplist_clips->empty()) return;
132 QItemSelectionModel *selected = ui->clip_list->selectionModel();
133 if (!selected->hasSelection()) {
134 preview_player->play_clip(*cliplist_clips->back(), 0);
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);
146 void MainWindow::preview_angle_clicked(unsigned stream_idx)
148 preview_player->override_angle(stream_idx);
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);
161 void MainWindow::play_clicked()
163 if (playlist_clips->empty()) return;
165 QItemSelectionModel *selected = ui->playlist->selectionModel();
167 if (!selected->hasSelection()) {
170 row = selected->selectedRows(0)[0].row();
173 const Clip &clip = *playlist_clips->clip(row);
174 live_player->play_clip(clip, clip.stream_idx);
175 playlist_clips->set_currently_playing(row);
178 void MainWindow::live_player_clip_done()
180 int row = playlist_clips->get_currently_playing();
181 if (row != -1 && row < int(playlist_clips->size()) - 1) {
183 const Clip &clip = *playlist_clips->clip(row);
184 live_player->play_clip(clip, clip.stream_idx);
185 playlist_clips->set_currently_playing(row);
187 playlist_clips->set_currently_playing(-1);
191 void MainWindow::resizeEvent(QResizeEvent *event)
193 QMainWindow::resizeEvent(event);
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);
200 void MainWindow::relayout()
202 ui->live_display->setMinimumHeight(ui->live_display->width() * 9 / 16);
205 bool MainWindow::eventFilter(QObject *watched, QEvent *event)
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.
211 unsigned stream_idx = ui->preview_display->get_stream_idx();
213 if (event->type() == QEvent::MouseButtonPress) {
214 QMouseEvent *mouse = (QMouseEvent *)event;
216 QTableView *destination;
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;
228 int column = destination->columnAt(mouse->x());
229 int row = destination->rowAt(mouse->y());
230 if (column == -1 || row == -1) return false;
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);
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);
256 scrub_column = column;
257 scrub_x_origin = mouse->x();
259 } else if (event->type() == QEvent::MouseMove) {
261 QMouseEvent *mouse = (QMouseEvent *)event;
262 int offset = mouse->x() - scrub_x_origin;
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;
272 int64_t pts = scrub_pts_origin + adjusted_offset * scrub_sensitivity;
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);
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);
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);
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);
300 return true; // Don't use this mouse movement for selecting things.
302 } else if (event->type() == QEvent::Wheel) {
303 QWheelEvent *wheel = (QWheelEvent *)event;
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);
318 int column = destination->columnAt(wheel->x());
319 int row = destination->rowAt(wheel->y());
320 if (column == -1 || row == -1) return false;
322 ClipProxy clip = (watched == ui->clip_list->viewport()) ?
323 cliplist_clips->clip(row) : playlist_clips->clip(row);
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);
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);
336 preview_single_frame(pts, stream_idx, LAST_BEFORE);
338 } else if (event->type() == QEvent::MouseButtonRelease) {
344 void MainWindow::preview_single_frame(int64_t pts, unsigned stream_idx, MainWindow::Rounding rounding)
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()) {
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()) {
364 fake_clip.pts_in = pts;
365 fake_clip.pts_out = pts + 1;
366 preview_player->play_clip(fake_clip, stream_idx);