mirror of
https://github.com/LMMS/lmms.git
synced 2026-03-31 20:34:25 -04:00
Fix bug introduced with #7595 where drag-moving the MDI canvas background would break the show/hide behavior of a fullscreen window. Closes #7751
1666 lines
44 KiB
C++
1666 lines
44 KiB
C++
/*
|
|
* MainWindow.cpp - implementation of LMMS-main-window
|
|
*
|
|
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
|
*
|
|
* This file is part of LMMS - https://lmms.io
|
|
*
|
|
* 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 2 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 (see COPYING); if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301 USA.
|
|
*
|
|
*/
|
|
|
|
#include "MainWindow.h"
|
|
|
|
#include <QApplication>
|
|
#include <QCloseEvent>
|
|
#include <QDesktopServices>
|
|
#include <QDomElement>
|
|
#include <QFileInfo>
|
|
#include <QMdiArea>
|
|
#include <QMenuBar>
|
|
#include <QMessageBox>
|
|
#include <QShortcut>
|
|
#include <QSplitter>
|
|
|
|
#include "AboutDialog.h"
|
|
#include "AutomationEditor.h"
|
|
#include "ControllerRackView.h"
|
|
#include "DeprecationHelper.h"
|
|
#include "embed.h"
|
|
#include "Engine.h"
|
|
#include "ExportProjectDialog.h"
|
|
#include "FileBrowser.h"
|
|
#include "FileDialog.h"
|
|
#include "Metronome.h"
|
|
#include "MixerView.h"
|
|
#include "GuiApplication.h"
|
|
#include "ImportFilter.h"
|
|
#include "InstrumentTrackView.h"
|
|
#include "InstrumentTrackWindow.h"
|
|
#include "MicrotunerConfig.h"
|
|
#include "PatternEditor.h"
|
|
#include "PianoRoll.h"
|
|
#include "PianoView.h"
|
|
#include "PluginBrowser.h"
|
|
#include "PluginFactory.h"
|
|
#include "PluginView.h"
|
|
#include "ProjectJournal.h"
|
|
#include "ProjectNotes.h"
|
|
#include "ProjectRenderer.h"
|
|
#include "RecentProjectsMenu.h"
|
|
#include "RemotePlugin.h"
|
|
#include "SetupDialog.h"
|
|
#include "SideBar.h"
|
|
#include "SongEditor.h"
|
|
#include "SubWindow.h"
|
|
#include "TemplatesMenu.h"
|
|
#include "TextFloat.h"
|
|
#include "TimeLineWidget.h"
|
|
#include "ToolButton.h"
|
|
#include "ToolPlugin.h"
|
|
#include "VersionedSaveDialog.h"
|
|
|
|
#include "lmmsversion.h"
|
|
|
|
|
|
namespace lmms::gui
|
|
{
|
|
|
|
|
|
MainWindow::MainWindow() :
|
|
m_workspace( nullptr ),
|
|
m_toolsMenu( nullptr ),
|
|
m_autoSaveTimer( this ),
|
|
m_viewMenu( nullptr ),
|
|
m_metronomeToggle( 0 ),
|
|
m_session( SessionState::Normal )
|
|
{
|
|
setAttribute( Qt::WA_DeleteOnClose );
|
|
|
|
auto main_widget = new QWidget(this);
|
|
auto vbox = new QVBoxLayout(main_widget);
|
|
vbox->setSpacing( 0 );
|
|
vbox->setContentsMargins(0, 0, 0, 0);
|
|
|
|
auto w = new QWidget(main_widget);
|
|
auto hbox = new QHBoxLayout(w);
|
|
hbox->setSpacing( 0 );
|
|
hbox->setContentsMargins(0, 0, 0, 0);
|
|
|
|
auto sideBar = new SideBar(Qt::Vertical, w);
|
|
|
|
auto splitter = new QSplitter(Qt::Horizontal, w);
|
|
splitter->setChildrenCollapsible( false );
|
|
|
|
ConfigManager* confMgr = ConfigManager::inst();
|
|
bool sideBarOnRight = confMgr->value("ui", "sidebaronright").toInt();
|
|
|
|
emit initProgress(tr("Preparing plugin browser"));
|
|
sideBar->appendTab( new PluginBrowser( splitter ) );
|
|
emit initProgress(tr("Preparing file browsers"));
|
|
sideBar->appendTab( new FileBrowser(
|
|
confMgr->userProjectsDir() + "*" +
|
|
confMgr->factoryProjectsDir(),
|
|
"*.mmp *.mmpz *.xml *.mid *.mpt",
|
|
tr( "My Projects" ),
|
|
embed::getIconPixmap( "project_file" ).transformed( QTransform().rotate( 90 ) ),
|
|
splitter, false,
|
|
confMgr->userProjectsDir(),
|
|
confMgr->factoryProjectsDir()));
|
|
sideBar->appendTab(
|
|
new FileBrowser(confMgr->userSamplesDir() + "*" + confMgr->factorySamplesDir(), FileItem::defaultFilters(),
|
|
tr("My Samples"), embed::getIconPixmap("sample_file").transformed(QTransform().rotate(90)), splitter, false,
|
|
confMgr->userSamplesDir(), confMgr->factorySamplesDir()));
|
|
sideBar->appendTab( new FileBrowser(
|
|
confMgr->userPresetsDir() + "*" +
|
|
confMgr->factoryPresetsDir(),
|
|
"*.xpf *.cs.xml *.xiz *.lv2",
|
|
tr( "My Presets" ),
|
|
embed::getIconPixmap( "preset_file" ).transformed( QTransform().rotate( 90 ) ),
|
|
splitter , false,
|
|
confMgr->userPresetsDir(),
|
|
confMgr->factoryPresetsDir()));
|
|
sideBar->appendTab(new FileBrowser(QDir::homePath(), FileItem::defaultFilters(), tr("My Home"),
|
|
embed::getIconPixmap("home").transformed(QTransform().rotate(90)), splitter, false));
|
|
|
|
QStringList root_paths;
|
|
QString title = tr("Root Directory");
|
|
bool dirs_as_items = false;
|
|
|
|
#ifdef LMMS_BUILD_APPLE
|
|
title = tr( "Volumes" );
|
|
root_paths += "/Volumes";
|
|
#elif defined(LMMS_BUILD_WIN32)
|
|
title = tr( "My Computer" );
|
|
dirs_as_items = true;
|
|
#endif
|
|
|
|
#if ! defined(LMMS_BUILD_APPLE)
|
|
QFileInfoList drives = QDir::drives();
|
|
for( const QFileInfo & drive : drives )
|
|
{
|
|
root_paths += drive.absolutePath();
|
|
}
|
|
#endif
|
|
|
|
sideBar->appendTab(new FileBrowser(root_paths.join("*"), FileItem::defaultFilters(), title,
|
|
embed::getIconPixmap("computer").transformed(QTransform().rotate(90)), splitter, dirs_as_items));
|
|
|
|
m_workspace = new MovableQMdiArea(splitter);
|
|
|
|
// Load background
|
|
emit initProgress(tr("Loading background picture"));
|
|
QString backgroundPicFile = ConfigManager::inst()->backgroundPicFile();
|
|
QImage backgroundPic;
|
|
if( !backgroundPicFile.isEmpty() )
|
|
{
|
|
backgroundPic = QImage( backgroundPicFile );
|
|
}
|
|
if( !backgroundPicFile.isNull() )
|
|
{
|
|
m_workspace->setBackground( backgroundPic );
|
|
}
|
|
else
|
|
{
|
|
m_workspace->setBackground( Qt::NoBrush );
|
|
}
|
|
|
|
m_workspace->setOption( QMdiArea::DontMaximizeSubWindowOnActivation );
|
|
m_workspace->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
m_workspace->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
|
|
hbox->addWidget(sideBar);
|
|
hbox->addWidget(splitter);
|
|
// If the user wants the sidebar on the right, we move the workspace and
|
|
// the splitter to the "left" side, or the first widgets in their list
|
|
if (sideBarOnRight)
|
|
{
|
|
splitter->insertWidget(0, m_workspace);
|
|
hbox->insertWidget(0, splitter);
|
|
}
|
|
|
|
// create global-toolbar at the top of our window
|
|
m_toolBar = new QWidget( main_widget );
|
|
m_toolBar->setObjectName( "mainToolbar" );
|
|
m_toolBar->setFixedHeight( 64 );
|
|
m_toolBar->move( 0, 0 );
|
|
|
|
// add layout for organizing quite complex toolbar-layouting
|
|
m_toolBarLayout = new QGridLayout( m_toolBar/*, 2, 1*/ );
|
|
m_toolBarLayout->setContentsMargins(0, 0, 0, 0);
|
|
m_toolBarLayout->setSpacing( 0 );
|
|
|
|
vbox->addWidget( m_toolBar );
|
|
vbox->addWidget( w );
|
|
setCentralWidget( main_widget );
|
|
|
|
m_updateTimer.start( 1000 / 60, this ); // 60 fps
|
|
|
|
if( ConfigManager::inst()->value( "ui", "enableautosave" ).toInt() )
|
|
{
|
|
// connect auto save
|
|
connect(&m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSave()));
|
|
m_autoSaveInterval = ConfigManager::inst()->value(
|
|
"ui", "saveinterval" ).toInt() < 1 ?
|
|
DEFAULT_AUTO_SAVE_INTERVAL :
|
|
ConfigManager::inst()->value(
|
|
"ui", "saveinterval" ).toInt();
|
|
|
|
// The auto save function mustn't run until there is a project
|
|
// to save or it will run over recover.mmp if you hesitate at the
|
|
// recover messagebox for a minute. It is now started in main.
|
|
// See autoSaveTimerReset() in MainWindow.h
|
|
}
|
|
|
|
connect( Engine::getSong(), SIGNAL(playbackStateChanged()),
|
|
this, SLOT(updatePlayPauseIcons()));
|
|
|
|
connect(Engine::getSong(), SIGNAL(modified()), SLOT(onSongModified()));
|
|
connect(Engine::getSong(), SIGNAL(projectFileNameChanged()), SLOT(onProjectFileNameChanged()));
|
|
|
|
maximized = isMaximized();
|
|
new QShortcut(QKeySequence(Qt::Key_F11), this, SLOT(toggleFullscreen()));
|
|
|
|
if (ConfigManager::inst()->value("tooltips", "disabled").toInt())
|
|
{
|
|
qApp->installEventFilter(this);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
for( PluginView *view : m_tools )
|
|
{
|
|
delete view->model();
|
|
delete view;
|
|
}
|
|
// TODO: Close tools
|
|
// dependencies are such that the editors must be destroyed BEFORE Song is deletect in Engine::destroy
|
|
// see issue #2015 on github
|
|
delete getGUI()->automationEditor();
|
|
delete getGUI()->pianoRoll();
|
|
delete getGUI()->songEditor();
|
|
// destroy engine which will do further cleanups etc.
|
|
Engine::destroy();
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::finalize()
|
|
{
|
|
resetWindowTitle();
|
|
setWindowIcon( embed::getIconPixmap( "icon_small" ) );
|
|
|
|
|
|
// project-popup-menu
|
|
auto project_menu = new QMenu(this);
|
|
menuBar()->addMenu( project_menu )->setText( tr( "&File" ) );
|
|
project_menu->addAction( embed::getIconPixmap( "project_new" ),
|
|
tr( "&New" ),
|
|
this, SLOT(createNewProject()),
|
|
QKeySequence::New );
|
|
|
|
auto templates_menu = new TemplatesMenu( this );
|
|
project_menu->addMenu(templates_menu);
|
|
|
|
project_menu->addAction( embed::getIconPixmap( "project_open" ),
|
|
tr( "&Open..." ),
|
|
this, SLOT(openProject()),
|
|
QKeySequence::Open );
|
|
|
|
project_menu->addMenu(new RecentProjectsMenu(this));
|
|
|
|
project_menu->addAction( embed::getIconPixmap( "project_save" ),
|
|
tr( "&Save" ),
|
|
this, SLOT(saveProject()),
|
|
QKeySequence::Save );
|
|
project_menu->addAction( embed::getIconPixmap( "project_save" ),
|
|
tr( "Save &As..." ),
|
|
this, SLOT(saveProjectAs()),
|
|
combine(Qt::CTRL, Qt::SHIFT, Qt::Key_S));
|
|
project_menu->addAction( embed::getIconPixmap( "project_save" ),
|
|
tr( "Save as New &Version" ),
|
|
this, SLOT(saveProjectAsNewVersion()),
|
|
combine(Qt::CTRL, Qt::ALT, Qt::Key_S));
|
|
|
|
project_menu->addAction( embed::getIconPixmap( "project_save" ),
|
|
tr( "Save as default template" ),
|
|
this, SLOT(saveProjectAsDefaultTemplate()));
|
|
|
|
project_menu->addSeparator();
|
|
project_menu->addAction( embed::getIconPixmap( "project_import" ),
|
|
tr( "Import..." ),
|
|
this,
|
|
SLOT(onImportProject()));
|
|
project_menu->addAction( embed::getIconPixmap( "project_export" ),
|
|
tr( "E&xport..." ),
|
|
this,
|
|
SLOT(onExportProject()),
|
|
combine(Qt::CTRL, Qt::Key_E));
|
|
project_menu->addAction( embed::getIconPixmap( "project_export" ),
|
|
tr( "E&xport Tracks..." ),
|
|
this,
|
|
SLOT(onExportProjectTracks()),
|
|
combine(Qt::CTRL, Qt::SHIFT, Qt::Key_E));
|
|
|
|
project_menu->addAction( embed::getIconPixmap( "midi_file" ),
|
|
tr( "Export &MIDI..." ),
|
|
this,
|
|
SLOT(onExportProjectMidi()),
|
|
combine(Qt::CTRL, Qt::Key_M));
|
|
|
|
project_menu->addSeparator();
|
|
project_menu->addAction( embed::getIconPixmap( "exit" ), tr( "&Quit" ),
|
|
qApp, SLOT(closeAllWindows()),
|
|
combine(Qt::CTRL, Qt::Key_Q));
|
|
|
|
auto edit_menu = new QMenu(this);
|
|
menuBar()->addMenu( edit_menu )->setText( tr( "&Edit" ) );
|
|
m_undoAction = edit_menu->addAction( embed::getIconPixmap( "edit_undo" ),
|
|
tr( "Undo" ),
|
|
this, SLOT(undo()),
|
|
QKeySequence::Undo );
|
|
m_redoAction = edit_menu->addAction( embed::getIconPixmap( "edit_redo" ),
|
|
tr( "Redo" ),
|
|
this, SLOT(redo()),
|
|
QKeySequence::Redo );
|
|
// Ensure that both (Ctrl+Y) and (Ctrl+Shift+Z) activate redo shortcut regardless of OS defaults
|
|
if (QKeySequence(QKeySequence::Redo) != QKeySequence(combine(Qt::CTRL, Qt::Key_Y)))
|
|
{
|
|
new QShortcut(QKeySequence(combine(Qt::CTRL, Qt::Key_Y)), this, SLOT(redo()));
|
|
}
|
|
if (QKeySequence(QKeySequence::Redo) != QKeySequence(combine(Qt::CTRL, Qt::SHIFT, Qt::Key_Z)))
|
|
{
|
|
new QShortcut(QKeySequence(combine(Qt::CTRL, Qt::SHIFT, Qt::Key_Z)), this, SLOT(redo()));
|
|
}
|
|
|
|
edit_menu->addSeparator();
|
|
edit_menu->addAction(embed::getIconPixmap("microtuner"), tr("Scales and keymaps"),
|
|
this, SLOT(toggleMicrotunerWin()));
|
|
edit_menu->addAction(embed::getIconPixmap("setup_general"), tr("Settings"),
|
|
this, SLOT(showSettingsDialog()));
|
|
|
|
connect(edit_menu, SIGNAL(aboutToShow()), this, SLOT(updateUndoRedoButtons()));
|
|
|
|
m_viewMenu = new QMenu( this );
|
|
menuBar()->addMenu( m_viewMenu )->setText( tr( "&View" ) );
|
|
connect( m_viewMenu, SIGNAL(aboutToShow()),
|
|
this, SLOT(updateViewMenu()));
|
|
connect( m_viewMenu, SIGNAL(triggered(QAction*)), this,
|
|
SLOT(updateConfig(QAction*)));
|
|
|
|
|
|
m_toolsMenu = new QMenu( this );
|
|
for( const Plugin::Descriptor* desc : getPluginFactory()->descriptors(Plugin::Type::Tool) )
|
|
{
|
|
m_toolsMenu->addAction( desc->logo->pixmap(), desc->displayName );
|
|
m_tools.push_back( ToolPlugin::instantiate( desc->name, /*this*/nullptr )
|
|
->createView(this) );
|
|
}
|
|
if( !m_toolsMenu->isEmpty() )
|
|
{
|
|
menuBar()->addMenu( m_toolsMenu )->setText( tr( "&Tools" ) );
|
|
connect( m_toolsMenu, SIGNAL(triggered(QAction*)),
|
|
this, SLOT(showTool(QAction*)));
|
|
}
|
|
|
|
|
|
// help-popup-menu
|
|
auto help_menu = new QMenu(this);
|
|
menuBar()->addMenu( help_menu )->setText( tr( "&Help" ) );
|
|
// May use offline help
|
|
if( true )
|
|
{
|
|
help_menu->addAction( embed::getIconPixmap( "help" ),
|
|
tr( "Online Help" ),
|
|
this, SLOT(browseHelp()));
|
|
}
|
|
else
|
|
{
|
|
help_menu->addAction( embed::getIconPixmap( "help" ),
|
|
tr( "Help" ),
|
|
this, SLOT(help()));
|
|
}
|
|
|
|
help_menu->addSeparator();
|
|
help_menu->addAction( embed::getIconPixmap( "icon_small" ), tr( "About" ),
|
|
this, SLOT(aboutLMMS()));
|
|
|
|
// create tool-buttons
|
|
auto project_new = new ToolButton(
|
|
embed::getIconPixmap("project_new"), tr("Create new project"), this, SLOT(createNewProject()), m_toolBar);
|
|
|
|
auto project_new_from_template = new ToolButton(embed::getIconPixmap("project_new_from_template"),
|
|
tr("Create new project from template"), this, SLOT(emptySlot()), m_toolBar);
|
|
project_new_from_template->setMenu( templates_menu );
|
|
project_new_from_template->setPopupMode( ToolButton::InstantPopup );
|
|
|
|
auto project_open = new ToolButton(
|
|
embed::getIconPixmap("project_open"), tr("Open existing project"), this, SLOT(openProject()), m_toolBar);
|
|
|
|
auto project_open_recent = new ToolButton(embed::getIconPixmap("project_open_recent"),
|
|
tr("Recently opened projects"), this, SLOT(emptySlot()), m_toolBar);
|
|
project_open_recent->setMenu( new RecentProjectsMenu(this) );
|
|
project_open_recent->setPopupMode( ToolButton::InstantPopup );
|
|
|
|
auto project_save = new ToolButton(
|
|
embed::getIconPixmap("project_save"), tr("Save current project"), this, SLOT(saveProject()), m_toolBar);
|
|
|
|
auto project_export = new ToolButton(
|
|
embed::getIconPixmap("project_export"), tr("Export current project"), this, SLOT(onExportProject()), m_toolBar);
|
|
|
|
m_metronomeToggle = new ToolButton(
|
|
embed::getIconPixmap( "metronome" ),
|
|
tr( "Metronome" ),
|
|
this, SLOT(onToggleMetronome()),
|
|
m_toolBar );
|
|
m_metronomeToggle->setCheckable(true);
|
|
m_metronomeToggle->setChecked(Engine::getSong()->metronome().active());
|
|
|
|
m_toolBarLayout->setColumnMinimumWidth( 0, 5 );
|
|
m_toolBarLayout->addWidget( project_new, 0, 1 );
|
|
m_toolBarLayout->addWidget( project_new_from_template, 0, 2 );
|
|
m_toolBarLayout->addWidget( project_open, 0, 3 );
|
|
m_toolBarLayout->addWidget( project_open_recent, 0, 4 );
|
|
m_toolBarLayout->addWidget( project_save, 0, 5 );
|
|
m_toolBarLayout->addWidget( project_export, 0, 6 );
|
|
m_toolBarLayout->addWidget( m_metronomeToggle, 0, 7 );
|
|
|
|
|
|
// window-toolbar
|
|
auto song_editor_window = new ToolButton(embed::getIconPixmap("songeditor"), tr("Song Editor") + " (Ctrl+1)", this,
|
|
SLOT(toggleSongEditorWin()), m_toolBar);
|
|
song_editor_window->setShortcut(combine(Qt::CTRL, Qt::Key_1));
|
|
|
|
auto pattern_editor_window = new ToolButton(embed::getIconPixmap("pattern_track_btn"),
|
|
tr("Pattern Editor") + " (Ctrl+2)", this, SLOT(togglePatternEditorWin()), m_toolBar);
|
|
pattern_editor_window->setShortcut(combine(Qt::CTRL, Qt::Key_2));
|
|
|
|
auto piano_roll_window = new ToolButton(
|
|
embed::getIconPixmap("piano"), tr("Piano Roll") + " (Ctrl+3)", this, SLOT(togglePianoRollWin()), m_toolBar);
|
|
piano_roll_window->setShortcut(combine(Qt::CTRL, Qt::Key_3));
|
|
|
|
auto automation_editor_window = new ToolButton(embed::getIconPixmap("automation"),
|
|
tr("Automation Editor") + " (Ctrl+4)", this, SLOT(toggleAutomationEditorWin()), m_toolBar);
|
|
automation_editor_window->setShortcut(combine(Qt::CTRL, Qt::Key_4));
|
|
|
|
auto mixer_window = new ToolButton(
|
|
embed::getIconPixmap("mixer"), tr("Mixer") + " (Ctrl+5)", this, SLOT(toggleMixerWin()), m_toolBar);
|
|
mixer_window->setShortcut(combine(Qt::CTRL, Qt::Key_5));
|
|
|
|
auto controllers_window = new ToolButton(embed::getIconPixmap("controller"),
|
|
tr("Show/hide controller rack") + " (Ctrl+6)", this, SLOT(toggleControllerRack()), m_toolBar);
|
|
controllers_window->setShortcut(combine(Qt::CTRL, Qt::Key_6));
|
|
|
|
auto project_notes_window = new ToolButton(embed::getIconPixmap("project_notes"),
|
|
tr("Show/hide project notes") + " (Ctrl+7)", this, SLOT(toggleProjectNotesWin()), m_toolBar);
|
|
project_notes_window->setShortcut(combine(Qt::CTRL, Qt::Key_7));
|
|
|
|
m_toolBarLayout->addWidget( song_editor_window, 1, 1 );
|
|
m_toolBarLayout->addWidget( pattern_editor_window, 1, 2 );
|
|
m_toolBarLayout->addWidget( piano_roll_window, 1, 3 );
|
|
m_toolBarLayout->addWidget( automation_editor_window, 1, 4 );
|
|
m_toolBarLayout->addWidget( mixer_window, 1, 5 );
|
|
m_toolBarLayout->addWidget( controllers_window, 1, 6 );
|
|
m_toolBarLayout->addWidget( project_notes_window, 1, 7 );
|
|
m_toolBarLayout->setColumnStretch( 100, 1 );
|
|
|
|
// setup-dialog opened before?
|
|
if( !ConfigManager::inst()->value( "app", "configured" ).toInt() )
|
|
{
|
|
ConfigManager::inst()->setValue( "app", "configured", "1" );
|
|
// no, so show it that user can setup everything
|
|
SetupDialog sd;
|
|
sd.exec();
|
|
}
|
|
// look whether the audio engine failed to start the audio device selected by the
|
|
// user and is using AudioDummy as a fallback
|
|
// or the audio device is set to invalid one
|
|
else if( Engine::audioEngine()->audioDevStartFailed() || !AudioEngine::isAudioDevNameValid(
|
|
ConfigManager::inst()->value( "audioengine", "audiodev" ) ) )
|
|
{
|
|
// if so, offer the audio settings section of the setup dialog
|
|
SetupDialog sd( SetupDialog::ConfigTab::AudioSettings );
|
|
sd.exec();
|
|
}
|
|
|
|
// Add editor subwindows
|
|
for (QWidget* widget : std::list<QWidget*>{
|
|
getGUI()->automationEditor(),
|
|
getGUI()->patternEditor(),
|
|
getGUI()->pianoRoll(),
|
|
getGUI()->songEditor()
|
|
})
|
|
{
|
|
QMdiSubWindow* window = addWindowedWidget(widget);
|
|
window->setWindowIcon(widget->windowIcon());
|
|
window->setAttribute(Qt::WA_DeleteOnClose, false);
|
|
window->resize(widget->sizeHint());
|
|
}
|
|
|
|
getGUI()->automationEditor()->parentWidget()->hide();
|
|
getGUI()->patternEditor()->parentWidget()->move(610, 5);
|
|
getGUI()->patternEditor()->parentWidget()->hide();
|
|
getGUI()->pianoRoll()->parentWidget()->move(5, 5);
|
|
getGUI()->pianoRoll()->parentWidget()->hide();
|
|
getGUI()->songEditor()->parentWidget()->move(5, 5);
|
|
getGUI()->songEditor()->parentWidget()->show();
|
|
|
|
// reset window title every time we change the state of a subwindow to show the correct title
|
|
for( const QMdiSubWindow * subWindow : workspace()->subWindowList() )
|
|
{
|
|
connect( subWindow, SIGNAL(windowStateChanged(Qt::WindowStates,Qt::WindowStates)), this, SLOT(resetWindowTitle()));
|
|
}
|
|
}
|
|
|
|
|
|
int MainWindow::addWidgetToToolBar( QWidget * _w, int _row, int _col )
|
|
{
|
|
int col = ( _col == -1 ) ? m_toolBarLayout->columnCount() + 7 : _col;
|
|
if( _w->height() > 32 || _row == -1 )
|
|
{
|
|
m_toolBarLayout->addWidget( _w, 0, col, 2, 1 );
|
|
}
|
|
else
|
|
{
|
|
m_toolBarLayout->addWidget( _w, _row, col );
|
|
}
|
|
return( col );
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::addSpacingToToolBar( int _size )
|
|
{
|
|
m_toolBarLayout->setColumnMinimumWidth( m_toolBarLayout->columnCount() +
|
|
7, _size );
|
|
}
|
|
|
|
|
|
|
|
|
|
SubWindow* MainWindow::addWindowedWidget(QWidget *w, Qt::WindowFlags windowFlags)
|
|
{
|
|
// wrap the widget in our own *custom* window that patches some errors in QMdiSubWindow
|
|
auto win = new SubWindow(m_workspace->viewport(), windowFlags);
|
|
win->setAttribute(Qt::WA_DeleteOnClose);
|
|
win->setWidget(w);
|
|
if (w && w->sizeHint().isValid()) {
|
|
auto titleBarHeight = win->titleBarHeight();
|
|
auto frameWidth = win->frameWidth();
|
|
QSize delta(2* frameWidth, titleBarHeight + frameWidth);
|
|
win->resize(delta + w->sizeHint());
|
|
}
|
|
m_workspace->addSubWindow(win);
|
|
return win;
|
|
}
|
|
|
|
|
|
void MainWindow::resetWindowTitle()
|
|
{
|
|
QString title(tr( "Untitled" ));
|
|
|
|
if( Engine::getSong()->projectFileName() != "" )
|
|
{
|
|
title = QFileInfo( Engine::getSong()->projectFileName()
|
|
).completeBaseName();
|
|
}
|
|
|
|
if( Engine::getSong()->isModified() )
|
|
{
|
|
title += '*';
|
|
}
|
|
|
|
if( getSession() == SessionState::Recover )
|
|
{
|
|
title += " - " + tr( "Recover session. Please save your work!" );
|
|
}
|
|
|
|
setWindowTitle( title + " - " + tr( "LMMS %1" ).arg( LMMS_VERSION ) );
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MainWindow::mayChangeProject(bool stopPlayback)
|
|
{
|
|
if( stopPlayback )
|
|
{
|
|
Engine::getSong()->stop();
|
|
}
|
|
|
|
if( !Engine::getSong()->isModified() && getSession() != SessionState::Recover )
|
|
{
|
|
return( true );
|
|
}
|
|
|
|
// Separate message strings for modified and recovered files
|
|
QString messageTitleRecovered = tr( "Recovered project not saved" );
|
|
QString messageRecovered = tr( "This project was recovered from the "
|
|
"previous session. It is currently "
|
|
"unsaved and will be lost if you don't "
|
|
"save it. Do you want to save it now?" );
|
|
|
|
QString messageTitleUnsaved = tr( "Project not saved" );
|
|
QString messageUnsaved = tr( "The current project was modified since "
|
|
"last saving. Do you want to save it "
|
|
"now?" );
|
|
|
|
QMessageBox mb( ( getSession() == SessionState::Recover ?
|
|
messageTitleRecovered : messageTitleUnsaved ),
|
|
( getSession() == SessionState::Recover ?
|
|
messageRecovered : messageUnsaved ),
|
|
QMessageBox::Question,
|
|
QMessageBox::Save,
|
|
QMessageBox::Discard,
|
|
QMessageBox::Cancel,
|
|
this );
|
|
int answer = mb.exec();
|
|
|
|
if( answer == QMessageBox::Save )
|
|
{
|
|
return( saveProject() );
|
|
}
|
|
else if( answer == QMessageBox::Discard )
|
|
{
|
|
if( getSession() == SessionState::Recover )
|
|
{
|
|
sessionCleanup();
|
|
}
|
|
return( true );
|
|
}
|
|
|
|
return( false );
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::clearKeyModifiers()
|
|
{
|
|
m_keyMods.m_ctrl = false;
|
|
m_keyMods.m_shift = false;
|
|
m_keyMods.m_alt = false;
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::saveWidgetState( QWidget * _w, QDomElement & _de )
|
|
{
|
|
// If our widget is the main content of a window (e.g. piano roll, Mixer, etc),
|
|
// we really care about the position of the *window* - not the position of the widget within its window
|
|
if( _w->parentWidget() != nullptr &&
|
|
_w->parentWidget()->inherits( "QMdiSubWindow" ) )
|
|
{
|
|
_w = _w->parentWidget();
|
|
}
|
|
|
|
// If the widget is a SubWindow, then we can make use of the getTrueNormalGeometry() method that
|
|
// performs the same as normalGeometry, but isn't broken on X11 ( see https://bugreports.qt.io/browse/QTBUG-256 )
|
|
auto asSubWindow = qobject_cast<SubWindow*>(_w);
|
|
QRect normalGeom = asSubWindow != nullptr ? asSubWindow->getTrueNormalGeometry() : _w->normalGeometry();
|
|
|
|
bool visible = _w->isVisible();
|
|
_de.setAttribute( "visible", visible );
|
|
_de.setAttribute( "minimized", _w->isMinimized() );
|
|
_de.setAttribute( "maximized", _w->isMaximized() );
|
|
|
|
_de.setAttribute( "x", normalGeom.x() );
|
|
_de.setAttribute( "y", normalGeom.y() );
|
|
|
|
QSize sizeToStore = normalGeom.size();
|
|
_de.setAttribute( "width", sizeToStore.width() );
|
|
_de.setAttribute( "height", sizeToStore.height() );
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::restoreWidgetState( QWidget * _w, const QDomElement & _de )
|
|
{
|
|
QRect r( qMax( 1, _de.attribute( "x" ).toInt() ),
|
|
qMax( 1, _de.attribute( "y" ).toInt() ),
|
|
qMax( _w->sizeHint().width(), _de.attribute( "width" ).toInt() ),
|
|
qMax( _w->minimumHeight(), _de.attribute( "height" ).toInt() ) );
|
|
if( _de.hasAttribute( "visible" ) && !r.isNull() )
|
|
{
|
|
// If our widget is the main content of a window (e.g. piano roll, Mixer, etc),
|
|
// we really care about the position of the *window* - not the position of the widget within its window
|
|
if ( _w->parentWidget() != nullptr &&
|
|
_w->parentWidget()->inherits( "QMdiSubWindow" ) )
|
|
{
|
|
_w = _w->parentWidget();
|
|
}
|
|
// first restore the window, as attempting to resize a maximized window causes graphics glitching
|
|
_w->setWindowState( _w->windowState() & ~(Qt::WindowMaximized | Qt::WindowMinimized) );
|
|
|
|
// Check isEmpty() to work around corrupt project files with empty size
|
|
if ( ! r.size().isEmpty() ) {
|
|
_w->resize( r.size() );
|
|
}
|
|
_w->move( r.topLeft() );
|
|
|
|
// set the window to its correct minimized/maximized/restored state
|
|
Qt::WindowStates flags = _w->windowState();
|
|
flags = _de.attribute( "minimized" ).toInt() ?
|
|
( flags | Qt::WindowMinimized ) :
|
|
( flags & ~Qt::WindowMinimized );
|
|
flags = _de.attribute( "maximized" ).toInt() ?
|
|
( flags | Qt::WindowMaximized ) :
|
|
( flags & ~Qt::WindowMaximized );
|
|
_w->setWindowState( flags );
|
|
|
|
_w->setVisible( _de.attribute( "visible" ).toInt() );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::emptySlot()
|
|
{
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::createNewProject()
|
|
{
|
|
if( mayChangeProject(true) )
|
|
{
|
|
Engine::getSong()->createNewProject();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::openProject()
|
|
{
|
|
if( mayChangeProject(false) )
|
|
{
|
|
FileDialog ofd( this, tr( "Open Project" ), "", tr( "LMMS (*.mmp *.mmpz)" ) );
|
|
|
|
ofd.setDirectory( ConfigManager::inst()->userProjectsDir() );
|
|
ofd.setFileMode( FileDialog::ExistingFiles );
|
|
if( ofd.exec () == QDialog::Accepted &&
|
|
!ofd.selectedFiles().isEmpty() )
|
|
{
|
|
Song *song = Engine::getSong();
|
|
|
|
song->stop();
|
|
setCursor( Qt::WaitCursor );
|
|
song->loadProject( ofd.selectedFiles()[0] );
|
|
setCursor( Qt::ArrowCursor );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MainWindow::saveProject()
|
|
{
|
|
if( Engine::getSong()->projectFileName() == "" )
|
|
{
|
|
return( saveProjectAs() );
|
|
}
|
|
else if( this->guiSaveProject() )
|
|
{
|
|
if( getSession() == SessionState::Recover )
|
|
{
|
|
sessionCleanup();
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MainWindow::saveProjectAs()
|
|
{
|
|
auto optionsWidget = new SaveOptionsWidget(Engine::getSong()->getSaveOptions());
|
|
VersionedSaveDialog sfd( this, optionsWidget, tr( "Save Project" ), "",
|
|
tr( "LMMS Project" ) + " (*.mmpz *.mmp);;" +
|
|
tr( "LMMS Project Template" ) + " (*.mpt)" );
|
|
QString f = Engine::getSong()->projectFileName();
|
|
if( f != "" )
|
|
{
|
|
sfd.setDirectory( QFileInfo( f ).absolutePath() );
|
|
sfd.selectFile( QFileInfo( f ).fileName() );
|
|
}
|
|
else
|
|
{
|
|
sfd.setDirectory( ConfigManager::inst()->userProjectsDir() );
|
|
}
|
|
|
|
// Don't write over file with suffix if no suffix is provided.
|
|
QString suffix = ConfigManager::inst()->value( "app",
|
|
"nommpz" ).toInt() == 0
|
|
? "mmpz"
|
|
: "mmp" ;
|
|
sfd.setDefaultSuffix( suffix );
|
|
|
|
if( sfd.exec () == FileDialog::Accepted &&
|
|
!sfd.selectedFiles().isEmpty() && sfd.selectedFiles()[0] != "" )
|
|
{
|
|
QString fname = sfd.selectedFiles()[0] ;
|
|
if( sfd.selectedNameFilter().contains( "(*.mpt)" ) )
|
|
{
|
|
// Remove the default suffix
|
|
fname.remove( "." + suffix );
|
|
if( !sfd.selectedFiles()[0].endsWith( ".mpt" ) )
|
|
{
|
|
if( VersionedSaveDialog::fileExistsQuery( fname + ".mpt",
|
|
tr( "Save project template" ) ) )
|
|
{
|
|
fname += ".mpt";
|
|
}
|
|
}
|
|
}
|
|
if( this->guiSaveProjectAs( fname ) )
|
|
{
|
|
if( getSession() == SessionState::Recover )
|
|
{
|
|
sessionCleanup();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MainWindow::saveProjectAsNewVersion()
|
|
{
|
|
QString fileName = Engine::getSong()->projectFileName();
|
|
if( fileName == "" )
|
|
{
|
|
return saveProjectAs();
|
|
}
|
|
else
|
|
{
|
|
do VersionedSaveDialog::changeFileNameVersion( fileName, true );
|
|
while ( QFile( fileName ).exists() );
|
|
|
|
return this->guiSaveProjectAs( fileName );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::saveProjectAsDefaultTemplate()
|
|
{
|
|
QString defaultTemplate = ConfigManager::inst()->userTemplateDir() + "default.mpt";
|
|
|
|
QFileInfo fileInfo(defaultTemplate);
|
|
if (fileInfo.exists())
|
|
{
|
|
if (QMessageBox::warning(this,
|
|
tr("Overwrite default template?"),
|
|
tr("This will overwrite your current default template."),
|
|
QMessageBox::Ok,
|
|
QMessageBox::Cancel) != QMessageBox::Ok)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
Engine::getSong()->saveProjectFile( defaultTemplate );
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::showSettingsDialog()
|
|
{
|
|
SetupDialog sd;
|
|
sd.exec();
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::aboutLMMS()
|
|
{
|
|
AboutDialog(this).exec();
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::help()
|
|
{
|
|
QMessageBox::information( this, tr( "Help not available" ),
|
|
tr( "Currently there's no help "
|
|
"available in LMMS.\n"
|
|
"Please visit "
|
|
"http://lmms.sf.net/wiki "
|
|
"for documentation on LMMS." ),
|
|
QMessageBox::Ok );
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::toggleWindow( QWidget *window, bool forceShow )
|
|
{
|
|
QWidget *parent = window->parentWidget();
|
|
|
|
if( forceShow ||
|
|
m_workspace->activeSubWindow() != parent ||
|
|
parent->isHidden() )
|
|
{
|
|
parent->show();
|
|
window->show();
|
|
window->setFocus();
|
|
}
|
|
else
|
|
{
|
|
parent->hide();
|
|
refocus();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::toggleFullscreen()
|
|
{
|
|
if ( !isFullScreen() )
|
|
{
|
|
maximized = isMaximized();
|
|
showFullScreen();
|
|
}
|
|
else
|
|
{
|
|
maximized ? showMaximized() : showNormal();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* When an editor window with focus is toggled off, attempt to set focus
|
|
* to the next visible editor window, or if none are visible, set focus
|
|
* to the parent window.
|
|
*/
|
|
void MainWindow::refocus()
|
|
{
|
|
const auto gui = getGUI();
|
|
|
|
// Attempt to set the focus on the first of these editors that is not hidden...
|
|
for (auto editorParent : { gui->songEditor()->parentWidget(), gui->patternEditor()->parentWidget(),
|
|
gui->pianoRoll()->parentWidget(), gui->automationEditor()->parentWidget() })
|
|
{
|
|
if (!editorParent->isHidden())
|
|
{
|
|
editorParent->setFocus();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// ... otherwise set the focus on the main window.
|
|
this->setFocus();
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::togglePatternEditorWin( bool forceShow )
|
|
{
|
|
toggleWindow( getGUI()->patternEditor(), forceShow );
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::toggleSongEditorWin()
|
|
{
|
|
toggleWindow( getGUI()->songEditor() );
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::toggleProjectNotesWin()
|
|
{
|
|
toggleWindow( getGUI()->getProjectNotes() );
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::togglePianoRollWin()
|
|
{
|
|
toggleWindow( getGUI()->pianoRoll() );
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::toggleAutomationEditorWin()
|
|
{
|
|
toggleWindow( getGUI()->automationEditor() );
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::toggleMixerWin()
|
|
{
|
|
toggleWindow( getGUI()->mixerView() );
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::toggleMicrotunerWin()
|
|
{
|
|
toggleWindow( getGUI()->getMicrotunerConfig() );
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::updateViewMenu()
|
|
{
|
|
m_viewMenu->clear();
|
|
// TODO: get current visibility for these and indicate in menu?
|
|
// Not that it's straight visible <-> invisible, more like
|
|
// not on top -> top <-> invisible
|
|
m_viewMenu->addAction(embed::getIconPixmap( "songeditor" ),
|
|
tr( "Song Editor" ) + "\tCtrl+1",
|
|
this, SLOT(toggleSongEditorWin())
|
|
);
|
|
m_viewMenu->addAction(embed::getIconPixmap("pattern_track"),
|
|
tr("Pattern Editor") + "\tCtrl+2",
|
|
this, SLOT(togglePatternEditorWin())
|
|
);
|
|
m_viewMenu->addAction(embed::getIconPixmap( "piano" ),
|
|
tr( "Piano Roll" ) + "\tCtrl+3",
|
|
this, SLOT(togglePianoRollWin())
|
|
);
|
|
m_viewMenu->addAction(embed::getIconPixmap( "automation" ),
|
|
tr( "Automation Editor" ) + "\tCtrl+4",
|
|
this,
|
|
SLOT(toggleAutomationEditorWin())
|
|
);
|
|
m_viewMenu->addAction(embed::getIconPixmap( "mixer" ),
|
|
tr( "Mixer" ) + "\tCtrl+5",
|
|
this, SLOT(toggleMixerWin())
|
|
);
|
|
m_viewMenu->addAction(embed::getIconPixmap( "controller" ),
|
|
tr( "Controller Rack" ) + "\tCtrl+6",
|
|
this, SLOT(toggleControllerRack())
|
|
);
|
|
m_viewMenu->addAction(embed::getIconPixmap( "project_notes" ),
|
|
tr( "Project Notes" ) + "\tCtrl+7",
|
|
this, SLOT(toggleProjectNotesWin())
|
|
);
|
|
|
|
m_viewMenu->addSeparator();
|
|
|
|
m_viewMenu->addAction(embed::getIconPixmap( "fullscreen" ),
|
|
tr( "Fullscreen" ) + "\tF11",
|
|
this, SLOT(toggleFullscreen())
|
|
);
|
|
|
|
m_viewMenu->addSeparator();
|
|
|
|
// Here we should put all look&feel -stuff from configmanager
|
|
// that is safe to change on the fly. There is probably some
|
|
// more elegant way to do this.
|
|
auto qa = new QAction(tr("Smooth scroll"), this);
|
|
qa->setData("smoothscroll");
|
|
qa->setCheckable( true );
|
|
qa->setChecked( ConfigManager::inst()->value( "ui", "smoothscroll" ).toInt() );
|
|
m_viewMenu->addAction(qa);
|
|
|
|
// Not yet.
|
|
/* qa = new QAction(tr( "One instrument track window" ), this);
|
|
qa->setData("oneinstrument");
|
|
qa->setCheckable( true );
|
|
qa->setChecked( ConfigManager::inst()->value( "ui", "oneinstrumenttrackwindow" ).toInt() );
|
|
m_viewMenu->addAction(qa);
|
|
*/
|
|
|
|
qa = new QAction(tr( "Enable note labels in piano roll" ), this);
|
|
qa->setData("printnotelabels");
|
|
qa->setCheckable( true );
|
|
qa->setChecked( ConfigManager::inst()->value( "ui", "printnotelabels" ).toInt() );
|
|
m_viewMenu->addAction(qa);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::updateConfig( QAction * _who )
|
|
{
|
|
QString tag = _who->data().toString();
|
|
bool checked = _who->isChecked();
|
|
|
|
if (tag == "tooltips")
|
|
{
|
|
ConfigManager::inst()->setValue( "tooltips", "disabled",
|
|
QString::number(!checked) );
|
|
|
|
if (checked) { qApp->removeEventFilter(this); }
|
|
else { qApp->installEventFilter(this); }
|
|
|
|
}
|
|
else if ( tag == "smoothscroll" )
|
|
{
|
|
ConfigManager::inst()->setValue( "ui", "smoothscroll",
|
|
QString::number(checked) );
|
|
}
|
|
else if ( tag == "oneinstrument" )
|
|
{
|
|
ConfigManager::inst()->setValue( "ui", "oneinstrumenttrackwindow",
|
|
QString::number(checked) );
|
|
}
|
|
else if ( tag == "printnotelabels" )
|
|
{
|
|
ConfigManager::inst()->setValue( "ui", "printnotelabels",
|
|
QString::number(checked) );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::onToggleMetronome()
|
|
{
|
|
Engine::getSong()->metronome().setActive(m_metronomeToggle->isChecked());
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::toggleControllerRack()
|
|
{
|
|
toggleWindow( getGUI()->getControllerRackView() );
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::updatePlayPauseIcons()
|
|
{
|
|
getGUI()->songEditor()->setPauseIcon( false );
|
|
getGUI()->automationEditor()->setPauseIcon( false );
|
|
getGUI()->patternEditor()->setPauseIcon( false );
|
|
getGUI()->pianoRoll()->setPauseIcon( false );
|
|
|
|
if( Engine::getSong()->isPlaying() )
|
|
{
|
|
switch( Engine::getSong()->playMode() )
|
|
{
|
|
case Song::PlayMode::Song:
|
|
getGUI()->songEditor()->setPauseIcon( true );
|
|
break;
|
|
|
|
case Song::PlayMode::AutomationClip:
|
|
getGUI()->automationEditor()->setPauseIcon( true );
|
|
break;
|
|
|
|
case Song::PlayMode::Pattern:
|
|
getGUI()->patternEditor()->setPauseIcon( true );
|
|
break;
|
|
|
|
case Song::PlayMode::MidiClip:
|
|
getGUI()->pianoRoll()->setPauseIcon( true );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void MainWindow::updateUndoRedoButtons()
|
|
{
|
|
// when the edit menu is shown, grey out the undo/redo buttons if there's nothing to undo/redo
|
|
// else, un-grey them
|
|
m_undoAction->setEnabled(Engine::projectJournal()->canUndo());
|
|
m_redoAction->setEnabled(Engine::projectJournal()->canRedo());
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::undo()
|
|
{
|
|
Engine::projectJournal()->undo();
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::redo()
|
|
{
|
|
Engine::projectJournal()->redo();
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::closeEvent( QCloseEvent * _ce )
|
|
{
|
|
if( mayChangeProject(true) )
|
|
{
|
|
// delete recovery file
|
|
if( ConfigManager::inst()->
|
|
value( "ui", "enableautosave" ).toInt() )
|
|
{
|
|
sessionCleanup();
|
|
_ce->accept();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_ce->ignore();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::sessionCleanup()
|
|
{
|
|
// delete recover session files
|
|
QFile::remove( ConfigManager::inst()->recoveryFile() );
|
|
setSession( SessionState::Normal );
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MainWindow::eventFilter(QObject* watched, QEvent* event)
|
|
{
|
|
// For now this function is only used to globally block tooltips
|
|
// It must be installed to QApplication through installEventFilter
|
|
if (event->type() == QEvent::ToolTip) { return true; }
|
|
|
|
return QObject::eventFilter(watched, event);
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::focusOutEvent( QFocusEvent * _fe )
|
|
{
|
|
// TODO Remove this function, since it is apparently never actually called!
|
|
// when loosing focus we do not receive key-(release!)-events anymore,
|
|
// so we might miss release-events of one the modifiers we're watching!
|
|
clearKeyModifiers();
|
|
QMainWindow::leaveEvent( _fe );
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::keyPressEvent( QKeyEvent * _ke )
|
|
{
|
|
switch( _ke->key() )
|
|
{
|
|
case Qt::Key_Control: m_keyMods.m_ctrl = true; break;
|
|
case Qt::Key_Shift: m_keyMods.m_shift = true; break;
|
|
case Qt::Key_Alt: m_keyMods.m_alt = true; break;
|
|
default:
|
|
{
|
|
InstrumentTrackWindow * w =
|
|
InstrumentTrackView::topLevelInstrumentTrackWindow();
|
|
if( w )
|
|
{
|
|
w->pianoView()->keyPressEvent( _ke );
|
|
}
|
|
if( !_ke->isAccepted() )
|
|
{
|
|
QMainWindow::keyPressEvent( _ke );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::keyReleaseEvent( QKeyEvent * _ke )
|
|
{
|
|
switch( _ke->key() )
|
|
{
|
|
case Qt::Key_Control: m_keyMods.m_ctrl = false; break;
|
|
case Qt::Key_Shift: m_keyMods.m_shift = false; break;
|
|
case Qt::Key_Alt: m_keyMods.m_alt = false; break;
|
|
default:
|
|
if( InstrumentTrackView::topLevelInstrumentTrackWindow() )
|
|
{
|
|
InstrumentTrackView::topLevelInstrumentTrackWindow()->
|
|
pianoView()->keyReleaseEvent( _ke );
|
|
}
|
|
if( !_ke->isAccepted() )
|
|
{
|
|
QMainWindow::keyReleaseEvent( _ke );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::timerEvent( QTimerEvent * _te)
|
|
{
|
|
emit periodicUpdate();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void MainWindow::showTool( QAction * _idx )
|
|
{
|
|
PluginView * p = m_tools[m_toolsMenu->actions().indexOf( _idx )];
|
|
p->show();
|
|
p->parentWidget()->show();
|
|
p->setFocus();
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::browseHelp()
|
|
{
|
|
// file:// alternative for offline help
|
|
QString url = "https://lmms.io/documentation/";
|
|
QDesktopServices::openUrl( url );
|
|
// TODO: Handle error
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::autoSave()
|
|
{
|
|
if( !Engine::getSong()->isExporting() &&
|
|
!Engine::getSong()->isLoadingProject() &&
|
|
!RemotePluginBase::isMainThreadWaiting() &&
|
|
!QApplication::mouseButtons() &&
|
|
( ConfigManager::inst()->value( "ui",
|
|
"enablerunningautosave" ).toInt() ||
|
|
! Engine::getSong()->isPlaying() ) )
|
|
{
|
|
Engine::getSong()->saveProjectFile(ConfigManager::inst()->recoveryFile());
|
|
autoSaveTimerReset(); // Reset timer
|
|
}
|
|
else
|
|
{
|
|
// try again in 10 seconds
|
|
if( getAutoSaveTimerInterval() != m_autoSaveShortTime )
|
|
{
|
|
autoSaveTimerReset( m_autoSaveShortTime );
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::onExportProjectMidi()
|
|
{
|
|
FileDialog efd( this );
|
|
|
|
efd.setFileMode( FileDialog::AnyFile );
|
|
|
|
QStringList types;
|
|
types << tr("MIDI File (*.mid)");
|
|
efd.setNameFilters( types );
|
|
QString base_filename;
|
|
QString const & projectFileName = Engine::getSong()->projectFileName();
|
|
if( !projectFileName.isEmpty() )
|
|
{
|
|
efd.setDirectory( QFileInfo( projectFileName ).absolutePath() );
|
|
base_filename = QFileInfo( projectFileName ).completeBaseName();
|
|
}
|
|
else
|
|
{
|
|
efd.setDirectory( ConfigManager::inst()->userProjectsDir() );
|
|
base_filename = tr( "untitled" );
|
|
}
|
|
efd.selectFile( base_filename + ".mid" );
|
|
efd.setDefaultSuffix( "mid");
|
|
efd.setWindowTitle( tr( "Select file for project-export..." ) );
|
|
|
|
efd.setAcceptMode( FileDialog::AcceptSave );
|
|
|
|
|
|
if( efd.exec() == QDialog::Accepted && !efd.selectedFiles().isEmpty() && !efd.selectedFiles()[0].isEmpty() )
|
|
{
|
|
const QString suffix = ".mid";
|
|
|
|
QString export_filename = efd.selectedFiles()[0];
|
|
if (!export_filename.endsWith(suffix)) export_filename += suffix;
|
|
|
|
Engine::getSong()->exportProjectMidi(export_filename);
|
|
}
|
|
}
|
|
|
|
void MainWindow::exportProject(bool multiExport)
|
|
{
|
|
QString const & projectFileName = Engine::getSong()->projectFileName();
|
|
|
|
FileDialog efd( getGUI()->mainWindow() );
|
|
|
|
if ( multiExport )
|
|
{
|
|
efd.setFileMode( FileDialog::Directory);
|
|
efd.setWindowTitle( tr( "Select directory for writing exported tracks..." ) );
|
|
if( !projectFileName.isEmpty() )
|
|
{
|
|
efd.setDirectory( QFileInfo( projectFileName ).absolutePath() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
efd.setFileMode( FileDialog::AnyFile );
|
|
int idx = 0;
|
|
QStringList types;
|
|
while( ProjectRenderer::fileEncodeDevices[idx].m_fileFormat != ProjectRenderer::ExportFileFormat::Count)
|
|
{
|
|
if(ProjectRenderer::fileEncodeDevices[idx].isAvailable()) {
|
|
types << tr(ProjectRenderer::fileEncodeDevices[idx].m_description);
|
|
}
|
|
++idx;
|
|
}
|
|
efd.setNameFilters( types );
|
|
QString baseFilename;
|
|
if( !projectFileName.isEmpty() )
|
|
{
|
|
efd.setDirectory( QFileInfo( projectFileName ).absolutePath() );
|
|
baseFilename = QFileInfo( projectFileName ).completeBaseName();
|
|
}
|
|
else
|
|
{
|
|
efd.setDirectory( ConfigManager::inst()->userProjectsDir() );
|
|
baseFilename = tr( "untitled" );
|
|
}
|
|
efd.selectFile( baseFilename + ProjectRenderer::fileEncodeDevices[0].m_extension );
|
|
efd.setWindowTitle( tr( "Select file for project-export..." ) );
|
|
}
|
|
|
|
QString suffix = "wav";
|
|
efd.setDefaultSuffix( suffix );
|
|
efd.setAcceptMode( FileDialog::AcceptSave );
|
|
|
|
if( efd.exec() == QDialog::Accepted && !efd.selectedFiles().isEmpty() &&
|
|
!efd.selectedFiles()[0].isEmpty() )
|
|
{
|
|
|
|
QString exportFileName = efd.selectedFiles()[0];
|
|
if ( !multiExport )
|
|
{
|
|
int stx = efd.selectedNameFilter().indexOf( "(*." );
|
|
int etx = efd.selectedNameFilter().indexOf( ")" );
|
|
|
|
if ( stx > 0 && etx > stx )
|
|
{
|
|
// Get first extension from selected dropdown.
|
|
// i.e. ".wav" from "WAV-File (*.wav), Dummy-File (*.dum)"
|
|
suffix = efd.selectedNameFilter().mid( stx + 2, etx - stx - 2 ).split( " " )[0].trimmed();
|
|
|
|
Qt::CaseSensitivity cs = Qt::CaseSensitive;
|
|
#if defined(LMMS_BUILD_APPLE) || defined(LMMS_BUILD_WIN32)
|
|
cs = Qt::CaseInsensitive;
|
|
#endif
|
|
exportFileName.remove( "." + suffix, cs );
|
|
if ( efd.selectedFiles()[0].endsWith( suffix ) )
|
|
{
|
|
if( VersionedSaveDialog::fileExistsQuery( exportFileName + suffix,
|
|
tr( "Save project" ) ) )
|
|
{
|
|
exportFileName += suffix;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ExportProjectDialog epd( exportFileName, getGUI()->mainWindow(), multiExport );
|
|
epd.exec();
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleSaveResult(QString const & filename, bool songSavedSuccessfully)
|
|
{
|
|
if (songSavedSuccessfully)
|
|
{
|
|
TextFloat::displayMessage( tr( "Project saved" ), tr( "The project %1 is now saved.").arg( filename ),
|
|
embed::getIconPixmap( "project_save", 24, 24 ), 2000 );
|
|
ConfigManager::inst()->addRecentlyOpenedProject(filename);
|
|
resetWindowTitle();
|
|
}
|
|
else
|
|
{
|
|
TextFloat::displayMessage( tr( "Project NOT saved." ), tr( "The project %1 was not saved!" ).arg(filename),
|
|
embed::getIconPixmap( "error" ), 4000 );
|
|
}
|
|
}
|
|
|
|
bool MainWindow::guiSaveProject()
|
|
{
|
|
Song * song = Engine::getSong();
|
|
bool const songSaveResult = song->guiSaveProject();
|
|
handleSaveResult(song->projectFileName(), songSaveResult);
|
|
|
|
return songSaveResult;
|
|
}
|
|
|
|
bool MainWindow::guiSaveProjectAs( const QString & filename )
|
|
{
|
|
Song * song = Engine::getSong();
|
|
bool const songSaveResult = song->guiSaveProjectAs(filename);
|
|
handleSaveResult(filename, songSaveResult);
|
|
|
|
return songSaveResult;
|
|
}
|
|
|
|
void MainWindow::onExportProject()
|
|
{
|
|
this->exportProject();
|
|
}
|
|
|
|
void MainWindow::onExportProjectTracks()
|
|
{
|
|
this->exportProject(true);
|
|
}
|
|
|
|
void MainWindow::onImportProject()
|
|
{
|
|
Song * song = Engine::getSong();
|
|
|
|
if (song)
|
|
{
|
|
FileDialog ofd( nullptr, tr( "Import file" ),
|
|
ConfigManager::inst()->userProjectsDir(),
|
|
tr("MIDI sequences") +
|
|
" (*.mid *.midi *.rmi);;" +
|
|
tr("Hydrogen projects") +
|
|
" (*.h2song);;" +
|
|
tr("All file types") +
|
|
" (*.*)");
|
|
|
|
ofd.setFileMode( FileDialog::ExistingFiles );
|
|
if( ofd.exec () == QDialog::Accepted && !ofd.selectedFiles().isEmpty() )
|
|
{
|
|
ImportFilter::import( ofd.selectedFiles()[0], song );
|
|
}
|
|
|
|
song->setLoadOnLaunch(false);
|
|
}
|
|
}
|
|
|
|
void MainWindow::onSongModified()
|
|
{
|
|
// Only update the window title if the code is executed from the GUI main thread.
|
|
// The assumption seems to be that the Song can also be set as modified from other
|
|
// threads. This is not a good design! Copied from the original implementation of
|
|
// Song::setModified.
|
|
if(QThread::currentThread() == this->thread())
|
|
{
|
|
this->resetWindowTitle();
|
|
}
|
|
}
|
|
|
|
void MainWindow::onProjectFileNameChanged()
|
|
{
|
|
this->resetWindowTitle();
|
|
}
|
|
|
|
|
|
MainWindow::MovableQMdiArea::MovableQMdiArea(QWidget* parent) :
|
|
QMdiArea(parent),
|
|
m_isBeingMoved(false),
|
|
m_lastX(0),
|
|
m_lastY(0)
|
|
{}
|
|
|
|
void MainWindow::MovableQMdiArea::mousePressEvent(QMouseEvent* event)
|
|
{
|
|
m_lastX = event->x();
|
|
m_lastY = event->y();
|
|
m_isBeingMoved = true;
|
|
setCursor(Qt::ClosedHandCursor);
|
|
}
|
|
|
|
void MainWindow::MovableQMdiArea::mouseMoveEvent(QMouseEvent* event)
|
|
{
|
|
if (m_isBeingMoved == false) { return; }
|
|
|
|
int minXBoundary = window()->width() - 100;
|
|
int maxXBoundary = 100;
|
|
int minYBoundary = window()->height() - 100;
|
|
int maxYBoundary = 100;
|
|
|
|
int minX = minXBoundary;
|
|
int maxX = maxXBoundary;
|
|
int minY = minYBoundary;
|
|
int maxY = maxYBoundary;
|
|
|
|
auto subWindows = subWindowList();
|
|
for (auto* curWindow : subWindows)
|
|
{
|
|
if (curWindow->isVisible())
|
|
{
|
|
minX = std::min(minX, curWindow->x());
|
|
maxX = std::max(maxX, curWindow->x() + curWindow->width());
|
|
minY = std::min(minY, curWindow->y());
|
|
maxY = std::max(maxY, curWindow->y() + curWindow->height());
|
|
}
|
|
}
|
|
|
|
int scrollX = m_lastX - event->x();
|
|
int scrollY = m_lastY - event->y();
|
|
|
|
scrollX = scrollX < 0 && minX >= minXBoundary ? 0 : scrollX;
|
|
scrollX = scrollX > 0 && maxX <= maxXBoundary ? 0 : scrollX;
|
|
scrollY = scrollY < 0 && minY >= minYBoundary ? 0 : scrollY;
|
|
scrollY = scrollY > 0 && maxY <= maxYBoundary ? 0 : scrollY;
|
|
|
|
for (auto* curWindow : subWindows)
|
|
{
|
|
// if widgets are maximized, then they shouldn't be moved
|
|
// moving a maximized window's normalGeometry is not implemented because of difficulties
|
|
if (curWindow->isMaximized() == false)
|
|
{
|
|
curWindow->move(curWindow->x() - scrollX, curWindow->y() - scrollY);
|
|
}
|
|
}
|
|
|
|
m_lastX = event->x();
|
|
m_lastY = event->y();
|
|
}
|
|
|
|
void MainWindow::MovableQMdiArea::mouseReleaseEvent(QMouseEvent* event)
|
|
{
|
|
setCursor(Qt::ArrowCursor);
|
|
m_isBeingMoved = false;
|
|
}
|
|
|
|
} // namespace lmms::gui
|