/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
***/
#include "manageddisplay.h"
#include
#include
#include "panel/panelmanager.h"
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
#include "render/backend/dynamicrenderer.h"
#endif
#include "render/opengl/openglrenderer.h"
#include "render/rendermanager.h"
namespace olive
{
#define super QWidget
ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent)
: QWidget(parent)
, color_manager_(nullptr)
, color_service_(nullptr)
, is_backend_neutral_(false)
{
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
// Create renderer
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
{
auto *dynamic_renderer = new DynamicRenderer(
RenderManager::BackendToString(
RenderManager::instance()->requested_backend()),
this);
if (!dynamic_renderer->Load()) {
qWarning() << "Failed to load dynamic render backend for viewer, falling back to OpenGL";
delete dynamic_renderer;
attached_renderer_ = new OpenGLRenderer(this);
} else {
attached_renderer_ = dynamic_renderer;
}
}
#else
attached_renderer_ = new OpenGLRenderer(this);
#endif
if (attached_renderer_->IsOpenGL()) {
// OpenGL path
inner_widget_ = new ManagedDisplayWidgetOpenGL();
inner_widget_->setAttribute(Qt::WA_TranslucentBackground, false);
connect(static_cast(inner_widget_),
&ManagedDisplayWidgetOpenGL::OnInit, this,
&ManagedDisplayWidget::OnInit, Qt::DirectConnection);
connect(static_cast(inner_widget_),
&ManagedDisplayWidgetOpenGL::OnDestroy, this,
&ManagedDisplayWidget::OnDestroy, Qt::DirectConnection);
connect(static_cast(inner_widget_),
&ManagedDisplayWidgetOpenGL::OnPaint, this,
&ManagedDisplayWidget::OnPaint, Qt::DirectConnection);
connect(static_cast(inner_widget_),
&ManagedDisplayWidgetOpenGL::frameSwapped, this,
&ManagedDisplayWidget::frameSwapped, Qt::DirectConnection);
inner_widget_->installEventFilter(this);
// Create widget wrapper for OpenGL window
#ifdef USE_QOPENGLWINDOW
wrapper_ = QWidget::createWindowContainer(
static_cast(inner_widget_));
#else
wrapper_ = inner_widget_;
#endif
layout->addWidget(wrapper_);
} else {
// Backend-neutral path (Vulkan, etc.)
is_backend_neutral_ = true;
auto *bn_widget = new ManagedDisplayWidgetBackendNeutral(this);
inner_widget_ = bn_widget;
inner_widget_->setAttribute(Qt::WA_OpaquePaintEvent);
inner_widget_->installEventFilter(this);
connect(bn_widget, &ManagedDisplayWidgetBackendNeutral::OnPaint, this,
&ManagedDisplayWidget::OnPaint, Qt::DirectConnection);
wrapper_ = inner_widget_;
layout->addWidget(wrapper_);
}
}
ManagedDisplayWidget::~ManagedDisplayWidget()
{
if (!is_backend_neutral_) {
MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR_INNER;
disconnect(static_cast(inner_widget_),
&ManagedDisplayWidgetOpenGL::OnDestroy, this,
&ManagedDisplayWidget::OnDestroy);
} else {
OnDestroy();
}
}
void ManagedDisplayWidget::ConnectColorManager(ColorManager *color_manager)
{
if (color_manager_ == color_manager) {
return;
}
if (color_manager_ != nullptr) {
disconnect(color_manager_, &ColorManager::ConfigChanged, this,
&ManagedDisplayWidget::ColorConfigChanged);
disconnect(color_manager_, &ColorManager::ReferenceSpaceChanged, this,
&ManagedDisplayWidget::ColorConfigChanged);
}
color_manager_ = color_manager;
if (color_manager_ != nullptr) {
connect(color_manager_, &ColorManager::ConfigChanged, this,
&ManagedDisplayWidget::ColorConfigChanged);
connect(color_manager_, &ColorManager::ReferenceSpaceChanged, this,
&ManagedDisplayWidget::ColorConfigChanged);
}
ColorConfigChanged();
emit ColorManagerChanged(color_manager_);
}
ColorManager *ManagedDisplayWidget::color_manager() const
{
return color_manager_;
}
void ManagedDisplayWidget::DisconnectColorManager()
{
ConnectColorManager(nullptr);
}
const ColorTransform &ManagedDisplayWidget::GetColorTransform() const
{
return color_transform_;
}
Menu *ManagedDisplayWidget::GetColorSpaceMenu(QMenu *parent, bool auto_connect)
{
QStringList colorspaces = color_manager()->ListAvailableColorspaces();
Menu *ocio_colorspace_menu = new Menu(tr("Color Space"), parent);
if (auto_connect) {
connect(ocio_colorspace_menu, &Menu::triggered, this,
&ManagedDisplayWidget::MenuColorspaceSelect);
}
foreach (const QString &c, colorspaces) {
QAction *action = ocio_colorspace_menu->addAction(c);
action->setCheckable(true);
action->setChecked(color_transform_.output() == c);
action->setData(c);
}
return ocio_colorspace_menu;
}
void ManagedDisplayWidget::ColorConfigChanged()
{
if (!color_manager_) {
color_service_ = nullptr;
return;
}
// When no explicit transform has been chosen, default to the config's
// display/view transform so the viewer shows a sensible image. Otherwise
// an empty transform falls back to the project's default input colorspace,
// which is usually a scene-referred space (e.g. ACEScg / Linear) and makes
// the picture look raw/wrong on a monitor.
if (color_transform_.output().isEmpty()) {
QString display = color_manager_->GetDefaultDisplay();
QString view = color_manager_->GetDefaultView(display);
SetColorTransform(color_manager_->GetCompliantColorSpace(
ColorTransform(display, view, QString()), true));
} else {
SetColorTransform(
color_manager_->GetCompliantColorSpace(color_transform_, false));
}
}
ColorProcessorPtr ManagedDisplayWidget::color_service()
{
return color_service_;
}
void ManagedDisplayWidget::ShowDefaultContextMenu()
{
Menu m(this);
if (color_manager_) {
m.addMenu(GetColorSpaceMenu(&m));
m.addSeparator();
m.addMenu(GetDisplayMenu(&m));
m.addMenu(GetViewMenu(&m));
m.addMenu(GetLookMenu(&m));
} else {
QAction *a = m.addAction(tr("No color manager connected"));
a->setEnabled(false);
}
m.exec(QCursor::pos());
}
void ManagedDisplayWidget::MenuDisplaySelect(QAction *action)
{
const ColorTransform &old_transform = GetColorTransform();
ColorTransform new_transform = color_manager()->GetCompliantColorSpace(
ColorTransform(action->data().toString(), old_transform.view(),
old_transform.look()));
SetColorTransform(new_transform);
}
void ManagedDisplayWidget::MenuViewSelect(QAction *action)
{
const ColorTransform &old_transform = GetColorTransform();
ColorTransform new_transform = color_manager()->GetCompliantColorSpace(
ColorTransform(old_transform.display(), action->data().toString(),
old_transform.look()));
SetColorTransform(new_transform);
}
void ManagedDisplayWidget::MenuLookSelect(QAction *action)
{
const ColorTransform &old_transform = GetColorTransform();
ColorTransform new_transform = color_manager()->GetCompliantColorSpace(
ColorTransform(old_transform.display(), old_transform.view(),
action->data().toString()));
SetColorTransform(new_transform);
}
void ManagedDisplayWidget::MenuColorspaceSelect(QAction *action)
{
SetColorTransform(color_manager()->GetCompliantColorSpace(
ColorTransform(action->data().toString())));
}
void ManagedDisplayWidget::OnDestroy()
{
attached_renderer_->Destroy();
attached_renderer_->PostDestroy();
}
void ManagedDisplayWidget::SetColorTransform(const ColorTransform &transform)
{
color_transform_ = transform;
SetupColorProcessor();
ColorProcessorChangedEvent();
}
void ManagedDisplayWidget::OnInit()
{
if (!is_backend_neutral_) {
QOpenGLContext *context =
static_cast(inner_widget_)->context();
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
if (auto *dynamic_renderer =
dynamic_cast(attached_renderer_)) {
dynamic_renderer->InitWithOpenGLContext(context);
dynamic_renderer->PostInit();
return;
}
#endif
static_cast(attached_renderer_)->Init(context);
static_cast(attached_renderer_)->PostInit();
} else {
attached_renderer_->Init();
attached_renderer_->PostInit();
}
}
void ManagedDisplayWidget::EnableDefaultContextMenu()
{
connect(this, &ManagedDisplayWidget::customContextMenuRequested, this,
&ManagedDisplayWidget::ShowDefaultContextMenu);
}
void ManagedDisplayWidget::ColorProcessorChangedEvent()
{
update();
}
void ManagedDisplayWidget::makeCurrent()
{
if (!is_backend_neutral_) {
static_cast(inner_widget_)->makeCurrent();
}
}
void ManagedDisplayWidget::doneCurrent()
{
if (!is_backend_neutral_) {
static_cast(inner_widget_)->doneCurrent();
}
}
QPaintDevice *ManagedDisplayWidget::paint_device() const
{
return inner_widget_;
}
void ManagedDisplayWidget::SetInnerMouseTracking(bool e)
{
if (wrapper_) {
wrapper_->setMouseTracking(e);
}
}
VideoParams ManagedDisplayWidget::GetViewportParams() const
{
int device_width = width() * devicePixelRatioF();
int device_height = height() * devicePixelRatioF();
PixelFormat device_format = static_cast(
OLIVE_CONFIG("OfflinePixelFormat").toInt());
return VideoParams(device_width, device_height, device_format,
VideoParams::kInternalChannelCount);
}
void ManagedDisplayWidget::update()
{
if (inner_widget_) {
inner_widget_->update();
}
}
bool ManagedDisplayWidget::eventFilter(QObject *o, QEvent *e)
{
if (o != inner_widget_) {
return super::eventFilter(o, e);
}
switch (e->type()) {
case QEvent::FocusIn:
// HACK: QWindow focus isn't accounted for in QApplication::focusChanged, so we handle it
// manually here.
PanelManager::instance()->FocusChanged(nullptr, this);
break;
case QEvent::ContextMenu: {
QContextMenuEvent *ctx = static_cast(e);
emit customContextMenuRequested(ctx->pos());
return true;
}
case QEvent::MouseButtonPress: {
// HACK: QWindows don't seem to receive ContextMenu events on right click (only when pressing
// the menu button on the keyboard) so we handle it manually here
/*QMouseEvent *ev = static_cast(e);
if (ev->button() == Qt::RightButton) {
emit customContextMenuRequested(ev->pos());
return true;
}*/
break;
}
default:
break;
}
return super::eventFilter(o, e);
}
Menu *ManagedDisplayWidget::GetDisplayMenu(QMenu *parent, bool auto_connect)
{
QStringList displays = color_manager()->ListAvailableDisplays();
Menu *ocio_display_menu = new Menu(tr("Display"), parent);
if (auto_connect) {
connect(ocio_display_menu, &Menu::triggered, this,
&ManagedDisplayWidget::MenuDisplaySelect);
}
foreach (const QString &d, displays) {
QAction *action = ocio_display_menu->addAction(d);
action->setCheckable(true);
action->setChecked(color_transform_.display() == d);
action->setData(d);
}
return ocio_display_menu;
}
Menu *ManagedDisplayWidget::GetViewMenu(QMenu *parent, bool auto_connect)
{
QStringList views =
color_manager()->ListAvailableViews(color_transform_.display());
Menu *ocio_view_menu = new Menu(tr("View"), parent);
if (auto_connect) {
connect(ocio_view_menu, &Menu::triggered, this,
&ManagedDisplayWidget::MenuViewSelect);
}
foreach (const QString &v, views) {
QAction *action = ocio_view_menu->addAction(v);
action->setCheckable(true);
action->setChecked(color_transform_.view() == v);
action->setData(v);
}
return ocio_view_menu;
}
Menu *ManagedDisplayWidget::GetLookMenu(QMenu *parent, bool auto_connect)
{
QStringList looks = color_manager()->ListAvailableLooks();
Menu *ocio_look_menu = new Menu(tr("Look"), parent);
if (auto_connect) {
connect(ocio_look_menu, &Menu::triggered, this,
&ManagedDisplayWidget::MenuLookSelect);
}
// Setup "no look" action
QAction *no_look_action = ocio_look_menu->addAction(tr("(None)"));
no_look_action->setCheckable(true);
no_look_action->setChecked(color_transform_.look().isEmpty());
no_look_action->setData(QString());
// Set up the rest of the looks
foreach (const QString &l, looks) {
QAction *action = ocio_look_menu->addAction(l);
action->setCheckable(true);
action->setChecked(color_transform_.look() == l);
action->setData(l);
}
return ocio_look_menu;
}
void ManagedDisplayWidget::SetupColorProcessor()
{
color_service_ = nullptr;
if (color_manager_) {
// (Re)create color processor
try {
color_service_ = ColorProcessor::Create(
color_manager_, color_manager_->GetReferenceColorSpace(),
color_transform_);
} catch (OCIO::Exception &e) {
QMessageBox::critical(
this, tr("OpenColorIO Error"),
tr("Failed to set color configuration: %1").arg(e.what()),
QMessageBox::Ok);
}
} else {
color_service_ = nullptr;
}
emit ColorProcessorChanged(color_service_);
}
}