+ if (!filename.endsWith(".mkv")) {
+ filename += ".mkv";
+ }
+ export_multitrack_clip(filename.toStdString(), clip);
+}
+
+void MainWindow::export_playlist_clip_interpolated_triggered()
+{
+ QItemSelectionModel *selected = ui->playlist->selectionModel();
+ if (!selected->hasSelection()) {
+ QMessageBox msgbox;
+ msgbox.setText("No clip selected in the playlist. Select one and try exporting again.");
+ msgbox.exec();
+ return;
+ }
+
+ QString filename = QFileDialog::getSaveFileName(this,
+ "Export interpolated clip", QString(), tr("Matroska video files (*.mkv)"));
+ if (filename.isNull()) {
+ // Cancel.
+ return;
+ }
+ if (!filename.endsWith(".mkv")) {
+ filename += ".mkv";
+ }
+
+ vector<Clip> clips;
+ QModelIndexList rows = selected->selectedRows();
+ for (QModelIndex index : rows) {
+ clips.push_back(*playlist_clips->clip(index.row()));
+ }
+ export_interpolated_clip(filename.toStdString(), clips);
+}
+
+void MainWindow::manual_triggered()
+{
+ if (!QDesktopServices::openUrl(QUrl("https://nageru.sesse.net/doc/"))) {
+ QMessageBox msgbox;
+ msgbox.setText("Could not launch manual in web browser.\nPlease see https://nageru.sesse.net/doc/ manually.");
+ msgbox.exec();
+ }
+}
+
+void MainWindow::about_triggered()
+{
+ AboutDialog("Futatabi", "Multicamera slow motion video server").exec();
+}
+
+void MainWindow::undo_triggered()
+{
+ // Finish any deferred action.
+ if (defer_timeout->isActive()) {
+ defer_timeout->stop();
+ state_changed(deferred_state);
+ }
+
+ StateProto redo_state;
+ *redo_state.mutable_clip_list() = cliplist_clips->serialize();
+ *redo_state.mutable_play_list() = playlist_clips->serialize();
+ redo_stack.push_back(std::move(redo_state));
+ ui->redo_action->setEnabled(true);
+
+ assert(undo_stack.size() > 1);
+
+ // Pop off the current state, which is always at the top of the stack.
+ undo_stack.pop_back();
+
+ StateProto state = undo_stack.back();
+ ui->undo_action->setEnabled(undo_stack.size() > 1);
+
+ replace_model(ui->clip_list, &cliplist_clips, new ClipList(state.clip_list()));
+ replace_model(ui->playlist, &playlist_clips, new PlayList(state.play_list()));
+
+ db.store_state(state);
+}
+
+void MainWindow::redo_triggered()
+{
+ assert(!redo_stack.empty());
+
+ ui->undo_action->setEnabled(true);
+ ui->redo_action->setEnabled(true);
+
+ undo_stack.push_back(std::move(redo_stack.back()));
+ redo_stack.pop_back();
+ ui->undo_action->setEnabled(true);
+ ui->redo_action->setEnabled(!redo_stack.empty());
+
+ const StateProto &state = undo_stack.back();
+ replace_model(ui->clip_list, &cliplist_clips, new ClipList(state.clip_list()));
+ replace_model(ui->playlist, &playlist_clips, new PlayList(state.play_list()));
+
+ db.store_state(state);
+}
+
+void MainWindow::quality_toggled(int quality, bool checked)
+{
+ if (!checked) {
+ return;
+ }
+ global_flags.interpolation_quality = quality;
+ if (quality != 0 && // Turning interpolation off is always possible.
+ quality != flow_initialized_interpolation_quality) {
+ QMessageBox msgbox;
+ msgbox.setText(QString::fromStdString(
+ "The interpolation quality for the main output cannot be changed at runtime, "
+ "except being turned completely off; it will take effect for exported files "
+ "only until next restart. The live output quality thus remains at " +
+ to_string(flow_initialized_interpolation_quality) + "."));
+ msgbox.exec();
+ }
+
+ save_settings();
+}
+
+void MainWindow::padding_toggled(double seconds, bool checked)
+{
+ if (!checked) {
+ return;
+ }
+ global_flags.cue_point_padding_seconds = seconds;
+ save_settings();
+}
+
+void MainWindow::highlight_camera_input(int stream_idx)
+{
+ for (unsigned i = 0; i < num_cameras; ++i) {
+ if (unsigned(stream_idx) == i) {
+ displays[i].frame->setStyleSheet("background: rgb(0,255,0)");
+ } else {
+ displays[i].frame->setStyleSheet("");
+ }
+ }
+ midi_mapper.highlight_camera_input(stream_idx);
+}
+
+void MainWindow::enable_or_disable_preview_button()
+{
+ // Follows the logic in preview_clicked().
+
+ if (ui->playlist->hasFocus()) {
+ // Allow the playlist as preview iff it has focus and something is selected.
+ // TODO: Is this part really relevant?
+ QItemSelectionModel *selected = ui->playlist->selectionModel();
+ if (selected->hasSelection()) {
+ ui->preview_btn->setEnabled(true);
+ midi_mapper.set_preview_enabled(true);
+ return;
+ }