diff --git a/include/ConfigManager.h b/include/ConfigManager.h index 3cba834e198..1414bf3fbcc 100644 --- a/include/ConfigManager.h +++ b/include/ConfigManager.h @@ -1,26 +1,26 @@ /* - * ConfigManager.h - class ConfigManager, a class for managing LMMS-configuration - * - * Copyright (c) 2005-2008 Tobias Doerffel - * - * 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. - * - */ +* ConfigManager.h - class ConfigManager, a class for managing LMMS-configuration +* +* Copyright (c) 2005-2008 Tobias Doerffel +* +* 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. +* +*/ #ifndef LMMS_CONFIG_MANAGER_H #define LMMS_CONFIG_MANAGER_H @@ -56,256 +56,266 @@ const QString PORTABLE_MODE_FILE = "/portable_mode.txt"; class LMMS_EXPORT ConfigManager : public QObject { - Q_OBJECT + Q_OBJECT - using UpgradeMethod = void(ConfigManager::*)(); + using UpgradeMethod = void(ConfigManager::*)(); public: - static inline ConfigManager * inst() - { - if(s_instanceOfMe == nullptr ) - { - s_instanceOfMe = new ConfigManager(); - } - return s_instanceOfMe; - } + static inline ConfigManager * inst() + { + if(s_instanceOfMe == nullptr ) + { + s_instanceOfMe = new ConfigManager(); + } + return s_instanceOfMe; + } - const QString & workingDir() const - { - return m_workingDir; - } + const QString & workingDir() const + { + return m_workingDir; + } - void initPortableWorkingDir(); + void initPortableWorkingDir(); - void initInstalledWorkingDir(); + void initInstalledWorkingDir(); - void initDevelopmentWorkingDir(); + void initDevelopmentWorkingDir(); - const QString & dataDir() const - { - return m_dataDir; - } + const QString & dataDir() const + { + return m_dataDir; + } - QString factoryProjectsDir() const - { - return dataDir() + PROJECTS_PATH; - } + QString factoryProjectsDir() const + { + return dataDir() + PROJECTS_PATH; + } - QString factoryTemplatesDir() const - { - return factoryProjectsDir() + TEMPLATE_PATH; - } + QString factoryTemplatesDir() const + { + return factoryProjectsDir() + TEMPLATE_PATH; + } - QString factoryPresetsDir() const - { - return dataDir() + PRESETS_PATH; - } + QString factoryPresetsDir() const + { + return dataDir() + PRESETS_PATH; + } - QString factorySamplesDir() const - { - return dataDir() + SAMPLES_PATH; - } + QString factorySamplesDir() const + { + return dataDir() + SAMPLES_PATH; + } - QString userProjectsDir() const - { - return workingDir() + PROJECTS_PATH; - } + QString userProjectsDir() const + { + return workingDir() + PROJECTS_PATH; + } - QString userTemplateDir() const - { - return workingDir() + TEMPLATE_PATH; - } + QString userTemplateDir() const + { + return workingDir() + TEMPLATE_PATH; + } - QString userPresetsDir() const - { - return workingDir() + PRESETS_PATH; - } + QString userPresetsDir() const + { + return workingDir() + PRESETS_PATH; + } - QString userSamplesDir() const - { - return workingDir() + SAMPLES_PATH; - } + QString userSamplesDir() const + { + return workingDir() + SAMPLES_PATH; + } - const QString & vstDir() const - { - return m_vstDir; - } + const QString & vstDir() const + { + return m_vstDir; + } - const QString & ladspaDir() const - { - return m_ladspaDir; - } + const QString & ladspaDir() const + { + return m_ladspaDir; + } - const QString & sf2Dir() const - { - return m_sf2Dir; - } + const QString & sf2Dir() const + { + return m_sf2Dir; + } #ifdef LMMS_HAVE_FLUIDSYNTH - const QString & sf2File() const - { - return m_sf2File; - } + const QString & sf2File() const + { + return m_sf2File; + } #endif #ifdef LMMS_HAVE_STK - const QString & stkDir() const - { - return m_stkDir; - } + const QString & stkDir() const + { + return m_stkDir; + } #endif - const QString & gigDir() const - { - return m_gigDir; - } - - - QString userVstDir() const - { - return m_vstDir; - } - - QString userLadspaDir() const - { - return workingDir() + LADSPA_PATH; - } - - QString userSf2Dir() const - { - return workingDir() + SF2_PATH; - } - - QString userGigDir() const - { - return workingDir() + GIG_PATH; - } - - QString defaultThemeDir() const - { - return m_dataDir + DEFAULT_THEME_PATH; - } - - QString themeDir() const - { - return m_themeDir; - } - - const QString & backgroundPicFile() const - { - return m_backgroundPicFile; - } - - QString trackIconsDir() const - { - return m_dataDir + TRACK_ICON_PATH; - } - - const QString recoveryFile() const - { - return m_workingDir + "recover.mmp"; - } - - inline const QStringList & recentlyOpenedProjects() const - { - return m_recentlyOpenedProjects; - } - - QString localeDir() const - { - return m_dataDir + LOCALE_PATH; - } - - const QString & version() const - { - return m_version; - } - - // Used when the configversion attribute is not present in a configuration file. - // Returns the appropriate config file version based on the LMMS version. - unsigned int legacyConfigVersion(); - - QString defaultVersion() const; + const QString & gigDir() const + { + return m_gigDir; + } + + + QString userVstDir() const + { + return m_vstDir; + } + + QString userLadspaDir() const + { + return workingDir() + LADSPA_PATH; + } + + QString userSf2Dir() const + { + return workingDir() + SF2_PATH; + } + + QString userGigDir() const + { + return workingDir() + GIG_PATH; + } + + QString defaultThemeDir() const + { + return m_dataDir + DEFAULT_THEME_PATH; + } + + QString themeDir() const + { + return m_themeDir; + } + + const QString & backgroundPicFile() const + { + return m_backgroundPicFile; + } + + QString trackIconsDir() const + { + return m_dataDir + TRACK_ICON_PATH; + } + + const QString recoveryFile() const + { + return m_workingDir + "recover.mmp"; + } + + inline const QStringList & recentlyOpenedProjects() const + { + return m_recentlyOpenedProjects; + } + + inline const QStringList & starredItems() const + { + return m_starredItems; + } + + bool isStarred(QString& path) const; + + QString localeDir() const + { + return m_dataDir + LOCALE_PATH; + } + + const QString & version() const + { + return m_version; + } + + // Used when the configversion attribute is not present in a configuration file. + // Returns the appropriate config file version based on the LMMS version. + unsigned int legacyConfigVersion(); + + QString defaultVersion() const; static bool enableBlockedPlugins(); - static QStringList availableVstEmbedMethods(); - QString vstEmbedMethod() const; + static QStringList availableVstEmbedMethods(); + QString vstEmbedMethod() const; - // Returns true if the working dir (e.g. ~/lmms) exists on disk. - bool hasWorkingDir() const; + // Returns true if the working dir (e.g. ~/lmms) exists on disk. + bool hasWorkingDir() const; - void addRecentlyOpenedProject(const QString & _file); + void addRecentlyOpenedProject(const QString & _file); + void addStarredItem(const QString& item); + void removeStarredItem(const QString& item); - QString value(const QString& cls, const QString& attribute, const QString& defaultVal = "") const; + QString value(const QString& cls, const QString& attribute, const QString& defaultVal = "") const; - void setValue(const QString & cls, const QString & attribute, - const QString & value); - void deleteValue(const QString & cls, const QString & attribute); + void setValue(const QString & cls, const QString & attribute, + const QString & value); + void deleteValue(const QString & cls, const QString & attribute); - void loadConfigFile(const QString & configFile = ""); - void saveConfigFile(); + void loadConfigFile(const QString & configFile = ""); + void saveConfigFile(); - void setWorkingDir(const QString & workingDir); - void setVSTDir(const QString & vstDir); - void setLADSPADir(const QString & ladspaDir); - void setSF2Dir(const QString & sf2Dir); - void setSF2File(const QString & sf2File); - void setSTKDir(const QString & stkDir); - void setGIGDir(const QString & gigDir); - void setThemeDir(const QString & themeDir); - void setBackgroundPicFile(const QString & backgroundPicFile); + void setWorkingDir(const QString & workingDir); + void setVSTDir(const QString & vstDir); + void setLADSPADir(const QString & ladspaDir); + void setSF2Dir(const QString & sf2Dir); + void setSF2File(const QString & sf2File); + void setSTKDir(const QString & stkDir); + void setGIGDir(const QString & gigDir); + void setThemeDir(const QString & themeDir); + void setBackgroundPicFile(const QString & backgroundPicFile); - // Creates the working directory & subdirectories on disk. - void createWorkingDir(); + // Creates the working directory & subdirectories on disk. + void createWorkingDir(); signals: - void valueChanged( QString cls, QString attribute, QString value ); + void valueChanged( QString cls, QString attribute, QString value ); private: - static ConfigManager * s_instanceOfMe; + static ConfigManager * s_instanceOfMe; - ConfigManager(); - ConfigManager(const ConfigManager & _c); - ~ConfigManager() override; + ConfigManager(); + ConfigManager(const ConfigManager & _c); + ~ConfigManager() override; - void upgrade_1_1_90(); - void upgrade_1_1_91(); - void upgrade_1_2_2(); - void upgrade(); + void upgrade_1_1_90(); + void upgrade_1_1_91(); + void upgrade_1_2_2(); + void upgrade(); - // List of all upgrade methods - static const std::vector UPGRADE_METHODS; + // List of all upgrade methods + static const std::vector UPGRADE_METHODS; - QString m_workingDir; - QString m_dataDir; - QString m_vstDir; - QString m_ladspaDir; - QString m_sf2Dir; + QString m_workingDir; + QString m_dataDir; + QString m_vstDir; + QString m_ladspaDir; + QString m_sf2Dir; #ifdef LMMS_HAVE_FLUIDSYNTH - QString m_sf2File; + QString m_sf2File; #endif #ifdef LMMS_HAVE_STK - QString m_stkDir; + QString m_stkDir; #endif - QString m_gigDir; - QString m_themeDir; - QString m_backgroundPicFile; - QString m_lmmsRcFile; - QString m_version; - unsigned int m_configVersion; - QStringList m_recentlyOpenedProjects; + QString m_gigDir; + QString m_themeDir; + QString m_backgroundPicFile; + QString m_lmmsRcFile; + QString m_version; + unsigned int m_configVersion; + QStringList m_recentlyOpenedProjects; + QStringList m_starredItems; - using stringPairVector = std::vector>; - using settingsMap = QMap; - settingsMap m_settings; + using stringPairVector = std::vector>; + using settingsMap = QMap; + settingsMap m_settings; - friend class Engine; + friend class Engine; }; diff --git a/include/FileBrowser.h b/include/FileBrowser.h index 9193da5e405..e8f9d26158d 100644 --- a/include/FileBrowser.h +++ b/include/FileBrowser.h @@ -1,26 +1,26 @@ /* - * FileBrowser.h - include file for FileBrowser - * - * Copyright (c) 2004-2014 Tobias Doerffel - * - * 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. - * - */ +* FileBrowser.h - include file for FileBrowser +* +* Copyright (c) 2004-2014 Tobias Doerffel +* +* 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. +* +*/ #ifndef LMMS_GUI_FILE_BROWSER_H #define LMMS_GUI_FILE_BROWSER_H @@ -35,10 +35,10 @@ #include "embed.h" #if (QT_VERSION >= QT_VERSION_CHECK(5,14,0)) - #include +#include #endif -#include +#include "MainWindow.h" #include "SideBarWidget.h" #include "lmmsconfig.h" @@ -59,144 +59,172 @@ class FileBrowserTreeWidget; class FileBrowser : public SideBarWidget { - Q_OBJECT + Q_OBJECT public: - /** - Create a file browser side bar widget - @param directories '*'-separated list of directories to search for. - If a directory of factory files should be in the list it - must be the last one (for the factory files delimiter to work) - @param filter Filter as used in QDir::match - @param recurse *to be documented* - */ - FileBrowser( const QString & directories, const QString & filter, - const QString & title, const QPixmap & pm, - QWidget * parent, bool dirs_as_items = false, - const QString& userDir = "", - const QString& factoryDir = ""); - - ~FileBrowser() override = default; - - static QStringList excludedPaths() - { - static auto s_excludedPaths = QStringList{ + /** + Create a file browser side bar widget + @param directories '*'-separated list of directories to search for. + If a directory of factory files should be in the list it + must be the last one (for the factory files delimiter to work) + @param filter Filter as used in QDir::match + @param title Title of the FileBrowser + @param pm QPixmap icon to be shown in the sidebar + @param parent Parent object + @param dirs_as_items //TODO *to be documented* + @param userDir Directory containing user content only. enables user content checbox + @param factoryDir Directory containing LMMS provided content only. enables factory content checbox + @param showDirs //TODO * to be documented* + @param items List of items (files or folders) to be shown. + */ + FileBrowser( const QString & directories, const QString & filter, + const QString & title, const QPixmap & pm, + QWidget * parent, bool dirs_as_items = false, + const QString& userDir = "", + const QString& factoryDir = "", + const QStringList & items = {}, + const bool showDirs = true + ); + + ~FileBrowser() override = default; + + static QStringList excludedPaths() + { + static auto s_excludedPaths = QStringList{ #ifdef LMMS_BUILD_LINUX - "/bin", "/boot", "/dev", "/etc", "/proc", "/run", "/sbin", - "/sys" + "/bin", "/boot", "/dev", "/etc", "/proc", "/run", "/sbin", + "/sys" #endif #ifdef LMMS_BUILD_WIN32 - "C:\\Windows" + "C:\\Windows" #endif - }; - return s_excludedPaths; - } - static QDir::Filters dirFilters() { return QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden; } - static QDir::SortFlags sortFlags() { return QDir::LocaleAware | QDir::DirsFirst | QDir::Name | QDir::IgnoreCase; } + }; + return s_excludedPaths; + } + static QDir::Filters dirFilters() { return QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden; } + static QDir::SortFlags sortFlags() { return QDir::LocaleAware | QDir::DirsFirst | QDir::Name | QDir::IgnoreCase; } + + [[nodiscard]] inline QString directories() { + return m_directories; + } + [[nodiscard]] inline QString userDir() { + return m_userDir; + } + + QStringList m_items; + +public slots: + void reloadTree(); private slots: - void reloadTree(); - void expandItems(const QList& expandedDirs, QTreeWidgetItem* item = nullptr); - void giveFocusToFilter(); + void expandItems(const QList& expandedDirs, QTreeWidgetItem* item = nullptr); + void giveFocusToFilter(); private: - void keyPressEvent( QKeyEvent * ke ) override; + void keyPressEvent(QKeyEvent * ke) override; - void addItems( const QString & path ); + void addEntry(const QFileInfo & entry, const QString & path); + void addItems(const QString & path); - void saveDirectoriesStates(); - void restoreDirectoriesStates(); + void saveDirectoriesStates(); + void restoreDirectoriesStates(); - void foundSearchMatch(FileSearch* search, const QString& match); - void searchCompleted(FileSearch* search); - void onSearch(const QString& filter); - void displaySearch(bool on); + void foundSearchMatch(FileSearch* search, const QString& match); + void searchCompleted(FileSearch* search); + void onSearch(const QString& filter); + void displaySearch(bool on); - void addContentCheckBox(); + void addContentCheckBox(); - FileBrowserTreeWidget * m_fileBrowserTreeWidget; - FileBrowserTreeWidget * m_searchTreeWidget; + FileBrowserTreeWidget * m_fileBrowserTreeWidget; + FileBrowserTreeWidget * m_searchTreeWidget; - QLineEdit * m_filterEdit; + QLineEdit * m_filterEdit; - std::shared_ptr m_currentSearch; - QProgressBar* m_searchIndicator = nullptr; + std::shared_ptr m_currentSearch; + QProgressBar* m_searchIndicator = nullptr; - QString m_directories; //!< Directories to search, split with '*' - QString m_filter; //!< Filter as used in QDir::match() + QString m_directories = ""; //!< Directories to search, split with '*' + QString m_filter; //!< Filter as used in QDir::match() - bool m_dirsAsItems; + bool m_dirsAsItems; - QCheckBox* m_showUserContent = nullptr; - QCheckBox* m_showFactoryContent = nullptr; - QCheckBox* m_showHiddenContent = nullptr; + QCheckBox* m_showUserContent = nullptr; + QCheckBox* m_showFactoryContent = nullptr; + QCheckBox* m_showHiddenContent = nullptr; - QBoxLayout *filterWidgetLayout = nullptr; - QBoxLayout *hiddenWidgetLayout = nullptr; - QBoxLayout *outerLayout = nullptr; - QString m_userDir; - QString m_factoryDir; - QList m_savedExpandedDirs; - QString m_previousFilterValue; + QBoxLayout *filterWidgetLayout = nullptr; + QBoxLayout *hiddenWidgetLayout = nullptr; + QBoxLayout *outerLayout = nullptr; + QString m_userDir = ""; + QString m_factoryDir = ""; + QList m_savedExpandedDirs; + QString m_previousFilterValue; } ; - +class Directory; class FileBrowserTreeWidget : public QTreeWidget { - Q_OBJECT + Q_OBJECT public: - FileBrowserTreeWidget( QWidget * parent ); - ~FileBrowserTreeWidget() override = default; + FileBrowserTreeWidget( FileBrowser * fileBrowser); + ~FileBrowserTreeWidget() override = default; - //! This method returns a QList with paths (QString's) of all directories - //! that are expanded in the tree. - QList expandedDirs( QTreeWidgetItem * item = nullptr ) const; + //! This method returns a QList with paths (QString's) of all directories + //! that are expanded in the tree. + QList expandedDirs( QTreeWidgetItem * item = nullptr ) const; protected: - void contextMenuEvent( QContextMenuEvent * e ) override; - void mousePressEvent( QMouseEvent * me ) override; - void mouseMoveEvent( QMouseEvent * me ) override; - void mouseReleaseEvent( QMouseEvent * me ) override; - void keyPressEvent( QKeyEvent * ke ) override; - void keyReleaseEvent( QKeyEvent * ke ) override; - void hideEvent( QHideEvent * he ) override; - void focusOutEvent( QFocusEvent * fe ) override; + void contextMenuEvent( QContextMenuEvent * e ) override; + void mousePressEvent( QMouseEvent * me ) override; + void mouseMoveEvent( QMouseEvent * me ) override; + void mouseReleaseEvent( QMouseEvent * me ) override; + void keyPressEvent( QKeyEvent * ke ) override; + void keyReleaseEvent( QKeyEvent * ke ) override; + void hideEvent( QHideEvent * he ) override; + void focusOutEvent( QFocusEvent * fe ) override; private: - //! Start a preview of a file item - void previewFileItem(FileItem* file); - //! If a preview is playing, stop it. - void stopPreview(); + //! Start a preview of a file item + void previewFileItem(FileItem* file); + //! If a preview is playing, stop it. + void stopPreview(); - void handleFile( FileItem * fi, InstrumentTrack * it ); - void openInNewInstrumentTrack( TrackContainer* tc, FileItem* item ); + static void handleFile( FileItem * f, InstrumentTrack * it ); + void openInNewInstrumentTrack( TrackContainer* tc, FileItem* item ); - bool m_mousePressed; - QPoint m_pressPos; + FileBrowser* m_parentBrowser; + bool m_mousePressed; + QPoint m_pressPos; + + //! This should only be accessed or modified when m_pphMutex is held + PlayHandle* m_previewPlayHandle; - //! This should only be accessed or modified when m_pphMutex is held - PlayHandle* m_previewPlayHandle; - #if (QT_VERSION >= QT_VERSION_CHECK(5,14,0)) - QRecursiveMutex m_pphMutex; + QRecursiveMutex m_pphMutex; #else - QMutex m_pphMutex; + QMutex m_pphMutex; #endif - QList getContextActions(FileItem* item, bool songEditor); + QList getContextActions(FileItem* item, bool songEditor); private slots: - void activateListItem( QTreeWidgetItem * item, int column ); - void openInNewInstrumentTrack( lmms::gui::FileItem* item, bool songEditor ); - bool openInNewSampleTrack( lmms::gui::FileItem* item ); - void sendToActiveInstrumentTrack( lmms::gui::FileItem* item ); - void updateDirectory( QTreeWidgetItem * item ); - void openContainingFolder( lmms::gui::FileItem* item ); - + void activateListItem( QTreeWidgetItem * item, int column ); + void openInNewInstrumentTrack( lmms::gui::FileItem* item, bool songEditor ); + bool openInNewSampleTrack( lmms::gui::FileItem* item ); + void sendToActiveInstrumentTrack( lmms::gui::FileItem* item ); + void updateDirectory( QTreeWidgetItem * item ); + void openDirectory( Directory* directory ); + void openContainingFolder( lmms::gui::FileItem* item ); + static bool supportsSelectOption(const QString &fileManager) { + return fileManager == "nautilus" || fileManager == "dolphin" || fileManager == "thunar" || + fileManager == "pcmanfm" || fileManager == "nemo" || fileManager == "caja" || + fileManager == "io.elementary.files" || fileManager == "spacefm" || fileManager == "pcmanfm-qt"; + } } ; @@ -204,50 +232,52 @@ private slots: class Directory : public QTreeWidgetItem { public: - Directory(const QString& filename, const QString& path, const QString& filter, bool disableEntryPopulation = false); - - void update(); - - inline QString fullName( QString path = QString() ) - { - if( path.isEmpty() ) - { - path = m_directories[0]; - } - if( ! path.isEmpty() ) - { - path += QDir::separator(); - } - return( QDir::cleanPath( path + text( 0 ) ) + - QDir::separator() ); - } - - inline void addDirectory( const QString & dir ) - { - m_directories.push_back( dir ); - } + Directory(const QString& filename, const QString& path, const QString& filter, bool disableEntryPopulation = false); + Directory(QDir dir, const QString& filter, bool disableEntryPopulation = false); + + void update(); + + inline QString fullName( QString path = QString() ) + { + if( path.isEmpty() ) + { + path = m_directories[0]; + } + if( ! path.isEmpty() ) + { + path += QDir::separator(); + } + return( QDir::cleanPath( path + text( 0 ) ) + + QDir::separator() ); + } + + inline void addDirectory( const QString & dir ) + { + m_directories.push_back( dir ); + } + private: - bool addItems( const QString & path ); + bool addItems(const QString & path); - QPixmap m_folderPixmap = embed::getIconPixmap("folder"); - QPixmap m_folderOpenedPixmap = embed::getIconPixmap("folder_opened"); - QPixmap m_folderLockedPixmap = embed::getIconPixmap("folder_locked"); + QPixmap m_folderPixmap = embed::getIconPixmap("folder"); + QPixmap m_folderOpenedPixmap = embed::getIconPixmap("folder_opened"); + QPixmap m_folderLockedPixmap = embed::getIconPixmap("folder_locked"); - //! Directories that lead here - //! Initially, this is just set to the current path of a directory - //! If, however, you have e.g. 'TripleOscillator/xyz' in two of the - //! file browser's search directories 'a' and 'b', this will have two - //! entries 'a/TripleOscillator' and 'b/TripleOscillator' - //! and 'xyz' in the tree widget - QStringList m_directories; - //! Filter as used in QDir::match() - QString m_filter; + //! Directories that lead here + //! Initially, this is just set to the current path of a directory + //! If, however, you have e.g. 'TripleOscillator/xyz' in two of the + //! file browser's search directories 'a' and 'b', this will have two + //! entries 'a/TripleOscillator' and 'b/TripleOscillator' + //! and 'xyz' in the tree widget + QStringList m_directories; + //! Filter as used in QDir::match() + QString m_filter; - int m_dirCount; - bool m_disableEntryPopulation = false; + int m_dirCount; + bool m_disableEntryPopulation = false; } ; @@ -256,64 +286,63 @@ class Directory : public QTreeWidgetItem class FileItem : public QTreeWidgetItem { public: - enum class FileType - { - Project, - Preset, - Sample, - SoundFont, - Patch, - Midi, - VstPlugin, - Unknown - } ; - - enum class FileHandling - { - NotSupported, - LoadAsProject, - LoadAsPreset, - LoadByPlugin, - ImportAsProject - } ; - - - FileItem( QTreeWidget * parent, const QString & name, - const QString & path ); - FileItem( const QString & name, const QString & path ); - - QString fullName() const - { - return QFileInfo(m_path, text(0)).absoluteFilePath(); - } - - inline FileType type() const - { - return( m_type ); - } - - inline FileHandling handling() const - { - return( m_handling ); - } - - inline bool isTrack() const - { - return m_handling == FileHandling::LoadAsPreset || m_handling == FileHandling::LoadByPlugin; - } - - QString extension(); - static QString extension( const QString & file ); - static QString defaultFilters(); - + enum class FileType + { + Project, + Preset, + Sample, + SoundFont, + Patch, + Midi, + VstPlugin, + Unknown + } ; + + enum class FileHandling + { + NotSupported, + LoadAsProject, + LoadAsPreset, + LoadByPlugin, + ImportAsProject + } ; + + + FileItem( QTreeWidget * parent, const QString & name, + const QString & path ); + FileItem( const QString & name, const QString & path ); + + QString fullName() const + { + return QFileInfo(m_path, text(0)).absoluteFilePath(); + } + + inline FileType type() const + { + return( m_type ); + } + + inline FileHandling handling() const + { + return( m_handling ); + } + + inline bool isTrack() const + { + return m_handling == FileHandling::LoadAsPreset || m_handling == FileHandling::LoadByPlugin; + } + + QString extension() const; + static QString extension( const QString & file ); + static QString defaultFilters(); private: - void initPixmaps(); - void determineFileType(); + void initPixmaps(); + void determineFileType(); - QString m_path; - FileType m_type; - FileHandling m_handling; + QString m_path; + FileType m_type; + FileHandling m_handling; } ; diff --git a/include/MainWindow.h b/include/MainWindow.h index 4442a7ac252..5b455579347 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -1,36 +1,39 @@ /* - * MainWindow.h - declaration of class MainWindow, the main window of LMMS - * - * Copyright (c) 2004-2014 Tobias Doerffel - * - * 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. - * - */ +* MainWindow.h - declaration of class MainWindow, the main window of LMMS +* +* Copyright (c) 2004-2014 Tobias Doerffel +* +* 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. +* +*/ #ifndef LMMS_GUI_MAIN_WINDOW_H #define LMMS_GUI_MAIN_WINDOW_H #include -#include #include #include +#include +#include #include "ConfigManager.h" +#include "SideBar.h" +#include "FileBrowser.h" class QAction; class QDomElement; @@ -49,212 +52,221 @@ class PluginView; class SubWindow; class ToolButton; class GuiApplication; - +class Directory; +class FileBrowser; class MainWindow : public QMainWindow { - Q_OBJECT + Q_OBJECT public: - QMdiArea* workspace() - { - return m_workspace; - } - - QWidget* toolBar() - { - return m_toolBar; - } - - int addWidgetToToolBar( QWidget * _w, int _row = -1, int _col = -1 ); - void addSpacingToToolBar( int _size ); - - // wrap the widget with a window decoration and add it to the workspace - LMMS_EXPORT SubWindow* addWindowedWidget(QWidget *w, Qt::WindowFlags windowFlags = QFlag(0)); - - - /// - /// \brief Asks whether changes made to the project are to be saved. - /// - /// Opens a dialog giving the user the choice to (a) confirm his choice - /// (such as opening a new file), (b) save the current project before - /// proceeding or (c) cancel the process. - /// - /// Every function that replaces the current file (e.g. creates new file, - /// opens another file...) must call this before and may only proceed if - /// this function returns true. - /// - /// \param stopPlayback whether playback should be stopped upon prompting. If set to false, the caller should ensure that Engine::getSong()->stop() is called before unloading/loading a song. - /// - /// \return true if the user allows the software to proceed, false if they - /// cancel the action. - /// - bool mayChangeProject(bool stopPlayback); - - // Auto save timer intervals. The slider in SetupDialog.cpp wants - // minutes and the rest milliseconds. - static const int DEFAULT_SAVE_INTERVAL_MINUTES = 2; - static const int DEFAULT_AUTO_SAVE_INTERVAL = DEFAULT_SAVE_INTERVAL_MINUTES * 60 * 1000; - - static const int m_autoSaveShortTime = 10 * 1000; // 10s short loop - - void autoSaveTimerReset( int msec = ConfigManager::inst()-> - value( "ui", "saveinterval" ).toInt() - * 60 * 1000 ) - { - if( msec < m_autoSaveShortTime ) // No 'saveinterval' in .lmmsrc.xml - { - msec = DEFAULT_AUTO_SAVE_INTERVAL; - } - m_autoSaveTimer.start( msec ); - } - - int getAutoSaveTimerInterval() - { - return m_autoSaveTimer.interval(); - } - - enum class SessionState - { - Normal, - Recover - }; - - void setSession( SessionState session ) - { - m_session = session; - } - - SessionState getSession() - { - return m_session; - } - - void sessionCleanup(); - - void clearKeyModifiers(); - - // TODO Remove this function, since m_shift can get stuck down. - // [[deprecated]] - bool isShiftPressed() - { - return m_keyMods.m_shift; - } - - static void saveWidgetState( QWidget * _w, QDomElement & _de ); - static void restoreWidgetState( QWidget * _w, const QDomElement & _de ); - - bool eventFilter(QObject* watched, QEvent* event) override; - -public slots: - void resetWindowTitle(); - - void emptySlot(); - void createNewProject(); - void openProject(); - bool saveProject(); - bool saveProjectAs(); - bool saveProjectAsNewVersion(); - void saveProjectAsDefaultTemplate(); - void showSettingsDialog(); - void aboutLMMS(); - void help(); - void toggleAutomationEditorWin(); - void togglePatternEditorWin(bool forceShow = false); - void toggleSongEditorWin(); - void toggleProjectNotesWin(); - void toggleMicrotunerWin(); - void toggleMixerWin(); - void togglePianoRollWin(); - void toggleControllerRack(); - void toggleFullscreen(); - - void updatePlayPauseIcons(); - - void updateUndoRedoButtons(); - void undo(); - void redo(); - - void autoSave(); + QMdiArea* workspace() + { + return m_workspace; + } + + QWidget* toolBar() + { + return m_toolBar; + } + + int addWidgetToToolBar( QWidget * _w, int _row = -1, int _col = -1 ); + void addSpacingToToolBar( int _size ); + + // wrap the widget with a window decoration and add it to the workspace + LMMS_EXPORT SubWindow* addWindowedWidget(QWidget *w, Qt::WindowFlags windowFlags = QFlag(0)); + + + /// + /// \brief Asks whether changes made to the project are to be saved. + /// + /// Opens a dialog giving the user the choice to (a) confirm his choice + /// (such as opening a new file), (b) save the current project before + /// proceeding or (c) cancel the process. + /// + /// Every function that replaces the current file (e.g. creates new file, + /// opens another file...) must call this before and may only proceed if + /// this function returns true. + /// + /// \param stopPlayback whether playback should be stopped upon prompting. If set to false, the caller should ensure that Engine::getSong()->stop() is called before unloading/loading a song. + /// + /// \return true if the user allows the software to proceed, false if they + /// cancel the action. + /// + bool mayChangeProject(bool stopPlayback); + + // Auto save timer intervals. The slider in SetupDialog.cpp wants + // minutes and the rest milliseconds. + static const int DEFAULT_SAVE_INTERVAL_MINUTES = 2; + static const int DEFAULT_AUTO_SAVE_INTERVAL = DEFAULT_SAVE_INTERVAL_MINUTES * 60 * 1000; + + static const int m_autoSaveShortTime = 10 * 1000; // 10s short loop + + void autoSaveTimerReset( int msec = ConfigManager::inst()-> + value( "ui", "saveinterval" ).toInt() + * 60 * 1000 ) + { + if( msec < m_autoSaveShortTime ) // No 'saveinterval' in .lmmsrc.xml + { + msec = DEFAULT_AUTO_SAVE_INTERVAL; + } + m_autoSaveTimer.start( msec ); + } + + int getAutoSaveTimerInterval() + { + return m_autoSaveTimer.interval(); + } + + enum class SessionState + { + Normal, + Recover + }; + + void setSession( SessionState session ) + { + m_session = session; + } + + SessionState getSession() + { + return m_session; + } + + void sessionCleanup(); + + void clearKeyModifiers(); + + // TODO Remove this function, since m_shift can get stuck down. + // [[deprecated]] + bool isShiftPressed() + { + return m_keyMods.m_shift; + } + + static void saveWidgetState( QWidget * _w, QDomElement & _de ); + static void restoreWidgetState( QWidget * _w, const QDomElement & _de ); + + bool eventFilter(QObject* watched, QEvent* event) override; + + void starItem(QString item); + void unstarItem(QString item); + bool isStarred(QString item); + + public slots: + void resetWindowTitle(); + + void emptySlot(); + void createNewProject(); + void openProject(); + bool saveProject(); + bool saveProjectAs(); + bool saveProjectAsNewVersion(); + void saveProjectAsDefaultTemplate(); + void showSettingsDialog(); + void aboutLMMS(); + void help(); + void toggleAutomationEditorWin(); + void togglePatternEditorWin(bool forceShow = false); + void toggleSongEditorWin(); + void toggleProjectNotesWin(); + void toggleMicrotunerWin(); + void toggleMixerWin(); + void togglePianoRollWin(); + void toggleControllerRack(); + void toggleFullscreen(); + + void updatePlayPauseIcons(); + + void updateUndoRedoButtons(); + void undo(); + void redo(); + + void autoSave(); private slots: - void onExportProjectMidi(); + void onExportProjectMidi(); protected: - void closeEvent( QCloseEvent * _ce ) override; - void focusOutEvent( QFocusEvent * _fe ) override; - void keyPressEvent( QKeyEvent * _ke ) override; - void keyReleaseEvent( QKeyEvent * _ke ) override; - void timerEvent( QTimerEvent * _ev ) override; + void closeEvent( QCloseEvent * _ce ) override; + void focusOutEvent( QFocusEvent * _fe ) override; + void keyPressEvent( QKeyEvent * _ke ) override; + void keyReleaseEvent( QKeyEvent * _ke ) override; + void timerEvent( QTimerEvent * _ev ) override; private: - MainWindow(); - MainWindow( const MainWindow & ); - ~MainWindow() override; + MainWindow(); + MainWindow( const MainWindow & ); + ~MainWindow() override; - void finalize(); + void finalize(); - void toggleWindow( QWidget *window, bool forceShow = false ); - void refocus(); + void toggleWindow( QWidget *window, bool forceShow = false ); + void refocus(); - void exportProject(bool multiExport = false); - void handleSaveResult(QString const & filename, bool songSavedSuccessfully); - bool guiSaveProject(); - bool guiSaveProjectAs( const QString & filename ); + void exportProject(bool multiExport = false); + void handleSaveResult(QString const & filename, bool songSavedSuccessfully); + bool guiSaveProject(); + bool guiSaveProjectAs( const QString & filename ); - QMdiArea * m_workspace; + SideBar* m_sideBar; + FileBrowser* m_starredItemBrowser; - QWidget * m_toolBar; - QGridLayout * m_toolBarLayout; + QMdiArea * m_workspace; - struct keyModifiers - { - keyModifiers() : - m_ctrl( false ), - m_shift( false ), - m_alt( false ) - { - } - bool m_ctrl; - bool m_shift; - bool m_alt; - } m_keyMods; + QWidget * m_toolBar; + QGridLayout * m_toolBarLayout; + bool m_dirs_as_items = false; + QWidget* m_innerWidget; - QMenu * m_toolsMenu; - QAction * m_undoAction; - QAction * m_redoAction; - QList m_tools; + struct keyModifiers + { + keyModifiers() : + m_ctrl( false ), + m_shift( false ), + m_alt( false ) + { + } + bool m_ctrl; + bool m_shift; + bool m_alt; + } m_keyMods; - QBasicTimer m_updateTimer; - QTimer m_autoSaveTimer; - int m_autoSaveInterval; + QMenu * m_toolsMenu; + QAction * m_undoAction; + QAction * m_redoAction; + QList m_tools; - friend class GuiApplication; + QBasicTimer m_updateTimer; + QTimer m_autoSaveTimer; + int m_autoSaveInterval; - QMenu * m_viewMenu; + friend class GuiApplication; - ToolButton * m_metronomeToggle; + QMenu * m_viewMenu; - SessionState m_session; - - bool maximized; + ToolButton * m_metronomeToggle; + + SessionState m_session; + + bool maximized; private slots: - void browseHelp(); - void showTool( QAction * _idx ); - void updateViewMenu(); - void updateConfig( QAction * _who ); - void onToggleMetronome(); - void onExportProject(); - void onExportProjectTracks(); - void onImportProject(); - void onSongModified(); - void onProjectFileNameChanged(); + void browseHelp(); + void showTool( QAction * _idx ); + void updateViewMenu(); + void updateConfig( QAction * _who ); + void onToggleMetronome(); + void onExportProject(); + void onExportProjectTracks(); + void onImportProject(); + void onSongModified(); + void onProjectFileNameChanged(); signals: - void periodicUpdate(); - void initProgress(const QString &msg); - + void periodicUpdate(); + void initProgress(const QString &msg); } ; diff --git a/include/SideBarWidget.h b/include/SideBarWidget.h index caf955327aa..e5966842564 100644 --- a/include/SideBarWidget.h +++ b/include/SideBarWidget.h @@ -1,26 +1,26 @@ /* - * SideBarWidget.h - base-class for all side-bar-widgets - * - * Copyright (c) 2004-2009 Tobias Doerffel - * - * 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. - * - */ +* SideBarWidget.h - base-class for all side-bar-widgets +* +* Copyright (c) 2004-2009 Tobias Doerffel +* +* 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. +* +*/ #ifndef LMMS_GUI_SIDE_BAR_WIDGET_H #define LMMS_GUI_SIDE_BAR_WIDGET_H @@ -37,53 +37,53 @@ namespace lmms::gui class SideBarWidget : public QWidget { - Q_OBJECT + Q_OBJECT public: - SideBarWidget( const QString & _title, const QPixmap & _icon, - QWidget * _parent ); - ~SideBarWidget() override = default; - - inline const QPixmap & icon() const - { - return m_icon; - } - inline const QString & title() const - { - return m_title; - } + SideBarWidget( const QString & _title, const QPixmap & _icon, + QWidget * _parent ); + ~SideBarWidget() override = default; + + inline const QPixmap & icon() const + { + return m_icon; + } + inline const QString & title() const + { + return m_title; + } + + QWidget * contentParent() + { + return m_contents; + } signals: - void closeButtonClicked(); + void closeButtonClicked(); protected: - void paintEvent( QPaintEvent * _pe ) override; - void resizeEvent( QResizeEvent * _re ) override; - void contextMenuEvent( QContextMenuEvent * ) override - { - } - - QWidget * contentParent() - { - return m_contents; - } - - void addContentWidget( QWidget * _w ) - { - m_layout->addWidget( _w ); - } - - void addContentLayout( QLayout * _l ) - { - m_layout->addLayout( _l ); - } + void paintEvent( QPaintEvent * _pe ) override; + void resizeEvent( QResizeEvent * _re ) override; + void contextMenuEvent( QContextMenuEvent * ) override + { + } + + void addContentWidget( QWidget * _w ) + { + m_layout->addWidget( _w ); + } + + void addContentLayout( QLayout * _l ) + { + m_layout->addLayout( _l ); + } private: - QWidget * m_contents; - QVBoxLayout * m_layout; - QString m_title; - QPixmap m_icon; - QPushButton * m_closeBtn; - const QSize m_buttonSize; + QWidget * m_contents; + QVBoxLayout * m_layout; + QString m_title; + QPixmap m_icon; + QPushButton * m_closeBtn; + const QSize m_buttonSize; } ; diff --git a/src/core/ConfigManager.cpp b/src/core/ConfigManager.cpp index d3c973020bf..f2ec34309ef 100644 --- a/src/core/ConfigManager.cpp +++ b/src/core/ConfigManager.cpp @@ -1,40 +1,38 @@ /* - * ConfigManager.cpp - implementation of class ConfigManager - * - * Copyright (c) 2005-2014 Tobias Doerffel - * - * 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. - * - */ +* ConfigManager.cpp - implementation of class ConfigManager +* +* Copyright (c) 2005-2014 Tobias Doerffel +* +* 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 "ConfigManager.h" +#include #include -#include #include -#include #include #include -#include "ConfigManager.h" +#include "GuiApplication.h" #include "MainWindow.h" #include "ProjectVersion.h" -#include "GuiApplication.h" - #include "lmmsversion.h" namespace lmms @@ -43,17 +41,17 @@ namespace lmms // Vector with all the upgrade methods const std::vector ConfigManager::UPGRADE_METHODS = { - &ConfigManager::upgrade_1_1_90 , &ConfigManager::upgrade_1_1_91, - &ConfigManager::upgrade_1_2_2 + &ConfigManager::upgrade_1_1_90 , &ConfigManager::upgrade_1_1_91, + &ConfigManager::upgrade_1_2_2 }; static inline QString ensureTrailingSlash(const QString & s ) { - if(! s.isEmpty() && !s.endsWith('/') && !s.endsWith('\\')) - { - return s + '/'; - } - return s; + if(! s.isEmpty() && !s.endsWith('/') && !s.endsWith('\\')) + { + return s + '/'; + } + return s; } @@ -61,32 +59,32 @@ ConfigManager * ConfigManager::s_instanceOfMe = nullptr; ConfigManager::ConfigManager() : - m_version(defaultVersion()), - m_configVersion( UPGRADE_METHODS.size() ) -{ - if (QFileInfo::exists(qApp->applicationDirPath() + PORTABLE_MODE_FILE)) - { - initPortableWorkingDir(); - } - else - { - initInstalledWorkingDir(); - } - m_dataDir = "data:/"; - m_vstDir = m_workingDir + "vst/"; - m_sf2Dir = m_workingDir + SF2_PATH; - m_gigDir = m_workingDir + GIG_PATH; - m_themeDir = defaultThemeDir(); - if (std::getenv("LMMS_DATA_DIR")) - { - QDir::addSearchPath("data", QString::fromLocal8Bit(std::getenv("LMMS_DATA_DIR"))); - } - initDevelopmentWorkingDir(); + m_version(defaultVersion()), + m_configVersion( UPGRADE_METHODS.size() ) +{ + if (QFileInfo::exists(qApp->applicationDirPath() + PORTABLE_MODE_FILE)) + { + initPortableWorkingDir(); + } + else + { + initInstalledWorkingDir(); + } + m_dataDir = "data:/"; + m_vstDir = m_workingDir + "vst/"; + m_sf2Dir = m_workingDir + SF2_PATH; + m_gigDir = m_workingDir + GIG_PATH; + m_themeDir = defaultThemeDir(); + if (std::getenv("LMMS_DATA_DIR")) + { + QDir::addSearchPath("data", QString::fromLocal8Bit(std::getenv("LMMS_DATA_DIR"))); + } + initDevelopmentWorkingDir(); #ifdef LMMS_BUILD_WIN32 - QDir::addSearchPath("data", qApp->applicationDirPath() + "/data/"); + QDir::addSearchPath("data", qApp->applicationDirPath() + "/data/"); #else - QDir::addSearchPath("data", qApp->applicationDirPath().section('/', 0, -2) + "/share/lmms/"); + QDir::addSearchPath("data", qApp->applicationDirPath().section('/', 0, -2) + "/share/lmms/"); #endif } @@ -96,96 +94,96 @@ ConfigManager::ConfigManager() : ConfigManager::~ConfigManager() { - saveConfigFile(); + saveConfigFile(); } void ConfigManager::upgrade_1_1_90() { - // Remove trailing " (bad latency!)" string which was once saved with PulseAudio - if(value("mixer", "audiodev").startsWith("PulseAudio (")) - { - setValue("mixer", "audiodev", "PulseAudio"); - } - - // MidiAlsaRaw used to store the device info as "Device" instead of "device" - if (value("MidiAlsaRaw", "device").isNull()) - { - // copy "device" = "Device" and then delete the old "Device" (further down) - QString oldDevice = value("MidiAlsaRaw", "Device"); - setValue("MidiAlsaRaw", "device", oldDevice); - } - if (!value("MidiAlsaRaw", "device").isNull()) - { - // delete the old "Device" in the case that we just copied it to "device" - // or if the user somehow set both the "Device" and "device" fields - deleteValue("MidiAlsaRaw", "Device"); - } + // Remove trailing " (bad latency!)" string which was once saved with PulseAudio + if(value("mixer", "audiodev").startsWith("PulseAudio (")) + { + setValue("mixer", "audiodev", "PulseAudio"); + } + + // MidiAlsaRaw used to store the device info as "Device" instead of "device" + if (value("MidiAlsaRaw", "device").isNull()) + { + // copy "device" = "Device" and then delete the old "Device" (further down) + QString oldDevice = value("MidiAlsaRaw", "Device"); + setValue("MidiAlsaRaw", "device", oldDevice); + } + if (!value("MidiAlsaRaw", "device").isNull()) + { + // delete the old "Device" in the case that we just copied it to "device" + // or if the user somehow set both the "Device" and "device" fields + deleteValue("MidiAlsaRaw", "Device"); + } } void ConfigManager::upgrade_1_1_91() { - // rename displaydbv to displaydbfs - if (!value("app", "displaydbv").isNull()) - { - setValue("app", "displaydbfs", value("app", "displaydbv")); - deleteValue("app", "displaydbv"); - } + // rename displaydbv to displaydbfs + if (!value("app", "displaydbv").isNull()) + { + setValue("app", "displaydbfs", value("app", "displaydbv")); + deleteValue("app", "displaydbv"); + } } void ConfigManager::upgrade_1_2_2() { - // Since mixer has been renamed to audioengine, we need to transfer the - // attributes from the old element to the new one - std::vector attrs = { - "audiodev", "mididev", "framesperaudiobuffer", "hqaudio", "samplerate" - }; + // Since mixer has been renamed to audioengine, we need to transfer the + // attributes from the old element to the new one + std::vector attrs = { + "audiodev", "mididev", "framesperaudiobuffer", "hqaudio", "samplerate" + }; - for (auto attr : attrs) - { - if (!value("mixer", attr).isNull()) - { - setValue("audioengine", attr, value("mixer", attr)); - deleteValue("mixer", attr); - } - } + for (auto attr : attrs) + { + if (!value("mixer", attr).isNull()) + { + setValue("audioengine", attr, value("mixer", attr)); + deleteValue("mixer", attr); + } + } - m_settings.remove("mixer"); + m_settings.remove("mixer"); } void ConfigManager::upgrade() { - // Skip the upgrade if versions match - if (m_version == LMMS_VERSION) - { - return; - } + // Skip the upgrade if versions match + if (m_version == LMMS_VERSION) + { + return; + } - // Runs all necessary upgrade methods - std::size_t max = std::min(static_cast(m_configVersion), UPGRADE_METHODS.size()); - std::for_each( UPGRADE_METHODS.begin() + max, UPGRADE_METHODS.end(), - [this](UpgradeMethod um) - { - (this->*um)(); - } - ); - - ProjectVersion createdWith = m_version; - - // Don't use old themes as they break the UI (i.e. 0.4 != 1.0, etc) - if (createdWith.setCompareType(ProjectVersion::CompareType::Minor) != LMMS_VERSION) - { - m_themeDir = defaultThemeDir(); - } + // Runs all necessary upgrade methods + std::size_t max = std::min(static_cast(m_configVersion), UPGRADE_METHODS.size()); + std::for_each( UPGRADE_METHODS.begin() + max, UPGRADE_METHODS.end(), + [this](UpgradeMethod um) + { + (this->*um)(); + } + ); + + ProjectVersion createdWith = m_version; + + // Don't use old themes as they break the UI (i.e. 0.4 != 1.0, etc) + if (createdWith.setCompareType(ProjectVersion::CompareType::Minor) != LMMS_VERSION) + { + m_themeDir = defaultThemeDir(); + } - // Bump the version, now that we are upgraded - m_version = LMMS_VERSION; - m_configVersion = UPGRADE_METHODS.size(); + // Bump the version, now that we are upgraded + m_version = LMMS_VERSION; + m_configVersion = UPGRADE_METHODS.size(); } QString ConfigManager::defaultVersion() const { - return LMMS_VERSION; + return LMMS_VERSION; } bool ConfigManager::enableBlockedPlugins() @@ -196,39 +194,39 @@ bool ConfigManager::enableBlockedPlugins() QStringList ConfigManager::availableVstEmbedMethods() { - QStringList methods; - methods.append("none"); - methods.append("qt"); + QStringList methods; + methods.append("none"); + methods.append("qt"); #ifdef LMMS_BUILD_WIN32 - methods.append("win32"); + methods.append("win32"); #endif #ifdef LMMS_BUILD_LINUX - if (static_cast(QApplication::instance())-> - platformName() == "xcb") - { - methods.append("xembed"); - } + if (static_cast(QApplication::instance())-> + platformName() == "xcb") + { + methods.append("xembed"); + } #endif - return methods; + return methods; } QString ConfigManager::vstEmbedMethod() const { - QStringList methods = availableVstEmbedMethods(); - QString defaultMethod = *(methods.end() - 1); - QString currentMethod = value( "ui", "vstembedmethod", defaultMethod ); - return methods.contains(currentMethod) ? currentMethod : defaultMethod; + QStringList methods = availableVstEmbedMethods(); + QString defaultMethod = *(methods.end() - 1); + QString currentMethod = value( "ui", "vstembedmethod", defaultMethod ); + return methods.contains(currentMethod) ? currentMethod : defaultMethod; } bool ConfigManager::hasWorkingDir() const { - return QDir(m_workingDir).exists(); + return QDir(m_workingDir).exists(); } void ConfigManager::setWorkingDir(const QString & workingDir) { - m_workingDir = ensureTrailingSlash(QDir::cleanPath(workingDir)); + m_workingDir = ensureTrailingSlash(QDir::cleanPath(workingDir)); } @@ -236,7 +234,7 @@ void ConfigManager::setWorkingDir(const QString & workingDir) void ConfigManager::setVSTDir(const QString & vstDir) { - m_vstDir = ensureTrailingSlash(vstDir); + m_vstDir = ensureTrailingSlash(vstDir); } @@ -244,7 +242,7 @@ void ConfigManager::setVSTDir(const QString & vstDir) void ConfigManager::setLADSPADir(const QString & ladspaDir) { - m_ladspaDir = ladspaDir; + m_ladspaDir = ladspaDir; } @@ -253,7 +251,7 @@ void ConfigManager::setLADSPADir(const QString & ladspaDir) void ConfigManager::setSTKDir(const QString & stkDir) { #ifdef LMMS_HAVE_STK - m_stkDir = ensureTrailingSlash(stkDir); + m_stkDir = ensureTrailingSlash(stkDir); #endif } @@ -262,7 +260,7 @@ void ConfigManager::setSTKDir(const QString & stkDir) void ConfigManager::setSF2Dir(const QString & sf2Dir) { - m_sf2Dir = sf2Dir; + m_sf2Dir = sf2Dir; } @@ -271,7 +269,7 @@ void ConfigManager::setSF2Dir(const QString & sf2Dir) void ConfigManager::setSF2File(const QString & sf2File) { #ifdef LMMS_HAVE_FLUIDSYNTH - m_sf2File = sf2File; + m_sf2File = sf2File; #endif } @@ -280,7 +278,7 @@ void ConfigManager::setSF2File(const QString & sf2File) void ConfigManager::setGIGDir(const QString & gigDir) { - m_gigDir = gigDir; + m_gigDir = gigDir; } @@ -288,7 +286,7 @@ void ConfigManager::setGIGDir(const QString & gigDir) void ConfigManager::setThemeDir(const QString & themeDir) { - m_themeDir = ensureTrailingSlash(themeDir); + m_themeDir = ensureTrailingSlash(themeDir); } @@ -296,7 +294,7 @@ void ConfigManager::setThemeDir(const QString & themeDir) void ConfigManager::setBackgroundPicFile(const QString & backgroundPicFile) { - m_backgroundPicFile = backgroundPicFile; + m_backgroundPicFile = backgroundPicFile; } @@ -304,35 +302,58 @@ void ConfigManager::setBackgroundPicFile(const QString & backgroundPicFile) void ConfigManager::createWorkingDir() { - QDir().mkpath(m_workingDir); + QDir().mkpath(m_workingDir); - QDir().mkpath(userProjectsDir()); - QDir().mkpath(userTemplateDir()); - QDir().mkpath(userSamplesDir()); - QDir().mkpath(userPresetsDir()); - QDir().mkpath(userGigDir()); - QDir().mkpath(userSf2Dir()); - QDir().mkpath(userVstDir()); - QDir().mkpath(userLadspaDir()); + QDir().mkpath(userProjectsDir()); + QDir().mkpath(userTemplateDir()); + QDir().mkpath(userSamplesDir()); + QDir().mkpath(userPresetsDir()); + QDir().mkpath(userGigDir()); + QDir().mkpath(userSf2Dir()); + QDir().mkpath(userVstDir()); + QDir().mkpath(userLadspaDir()); } void ConfigManager::addRecentlyOpenedProject(const QString & file) { - QFileInfo recentFile(file); - if(recentFile.suffix().toLower() == "mmp" || - recentFile.suffix().toLower() == "mmpz" || - recentFile.suffix().toLower() == "mpt") - { - m_recentlyOpenedProjects.removeAll(file); - if(m_recentlyOpenedProjects.size() > 50) - { - m_recentlyOpenedProjects.removeLast(); - } - m_recentlyOpenedProjects.push_front(file); - ConfigManager::inst()->saveConfigFile(); + QFileInfo recentFile(file); + if(recentFile.suffix().toLower() == "mmp" || + recentFile.suffix().toLower() == "mmpz" || + recentFile.suffix().toLower() == "mpt") + { + m_recentlyOpenedProjects.removeAll(file); + if(m_recentlyOpenedProjects.size() > 50) + { + m_recentlyOpenedProjects.removeLast(); + } + m_recentlyOpenedProjects.push_front(file); + ConfigManager::inst()->saveConfigFile(); + } +} + +void ConfigManager::addStarredItem(const QString & item) +{ + QFileInfo starredItem(item); + m_starredItems.push_back(item); + ConfigManager::inst()->saveConfigFile(); +} + +void ConfigManager::removeStarredItem(const QString & item) +{ + m_starredItems.removeAll(item); + ConfigManager::inst()->saveConfigFile(); +} + +bool ConfigManager::isStarred(QString & path) const +{ + // Remove trailing separator if it exists + if (path.endsWith('/')) { + path.chop(1); // Remove the last character if it's a '/' } + + return starredItems().contains(path); } @@ -340,152 +361,165 @@ void ConfigManager::addRecentlyOpenedProject(const QString & file) QString ConfigManager::value(const QString& cls, const QString& attribute, const QString& defaultVal) const { - if (m_settings.find(cls) != m_settings.end()) - { - for (const auto& setting : m_settings[cls]) - { - if (setting.first == attribute) - { - return setting.second; - } - } - } - return defaultVal; + if (m_settings.find(cls) != m_settings.end()) + { + for (const auto& setting : m_settings[cls]) + { + if (setting.first == attribute) + { + return setting.second; + } + } + } + return defaultVal; } void ConfigManager::setValue(const QString & cls, - const QString & attribute, - const QString & value) -{ - if(m_settings.contains(cls)) - { - for(QPair& pair : m_settings[cls]) - { - if(pair.first == attribute) - { - if (pair.second != value) - { - pair.second = value; - emit valueChanged(cls, attribute, value); - } - return; - } - } - } - // not in map yet, so we have to add it... - m_settings[cls].push_back(qMakePair(attribute, value)); + const QString & attribute, + const QString & value) +{ + if(m_settings.contains(cls)) + { + for(QPair& pair : m_settings[cls]) + { + if(pair.first == attribute) + { + if (pair.second != value) + { + pair.second = value; + emit valueChanged(cls, attribute, value); + } + return; + } + } + } + // not in map yet, so we have to add it... + m_settings[cls].push_back(qMakePair(attribute, value)); } void ConfigManager::deleteValue(const QString & cls, const QString & attribute) { - if(m_settings.contains(cls)) - { - for(stringPairVector::iterator it = m_settings[cls].begin(); - it != m_settings[cls].end(); ++it) - { - if((*it).first == attribute) - { - m_settings[cls].erase(it); - return; - } - } - } + if(m_settings.contains(cls)) + { + for(stringPairVector::iterator it = m_settings[cls].begin(); + it != m_settings[cls].end(); ++it) + { + if((*it).first == attribute) + { + m_settings[cls].erase(it); + return; + } + } + } } void ConfigManager::loadConfigFile(const QString & configFile) { - // read the XML file and create DOM tree - // Allow configuration file override through --config commandline option - if (!configFile.isEmpty()) - { - m_lmmsRcFile = configFile; - } - - QFile cfg_file(m_lmmsRcFile); - QDomDocument dom_tree; - - if(cfg_file.open(QIODevice::ReadOnly)) - { - QString errorString; - int errorLine, errorCol; - if(dom_tree.setContent(&cfg_file, false, &errorString, &errorLine, &errorCol)) - { - // get the head information from the DOM - QDomElement root = dom_tree.documentElement(); - - QDomNode node = root.firstChild(); - - // Cache LMMS version - if (!root.attribute("version").isNull()) { - m_version = root.attribute("version"); - } - - // Get the version of the configuration file (for upgrade purposes) - if( root.attribute("configversion").isNull() ) - { - m_configVersion = legacyConfigVersion(); // No configversion attribute found - } - else - { - bool success; - m_configVersion = root.attribute("configversion").toUInt(&success); - if( !success ) qWarning("Config Version conversion failure."); - } - - // create the settings-map out of the DOM - while(!node.isNull()) - { - if(node.isElement() && - node.toElement().hasAttributes ()) - { - stringPairVector attr; - QDomNamedNodeMap node_attr = - node.toElement().attributes(); - for(int i = 0; i < node_attr.count(); - ++i) - { - QDomNode n = node_attr.item(i); - if(n.isAttr()) - { - attr.push_back(qMakePair(n.toAttr().name(), - n.toAttr().value())); - } - } - m_settings[node.nodeName()] = attr; - } - else if(node.nodeName() == "recentfiles") - { - m_recentlyOpenedProjects.clear(); - QDomNode n = node.firstChild(); - while(!n.isNull()) - { - if(n.isElement() && n.toElement().hasAttributes()) - { - m_recentlyOpenedProjects << - n.toElement().attribute("path"); - } - n = n.nextSibling(); - } - } - node = node.nextSibling(); - } - - if(value("paths", "theme") != "") - { - m_themeDir = value("paths", "theme"); + // read the XML file and create DOM tree + // Allow configuration file override through --config commandline option + if (!configFile.isEmpty()) + { + m_lmmsRcFile = configFile; + } + + QFile cfg_file(m_lmmsRcFile); + QDomDocument dom_tree; + + if(cfg_file.open(QIODevice::ReadOnly)) + { + QString errorString; + int errorLine, errorCol; + if(dom_tree.setContent(&cfg_file, false, &errorString, &errorLine, &errorCol)) + { + // get the head information from the DOM + QDomElement root = dom_tree.documentElement(); + + QDomNode node = root.firstChild(); + + // Cache LMMS version + if (!root.attribute("version").isNull()) { + m_version = root.attribute("version"); + } + + // Get the version of the configuration file (for upgrade purposes) + if( root.attribute("configversion").isNull() ) + { + m_configVersion = legacyConfigVersion(); // No configversion attribute found + } + else + { + bool success; + m_configVersion = root.attribute("configversion").toUInt(&success); + if( !success ) qWarning("Config Version conversion failure."); + } + + // create the settings-map out of the DOM + while(!node.isNull()) + { + if(node.isElement() && + node.toElement().hasAttributes ()) + { + stringPairVector attr; + QDomNamedNodeMap node_attr = + node.toElement().attributes(); + for(int i = 0; i < node_attr.count(); + ++i) + { + QDomNode n = node_attr.item(i); + if(n.isAttr()) + { + attr.push_back(qMakePair(n.toAttr().name(), + n.toAttr().value())); + } + } + m_settings[node.nodeName()] = attr; + } + else if(node.nodeName() == "recentfiles") + { + m_recentlyOpenedProjects.clear(); + QDomNode n = node.firstChild(); + while(!n.isNull()) + { + if(n.isElement() && n.toElement().hasAttributes()) + { + m_recentlyOpenedProjects << + n.toElement().attribute("path"); + } + n = n.nextSibling(); + } + } + else if(node.nodeName() == "starreditems") + { + m_starredItems.clear(); + QDomNode n = node.firstChild(); + while(!n.isNull()) + { + if(n.isElement() && n.toElement().hasAttributes()) + { + m_starredItems << + n.toElement().attribute("path"); + } + n = n.nextSibling(); + } + } + node = node.nextSibling(); + } + + if(value("paths", "theme") != "") + { + m_themeDir = value("paths", "theme"); #ifdef LMMS_BUILD_WIN32 - // Detect a QDir/QFile hang on Windows - // see issue #3417 on github - bool badPath = (m_themeDir == "/" || m_themeDir == "\\"); + // Detect a QDir/QFile hang on Windows + // see issue #3417 on github + bool badPath = (m_themeDir == "/" || m_themeDir == "\\"); #else - bool badPath = false; + bool badPath = false; #endif - if(badPath || !QDir(m_themeDir).exists() || !QFile(m_themeDir + "/style.css").exists()) { @@ -524,53 +558,53 @@ void ConfigManager::loadConfigFile(const QString & configFile) !QDir( m_vstDir ).exists() ) { #ifdef LMMS_BUILD_WIN32 - QString programFiles = QString::fromLocal8Bit(getenv("ProgramFiles")); - m_vstDir = programFiles + "/VstPlugins/"; + QString programFiles = QString::fromLocal8Bit(getenv("ProgramFiles")); + m_vstDir = programFiles + "/VstPlugins/"; #else - m_vstDir = m_workingDir + "plugins/vst/"; + m_vstDir = m_workingDir + "plugins/vst/"; #endif - } + } - if(m_ladspaDir.isEmpty() ) - { - m_ladspaDir = userLadspaDir(); - } + if(m_ladspaDir.isEmpty() ) + { + m_ladspaDir = userLadspaDir(); + } #ifdef LMMS_HAVE_STK - if(m_stkDir.isEmpty() || m_stkDir == QDir::separator() || m_stkDir == "/" || - !QDir(m_stkDir).exists()) - { + if(m_stkDir.isEmpty() || m_stkDir == QDir::separator() || m_stkDir == "/" || + !QDir(m_stkDir).exists()) + { #if defined(LMMS_BUILD_WIN32) - m_stkDir = m_dataDir + "stk/rawwaves/"; + m_stkDir = m_dataDir + "stk/rawwaves/"; #else - // Look for bundled raw waves first - m_stkDir = qApp->applicationDirPath() + "/../share/stk/rawwaves/"; - // Try system installations if not exists - if (!QDir(m_stkDir).exists()) - { - m_stkDir = "/usr/local/share/stk/rawwaves/"; - } - if (!QDir(m_stkDir).exists()) - { - m_stkDir = "/usr/share/stk/rawwaves/"; - } + // Look for bundled raw waves first + m_stkDir = qApp->applicationDirPath() + "/../share/stk/rawwaves/"; + // Try system installations if not exists + if (!QDir(m_stkDir).exists()) + { + m_stkDir = "/usr/local/share/stk/rawwaves/"; + } + if (!QDir(m_stkDir).exists()) + { + m_stkDir = "/usr/share/stk/rawwaves/"; + } #endif - } + } #endif // LMMS_HAVE_STK - upgrade(); + upgrade(); - QStringList searchPaths; - if (std::getenv("LMMS_THEME_PATH")) - searchPaths << std::getenv("LMMS_THEME_PATH"); - searchPaths << themeDir() << defaultThemeDir(); - QDir::setSearchPaths("resources", searchPaths); + QStringList searchPaths; + if (std::getenv("LMMS_THEME_PATH")) + searchPaths << std::getenv("LMMS_THEME_PATH"); + searchPaths << themeDir() << defaultThemeDir(); + QDir::setSearchPaths("resources", searchPaths); - // Create any missing subdirectories in the working dir, but only if the working dir exists - if(hasWorkingDir()) - { - createWorkingDir(); - } + // Create any missing subdirectories in the working dir, but only if the working dir exists + if(hasWorkingDir()) + { + createWorkingDir(); + } } @@ -578,153 +612,164 @@ void ConfigManager::loadConfigFile(const QString & configFile) void ConfigManager::saveConfigFile() { - setValue("paths", "theme", m_themeDir); - setValue("paths", "workingdir", m_workingDir); - setValue("paths", "vstdir", m_vstDir); - setValue("paths", "gigdir", m_gigDir); - setValue("paths", "sf2dir", m_sf2Dir); - setValue("paths", "ladspadir", m_ladspaDir); + setValue("paths", "theme", m_themeDir); + setValue("paths", "workingdir", m_workingDir); + setValue("paths", "vstdir", m_vstDir); + setValue("paths", "gigdir", m_gigDir); + setValue("paths", "sf2dir", m_sf2Dir); + setValue("paths", "ladspadir", m_ladspaDir); #ifdef LMMS_HAVE_STK - setValue("paths", "stkdir", m_stkDir); + setValue("paths", "stkdir", m_stkDir); #endif #ifdef LMMS_HAVE_FLUIDSYNTH - setValue("paths", "defaultsf2", m_sf2File); + setValue("paths", "defaultsf2", m_sf2File); #endif - setValue("paths", "backgroundtheme", m_backgroundPicFile); - - QDomDocument doc("lmms-config-file"); - - QDomElement lmms_config = doc.createElement("lmms"); - lmms_config.setAttribute("version", m_version); - lmms_config.setAttribute("configversion", m_configVersion); - doc.appendChild(lmms_config); - - for (auto it = m_settings.begin(); it != m_settings.end(); ++it) - { - QDomElement n = doc.createElement(it.key()); - for (const auto& [first, second] : *it) - { - n.setAttribute(first, second); - } - lmms_config.appendChild(n); - } - - QDomElement recent_files = doc.createElement("recentfiles"); - - for (const auto& recentlyOpenedProject : m_recentlyOpenedProjects) - { - QDomElement n = doc.createElement("file"); - n.setAttribute("path", recentlyOpenedProject); - recent_files.appendChild(n); - } - lmms_config.appendChild(recent_files); - - QString xml = "\n" + doc.toString(2); - - QFile outfile(m_lmmsRcFile); - if(!outfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) - { - using gui::MainWindow; - - QString title, message; - title = MainWindow::tr("Could not open file"); - message = MainWindow::tr("Could not open file %1 " - "for writing.\nPlease make " - "sure you have write " - "permission to the file and " - "the directory containing the " - "file and try again!" - ).arg(m_lmmsRcFile); - if (gui::getGUI() != nullptr) - { - QMessageBox::critical(nullptr, title, message, - QMessageBox::Ok, - QMessageBox::NoButton); - } - return; - } - - outfile.write(xml.toUtf8()); - outfile.close(); + setValue("paths", "backgroundtheme", m_backgroundPicFile); + + QDomDocument doc("lmms-config-file"); + + QDomElement lmms_config = doc.createElement("lmms"); + lmms_config.setAttribute("version", m_version); + lmms_config.setAttribute("configversion", m_configVersion); + doc.appendChild(lmms_config); + + for (auto it = m_settings.begin(); it != m_settings.end(); ++it) + { + QDomElement n = doc.createElement(it.key()); + for (const auto& [first, second] : *it) + { + n.setAttribute(first, second); + } + lmms_config.appendChild(n); + } + + QDomElement recent_files = doc.createElement("recentfiles"); + + for (const auto& recentlyOpenedProject : m_recentlyOpenedProjects) + { + QDomElement n = doc.createElement("file"); + n.setAttribute("path", recentlyOpenedProject); + recent_files.appendChild(n); + } + + QDomElement starred_items = doc.createElement("starreditems"); + + for (const auto& starredItem : m_starredItems) + { + QDomElement n = doc.createElement("item"); + n.setAttribute("path", starredItem); + starred_items.appendChild(n); + } + + lmms_config.appendChild(recent_files); + lmms_config.appendChild(starred_items); + + QString xml = "\n" + doc.toString(2); + + QFile outfile(m_lmmsRcFile); + if(!outfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + using gui::MainWindow; + + QString title, message; + title = MainWindow::tr("Could not open file"); + message = MainWindow::tr("Could not open file %1 " + "for writing.\nPlease make " + "sure you have write " + "permission to the file and " + "the directory containing the " + "file and try again!" + ).arg(m_lmmsRcFile); + if (gui::getGUI() != nullptr) + { + QMessageBox::critical(nullptr, title, message, + QMessageBox::Ok, + QMessageBox::NoButton); + } + return; + } + + outfile.write(xml.toUtf8()); + outfile.close(); } void ConfigManager::initPortableWorkingDir() { - QString applicationPath = qApp->applicationDirPath(); - m_workingDir = applicationPath + "/lmms-workspace/"; - m_lmmsRcFile = applicationPath + "/.lmmsrc.xml"; + QString applicationPath = qApp->applicationDirPath(); + m_workingDir = applicationPath + "/lmms-workspace/"; + m_lmmsRcFile = applicationPath + "/.lmmsrc.xml"; } void ConfigManager::initInstalledWorkingDir() { - m_workingDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/lmms/"; - m_lmmsRcFile = QDir::home().absolutePath() +"/.lmmsrc.xml"; - // Detect < 1.2.0 working directory as a courtesy - if ( QFileInfo( QDir::home().absolutePath() + "/lmms/projects/" ).exists() ) - m_workingDir = QDir::home().absolutePath() + "/lmms/"; + m_workingDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/lmms/"; + m_lmmsRcFile = QDir::home().absolutePath() +"/.lmmsrc.xml"; + // Detect < 1.2.0 working directory as a courtesy + if ( QFileInfo( QDir::home().absolutePath() + "/lmms/projects/" ).exists() ) + m_workingDir = QDir::home().absolutePath() + "/lmms/"; } void ConfigManager::initDevelopmentWorkingDir() { - // If we're in development (lmms is not installed) let's get the source and - // binary directories by reading the CMake Cache - QDir appPath = qApp->applicationDirPath(); - // If in tests, get parent directory - if (appPath.dirName() == "tests") { - appPath.cdUp(); - } - QFile cmakeCache(appPath.absoluteFilePath("CMakeCache.txt")); - if (cmakeCache.exists()) { - cmakeCache.open(QFile::ReadOnly); - QTextStream stream(&cmakeCache); - - // Find the lines containing something like lmms_SOURCE_DIR:static= - // and lmms_BINARY_DIR:static= - int done = 0; - while(! stream.atEnd()) - { - QString line = stream.readLine(); - - if (line.startsWith("lmms_SOURCE_DIR:")) { - QString srcDir = line.section('=', -1).trimmed(); - QDir::addSearchPath("data", srcDir + "/data/"); - done++; - } - if (line.startsWith("lmms_BINARY_DIR:")) { - m_lmmsRcFile = line.section('=', -1).trimmed() + QDir::separator() + - ".lmmsrc.xml"; - done++; - } - if (done == 2) - { - break; - } - } - - cmakeCache.close(); - } + // If we're in development (lmms is not installed) let's get the source and + // binary directories by reading the CMake Cache + QDir appPath = qApp->applicationDirPath(); + // If in tests, get parent directory + if (appPath.dirName() == "tests") { + appPath.cdUp(); + } + QFile cmakeCache(appPath.absoluteFilePath("CMakeCache.txt")); + if (cmakeCache.exists()) { + cmakeCache.open(QFile::ReadOnly); + QTextStream stream(&cmakeCache); + + // Find the lines containing something like lmms_SOURCE_DIR:static= + // and lmms_BINARY_DIR:static= + int done = 0; + while(! stream.atEnd()) + { + QString line = stream.readLine(); + + if (line.startsWith("lmms_SOURCE_DIR:")) { + QString srcDir = line.section('=', -1).trimmed(); + QDir::addSearchPath("data", srcDir + "/data/"); + done++; + } + if (line.startsWith("lmms_BINARY_DIR:")) { + m_lmmsRcFile = line.section('=', -1).trimmed() + QDir::separator() + + ".lmmsrc.xml"; + done++; + } + if (done == 2) + { + break; + } + } + + cmakeCache.close(); + } } // If configversion is not present, we will convert the LMMS version to the appropriate // configuration file version for backwards compatibility. unsigned int ConfigManager::legacyConfigVersion() { - ProjectVersion createdWith = m_version; - - createdWith.setCompareType(ProjectVersion::CompareType::Build); - - if( createdWith < "1.1.90" ) - { - return 0; - } - else if( createdWith < "1.1.91" ) - { - return 1; - } - else - { - return 2; - } + ProjectVersion createdWith = m_version; + + createdWith.setCompareType(ProjectVersion::CompareType::Build); + + if( createdWith < "1.1.90" ) + { + return 0; + } + else if( createdWith < "1.1.91" ) + { + return 1; + } + else + { + return 2; + } } diff --git a/src/gui/FileBrowser.cpp b/src/gui/FileBrowser.cpp index e5168fac8bb..7780932bea8 100644 --- a/src/gui/FileBrowser.cpp +++ b/src/gui/FileBrowser.cpp @@ -1,27 +1,27 @@ /* - * FileBrowser.cpp - implementation of the project-, preset- and - * sample-file-browser - * - * Copyright (c) 2004-2014 Tobias Doerffel - * - * 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. - * - */ +* FileBrowser.cpp - implementation of the project-, preset- and +* sample-file-browser +* +* Copyright (c) 2004-2014 Tobias Doerffel +* +* 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 "FileBrowser.h" @@ -29,12 +29,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include #include @@ -45,7 +45,6 @@ #include "ConfigManager.h" #include "DataFile.h" #include "Engine.h" -#include "FileBrowser.h" #include "FileSearch.h" #include "GuiApplication.h" #include "ImportFilter.h" @@ -73,452 +72,468 @@ namespace lmms::gui enum TreeWidgetItemTypes { - TypeFileItem = QTreeWidgetItem::UserType, - TypeDirectoryItem + TypeFileItem = QTreeWidgetItem::UserType, + TypeDirectoryItem } ; FileBrowser::FileBrowser(const QString & directories, const QString & filter, - const QString & title, const QPixmap & pm, - QWidget * parent, bool dirs_as_items, - const QString& userDir, - const QString& factoryDir): - SideBarWidget( title, pm, parent ), - m_directories( directories ), - m_filter( filter ), - m_dirsAsItems( dirs_as_items ), - m_userDir(userDir), - m_factoryDir(factoryDir) + const QString & title, const QPixmap & pm, + QWidget * parent, bool dirs_as_items, + const QString& userDir, + const QString& factoryDir, + const QStringList & items, + const bool showDirectories + ): + SideBarWidget(title, pm, parent), + m_directories(directories), + m_filter(filter), + m_dirsAsItems(dirs_as_items), + m_userDir(userDir), + m_factoryDir(factoryDir), + m_items(items) { - setWindowTitle( tr( "Browser" ) ); - addContentCheckBox(); + setWindowTitle(tr("Browser")); + + addContentCheckBox(); - auto searchWidget = new QWidget(contentParent()); - searchWidget->setFixedHeight( 24 ); + auto searchWidget = new QWidget(this); + searchWidget->setFixedHeight(24); - auto searchWidgetLayout = new QHBoxLayout(searchWidget); - searchWidgetLayout->setContentsMargins(0, 0, 0, 0); - searchWidgetLayout->setSpacing( 0 ); + auto searchWidgetLayout = new QHBoxLayout(searchWidget); + searchWidgetLayout->setContentsMargins(0, 0, 0, 0); + searchWidgetLayout->setSpacing(0); - m_filterEdit = new QLineEdit(searchWidget); - m_filterEdit->setPlaceholderText(tr("Search")); - m_filterEdit->setClearButtonEnabled(true); - m_filterEdit->addAction(embed::getIconPixmap("zoom"), QLineEdit::LeadingPosition); + m_filterEdit = new QLineEdit(searchWidget); + m_filterEdit->setPlaceholderText(tr("Search")); + m_filterEdit->setClearButtonEnabled(true); + m_filterEdit->addAction(embed::getIconPixmap("zoom"), QLineEdit::LeadingPosition); - connect(m_filterEdit, &QLineEdit::textEdited, this, &FileBrowser::onSearch); + connect(m_filterEdit, &QLineEdit::textEdited, this, &FileBrowser::onSearch); - auto reload_btn = new QPushButton(embed::getIconPixmap("reload"), QString(), searchWidget); - reload_btn->setToolTip( tr( "Refresh list" ) ); - connect( reload_btn, SIGNAL(clicked()), this, SLOT(reloadTree())); + auto reload_btn = new QPushButton(embed::getIconPixmap("reload"), QString(), searchWidget); + reload_btn->setToolTip(tr("Refresh list")); + connect(reload_btn, SIGNAL(clicked()), this, SLOT(reloadTree())); - searchWidgetLayout->addWidget( m_filterEdit ); - searchWidgetLayout->addSpacing( 5 ); - searchWidgetLayout->addWidget( reload_btn ); + searchWidgetLayout->addWidget(m_filterEdit); + searchWidgetLayout->addSpacing(5); + searchWidgetLayout->addWidget(reload_btn); - addContentWidget( searchWidget ); + addContentWidget( searchWidget ); - m_fileBrowserTreeWidget = new FileBrowserTreeWidget( contentParent() ); - addContentWidget( m_fileBrowserTreeWidget ); + m_fileBrowserTreeWidget = new FileBrowserTreeWidget( this ); + addContentWidget( m_fileBrowserTreeWidget ); - m_searchTreeWidget = new FileBrowserTreeWidget(contentParent()); - m_searchTreeWidget->hide(); - addContentWidget(m_searchTreeWidget); + m_searchTreeWidget = new FileBrowserTreeWidget(this); + m_searchTreeWidget->hide(); + addContentWidget(m_searchTreeWidget); - m_searchIndicator = new QProgressBar(this); - m_searchIndicator->setMinimum(0); - m_searchIndicator->setMaximum(100); - addContentWidget(m_searchIndicator); + m_searchIndicator = new QProgressBar(this); + m_searchIndicator->setMinimum(0); + m_searchIndicator->setMaximum(100); + addContentWidget(m_searchIndicator); - // Whenever the FileBrowser has focus, Ctrl+F should direct focus to its filter box. - auto filterFocusShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this, SLOT(giveFocusToFilter())); - filterFocusShortcut->setContext(Qt::WidgetWithChildrenShortcut); + // Whenever the FileBrowser has focus, Ctrl+F should direct focus to its filter box. + auto filterFocusShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this, SLOT(giveFocusToFilter())); + filterFocusShortcut->setContext(Qt::WidgetWithChildrenShortcut); - m_previousFilterValue = ""; + m_previousFilterValue = ""; - reloadTree(); - show(); + reloadTree(); + show(); } void FileBrowser::addContentCheckBox() { - // user dir and factory dir checkboxes will display individually depending on whether they are empty. - const bool user_checkbox = !m_userDir.isEmpty(); - const bool factory = !m_factoryDir.isEmpty(); + // user dir and factory dir checkboxes will display individually depending on whether they are empty. + const bool user_checkbox = !m_userDir.isEmpty(); + const bool factory = !m_factoryDir.isEmpty(); - auto filterWidget = new QWidget(contentParent()); + auto filterWidget = new QWidget(contentParent()); - outerLayout = new QBoxLayout(QBoxLayout::Direction::TopToBottom, filterWidget); - outerLayout->setSpacing(0); + outerLayout = new QBoxLayout(QBoxLayout::Direction::TopToBottom, filterWidget); + outerLayout->setSpacing(0); - if (user_checkbox || factory){ - filterWidgetLayout = new QBoxLayout(QBoxLayout::Direction::LeftToRight); - filterWidgetLayout->setContentsMargins(0, 0, 0, 0); - filterWidgetLayout->setSpacing(0); + if (user_checkbox || factory){ + filterWidgetLayout = new QBoxLayout(QBoxLayout::Direction::LeftToRight); + filterWidgetLayout->setContentsMargins(0, 0, 0, 0); + filterWidgetLayout->setSpacing(0); - outerLayout->addLayout(filterWidgetLayout); - } + outerLayout->addLayout(filterWidgetLayout); + } - hiddenWidgetLayout = new QBoxLayout(QBoxLayout::Direction::LeftToRight); - hiddenWidgetLayout->setContentsMargins(0, 0, 0, 0); - hiddenWidgetLayout->setSpacing(0); + hiddenWidgetLayout = new QBoxLayout(QBoxLayout::Direction::LeftToRight); + hiddenWidgetLayout->setContentsMargins(0, 0, 0, 0); + hiddenWidgetLayout->setSpacing(0); - outerLayout->addLayout(hiddenWidgetLayout); + outerLayout->addLayout(hiddenWidgetLayout); - auto configCheckBox = [this](QBoxLayout* boxLayout, QCheckBox* box, Qt::CheckState checkState) - { - box->setCheckState(checkState); - connect(box, SIGNAL(stateChanged(int)), this, SLOT(reloadTree())); - boxLayout->addWidget(box); - }; - - if (user_checkbox) { - m_showUserContent = new QCheckBox(tr("User content")); - configCheckBox(filterWidgetLayout, m_showUserContent, Qt::Checked); - } + auto configCheckBox = [this](QBoxLayout* boxLayout, QCheckBox* box, Qt::CheckState checkState) + { + box->setCheckState(checkState); + connect(box, SIGNAL(stateChanged(int)), this, SLOT(reloadTree())); + boxLayout->addWidget(box); + }; - if (factory) { - m_showFactoryContent = new QCheckBox(tr("Factory content")); - configCheckBox(filterWidgetLayout, m_showFactoryContent, Qt::Checked); - } + if (user_checkbox) { + m_showUserContent = new QCheckBox(tr("User content")); + configCheckBox(filterWidgetLayout, m_showUserContent, Qt::Checked); + } - m_showHiddenContent = new QCheckBox(tr("Hidden content")); - configCheckBox(hiddenWidgetLayout, m_showHiddenContent, Qt::Unchecked); + if (factory) { + m_showFactoryContent = new QCheckBox(tr("Factory content")); + configCheckBox(filterWidgetLayout, m_showFactoryContent, Qt::Checked); + } - addContentWidget(filterWidget); + m_showHiddenContent = new QCheckBox(tr("Hidden content")); + configCheckBox(hiddenWidgetLayout, m_showHiddenContent, Qt::Unchecked); + + addContentWidget(filterWidget); } void FileBrowser::saveDirectoriesStates() -{ - m_savedExpandedDirs = m_fileBrowserTreeWidget->expandedDirs(); +{ + m_savedExpandedDirs = m_fileBrowserTreeWidget->expandedDirs(); } - + void FileBrowser::restoreDirectoriesStates() { - expandItems(m_savedExpandedDirs); + expandItems(m_savedExpandedDirs); } void FileBrowser::foundSearchMatch(FileSearch* search, const QString& match) { - assert(search != nullptr); - if (m_currentSearch.get() != search) { return; } - - auto basePath = QString{}; - for (const auto& path : m_directories.split('*')) - { - if (!match.startsWith(QDir{path}.absolutePath())) { continue; } - basePath = path; - break; - } - - if (basePath.isEmpty()) { return; } - - const auto baseDir = QDir{basePath}; - const auto matchInfo = QFileInfo{match}; - const auto matchRelativeToBasePath = baseDir.relativeFilePath(match); - - auto pathParts = QDir::cleanPath(matchRelativeToBasePath).split("/"); - auto currentItem = static_cast(nullptr); - auto currentDir = baseDir; - - for (const auto& pathPart : pathParts) - { - auto childCount = currentItem ? currentItem->childCount() : m_searchTreeWidget->topLevelItemCount(); - auto childItem = static_cast(nullptr); - - for (int i = 0; i < childCount; ++i) - { - auto item = currentItem ? currentItem->child(i) : m_searchTreeWidget->topLevelItem(i); - if (item->text(0) == pathPart) - { - childItem = item; - break; - } - - } - - if (!childItem) - { - auto pathPartInfo = QFileInfo(currentDir, pathPart); - if (pathPartInfo.isDir()) - { - // Only update directory (i.e., add entries) when it is the matched directory (so do not update - // parents since entries would be added to them that did not match the filter) - const auto disablePopulation = pathParts.indexOf(pathPart) < pathParts.size() - 1; - - auto item = new Directory(pathPart, currentDir.path(), m_filter, disablePopulation); - currentItem ? currentItem->addChild(item) : m_searchTreeWidget->addTopLevelItem(item); - item->update(); - if (disablePopulation) { m_searchTreeWidget->expandItem(item); } - childItem = item; - } - else - { - auto item = new FileItem(pathPart, currentDir.path()); - currentItem ? currentItem->addChild(item) : m_searchTreeWidget->addTopLevelItem(item); - childItem = item; - } - } - - currentItem = childItem; - if (!currentDir.cd(pathPart)) { break; } - } + assert(search != nullptr); + if (m_currentSearch.get() != search) { return; } + + auto basePath = QString{}; + for (const auto& path : m_directories.split('*')) + { + if (!match.startsWith(QDir{path}.absolutePath())) { continue; } + basePath = path; + break; + } + + if (basePath.isEmpty()) { return; } + + const auto baseDir = QDir{basePath}; + const auto matchInfo = QFileInfo{match}; + const auto matchRelativeToBasePath = baseDir.relativeFilePath(match); + + auto pathParts = QDir::cleanPath(matchRelativeToBasePath).split("/"); + auto currentItem = static_cast(nullptr); + auto currentDir = baseDir; + + for (const auto& pathPart : pathParts) + { + auto childCount = currentItem ? currentItem->childCount() : m_searchTreeWidget->topLevelItemCount(); + auto childItem = static_cast(nullptr); + + for (int i = 0; i < childCount; ++i) + { + auto item = currentItem ? currentItem->child(i) : m_searchTreeWidget->topLevelItem(i); + if (item->text(0) == pathPart) + { + childItem = item; + break; + } + + } + + if (!childItem) + { + auto pathPartInfo = QFileInfo(currentDir, pathPart); + if (pathPartInfo.isDir()) + { + // Only update directory (i.e., add entries) when it is the matched directory (so do not update + // parents since entries would be added to them that did not match the filter) + const auto disablePopulation = pathParts.indexOf(pathPart) < pathParts.size() - 1; + + auto item = new Directory(pathPart, currentDir.path(), m_filter, disablePopulation); + currentItem ? currentItem->addChild(item) : m_searchTreeWidget->addTopLevelItem(item); + item->update(); + if (disablePopulation) { m_searchTreeWidget->expandItem(item); } + childItem = item; + } + else + { + auto item = new FileItem(pathPart, currentDir.path()); + currentItem ? currentItem->addChild(item) : m_searchTreeWidget->addTopLevelItem(item); + childItem = item; + } + } + + currentItem = childItem; + if (!currentDir.cd(pathPart)) { break; } + } } void FileBrowser::searchCompleted(FileSearch* search) { - assert(search != nullptr); - if (m_currentSearch.get() != search) { return; } + assert(search != nullptr); + if (m_currentSearch.get() != search) { return; } - m_currentSearch.reset(); - m_searchIndicator->setMaximum(100); + m_currentSearch.reset(); + m_searchIndicator->setMaximum(100); } void FileBrowser::onSearch(const QString& filter) { - if (m_currentSearch) { m_currentSearch->cancel(); } + if (m_currentSearch) { m_currentSearch->cancel(); } - if (filter.isEmpty()) - { - displaySearch(false); - return; - } + if (filter.isEmpty()) + { + displaySearch(false); + return; + } - auto directories = m_directories.split('*'); - if (m_showUserContent && !m_showUserContent->isChecked()) { directories.removeAll(m_userDir); } - if (m_showFactoryContent && !m_showFactoryContent->isChecked()) { directories.removeAll(m_factoryDir); } - if (directories.isEmpty()) { return; } + auto directories = m_directories.split('*'); + if (m_showUserContent && !m_showUserContent->isChecked()) { directories.removeAll(m_userDir); } + if (m_showFactoryContent && !m_showFactoryContent->isChecked()) { directories.removeAll(m_factoryDir); } + if (directories.isEmpty()) { return; } - m_searchTreeWidget->clear(); - displaySearch(true); + m_searchTreeWidget->clear(); + displaySearch(true); - auto browserExtensions = m_filter; - const auto searchExtensions = browserExtensions.remove("*.").split(' '); + auto browserExtensions = m_filter; + const auto searchExtensions = browserExtensions.remove("*.").split(' '); - auto search = std::make_shared( - filter, directories, searchExtensions, excludedPaths(), dirFilters(), sortFlags()); - connect(search.get(), &FileSearch::foundMatch, this, &FileBrowser::foundSearchMatch, Qt::QueuedConnection); - connect(search.get(), &FileSearch::searchCompleted, this, &FileBrowser::searchCompleted, Qt::QueuedConnection); + auto search = std::make_shared( + filter, directories, searchExtensions, excludedPaths(), dirFilters(), sortFlags()); + connect(search.get(), &FileSearch::foundMatch, this, &FileBrowser::foundSearchMatch, Qt::QueuedConnection); + connect(search.get(), &FileSearch::searchCompleted, this, &FileBrowser::searchCompleted, Qt::QueuedConnection); - m_currentSearch = search; - ThreadPool::instance().enqueue([search] { (*search)(); }); + m_currentSearch = search; + ThreadPool::instance().enqueue([search] { (*search)(); }); } void FileBrowser::displaySearch(bool on) { - if (on) - { - m_searchTreeWidget->show(); - m_fileBrowserTreeWidget->hide(); - m_searchIndicator->setMaximum(0); - return; - } - - m_searchTreeWidget->hide(); - m_fileBrowserTreeWidget->show(); - m_searchIndicator->setMaximum(100); + if (on) + { + m_searchTreeWidget->show(); + m_fileBrowserTreeWidget->hide(); + m_searchIndicator->setMaximum(0); + return; + } + + m_searchTreeWidget->hide(); + m_fileBrowserTreeWidget->show(); + m_searchIndicator->setMaximum(100); } void FileBrowser::reloadTree() { - if (m_filterEdit->text().isEmpty()) - { - saveDirectoriesStates(); - } - - m_fileBrowserTreeWidget->clear(); - - QStringList paths = m_directories.split('*'); - - if (m_showUserContent && !m_showUserContent->isChecked()) - { - paths.removeAll(m_userDir); - } - - if (m_showFactoryContent && !m_showFactoryContent->isChecked()) - { - paths.removeAll(m_factoryDir); - } - - if (!paths.isEmpty()) - { - for (const auto& path : paths) - { - addItems(path); - } - } - - if (m_filterEdit->text().isEmpty()) - { - restoreDirectoriesStates(); - } - else - { - onSearch(m_filterEdit->text()); - } + if (m_filterEdit->text().isEmpty()) + { + saveDirectoriesStates(); + } + + m_fileBrowserTreeWidget->clear(); + + // if the string is empty make an empty list. this way it won't have an Empty element. + QStringList paths = m_directories.isEmpty() ? QStringList() : m_directories.split('*'); + + if (m_showUserContent && !m_showUserContent->isChecked()) + { + paths.removeAll(m_userDir); + } + + if (m_showFactoryContent && !m_showFactoryContent->isChecked()) + { + paths.removeAll(m_factoryDir); + } + + for (const auto& item : m_items) + { + QFileInfo entry = QFileInfo(item); + addEntry(entry, entry.absoluteDir().absolutePath()); + } + + if (!paths.isEmpty()) + { + for (const auto& path : paths) + { + addItems(path); + } + } + + if (m_filterEdit->text().isEmpty()) + { + restoreDirectoriesStates(); + } + else + { + onSearch(m_filterEdit->text()); + } } void FileBrowser::expandItems(const QList& expandedDirs, QTreeWidgetItem* item) { - if (expandedDirs.isEmpty()) { return; } - - int numChildren = item ? item->childCount() : m_fileBrowserTreeWidget->topLevelItemCount(); - for (int i = 0; i < numChildren; ++i) - { - auto it = item ? item->child(i) : m_fileBrowserTreeWidget->topLevelItem(i); - auto d = dynamic_cast(it); - if (d) - { - d->setExpanded(expandedDirs.contains(d->fullName())); - if (it->childCount() > 0) - { - expandItems(expandedDirs, it); - } - } - - it->setHidden(false); - } + if (expandedDirs.isEmpty()) { return; } + + int numChildren = item ? item->childCount() : m_fileBrowserTreeWidget->topLevelItemCount(); + for (int i = 0; i < numChildren; ++i) + { + auto it = item ? item->child(i) : m_fileBrowserTreeWidget->topLevelItem(i); + auto d = dynamic_cast(it); + if (d) + { + d->setExpanded(expandedDirs.contains(d->fullName())); + if (it->childCount() > 0) + { + expandItems(expandedDirs, it); + } + } + + it->setHidden(false); + } } void FileBrowser::giveFocusToFilter() { - if (!m_filterEdit->hasFocus()) - { - // give focus to filter text box and highlight its text for quick editing if not previously focused - m_filterEdit->setFocus(); - m_filterEdit->selectAll(); - } + if (!m_filterEdit->hasFocus()) + { + // give focus to filter text box and highlight its text for quick editing if not previously focused + m_filterEdit->setFocus(); + m_filterEdit->selectAll(); + } } +void FileBrowser::addEntry(const QFileInfo & entry, const QString & path) { + if (FileBrowser::excludedPaths().contains(entry.absoluteFilePath())) { return; } + // Normalize the path + QString normalizedPath = QDir::cleanPath(entry.absoluteFilePath()); -void FileBrowser::addItems(const QString & path ) -{ - if (FileBrowser::excludedPaths().contains(path)) { return; } - - if( m_dirsAsItems ) - { - m_fileBrowserTreeWidget->addTopLevelItem( new Directory( path, QString(), m_filter ) ); - return; + // Remove trailing separator if it exists + if (normalizedPath.endsWith(QDir::separator())) { + normalizedPath.chop(1); // Remove the last character if it's a '/' } - // try to add all directories from file system alphabetically into the tree - QDir cdir(path); - if (!cdir.isReadable()) { return; } - QFileInfoList entries = cdir.entryInfoList( - m_filter.split(' '), - dirFilters(), - QDir::LocaleAware | QDir::DirsFirst | QDir::Name | QDir::IgnoreCase); - for (const auto& entry : entries) - { - if (FileBrowser::excludedPaths().contains(entry.absoluteFilePath())) { continue; } + // Now get the file name from the normalized path + QString fileName = QFileInfo(normalizedPath).fileName(); - QString fileName = entry.fileName(); - if (entry.isHidden() && m_showHiddenContent && !m_showHiddenContent->isChecked()) continue; - if (entry.isDir()) + if (entry.isHidden() && m_showHiddenContent && !m_showHiddenContent->isChecked()) return; + if (entry.isDir()) + { + // Merge dir's together + bool orphan = true; + for (int i = 0; i < m_fileBrowserTreeWidget->topLevelItemCount(); ++i) { - // Merge dir's together - bool orphan = true; - for (int i = 0; i < m_fileBrowserTreeWidget->topLevelItemCount(); ++i) + auto d = dynamic_cast(m_fileBrowserTreeWidget->topLevelItem(i)); + if (d == nullptr || fileName < d->text(0)) { - auto d = dynamic_cast(m_fileBrowserTreeWidget->topLevelItem(i)); - if (d == nullptr || fileName < d->text(0)) - { - // insert before item, we're done - auto dd = new Directory(fileName, path, m_filter); - m_fileBrowserTreeWidget->insertTopLevelItem(i,dd); - dd->update(); // add files to the directory - orphan = false; - break; - } - else if (fileName == d->text(0)) - { - // imagine we have subdirs named "TripleOscillator/xyz" in - // two directories from m_directories - // then only add one tree widget for both - // so we don't add a new Directory - we just - // add the path to the current directory - d->addDirectory(path); - d->update(); - orphan = false; - break; - } + // insert before item, we're done + auto dd = new Directory(fileName, path, m_filter); + m_fileBrowserTreeWidget->insertTopLevelItem(i,dd); + dd->update(); // add files to the directory + orphan = false; + break; } - if (orphan) + else if (fileName == d->text(0)) { - // it has not yet been added yet, so it's (lexically) - // larger than all other dirs => append it at the bottom - auto d = new Directory(fileName, path, m_filter); + // imagine we have subdirs named "TripleOscillator/xyz" in + // two directories from m_directories + // then only add one tree widget for both + // so we don't add a new Directory - we just + // add the path to the current directory + d->addDirectory(path); d->update(); - m_fileBrowserTreeWidget->addTopLevelItem(d); + orphan = false; + break; } } - else if (entry.isFile()) + if (orphan) { - // TODO: don't insert instead of removing, order changed - // remove existing file-items - QList existing = m_fileBrowserTreeWidget->findItems(fileName, Qt::MatchFixedString); - if (!existing.empty()) - { - delete existing.front(); - } - (void) new FileItem(m_fileBrowserTreeWidget, fileName, path); + // it has not yet been added yet, so it's (lexically) + // larger than all other dirs => append it at the bottom + auto d = new Directory(fileName, path, m_filter); + d->update(); + m_fileBrowserTreeWidget->addTopLevelItem(d); } } + else if (entry.isFile()) + { + // TODO: don't insert instead of removing, order changed + // remove existing file-items + QList existing = m_fileBrowserTreeWidget->findItems(fileName, Qt::MatchFixedString); + if (!existing.empty()) + { + delete existing.front(); + } + (void) new FileItem(m_fileBrowserTreeWidget, fileName, path); + } } - +void FileBrowser::addItems(const QString & path ) +{ + if (FileBrowser::excludedPaths().contains(path)) { return; } + + if( m_dirsAsItems ) + { + m_fileBrowserTreeWidget->addTopLevelItem( new Directory( path, QString(), m_filter ) ); + return; + } + + // try to add all directories from file system alphabetically into the tree + QDir cdir(path); + if (!cdir.isReadable()) { return; } + QFileInfoList entries = cdir.entryInfoList( + m_filter.split(' '), + dirFilters(), + QDir::LocaleAware | QDir::DirsFirst | QDir::Name | QDir::IgnoreCase); + for (const auto& entry : entries) + { + addEntry(entry, path); + } +} void FileBrowser::keyPressEvent(QKeyEvent * ke ) { - switch( ke->key() ){ - case Qt::Key_F5: - reloadTree(); - break; - default: - ke->ignore(); - } + switch( ke->key() ){ + case Qt::Key_F5: + reloadTree(); + break; + default: + ke->ignore(); + } } - - - - - - - -FileBrowserTreeWidget::FileBrowserTreeWidget(QWidget * parent ) : - QTreeWidget( parent ), - m_mousePressed( false ), - m_pressPos(), - m_previewPlayHandle( nullptr ) +FileBrowserTreeWidget::FileBrowserTreeWidget(FileBrowser * fileBrowser) : + QTreeWidget( fileBrowser->contentParent() ), + m_parentBrowser(fileBrowser), + m_mousePressed( false ), + m_pressPos(), + m_previewPlayHandle( nullptr ) #if (QT_VERSION < QT_VERSION_CHECK(5,14,0)) - ,m_pphMutex(QMutex::Recursive) + ,m_pphMutex(QMutex::Recursive) #endif { - setColumnCount( 1 ); - headerItem()->setHidden( true ); - setSortingEnabled( false ); + setColumnCount( 1 ); + headerItem()->setHidden( true ); + setSortingEnabled( false ); - connect( this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), - SLOT(activateListItem(QTreeWidgetItem*,int))); - connect( this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), - SLOT(updateDirectory(QTreeWidgetItem*))); - connect( this, SIGNAL(itemExpanded(QTreeWidgetItem*)), - SLOT(updateDirectory(QTreeWidgetItem*))); + connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), + SLOT(activateListItem(QTreeWidgetItem*,int))); + connect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), + SLOT(updateDirectory(QTreeWidgetItem*))); + connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)), + SLOT(updateDirectory(QTreeWidgetItem*))); #if QT_VERSION < QT_VERSION_CHECK(5, 12, 2) && defined LMMS_BUILD_WIN32 - // Set the font for the QTreeWidget to the Windows System font to make sure that - // truncated (elided) items use the same font as non-truncated items. - // This is a workaround for this qt bug, fixed in 5.12.2: https://bugreports.qt.io/browse/QTBUG-29232 - // TODO: remove this when all builds use a recent enough version of qt. - setFont( GuiApplication::getWin32SystemFont() ); + // Set the font for the QTreeWidget to the Windows System font to make sure that + // truncated (elided) items use the same font as non-truncated items. + // This is a workaround for this qt bug, fixed in 5.12.2: https://bugreports.qt.io/browse/QTBUG-29232 + // TODO: remove this when all builds use a recent enough version of qt. + setFont( GuiApplication::getWin32SystemFont() ); #endif } @@ -527,71 +542,69 @@ FileBrowserTreeWidget::FileBrowserTreeWidget(QWidget * parent ) : QList FileBrowserTreeWidget::expandedDirs( QTreeWidgetItem * item ) const { - int numChildren = item ? item->childCount() : topLevelItemCount(); - QList dirs; - for (int i = 0; i < numChildren; ++i) - { - QTreeWidgetItem * it = item ? item->child(i) : topLevelItem(i); - - // Add expanded top level directories. - if (it->isExpanded() && (it->type() == TypeDirectoryItem)) - { - auto d = static_cast(it); - dirs.append( d->fullName() ); - } - - // Add expanded child directories (recurse). - if (it->childCount()) - { - dirs.append( expandedDirs( it ) ); - } - } - return dirs; + int numChildren = item ? item->childCount() : topLevelItemCount(); + QList dirs; + for (int i = 0; i < numChildren; ++i) + { + QTreeWidgetItem * it = item ? item->child(i) : topLevelItem(i); + + // Add expanded top level directories. + if (it->isExpanded() && (it->type() == TypeDirectoryItem)) + { + auto d = static_cast(it); + dirs.append( d->fullName() ); + } + + // Add expanded child directories (recurse). + if (it->childCount()) + { + dirs.append( expandedDirs( it ) ); + } + } + return dirs; } void FileBrowserTreeWidget::keyPressEvent(QKeyEvent * ke ) { - // Shorter names for some commonly used properties of the event - const auto key = ke->key(); - const bool vertical = (key == Qt::Key_Up || key == Qt::Key_Down); - const bool horizontal = (key == Qt::Key_Left || key == Qt::Key_Right); - const bool insert = (key == Qt::Key_Enter || key == Qt::Key_Return); - const bool preview = (key == Qt::Key_Space); - - // First of all, forward all keypresses - QTreeWidget::keyPressEvent(ke); - // Then, ignore all autorepeats (they would spam new tracks or previews) - if (ke->isAutoRepeat()) { return; } - // We should stop any running previews before we do anything new - else if (vertical || horizontal || preview || insert) { stopPreview(); } - - // Try to get the currently selected item as a FileItem - auto file = dynamic_cast(currentItem()); - // If it's null (folder, separator, etc.), there's nothing left for us to do - if (file == nullptr) { return; } - - // When moving to a new sound, preview it. Skip presets, they can play forever - if (vertical && file->type() == FileItem::FileType::Sample) - { - previewFileItem(file); - } - - // When enter is pressed, add the selected item... - if (insert) - { - // ...to the song editor by default, or to the pattern editor if ctrl is held - bool songEditor = !(ke->modifiers() & Qt::ControlModifier); - // If shift is held, we send the item to a new sample track... - bool sampleTrack = ke->modifiers() & Qt::ShiftModifier; - // ...but only in the song editor. So, ctrl+shift enter does nothing - if (sampleTrack && songEditor){ openInNewSampleTrack(file); } - // Otherwise we send the item as a new instrument track - else if (!sampleTrack){ openInNewInstrumentTrack(file, songEditor); } - } - - // When space is pressed, start a preview of the selected item - if (preview) { previewFileItem(file); } + // Shorter names for some commonly used properties of the event + const auto key = ke->key(); + const bool vertical = (key == Qt::Key_Up || key == Qt::Key_Down); + const bool horizontal = (key == Qt::Key_Left || key == Qt::Key_Right); + const bool insert = (key == Qt::Key_Enter || key == Qt::Key_Return); + const bool preview = (key == Qt::Key_Space); + + // First of all, forward all keypresses + QTreeWidget::keyPressEvent(ke); + // Then, ignore all autorepeats (they would spam new tracks or previews) + if (ke->isAutoRepeat()) { return; } + // We should stop any running previews before we do anything new + else if (vertical || horizontal || preview || insert) { stopPreview(); } + + // Try to get the currently selected item as a FileItem + auto file = dynamic_cast(currentItem()); + + // When moving to a new sound, preview it. Skip presets, they can play forever + if (vertical && file->type() == FileItem::FileType::Sample) + { + previewFileItem(file); + } + + // When enter is pressed, add the selected item... + if (insert) + { + // ...to the song editor by default, or to the pattern editor if ctrl is held + bool songEditor = !(ke->modifiers() & Qt::ControlModifier); + // If shift is held, we send the item to a new sample track... + bool sampleTrack = ke->modifiers() & Qt::ShiftModifier; + // ...but only in the song editor. So, ctrl+shift enter does nothing + if (sampleTrack && songEditor){ openInNewSampleTrack(file); } + // Otherwise we send the item as a new instrument track + else if (!sampleTrack){ openInNewInstrumentTrack(file, songEditor); } + } + + // When space is pressed, start a preview of the selected item + if (preview) { previewFileItem(file); } } @@ -599,8 +612,8 @@ void FileBrowserTreeWidget::keyPressEvent(QKeyEvent * ke ) void FileBrowserTreeWidget::keyReleaseEvent(QKeyEvent* ke) { - // Cancel previews when the space key is released - if (ke->key() == Qt::Key_Space && !ke->isAutoRepeat()) { stopPreview(); } + // Cancel previews when the space key is released + if (ke->key() == Qt::Key_Space && !ke->isAutoRepeat()) { stopPreview(); } } @@ -608,9 +621,9 @@ void FileBrowserTreeWidget::keyReleaseEvent(QKeyEvent* ke) void FileBrowserTreeWidget::hideEvent(QHideEvent* he) { - // Cancel previews when the user switches tabs or hides the sidebar - stopPreview(); - QTreeWidget::hideEvent(he); + // Cancel previews when the user switches tabs or hides the sidebar + stopPreview(); + QTreeWidget::hideEvent(he); } @@ -618,9 +631,9 @@ void FileBrowserTreeWidget::hideEvent(QHideEvent* he) void FileBrowserTreeWidget::focusOutEvent(QFocusEvent* fe) { - // Cancel previews when the user clicks outside the browser - stopPreview(); - QTreeWidget::focusOutEvent(fe); + // Cancel previews when the user clicks outside the browser + stopPreview(); + QTreeWidget::focusOutEvent(fe); } @@ -628,95 +641,146 @@ void FileBrowserTreeWidget::focusOutEvent(QFocusEvent* fe) void FileBrowserTreeWidget::contextMenuEvent(QContextMenuEvent * e ) { - auto file = dynamic_cast(itemAt(e->pos())); - if( file != nullptr && file->isTrack() ) - { - QMenu contextMenu( this ); - contextMenu.addAction( - tr( "Send to active instrument-track" ), - [=]{ sendToActiveInstrumentTrack(file); } - ); + MainWindow* mainWindow = GuiApplication::instance()->mainWindow(); - contextMenu.addSeparator(); +#ifdef LMMS_BUILD_APPLE + QString fileManager = tr("Finder"); +#elif defined(LMMS_BUILD_WIN32) + QString fileManager = tr("File Explorer"); +#else + QString fileManager = tr("file manager"); // note: this is intentionally not capitalized as it isn't a proper noun +#endif - contextMenu.addAction( - QIcon(embed::getIconPixmap("folder")), - tr("Open containing folder"), - [=]{ openContainingFolder(file); } - ); + QTreeWidgetItem* item = itemAt(e->pos()); - auto songEditorHeader = new QAction(tr("Song Editor"), nullptr); - songEditorHeader->setDisabled(true); - contextMenu.addAction( songEditorHeader ); - contextMenu.addActions( getContextActions(file, true) ); + auto file = dynamic_cast(item); - auto patternEditorHeader = new QAction(tr("Pattern Editor"), nullptr); - patternEditorHeader->setDisabled(true); - contextMenu.addAction(patternEditorHeader); - contextMenu.addActions( getContextActions(file, false) ); + QMenu contextMenu( this ); - // We should only show the menu if it contains items - if (!contextMenu.isEmpty()) { contextMenu.exec( e->globalPos() ); } - } -} + auto dir = dynamic_cast(item); // TODO: this might not be a great way to check if it's a directory + if( file != nullptr) + { + bool starred = mainWindow->isStarred(file->fullName()); + if (file->isTrack()) { + contextMenu.addAction( + tr( "Send to active instrument-track" ), + [=]{ sendToActiveInstrumentTrack(file); } + ); -QList FileBrowserTreeWidget::getContextActions(FileItem* file, bool songEditor) -{ - QList result = QList(); - const bool fileIsSample = file->type() == FileItem::FileType::Sample; + contextMenu.addSeparator(); + } - QString instrumentAction = fileIsSample ? - tr("Send to new AudioFileProcessor instance") : - tr("Send to new instrument track"); - QString shortcutMod = songEditor ? "" : UI_CTRL_KEY + QString(" + "); + contextMenu.addAction( + QIcon(embed::getIconPixmap("folder")), - auto toInstrument = new QAction(instrumentAction + tr(" (%2Enter)").arg(shortcutMod), nullptr); - connect(toInstrument, &QAction::triggered, - [=]{ openInNewInstrumentTrack(file, songEditor); }); - result.append(toInstrument); + tr("Show in") + " " +fileManager, + [=]{ openContainingFolder(file); } + ); - if (songEditor && fileIsSample) - { - auto toSampleTrack = new QAction(tr("Send to new sample track (Shift + Enter)"), nullptr); - connect(toSampleTrack, &QAction::triggered, - [=]{ openInNewSampleTrack(file); }); - result.append(toSampleTrack); - } + std::function starAction = !starred + ? std::function([=]{ mainWindow->starItem(file->fullName()); }) + : std::function([=]{ mainWindow->unstarItem(file->fullName()); }); - return result; -} + // a star or pin icon would be nice + contextMenu.addAction( + !starred ? tr("Star") : tr("Unstar") + " " + tr("file"), + starAction + ); + if (file->isTrack()) { + auto songEditorHeader = new QAction(tr("Song Editor"), nullptr); + songEditorHeader->setDisabled(true); + contextMenu.addAction( songEditorHeader ); + contextMenu.addActions( getContextActions(file, true) ); + auto patternEditorHeader = new QAction(tr("Pattern Editor"), nullptr); + patternEditorHeader->setDisabled(true); + contextMenu.addAction(patternEditorHeader); + contextMenu.addActions( getContextActions(file, false) ); + } -void FileBrowserTreeWidget::mousePressEvent(QMouseEvent * me ) + } else if (dir) { + bool starred = mainWindow->isStarred(dir->fullName()); + + contextMenu.addAction( + QIcon(embed::getIconPixmap("folder")), + tr("Open in") + " " + fileManager, + [=]{ openDirectory(dir); } + ); + + std::function dirStarAction = !starred + ? std::function([=]{ mainWindow->starItem(dir->fullName()); }) + : std::function([=]{ mainWindow->unstarItem(dir->fullName()); }); + + // a star or pin icon would be nice + contextMenu.addAction( + !starred ? tr("Star") : tr("Unstar") + " " + tr("folder"), + dirStarAction + ); + } + + // We should only show the menu if it contains items + if (!contextMenu.isEmpty()) { contextMenu.exec( e->globalPos() ); } +} + +QList FileBrowserTreeWidget::getContextActions(FileItem* file, bool songEditor) { - // Forward the event - QTreeWidgetItem * i = itemAt(me->pos()); - QTreeWidget::mousePressEvent(me); - // QTreeWidget handles right clicks for us, so we only care about left clicks - if(me->button() != Qt::LeftButton) { return; } + QList result = QList(); + const bool fileIsSample = file->type() == FileItem::FileType::Sample; + + QString instrumentAction = fileIsSample ? + tr("Send to new AudioFileProcessor instance") : + tr("Send to new instrument track"); + QString shortcutMod = songEditor ? "" : UI_CTRL_KEY + QString(" + "); + + auto toInstrument = new QAction(instrumentAction + tr(" (%2Enter)").arg(shortcutMod), nullptr); + connect(toInstrument, &QAction::triggered, + [=]{ openInNewInstrumentTrack(file, songEditor); }); + result.append(toInstrument); + + if (songEditor && fileIsSample) + { + auto toSampleTrack = new QAction(tr("Send to new sample track (Shift + Enter)"), nullptr); + connect(toSampleTrack, &QAction::triggered, + [=]{ openInNewSampleTrack(file); }); + result.append(toSampleTrack); + } + + return result; +} - if (i) - { - // TODO: Restrict to visible selection -// if ( _me->x() > header()->cellPos( header()->mapToActual( 0 ) ) -// + treeStepSize() * ( i->depth() + ( rootIsDecorated() ? -// 1 : 0 ) ) + itemMargin() || -// _me->x() < header()->cellPos( -// header()->mapToActual( 0 ) ) ) -// { - m_pressPos = me->pos(); - m_mousePressed = true; -// } - } - auto f = dynamic_cast(i); - if(f != nullptr) { previewFileItem(f); } + + +void FileBrowserTreeWidget::mousePressEvent(QMouseEvent * me ) +{ + // Forward the event + QTreeWidgetItem * i = itemAt(me->pos()); + QTreeWidget::mousePressEvent(me); + // QTreeWidget handles right clicks for us, so we only care about left clicks + if(me->button() != Qt::LeftButton) { return; } + + if (i) + { + // TODO: Restrict to visible selection + // if ( _me->x() > header()->cellPos( header()->mapToActual( 0 ) ) + // + treeStepSize() * ( i->depth() + ( rootIsDecorated() ? + // 1 : 0 ) ) + itemMargin() || + // _me->x() < header()->cellPos( + // header()->mapToActual( 0 ) ) ) + // { + m_pressPos = me->pos(); + m_mousePressed = true; + // } + } + + auto f = dynamic_cast(i); + if(f != nullptr) { previewFileItem(f); } } @@ -724,66 +788,66 @@ void FileBrowserTreeWidget::mousePressEvent(QMouseEvent * me ) void FileBrowserTreeWidget::previewFileItem(FileItem* file) { // TODO: We should do this work outside the event thread - // Lock the preview mutex - QMutexLocker previewLocker(&m_pphMutex); - // If something is already playing, stop it before we continue - stopPreview(); - - PlayHandle* newPPH = nullptr; - const QString fileName = file->fullName(); - const QString ext = file->extension(); - - // In special case of sample-files we do not care about - // handling() rather than directly creating a SamplePlayHandle - if (file->type() == FileItem::FileType::Sample) - { - TextFloat * tf = TextFloat::displayMessage( - tr("Loading sample"), - tr("Please wait, loading sample for preview..."), - embed::getIconPixmap("sample_file", 24, 24), 0); - // TODO: this can be removed once we do this outside the event thread - qApp->processEvents(QEventLoop::ExcludeUserInputEvents); - if (auto buffer = SampleLoader::createBufferFromFile(fileName)) - { - auto s = new SamplePlayHandle(new lmms::Sample{std::move(buffer)}); - s->setDoneMayReturnTrue(false); - newPPH = s; - } - delete tf; - } - else if ( - (ext == "xiz" || ext == "sf2" || ext == "sf3" || - ext == "gig" || ext == "pat") - && !getPluginFactory()->pluginSupportingExtension(ext).isNull()) - { - const bool isPlugin = file->handling() == FileItem::FileHandling::LoadByPlugin; - newPPH = new PresetPreviewPlayHandle(fileName, isPlugin); - } - else if (file->type() != FileItem::FileType::VstPlugin && file->isTrack()) - { - DataFile dataFile(fileName); - if (dataFile.validate(ext)) - { - const bool isPlugin = file->handling() == FileItem::FileHandling::LoadByPlugin; - newPPH = new PresetPreviewPlayHandle(fileName, isPlugin, &dataFile); - } - else - { - QMessageBox::warning(0, tr ("Error"), - tr("%1 does not appear to be a valid %2 file") - .arg(fileName, ext), - QMessageBox::Ok, QMessageBox::NoButton); - } - } - - if (newPPH != nullptr) - { - if (Engine::audioEngine()->addPlayHandle(newPPH)) - { - m_previewPlayHandle = newPPH; - } - else { m_previewPlayHandle = nullptr; } - } + // Lock the preview mutex + QMutexLocker previewLocker(&m_pphMutex); + // If something is already playing, stop it before we continue + stopPreview(); + + PlayHandle* newPPH = nullptr; + const QString fileName = file->fullName(); + const QString ext = file->extension(); + + // In special case of sample-files we do not care about + // handling() rather than directly creating a SamplePlayHandle + if (file->type() == FileItem::FileType::Sample) + { + TextFloat * tf = TextFloat::displayMessage( + tr("Loading sample"), + tr("Please wait, loading sample for preview..."), + embed::getIconPixmap("sample_file", 24, 24), 0); + // TODO: this can be removed once we do this outside the event thread + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); + if (auto buffer = SampleLoader::createBufferFromFile(fileName)) + { + auto s = new SamplePlayHandle(new lmms::Sample{std::move(buffer)}); + s->setDoneMayReturnTrue(false); + newPPH = s; + } + delete tf; + } + else if ( + (ext == "xiz" || ext == "sf2" || ext == "sf3" || + ext == "gig" || ext == "pat") + && !getPluginFactory()->pluginSupportingExtension(ext).isNull()) + { + const bool isPlugin = file->handling() == FileItem::FileHandling::LoadByPlugin; + newPPH = new PresetPreviewPlayHandle(fileName, isPlugin); + } + else if (file->type() != FileItem::FileType::VstPlugin && file->isTrack()) + { + DataFile dataFile(fileName); + if (dataFile.validate(ext)) + { + const bool isPlugin = file->handling() == FileItem::FileHandling::LoadByPlugin; + newPPH = new PresetPreviewPlayHandle(fileName, isPlugin, &dataFile); + } + else + { + QMessageBox::warning(0, tr ("Error"), + tr("%1 does not appear to be a valid %2 file") + .arg(fileName, ext), + QMessageBox::Ok, QMessageBox::NoButton); + } + } + + if (newPPH != nullptr) + { + if (Engine::audioEngine()->addPlayHandle(newPPH)) + { + m_previewPlayHandle = newPPH; + } + else { m_previewPlayHandle = nullptr; } + } } @@ -791,12 +855,12 @@ void FileBrowserTreeWidget::previewFileItem(FileItem* file) void FileBrowserTreeWidget::stopPreview() { - QMutexLocker previewLocker(&m_pphMutex); - if (m_previewPlayHandle != nullptr) - { - Engine::audioEngine()->removePlayHandle(m_previewPlayHandle); - m_previewPlayHandle = nullptr; - } + QMutexLocker previewLocker(&m_pphMutex); + if (m_previewPlayHandle != nullptr) + { + Engine::audioEngine()->removePlayHandle(m_previewPlayHandle); + m_previewPlayHandle = nullptr; + } } @@ -804,55 +868,55 @@ void FileBrowserTreeWidget::stopPreview() void FileBrowserTreeWidget::mouseMoveEvent( QMouseEvent * me ) { - if( m_mousePressed == true && - ( m_pressPos - me->pos() ).manhattanLength() > - QApplication::startDragDistance() ) - { - // make sure any playback is stopped - mouseReleaseEvent( nullptr ); - - auto f = dynamic_cast(itemAt(m_pressPos)); - if( f != nullptr ) - { - switch( f->type() ) - { - case FileItem::FileType::Preset: - new StringPairDrag( f->handling() == FileItem::FileHandling::LoadAsPreset ? - "presetfile" : "pluginpresetfile", - f->fullName(), - embed::getIconPixmap( "preset_file" ), this ); - break; - - case FileItem::FileType::Sample: - new StringPairDrag( "samplefile", f->fullName(), - embed::getIconPixmap( "sample_file" ), this ); - break; - case FileItem::FileType::SoundFont: - new StringPairDrag( "soundfontfile", f->fullName(), - embed::getIconPixmap( "soundfont_file" ), this ); - break; - case FileItem::FileType::Patch: - new StringPairDrag( "patchfile", f->fullName(), - embed::getIconPixmap( "sample_file" ), this ); - break; - case FileItem::FileType::VstPlugin: - new StringPairDrag( "vstpluginfile", f->fullName(), - embed::getIconPixmap( "vst_plugin_file" ), this ); - break; - case FileItem::FileType::Midi: - new StringPairDrag( "importedproject", f->fullName(), - embed::getIconPixmap( "midi_file" ), this ); - break; - case FileItem::FileType::Project: - new StringPairDrag( "projectfile", f->fullName(), - embed::getIconPixmap( "project_file" ), this ); - break; - - default: - break; - } - } - } + if(m_mousePressed && + ( m_pressPos - me->pos() ).manhattanLength() > + QApplication::startDragDistance() ) + { + // make sure any playback is stopped + mouseReleaseEvent( nullptr ); + + auto f = dynamic_cast(itemAt(m_pressPos)); + if( f != nullptr ) + { + switch( f->type() ) + { + case FileItem::FileType::Preset: + new StringPairDrag( f->handling() == FileItem::FileHandling::LoadAsPreset ? + "presetfile" : "pluginpresetfile", + f->fullName(), + embed::getIconPixmap( "preset_file" ), this ); + break; + + case FileItem::FileType::Sample: + new StringPairDrag( "samplefile", f->fullName(), + embed::getIconPixmap( "sample_file" ), this ); + break; + case FileItem::FileType::SoundFont: + new StringPairDrag( "soundfontfile", f->fullName(), + embed::getIconPixmap( "soundfont_file" ), this ); + break; + case FileItem::FileType::Patch: + new StringPairDrag( "patchfile", f->fullName(), + embed::getIconPixmap( "sample_file" ), this ); + break; + case FileItem::FileType::VstPlugin: + new StringPairDrag( "vstpluginfile", f->fullName(), + embed::getIconPixmap( "vst_plugin_file" ), this ); + break; + case FileItem::FileType::Midi: + new StringPairDrag( "importedproject", f->fullName(), + embed::getIconPixmap( "midi_file" ), this ); + break; + case FileItem::FileType::Project: + new StringPairDrag( "projectfile", f->fullName(), + embed::getIconPixmap( "project_file" ), this ); + break; + + default: + break; + } + } + } } @@ -860,18 +924,18 @@ void FileBrowserTreeWidget::mouseMoveEvent( QMouseEvent * me ) void FileBrowserTreeWidget::mouseReleaseEvent(QMouseEvent * me ) { - m_mousePressed = false; - - // If a preview is running, we may need to stop it. Otherwise, we're done - QMutexLocker previewLocker(&m_pphMutex); - if (m_previewPlayHandle == nullptr) { return; } - - // Only sample previews may continue after mouse up. Is this a sample preview? - bool isSample = m_previewPlayHandle->type() == PlayHandle::Type::SamplePlayHandle; - // Even sample previews should only continue if the user wants them to. Do they? - bool shouldContinue = ConfigManager::inst()->value("ui", "letpreviewsfinish").toInt(); - // If both are true the preview may continue, otherwise we stop it - if (!(isSample && shouldContinue)) { stopPreview(); } + m_mousePressed = false; + + // If a preview is running, we may need to stop it. Otherwise, we're done + QMutexLocker previewLocker(&m_pphMutex); + if (m_previewPlayHandle == nullptr) { return; } + + // Only sample previews may continue after mouse up. Is this a sample preview? + bool isSample = m_previewPlayHandle->type() == PlayHandle::Type::SamplePlayHandle; + // Even sample previews should only continue if the user wants them to. Do they? + bool shouldContinue = ConfigManager::inst()->value("ui", "letpreviewsfinish").toInt(); + // If both are true the preview may continue, otherwise we stop it + if (!(isSample && shouldContinue)) { stopPreview(); } } @@ -879,71 +943,71 @@ void FileBrowserTreeWidget::mouseReleaseEvent(QMouseEvent * me ) void FileBrowserTreeWidget::handleFile(FileItem * f, InstrumentTrack * it) { - Engine::audioEngine()->requestChangeInModel(); - switch( f->handling() ) - { - case FileItem::FileHandling::LoadAsProject: - if( getGUI()->mainWindow()->mayChangeProject(true) ) - { - Engine::getSong()->loadProject( f->fullName() ); - } - break; - - case FileItem::FileHandling::LoadByPlugin: - { - const QString e = f->extension(); - Instrument * i = it->instrument(); - if( i == nullptr || - !i->descriptor()->supportsFileType( e ) ) - { - PluginFactory::PluginInfoAndKey piakn = - getPluginFactory()->pluginSupportingExtension(e); - i = it->loadInstrument(piakn.info.name(), &piakn.key); - } - i->loadFile( f->fullName() ); - break; - } - - case FileItem::FileHandling::LoadAsPreset: { - DataFile dataFile(f->fullName()); - it->replaceInstrument(dataFile); - break; - } - case FileItem::FileHandling::ImportAsProject: - ImportFilter::import( f->fullName(), - Engine::getSong() ); - break; - - case FileItem::FileHandling::NotSupported: - default: - break; - - } - Engine::audioEngine()->doneChangeInModel(); + Engine::audioEngine()->requestChangeInModel(); + switch( f->handling() ) + { + case FileItem::FileHandling::LoadAsProject: + if( getGUI()->mainWindow()->mayChangeProject(true) ) + { + Engine::getSong()->loadProject( f->fullName() ); + } + break; + + case FileItem::FileHandling::LoadByPlugin: + { + const QString e = f->extension(); + Instrument * i = it->instrument(); + if( i == nullptr || + !i->descriptor()->supportsFileType( e ) ) + { + PluginFactory::PluginInfoAndKey piakn = + getPluginFactory()->pluginSupportingExtension(e); + i = it->loadInstrument(piakn.info.name(), &piakn.key); + } + i->loadFile( f->fullName() ); + break; + } + + case FileItem::FileHandling::LoadAsPreset: { + DataFile dataFile(f->fullName()); + it->replaceInstrument(dataFile); + break; + } + case FileItem::FileHandling::ImportAsProject: + ImportFilter::import( f->fullName(), + Engine::getSong() ); + break; + + case FileItem::FileHandling::NotSupported: + default: + break; + + } + Engine::audioEngine()->doneChangeInModel(); } void FileBrowserTreeWidget::activateListItem(QTreeWidgetItem * item, - int column ) + int column ) { - auto f = dynamic_cast(item); - if( f == nullptr ) - { - return; - } - - if( f->handling() == FileItem::FileHandling::LoadAsProject || - f->handling() == FileItem::FileHandling::ImportAsProject ) - { - handleFile( f, nullptr ); - } - else if( f->handling() != FileItem::FileHandling::NotSupported ) - { - auto it = dynamic_cast(Track::create(Track::Type::Instrument, Engine::patternStore())); - handleFile( f, it ); - } + auto f = dynamic_cast(item); + if( f == nullptr ) + { + return; + } + + if( f->handling() == FileItem::FileHandling::LoadAsProject || + f->handling() == FileItem::FileHandling::ImportAsProject ) + { + handleFile(f, nullptr); + } + else if( f->handling() != FileItem::FileHandling::NotSupported ) + { + auto it = dynamic_cast(Track::create(Track::Type::Instrument, Engine::patternStore())); + handleFile(f, it); + } } @@ -951,11 +1015,11 @@ void FileBrowserTreeWidget::activateListItem(QTreeWidgetItem * item, void FileBrowserTreeWidget::openInNewInstrumentTrack(TrackContainer* tc, FileItem* item) { - if(item->isTrack()) - { - auto it = dynamic_cast(Track::create(Track::Type::Instrument, tc)); - handleFile(item, it); - } + if(item->isTrack()) + { + auto it = dynamic_cast(Track::create(Track::Type::Instrument, tc)); + handleFile(item, it); + } } @@ -963,10 +1027,10 @@ void FileBrowserTreeWidget::openInNewInstrumentTrack(TrackContainer* tc, FileIte void FileBrowserTreeWidget::openInNewInstrumentTrack(FileItem* item, bool songEditor) { - // Get the correct TrackContainer. Ternary doesn't compile here - TrackContainer* tc = Engine::getSong(); - if (!songEditor) { tc = Engine::patternStore(); } - openInNewInstrumentTrack(tc, item); + // Get the correct TrackContainer. Ternary doesn't compile here + TrackContainer* tc = Engine::getSong(); + if (!songEditor) { tc = Engine::patternStore(); } + openInNewInstrumentTrack(tc, item); } @@ -974,57 +1038,108 @@ void FileBrowserTreeWidget::openInNewInstrumentTrack(FileItem* item, bool songEd bool FileBrowserTreeWidget::openInNewSampleTrack(FileItem* item) { - // Can't add non-samples to a sample track - if (item->type() != FileItem::FileType::Sample) { return false; } - - // Create a new sample track for this sample - auto sampleTrack = static_cast(Track::create(Track::Type::Sample, Engine::getSong())); - - // Add the sample clip to the track - Engine::audioEngine()->requestChangeInModel(); - SampleClip* clip = static_cast(sampleTrack->createClip(0)); - clip->setSampleFile(item->fullName()); - Engine::audioEngine()->doneChangeInModel(); - return true; + // Can't add non-samples to a sample track + if (item->type() != FileItem::FileType::Sample) { return false; } + + // Create a new sample track for this sample + auto sampleTrack = static_cast(Track::create(Track::Type::Sample, Engine::getSong())); + + // Add the sample clip to the track + Engine::audioEngine()->requestChangeInModel(); + SampleClip* clip = static_cast(sampleTrack->createClip(0)); + clip->setSampleFile(item->fullName()); + Engine::audioEngine()->doneChangeInModel(); + return true; } - - +void FileBrowserTreeWidget::openDirectory(Directory* directory) { + QDesktopServices::openUrl(QUrl::fromLocalFile(directory->fullName())); +} void FileBrowserTreeWidget::openContainingFolder(FileItem* item) { - // Delegate to QDesktopServices::openUrl with the directory of the selected file. Please note that - // this will only open the directory but not select the file as this is much more complicated due - // to different implementations that are needed for different platforms (Linux/Windows/MacOS). - - // Using QDesktopServices::openUrl seems to be the most simple cross platform way which uses - // functionality that's already available in Qt. - QFileInfo fileInfo(item->fullName()); - QDesktopServices::openUrl(QUrl::fromLocalFile(fileInfo.dir().path())); + QFileInfo fileInfo(item->fullName()); + QString path = fileInfo.absoluteFilePath(); + QString directory = fileInfo.absolutePath(); + QString command; + +#ifdef _WIN32 + // Windows + command = "explorer /select,\"" + path + "\""; +#elif __APPLE__ + // macOS + command = "open -R \"" + path + "\""; +#else + // Linux & BSD + // there are a lot of potential file managers on these systems so we need to figure out what to use. + + // Check user preferences from environment variable + QString fileManager = qgetenv("FILE_MANAGER"); + if (fileManager.isEmpty()) { + fileManager = qgetenv("XDG_FILE_MANAGER"); // not actually in the freedesktop spec but is sometimes used + } + + // If no preference is set, check the desktop environment + if (fileManager.isEmpty()) { + QString desktopEnv = qgetenv("XDG_CURRENT_DESKTOP"); + if (desktopEnv.isEmpty()) { + desktopEnv = qgetenv("DESKTOP_SESSION"); + } + + if (desktopEnv.contains("GNOME", Qt::CaseInsensitive)) { + fileManager = "nautilus"; + } else if (desktopEnv.contains("KDE", Qt::CaseInsensitive)) { + fileManager = "dolphin"; + } else if (desktopEnv.contains("XFCE", Qt::CaseInsensitive)) { + fileManager = "thunar"; + } else if (desktopEnv.contains("LXDE", Qt::CaseInsensitive)) { + fileManager = "pcmanfm"; + } else if (desktopEnv.contains("CINNAMON", Qt::CaseInsensitive)) { + fileManager = "nemo"; + } else if (desktopEnv.contains("MATE", Qt::CaseInsensitive)) { + fileManager = "caja"; + } else if (desktopEnv.contains("PANTHEON", Qt::CaseInsensitive)) { + fileManager = "io.elementary.files"; + } else if (desktopEnv.contains("LXQT", Qt::CaseInsensitive)) { + fileManager = "pcmanfm-qt"; + } else { + QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + return; // Exit if no suitable file manager found + } + } + + // Check if the file manager supports the --select option + if (supportsSelectOption(fileManager)) { + QProcess::startDetached(fileManager + " --select \"" + path + "\""); + } else { + QDesktopServices::openUrl(QUrl::fromLocalFile(directory)); + } +#endif } + void FileBrowserTreeWidget::sendToActiveInstrumentTrack( FileItem* item ) { - // get all windows opened in the workspace - QList pl = - getGUI()->mainWindow()->workspace()-> - subWindowList( QMdiArea::StackingOrder ); - QListIterator w( pl ); - w.toBack(); - // now we travel through the window-list until we find an - // instrument-track - while( w.hasPrevious() ) - { - auto itw = dynamic_cast(w.previous()->widget()); - if( itw != nullptr && itw->isHidden() == false ) - { - handleFile( item, itw->model() ); - break; - } - } + // get all windows opened in the workspace + QList pl = + getGUI()->mainWindow()->workspace()-> + subWindowList( QMdiArea::StackingOrder ); + QListIterator w( pl ); + w.toBack(); + // now we travel through the window-list until we find an + // instrument-track + while( w.hasPrevious() ) + { + auto itw = dynamic_cast(w.previous()->widget()); + if( itw != nullptr && itw->isHidden() == false ) + { + handleFile(item, itw->model()); + break; + } + } } @@ -1032,59 +1147,70 @@ void FileBrowserTreeWidget::sendToActiveInstrumentTrack( FileItem* item ) void FileBrowserTreeWidget::updateDirectory(QTreeWidgetItem * item ) { - auto dir = dynamic_cast(item); - if( dir != nullptr ) - { - dir->update(); - } + auto dir = dynamic_cast(item); + if( dir != nullptr ) + { + dir->update(); + } } Directory::Directory(const QString& filename, const QString& path, const QString& filter, bool disableEntryPopulation) - : QTreeWidgetItem(QStringList(filename), TypeDirectoryItem) - , m_directories(path) - , m_filter(filter) - , m_dirCount(0) - , m_disableEntryPopulation(disableEntryPopulation) + : QTreeWidgetItem(QStringList(filename), TypeDirectoryItem) + , m_directories(path) + , m_filter(filter) + , m_dirCount(0) + , m_disableEntryPopulation(disableEntryPopulation) { - setIcon(0, !QDir{fullName()}.isReadable() ? m_folderLockedPixmap : m_folderPixmap); - setChildIndicatorPolicy( QTreeWidgetItem::ShowIndicator ); + setIcon(0, !QDir{fullName()}.isReadable() ? m_folderLockedPixmap : m_folderPixmap); + setChildIndicatorPolicy( QTreeWidgetItem::ShowIndicator ); } -void Directory::update() +Directory::Directory(QDir dir, const QString& filter, bool disableEntryPopulation) + : QTreeWidgetItem(QStringList(dir.dirName()), TypeDirectoryItem) + , m_directories(dir.absolutePath()) + , m_filter(filter) + , m_dirCount(0) + , m_disableEntryPopulation(disableEntryPopulation) { - if( !isExpanded() ) - { - setIcon(0, m_folderPixmap); - return; - } + setIcon(0, !QDir{fullName()}.isReadable() ? m_folderLockedPixmap : m_folderPixmap); + setChildIndicatorPolicy( QTreeWidgetItem::ShowIndicator ); +} - setIcon(0, m_folderOpenedPixmap); - if (!m_disableEntryPopulation && !childCount()) - { - m_dirCount = 0; - // for all paths leading here, add their items - for (const auto& directory : m_directories) - { - int filesBeforeAdd = childCount() - m_dirCount; - if(addItems(fullName(directory)) && directory.contains(ConfigManager::inst()->dataDir())) - { - // factory file directory is added - // note: those are always added last - int filesNow = childCount() - m_dirCount; - if(filesNow > filesBeforeAdd) // any file appended? - { - auto sep = new QTreeWidgetItem; - sep->setText( 0, - FileBrowserTreeWidget::tr( - "--- Factory files ---" ) ); - sep->setIcon( 0, embed::getIconPixmap( - "factory_files" ) ); - // add delimeter after last file before appending our files - insertChild( filesBeforeAdd + m_dirCount, sep ); - } - } - } - } +void Directory::update() +{ + if( !isExpanded() ) + { + setIcon(0, m_folderPixmap); + return; + } + + setIcon(0, m_folderOpenedPixmap); + if (!m_disableEntryPopulation && !childCount()) + { + m_dirCount = 0; + // for all paths leading here, add their items + for (const auto& directory : m_directories) + { + int filesBeforeAdd = childCount() - m_dirCount; + if(addItems(fullName(directory)) && directory.contains(ConfigManager::inst()->dataDir())) + { + // factory file directory is added + // note: those are always added last + int filesNow = childCount() - m_dirCount; + if(filesNow > filesBeforeAdd) // any file appended? + { + auto sep = new QTreeWidgetItem; + sep->setText( 0, + FileBrowserTreeWidget::tr( + "--- Factory files ---" ) ); + sep->setIcon( 0, embed::getIconPixmap( + "factory_files" ) ); + // add delimeter after last file before appending our files + insertChild( filesBeforeAdd + m_dirCount, sep ); + } + } + } + } } @@ -1092,60 +1218,57 @@ void Directory::update() bool Directory::addItems(const QString& path) { - if (FileBrowser::excludedPaths().contains(path)) { return false; } - - QDir thisDir(path); - if (!thisDir.isReadable()) { return false; } - - treeWidget()->setUpdatesEnabled(false); - - QFileInfoList entries - = thisDir.entryInfoList(m_filter.split(' '), FileBrowser::dirFilters(), FileBrowser::sortFlags()); - for (const auto& entry : entries) - { - if (FileBrowser::excludedPaths().contains(entry.absoluteFilePath())) { continue; } - - QString fileName = entry.fileName(); - if (entry.isDir()) - { - auto dir = new Directory(fileName, path, m_filter); - addChild(dir); - m_dirCount++; - } - else if (entry.isFile()) - { - auto fileItem = new FileItem(fileName, path); - addChild(fileItem); - } - } - - treeWidget()->setUpdatesEnabled(true); - - // return true if we added any child items - return childCount() > 0; + if (FileBrowser::excludedPaths().contains(path)) { return false; } + + QDir thisDir(path); + if (!thisDir.isReadable()) { return false; } + + treeWidget()->setUpdatesEnabled(false); + + QFileInfoList entries + = thisDir.entryInfoList(m_filter.split(' '), FileBrowser::dirFilters(), FileBrowser::sortFlags()); + for (const auto& entry : entries) + { + if (FileBrowser::excludedPaths().contains(entry.absoluteFilePath())) { continue; } + + QString fileName = entry.fileName(); + if (entry.isDir()) + { + auto dir = new Directory(fileName, path, m_filter); + addChild(dir); + m_dirCount++; + } + else if (entry.isFile()) + { + auto fileItem = new FileItem(fileName, path); + addChild(fileItem); + } + } + + treeWidget()->setUpdatesEnabled(true); + + // return true if we added any child items + return childCount() > 0; } - - - FileItem::FileItem(QTreeWidget * parent, const QString & name, - const QString & path ) : - QTreeWidgetItem( parent, QStringList( name) , TypeFileItem ), - m_path( path ) + const QString & path ) : + QTreeWidgetItem( parent, QStringList( name) , TypeFileItem ), + m_path( path ) { - determineFileType(); - initPixmaps(); + determineFileType(); + initPixmaps(); } FileItem::FileItem(const QString & name, const QString & path ) : - QTreeWidgetItem( QStringList( name ), TypeFileItem ), - m_path( path ) + QTreeWidgetItem( QStringList( name ), TypeFileItem ), + m_path( path ) { - determineFileType(); - initPixmaps(); + determineFileType(); + initPixmaps(); } @@ -1153,40 +1276,40 @@ FileItem::FileItem(const QString & name, const QString & path ) : void FileItem::initPixmaps() { - static auto s_projectFilePixmap = embed::getIconPixmap("project_file", 16, 16); - static auto s_presetFilePixmap = embed::getIconPixmap("preset_file", 16, 16); - static auto s_sampleFilePixmap = embed::getIconPixmap("sample_file", 16, 16); - static auto s_soundfontFilePixmap = embed::getIconPixmap("soundfont_file", 16, 16); - static auto s_vstPluginFilePixmap = embed::getIconPixmap("vst_plugin_file", 16, 16); - static auto s_midiFilePixmap = embed::getIconPixmap("midi_file", 16, 16); - static auto s_unknownFilePixmap = embed::getIconPixmap("unknown_file"); - - switch( m_type ) - { - case FileType::Project: - setIcon(0, s_projectFilePixmap); - break; - case FileType::Preset: - setIcon(0, s_presetFilePixmap); - break; - case FileType::SoundFont: - setIcon(0, s_soundfontFilePixmap); - break; - case FileType::VstPlugin: - setIcon(0, s_vstPluginFilePixmap); - break; - case FileType::Sample: - case FileType::Patch: // TODO - setIcon(0, s_sampleFilePixmap); - break; - case FileType::Midi: - setIcon(0, s_midiFilePixmap); - break; - case FileType::Unknown: - default: - setIcon(0, s_unknownFilePixmap); - break; - } + static auto s_projectFilePixmap = embed::getIconPixmap("project_file", 16, 16); + static auto s_presetFilePixmap = embed::getIconPixmap("preset_file", 16, 16); + static auto s_sampleFilePixmap = embed::getIconPixmap("sample_file", 16, 16); + static auto s_soundfontFilePixmap = embed::getIconPixmap("soundfont_file", 16, 16); + static auto s_vstPluginFilePixmap = embed::getIconPixmap("vst_plugin_file", 16, 16); + static auto s_midiFilePixmap = embed::getIconPixmap("midi_file", 16, 16); + static auto s_unknownFilePixmap = embed::getIconPixmap("unknown_file"); + + switch( m_type ) + { + case FileType::Project: + setIcon(0, s_projectFilePixmap); + break; + case FileType::Preset: + setIcon(0, s_presetFilePixmap); + break; + case FileType::SoundFont: + setIcon(0, s_soundfontFilePixmap); + break; + case FileType::VstPlugin: + setIcon(0, s_vstPluginFilePixmap); + break; + case FileType::Sample: + case FileType::Patch: // TODO + setIcon(0, s_sampleFilePixmap); + break; + case FileType::Midi: + setIcon(0, s_midiFilePixmap); + break; + case FileType::Unknown: + default: + setIcon(0, s_unknownFilePixmap); + break; + } } @@ -1194,75 +1317,75 @@ void FileItem::initPixmaps() void FileItem::determineFileType() { - m_handling = FileHandling::NotSupported; - - const QString ext = extension(); - if( ext == "mmp" || ext == "mpt" || ext == "mmpz" ) - { - m_type = FileType::Project; - m_handling = FileHandling::LoadAsProject; - } - else if( ext == "xpf" || ext == "xml" ) - { - m_type = FileType::Preset; - m_handling = FileHandling::LoadAsPreset; - } - else if( ext == "xiz" && ! getPluginFactory()->pluginSupportingExtension(ext).isNull() ) - { - m_type = FileType::Preset; - m_handling = FileHandling::LoadByPlugin; - } - else if( ext == "sf2" || ext == "sf3" ) - { - m_type = FileType::SoundFont; - } - else if( ext == "pat" ) - { - m_type = FileType::Patch; - } - else if( ext == "mid" || ext == "midi" || ext == "rmi" ) - { - m_type = FileType::Midi; - m_handling = FileHandling::ImportAsProject; - } - else if( ext == "dll" + m_handling = FileHandling::NotSupported; + + const QString ext = extension(); + if( ext == "mmp" || ext == "mpt" || ext == "mmpz" ) + { + m_type = FileType::Project; + m_handling = FileHandling::LoadAsProject; + } + else if( ext == "xpf" || ext == "xml" ) + { + m_type = FileType::Preset; + m_handling = FileHandling::LoadAsPreset; + } + else if( ext == "xiz" && ! getPluginFactory()->pluginSupportingExtension(ext).isNull() ) + { + m_type = FileType::Preset; + m_handling = FileHandling::LoadByPlugin; + } + else if( ext == "sf2" || ext == "sf3" ) + { + m_type = FileType::SoundFont; + } + else if( ext == "pat" ) + { + m_type = FileType::Patch; + } + else if( ext == "mid" || ext == "midi" || ext == "rmi" ) + { + m_type = FileType::Midi; + m_handling = FileHandling::ImportAsProject; + } + else if( ext == "dll" #ifdef LMMS_BUILD_LINUX - || ext == "so" + || ext == "so" #endif - ) - { - m_type = FileType::VstPlugin; - m_handling = FileHandling::LoadByPlugin; - } - else if ( ext == "lv2" ) - { - m_type = FileType::Preset; - m_handling = FileHandling::LoadByPlugin; - } - else - { - m_type = FileType::Unknown; - } - - if( m_handling == FileHandling::NotSupported && - !ext.isEmpty() && ! getPluginFactory()->pluginSupportingExtension(ext).isNull() ) - { - m_handling = FileHandling::LoadByPlugin; - // classify as sample if not classified by anything yet but can - // be handled by a certain plugin - if( m_type == FileType::Unknown ) - { - m_type = FileType::Sample; - } - } + ) + { + m_type = FileType::VstPlugin; + m_handling = FileHandling::LoadByPlugin; + } + else if ( ext == "lv2" ) + { + m_type = FileType::Preset; + m_handling = FileHandling::LoadByPlugin; + } + else + { + m_type = FileType::Unknown; + } + + if( m_handling == FileHandling::NotSupported && + !ext.isEmpty() && ! getPluginFactory()->pluginSupportingExtension(ext).isNull() ) + { + m_handling = FileHandling::LoadByPlugin; + // classify as sample if not classified by anything yet but can + // be handled by a certain plugin + if( m_type == FileType::Unknown ) + { + m_type = FileType::Sample; + } + } } -QString FileItem::extension() +QString FileItem::extension() const { - return extension( fullName() ); + return extension( fullName() ); } @@ -1270,32 +1393,32 @@ QString FileItem::extension() QString FileItem::extension(const QString & file ) { - return QFileInfo( file ).suffix().toLower(); + return QFileInfo( file ).suffix().toLower(); } QString FileItem::defaultFilters() { - const auto projectFilters = QStringList{"*.mmp", "*.mpt", "*.mmpz"}; - const auto presetFilters = QStringList{"*.xpf", "*.xml", "*.xiz", "*.lv2"}; - const auto soundFontFilters = QStringList{"*.sf2", "*.sf3"}; - const auto patchFilters = QStringList{"*.pat"}; - const auto midiFilters = QStringList{"*.mid", "*.midi", "*.rmi"}; - - auto vstPluginFilters = QStringList{"*.dll"}; + const auto projectFilters = QStringList{"*.mmp", "*.mpt", "*.mmpz"}; + const auto presetFilters = QStringList{"*.xpf", "*.xml", "*.xiz", "*.lv2"}; + const auto soundFontFilters = QStringList{"*.sf2", "*.sf3"}; + const auto patchFilters = QStringList{"*.pat"}; + const auto midiFilters = QStringList{"*.mid", "*.midi", "*.rmi"}; + + auto vstPluginFilters = QStringList{"*.dll"}; #ifdef LMMS_BUILD_LINUX - vstPluginFilters.append("*.so"); + vstPluginFilters.append("*.so"); #endif - auto audioFilters - = QStringList{"*.wav", "*.ogg", "*.ds", "*.flac", "*.spx", "*.voc", "*.aif", "*.aiff", "*.au", "*.raw"}; + auto audioFilters + = QStringList{"*.wav", "*.ogg", "*.ds", "*.flac", "*.spx", "*.voc", "*.aif", "*.aiff", "*.au", "*.raw"}; #ifdef LMMS_HAVE_SNDFILE_MP3 - audioFilters.append("*.mp3"); + audioFilters.append("*.mp3"); #endif - const auto extensions = projectFilters + presetFilters + soundFontFilters + patchFilters + midiFilters - + vstPluginFilters + audioFilters; + const auto extensions = projectFilters + presetFilters + soundFontFilters + patchFilters + midiFilters + + vstPluginFilters + audioFilters; - return extensions.join(" "); + return extensions.join(" "); } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index d534be96f3f..d93224ff992 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1,54 +1,50 @@ /* - * MainWindow.cpp - implementation of LMMS-main-window - * - * Copyright (c) 2004-2014 Tobias Doerffel - * - * 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. - * - */ +* MainWindow.cpp - implementation of LMMS-main-window +* +* Copyright (c) 2004-2014 Tobias Doerffel +* +* 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 -#include #include -#include #include #include #include #include #include #include +#include #include "AboutDialog.h" #include "AutomationEditor.h" #include "ControllerRackView.h" -#include "embed.h" #include "Engine.h" #include "ExportProjectDialog.h" -#include "FileBrowser.h" #include "FileDialog.h" -#include "MixerView.h" #include "GuiApplication.h" #include "ImportFilter.h" #include "InstrumentTrackView.h" #include "InstrumentTrackWindow.h" #include "MicrotunerConfig.h" +#include "MixerView.h" #include "PatternEditor.h" #include "PianoRoll.h" #include "PianoView.h" @@ -70,172 +66,188 @@ #include "ToolButton.h" #include "ToolPlugin.h" #include "VersionedSaveDialog.h" - +#include "embed.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 ) + 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; + 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); + + m_innerWidget = new QWidget(main_widget); + auto hbox = new QHBoxLayout(m_innerWidget); + hbox->setSpacing( 0 ); + hbox->setContentsMargins(0, 0, 0, 0); + + m_sideBar = new SideBar(Qt::Vertical, m_innerWidget); + + auto splitter = new QSplitter(Qt::Horizontal, m_innerWidget); + splitter->setChildrenCollapsible( false ); + + ConfigManager* confMgr = ConfigManager::inst(); + bool sideBarOnRight = confMgr->value("ui", "sidebaronright").toInt(); + + emit initProgress(tr("Preparing plugin browser")); + m_sideBar->appendTab( new PluginBrowser( splitter ) ); + emit initProgress(tr("Preparing file browsers")); + m_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())); + m_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())); + m_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())); + m_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"); #ifdef LMMS_BUILD_APPLE - title = tr( "Volumes" ); - root_paths += "/Volumes"; + title = tr( "Volumes" ); + root_paths += "/Volumes"; #elif defined(LMMS_BUILD_WIN32) - title = tr( "My Computer" ); - dirs_as_items = true; + title = tr( "My Computer" ); + m_dirs_as_items = true; #endif #if ! defined(LMMS_BUILD_APPLE) - QFileInfoList drives = QDir::drives(); - for( const QFileInfo & drive : drives ) - { - root_paths += drive.absolutePath(); - } + 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 QMdiArea(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::ScrollBarAsNeeded ); - m_workspace->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded ); - - 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); - } + m_sideBar->appendTab(new FileBrowser(root_paths.join("*"), FileItem::defaultFilters(), title, + embed::getIconPixmap("computer").transformed(QTransform().rotate(90)), splitter, m_dirs_as_items)); + + m_starredItemBrowser = new FileBrowser( + "", + FileItem::defaultFilters(), + "Starred Items", + embed::getIconPixmap("folder") + .transformed(QTransform() + .rotate(90).scale(2, 2)), + splitter, + m_dirs_as_items, + "", + "", + confMgr->starredItems() + ); + + m_sideBar->appendTab( + m_starredItemBrowser + ); + + m_workspace = new QMdiArea(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::ScrollBarAsNeeded ); + m_workspace->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded ); + + hbox->addWidget(m_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(m_innerWidget); + 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 (confMgr->value("tooltips", "disabled").toInt()) + { + qApp->installEventFilter(this); + } } @@ -243,19 +255,19 @@ MainWindow::MainWindow() : 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(); + 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(); } @@ -263,269 +275,307 @@ MainWindow::~MainWindow() 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()), - Qt::CTRL + Qt::SHIFT + Qt::Key_S ); - project_menu->addAction( embed::getIconPixmap( "project_save" ), - tr( "Save as New &Version" ), - this, SLOT(saveProjectAsNewVersion()), - 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()), - Qt::CTRL + Qt::Key_E ); - project_menu->addAction( embed::getIconPixmap( "project_export" ), - tr( "E&xport Tracks..." ), - this, - SLOT(onExportProjectTracks()), - Qt::CTRL + Qt::SHIFT + Qt::Key_E ); - - project_menu->addAction( embed::getIconPixmap( "midi_file" ), - tr( "Export &MIDI..." ), - this, - SLOT(onExportProjectMidi()), - Qt::CTRL + Qt::Key_M ); - - project_menu->addSeparator(); - project_menu->addAction( embed::getIconPixmap( "exit" ), tr( "&Quit" ), - qApp, SLOT(closeAllWindows()), - 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(Qt::CTRL + Qt::Key_Y)) - { - new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_Y ), this, SLOT(redo())); - } - if (QKeySequence(QKeySequence::Redo) != QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z )) - { - new QShortcut( QKeySequence( 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())); + 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()), + Qt::CTRL + Qt::SHIFT + Qt::Key_S ); + project_menu->addAction( embed::getIconPixmap( "project_save" ), + tr( "Save as New &Version" ), + this, SLOT(saveProjectAsNewVersion()), + 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()), + Qt::CTRL + Qt::Key_E ); + project_menu->addAction( embed::getIconPixmap( "project_export" ), + tr( "E&xport Tracks..." ), + this, + SLOT(onExportProjectTracks()), + Qt::CTRL + Qt::SHIFT + Qt::Key_E ); + + project_menu->addAction( embed::getIconPixmap( "midi_file" ), + tr( "Export &MIDI..." ), + this, + SLOT(onExportProjectMidi()), + Qt::CTRL + Qt::Key_M ); + + project_menu->addSeparator(); + project_menu->addAction( embed::getIconPixmap( "exit" ), tr( "&Quit" ), + qApp, SLOT(closeAllWindows()), + 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(Qt::CTRL + Qt::Key_Y)) + { + new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_Y ), this, SLOT(redo())); + } + if (QKeySequence(QKeySequence::Redo) != QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z )) + { + new QShortcut( QKeySequence( 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::audioEngine()->isMetronomeActive()); + + 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( 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(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( 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( 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( 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( 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( 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{ + 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())); + } +} - connect(edit_menu, SIGNAL(aboutToShow()), this, SLOT(updateUndoRedoButtons())); +void MainWindow::starItem(QString item) { + ConfigManager* confMgr = ConfigManager::inst(); - 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*))); + // Remove trailing separator if it exists + if (item.endsWith('/')) { + item.chop(1); // Remove the last character if it's a '/' + } + confMgr->addStarredItem(item); + m_starredItemBrowser->m_items.push_back(item); + m_starredItemBrowser->reloadTree(); +} +void MainWindow::unstarItem(QString item) +{ + ConfigManager* confMgr = ConfigManager::inst(); - 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*))); + // Remove trailing separator if it exists + if (item.endsWith('/')) { + item.chop(1); // Remove the last character if it's a '/' } + confMgr->removeStarredItem(item); + m_starredItemBrowser->m_items.removeAll(item); + m_starredItemBrowser->reloadTree(); +} - // 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::audioEngine()->isMetronomeActive()); - - 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( 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(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( 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( 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( 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( 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( 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(); - } +bool MainWindow::isStarred(QString item) +{ + ConfigManager* confMgr = ConfigManager::inst(); - // Add editor subwindows - for (QWidget* widget : std::list{ - 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()); + // Remove trailing separator if it exists + if (item.endsWith('/')) { + item.chop(1); // Remove the last character if it's a '/' } - 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())); - } + return confMgr->isStarred(item); } @@ -533,16 +583,16 @@ void MainWindow::finalize() 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 ); + 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 ); } @@ -550,8 +600,8 @@ int MainWindow::addWidgetToToolBar( QWidget * _w, int _row, int _col ) void MainWindow::addSpacingToToolBar( int _size ) { - m_toolBarLayout->setColumnMinimumWidth( m_toolBarLayout->columnCount() + - 7, _size ); + m_toolBarLayout->setColumnMinimumWidth( m_toolBarLayout->columnCount() + + 7, _size ); } @@ -559,42 +609,42 @@ void MainWindow::addSpacingToToolBar( int _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; + // 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" )); + QString title(tr( "Untitled" )); - if( Engine::getSong()->projectFileName() != "" ) - { - title = QFileInfo( Engine::getSong()->projectFileName() - ).completeBaseName(); - } + if( Engine::getSong()->projectFileName() != "" ) + { + title = QFileInfo( Engine::getSong()->projectFileName() + ).completeBaseName(); + } - if( Engine::getSong()->isModified() ) - { - title += '*'; - } + if( Engine::getSong()->isModified() ) + { + title += '*'; + } - if( getSession() == SessionState::Recover ) - { - title += " - " + tr( "Recover session. Please save your work!" ); - } + if( getSession() == SessionState::Recover ) + { + title += " - " + tr( "Recover session. Please save your work!" ); + } - setWindowTitle( title + " - " + tr( "LMMS %1" ).arg( LMMS_VERSION ) ); + setWindowTitle( title + " - " + tr( "LMMS %1" ).arg( LMMS_VERSION ) ); } @@ -602,53 +652,53 @@ void MainWindow::resetWindowTitle() 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 ); + 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 ); } @@ -656,9 +706,9 @@ bool MainWindow::mayChangeProject(bool stopPlayback) void MainWindow::clearKeyModifiers() { - m_keyMods.m_ctrl = false; - m_keyMods.m_shift = false; - m_keyMods.m_alt = false; + m_keyMods.m_ctrl = false; + m_keyMods.m_shift = false; + m_keyMods.m_alt = false; } @@ -666,30 +716,30 @@ void MainWindow::clearKeyModifiers() 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(_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() ); + // 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(_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() ); } @@ -697,40 +747,40 @@ void MainWindow::saveWidgetState( QWidget * _w, QDomElement & _de ) 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() ); - } + 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() ); + } } @@ -743,10 +793,10 @@ void MainWindow::emptySlot() void MainWindow::createNewProject() { - if( mayChangeProject(true) ) - { - Engine::getSong()->createNewProject(); - } + if( mayChangeProject(true) ) + { + Engine::getSong()->createNewProject(); + } } @@ -754,23 +804,23 @@ void MainWindow::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 ); - } - } + 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 ); + } + } } @@ -778,19 +828,19 @@ void MainWindow::openProject() bool MainWindow::saveProject() { - if( Engine::getSong()->projectFileName() == "" ) - { - return( saveProjectAs() ); - } - else if( this->guiSaveProject() ) - { - if( getSession() == SessionState::Recover ) - { - sessionCleanup(); - } - return true; - } - return false; + if( Engine::getSong()->projectFileName() == "" ) + { + return( saveProjectAs() ); + } + else if( this->guiSaveProject() ) + { + if( getSession() == SessionState::Recover ) + { + sessionCleanup(); + } + return true; + } + return false; } @@ -798,55 +848,55 @@ bool MainWindow::saveProject() 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; + 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; } @@ -854,18 +904,18 @@ bool MainWindow::saveProjectAs() 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 ); - } + QString fileName = Engine::getSong()->projectFileName(); + if( fileName == "" ) + { + return saveProjectAs(); + } + else + { + do VersionedSaveDialog::changeFileNameVersion( fileName, true ); + while ( QFile( fileName ).exists() ); + + return this->guiSaveProjectAs( fileName ); + } } @@ -873,22 +923,22 @@ bool MainWindow::saveProjectAsNewVersion() 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 ); + 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 ); } @@ -896,8 +946,8 @@ void MainWindow::saveProjectAsDefaultTemplate() void MainWindow::showSettingsDialog() { - SetupDialog sd; - sd.exec(); + SetupDialog sd; + sd.exec(); } @@ -905,7 +955,7 @@ void MainWindow::showSettingsDialog() void MainWindow::aboutLMMS() { - AboutDialog(this).exec(); + AboutDialog(this).exec(); } @@ -913,13 +963,13 @@ void MainWindow::aboutLMMS() 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 ); + 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 ); } @@ -927,73 +977,73 @@ void MainWindow::help() 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(); - } - - // Workaround for Qt Bug #260116 - m_workspace->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); - m_workspace->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); - m_workspace->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ); - m_workspace->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded ); + QWidget *parent = window->parentWidget(); + + if( forceShow || + m_workspace->activeSubWindow() != parent || + parent->isHidden() ) + { + parent->show(); + window->show(); + window->setFocus(); + } + else + { + parent->hide(); + refocus(); + } + + // Workaround for Qt Bug #260116 + m_workspace->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + m_workspace->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + m_workspace->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ); + m_workspace->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded ); } void MainWindow::toggleFullscreen() { - if ( !isFullScreen() ) - { - maximized = isMaximized(); - showFullScreen(); - } - else - { - maximized ? showMaximized() : showNormal(); - } + 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. - */ +* 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() { - QList editors; - editors - << getGUI()->songEditor()->parentWidget() - << getGUI()->patternEditor()->parentWidget() - << getGUI()->pianoRoll()->parentWidget() - << getGUI()->automationEditor()->parentWidget(); - - bool found = false; - QList::Iterator editor; - for( editor = editors.begin(); editor != editors.end(); ++editor ) - { - if( ! (*editor)->isHidden() ) { - (*editor)->setFocus(); - found = true; - break; - } - } - - if( ! found ) - this->setFocus(); + QList editors; + editors + << getGUI()->songEditor()->parentWidget() + << getGUI()->patternEditor()->parentWidget() + << getGUI()->pianoRoll()->parentWidget() + << getGUI()->automationEditor()->parentWidget(); + + bool found = false; + QList::Iterator editor; + for( editor = editors.begin(); editor != editors.end(); ++editor ) + { + if( ! (*editor)->isHidden() ) { + (*editor)->setFocus(); + found = true; + break; + } + } + + if( ! found ) + this->setFocus(); } @@ -1001,7 +1051,7 @@ void MainWindow::refocus() void MainWindow::togglePatternEditorWin( bool forceShow ) { - toggleWindow( getGUI()->patternEditor(), forceShow ); + toggleWindow( getGUI()->patternEditor(), forceShow ); } @@ -1009,7 +1059,7 @@ void MainWindow::togglePatternEditorWin( bool forceShow ) void MainWindow::toggleSongEditorWin() { - toggleWindow( getGUI()->songEditor() ); + toggleWindow( getGUI()->songEditor() ); } @@ -1017,7 +1067,7 @@ void MainWindow::toggleSongEditorWin() void MainWindow::toggleProjectNotesWin() { - toggleWindow( getGUI()->getProjectNotes() ); + toggleWindow( getGUI()->getProjectNotes() ); } @@ -1025,7 +1075,7 @@ void MainWindow::toggleProjectNotesWin() void MainWindow::togglePianoRollWin() { - toggleWindow( getGUI()->pianoRoll() ); + toggleWindow( getGUI()->pianoRoll() ); } @@ -1033,7 +1083,7 @@ void MainWindow::togglePianoRollWin() void MainWindow::toggleAutomationEditorWin() { - toggleWindow( getGUI()->automationEditor() ); + toggleWindow( getGUI()->automationEditor() ); } @@ -1041,14 +1091,14 @@ void MainWindow::toggleAutomationEditorWin() void MainWindow::toggleMixerWin() { - toggleWindow( getGUI()->mixerView() ); + toggleWindow( getGUI()->mixerView() ); } void MainWindow::toggleMicrotunerWin() { - toggleWindow( getGUI()->getMicrotunerConfig() ); + toggleWindow( getGUI()->getMicrotunerConfig() ); } @@ -1056,77 +1106,77 @@ void MainWindow::toggleMicrotunerWin() 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("Volume as dBFS"), this); - qa->setData("displaydbfs"); - qa->setCheckable( true ); - qa->setChecked( ConfigManager::inst()->value( "app", "displaydbfs" ).toInt() ); - m_viewMenu->addAction(qa); - - 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); + 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("Volume as dBFS"), this); + qa->setData("displaydbfs"); + qa->setCheckable( true ); + qa->setChecked( ConfigManager::inst()->value( "app", "displaydbfs" ).toInt() ); + m_viewMenu->addAction(qa); + + 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); } @@ -1135,45 +1185,45 @@ void MainWindow::updateViewMenu() void MainWindow::updateConfig( QAction * _who ) { - QString tag = _who->data().toString(); - bool checked = _who->isChecked(); - - if( tag == "displaydbfs" ) - { - ConfigManager::inst()->setValue( "app", "displaydbfs", - QString::number(checked) ); - } - else 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) ); - } + QString tag = _who->data().toString(); + bool checked = _who->isChecked(); + + if( tag == "displaydbfs" ) + { + ConfigManager::inst()->setValue( "app", "displaydbfs", + QString::number(checked) ); + } + else 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::audioEngine()->setMetronomeActive( m_metronomeToggle->isChecked() ); + Engine::audioEngine()->setMetronomeActive( m_metronomeToggle->isChecked() ); } @@ -1181,7 +1231,7 @@ void MainWindow::onToggleMetronome() void MainWindow::toggleControllerRack() { - toggleWindow( getGUI()->getControllerRackView() ); + toggleWindow( getGUI()->getControllerRackView() ); } @@ -1189,51 +1239,51 @@ void MainWindow::toggleControllerRack() 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; - } - } + 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()); + // 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(); + Engine::projectJournal()->undo(); } @@ -1241,7 +1291,7 @@ void MainWindow::undo() void MainWindow::redo() { - Engine::projectJournal()->redo(); + Engine::projectJournal()->redo(); } @@ -1249,20 +1299,20 @@ void MainWindow::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(); - } + if( mayChangeProject(true) ) + { + // delete recovery file + if( ConfigManager::inst()-> + value( "ui", "enableautosave" ).toInt() ) + { + sessionCleanup(); + _ce->accept(); + } + } + else + { + _ce->ignore(); + } } @@ -1270,9 +1320,9 @@ void MainWindow::closeEvent( QCloseEvent * _ce ) void MainWindow::sessionCleanup() { - // delete recover session files - QFile::remove( ConfigManager::inst()->recoveryFile() ); - setSession( SessionState::Normal ); + // delete recover session files + QFile::remove( ConfigManager::inst()->recoveryFile() ); + setSession( SessionState::Normal ); } @@ -1280,11 +1330,11 @@ void MainWindow::sessionCleanup() 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; } + // 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); + return QObject::eventFilter(watched, event); } @@ -1292,11 +1342,11 @@ bool MainWindow::eventFilter(QObject* watched, QEvent* 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 ); + // 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 ); } @@ -1304,25 +1354,25 @@ void MainWindow::focusOutEvent( QFocusEvent * _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 ); - } - } - } + 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 ); + } + } + } } @@ -1330,22 +1380,22 @@ void MainWindow::keyPressEvent( QKeyEvent * _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 ); - } - } + 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 ); + } + } } @@ -1353,7 +1403,7 @@ void MainWindow::keyReleaseEvent( QKeyEvent * _ke ) void MainWindow::timerEvent( QTimerEvent * _te) { - emit periodicUpdate(); + emit periodicUpdate(); } @@ -1362,10 +1412,10 @@ void MainWindow::timerEvent( QTimerEvent * _te) void MainWindow::showTool( QAction * _idx ) { - PluginView * p = m_tools[m_toolsMenu->actions().indexOf( _idx )]; - p->show(); - p->parentWidget()->show(); - p->setFocus(); + PluginView * p = m_tools[m_toolsMenu->actions().indexOf( _idx )]; + p->show(); + p->parentWidget()->show(); + p->setFocus(); } @@ -1373,10 +1423,10 @@ void MainWindow::showTool( QAction * _idx ) void MainWindow::browseHelp() { - // file:// alternative for offline help - QString url = "https://lmms.io/documentation/"; - QDesktopServices::openUrl( url ); - // TODO: Handle error + // file:// alternative for offline help + QString url = "https://lmms.io/documentation/"; + QDesktopServices::openUrl( url ); + // TODO: Handle error } @@ -1384,234 +1434,234 @@ void MainWindow::browseHelp() 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 ); - } - } + 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); - } + 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; + 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; + 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(); - } + 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 ); - } + 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); + Song * song = Engine::getSong(); + bool const songSaveResult = song->guiSaveProject(); + handleSaveResult(song->projectFileName(), songSaveResult); - return songSaveResult; + return songSaveResult; } bool MainWindow::guiSaveProjectAs( const QString & filename ) { - Song * song = Engine::getSong(); - bool const songSaveResult = song->guiSaveProjectAs(filename); - handleSaveResult(filename, songSaveResult); + Song * song = Engine::getSong(); + bool const songSaveResult = song->guiSaveProjectAs(filename); + handleSaveResult(filename, songSaveResult); - return songSaveResult; + return songSaveResult; } void MainWindow::onExportProject() { - this->exportProject(); + this->exportProject(); } void MainWindow::onExportProjectTracks() { - this->exportProject(true); + 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); - } + 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(); - } + // 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(); + this->resetWindowTitle(); } diff --git a/src/gui/SideBarWidget.cpp b/src/gui/SideBarWidget.cpp index c218bedd335..ba3b707a914 100644 --- a/src/gui/SideBarWidget.cpp +++ b/src/gui/SideBarWidget.cpp @@ -1,26 +1,26 @@ /* - * SideBarWidget.cpp - implementation of base-widget for side-bar - * - * Copyright (c) 2004-2009 Tobias Doerffel - * - * 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. - * - */ +* SideBarWidget.cpp - implementation of base-widget for side-bar +* +* Copyright (c) 2004-2009 Tobias Doerffel +* +* 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 "SideBarWidget.h" @@ -35,21 +35,21 @@ namespace lmms::gui SideBarWidget::SideBarWidget( const QString & _title, const QPixmap & _icon, - QWidget * _parent ) : - QWidget( _parent ), - m_title( _title ), - m_icon(_icon), - m_buttonSize(17, 17) + QWidget * _parent ) : + QWidget( _parent ), + m_title( _title ), + m_icon(_icon), + m_buttonSize(17, 17) { - m_contents = new QWidget( this ); - m_layout = new QVBoxLayout( m_contents ); - m_layout->setSpacing( 5 ); - m_layout->setContentsMargins(0, 0, 0, 0); - m_closeBtn = new QPushButton(embed::getIconPixmap("close"), QString(), this); - m_closeBtn->resize(m_buttonSize); - m_closeBtn->setToolTip(tr("Close")); - connect(m_closeBtn, &QPushButton::clicked, - [=]() { this->closeButtonClicked(); }); + m_contents = new QWidget( this ); + m_layout = new QVBoxLayout( m_contents ); + m_layout->setSpacing( 5 ); + m_layout->setContentsMargins(0, 0, 0, 0); + m_closeBtn = new QPushButton(embed::getIconPixmap("close"), QString(), this); + m_closeBtn->resize(m_buttonSize); + m_closeBtn->setToolTip(tr("Close")); + connect(m_closeBtn, &QPushButton::clicked, + [=]() { this->closeButtonClicked(); }); } @@ -57,34 +57,34 @@ SideBarWidget::SideBarWidget( const QString & _title, const QPixmap & _icon, void SideBarWidget::paintEvent( QPaintEvent * ) { - QPainter p( this ); - p.fillRect( 0, 0, width(), 27, palette().highlight().color() ); + QPainter p( this ); + p.fillRect( 0, 0, width(), 27, palette().highlight().color() ); - QFont f = p.font(); - f.setBold( true ); - f.setUnderline(false); - f.setPointSize( f.pointSize() + 2 ); - p.setFont( f ); + QFont f = p.font(); + f.setBold( true ); + f.setUnderline(false); + f.setPointSize( f.pointSize() + 2 ); + p.setFont( f ); - p.setPen( palette().highlightedText().color() ); + p.setPen( palette().highlightedText().color() ); - const int tx = m_icon.width() + 8; + const int tx = m_icon.width() + 8; - QFontMetrics metrics( f ); - const int ty = (metrics.ascent() + m_icon.height()) / 2; - p.drawText( tx, ty, m_title ); + QFontMetrics metrics( f ); + const int ty = (metrics.ascent() + m_icon.height()) / 2; + p.drawText( tx, ty, m_title ); - p.drawPixmap( 2, 2, m_icon.transformed( QTransform().rotate( -90 ) ) ); + p.drawPixmap( 2, 2, m_icon.transformed( QTransform().rotate( -90 ) ) ); } void SideBarWidget::resizeEvent( QResizeEvent * ) { - const int MARGIN = 6; - m_contents->setGeometry( MARGIN, 40 + MARGIN, width() - MARGIN * 2, - height() - MARGIN * 2 - 40 ); - m_closeBtn->move(m_contents->geometry().width() - MARGIN - 5, 5); + const int MARGIN = 6; + m_contents->setGeometry( MARGIN, 40 + MARGIN, width() - MARGIN * 2, + height() - MARGIN * 2 - 40 ); + m_closeBtn->move(m_contents->geometry().width() - MARGIN - 5, 5); }