ui: Use native file selection dialogs

This commit is contained in:
Matt Borgerson 2020-03-25 01:46:12 -07:00
parent a0e1a7fa40
commit 3273e550a1
10 changed files with 336 additions and 27 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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"

21
configure vendored
View File

@ -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

@ -1 +0,0 @@
Subproject commit a69099ad7557b4ef21ea649277d8e589e28f4af8

View File

@ -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)

287
ui/noc_file_dialog.h Normal file
View File

@ -0,0 +1,287 @@
/* noc_file_dialog library
*
* Copyright (c) 2015 Guillaume Chereau <guillaume@noctua-software.com>
*
* 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 <stdlib.h>
#include <string.h>
static char *g_noc_file_dialog_ret = NULL;
#ifdef NOC_FILE_DIALOG_GTK
#include <gtk/gtk.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#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 <windows.h>
#include <commdlg.h>
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 <AppKit/AppKit.h>
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

3
ui/noc_file_dialog_gtk.c Normal file
View File

@ -0,0 +1,3 @@
#define NOC_FILE_DIALOG_GTK 1
#define NOC_FILE_DIALOG_IMPLEMENTATION 1
#include "noc_file_dialog.h"

View File

@ -0,0 +1,3 @@
#define NOC_FILE_DIALOG_OSX 1
#define NOC_FILE_DIALOG_IMPLEMENTATION 1
#include "noc_file_dialog.h"

View File

@ -0,0 +1,3 @@
#define NOC_FILE_DIALOG_WIN32 1
#define NOC_FILE_DIALOG_IMPLEMENTATION 1
#include "noc_file_dialog.h"

View File

@ -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);