]> git.sesse.net Git - nageru/blob - mainwindow.cpp
Add functionality for scrubbing in/out points in the clip list.
[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         // For scrubbing in the pts columns.
35         ui->clip_list->viewport()->installEventFilter(this);
36
37         playlist_clips = new PlayList();
38         ui->playlist->setModel(playlist_clips);
39
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;
46                         return;
47                 }
48                 Clip clip;
49                 clip.pts_in = current_pts;
50                 cliplist_clips->add_clip(clip);
51         });
52
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?
59                 }
60         });
61
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);
65
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);
69
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);
73
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();
79                 });
80         });
81 }
82
83 void MainWindow::queue_clicked()
84 {
85         QItemSelectionModel *selected = ui->clip_list->selectionModel();
86         if (!selected->hasSelection()) {
87                 Clip clip = *cliplist_clips->back();
88                 clip.stream_idx = 0;
89                 playlist_clips->add_clip(clip);
90                 return;
91         }
92
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);
99         }
100 }
101
102 void MainWindow::preview_clicked()
103 {
104         if (cliplist_clips->empty()) return;
105
106         QItemSelectionModel *selected = ui->clip_list->selectionModel();
107         if (!selected->hasSelection()) {
108                 preview_player->play_clip(*cliplist_clips->back(), 0);
109                 return;
110         }
111
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);
117         }
118 }
119
120 void MainWindow::play_clicked()
121 {
122         if (playlist_clips->empty()) return;
123
124         QItemSelectionModel *selected = ui->playlist->selectionModel();
125         int row;
126         if (!selected->hasSelection()) {
127                 row = 0;
128         } else {
129                 row = selected->selectedRows(0)[0].row();
130         }
131
132         const Clip &clip = *playlist_clips->clip(row);
133         live_player->play_clip(clip, clip.stream_idx);
134         playlist_clips->set_currently_playing(row);
135 }
136
137 void MainWindow::live_player_clip_done()
138 {
139         int row = playlist_clips->get_currently_playing();
140         if (row != -1 && row < int(playlist_clips->size()) - 1) {
141                 ++row;
142                 const Clip &clip = *playlist_clips->clip(row);
143                 live_player->play_clip(clip, clip.stream_idx);
144                 playlist_clips->set_currently_playing(row);
145         } else {
146                 playlist_clips->set_currently_playing(-1);
147         }
148 }
149
150 void MainWindow::resizeEvent(QResizeEvent *event)
151 {
152         QMainWindow::resizeEvent(event);
153
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);
157 }
158
159 void MainWindow::relayout()
160 {
161         ui->live_display->setMinimumHeight(ui->live_display->width() * 9 / 16);
162 }
163
164 bool MainWindow::eventFilter(QObject *watched, QEvent *event)
165 {
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.
169
170         if (event->type() == QEvent::MouseButtonPress) {
171                 QMouseEvent *mouse = (QMouseEvent *)event;
172
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;
176
177                 if (ClipList::Column(column) == ClipList::Column::IN) {
178                         scrubbing = true;
179                         scrub_row = row;
180                         scrub_column = ClipList::Column::IN;
181                         scrub_x_origin = mouse->x();
182                         scrub_pts_origin = cliplist_clips->clip(scrub_row)->pts_in;
183
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) {
187                         scrubbing = true;
188                         scrub_row = row;
189                         scrub_column = ClipList::Column::OUT;
190                         scrub_x_origin = mouse->x();
191                         scrub_pts_origin = cliplist_clips->clip(scrub_row)->pts_out;
192
193                         unsigned stream_idx = ui->preview_display->get_stream_idx();
194                         preview_single_frame(scrub_pts_origin, stream_idx, LAST_BEFORE);
195                 }
196         } else if (event->type() == QEvent::MouseMove) {
197                 if (scrubbing) {
198                         QMouseEvent *mouse = (QMouseEvent *)event;
199                         int offset = mouse->x() - scrub_x_origin;
200                         int adjusted_offset;
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;
205                         } else {
206                                 adjusted_offset = 0;
207                         }
208
209                         unsigned stream_idx = ui->preview_display->get_stream_idx();
210                         int64_t pts = scrub_pts_origin + adjusted_offset * scrub_sensitivity;
211
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);
217                         } else {
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);
222                         }
223
224                         return true;  // Don't use this mouse movement for selecting things.
225                 }
226         } else if (event->type() == QEvent::Wheel) {
227                 QWheelEvent *wheel = (QWheelEvent *)event;
228
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;
232
233                 ClipProxy clip = cliplist_clips->clip(scrub_row);
234                 unsigned stream_idx = ui->preview_display->get_stream_idx();
235
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);
240                         clip->pts_in = pts;
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);
246                         clip->pts_out = pts;
247                         preview_single_frame(pts, stream_idx, LAST_BEFORE);
248                 }
249         } else if (event->type() == QEvent::MouseButtonRelease) {
250                 scrubbing = false;
251         }
252         return false;
253 }
254
255 void MainWindow::preview_single_frame(int64_t pts, unsigned stream_idx, MainWindow::Rounding rounding)
256 {
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()) {
262                         pts = *it;
263                 }
264         } else {
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()) {
270                         pts = *it;
271                 }
272         }
273
274         Clip fake_clip;
275         fake_clip.pts_in = pts;
276         fake_clip.pts_out = pts + 1;
277         preview_player->play_clip(fake_clip, stream_idx);
278 }