/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
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 "mainwindow.h"
#include
#include
#include
#include
#include
#ifdef Q_OS_LINUX
#include
#endif
#include "dialog/about/about.h"
#include "mainmenu.h"
#include "mainstatusbar.h"
#include "widget/timelinewidget/undo/timelineundoworkarea.h"
namespace olive {
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
last_multicam_panel_(nullptr)
{
// Resizes main window to desktop geometry on startup. Fixes the following issues:
// * Qt on Windows has a bug that "de-maximizes" the window when widgets are added, resizing the
// window beforehand works around that issue and we just set it to whatever size is available.
// * On Linux, it seems the window starts off at a vastly different size and then maximizes
// which throws off the proportions and makes the resulting layout wonky.
if (!qApp->screens().empty()) {
resize(qApp->screens().at(0)->availableSize());
}
#ifdef Q_OS_WINDOWS
// Set up taskbar button progress bar (used for some modal tasks like exporting)
taskbar_btn_id_ = RegisterWindowMessage(TEXT("TaskbarButtonCreated"));
taskbar_interface_ = nullptr;
#endif
first_show_ = true;
// Create empty central widget - we don't actually want a central widget (so we set its maximum
// size to 0,0) but some of Qt's docking/undocking fails without it
QWidget* centralWidget = new QWidget(this);
centralWidget->setMaximumSize(QSize(0, 0));
setCentralWidget(centralWidget);
// Set tabs to be on top of panels (default behavior is bottom)
setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
// Allow panels to be tabbed within each other
setDockNestingEnabled(true);
// Create and set main menu
MainMenu* main_menu = new MainMenu(this);
setMenuBar(main_menu);
LoadCustomShortcuts();
// Create and set status bar
MainStatusBar* status_bar = new MainStatusBar(this);
status_bar->ConnectTaskManager(TaskManager::instance());
connect(status_bar, &MainStatusBar::DoubleClicked, this, &MainWindow::StatusBarDoubleClicked);
setStatusBar(status_bar);
// Create standard panels
node_panel_ = new NodePanel(this);
footage_viewer_panel_ = new FootageViewerPanel(this);
param_panel_ = new ParamPanel(this);
curve_panel_ = new CurvePanel(this);
sequence_viewer_panel_ = new SequenceViewerPanel(this);
multicam_panel_ = new MulticamPanel(this);
pixel_sampler_panel_ = new PixelSamplerPanel(this);
AppendProjectPanel();
tool_panel_ = new ToolPanel(this);
task_man_panel_ = new TaskManagerPanel(this);
AppendTimelinePanel();
audio_monitor_panel_ = new AudioMonitorPanel(this);
scope_panel_ = new ScopePanel(this);
// Make node-related connections
connect(node_panel_, &NodePanel::NodeSelectionChangedWithContexts, param_panel_, &ParamPanel::SetSelectedNodes);
connect(node_panel_, &NodePanel::NodeGroupOpened, this, &MainWindow::NodePanelGroupOpenedOrClosed);
connect(node_panel_, &NodePanel::NodeGroupClosed, this, &MainWindow::NodePanelGroupOpenedOrClosed);
connect(param_panel_, &ParamPanel::FocusedNodeChanged, sequence_viewer_panel_, &ViewerPanel::SetGizmos);
connect(param_panel_, &ParamPanel::RequestViewerToStartEditingText, sequence_viewer_panel_, &ViewerPanel::RequestStartEditingText);
connect(param_panel_, &ParamPanel::FocusedNodeChanged, curve_panel_, &CurvePanel::SetNode);
connect(param_panel_, &ParamPanel::SelectedNodesChanged, node_panel_, &NodePanel::Select);
// Connect time signals together
AddMainTimePanel(multicam_panel_);
AddMainTimePanel(curve_panel_);
AddMainTimePanel(param_panel_);
AddMainTimePanel(sequence_viewer_panel_);
sequence_viewer_panel_->ConnectTimeBasedPanel(param_panel_);
sequence_viewer_panel_->ConnectTimeBasedPanel(curve_panel_);
sequence_viewer_panel_->ConnectTimeBasedPanel(multicam_panel_);
connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged);
sequence_viewer_panel_->AddPlaybackDevice(multicam_panel_->GetMulticamWidget()->GetDisplayWidget());
scope_panel_->SetViewerPanel(sequence_viewer_panel_);
UpdateTitle();
QMetaObject::invokeMethod(this, &MainWindow::SetDefaultLayout, Qt::QueuedConnection);
}
MainWindow::~MainWindow()
{
#ifdef Q_OS_WINDOWS
if (taskbar_interface_) {
taskbar_interface_->Release();
}
#endif
}
void MainWindow::LoadLayout(const MainWindowLayoutInfo &info)
{
foreach (Folder* folder, info.open_folders()) {
FolderOpen(folder->project(), folder, true);
}
foreach (const MainWindowLayoutInfo::OpenSequence& sequence, info.open_sequences()) {
TimelinePanel* panel = OpenSequence(sequence.sequence, info.open_sequences().size() == 1);
panel->RestoreSplitterState(sequence.panel_state);
}
restoreState(info.state());
}
MainWindowLayoutInfo MainWindow::SaveLayout() const
{
MainWindowLayoutInfo info;
foreach (ProjectPanel* panel, folder_panels_) {
if (panel->project()) {
info.add_folder(panel->get_root());
}
}
foreach (TimelinePanel* panel, timeline_panels_) {
if (panel->GetConnectedViewer()) {
info.add_sequence({static_cast(panel->GetConnectedViewer()),
panel->SaveSplitterState()});
}
}
info.set_state(saveState());
return info;
}
TimelinePanel* MainWindow::OpenSequence(Sequence *sequence, bool enable_focus)
{
// See if this sequence is already open, and switch to it if so
foreach (TimelinePanel* tl, timeline_panels_) {
if (tl->GetConnectedViewer() == sequence) {
tl->raise();
return tl;
}
}
// See if we have any sequences open or not
TimelinePanel* panel;
if (!timeline_panels_.first()->GetConnectedViewer()) {
panel = timeline_panels_.first();
} else {
panel = AppendTimelinePanel();
//enable_focus = false;
}
panel->ConnectViewerNode(sequence);
if (enable_focus) {
TimelineFocused(sequence);
UpdateAudioMonitorParams(sequence);
}
return panel;
}
void MainWindow::CloseSequence(Sequence *sequence)
{
// We defer to RemoveTimelinePanel() to close the panels, which may delete and remove indices from timeline_panels_.
// We make a copy so that our array here doesn't get ruined by what RemoveTimelinePanel() does
QList copy = timeline_panels_;
foreach (TimelinePanel* tp, copy) {
if (tp->GetConnectedViewer() == sequence) {
RemoveTimelinePanel(tp);
}
}
}
bool MainWindow::IsSequenceOpen(Sequence *sequence) const
{
foreach (TimelinePanel* tp, timeline_panels_) {
if (tp->GetConnectedViewer() == sequence) {
return true;
}
}
return false;
}
void MainWindow::FolderOpen(Project* p, Folder *i, bool floating)
{
ProjectPanel* panel = new ProjectPanel(this);
panel->set_project(p);
panel->set_root(i);
// Tabify with source project panel
foreach (ProjectPanel* proj_panel, project_panels_) {
if (proj_panel->project() == p) {
tabifyDockWidget(proj_panel, panel);
break;
}
}
panel->setFloating(floating);
panel->show();
panel->raise();
// If the panel is closed, just destroy it
panel->SetSignalInsteadOfClose(true);
panel->setProperty("parent_list", reinterpret_cast(&folder_panels_));
connect(panel, &ProjectPanel::CloseRequested, this, &MainWindow::FloatingPanelCloseRequested);
folder_panels_.append(panel);
}
void MainWindow::OpenNodeInViewer(ViewerOutput *node)
{
if (viewer_panels_.contains(node)) {
// This node already has a viewer, raise it
viewer_panels_.value(node)->raise();
} else {
// Create a viewer for this node
ViewerPanel* viewer = new ViewerPanel(this);
viewer->SetSignalInsteadOfClose(true);
viewer->setFloating(true);
viewer->setVisible(true);
viewer->ConnectViewerNode(node);
connect(viewer, &ViewerPanel::CloseRequested, this, &MainWindow::ViewerCloseRequested);
connect(node, &ViewerOutput::RemovedFromGraph, this, &MainWindow::ViewerWithPanelRemovedFromGraph);
viewer_panels_.insert(node, viewer);
}
}
void MainWindow::SetFullscreen(bool fullscreen)
{
if (fullscreen) {
setWindowState(windowState() | Qt::WindowFullScreen);
} else {
setWindowState(windowState() & ~Qt::WindowFullScreen);
}
}
void MainWindow::ToggleMaximizedPanel()
{
if (premaximized_state_.isEmpty()) {
// Assume nothing is maximized at the moment
// Find the currently focused panel
PanelWidget* currently_hovered = PanelManager::instance()->CurrentlyHovered();
// If no panel is hovered, fallback to the currently active panel
if (!currently_hovered) {
currently_hovered = PanelManager::instance()->CurrentlyFocused();
// If no panel is hovered or focused, do nothing
if (!currently_hovered) {
return;
}
}
// If this panel is not actually on the main window, this is a no-op
if (currently_hovered->isFloating()) {
return;
}
// Save the current state so it can be restored later
premaximized_state_ = saveState();
// For every other panel that is on the main window, hide it
foreach (PanelWidget* panel, PanelManager::instance()->panels()) {
if (!panel->isFloating() && panel != currently_hovered) {
panel->setVisible(false);
}
}
} else {
// Preserve currently focused panel
auto currently_focused_panel = PanelManager::instance()->CurrentlyFocused(false);
// Assume we are currently maximized, restore the state
PanelManager::instance()->SetSuppressChangedSignal(true);
restoreState(premaximized_state_);
premaximized_state_.clear();
currently_focused_panel->raise();
currently_focused_panel->setFocus();
PanelManager::instance()->SetSuppressChangedSignal(false);
}
}
void MainWindow::ProjectOpen(Project *p)
{
// See if this project is already open, and switch to it if so
foreach (ProjectPanel* pl, project_panels_) {
if (pl->project() == p) {
pl->raise();
return;
}
}
ProjectPanel* panel;
if (!project_panels_.first()->project()) {
panel = project_panels_.first();
} else {
panel = AppendProjectPanel();
}
panel->set_project(p);
panel->setFocus();
}
void MainWindow::ProjectClose(Project *p)
{
// Close project from NodeParamView
param_panel_->CloseContextsBelongingToProject(p);
// Close project from NodeView
node_panel_->CloseContextsBelongingToProject(p);
// Close any nodes open in TimeBasedWidgets
foreach (PanelWidget* panel, PanelManager::instance()->panels()) {
TimeBasedPanel* tbp = dynamic_cast(panel);
if (tbp && tbp->GetConnectedViewer() && tbp->GetConnectedViewer()->project() == p) {
if (dynamic_cast(tbp)) {
// Prefer our CloseSequence function which will delete any unnecessary timeline panels
CloseSequence(static_cast(tbp->GetConnectedViewer()));
} else {
tbp->DisconnectViewerNode();
}
}
}
// Close any extra folder panels
foreach (ProjectPanel* panel, folder_panels_) {
if (panel->project() == p) {
panel->close();
}
}
// Close project from project panel
foreach (ProjectPanel* panel, project_panels_) {
if (panel->project() == p) {
RemoveProjectPanel(panel);
}
}
}
void MainWindow::SetApplicationProgressStatus(ProgressStatus status)
{
#if defined(Q_OS_WINDOWS)
if (taskbar_interface_) {
switch (status) {
case kProgressShow:
taskbar_interface_->SetProgressState(reinterpret_cast(this->winId()), TBPF_NORMAL);
break;
case kProgressNone:
taskbar_interface_->SetProgressState(reinterpret_cast(this->winId()), TBPF_NOPROGRESS);
break;
case kProgressError:
taskbar_interface_->SetProgressState(reinterpret_cast(this->winId()), TBPF_ERROR);
break;
}
}
#elif defined(Q_OS_MAC)
#endif
}
void MainWindow::SetApplicationProgressValue(int value)
{
#if defined(Q_OS_WINDOWS)
if (taskbar_interface_) {
taskbar_interface_->SetProgressValue(reinterpret_cast(this->winId()), value, 100);
}
#elif defined(Q_OS_MAC)
#endif
}
void MainWindow::SelectFootage(const QVector &e)
{
for (ProjectPanel *p : project_panels_) {
SelectFootageForProjectPanel(e, p);
}
for (ProjectPanel *p : folder_panels_) {
SelectFootageForProjectPanel(e, p);
}
}
void MainWindow::closeEvent(QCloseEvent *e)
{
// Try to close all projects (this will return false if the user chooses not to close)
if (!Core::instance()->CloseAllProjects(false)) {
e->ignore();
return;
}
scope_panel_->SetViewerPanel(nullptr);
PanelManager::instance()->DeleteAllPanels();
SaveCustomShortcuts();
QMainWindow::closeEvent(e);
}
#ifdef Q_OS_WINDOWS
bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
if (static_cast(message)->message == taskbar_btn_id_) {
// Attempt to create taskbar button progress handle
HRESULT hr = CoCreateInstance(CLSID_TaskbarList,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITaskbarList3,
reinterpret_cast(&taskbar_interface_));
if (SUCCEEDED(hr)) {
hr = taskbar_interface_->HrInit();
if (FAILED(hr)) {
taskbar_interface_->Release();
taskbar_interface_ = nullptr;
}
}
}
return QMainWindow::nativeEvent(eventType, message, result);
}
#endif
void MainWindow::StatusBarDoubleClicked()
{
task_man_panel_->show();
task_man_panel_->raise();
}
void MainWindow::NodePanelGroupOpenedOrClosed()
{
NodePanel *p = static_cast(sender());
param_panel_->SetContexts(p->GetContexts());
}
void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks)
{
TimelinePanel *panel = static_cast(sender());
if (PanelManager::instance()->CurrentlyFocused(false) == panel) {
UpdateNodePanelContextFromTimelinePanel(panel);
last_multicam_panel_ = panel;
UpdateMulticamNode();
}
}
void MainWindow::UpdateMulticamNode()
{
TimelinePanel *panel = last_multicam_panel_;
if (!panel) {
return;
}
ClipBlock *clip = nullptr;
MultiCamNode *multicam = nullptr;
for (Block *b : panel->GetSelectedBlocks()) {
if (b->range().Contains(panel->GetTime())) {
if ((clip = dynamic_cast(b))) {
if ((multicam = clip->FindMulticam())) {
break;
}
}
}
}
if (!multicam && panel->GetSequence()) {
const QVector