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 // For scrubbing in the pts columns.
35 ui->clip_list->viewport()->installEventFilter(this);
37 playlist_clips = new PlayList();
38 ui->playlist->setModel(playlist_clips);
40 // TODO: These are too big for lambdas.
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, []{
44 if (!cliplist_clips->empty() && cliplist_clips->back()->pts_out < 0) {
45 cliplist_clips->back()->pts_in = current_pts;
49 clip.pts_in = current_pts;
50 cliplist_clips->add_clip(clip);
53 QShortcut *cue_out = new QShortcut(QKeySequence(Qt::Key_S), this);
54 connect(cue_out, &QShortcut::activated, ui->cue_out_btn, &QPushButton::click);
55 connect(ui->cue_out_btn, &QPushButton::clicked, []{
56 if (!cliplist_clips->empty()) {
57 cliplist_clips->back()->pts_out = current_pts;
58 // TODO: select the row in the clip list?
62 QShortcut *queue = new QShortcut(QKeySequence(Qt::Key_Q), this);
63 connect(queue, &QShortcut::activated, ui->queue_btn, &QPushButton::click);
64 connect(ui->queue_btn, &QPushButton::clicked, this, &MainWindow::queue_clicked);
66 QShortcut *preview = new QShortcut(QKeySequence(Qt::Key_W), this);
67 connect(preview, &QShortcut::activated, ui->preview_btn, &QPushButton::click);
68 connect(ui->preview_btn, &QPushButton::clicked, this, &MainWindow::preview_clicked);
70 QShortcut *play = new QShortcut(QKeySequence(Qt::Key_Space), this);
71 connect(play, &QShortcut::activated, ui->play_btn, &QPushButton::click);
72 connect(ui->play_btn, &QPushButton::clicked, this, &MainWindow::play_clicked);
74 preview_player = new Player(ui->preview_display);
75 live_player = new Player(ui->live_display);
76 live_player->set_done_callback([this]{
77 post_to_main_thread([this]{
78 live_player_clip_done();
83 void MainWindow::queue_clicked()
85 QItemSelectionModel *selected = ui->clip_list->selectionModel();
86 if (!selected->hasSelection()) {
87 Clip clip = *cliplist_clips->back();
89 playlist_clips->add_clip(clip);
93 QModelIndex index = selected->currentIndex();
94 if (index.column() >= int(ClipList::Column::CAMERA_1) &&
95 index.column() <= int(ClipList::Column::CAMERA_4)) {
96 Clip clip = *cliplist_clips->clip(index.row());
97 clip.stream_idx = index.column() - int(ClipList::Column::CAMERA_1);
98 playlist_clips->add_clip(clip);
102 void MainWindow::preview_clicked()
104 if (cliplist_clips->empty()) return;
106 QItemSelectionModel *selected = ui->clip_list->selectionModel();
107 if (!selected->hasSelection()) {
108 preview_player->play_clip(*cliplist_clips->back(), 0);
112 QModelIndex index = selected->currentIndex();
113 if (index.column() >= int(ClipList::Column::CAMERA_1) &&
114 index.column() <= int(ClipList::Column::CAMERA_4)) {
115 unsigned stream_idx = index.column() - int(ClipList::Column::CAMERA_1);
116 preview_player->play_clip(*cliplist_clips->clip(index.row()), stream_idx);
120 void MainWindow::play_clicked()
122 if (playlist_clips->empty()) return;
124 QItemSelectionModel *selected = ui->playlist->selectionModel();
126 if (!selected->hasSelection()) {
129 row = selected->selectedRows(0)[0].row();
132 const Clip &clip = *playlist_clips->clip(row);
133 live_player->play_clip(clip, clip.stream_idx);
134 playlist_clips->set_currently_playing(row);
137 void MainWindow::live_player_clip_done()
139 int row = playlist_clips->get_currently_playing();
140 if (row != -1 && row < int(playlist_clips->size()) - 1) {
142 const Clip &clip = *playlist_clips->clip(row);
143 live_player->play_clip(clip, clip.stream_idx);
144 playlist_clips->set_currently_playing(row);
146 playlist_clips->set_currently_playing(-1);
150 void MainWindow::resizeEvent(QResizeEvent *event)
152 QMainWindow::resizeEvent(event);
154 // Ask for a relayout, but only after the event loop is done doing relayout
155 // on everything else.
156 QMetaObject::invokeMethod(this, "relayout", Qt::QueuedConnection);
159 void MainWindow::relayout()
161 ui->live_display->setMinimumHeight(ui->live_display->width() * 9 / 16);
164 bool MainWindow::eventFilter(QObject *watched, QEvent *event)
166 constexpr int dead_zone_pixels = 3; // To avoid that simple clicks get misinterpreted.
167 constexpr int scrub_sensitivity = 100; // pts units per pixel.
168 constexpr int wheel_sensitivity = 100; // pts units per degree.
170 if (event->type() == QEvent::MouseButtonPress) {
171 QMouseEvent *mouse = (QMouseEvent *)event;
173 int column = ui->clip_list->columnAt(mouse->x());
174 int row = ui->clip_list->rowAt(mouse->y());
175 if (column == -1 || row == -1) return false;
177 if (ClipList::Column(column) == ClipList::Column::IN) {
180 scrub_column = ClipList::Column::IN;
181 scrub_x_origin = mouse->x();
182 scrub_pts_origin = cliplist_clips->clip(scrub_row)->pts_in;
184 unsigned stream_idx = ui->preview_display->get_stream_idx();
185 preview_single_frame(scrub_pts_origin, stream_idx, FIRST_AT_OR_AFTER);
186 } else if (ClipList::Column(column) == ClipList::Column::OUT) {
189 scrub_column = ClipList::Column::OUT;
190 scrub_x_origin = mouse->x();
191 scrub_pts_origin = cliplist_clips->clip(scrub_row)->pts_out;
193 unsigned stream_idx = ui->preview_display->get_stream_idx();
194 preview_single_frame(scrub_pts_origin, stream_idx, LAST_BEFORE);
196 } else if (event->type() == QEvent::MouseMove) {
198 QMouseEvent *mouse = (QMouseEvent *)event;
199 int offset = mouse->x() - scrub_x_origin;
201 if (offset >= dead_zone_pixels) {
202 adjusted_offset = offset - dead_zone_pixels;
203 } else if (offset < -dead_zone_pixels) {
204 adjusted_offset = offset + dead_zone_pixels;
209 unsigned stream_idx = ui->preview_display->get_stream_idx();
210 int64_t pts = scrub_pts_origin + adjusted_offset * scrub_sensitivity;
212 if (scrub_column == ClipList::Column::IN) {
213 pts = std::max<int64_t>(pts, 0);
214 pts = std::min(pts, cliplist_clips->clip(scrub_row)->pts_out);
215 cliplist_clips->clip(scrub_row)->pts_in = pts;
216 preview_single_frame(pts, stream_idx, FIRST_AT_OR_AFTER);
218 pts = std::max(pts, cliplist_clips->clip(scrub_row)->pts_in);
219 pts = std::min(pts, current_pts);
220 cliplist_clips->clip(scrub_row)->pts_out = pts;
221 preview_single_frame(pts, stream_idx, LAST_BEFORE);
224 return true; // Don't use this mouse movement for selecting things.
226 } else if (event->type() == QEvent::Wheel) {
227 QWheelEvent *wheel = (QWheelEvent *)event;
229 int column = ui->clip_list->columnAt(wheel->x());
230 int row = ui->clip_list->rowAt(wheel->y());
231 if (column == -1 || row == -1) return false;
233 ClipProxy clip = cliplist_clips->clip(scrub_row);
234 unsigned stream_idx = ui->preview_display->get_stream_idx();
236 if (ClipList::Column(column) == ClipList::Column::IN) {
237 int64_t pts = clip->pts_in + wheel->angleDelta().y() * wheel_sensitivity;
238 pts = std::max<int64_t>(pts, 0);
239 pts = std::min(pts, clip->pts_out);
241 preview_single_frame(pts, stream_idx, FIRST_AT_OR_AFTER);
242 } else if (ClipList::Column(column) == ClipList::Column::OUT) {
243 int64_t pts = clip->pts_out + wheel->angleDelta().y() * wheel_sensitivity;
244 pts = std::max(pts, clip->pts_in);
245 pts = std::min(pts, current_pts);
247 preview_single_frame(pts, stream_idx, LAST_BEFORE);
249 } else if (event->type() == QEvent::MouseButtonRelease) {
255 void MainWindow::preview_single_frame(int64_t pts, unsigned stream_idx, MainWindow::Rounding rounding)
257 if (rounding == LAST_BEFORE) {
258 lock_guard<mutex> lock(frame_mu);
259 if (frames[stream_idx].empty()) return;
260 auto it = lower_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts);
261 if (it != frames[stream_idx].end()) {
265 assert(rounding == FIRST_AT_OR_AFTER);
266 lock_guard<mutex> lock(frame_mu);
267 if (frames[stream_idx].empty()) return;
268 auto it = upper_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts - 1);
269 if (it != frames[stream_idx].end()) {
275 fake_clip.pts_in = pts;
276 fake_clip.pts_out = pts + 1;
277 preview_player->play_clip(fake_clip, stream_idx);