Skip to content

Commit 9159533

Browse files
AW1534tresfPhysSongsakertoothmessmerd
authored
FileBrowser: "Open containing folder" automatically selects file in the OS's file manager (LMMS#7700)
Introduces a new class FileRevealer to manage file selection across different platforms (Windows, macOS, Linux, and other *nix operating systems with xdg). Includes methods to open and select files or directories using the default file manager on the respective platform. --------- Co-authored-by: Tres Finocchiaro <tres.finocchiaro@gmail.com> Co-authored-by: Hyunjin Song <tteu.ingog@gmail.com> Co-authored-by: Sotonye Atemie <sakertooth@gmail.com> Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com>
1 parent 050df38 commit 9159533

File tree

5 files changed

+298
-43
lines changed

5 files changed

+298
-43
lines changed

include/FileBrowser.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,6 @@ private slots:
195195
bool openInNewSampleTrack( lmms::gui::FileItem* item );
196196
void sendToActiveInstrumentTrack( lmms::gui::FileItem* item );
197197
void updateDirectory( QTreeWidgetItem * item );
198-
void openContainingFolder( lmms::gui::FileItem* item );
199-
200198
} ;
201199

202200

include/FileRevealer.h

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* FileRevealer.h - include file for FileRevealer
3+
*
4+
* Copyright (c) 2025 Andrew Wiltshire <aw1lt / at/ proton/ dot/me >
5+
*
6+
* This file is part of LMMS - https://lmms.io
7+
*
8+
* This program is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU General Public
10+
* License as published by the Free Software Foundation; either
11+
* version 2 of the License, or (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
* General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public
19+
* License along with this program (see COPYING); if not, write to the
20+
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21+
* Boston, MA 02110-1301 USA.
22+
*
23+
*/
24+
25+
#ifndef LMMS_FILE_REVEALER_H
26+
#define LMMS_FILE_REVEALER_H
27+
28+
#include <QFileInfo>
29+
30+
namespace lmms {
31+
32+
/**
33+
* @class FileRevealer
34+
* @brief A utility class for revealing files and directories in the system's file manager.
35+
*/
36+
class FileRevealer
37+
{
38+
public:
39+
/**
40+
* @brief Retrieves the default file manager for the current platform.
41+
* @return The name or command of the default file manager.
42+
*/
43+
static const QString& getDefaultFileManager();
44+
45+
/**
46+
* @brief Opens the directory containing the specified file or folder in the file manager.
47+
* @param item The QFileInfo object representing the file or folder.
48+
*/
49+
static void openDir(const QFileInfo item);
50+
51+
/**
52+
* @brief Checks whether the file manager supports selecting a specific file.
53+
* @return True if selection is supported, otherwise false.
54+
*/
55+
static const QStringList& getSelectCommand();
56+
57+
/**
58+
* @brief Opens the file manager and selects the specified file if supported.
59+
* @param item The QFileInfo object representing the file to reveal.
60+
*/
61+
static void reveal(const QFileInfo item);
62+
63+
private:
64+
static bool s_canSelect;
65+
66+
protected:
67+
/**
68+
* @brief Determines if the given command supports the argument
69+
* @param command The name of the file manager to check.
70+
* @param arg The command line argument to parse for
71+
* @return True if the file command the argument, otherwise false.
72+
*/
73+
static bool supportsArg(const QString& command, const QString& arg);
74+
};
75+
76+
} // namespace lmms
77+
#endif // LMMS_FILE_REVEALER_H

src/gui/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ SET(LMMS_SRCS
1414
gui/EffectView.cpp
1515
gui/embed.cpp
1616
gui/FileBrowser.cpp
17+
gui/FileRevealer.cpp
1718
gui/GuiApplication.cpp
1819
gui/LadspaControlView.cpp
1920
gui/LfoControllerDialog.cpp

src/gui/FileBrowser.cpp

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@
2626
#include "FileBrowser.h"
2727

2828
#include <QApplication>
29-
#include <QDesktopServices>
3029
#include <QDirIterator>
3130
#include <QHBoxLayout>
32-
#include <QKeyEvent>
3331
#include <QLineEdit>
3432
#include <QMdiArea>
3533
#include <QMdiSubWindow>
3634
#include <QMenu>
3735
#include <QMessageBox>
36+
#include <QProcess>
3837
#include <QPushButton>
3938
#include <QShortcut>
4039
#include <QStringList>
@@ -45,7 +44,7 @@
4544
#include "ConfigManager.h"
4645
#include "DataFile.h"
4746
#include "Engine.h"
48-
#include "FileBrowser.h"
47+
#include "FileRevealer.h"
4948
#include "FileSearch.h"
5049
#include "GuiApplication.h"
5150
#include "ImportFilter.h"
@@ -624,28 +623,35 @@ void FileBrowserTreeWidget::focusOutEvent(QFocusEvent* fe)
624623
QTreeWidget::focusOutEvent(fe);
625624
}
626625

626+
void FileBrowserTreeWidget::contextMenuEvent(QContextMenuEvent* e)
627+
{
628+
#ifdef LMMS_BUILD_APPLE
629+
QString fileManager = tr("Finder");
630+
#elif defined(LMMS_BUILD_WIN32)
631+
QString fileManager = tr("Explorer");
632+
#else
633+
QString fileManager = tr("file manager");
634+
#endif
627635

636+
QTreeWidgetItem* item = itemAt(e->pos());
637+
if (item == nullptr) { return; } // program hangs when right-clicking on empty space otherwise
628638

639+
QMenu contextMenu(this);
629640

630-
void FileBrowserTreeWidget::contextMenuEvent(QContextMenuEvent * e )
631-
{
632-
auto file = dynamic_cast<FileItem*>(itemAt(e->pos()));
633-
if( file != nullptr && file->isTrack() )
641+
switch (item->type())
634642
{
635-
QMenu contextMenu( this );
636-
637-
contextMenu.addAction(
638-
tr( "Send to active instrument-track" ),
639-
[=, this]{ sendToActiveInstrumentTrack(file); }
640-
);
643+
case TypeFileItem: {
644+
auto file = dynamic_cast<FileItem*>(item);
641645

642-
contextMenu.addSeparator();
646+
if (file->isTrack())
647+
{
648+
contextMenu.addAction(
649+
tr("Send to active instrument-track"), [=, this] { sendToActiveInstrumentTrack(file); });
650+
contextMenu.addSeparator();
651+
}
643652

644-
contextMenu.addAction(
645-
QIcon(embed::getIconPixmap("folder")),
646-
tr("Open containing folder"),
647-
[=, this]{ openContainingFolder(file); }
648-
);
653+
contextMenu.addAction(QIcon(embed::getIconPixmap("folder")), tr("Show in %1").arg(fileManager),
654+
[=] { FileRevealer::reveal(file->fullName()); });
649655

650656
auto songEditorHeader = new QAction(tr("Song Editor"), nullptr);
651657
songEditorHeader->setDisabled(true);
@@ -656,14 +662,20 @@ void FileBrowserTreeWidget::contextMenuEvent(QContextMenuEvent * e )
656662
patternEditorHeader->setDisabled(true);
657663
contextMenu.addAction(patternEditorHeader);
658664
contextMenu.addActions( getContextActions(file, false) );
659-
660-
// We should only show the menu if it contains items
661-
if (!contextMenu.isEmpty()) { contextMenu.exec( e->globalPos() ); }
665+
break;
666+
}
667+
case TypeDirectoryItem: {
668+
auto dir = dynamic_cast<Directory*>(item);
669+
contextMenu.addAction(QIcon(embed::getIconPixmap("folder")), tr("Open in %1").arg(fileManager), [=] {
670+
FileRevealer::openDir(dir->fullName());
671+
});
672+
break;
673+
}
662674
}
663-
}
664-
665-
666675

676+
// Only show the menu if it contains items
677+
if (!contextMenu.isEmpty()) { contextMenu.exec(e->globalPos()); }
678+
}
667679

668680
QList<QAction*> FileBrowserTreeWidget::getContextActions(FileItem* file, bool songEditor)
669681
{
@@ -991,22 +1003,6 @@ bool FileBrowserTreeWidget::openInNewSampleTrack(FileItem* item)
9911003

9921004

9931005

994-
995-
void FileBrowserTreeWidget::openContainingFolder(FileItem* item)
996-
{
997-
// Delegate to QDesktopServices::openUrl with the directory of the selected file. Please note that
998-
// this will only open the directory but not select the file as this is much more complicated due
999-
// to different implementations that are needed for different platforms (Linux/Windows/MacOS).
1000-
1001-
// Using QDesktopServices::openUrl seems to be the most simple cross platform way which uses
1002-
// functionality that's already available in Qt.
1003-
QFileInfo fileInfo(item->fullName());
1004-
QDesktopServices::openUrl(QUrl::fromLocalFile(fileInfo.dir().path()));
1005-
}
1006-
1007-
1008-
1009-
10101006
void FileBrowserTreeWidget::sendToActiveInstrumentTrack( FileItem* item )
10111007
{
10121008
// get all windows opened in the workspace

0 commit comments

Comments
 (0)