diff --git a/Source/Engine/Platform/Base/FileSystemBase.h b/Source/Engine/Platform/Base/FileSystemBase.h
index d0284042b..fc3d75bb1 100644
--- a/Source/Engine/Platform/Base/FileSystemBase.h
+++ b/Source/Engine/Platform/Base/FileSystemBase.h
@@ -50,11 +50,16 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(FileSystemBase);
///
/// The parent window or null.
/// The initial directory.
- /// The custom filter.
+ /// The file filter string as null-terminated pairs of name and list of extensions. Multiple file extensions must be separated with semicolon.
/// True if allow multiple files to be selected, otherwise use single-file mode.
/// The dialog title.
/// The output names of the files picked by the user.
/// True if failed, otherwise false.
+ ///
+ /// Example file filters:
+ /// "All Files\0*.*"
+ /// "All Files\0*.*\0Image Files\0*.png;*.jpg"
+ ///
API_FUNCTION() static bool ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, API_PARAM(Out) Array& filenames);
///
@@ -62,11 +67,16 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(FileSystemBase);
///
/// The parent window.
/// The initial directory.
- /// The filter.
+ /// The file filter string as null-terminated pairs of name and list of extensions. Multiple file extensions must be separated with semicolon.
/// True if allow multiple files to be selected, otherwise use single-file mode.
/// The title.
/// The output names of the files picked by the user.
/// True if failed, otherwise false.
+ ///
+ /// Example file filters:
+ /// "All Files\0*.*"
+ /// "All Files\0*.*\0Image Files\0*.png;*.jpg"
+ ///
API_FUNCTION() static bool ShowSaveFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, API_PARAM(Out) Array& filenames);
///
diff --git a/Source/Engine/Platform/Base/StringUtilsBase.cpp b/Source/Engine/Platform/Base/StringUtilsBase.cpp
index 6f1d741d5..d005f8a04 100644
--- a/Source/Engine/Platform/Base/StringUtilsBase.cpp
+++ b/Source/Engine/Platform/Base/StringUtilsBase.cpp
@@ -528,4 +528,17 @@ String StringUtils::ToString(double 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
diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp
index 7291dc8ac..f8ad0d476 100644
--- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp
+++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp
@@ -4,7 +4,9 @@
#include "LinuxFileSystem.h"
#include "Engine/Platform/File.h"
+#include "Engine/Platform/StringUtils.h"
#include "Engine/Core/Types/String.h"
+#include "Engine/Core/Types/StringBuilder.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/TimeSpan.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<> titleAnsi(*title, title.Length());
+ const char* initDir = initialDirectory.HasChars() ? initialDirectoryAnsi.Get() : ".";
char cmd[2048];
- if (FileSystem::FileExists(TEXT("/usr/bin/zenity")))
+ String xdgCurrentDesktop;
+ StringBuilder fileFilter;
+ Array 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
- // TODO: multiSelect support
- // TODO: filter support
- sprintf(cmd, "/usr/bin/zenity --modal --file-selection --title=\"%s\" ", titleAnsi.Get());
+ for (int32 i = 1; i < fileFilterEntries.Count(); i += 2)
+ {
+ String extensions(fileFilterEntries[i]);
+ 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
- // TODO: filter support
- const char* initDir = initialDirectory.HasChars() ? initialDirectoryAnsi.Get() : ".";
- sprintf(cmd, "/usr/bin/kdialog --getopenfilename \"%s\" --title \"%s\" ", initDir, titleAnsi.Get());
+ for (int32 i = 1; i < fileFilterEntries.Count(); i += 2)
+ {
+ String extensions(fileFilterEntries[i]);
+ 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
{
diff --git a/Source/Engine/Platform/StringUtils.h b/Source/Engine/Platform/StringUtils.h
index 6f90bbe92..ea982c2ee 100644
--- a/Source/Engine/Platform/StringUtils.h
+++ b/Source/Engine/Platform/StringUtils.h
@@ -438,6 +438,13 @@ public:
static String ToString(uint64 value);
static String ToString(float 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)