Improve Linux file dialog support

Adds support for multi selection, filtering and initial folder for both zenity and kdialog. kdialog is also preferred over zenity when KDE is detected.
This commit is contained in:
2023-02-03 22:00:50 +02:00
parent d8a9b699ad
commit 0ac5db45d1
4 changed files with 62 additions and 12 deletions

View File

@@ -50,11 +50,16 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(FileSystemBase);
/// </summary> /// </summary>
/// <param name="parentWindow">The parent window or null.</param> /// <param name="parentWindow">The parent window or null.</param>
/// <param name="initialDirectory">The initial directory.</param> /// <param name="initialDirectory">The initial directory.</param>
/// <param name="filter">The custom filter.</param> /// <param name="filter">The file filter string as null-terminated pairs of name and list of extensions. Multiple file extensions must be separated with semicolon.</param>
/// <param name="multiSelect">True if allow multiple files to be selected, otherwise use single-file mode.</param> /// <param name="multiSelect">True if allow multiple files to be selected, otherwise use single-file mode.</param>
/// <param name="title">The dialog title.</param> /// <param name="title">The dialog title.</param>
/// <param name="filenames">The output names of the files picked by the user.</param> /// <param name="filenames">The output names of the files picked by the user.</param>
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
/// <remarks>
/// Example file filters:
/// "All Files\0*.*"
/// "All Files\0*.*\0Image Files\0*.png;*.jpg"
/// </remarks>
API_FUNCTION() static bool ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, API_PARAM(Out) Array<String, HeapAllocation>& filenames); API_FUNCTION() static bool ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, API_PARAM(Out) Array<String, HeapAllocation>& filenames);
/// <summary> /// <summary>
@@ -62,11 +67,16 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(FileSystemBase);
/// </summary> /// </summary>
/// <param name="parentWindow">The parent window.</param> /// <param name="parentWindow">The parent window.</param>
/// <param name="initialDirectory">The initial directory.</param> /// <param name="initialDirectory">The initial directory.</param>
/// <param name="filter">The filter.</param> /// <param name="filter">The file filter string as null-terminated pairs of name and list of extensions. Multiple file extensions must be separated with semicolon.</param>
/// <param name="multiSelect">True if allow multiple files to be selected, otherwise use single-file mode.</param> /// <param name="multiSelect">True if allow multiple files to be selected, otherwise use single-file mode.</param>
/// <param name="title">The title.</param> /// <param name="title">The title.</param>
/// <param name="filenames">The output names of the files picked by the user.</param> /// <param name="filenames">The output names of the files picked by the user.</param>
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
/// <remarks>
/// Example file filters:
/// "All Files\0*.*"
/// "All Files\0*.*\0Image Files\0*.png;*.jpg"
/// </remarks>
API_FUNCTION() static bool ShowSaveFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, API_PARAM(Out) Array<String, HeapAllocation>& filenames); API_FUNCTION() static bool ShowSaveFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, API_PARAM(Out) Array<String, HeapAllocation>& filenames);
/// <summary> /// <summary>

View File

@@ -528,4 +528,17 @@ String StringUtils::ToString(double value)
return String::Format(TEXT("{}"), value); return String::Format(TEXT("{}"), value);
} }
String StringUtils::GetZZString(const Char* str)
{
const Char* end = str;
while (*end != '\0')
{
end++;
if (*end == '\0')
end++;
}
const int len = end - str;
return String(str, len);
}
#undef STRING_UTILS_ITOSTR_BUFFER_SIZE #undef STRING_UTILS_ITOSTR_BUFFER_SIZE

View File

