From 3273e550a172c7cb13af20a69be9109578b3c514 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Wed, 25 Mar 2020 01:46:12 -0700 Subject: [PATCH] ui: Use native file selection dialogs --- .gitmodules | 3 - build.sh | 4 - configure | 21 +++ ui/ImGuiFileDialog | 1 - ui/Makefile.objs | 8 +- ui/noc_file_dialog.h | 287 +++++++++++++++++++++++++++++++++++++ ui/noc_file_dialog_gtk.c | 3 + ui/noc_file_dialog_macos.m | 3 + ui/noc_file_dialog_win32.c | 3 + ui/xemu-hud.cc | 30 ++-- 10 files changed, 336 insertions(+), 27 deletions(-) delete mode 160000 ui/ImGuiFileDialog create mode 100644 ui/noc_file_dialog.h create mode 100644 ui/noc_file_dialog_gtk.c create mode 100644 ui/noc_file_dialog_macos.m create mode 100644 ui/noc_file_dialog_win32.c diff --git a/.gitmodules b/.gitmodules index a5c960e731..f2c1c26910 100644 --- a/.gitmodules +++ b/.gitmodules @@ -61,9 +61,6 @@ [submodule "ui/imgui"] path = ui/imgui url = https://github.com/ocornut/imgui -[submodule "ui/ImGuiFileDialog"] - path = ui/ImGuiFileDialog - url = https://github.com/aiekick/ImGuiFileDialog [submodule "ui/inih"] path = ui/inih url = https://github.com/benhoyt/inih diff --git a/build.sh b/build.sh index 272fae2ca8..cdba438e7b 100755 --- a/build.sh +++ b/build.sh @@ -75,10 +75,6 @@ esac # Ensure required submodules get checked out git submodule update --init ui/inih git submodule update --init ui/imgui -git submodule update --init ui/ImGuiFileDialog -pushd ui/ImGuiFileDialog -git submodule update --init 3rdparty/dirent -popd # find absolute path (and resolve symlinks) to build out of tree configure="$(dirname "$($readlink -f "${0}")")/configure" diff --git a/configure b/configure index f4d962493a..399e05b29c 100755 --- a/configure +++ b/configure @@ -4390,6 +4390,27 @@ fi # test "$opengl" != "no" # LIBS="$LIBS $epoxy_libs" # QEMU_CFLAGS="$QEMU_CFLAGS $epoxy_flags" +# Depend on GTK for native file dialog boxes +# FIXME: Add fallback when GTK is unavailable +if test "$linux" = "yes" ; then + xemu_gtk=yes +fi + +if test "$xemu_gtk" != "no"; then + gtkpackage="gtk+-3.0" + gtkversion="3.14.0" + if $pkg_config --exists "$gtkpackage >= $gtkversion"; then + xemu_gtk_cflags=$($pkg_config --cflags $gtkpackage) + xemu_gtk_libs=$($pkg_config --libs $gtkpackage) + xemu_gtk_version=$($pkg_config --modversion $gtkpackage) + QEMU_LDFLAGS="$QEMU_LDFLAGS $xemu_gtk_libs" + QEMU_CFLAGS="$QEMU_CFLAGS $xemu_gtk_cflags" + elif test "$xemu_gtk" = "yes"; then + feature_not_found "gtk" "Install gtk3-devel" + fi +fi + + ########################################## # libxml2 probe diff --git a/ui/ImGuiFileDialog b/ui/ImGuiFileDialog deleted file mode 160000 index a69099ad75..0000000000 --- a/ui/ImGuiFileDialog +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a69099ad7557b4ef21ea649277d8e589e28f4af8 diff --git a/ui/Makefile.objs b/ui/Makefile.objs index 60439718e4..56bbf711a5 100644 --- a/ui/Makefile.objs +++ b/ui/Makefile.objs @@ -37,19 +37,21 @@ sdl.mo-objs := \ imgui/imgui_draw.o \ imgui/imgui_widgets.o \ imgui/examples/imgui_impl_opengl3.o \ - imgui/examples/imgui_impl_sdl.o \ - ImGuiFileDialog/ImGuiFileDialog.o + imgui/examples/imgui_impl_sdl.o ui/xemu-shaders.o: ui/shader/xemu-logo-frag.h ifeq ($(CONFIG_WIN32),y) -IMGUI_FLAGS = -DWIN32 -DMINGW32 -Iui/ImGuiFileDialog/3rdparty/dirent/include +IMGUI_FLAGS = -DWIN32 -DMINGW32 +sdl.mo-objs := $(sdl.mo-objs) noc_file_dialog_win32.o endif ifeq ($(CONFIG_LINUX),y) IMGUI_FLAGS = -DLINUX +sdl.mo-objs := $(sdl.mo-objs) noc_file_dialog_gtk.o endif ifeq ($(CONFIG_DARWIN),y) IMGUI_FLAGS = -DAPPLE +sdl.mo-objs := $(sdl.mo-objs) noc_file_dialog_macos.o endif sdl.mo-cflags := $(SDL_CFLAGS) -DIMGUI_IMPL_OPENGL_LOADER_CUSTOM="\"epoxy/gl.h\"" -Iui/imgui $(IMGUI_FLAGS) diff --git a/ui/noc_file_dialog.h b/ui/noc_file_dialog.h new file mode 100644 index 0000000000..5fb8e0013f --- /dev/null +++ b/ui/noc_file_dialog.h @@ -0,0 +1,287 @@ +/* noc_file_dialog library + * + * Copyright (c) 2015 Guillaume Chereau + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* A portable library to create open and save dialogs on linux, osx and + * windows. + * + * The library define a single function : noc_file_dialog_open. + * With three different implementations. + * + * Usage: + * + * The library does not automatically select the implementation, you need to + * define one of those macros before including this file: + * + * NOC_FILE_DIALOG_GTK + * NOC_FILE_DIALOG_WIN32 + * NOC_FILE_DIALOG_OSX + */ + +enum { + NOC_FILE_DIALOG_OPEN = 1 << 0, // Create an open file dialog. + NOC_FILE_DIALOG_SAVE = 1 << 1, // Create a save file dialog. + NOC_FILE_DIALOG_DIR = 1 << 2, // Open a directory. + NOC_FILE_DIALOG_OVERWRITE_CONFIRMATION = 1 << 3, +}; + +// There is a single function defined. + +/* flags : union of the NOC_FILE_DIALOG_XXX masks. + * filters : a list of strings separated by '\0' of the form: + * "name1 reg1 name2 reg2 ..." + * The last value is followed by two '\0'. For example, + * to filter png and jpeg files, you can use: + * "png\0*.png\0jpeg\0*.jpeg\0" + * You can also separate patterns with ';': + * "jpeg\0*.jpg;*.jpeg\0" + * Set to NULL for no filter. + * default_path : the default file to use or NULL. + * default_name : the default file name to use or NULL. + * + * The function return a C string. There is no need to free it, as it is + * managed by the library. The string is valid until the next call to + * no_dialog_open. If the user canceled, the return value is NULL. + */ +const char *noc_file_dialog_open(int flags, + const char *filters, + const char *default_path, + const char *default_name); + +#ifdef NOC_FILE_DIALOG_IMPLEMENTATION + +#include +#include + +static char *g_noc_file_dialog_ret = NULL; + +#ifdef NOC_FILE_DIALOG_GTK + +#include + +#ifdef GDK_WINDOWING_X11 +#include +#endif + +const char *noc_file_dialog_open(int flags, + const char *filters, + const char *default_path, + const char *default_name) +{ + GtkWidget *dialog; + GtkFileFilter *filter; + GtkFileChooser *chooser; + GtkFileChooserAction action; + gint res; + char buf[128], *patterns; + + action = flags & NOC_FILE_DIALOG_SAVE ? GTK_FILE_CHOOSER_ACTION_SAVE : + GTK_FILE_CHOOSER_ACTION_OPEN; + if (flags & NOC_FILE_DIALOG_DIR) + action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; + + gtk_init_check(NULL, NULL); + dialog = gtk_file_chooser_dialog_new( + flags & NOC_FILE_DIALOG_SAVE ? "Save File" : "Open File", + NULL, + action, + "_Cancel", GTK_RESPONSE_CANCEL, + flags & NOC_FILE_DIALOG_SAVE ? "_Save" : "_Open", GTK_RESPONSE_ACCEPT, + NULL ); + chooser = GTK_FILE_CHOOSER(dialog); + if (flags & NOC_FILE_DIALOG_OVERWRITE_CONFIRMATION) + gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE); + + if (default_path) + gtk_file_chooser_set_filename(chooser, default_path); + if (default_name) + gtk_file_chooser_set_current_name(chooser, default_name); + + while (filters && *filters) { + filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, filters); + filters += strlen(filters) + 1; + + // Split the filter pattern with ';'. + strcpy(buf, filters); + buf[strlen(buf)] = '\0'; + for (patterns = buf; *patterns; patterns++) + if (*patterns == ';') *patterns = '\0'; + patterns = buf; + while (*patterns) { + gtk_file_filter_add_pattern(filter, patterns); + patterns += strlen(patterns) + 1; + } + + gtk_file_chooser_add_filter(chooser, filter); + filters += strlen(filters) + 1; + } + + gtk_widget_show_all(dialog); +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) { + GdkWindow *window = gtk_widget_get_window(dialog); + gdk_window_set_events(window, + gdk_window_get_events(window) | GDK_PROPERTY_CHANGE_MASK); + gtk_window_present_with_time(GTK_WINDOW(dialog), + gdk_x11_get_server_time(window)); + } +#endif + res = gtk_dialog_run(GTK_DIALOG(dialog)); + + free(g_noc_file_dialog_ret); + g_noc_file_dialog_ret = NULL; + + if (res == GTK_RESPONSE_ACCEPT) + g_noc_file_dialog_ret = gtk_file_chooser_get_filename(chooser); + gtk_widget_destroy(dialog); + while (gtk_events_pending()) gtk_main_iteration(); + return g_noc_file_dialog_ret; +} + +#endif + +#ifdef NOC_FILE_DIALOG_WIN32 + +#include +#include + +const char *noc_file_dialog_open(int flags, + const char *filters, + const char *default_path, + const char *default_name) +{ + OPENFILENAME ofn; // common dialog box structure + char szFile[_MAX_PATH]; // buffer for file name + char initialDir[_MAX_PATH]; + char drive[_MAX_DRIVE]; + char dir[_MAX_DIR]; + char fname[_MAX_FNAME]; + char ext[_MAX_EXT]; + int ret; + + // init default dir and file name + _splitpath_s(default_path, drive, _MAX_DRIVE, dir, _MAX_DIR, fname, + _MAX_FNAME, ext, _MAX_EXT ); + _makepath_s(initialDir, _MAX_PATH, drive, dir, NULL, NULL); + if (default_name) + strncpy(szFile, default_name, sizeof(szFile) - 1); + else + _makepath_s(szFile, _MAX_PATH, NULL, NULL, fname, ext); + + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFile = szFile; + ofn.nMaxFile = sizeof(szFile); + ofn.lpstrFilter = filters; + ofn.nFilterIndex = 1; + ofn.lpstrFileTitle = NULL; + ofn.nMaxFileTitle = 0; + ofn.lpstrInitialDir = initialDir; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; + + if (flags & NOC_FILE_DIALOG_OPEN) + ret = GetOpenFileName(&ofn); + else + ret = GetSaveFileName(&ofn); + + free(g_noc_file_dialog_ret); + g_noc_file_dialog_ret = ret ? strdup(szFile) : NULL; + return g_noc_file_dialog_ret; +} + +#endif + +#ifdef NOC_FILE_DIALOG_OSX + +#include + +const char *noc_file_dialog_open(int flags, + const char *filters, + const char *default_path, + const char *default_name) +{ + NSURL *url; + const char *utf8_path; + NSSavePanel *panel; + NSOpenPanel *open_panel; + NSMutableArray *types_array; + NSURL *default_url; + char buf[128], *patterns; + // XXX: I don't know about memory management with cococa, need to check + // if I leak memory here. + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + if (flags & NOC_FILE_DIALOG_OPEN) { + panel = open_panel = [NSOpenPanel openPanel]; + } else { + panel = [NSSavePanel savePanel]; + } + + if (flags & NOC_FILE_DIALOG_DIR) { + [open_panel setCanChooseDirectories:YES]; + [open_panel setCanChooseFiles:NO]; + } + + if (default_path) { + default_url = [NSURL fileURLWithPath: + [NSString stringWithUTF8String:default_path]]; + [panel setDirectoryURL:default_url]; + [panel setNameFieldStringValue:default_url.lastPathComponent]; + } + + if (filters) { + types_array = [NSMutableArray array]; + while (*filters) { + filters += strlen(filters) + 1; // skip the name + // Split the filter pattern with ';'. + strcpy(buf, filters); + buf[strlen(buf) + 1] = '\0'; + for (patterns = buf; *patterns; patterns++) + if (*patterns == ';') *patterns = '\0'; + patterns = buf; + while (*patterns) { + assert(strncmp(patterns, "*.", 2) == 0); + patterns += 2; // Skip the "*." + [types_array addObject:[NSString stringWithUTF8String: patterns]]; + patterns += strlen(patterns) + 1; + } + filters += strlen(filters) + 1; + } + [panel setAllowedFileTypes:types_array]; + } + + free(g_noc_file_dialog_ret); + g_noc_file_dialog_ret = NULL; + if ( [panel runModal] == NSModalResponseOK ) { + url = [panel URL]; + utf8_path = [[url path] UTF8String]; + g_noc_file_dialog_ret = strdup(utf8_path); + } + + [pool release]; + return g_noc_file_dialog_ret; +} +#endif + + +#endif diff --git a/ui/noc_file_dialog_gtk.c b/ui/noc_file_dialog_gtk.c new file mode 100644 index 0000000000..92976254ec --- /dev/null +++ b/ui/noc_file_dialog_gtk.c @@ -0,0 +1,3 @@ +#define NOC_FILE_DIALOG_GTK 1 +#define NOC_FILE_DIALOG_IMPLEMENTATION 1 +#include "noc_file_dialog.h" diff --git a/ui/noc_file_dialog_macos.m b/ui/noc_file_dialog_macos.m new file mode 100644 index 0000000000..6ff06e9eeb --- /dev/null +++ b/ui/noc_file_dialog_macos.m @@ -0,0 +1,3 @@ +#define NOC_FILE_DIALOG_OSX 1 +#define NOC_FILE_DIALOG_IMPLEMENTATION 1 +#include "noc_file_dialog.h" diff --git a/ui/noc_file_dialog_win32.c b/ui/noc_file_dialog_win32.c new file mode 100644 index 0000000000..644c9139b0 --- /dev/null +++ b/ui/noc_file_dialog_win32.c @@ -0,0 +1,3 @@ +#define NOC_FILE_DIALOG_WIN32 1 +#define NOC_FILE_DIALOG_IMPLEMENTATION 1 +#include "noc_file_dialog.h" diff --git a/ui/xemu-hud.cc b/ui/xemu-hud.cc index d1c25f71e0..b3534c7cd5 100644 --- a/ui/xemu-hud.cc +++ b/ui/xemu-hud.cc @@ -31,11 +31,12 @@ #include "xemu-version.h" #include "imgui/imgui.h" -#include "ImGuiFileDialog/ImGuiFileDialog.h" #include "imgui/examples/imgui_impl_sdl.h" #include "imgui/examples/imgui_impl_opengl3.h" extern "C" { +#include "noc_file_dialog.h" + // Include necessary QEMU headers #include "qemu/osdep.h" #include "qemu-common.h" @@ -55,6 +56,11 @@ extern "C" { #include "qemu/option.h" #include "qemu/config-file.h" #undef typename +#undef atomic_fetch_add +#undef atomic_fetch_and +#undef atomic_fetch_xor +#undef atomic_fetch_or +#undef atomic_fetch_sub } uint32_t c = 0x81dc8a21; // FIXME: Use existing theme colors here @@ -972,20 +978,12 @@ struct SettingsWindow } ImGui::SameLine(); if (ImGui::Button("Browse...", ImVec2(100, 0))) { - ImGuiFileDialog::Instance()->OpenDialog(name, name, filters, strlen(buf) > 0 ? buf : "."); + const char *selected = noc_file_dialog_open(NOC_FILE_DIALOG_OPEN, filters, buf, NULL); + if (selected != NULL) { + strncpy(buf, selected, len-1); + } } ImGui::PopID(); - - if (ImGuiFileDialog::Instance()->FileDialog(name)) { - if (ImGuiFileDialog::Instance()->IsOk == true) { - std::string filePathName = ImGuiFileDialog::Instance()->GetFilepathName(); - std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath(); - strcpy(buf, filePathName.c_str()); - dirty = true; - } - - ImGuiFileDialog::Instance()->CloseDialog(name); - } } void Draw(const char* title, bool* p_open) @@ -1001,9 +999,9 @@ struct SettingsWindow Load(); } - const char *rom_file_filters = ".bin\0.rom\0\0"; - const char *iso_file_filters = ".iso\0\0"; - const char *qcow_file_filters = ".qcow2\0\0"; + const char *rom_file_filters = ".bin Files\0*.bin\0.rom Files\0*.rom\0All Files\0*.*\0"; + const char *iso_file_filters = ".iso Files\0*.iso\0All Files\0*.*\0"; + const char *qcow_file_filters = ".qcow2 Files\0*.qcow2\0All Files\0*.*\0"; ImGui::Columns(2, "", false); ImGui::SetColumnWidth(0, ImGui::GetWindowWidth()*0.25);