diff --git a/app/widget/viewer/CMakeLists.txt b/app/widget/viewer/CMakeLists.txt index 44bcbbb54..d010f11d8 100644 --- a/app/widget/viewer/CMakeLists.txt +++ b/app/widget/viewer/CMakeLists.txt @@ -28,5 +28,7 @@ set(OLIVE_SOURCES widget/viewer/viewerglwidget.cpp widget/viewer/viewersizer.h widget/viewer/viewersizer.cpp + widget/viewer/viewerwindow.h + widget/viewer/viewerwindow.cpp PARENT_SCOPE ) diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp index 706e63dc5..b1a8821d1 100644 --- a/app/widget/viewer/footageviewer.cpp +++ b/app/widget/viewer/footageviewer.cpp @@ -13,7 +13,7 @@ FootageViewerWidget::FootageViewerWidget(QWidget *parent) : audio_node_ = new AudioInput(); viewer_node_ = new ViewerOutput(); - connect(gl_widget_, &ViewerGLWidget::DragStarted, this, &FootageViewerWidget::StartFootageDrag); + connect(main_gl_widget(), &ViewerGLWidget::DragStarted, this, &FootageViewerWidget::StartFootageDrag); controls_->SetAudioVideoDragButtonsVisible(true); connect(controls_, &PlaybackControls::VideoPressed, this, &FootageViewerWidget::StartVideoDrag); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index d1f855a2a..a1e8e017e 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -21,8 +21,10 @@ #include "viewer.h" #include +#include #include #include +#include #include #include @@ -55,11 +57,12 @@ ViewerWidget::ViewerWidget(QWidget *parent) : sizer_ = new ViewerSizer(); stack_->addWidget(sizer_); - gl_widget_ = new ViewerGLWidget(); - connect(gl_widget_, &ViewerGLWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); - connect(gl_widget_, &ViewerGLWidget::CursorColor, this, &ViewerWidget::CursorColor); - connect(sizer_, &ViewerSizer::RequestMatrix, gl_widget_, &ViewerGLWidget::SetMatrix); - sizer_->SetWidget(gl_widget_); + ViewerGLWidget* main_widget = new ViewerGLWidget(); + connect(main_widget, &ViewerGLWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); + connect(main_widget, &ViewerGLWidget::CursorColor, this, &ViewerWidget::CursorColor); + connect(sizer_, &ViewerSizer::RequestMatrix, main_widget, &ViewerGLWidget::SetMatrix); + sizer_->SetWidget(main_widget); + gl_widgets_.append(main_widget); // Create waveform view when audio is connected and video isn't waveform_view_ = new AudioWaveformView(); @@ -145,12 +148,18 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) SizeChangedSlot(n->video_params().width(), n->video_params().height()); LengthChangedSlot(n->Length()); + ColorManager* using_manager; if (override_color_manager_) { - gl_widget_->ConnectColorManager(override_color_manager_); + using_manager = override_color_manager_; } else if (n->parent()) { - gl_widget_->ConnectColorManager(static_cast(n->parent())->project()->color_manager()); + using_manager = static_cast(n->parent())->project()->color_manager(); } else { qWarning() << "Failed to find a suitable color manager for the connected viewer node"; + using_manager = nullptr; + } + + foreach (ViewerGLWidget* glw, gl_widgets_) { + glw->ConnectColorManager(using_manager); } divider_ = CalculateDivider(); @@ -181,7 +190,9 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) // Effectively disables the viewer and clears the state SizeChangedSlot(0, 0); - gl_widget_->DisconnectColorManager(); + foreach (ViewerGLWidget* glw, gl_widgets_) { + glw->DisconnectColorManager(); + } waveform_view_->ConnectTimelinePoints(nullptr); } @@ -213,6 +224,16 @@ void ViewerWidget::resizeEvent(QResizeEvent *event) UpdateMinimumScale(); } +const QList &ViewerWidget::gl_widgets() const +{ + return gl_widgets_; +} + +ViewerGLWidget *ViewerWidget::main_gl_widget() const +{ + return gl_widgets_.first(); +} + void ViewerWidget::TogglePlayPause() { if (IsPlaying()) { @@ -249,7 +270,9 @@ void ViewerWidget::SetOverrideSize(int width, int height) void ViewerWidget::SetMatrix(const QMatrix4x4 &mat) { - gl_widget_->SetMatrix(mat); + foreach (ViewerGLWidget* glw, gl_widgets_) { + glw->SetMatrix(mat); + } } VideoRenderBackend *ViewerWidget::video_renderer() const @@ -260,12 +283,12 @@ VideoRenderBackend *ViewerWidget::video_renderer() const void ViewerWidget::UpdateTextureFromNode(const rational& time) { if (!GetConnectedNode() || time >= GetConnectedNode()->Length()) { - gl_widget_->SetImage(QString()); + main_gl_widget()->SetImage(QString()); } else { QString frame_fn = video_renderer_->GetCachedFrame(time); if (!frame_fn.isEmpty()) { - gl_widget_->SetImage(frame_fn); + main_gl_widget()->SetImage(frame_fn); } } } @@ -294,7 +317,7 @@ void ViewerWidget::PlayInternal(int speed) controls_->ShowPauseButton(); if (stack_->currentWidget() == sizer_) { - connect(gl_widget_, &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); + connect(main_gl_widget(), &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); } else { connect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate); } @@ -325,7 +348,7 @@ int ViewerWidget::CalculateDivider() { if (GetConnectedNode() && Config::Current()["AutoSelectDivider"].toBool()) { int long_side_of_video = qMax(GetConnectedNode()->video_params().width(), GetConnectedNode()->video_params().height()); - int long_side_of_widget = qMax(gl_widget_->width(), gl_widget_->height()); + int long_side_of_widget = qMax(main_gl_widget()->width(), main_gl_widget()->height()); return qMax(1, long_side_of_video / long_side_of_widget); } @@ -356,6 +379,35 @@ void ViewerWidget::UpdateStack() } } +void ViewerWidget::SetFullScreenDisplay(QAction *action) +{ + QScreen* target = QGuiApplication::screens().at(action->data().toInt()); + + ViewerWindow* vw = new ViewerWindow(this); + + vw->showFullScreen(); + vw->setGeometry(target->geometry()); + vw->gl_widget()->ConnectColorManager(main_gl_widget()->color_manager()); + main_gl_widget()->ConnectSibling(vw->gl_widget()); + connect(vw, &ViewerWindow::destroyed, this, &ViewerWidget::WindowAboutToClose); + connect(vw->gl_widget(), &ViewerGLWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); + + if (GetConnectedNode()) { + vw->SetResolution(GetConnectedNode()->video_params().width(), GetConnectedNode()->video_params().height()); + } + + windows_.append(vw); + gl_widgets_.append(vw->gl_widget()); +} + +void ViewerWidget::WindowAboutToClose() +{ + ViewerWindow* vw = static_cast(sender()); + + windows_.removeAll(vw); + gl_widgets_.removeAll(vw->gl_widget()); +} + void ViewerWidget::UpdateRendererParameters() { if (!GetConnectedNode()) { @@ -385,40 +437,42 @@ void ViewerWidget::UpdateRendererParameters() void ViewerWidget::ShowContextMenu(const QPoint &pos) { - QMenu menu(this); + QMenu menu(static_cast(sender())); + + context_menu_widget_ = static_cast(sender()); // Color options - if (gl_widget_->color_manager() && color_menu_enabled_) { - QStringList displays = gl_widget_->color_manager()->ListAvailableDisplays(); + if (context_menu_widget_->color_manager() && color_menu_enabled_) { + QStringList displays = context_menu_widget_->color_manager()->ListAvailableDisplays(); QMenu* ocio_display_menu = menu.addMenu(tr("Display")); - connect(ocio_display_menu, &QMenu::triggered, this, &ViewerWidget::ColorDisplayChanged); + connect(ocio_display_menu, &QMenu::triggered, this, &ViewerWidget::ContextMenuOCIODisplay); foreach (const QString& d, displays) { QAction* action = ocio_display_menu->addAction(d); action->setCheckable(true); - action->setChecked(gl_widget_->ocio_display() == d); + action->setChecked(context_menu_widget_->ocio_display() == d); action->setData(d); } - QStringList views = gl_widget_->color_manager()->ListAvailableViews(gl_widget_->ocio_display()); + QStringList views = context_menu_widget_->color_manager()->ListAvailableViews(context_menu_widget_->ocio_display()); QMenu* ocio_view_menu = menu.addMenu(tr("View")); - connect(ocio_view_menu, &QMenu::triggered, this, &ViewerWidget::ColorViewChanged); + connect(ocio_view_menu, &QMenu::triggered, this, &ViewerWidget::ContextMenuOCIOView); foreach (const QString& v, views) { QAction* action = ocio_view_menu->addAction(v); action->setCheckable(true); - action->setChecked(gl_widget_->ocio_view() == v); + action->setChecked(context_menu_widget_->ocio_view() == v); action->setData(v); } - QStringList looks = gl_widget_->color_manager()->ListAvailableLooks(); + QStringList looks = context_menu_widget_->color_manager()->ListAvailableLooks(); QMenu* ocio_look_menu = menu.addMenu(tr("Look")); - connect(ocio_look_menu, &QMenu::triggered, this, &ViewerWidget::ColorLookChanged); + connect(ocio_look_menu, &QMenu::triggered, this, &ViewerWidget::ContextMenuOCIOLook); QAction* no_look_action = ocio_look_menu->addAction(tr("(None)")); no_look_action->setCheckable(true); - no_look_action->setChecked(gl_widget_->ocio_look().isEmpty()); + no_look_action->setChecked(context_menu_widget_->ocio_look().isEmpty()); foreach (const QString& l, looks) { QAction* action = ocio_look_menu->addAction(l); action->setCheckable(true); - action->setChecked(gl_widget_->ocio_look() == l); + action->setChecked(context_menu_widget_->ocio_look() == l); action->setData(l); } @@ -434,6 +488,13 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) } connect(playback_resolution_menu, &QMenu::triggered, this, &ViewerWidget::SetDividerFromMenu); + foreach (QAction* a, playback_resolution_menu->actions()) { + a->setCheckable(true); + if (a->data() == divider_) { + a->setChecked(true); + } + } + // Viewer Zoom Level QMenu* zoom_menu = menu.addMenu(tr("Zoom")); int zoom_levels[] = {10, 25, 50, 75, 100, 150, 200, 400}; @@ -443,12 +504,18 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) } connect(zoom_menu, &QMenu::triggered, this, &ViewerWidget::SetZoomFromMenu); - foreach (QAction* a, playback_resolution_menu->actions()) { - a->setCheckable(true); - if (a->data() == divider_) { - a->setChecked(true); - } + // Full Screen Menu + QMenu* full_screen_menu = menu.addMenu(tr("Full Screen")); + for (int i=0;iaddAction(tr("Screen %1: %2x%3").arg(QString::number(i), + QString::number(s->size().width()), + QString::number(s->size().height()))); + + a->setData(i); } + connect(full_screen_menu, &QMenu::triggered, this, &ViewerWidget::SetFullScreenDisplay); menu.exec(static_cast(sender())->mapToGlobal(pos)); } @@ -466,7 +533,7 @@ void ViewerWidget::Pause() controls_->ShowPlayButton(); if (stack_->currentWidget() == sizer_) { - disconnect(gl_widget_, &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); + disconnect(main_gl_widget(), &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); } else { disconnect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate); } @@ -514,27 +581,49 @@ void ViewerWidget::ShuttleRight() void ViewerWidget::SetOCIOParameters(const QString &display, const QString &view, const QString &look) { - gl_widget_->SetOCIOParameters(display, view, look); + SetOCIOParameters(display, view, look, main_gl_widget()); +} + +void ViewerWidget::SetOCIOParameters(const QString &display, const QString &view, const QString &look, ViewerGLWidget* sender) +{ + sender->SetOCIOParameters(display, view, look); } void ViewerWidget::SetOCIODisplay(const QString &display) { - gl_widget_->SetOCIODisplay(display); + SetOCIODisplay(display, main_gl_widget()); +} + +void ViewerWidget::SetOCIODisplay(const QString &display, ViewerGLWidget* sender) +{ + sender->SetOCIODisplay(display); } void ViewerWidget::SetOCIOView(const QString &view) { - gl_widget_->SetOCIOView(view); + SetOCIOView(view, main_gl_widget()); } void ViewerWidget::SetOCIOLook(const QString &look) { - gl_widget_->SetOCIOLook(look); + SetOCIOLook(look, main_gl_widget()); +} + +void ViewerWidget::SetOCIOView(const QString &view, ViewerGLWidget* sender) +{ + sender->SetOCIOView(view); +} + +void ViewerWidget::SetOCIOLook(const QString &look, ViewerGLWidget* sender) +{ + sender->SetOCIOLook(look); } void ViewerWidget::SetSignalCursorColorEnabled(bool e) { - gl_widget_->SetSignalCursorColorEnabled(e); + foreach (ViewerGLWidget* glw, gl_widgets_) { + glw->SetSignalCursorColorEnabled(e); + } } void ViewerWidget::TimebaseChangedEvent(const rational &timebase) @@ -576,6 +665,10 @@ void ViewerWidget::RendererCachedTime(const rational &time, qint64 job_time) void ViewerWidget::SizeChangedSlot(int width, int height) { sizer_->SetChildSize(width, height); + + foreach (ViewerWindow* vw, windows_) { + vw->SetResolution(width, height); + } } void ViewerWidget::LengthChangedSlot(const rational &length) @@ -585,19 +678,19 @@ void ViewerWidget::LengthChangedSlot(const rational &length) UpdateMinimumScale(); } -void ViewerWidget::ColorDisplayChanged(QAction* action) +void ViewerWidget::ContextMenuOCIODisplay(QAction* action) { - SetOCIODisplay(action->data().toString()); + SetOCIODisplay(action->data().toString(), context_menu_widget_); } -void ViewerWidget::ColorViewChanged(QAction *action) +void ViewerWidget::ContextMenuOCIOView(QAction *action) { - SetOCIOView(action->data().toString()); + SetOCIOView(action->data().toString(), context_menu_widget_); } -void ViewerWidget::ColorLookChanged(QAction *action) +void ViewerWidget::ContextMenuOCIOLook(QAction *action) { - SetOCIOLook(action->data().toString()); + SetOCIOLook(action->data().toString(), context_menu_widget_); } void ViewerWidget::SetDividerFromMenu(QAction *action) diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 87a817966..690a9c6ef 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -36,6 +36,7 @@ #include "render/backend/audio/audiobackend.h" #include "viewerglwidget.h" #include "viewersizer.h" +#include "viewerwindow.h" #include "widget/playbackcontrols/playbackcontrols.h" #include "widget/timebased/timebased.h" @@ -132,10 +133,11 @@ protected: OpenGLBackend* video_renderer_; AudioBackend* audio_renderer_; - ViewerGLWidget* gl_widget_; - PlaybackControls* controls_; + const QList &gl_widgets() const; + ViewerGLWidget* main_gl_widget() const; + private: void UpdateTimeInternal(int64_t i); @@ -149,6 +151,11 @@ private: void UpdateMinimumScale(); + void SetOCIOParameters(const QString& display, const QString& view, const QString& look, ViewerGLWidget* sender); + void SetOCIODisplay(const QString& display, ViewerGLWidget* sender); + void SetOCIOView(const QString& view, ViewerGLWidget* sender); + void SetOCIOLook(const QString& look, ViewerGLWidget* sender); + QStackedWidget* stack_; ViewerSizer* sizer_; @@ -172,6 +179,12 @@ private: AudioWaveformView* waveform_view_; + QList windows_; + + QList gl_widgets_; + + ViewerGLWidget* context_menu_widget_; + private slots: void PlaybackTimerUpdate(); @@ -188,17 +201,17 @@ private slots: /** * @brief Slot called whenever this viewer's OCIO display setting has changed */ - void ColorDisplayChanged(QAction* action); + void ContextMenuOCIODisplay(QAction* action); /** * @brief Slot called whenever this viewer's OCIO view setting has changed */ - void ColorViewChanged(QAction* action); + void ContextMenuOCIOView(QAction* action); /** * @brief Slot called whenever this viewer's OCIO look setting has changed */ - void ColorLookChanged(QAction* action); + void ContextMenuOCIOLook(QAction* action); void SetDividerFromMenu(QAction* action); @@ -208,6 +221,10 @@ private slots: void UpdateStack(); + void SetFullScreenDisplay(QAction* action); + + void WindowAboutToClose(); + }; #endif // VIEWER_WIDGET_H diff --git a/app/widget/viewer/viewerglwidget.cpp b/app/widget/viewer/viewerglwidget.cpp index c2b1f5db1..b58c7228d 100644 --- a/app/widget/viewer/viewerglwidget.cpp +++ b/app/widget/viewer/viewerglwidget.cpp @@ -114,6 +114,8 @@ void ViewerGLWidget::SetImage(const QString &fn) texture_.Upload(load_buffer_.data()); + emit LoadedBuffer(&load_buffer_); + doneCurrent(); has_image_ = true; @@ -128,12 +130,16 @@ void ViewerGLWidget::SetImage(const QString &fn) } update(); + + if (has_image_) { + emit LoadedBuffer(&load_buffer_); + } else { + emit LoadedBuffer(nullptr); + } } void ViewerGLWidget::SetSignalCursorColorEnabled(bool e) { - qDebug() << e; - signal_cursor_color_ = e; setMouseTracking(e); @@ -149,6 +155,28 @@ void ViewerGLWidget::SetSignalCursorColorEnabled(bool e) } } +void ViewerGLWidget::SetImageFromLoadBuffer(Frame *in_buffer) +{ + has_image_ = in_buffer; + + if (has_image_) { + makeCurrent(); + + if (!texture_.IsCreated() + || texture_.width() != in_buffer->width() + || texture_.height() != in_buffer->height() + || texture_.format() != in_buffer->format()) { + texture_.Create(context(), in_buffer->width(), in_buffer->height(), in_buffer->format(), in_buffer->data()); + } else { + texture_.Upload(in_buffer->data()); + } + + doneCurrent(); + } + + update(); +} + void ViewerGLWidget::SetOCIODisplay(const QString &display) { ocio_display_ = display; @@ -198,6 +226,12 @@ const QString &ViewerGLWidget::ocio_look() const return ocio_look_; } +void ViewerGLWidget::ConnectSibling(ViewerGLWidget *sibling) +{ + connect(this, &ViewerGLWidget::LoadedBuffer, sibling, &ViewerGLWidget::SetImageFromLoadBuffer, Qt::QueuedConnection); + sibling->SetImageFromLoadBuffer(&load_buffer_); +} + void ViewerGLWidget::mousePressEvent(QMouseEvent *event) { QOpenGLWidget::mousePressEvent(event); diff --git a/app/widget/viewer/viewerglwidget.h b/app/widget/viewer/viewerglwidget.h index 2110b236e..2fbf05b1d 100644 --- a/app/widget/viewer/viewerglwidget.h +++ b/app/widget/viewer/viewerglwidget.h @@ -81,6 +81,10 @@ public: const QString& ocio_view() const; const QString& ocio_look() const; + const QMatrix4x4& GetMatrix(); + + void ConnectSibling(ViewerGLWidget* sibling); + public slots: /** * @brief Set the texture to draw and draw it @@ -130,6 +134,14 @@ public slots: */ void SetSignalCursorColorEnabled(bool e); + /** + * @brief Overrides the image with the load buffer of another ViewerGLWidget + * + * If there are multiple ViewerGLWidgets showing the same thing, this is faster than decoding the image from file + * each time. + */ + void SetImageFromLoadBuffer(Frame* in_buffer); + signals: /** * @brief Signal emitted when the user starts dragging from the viewer @@ -141,6 +153,11 @@ signals: */ void CursorColor(const Color& reference, const Color& display); + /** + * @brief Connect this to the SetImageFromLoadBuffer() slot of another ViewerGLWidget to show the same thing + */ + void LoadedBuffer(Frame* load_buffer); + protected: /** * @brief Override the mouse press event simply to emit the DragStarted() signal diff --git a/app/widget/viewer/viewerwindow.cpp b/app/widget/viewer/viewerwindow.cpp new file mode 100644 index 000000000..b66b9b088 --- /dev/null +++ b/app/widget/viewer/viewerwindow.cpp @@ -0,0 +1,55 @@ +#include "viewerwindow.h" + +#include +#include + +ViewerWindow::ViewerWindow(QWidget *parent) : + QWidget(parent, Qt::Window | Qt::WindowStaysOnTopHint) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + + gl_widget_ = new ViewerGLWidget(); + layout->addWidget(gl_widget_); +} + +ViewerGLWidget *ViewerWindow::gl_widget() const +{ + return gl_widget_; +} + +void ViewerWindow::SetResolution(int width, int height) +{ + // Set GL widget matrix to maintain this texture's aspect ratio + double window_ar = static_cast(this->width()) / static_cast(this->height()); + double image_ar = static_cast(width) / static_cast(height); + + QMatrix4x4 mat; + + if (window_ar > image_ar) { + // Window is wider than image, adjust X scale + mat.scale(image_ar / window_ar, 1.0f, 1.0f); + } else if (window_ar < image_ar) { + // Window is taller than image, adjust Y scale + mat.scale(1.0f, window_ar / image_ar, 1.0f); + } + + gl_widget_->SetMatrix(mat); +} + +void ViewerWindow::keyPressEvent(QKeyEvent *e) +{ + QWidget::keyPressEvent(e); + + if (e->key() == Qt::Key_Escape) { + close(); + } +} + +void ViewerWindow::closeEvent(QCloseEvent *e) +{ + QWidget::closeEvent(e); + + deleteLater(); +} diff --git a/app/widget/viewer/viewerwindow.h b/app/widget/viewer/viewerwindow.h new file mode 100644 index 000000000..8ad17229b --- /dev/null +++ b/app/widget/viewer/viewerwindow.h @@ -0,0 +1,30 @@ +#ifndef VIEWERWINDOW_H +#define VIEWERWINDOW_H + +#include + +#include "viewerglwidget.h" + +class ViewerWindow : public QWidget +{ +public: + ViewerWindow(QWidget* parent = nullptr); + + ViewerGLWidget* gl_widget() const; + + /** + * @brief Used to adjust resulting picture to be the right aspect ratio + */ + void SetResolution(int width, int height); + +protected: + virtual void keyPressEvent(QKeyEvent* e) override; + + virtual void closeEvent(QCloseEvent* e) override; + +private: + ViewerGLWidget* gl_widget_; + +}; + +#endif // VIEWERWINDOW_H