@@ -4,7 +4,9 @@
#include "LinuxFileSystem.h" #include "LinuxFileSystem.h"
#include "Engine/Platform/File.h" #include "Engine/Platform/File.h"
#include "Engine/Platform/StringUtils.h"
#include "Engine/Core/Types/String.h" #include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringBuilder.h"
#include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Math/Math.h" #include "Engine/Core/Math/Math.h"
@@ -27,20 +29,38 @@ bool LinuxFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView&
{ {
const StringAsANSI<> initialDirectoryAnsi(*initialDirectory, initialDirectory.Length()); const StringAsANSI<> initialDirectoryAnsi(*initialDirectory, initialDirectory.Length());
const StringAsANSI<> titleAnsi(*title, title.Length()); const StringAsANSI<> titleAnsi(*title, title.Length());
const char* initDir = initialDirectory.HasChars() ? initialDirectoryAnsi.Get() : ".";
char cmd[2048]; char cmd[2048];
if (FileSystem::FileExists(TEXT("/usr/bin/zenity"))) String xdgCurrentDesktop;
StringBuilder fileFilter;
Array<String> fileFilterEntries;
Platform::GetEnvironmentVariable(TEXT("XDG_CURRENT_DESKTOP"), xdgCurrentDesktop);
StringUtils::GetZZString(filter.Get()).Split('\0', fileFilterEntries);
const bool zenitySupported = FileSystem::FileExists(TEXT("/usr/bin/zenity"));
const bool kdialogSupported = FileSystem::FileExists(TEXT("/usr/bin/kdialog"));
if (zenitySupported && (xdgCurrentDesktop != TEXT("KDE") || !kdialogSupported)) // Prefer kdialog when running on KDE
{ {
// TODO: initialDirectory support for (int32 i = 1; i < fileFilterEntries.Count(); i += 2)
// TODO: multiSelect support {
// TODO: filter support String extensions(fileFilterEntries[i]);
sprintf(cmd, "/usr/bin/zenity --modal --file-selection --title=\"%s\" ", titleAnsi.Get()); fileFilterEntries[i].Replace(TEXT(";"), TEXT(" "));
fileFilter.Append(String::Format(TEXT("{0}--file-filter=\"{1}|{2}\""), i > 1 ? TEXT(" ") : TEXT(""), fileFilterEntries[i-1].Get(), extensions.Get()));
}
sprintf(cmd, "/usr/bin/zenity --modal --file-selection %s--filename=\"%s\" --title=\"%s\" %s ", multiSelect ? "--multiple --separator=$'\n' " : " ", initDir, titleAnsi.Get(), fileFilter.ToStringView().ToStringAnsi().GetText());
} }
else if (FileSystem::FileExists(TEXT("/usr/bin/kdialog"))) else if (kdialogSupported)
{ {
// TODO: multiSelect support for (int32 i = 1; i < fileFilterEntries.Count(); i += 2)
// TODO: filter support {
const char* initDir = initialDirectory.HasChars() ? initialDirectoryAnsi.Get() : "."; String extensions(fileFilterEntries[i]);
sprintf(cmd, "/usr/bin/kdialog --getopenfilename \"%s\" --title \"%s\" ", initDir, titleAnsi.Get()); fileFilterEntries[i].Replace(TEXT(";"), TEXT(" "));
fileFilter.Append(String::Format(TEXT("{0}\"{1}({2})\""), i > 1 ? TEXT(" ") : TEXT(""), fileFilterEntries[i-1].Get(), extensions.Get()));
}
fileFilter.Append(String::Format(TEXT("{0}\"{1}({2})\""), TEXT(" "), TEXT("many things"), TEXT("*.png *.jpg")));
sprintf(cmd, "/usr/bin/kdialog --getopenfilename %s--title \"%s\" \"%s\" %s ", multiSelect ? "--multiple --separate-output " : " ", titleAnsi.Get(), initDir, fileFilter.ToStringView().ToStringAnsi().GetText());
} }
else else
{ {

View File

@@ -438,6 +438,13 @@ public:
static String ToString(uint64 value); static String ToString(uint64 value);
static String ToString(float value); static String ToString(float value);
static String ToString(double value); static String ToString(double value);
public:
// Returns the String to double null-terminated string
// @param str Double null-terminated string
// @return Double null-terminated String
static String GetZZString(const Char* str);
}; };
inline uint32 GetHash(const char* key) inline uint32 GetHash(const char* key)