X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=futatabi%2Fmainwindow.cpp;h=0bc11d810e0c8e411d65a655987f387874b37a07;hb=refs%2Fheads%2Fmaster;hp=a5592930e2a3217df0b980db3d65165a362c4781;hpb=3974e42a86665521c477ab0c1dfc0af1a28bede1;p=nageru diff --git a/futatabi/mainwindow.cpp b/futatabi/mainwindow.cpp index a559293..29eeb75 100644 --- a/futatabi/mainwindow.cpp +++ b/futatabi/mainwindow.cpp @@ -7,6 +7,7 @@ #include "player.h" #include "futatabi_midi_mapping.pb.h" #include "midi_mapping_dialog.h" +#include "pbo_pool.h" #include "shared/aboutdialog.h" #include "shared/disk_space_estimator.h" #include "shared/post_to_main_thread.h" @@ -205,6 +206,10 @@ MainWindow::MainWindow() connect(play, &QShortcut::activated, ui->play_btn, &QPushButton::click); connect(ui->play_btn, &QPushButton::clicked, this, &MainWindow::play_clicked); + QShortcut *next = new QShortcut(QKeySequence(Qt::Key_N), this); + connect(next, &QShortcut::activated, ui->next_btn, &QPushButton::click); + connect(ui->next_btn, &QPushButton::clicked, this, &MainWindow::next_clicked); + connect(ui->stop_btn, &QPushButton::clicked, this, &MainWindow::stop_clicked); ui->stop_btn->setEnabled(false); @@ -242,7 +247,7 @@ MainWindow::MainWindow() live_player_done(); }); }); - live_player->set_progress_callback([this](const map &progress, double time_remaining) { + live_player->set_progress_callback([this](const map &progress, TimeRemaining time_remaining) { post_to_main_thread([this, progress, time_remaining] { live_player_clip_progress(progress, time_remaining); }); @@ -285,7 +290,7 @@ MainWindow::MainWindow() if (!load_midi_mapping_from_file(global_flags.midi_mapping_filename, &midi_mapping)) { fprintf(stderr, "Couldn't load MIDI mapping '%s'; exiting.\n", global_flags.midi_mapping_filename.c_str()); - exit(1); + abort(); } midi_mapper.set_midi_mapping(midi_mapping); } @@ -297,9 +302,17 @@ void MainWindow::change_num_cameras() { assert(num_cameras >= displays.size()); // We only add, never remove. + // Make new entries to hide the displays. + for (unsigned i = displays.size(); i < num_cameras; ++i) { + char title[256]; + snprintf(title, sizeof(title), "Camera %u", i + 1); + QAction *hide_action = ui->hide_camera_menu->addAction(title); + hide_action->setCheckable(true); + hide_action->setChecked(false); + connect(hide_action, &QAction::toggled, bind(&MainWindow::hide_camera_toggled, this, i, _1)); + } + // Make new display rows. - unsigned display_rows = (num_cameras + 1) / 2; - ui->video_displays->setStretch(1, display_rows); for (unsigned i = displays.size(); i < num_cameras; ++i) { QFrame *frame = new QFrame(this); frame->setAutoFillBackground(true); @@ -312,15 +325,18 @@ void MainWindow::change_num_cameras() display->setAutoFillBackground(true); layout->addWidget(display); - ui->input_displays->addWidget(frame, i / 2, i % 2); - display->set_overlay(to_string(i + 1)); + if (global_flags.source_labels.count(i + 1)) { + display->set_overlay(global_flags.source_labels[i + 1]); + } else { + display->set_overlay(to_string(i + 1)); + } QPushButton *preview_btn = new QPushButton(this); preview_btn->setMaximumSize(20, 17); preview_btn->setText(QString::fromStdString(to_string(i + 1))); ui->preview_layout->addWidget(preview_btn); - displays.emplace_back(FrameAndDisplay{ frame, display, preview_btn }); + displays.emplace_back(FrameAndDisplay{ frame, display, preview_btn, /*hidden=*/false }); connect(display, &JPEGFrameView::clicked, preview_btn, &QPushButton::click); QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_1 + i), this); @@ -328,6 +344,7 @@ void MainWindow::change_num_cameras() connect(preview_btn, &QPushButton::clicked, [this, i] { preview_angle_clicked(i); }); } + relayout_displays(); cliplist_clips->change_num_cameras(num_cameras); playlist_clips->change_num_cameras(num_cameras); @@ -335,6 +352,28 @@ void MainWindow::change_num_cameras() QMetaObject::invokeMethod(this, "relayout", Qt::QueuedConnection); } +void MainWindow::relayout_displays() +{ + while (ui->input_displays->count() > 0) { + QLayoutItem *item = ui->input_displays->takeAt(0); + ui->input_displays->removeWidget(item->widget()); + } + + unsigned cell_idx = 0; + for (unsigned i = 0; i < displays.size(); ++i) { + if (displays[i].hidden) { + displays[i].frame->setVisible(false); + } else { + displays[i].frame->setVisible(true); + ui->input_displays->addWidget(displays[i].frame, cell_idx / 2, cell_idx % 2); + ++cell_idx; + } + } + ui->video_displays->setStretch(1, (cell_idx + 1) / 2); + + QMetaObject::invokeMethod(this, "relayout", Qt::QueuedConnection); +} + MainWindow::~MainWindow() { // We don't have a context to release Player's OpenGL resources in here, @@ -354,6 +393,10 @@ void MainWindow::cue_in_clicked() playlist_selection_changed(); } + // Show the clip in the preview. + unsigned stream_idx = ui->preview_display->get_stream_idx(); + preview_single_frame(cliplist_clips->mutable_back()->pts_in, stream_idx, FIRST_AT_OR_AFTER); + // Select the item so that we can jog it. ui->clip_list->setFocus(); QModelIndex index = cliplist_clips->index(cliplist_clips->size() - 1, int(ClipList::Column::IN)); @@ -370,6 +413,10 @@ void MainWindow::cue_out_clicked() cliplist_clips->mutable_back()->pts_out = current_pts + lrint(global_flags.cue_out_point_padding_seconds * TIMEBASE); + // Show the clip in the preview. (TODO: This won't take padding into account.) + unsigned stream_idx = ui->preview_display->get_stream_idx(); + preview_single_frame(cliplist_clips->mutable_back()->pts_out, stream_idx, LAST_BEFORE); + // Select the item so that we can jog it. ui->clip_list->setFocus(); QModelIndex index = cliplist_clips->index(cliplist_clips->size() - 1, int(ClipList::Column::OUT)); @@ -390,11 +437,9 @@ void MainWindow::queue_clicked() if (!selected->hasSelection()) { Clip clip = *cliplist_clips->back(); clip.stream_idx = 0; - if (clip.pts_out != -1) { - playlist_clips->add_clip(clip); - playlist_selection_changed(); - ui->playlist->scrollToBottom(); - } + playlist_clips->add_clip(clip); + playlist_selection_changed(); + ui->playlist->scrollToBottom(); return; } @@ -406,15 +451,13 @@ void MainWindow::queue_clicked() clip.stream_idx = ui->preview_display->get_stream_idx(); } - if (clip.pts_out != -1) { - playlist_clips->add_clip(clip); - playlist_selection_changed(); - ui->playlist->scrollToBottom(); - if (!ui->playlist->selectionModel()->hasSelection()) { - // TODO: Figure out why this doesn't always seem to actually select the row. - QModelIndex bottom = playlist_clips->index(playlist_clips->size() - 1, 0); - ui->playlist->setCurrentIndex(bottom); - } + playlist_clips->add_clip(clip); + playlist_selection_changed(); + ui->playlist->scrollToBottom(); + if (!ui->playlist->selectionModel()->hasSelection()) { + // TODO: Figure out why this doesn't always seem to actually select the row. + QModelIndex bottom = playlist_clips->index(playlist_clips->size() - 1, 0); + ui->playlist->setCurrentIndex(bottom); } } @@ -453,6 +496,9 @@ void MainWindow::preview_clicked() } else { clip.stream_idx = ui->preview_display->get_stream_idx(); } + if (clip.pts_out == -1) { + clip.pts_out = clip.pts_in + int64_t(TIMEBASE) * 86400 * 7; // One week; effectively infinite, but without overflow issues. + } preview_player->play(clip); preview_playing = true; enable_or_disable_preview_button(); @@ -588,10 +634,7 @@ void MainWindow::defer_timer_expired() void MainWindow::content_changed() { // If we are playing, update the part of the playlist that's not playing yet. - vector clips; - for (unsigned row = 0; row < playlist_clips->size(); ++row) { - clips.emplace_back(*playlist_clips->clip_with_id(row)); - } + vector clips = get_playlist(0, playlist_clips->size()); live_player->splice_play(clips); // Serialize the state. @@ -652,10 +695,7 @@ void MainWindow::play_clicked() } unsigned start_row = selected->selectedRows(0)[0].row(); - vector clips; - for (unsigned row = start_row; row < playlist_clips->size(); ++row) { - clips.emplace_back(*playlist_clips->clip_with_id(row)); - } + vector clips = get_playlist(start_row, playlist_clips->size()); live_player->play(clips); playlist_clips->set_progress({ { start_row, 0.0f } }); ui->playlist->selectionModel()->clear(); @@ -663,6 +703,11 @@ void MainWindow::play_clicked() playlist_selection_changed(); } +void MainWindow::next_clicked() +{ + live_player->skip_to_next(); +} + void MainWindow::stop_clicked() { Clip fake_clip; @@ -704,7 +749,7 @@ void MainWindow::live_player_done() playlist_selection_changed(); } -void MainWindow::live_player_clip_progress(const map &progress, double time_remaining) +void MainWindow::live_player_clip_progress(const map &progress, TimeRemaining time_remaining) { playlist_clips->set_progress(progress); set_output_status(format_duration(time_remaining) + " left"); @@ -733,6 +778,7 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) { enable_or_disable_preview_button(); + playlist_selection_changed(); hidden_jog_column = -1; } @@ -801,6 +847,11 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) if (mouse->modifiers() & Qt::KeyboardModifier::ShiftModifier) { scrub_sensitivity *= 10; wheel_sensitivity *= 10; + if (mouse->modifiers() & Qt::KeyboardModifier::ControlModifier) { + // Ctrl+Shift is a super-modifier, meant only for things like “go back two hours”. + scrub_sensitivity *= 100; + wheel_sensitivity *= 100; + } } if (mouse->modifiers() & Qt::KeyboardModifier::AltModifier) { // Note: Shift + Alt cancel each other out. scrub_sensitivity /= 10; @@ -856,6 +907,11 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) if (wheel->modifiers() & Qt::KeyboardModifier::ShiftModifier) { scrub_sensitivity *= 10; wheel_sensitivity *= 10; + if (wheel->modifiers() & Qt::KeyboardModifier::ControlModifier) { + // Ctrl+Shift is a super-modifier, meant only for things like “go back two hours”. + scrub_sensitivity *= 100; + wheel_sensitivity *= 100; + } } if (wheel->modifiers() & Qt::KeyboardModifier::AltModifier) { // Note: Shift + Alt cancel each other out. scrub_sensitivity /= 10; @@ -872,7 +928,7 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) } else if (watched == ui->playlist->viewport()) { destination = ui->playlist; jog_destination = JOG_PLAYLIST; - if (destination->columnAt(wheel->x()) != int(PlayList::Column::CAMERA)) { + if (destination->columnAt(wheel->position().x()) != int(PlayList::Column::CAMERA)) { last_mousewheel_camera_row = -1; } } else { @@ -880,8 +936,8 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) return false; } - int column = destination->columnAt(wheel->x()); - int row = destination->rowAt(wheel->y()); + int column = destination->columnAt(wheel->position().x()); + int row = destination->rowAt(wheel->position().y()); if (column == -1 || row == -1) return false; @@ -941,6 +997,19 @@ void MainWindow::playlist_selection_changed() any_selected && selected->selectedRows().back().row() < int(playlist_clips->size()) - 1); ui->play_btn->setEnabled(any_selected); + ui->next_btn->setEnabled(ui->stop_btn->isEnabled()); // TODO: Perhaps not if we're on the last clip? + midi_mapper.set_next_ready(ui->next_btn->isEnabled() ? MIDIMapper::On : MIDIMapper::Off); + + // NOTE: The hidden button is still reachable by keyboard or MIDI. + if (any_selected) { + ui->play_btn->setVisible(true); + } else if (ui->stop_btn->isEnabled()) { // Playing. + ui->play_btn->setVisible(false); + } else { + ui->play_btn->setVisible(true); + } + ui->next_btn->setVisible(!ui->play_btn->isVisible()); + if (ui->stop_btn->isEnabled()) { // Playing. midi_mapper.set_play_enabled(MIDIMapper::On); } else if (any_selected) { @@ -956,7 +1025,7 @@ void MainWindow::playlist_selection_changed() for (size_t row = selected->selectedRows().front().row(); row < playlist_clips->size(); ++row) { clips.emplace_back(*playlist_clips->clip_with_id(row)); } - double remaining = compute_total_time(clips); + TimeRemaining remaining = compute_total_time(clips); set_output_status(format_duration(remaining) + " ready"); } } @@ -980,6 +1049,19 @@ void MainWindow::clip_list_selection_changed(const QModelIndex ¤t, const Q enable_or_disable_queue_button(); } +vector MainWindow::get_playlist(size_t start_row, size_t end_row) +{ + vector clips; + for (unsigned row = start_row; row < end_row; ++row) { + ClipWithID clip = *playlist_clips->clip_with_id(row); + if (clip.clip.pts_out == -1) { + clip.clip.pts_out = clip.clip.pts_in + int64_t(TIMEBASE) * 86400 * 7; // One week; effectively infinite, but without overflow issues. + } + clips.emplace_back(clip); + } + return clips; +} + void MainWindow::report_disk_space(off_t free_bytes, double estimated_seconds_left) { char time_str[256]; @@ -1174,6 +1256,12 @@ void MainWindow::out_padding_toggled(double seconds, bool checked) save_settings(); } +void MainWindow::hide_camera_toggled(unsigned camera_idx, bool checked) +{ + displays[camera_idx].hidden = checked; + relayout_displays(); +} + void MainWindow::highlight_camera_input(int stream_idx) { for (unsigned i = 0; i < num_cameras; ++i) { @@ -1220,15 +1308,7 @@ void MainWindow::enable_or_disable_queue_button() if (cliplist_clips->empty()) { enabled = false; } else { - QItemSelectionModel *selected = ui->clip_list->selectionModel(); - if (!selected->hasSelection()) { - Clip clip = *cliplist_clips->back(); - enabled = clip.pts_out != -1; - } else { - QModelIndex index = selected->currentIndex(); - Clip clip = *cliplist_clips->clip(index.row()); - enabled = clip.pts_out != -1; - } + enabled = true; } ui->queue_btn->setEnabled(enabled); @@ -1288,6 +1368,13 @@ void MainWindow::play() }); } +void MainWindow::next() +{ + post_to_main_thread([this] { + next_clicked(); + }); +} + void MainWindow::toggle_lock() { post_to_main_thread([this] {