Add option to favorite items in the file browser (#7753)

Added the ability to favorite items. This gets added to its own tab named "My Favorites".

---------

Co-authored-by: Sotonye Atemie <sakertooth@gmail.com>
This commit is contained in:
Andrew Wiltshire
2025-04-28 02:34:37 +01:00
committed by GitHub
parent 510fbf6ffc
commit d403a54140
10 changed files with 202 additions and 77 deletions

View File

Before

Width:  |  Height:  |  Size: 844 B

After

Width:  |  Height:  |  Size: 844 B

View File

Before

Width:  |  Height:  |  Size: 264 B

After

Width:  |  Height:  |  Size: 264 B

View File

@@ -214,6 +214,8 @@ public:
return m_recentlyOpenedProjects;
}
const QStringList& favoriteItems() { return m_favoriteItems; }
QString localeDir() const
{
return m_dataDir + LOCALE_PATH;
@@ -240,6 +242,10 @@ public:
void addRecentlyOpenedProject(const QString & _file);
void addFavoriteItem(const QString& item);
void removeFavoriteItem(const QString& item);
bool isFavoriteItem(const QString& item);
QString value(const QString& cls, const QString& attribute, const QString& defaultVal = "") const;
void setValue(const QString & cls, const QString & attribute,
@@ -265,6 +271,7 @@ public:
signals:
void valueChanged( QString cls, QString attribute, QString value );
void favoritesChanged();
private:
static ConfigManager * s_instanceOfMe;
@@ -299,6 +306,7 @@ private:
QString m_version;
unsigned int m_configVersion;
QStringList m_recentlyOpenedProjects;
QStringList m_favoriteItems;
using stringPairVector = std::vector<QPair<QString, QString>>;
using settingsMap = QMap<QString, stringPairVector>;

View File

@@ -61,19 +61,22 @@ class FileBrowser : public SideBarWidget
{
Q_OBJECT
public:
enum class Type
{
Normal,
Favorites
};
/**
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 = "");
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(Type type, 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;
@@ -90,6 +93,7 @@ public:
};
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; }
@@ -101,7 +105,7 @@ private slots:
private:
void keyPressEvent( QKeyEvent * ke ) override;
void addItems( const QString & path );
void addItems(const QString & path);
void saveDirectoriesStates();
void restoreDirectoriesStates();
@@ -117,6 +121,7 @@ private:
FileBrowserTreeWidget * m_searchTreeWidget;
QLineEdit * m_filterEdit;
Type m_type;
std::shared_ptr<FileSearch> m_currentSearch;
QProgressBar* m_searchIndicator = nullptr;
@@ -216,8 +221,7 @@ public:
{
path += QDir::separator();
}
return( QDir::cleanPath( path + text( 0 ) ) +
QDir::separator() );
return QDir::cleanPath(path + text(0));
}
inline void addDirectory( const QString & dir )

View File

@@ -46,6 +46,7 @@ class ConfigManager;
namespace gui
{
class FileBrowser;
class PluginView;
class SubWindow;
class ToolButton;

View File

@@ -31,8 +31,8 @@
namespace lmms::PathUtil
{
enum class Base { Absolute, ProjectDir, FactorySample, UserSample, UserVST, Preset,
UserLADSPA, DefaultLADSPA, UserSoundfont, DefaultSoundfont, UserGIG, DefaultGIG,
enum class Base { Absolute, ProjectDir, FactoryProjects, FactorySample, UserSample, UserVST, Preset,
FactoryPresets, UserLADSPA, DefaultLADSPA, UserSoundfont, DefaultSoundfont, UserGIG, DefaultGIG,
LocalDir };
//! Return the directory associated with a given base as a QString

View File

@@ -22,19 +22,19 @@
*
*/
#include "ConfigManager.h"
#include <QDomElement>
#include <QDir>
#include <QMessageBox>
#include <PathUtil.h>
#include <QApplication>
#include <QDir>
#include <QDomElement>
#include <QMessageBox>
#include <QStandardPaths>
#include <QTextStream>
#include "ConfigManager.h"
#include "GuiApplication.h"
#include "MainWindow.h"
#include "ProjectVersion.h"
#include "GuiApplication.h"
#include "lmmsversion.h"
namespace lmms
@@ -299,9 +299,6 @@ void ConfigManager::setBackgroundPicFile(const QString & backgroundPicFile)
m_backgroundPicFile = backgroundPicFile;
}
void ConfigManager::createWorkingDir()
{
QDir().mkpath(m_workingDir);
@@ -335,8 +332,27 @@ void ConfigManager::addRecentlyOpenedProject(const QString & file)
}
}
void ConfigManager::addFavoriteItem(const QString& item)
{
m_favoriteItems.push_back(item);
saveConfigFile();
emit favoritesChanged();
}
void ConfigManager::removeFavoriteItem(const QString& item)
{
m_favoriteItems.removeAll(item);
saveConfigFile();
emit favoritesChanged();
}
bool ConfigManager::isFavoriteItem(const QString& item)
{
const auto& items = favoriteItems();
const auto it = std::find_if(items.begin(), items.end(),
[&](const auto& favoriteItem) { return QFileInfo{item} == QFileInfo{favoriteItem}; });
return it != items.end();
}
QString ConfigManager::value(const QString& cls, const QString& attribute, const QString& defaultVal) const
{
@@ -466,8 +482,20 @@ void ConfigManager::loadConfigFile(const QString & configFile)
{
if(n.isElement() && n.toElement().hasAttributes())
{
m_recentlyOpenedProjects <<
n.toElement().attribute("path");
m_recentlyOpenedProjects << n.toElement().attribute("path");
}
n = n.nextSibling();
}
}
else if (node.nodeName() == "favoriteitems")
{
m_favoriteItems.clear();
QDomNode n = node.firstChild();
while (!n.isNull())
{
if (n.isElement() && n.toElement().hasAttributes())
{
m_favoriteItems << n.toElement().attribute("path");
}
n = n.nextSibling();
}
@@ -571,6 +599,16 @@ void ConfigManager::loadConfigFile(const QString & configFile)
{
createWorkingDir();
}
for (auto& file : m_recentlyOpenedProjects)
{
file = PathUtil::toAbsolute(file);
}
for (auto& file : m_favoriteItems)
{
file = PathUtil::toAbsolute(file);
}
}
@@ -614,11 +652,22 @@ void ConfigManager::saveConfigFile()
for (const auto& recentlyOpenedProject : m_recentlyOpenedProjects)
{
QDomElement n = doc.createElement("file");
n.setAttribute("path", recentlyOpenedProject);
n.setAttribute("path", PathUtil::toShortestRelative(recentlyOpenedProject));
recent_files.appendChild(n);
}
lmms_config.appendChild(recent_files);
QDomElement favorite_items = doc.createElement("favoriteitems");
for (const auto& favoriteItem : m_favoriteItems)
{
QDomElement n = doc.createElement("item");
n.setAttribute("path", PathUtil::toShortestRelative(favoriteItem));
favorite_items.appendChild(n);
}
lmms_config.appendChild(favorite_items);
QString xml = "<?xml version=\"1.0\"?>\n" + doc.toString(2);
QFile outfile(m_lmmsRcFile);

View File

@@ -9,8 +9,8 @@
namespace lmms::PathUtil
{
auto relativeBases = std::array{ Base::ProjectDir, Base::FactorySample, Base::UserSample, Base::UserVST, Base::Preset,
Base::UserLADSPA, Base::DefaultLADSPA, Base::UserSoundfont, Base::DefaultSoundfont, Base::UserGIG, Base::DefaultGIG,
auto relativeBases = std::array{ Base::ProjectDir, Base::FactoryProjects, Base::FactorySample, Base::UserSample, Base::UserVST, Base::Preset,
Base::FactoryPresets, Base::UserLADSPA, Base::DefaultLADSPA, Base::UserSoundfont, Base::DefaultSoundfont, Base::UserGIG, Base::DefaultGIG,
Base::LocalDir };
QString baseLocation(const Base base, bool* error /* = nullptr*/)
@@ -22,6 +22,11 @@ namespace lmms::PathUtil
switch (base)
{
case Base::ProjectDir : loc = ConfigManager::inst()->userProjectsDir(); break;
case Base::FactoryProjects :
{
QDir fpd = QDir(ConfigManager::inst()->factoryProjectsDir());
loc = fpd.absolutePath(); break;
}
case Base::FactorySample :
{
QDir fsd = QDir(ConfigManager::inst()->factorySamplesDir());
@@ -30,6 +35,11 @@ namespace lmms::PathUtil
case Base::UserSample : loc = ConfigManager::inst()->userSamplesDir(); break;
case Base::UserVST : loc = ConfigManager::inst()->userVstDir(); break;
case Base::Preset : loc = ConfigManager::inst()->userPresetsDir(); break;
case Base::FactoryPresets :
{
QDir fpd = QDir(ConfigManager::inst()->factoryPresetsDir());
loc = fpd.absolutePath(); break;
}
case Base::UserLADSPA : loc = ConfigManager::inst()->ladspaDir(); break;
case Base::DefaultLADSPA : loc = ConfigManager::inst()->userLadspaDir(); break;
case Base::UserSoundfont : loc = ConfigManager::inst()->sf2Dir(); break;
@@ -70,10 +80,12 @@ namespace lmms::PathUtil
switch (base)
{
case Base::ProjectDir : return QStringLiteral("userprojects:");
case Base::FactoryProjects : return QStringLiteral("factoryprojects:");
case Base::FactorySample : return QStringLiteral("factorysample:");
case Base::UserSample : return QStringLiteral("usersample:");
case Base::UserVST : return QStringLiteral("uservst:");
case Base::Preset : return QStringLiteral("preset:");
case Base::FactoryPresets : return QStringLiteral("factorypreset:");
case Base::UserLADSPA : return QStringLiteral("userladspa:");
case Base::DefaultLADSPA : return QStringLiteral("defaultladspa:");
case Base::UserSoundfont : return QStringLiteral("usersoundfont:");

View File

@@ -25,6 +25,7 @@
#include "FileBrowser.h"
#include <PathUtil.h>
#include <QApplication>
#include <QDirIterator>
#include <QHBoxLayout>
@@ -77,18 +78,15 @@ enum TreeWidgetItemTypes
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)
FileBrowser::FileBrowser(Type type, 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_type(type)
, m_directories(directories)
, m_filter(filter)
, m_dirsAsItems(dirs_as_items)
, m_userDir(userDir)
, m_factoryDir(factoryDir)
{
setWindowTitle( tr( "Browser" ) );
@@ -136,6 +134,14 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter,
m_previousFilterValue = "";
if (m_type == Type::Favorites)
{
connect(ConfigManager::inst(), &ConfigManager::favoritesChanged, [this] {
m_directories = ConfigManager::inst()->favoriteItems().join("*");
reloadTree();
});
}
reloadTree();
show();
}
@@ -329,7 +335,7 @@ void FileBrowser::reloadTree()
m_fileBrowserTreeWidget->clear();
QStringList paths = m_directories.split('*');
auto paths = m_directories.isEmpty() ? QStringList{} : m_directories.split('*');
if (m_showUserContent && !m_showUserContent->isChecked())
{
@@ -341,12 +347,37 @@ void FileBrowser::reloadTree()
paths.removeAll(m_factoryDir);
}
if (!paths.isEmpty())
switch (m_type)
{
case Type::Favorites:
for (auto& path : paths)
{
while (path.endsWith('/') || path.endsWith('\\') || path.endsWith("."))
{
path.chop(1);
}
auto info = QFileInfo{PathUtil::toAbsolute(path)};
if (info.isDir())
{
auto dir = new Directory(info.fileName(), info.absolutePath(), m_filter);
dir->update();
m_fileBrowserTreeWidget->addTopLevelItem(dir);
}
else if (info.isFile())
{
auto file = new FileItem(info.fileName(), info.path());
m_fileBrowserTreeWidget->addTopLevelItem(file);
}
}
break;
case Type::Normal:
for (const auto& path : paths)
{
addItems(path);
}
break;
}
if (m_filterEdit->text().isEmpty())
@@ -642,17 +673,29 @@ void FileBrowserTreeWidget::contextMenuEvent(QContextMenuEvent* e)
{
case TypeFileItem: {
auto file = dynamic_cast<FileItem*>(item);
if (file->isTrack())
{
contextMenu.addAction(
tr("Send to active instrument-track"), [=, this] { sendToActiveInstrumentTrack(file); });
contextMenu.addSeparator();
}
const auto path = QFileInfo{file->fullName()}.absoluteFilePath();
contextMenu.addAction(QIcon(embed::getIconPixmap("folder")), tr("Show in %1").arg(fileManager),
[=] { FileRevealer::reveal(file->fullName()); });
if (ConfigManager::inst()->isFavoriteItem(file->fullName()))
{
contextMenu.addAction(
QIcon(embed::getIconPixmap("star")), tr("Remove favorite file"), [path] { ConfigManager::inst()->removeFavoriteItem(path); });
}
else
{
contextMenu.addAction(
QIcon(embed::getIconPixmap("star")), tr("Add favorite file"), [path] { ConfigManager::inst()->addFavoriteItem(path); });
}
if (file->isTrack())
{
contextMenu.addSeparator();
contextMenu.addAction(
tr("Send to active instrument-track"), [=, this] { sendToActiveInstrumentTrack(file); });
}
auto songEditorHeader = new QAction(tr("Song Editor"), nullptr);
songEditorHeader->setDisabled(true);
contextMenu.addAction( songEditorHeader );
@@ -666,9 +709,20 @@ void FileBrowserTreeWidget::contextMenuEvent(QContextMenuEvent* e)
}
case TypeDirectoryItem: {
auto dir = dynamic_cast<Directory*>(item);
const auto path = QFileInfo{dir->fullName()}.absoluteFilePath();
contextMenu.addAction(QIcon(embed::getIconPixmap("folder")), tr("Open in %1").arg(fileManager), [=] {
FileRevealer::openDir(dir->fullName());
});
if (ConfigManager::inst()->isFavoriteItem(dir->fullName()))
{
contextMenu.addAction(QIcon(embed::getIconPixmap("star")), tr("Remove favorite folder"), [path] { ConfigManager::inst()->removeFavoriteItem(path); });
}
else
{
contextMenu.addAction(QIcon(embed::getIconPixmap("star")), tr("Add favorite folder"), [path] { ConfigManager::inst()->addFavoriteItem(path); });
}
break;
}
}

View File

@@ -111,30 +111,27 @@ MainWindow::MainWindow() :
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));
sideBar->appendTab(new FileBrowser(FileBrowser::Type::Favorites, ConfigManager::inst()->favoriteItems().join("*"), FileItem::defaultFilters(), "My Favorites",
embed::getIconPixmap("star").transformed(QTransform().rotate(90)), splitter, false, "", ""));
sideBar->appendTab(new FileBrowser(FileBrowser::Type::Normal,
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(FileBrowser::Type::Normal,
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(FileBrowser::Type::Normal,
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(FileBrowser::Type::Normal, QDir::homePath(), FileItem::defaultFilters(),
tr("My Home"), embed::getIconPixmap("home").transformed(QTransform().rotate(90)), splitter, false));
QStringList root_paths;
QString title = tr("Root Directory");
@@ -156,7 +153,7 @@ MainWindow::MainWindow() :
}
#endif
sideBar->appendTab(new FileBrowser(root_paths.join("*"), FileItem::defaultFilters(), title,
sideBar->appendTab(new FileBrowser(FileBrowser::Type::Normal, root_paths.join("*"), FileItem::defaultFilters(), title,
embed::getIconPixmap("computer").transformed(QTransform().rotate(90)), splitter, dirs_as_items));
m_workspace = new MovableQMdiArea(splitter);