mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-30 06:32:42 +00:00
Several changes to the native file selector on all platforms
* Added she::FileDialog::setType() to select the different kind of dialog (open one file, multiple files, save one file, open a folder) * Added capacity to select folders with she::FileDialog * Added she::CommonFileDialog so all native dialogs share common code * Windows: * Reimplemented using the IFileDialog COM interface introduced in Windows Vista (this one supports choosing folders when FOS_PICKFOLDERS flags is used) * Added she::ComPtr<> utility class to Windows port (it might be useful in laf::base in a future, or we might use the Microsoft CComPtr or _com_ptr_t types in a future) * GTK: * Reimplemented to use gdk and gtk directly instead of using gtkmm library * Now it's centered on the given she::Display (instead of being centered on the screen) * Default location is the desktop instead of documents (this should be configurable in a future "more generic" solution) * Removed WITH_DEPRECATED_GLIB_SUPPORT and fixed compilation with GTK file selector
This commit is contained in:
parent
d088ee0f38
commit
5811bba2b1
@ -55,7 +55,6 @@ enable_testing()
|
||||
|
||||
option(WITH_WEBP_SUPPORT "Enable support to load/save .webp files" off)
|
||||
option(WITH_GTK_FILE_DIALOG_SUPPORT "Enable support for the experimental native GTK File Dialog" off)
|
||||
option(WITH_DEPRECATED_GLIB_SUPPORT "Enable support for older glib versions" off)
|
||||
option(WITH_DESKTOP_INTEGRATION "Enable desktop integration modules" off)
|
||||
option(WITH_QT_THUMBNAILER "Enable kde5/qt5 thumnailer" off)
|
||||
|
||||
@ -93,6 +92,16 @@ else()
|
||||
option(USE_SKIA_BACKEND "Use Skia backend" off)
|
||||
endif()
|
||||
|
||||
# Check valid gtk + libpng combination
|
||||
if(WITH_GTK_FILE_DIALOG_SUPPORT)
|
||||
if(NOT USE_SHARED_LIBPNG)
|
||||
message(FATAL_ERROR "Cannot compile with gtk and static libpng, set USE_SHARED_LIBPNG=ON")
|
||||
endif()
|
||||
if(NOT USE_SHARED_HARFBUZZ)
|
||||
message(FATAL_ERROR "Cannot compile with gtk and static HarfBuzz, set USE_SHARED_HARFBUZZ=ON")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
######################################################################
|
||||
# Profile build type
|
||||
|
||||
@ -395,13 +404,6 @@ if(UNIX AND NOT APPLE AND NOT BEOS)
|
||||
if(XDGA_FOUND)
|
||||
list(APPEND PLATFORM_LIBS Xxf86dga ${X11_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(WITH_GTK_FILE_DIALOG_SUPPORT)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTKMM gtkmm-3.0)
|
||||
include_directories(${GTKMM_INCLUDE_DIRS})
|
||||
link_directories(${GTKMM_LIBRARY_DIRS})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# -- Windows --
|
||||
|
@ -37,17 +37,19 @@ bool show_file_selector(
|
||||
dlg->setTitle(title);
|
||||
dlg->setFileName(initialPath);
|
||||
|
||||
she::FileDialog::Type nativeType = she::FileDialog::Type::OpenFile;
|
||||
switch (type) {
|
||||
case FileSelectorType::Open:
|
||||
nativeType = she::FileDialog::Type::OpenFile;
|
||||
break;
|
||||
case FileSelectorType::OpenMultiple:
|
||||
dlg->toOpenFile();
|
||||
if (type == FileSelectorType::OpenMultiple)
|
||||
dlg->setMultipleSelection(true);
|
||||
nativeType = she::FileDialog::Type::OpenFiles;
|
||||
break;
|
||||
case FileSelectorType::Save:
|
||||
dlg->toSaveFile();
|
||||
nativeType = she::FileDialog::Type::SaveFile;
|
||||
break;
|
||||
}
|
||||
dlg->setType(nativeType);
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
base::split_string(showExtensions, tokens, ",");
|
||||
|
@ -153,9 +153,12 @@ if(APPLE)
|
||||
endif()
|
||||
|
||||
if(WITH_GTK_FILE_DIALOG_SUPPORT AND UNIX AND NOT APPLE AND NOT BEOS)
|
||||
if(WITH_DEPRECATED_GLIB_SUPPORT)
|
||||
add_definitions(-DASEPRITE_DEPRECATED_GLIB_SUPPORT)
|
||||
endif()
|
||||
# Find gtkmm library
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK gtk+-3.0)
|
||||
include_directories(${GTK_INCLUDE_DIRS})
|
||||
link_directories(${GTK_LIBRARY_DIRS})
|
||||
|
||||
add_definitions(-DASEPRITE_WITH_GTK_FILE_DIALOG_SUPPORT)
|
||||
list(APPEND SHE_SOURCES
|
||||
gtk/native_dialogs.cpp)
|
||||
|
@ -736,7 +736,7 @@ void* Alleg4Display::nativeHandle()
|
||||
#elif defined __APPLE__
|
||||
return get_osx_window();
|
||||
#else
|
||||
return nullptr;
|
||||
return (void*)_xwin.window;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
53
src/she/common/file_dialog.h
Normal file
53
src/she/common/file_dialog.h
Normal file
@ -0,0 +1,53 @@
|
||||
// SHE library
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef SHE_COMMON_FILE_DIALOG_H
|
||||
#define SHE_COMMON_FILE_DIALOG_H
|
||||
#pragma once
|
||||
|
||||
#include "she/native_dialogs.h"
|
||||
|
||||
namespace she {
|
||||
|
||||
class CommonFileDialog : public FileDialog {
|
||||
public:
|
||||
CommonFileDialog()
|
||||
: m_type(Type::OpenFile) {
|
||||
}
|
||||
|
||||
void dispose() override {
|
||||
delete this;
|
||||
}
|
||||
|
||||
void setType(const Type type) override {
|
||||
m_type = type;
|
||||
}
|
||||
|
||||
void setTitle(const std::string& title) override {
|
||||
m_title = title;
|
||||
}
|
||||
|
||||
void setDefaultExtension(const std::string& extension) override {
|
||||
m_defExtension = extension;
|
||||
}
|
||||
|
||||
void addFilter(const std::string& extension, const std::string& description) override {
|
||||
if (m_defExtension.empty())
|
||||
m_defExtension = extension;
|
||||
|
||||
m_filters.push_back(std::make_pair(extension, description));
|
||||
}
|
||||
|
||||
protected:
|
||||
Type m_type;
|
||||
std::string m_title;
|
||||
std::string m_defExtension;
|
||||
std::vector<std::pair<std::string, std::string>> m_filters;
|
||||
};
|
||||
|
||||
} // namespace she
|
||||
|
||||
#endif
|
@ -88,7 +88,7 @@ public:
|
||||
m_nativeDialogs = new NativeDialogsOSX();
|
||||
#elif defined(ASEPRITE_WITH_GTK_FILE_DIALOG_SUPPORT) && defined(__linux__)
|
||||
if (!m_nativeDialogs)
|
||||
m_nativeDialogs = new NativeDialogsGTK3();
|
||||
m_nativeDialogs = new NativeDialogsGTK();
|
||||
#endif
|
||||
return m_nativeDialogs;
|
||||
}
|
||||
|
@ -1,194 +1,258 @@
|
||||
// GTK Component of SHE library
|
||||
// SHE library - GTK dialogs
|
||||
// Copyright (C) 2017 David Capello
|
||||
// Copyright (C) 2016 Gabriel Rauter
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
//disable EMPTY_STRING macro already set in allegro, enabling it at the end of file
|
||||
#pragma push_macro("EMPTY_STRING")
|
||||
#undef EMPTY_STRING
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "she/gtk/native_dialogs.h"
|
||||
|
||||
#include "base/fs.h"
|
||||
#include "base/string.h"
|
||||
#include "she/common/file_dialog.h"
|
||||
#include "she/display.h"
|
||||
#include "she/error.h"
|
||||
|
||||
#include <gtkmm/application.h>
|
||||
#include <gtkmm/filechooserdialog.h>
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/filefilter.h>
|
||||
#include <gtkmm/image.h>
|
||||
#include <gdkmm/pixbuf.h>
|
||||
#include <glibmm/refptr.h>
|
||||
#include <glibmm/miscutils.h>
|
||||
#include <glibmm/fileutils.h>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <gdk/gdk.h>
|
||||
#include <gdk/gdkx.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
namespace she {
|
||||
|
||||
class FileDialogGTK3 : public FileDialog, public Gtk::FileChooserDialog {
|
||||
class FileDialogGTK : public CommonFileDialog {
|
||||
public:
|
||||
FileDialogGTK3(Glib::RefPtr<Gtk::Application> app) :
|
||||
Gtk::FileChooserDialog(""), m_app(app), m_cancel(true) {
|
||||
this->add_button("_Cancel", Gtk::RESPONSE_CANCEL);
|
||||
m_ok_button = this->add_button("_Open", Gtk::RESPONSE_OK);
|
||||
this->set_default_response(Gtk::RESPONSE_OK);
|
||||
m_filter_all = Gtk::FileFilter::create();
|
||||
m_filter_all->set_name("All formats");
|
||||
this->set_do_overwrite_confirmation();
|
||||
if (FileDialogGTK3::lastUsedDir().empty()) {
|
||||
#ifdef ASEPRITE_DEPRECATED_GLIB_SUPPORT
|
||||
FileDialogGTK3::lastUsedDir() = Glib::get_user_special_dir(G_USER_DIRECTORY_DOCUMENTS);
|
||||
#else
|
||||
FileDialogGTK3::lastUsedDir() = Glib::get_user_special_dir(Glib::USER_DIRECTORY_DOCUMENTS);
|
||||
#endif
|
||||
}
|
||||
this->set_use_preview_label(false);
|
||||
this->set_preview_widget(m_preview);
|
||||
this->signal_update_preview().connect(sigc::mem_fun(this, &FileDialogGTK3::updatePreview));
|
||||
}
|
||||
|
||||
void on_show() override {
|
||||
//setting the filename only works properly when the dialog is shown
|
||||
if (!m_file_name.empty()) {
|
||||
if (this->get_action() == Gtk::FILE_CHOOSER_ACTION_OPEN) {
|
||||
this->set_current_folder(m_file_name);
|
||||
} else {
|
||||
if (Glib::file_test(m_file_name, Glib::FILE_TEST_EXISTS)) {
|
||||
this->set_filename(m_file_name);
|
||||
} else {
|
||||
this->set_current_folder(FileDialogGTK3::lastUsedDir());
|
||||
this->set_current_name(m_file_name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this->set_current_folder(FileDialogGTK3::lastUsedDir());
|
||||
}
|
||||
//TODO set position centered to parent window, need she::screen to provide info
|
||||
this->set_position(Gtk::WIN_POS_CENTER);
|
||||
Gtk::FileChooserDialog::on_show();
|
||||
this->raise();
|
||||
}
|
||||
|
||||
void on_response(int response_id) override {
|
||||
switch(response_id) {
|
||||
case(Gtk::RESPONSE_OK): {
|
||||
m_cancel = false;
|
||||
m_file_name = this->get_filename();
|
||||
FileDialogGTK3::lastUsedDir() = this->get_current_folder();
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->hide();
|
||||
}
|
||||
|
||||
void dispose() override {
|
||||
for (auto& window : m_app->get_windows()) {
|
||||
window->close();
|
||||
}
|
||||
m_app->quit();
|
||||
delete this;
|
||||
}
|
||||
|
||||
void toOpenFile() override {
|
||||
this->set_action(Gtk::FILE_CHOOSER_ACTION_OPEN);
|
||||
m_ok_button->set_label("_Open");
|
||||
}
|
||||
|
||||
void toSaveFile() override {
|
||||
this->set_action(Gtk::FILE_CHOOSER_ACTION_SAVE);
|
||||
m_ok_button->set_label("_Save");
|
||||
}
|
||||
|
||||
void setTitle(const std::string& title) override {
|
||||
this->set_title(title);
|
||||
}
|
||||
|
||||
void setDefaultExtension(const std::string& extension) override {
|
||||
m_default_extension = extension;
|
||||
}
|
||||
|
||||
void addFilter(const std::string& extension, const std::string& description) override {
|
||||
auto filter = Gtk::FileFilter::create();
|
||||
filter->set_name(description);
|
||||
filter->add_pattern("*." + extension);
|
||||
m_filter_all->add_pattern("*." + extension);
|
||||
m_filters[extension] = filter;
|
||||
FileDialogGTK() {
|
||||
}
|
||||
|
||||
std::string fileName() override {
|
||||
return m_file_name;
|
||||
return m_filename;
|
||||
}
|
||||
|
||||
void getMultipleFileNames(std::vector<std::string>& output) override {
|
||||
output = m_filenames;
|
||||
}
|
||||
|
||||
void setFileName(const std::string& filename) override {
|
||||
m_file_name = filename;
|
||||
}
|
||||
|
||||
void updatePreview() {
|
||||
this->set_preview_widget_active(false);
|
||||
std::string fileName = this->get_filename();
|
||||
try {
|
||||
if (!Glib::file_test(fileName, Glib::FILE_TEST_IS_DIR)) {
|
||||
auto previewPixbuf = Gdk::Pixbuf::create_from_file(fileName, 256, 256, true);
|
||||
m_preview.set(previewPixbuf);
|
||||
this->set_preview_widget_active();
|
||||
}
|
||||
} catch(Glib::Error e) {}
|
||||
m_filename = base::get_file_name(filename);
|
||||
m_initialDir = base::get_file_path(filename);
|
||||
}
|
||||
|
||||
bool show(Display* parent) override {
|
||||
//keep pointer on parent display to get information later
|
||||
m_display = parent;
|
||||
static std::string s_lastUsedDir;
|
||||
if (s_lastUsedDir.empty())
|
||||
s_lastUsedDir = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP);
|
||||
|
||||
//add filters in order they will appear
|
||||
this->add_filter(m_filter_all);
|
||||
const char* okLabel;
|
||||
GtkFileChooserAction action;
|
||||
|
||||
for (const auto& filter : m_filters) {
|
||||
this->add_filter(filter.second) ;
|
||||
if (filter.first.compare(m_default_extension) == 0) {
|
||||
this->set_filter(filter.second);
|
||||
switch (m_type) {
|
||||
case Type::OpenFile:
|
||||
case Type::OpenFiles:
|
||||
action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
okLabel = "_Open";
|
||||
break;
|
||||
case Type::OpenFolder:
|
||||
action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
|
||||
okLabel = "_Open Folder";
|
||||
break;
|
||||
case Type::SaveFile:
|
||||
action = GTK_FILE_CHOOSER_ACTION_SAVE;
|
||||
okLabel = "_Save";
|
||||
break;
|
||||
}
|
||||
|
||||
// GtkWindow* gtkParent = nullptr;
|
||||
GtkWidget* dialog = gtk_file_chooser_dialog_new(
|
||||
m_title.c_str(),
|
||||
nullptr,
|
||||
action,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
okLabel, GTK_RESPONSE_ACCEPT,
|
||||
nullptr);
|
||||
|
||||
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
|
||||
|
||||
GtkFileChooser* chooser = GTK_FILE_CHOOSER(dialog);
|
||||
m_chooser = chooser;
|
||||
|
||||
if (m_type == Type::SaveFile)
|
||||
gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE);
|
||||
else if (m_type == Type::OpenFiles)
|
||||
gtk_file_chooser_set_select_multiple(chooser, true);
|
||||
|
||||
if (m_type != Type::OpenFolder) {
|
||||
setupFilters(base::get_file_extension(m_filename));
|
||||
setupPreview();
|
||||
}
|
||||
|
||||
if (m_initialDir.empty())
|
||||
gtk_file_chooser_set_current_folder(chooser, s_lastUsedDir.c_str());
|
||||
else
|
||||
gtk_file_chooser_set_current_folder(chooser, m_initialDir.c_str());
|
||||
|
||||
if (!m_filename.empty()) {
|
||||
std::string fn = m_filename;
|
||||
// Add default extension
|
||||
if (m_type == Type::SaveFile && base::get_file_extension(fn).empty()) {
|
||||
fn.push_back('.');
|
||||
fn += m_defExtension;
|
||||
}
|
||||
gtk_file_chooser_set_current_name(chooser, fn.c_str());
|
||||
}
|
||||
|
||||
// Setup the "parent" display as the parent of the dialog (we've
|
||||
// to convert a X11 Window into a GdkWindow to do this).
|
||||
GdkWindow* gdkParentWindow = nullptr;
|
||||
if (parent) {
|
||||
GdkWindow* gdkWindow = gtk_widget_get_root_window(dialog);
|
||||
|
||||
gdkParentWindow =
|
||||
gdk_x11_window_foreign_new_for_display(
|
||||
gdk_window_get_display(gdkWindow),
|
||||
(::Window)parent->nativeHandle());
|
||||
|
||||
gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT);
|
||||
gdk_window_set_transient_for(gdkWindow, gdkParentWindow);
|
||||
}
|
||||
else {
|
||||
gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
|
||||
}
|
||||
|
||||
// Show the dialog
|
||||
gint res = gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
if (res == GTK_RESPONSE_ACCEPT) {
|
||||
s_lastUsedDir = gtk_file_chooser_get_current_folder(chooser);
|
||||
m_filename = gtk_file_chooser_get_filename(chooser);
|
||||
|
||||
if (m_type == Type::OpenFiles) {
|
||||
GSList* list = gtk_file_chooser_get_filenames(chooser);
|
||||
g_slist_foreach(
|
||||
list,
|
||||
[](void* fn, void* userdata){
|
||||
auto self = (FileDialogGTK*)userdata;
|
||||
self->m_filenames.push_back((char*)fn);
|
||||
g_free(fn);
|
||||
}, this);
|
||||
g_slist_free(list);
|
||||
}
|
||||
}
|
||||
|
||||
auto filter_any = Gtk::FileFilter::create();
|
||||
filter_any->set_name("Any files");
|
||||
filter_any->add_pattern("*");
|
||||
this->add_filter(filter_any);
|
||||
gtk_widget_destroy(dialog);
|
||||
if (gdkParentWindow)
|
||||
g_object_unref(gdkParentWindow);
|
||||
|
||||
//Run dialog in context of a gtk application so it can be destroys properly
|
||||
m_app->run(*this);
|
||||
return !m_cancel;
|
||||
// Pump gtk+ events to finally hide the dialog from the screen
|
||||
while (gtk_events_pending())
|
||||
gtk_main_iteration();
|
||||
|
||||
return (res == GTK_RESPONSE_ACCEPT);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_file_name;
|
||||
std::string m_default_extension;
|
||||
Glib::RefPtr<Gtk::FileFilter> m_filter_all;
|
||||
std::map<std::string, Glib::RefPtr<Gtk::FileFilter>> m_filters;
|
||||
Gtk::Button* m_ok_button;
|
||||
Glib::RefPtr<Gtk::Application> m_app;
|
||||
Gtk::Image m_preview;
|
||||
Display* m_display;
|
||||
bool m_cancel;
|
||||
static std::string& lastUsedDir() { static std::string lastUsedDir; return lastUsedDir; }
|
||||
void setupFilters(const std::string& fnExtension) {
|
||||
// Filter for all known formats
|
||||
GtkFileFilter* gtkFilter = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(gtkFilter, "All formats");
|
||||
for (const auto& filter : m_filters) {
|
||||
const std::string& ext = filter.first;
|
||||
std::string pat = "*." + ext;
|
||||
gtk_file_filter_add_pattern(gtkFilter, pat.c_str());
|
||||
}
|
||||
gtk_file_chooser_add_filter(m_chooser, gtkFilter);
|
||||
|
||||
// One filter for each format
|
||||
for (const auto& filter : m_filters) {
|
||||
const std::string& ext = filter.first;
|
||||
const std::string& desc = filter.second;
|
||||
std::string pat = "*." + ext;
|
||||
|
||||
gtkFilter = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(gtkFilter, desc.c_str());
|
||||
gtk_file_filter_add_pattern(gtkFilter, pat.c_str());
|
||||
gtk_file_chooser_add_filter(m_chooser, gtkFilter);
|
||||
|
||||
if (base::utf8_icmp(ext, fnExtension) == 0)
|
||||
gtk_file_chooser_set_filter(m_chooser, gtkFilter);
|
||||
}
|
||||
|
||||
// One filter for all files
|
||||
gtkFilter = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(gtkFilter, "All files");
|
||||
gtk_file_filter_add_pattern(gtkFilter, "*");
|
||||
gtk_file_chooser_add_filter(m_chooser, gtkFilter);
|
||||
}
|
||||
|
||||
void setupPreview() {
|
||||
m_preview = gtk_image_new();
|
||||
|
||||
gtk_file_chooser_set_use_preview_label(m_chooser, false);
|
||||
gtk_file_chooser_set_preview_widget(m_chooser, m_preview);
|
||||
|
||||
g_signal_connect(
|
||||
m_chooser, "update-preview",
|
||||
G_CALLBACK(&FileDialogGTK::s_onUpdatePreview), this);
|
||||
}
|
||||
|
||||
static void s_onUpdatePreview(GtkFileChooser* chooser, gpointer userData) {
|
||||
((FileDialogGTK*)userData)->onUpdatePreview();
|
||||
}
|
||||
void onUpdatePreview() {
|
||||
// Disable preview because we don't know if we will be able to
|
||||
// load/generate the preview successfully.
|
||||
gtk_file_chooser_set_preview_widget_active(m_chooser, false);
|
||||
|
||||
const char* fn = gtk_file_chooser_get_filename(m_chooser);
|
||||
if (fn && base::is_file(fn)) {
|
||||
GError* err = nullptr;
|
||||
GdkPixbuf* previewPixbuf =
|
||||
gdk_pixbuf_new_from_file_at_scale(fn, 256, 256, true, &err);
|
||||
if (previewPixbuf) {
|
||||
gtk_image_set_from_pixbuf(GTK_IMAGE(m_preview), previewPixbuf);
|
||||
g_object_unref(previewPixbuf);
|
||||
|
||||
// Now we can enable the preview panel as the preview was
|
||||
// generated.
|
||||
gtk_file_chooser_set_preview_widget_active(m_chooser, true);
|
||||
}
|
||||
if (err)
|
||||
g_error_free(err);
|
||||
}
|
||||
}
|
||||
|
||||
std::string m_filename;
|
||||
std::string m_initialDir;
|
||||
std::vector<std::string> m_filenames;
|
||||
GtkFileChooser* m_chooser;
|
||||
GtkWidget* m_preview;
|
||||
};
|
||||
|
||||
NativeDialogsGTK3::NativeDialogsGTK3()
|
||||
NativeDialogsGTK::NativeDialogsGTK()
|
||||
: m_gtkApp(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
FileDialog* NativeDialogsGTK3::createFileDialog()
|
||||
NativeDialogsGTK::~NativeDialogsGTK()
|
||||
{
|
||||
m_app = Gtk::Application::create();
|
||||
FileDialog* dialog = new FileDialogGTK3(m_app);
|
||||
return dialog;
|
||||
if (m_gtkApp) {
|
||||
g_object_unref(m_gtkApp);
|
||||
m_gtkApp = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog* NativeDialogsGTK::createFileDialog()
|
||||
{
|
||||
if (!m_gtkApp) {
|
||||
int argc = 0;
|
||||
char** argv = nullptr;
|
||||
gtk_init(&argc, &argv);
|
||||
|
||||
m_gtkApp = gtk_application_new(nullptr, G_APPLICATION_FLAGS_NONE);
|
||||
}
|
||||
return new FileDialogGTK;
|
||||
}
|
||||
|
||||
} // namespace she
|
||||
#pragma pop_macro("EMPTY_STRING")
|
||||
|
@ -1,32 +1,29 @@
|
||||
// GTK Component of SHE library
|
||||
// SHE library - GTK dialogs
|
||||
// Copyright (C) 2017 David Capello
|
||||
// Copyright (C) 2016 Gabriel Rauter
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
//disable EMPTY_STRING macro already set in allegro, enabling it at the end of file
|
||||
#pragma push_macro("EMPTY_STRING")
|
||||
#undef EMPTY_STRING
|
||||
#ifndef SHE_GTK_NATIVE_DIALOGS_H_INCLUDED
|
||||
#define SHE_GTK_NATIVE_DIALOGS_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "she/native_dialogs.h"
|
||||
|
||||
#include <gtkmm/application.h>
|
||||
#include <glibmm/refptr.h>
|
||||
extern "C" struct _GtkApplication;
|
||||
|
||||
namespace she {
|
||||
|
||||
class NativeDialogsGTK3 : public NativeDialogs {
|
||||
class NativeDialogsGTK : public NativeDialogs {
|
||||
public:
|
||||
NativeDialogsGTK3();
|
||||
NativeDialogsGTK();
|
||||
~NativeDialogsGTK();
|
||||
FileDialog* createFileDialog() override;
|
||||
private:
|
||||
Glib::RefPtr<Gtk::Application> m_app;
|
||||
_GtkApplication* m_gtkApp;
|
||||
};
|
||||
|
||||
} // namespace she
|
||||
|
||||
#endif
|
||||
#pragma pop_macro("EMPTY_STRING")
|
||||
|
@ -16,13 +16,18 @@ namespace she {
|
||||
|
||||
class FileDialog {
|
||||
public:
|
||||
enum class Type {
|
||||
OpenFile,
|
||||
OpenFiles,
|
||||
OpenFolder,
|
||||
SaveFile,
|
||||
};
|
||||
|
||||
virtual ~FileDialog() { }
|
||||
virtual void dispose() = 0;
|
||||
virtual void toOpenFile() = 0; // Configure the dialog to open a file
|
||||
virtual void toSaveFile() = 0; // Configure the dialog to save a file
|
||||
virtual void setType(const Type type) = 0;
|
||||
virtual void setTitle(const std::string& title) = 0;
|
||||
virtual void setDefaultExtension(const std::string& extension) = 0;
|
||||
virtual void setMultipleSelection(bool multiple) = 0;
|
||||
virtual void addFilter(const std::string& extension, const std::string& description) = 0;
|
||||
virtual std::string fileName() = 0;
|
||||
virtual void getMultipleFileNames(std::vector<std::string>& output) = 0;
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "base/fs.h"
|
||||
#include "she/common/file_dialog.h"
|
||||
#include "she/display.h"
|
||||
#include "she/keys.h"
|
||||
#include "she/native_cursor.h"
|
||||
@ -78,43 +79,9 @@
|
||||
|
||||
namespace she {
|
||||
|
||||
class FileDialogOSX : public FileDialog {
|
||||
class FileDialogOSX : public CommonFileDialog {
|
||||
public:
|
||||
FileDialogOSX()
|
||||
: m_save(false)
|
||||
, m_multipleSelection(false)
|
||||
{
|
||||
}
|
||||
|
||||
void dispose() override {
|
||||
delete this;
|
||||
}
|
||||
|
||||
void toOpenFile() override {
|
||||
m_save = false;
|
||||
}
|
||||
|
||||
void toSaveFile() override {
|
||||
m_save = true;
|
||||
}
|
||||
|
||||
void setTitle(const std::string& title) override {
|
||||
m_title = title;
|
||||
}
|
||||
|
||||
void setDefaultExtension(const std::string& extension) override {
|
||||
m_defExtension = extension;
|
||||
}
|
||||
|
||||
void setMultipleSelection(bool multiple) override {
|
||||
m_multipleSelection = multiple;
|
||||
}
|
||||
|
||||
void addFilter(const std::string& extension, const std::string& description) override {
|
||||
if (m_defExtension.empty())
|
||||
m_defExtension = extension;
|
||||
|
||||
m_filters.push_back(std::make_pair(description, extension));
|
||||
FileDialogOSX() {
|
||||
}
|
||||
|
||||
std::string fileName() override {
|
||||
@ -134,13 +101,14 @@ public:
|
||||
@autoreleasepool {
|
||||
NSSavePanel* panel = nil;
|
||||
|
||||
if (m_save) {
|
||||
if (m_type == Type::SaveFile) {
|
||||
panel = [NSSavePanel new];
|
||||
}
|
||||
else {
|
||||
panel = [NSOpenPanel new];
|
||||
[(NSOpenPanel*)panel setAllowsMultipleSelection:(m_multipleSelection ? YES: NO)];
|
||||
[(NSOpenPanel*)panel setCanChooseDirectories:NO];
|
||||
[(NSOpenPanel*)panel setAllowsMultipleSelection:(m_type == Type::OpenFiles ? YES: NO)];
|
||||
[(NSOpenPanel*)panel setCanChooseFiles:(m_type != Type::OpenFolder ? YES: NO)];
|
||||
[(NSOpenPanel*)panel setCanChooseDirectories:(m_type == Type::OpenFolder ? YES: NO)];
|
||||
}
|
||||
|
||||
[panel setTitle:[NSString stringWithUTF8String:m_title.c_str()]];
|
||||
@ -153,13 +121,17 @@ public:
|
||||
if (!defName.empty())
|
||||
[panel setNameFieldStringValue:[NSString stringWithUTF8String:defName.c_str()]];
|
||||
|
||||
NSMutableArray* types = [[NSMutableArray alloc] init];
|
||||
// The first extension in the array is used as the default one.
|
||||
if (!m_defExtension.empty())
|
||||
[types addObject:[NSString stringWithUTF8String:m_defExtension.c_str()]];
|
||||
for (const auto& filter : m_filters)
|
||||
[types addObject:[NSString stringWithUTF8String:filter.second.c_str()]];
|
||||
[panel setAllowedFileTypes:types];
|
||||
if (m_type != Type::OpenFolder && !m_filters.empty()) {
|
||||
NSMutableArray* types = [[NSMutableArray alloc] init];
|
||||
// The first extension in the array is used as the default one.
|
||||
if (!m_defExtension.empty())
|
||||
[types addObject:[NSString stringWithUTF8String:m_defExtension.c_str()]];
|
||||
for (const auto& filter : m_filters)
|
||||
[types addObject:[NSString stringWithUTF8String:filter.first.c_str()]];
|
||||
[panel setAllowedFileTypes:types];
|
||||
if (m_type == Type::SaveFile)
|
||||
[panel setAllowsOtherFileTypes:NO];
|
||||
}
|
||||
|
||||
OpenSaveHelper* helper = [OpenSaveHelper new];
|
||||
[helper setPanel:panel];
|
||||
@ -167,7 +139,7 @@ public:
|
||||
[helper performSelectorOnMainThread:@selector(runModal) withObject:nil waitUntilDone:YES];
|
||||
|
||||
if ([helper result] == NSFileHandlingPanelOKButton) {
|
||||
if (m_multipleSelection) {
|
||||
if (m_type == Type::OpenFiles) {
|
||||
for (NSURL* url in [(NSOpenPanel*)panel URLs]) {
|
||||
m_filename = [[url path] UTF8String];
|
||||
m_filenames.push_back(m_filename);
|
||||
@ -186,13 +158,8 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> m_filters;
|
||||
std::string m_defExtension;
|
||||
std::string m_filename;
|
||||
std::vector<std::string> m_filenames;
|
||||
std::string m_title;
|
||||
bool m_save;
|
||||
bool m_multipleSelection;
|
||||
};
|
||||
|
||||
NativeDialogsOSX::NativeDialogsOSX()
|
||||
|
37
src/she/win/comptr.h
Normal file
37
src/she/win/comptr.h
Normal file
@ -0,0 +1,37 @@
|
||||
// SHE library
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef SHE_WIN_COMPTR_H_INCLUDED
|
||||
#define SHE_WIN_COMPTR_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "base/disable_copying.h"
|
||||
|
||||
namespace she {
|
||||
|
||||
template<class T>
|
||||
class ComPtr {
|
||||
public:
|
||||
ComPtr() : m_ptr(nullptr) { }
|
||||
~ComPtr() {
|
||||
if (m_ptr) {
|
||||
m_ptr->Release(); // Call IUnknown::Release() automatically
|
||||
#ifdef _DEBUG
|
||||
m_ptr = nullptr;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
T** operator&() { return &m_ptr; }
|
||||
T* operator->() { return m_ptr; }
|
||||
private:
|
||||
T* m_ptr;
|
||||
|
||||
DISABLE_COPYING(ComPtr);
|
||||
};
|
||||
|
||||
} // namespace she
|
||||
|
||||
#endif
|
@ -12,10 +12,13 @@
|
||||
|
||||
#include "base/fs.h"
|
||||
#include "base/string.h"
|
||||
#include "she/common/file_dialog.h"
|
||||
#include "she/display.h"
|
||||
#include "she/error.h"
|
||||
#include "she/win/comptr.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <shobjidl.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -25,44 +28,11 @@ namespace she {
|
||||
// 32k is the limit for Win95/98/Me/NT4/2000/XP with ANSI version
|
||||
#define FILENAME_BUFSIZE (1024*32)
|
||||
|
||||
class FileDialogWin32 : public FileDialog {
|
||||
class FileDialogWin32 : public CommonFileDialog {
|
||||
public:
|
||||
FileDialogWin32()
|
||||
: m_filename(FILENAME_BUFSIZE)
|
||||
, m_save(false)
|
||||
, m_multipleSelection(false) {
|
||||
}
|
||||
|
||||
void dispose() override {
|
||||
delete this;
|
||||
}
|
||||
|
||||
void toOpenFile() override {
|
||||
m_save = false;
|
||||
}
|
||||
|
||||
void toSaveFile() override {
|
||||
m_save = true;
|
||||
}
|
||||
|
||||
void setTitle(const std::string& title) override {
|
||||
m_title = base::from_utf8(title);
|
||||
}
|
||||
|
||||
void setDefaultExtension(const std::string& extension) override {
|
||||
m_defExtension = base::from_utf8(extension);
|
||||
}
|
||||
|
||||
void setMultipleSelection(bool multiple) override {
|
||||
m_multipleSelection = multiple;
|
||||
}
|
||||
|
||||
void addFilter(const std::string& extension, const std::string& description) override {
|
||||
if (m_defExtension.empty()) {
|
||||
m_defExtension = base::from_utf8(extension);
|
||||
m_defFilter = 0;
|
||||
}
|
||||
m_filters.push_back(std::make_pair(extension, description));
|
||||
, m_defFilter(0) {
|
||||
}
|
||||
|
||||
std::string fileName() override {
|
||||
@ -79,6 +49,147 @@ public:
|
||||
}
|
||||
|
||||
bool show(Display* parent) override {
|
||||
bool result = false;
|
||||
bool shown = false;
|
||||
|
||||
HRESULT hr = showWithNewAPI(parent, result, shown);
|
||||
if (FAILED(hr) && !shown)
|
||||
hr = showWithOldAPI(parent, result);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
return result;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
HRESULT showWithNewAPI(Display* parent, bool& result, bool& shown) {
|
||||
ComPtr<IFileDialog> dlg;
|
||||
HRESULT hr = CoCreateInstance(
|
||||
(m_type == Type::SaveFile ? CLSID_FileSaveDialog:
|
||||
CLSID_FileOpenDialog),
|
||||
nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&dlg));
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
FILEOPENDIALOGOPTIONS options =
|
||||
FOS_NOCHANGEDIR |
|
||||
FOS_PATHMUSTEXIST |
|
||||
FOS_FORCEFILESYSTEM;
|
||||
|
||||
switch (m_type) {
|
||||
case Type::OpenFile:
|
||||
options |= FOS_FILEMUSTEXIST;
|
||||
break;
|
||||
case Type::OpenFiles:
|
||||
options |= FOS_FILEMUSTEXIST
|
||||
| FOS_ALLOWMULTISELECT;
|
||||
break;
|
||||
case Type::OpenFolder:
|
||||
options |= FOS_PICKFOLDERS;
|
||||
break;
|
||||
case Type::SaveFile:
|
||||
options |= FOS_OVERWRITEPROMPT;
|
||||
break;
|
||||
}
|
||||
|
||||
hr = dlg->SetOptions(options);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
if (!m_title.empty()) {
|
||||
std::wstring title = base::from_utf8(m_title);
|
||||
hr = dlg->SetTitle(title.c_str());
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
}
|
||||
|
||||
if (std::wcslen(&m_filename[0]) > 0) {
|
||||
hr = dlg->SetFileName(&m_filename[0]);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
}
|
||||
|
||||
if (!m_defExtension.empty()) {
|
||||
std::wstring defExt = base::from_utf8(m_defExtension);
|
||||
hr = dlg->SetDefaultExtension(defExt.c_str());
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
}
|
||||
|
||||
if (m_type != Type::OpenFolder && !m_filters.empty()) {
|
||||
std::vector<COMDLG_FILTERSPEC> specs;
|
||||
getFiltersForIFileDialog(specs);
|
||||
hr = dlg->SetFileTypes(specs.size(), &specs[0]);
|
||||
freeFiltersForIFileDialog(specs);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
hr = dlg->SetFileTypeIndex(m_defFilter+1); // One-based index
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dlg->Show(parent ? (HWND)parent->nativeHandle(): nullptr);
|
||||
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
|
||||
shown = true;
|
||||
result = false;
|
||||
return S_OK;
|
||||
}
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
shown = true;
|
||||
|
||||
if (m_type == Type::OpenFiles) {
|
||||
ComPtr<IFileOpenDialog> odlg;
|
||||
hr = dlg->QueryInterface(IID_IFileOpenDialog, (void**)&odlg);
|
||||
ComPtr<IShellItemArray> items;
|
||||
hr = odlg->GetResults(&items);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
DWORD nitems = 0;
|
||||
hr = items->GetCount(&nitems);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
for (DWORD i=0; i<nitems; ++i) {
|
||||
ComPtr<IShellItem> item;
|
||||
hr = items->GetItemAt(i, &item);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
LPWSTR fn;
|
||||
hr = item->GetDisplayName(SIGDN_FILESYSPATH, &fn);
|
||||
if (SUCCEEDED(hr)) {
|
||||
m_filenames.push_back(base::to_utf8(fn));
|
||||
CoTaskMemFree(fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
ComPtr<IShellItem> item;
|
||||
hr = dlg->GetResult(&item);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
LPWSTR fn;
|
||||
hr = item->GetDisplayName(SIGDN_FILESYSPATH, &fn);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
wcscpy(&m_filename[0], fn);
|
||||
m_filenames.push_back(base::to_utf8(&m_filename[0]));
|
||||
CoTaskMemFree(fn);
|
||||
}
|
||||
|
||||
result = (hr == S_OK);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT showWithOldAPI(Display* parent, bool& result) {
|
||||
std::wstring title = base::from_utf8(m_title);
|
||||
std::wstring defExt = base::from_utf8(m_defExtension);
|
||||
std::wstring filtersWStr = getFiltersForGetOpenFileName();
|
||||
|
||||
OPENFILENAME ofn;
|
||||
@ -92,8 +203,8 @@ public:
|
||||
ofn.nMaxFile = FILENAME_BUFSIZE;
|
||||
if (!m_initialDir.empty())
|
||||
ofn.lpstrInitialDir = m_initialDir.c_str();
|
||||
ofn.lpstrTitle = m_title.c_str();
|
||||
ofn.lpstrDefExt = m_defExtension.c_str();
|
||||
ofn.lpstrTitle = title.c_str();
|
||||
ofn.lpstrDefExt = defExt.c_str();
|
||||
ofn.Flags =
|
||||
OFN_ENABLESIZING |
|
||||
OFN_EXPLORER |
|
||||
@ -101,20 +212,21 @@ public:
|
||||
OFN_NOCHANGEDIR |
|
||||
OFN_PATHMUSTEXIST;
|
||||
|
||||
if (!m_save) {
|
||||
if (m_type == Type::SaveFile) {
|
||||
ofn.Flags |= OFN_OVERWRITEPROMPT;
|
||||
}
|
||||
else {
|
||||
ofn.Flags |= OFN_FILEMUSTEXIST;
|
||||
if (m_multipleSelection)
|
||||
if (m_type == Type::OpenFiles)
|
||||
ofn.Flags |= OFN_ALLOWMULTISELECT;
|
||||
}
|
||||
else
|
||||
ofn.Flags |= OFN_OVERWRITEPROMPT;
|
||||
|
||||
BOOL res;
|
||||
if (m_save)
|
||||
if (m_type == Type::SaveFile)
|
||||
res = GetSaveFileName(&ofn);
|
||||
else {
|
||||
res = GetOpenFileName(&ofn);
|
||||
if (res && m_multipleSelection) {
|
||||
if (res && m_type == Type::OpenFiles) {
|
||||
WCHAR* p = &m_filename[0];
|
||||
std::string path = base::to_utf8(p);
|
||||
|
||||
@ -146,10 +258,45 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
return (res != FALSE);
|
||||
result = (res != FALSE);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
void getFiltersForIFileDialog(std::vector<COMDLG_FILTERSPEC>& specs) const {
|
||||
specs.resize(m_filters.size()+2);
|
||||
|
||||
int i = 0, j = 0;
|
||||
specs[i].pszName = _wcsdup(L"All formats");
|
||||
std::wstring exts;
|
||||
bool first = true;
|
||||
for (const auto& filter : m_filters) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
exts.push_back(';');
|
||||
exts.append(L"*.");
|
||||
exts.append(base::from_utf8(filter.first));
|
||||
}
|
||||
specs[i].pszSpec = _wcsdup(exts.c_str());
|
||||
++i;
|
||||
|
||||
for (const auto& filter : m_filters) {
|
||||
specs[i].pszName = _wcsdup(base::from_utf8(filter.second).c_str());
|
||||
specs[i].pszSpec = _wcsdup(base::from_utf8("*." + filter.first).c_str());
|
||||
++i;
|
||||
}
|
||||
|
||||
specs[i].pszName = _wcsdup(L"All files");
|
||||
specs[i].pszSpec = _wcsdup(L"*.*");
|
||||
++i;
|
||||
}
|
||||
|
||||
void freeFiltersForIFileDialog(std::vector<COMDLG_FILTERSPEC>& specs) const {
|
||||
for (auto& spec : specs) {
|
||||
free((void*)spec.pszName);
|
||||
free((void*)spec.pszSpec);
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring getFiltersForGetOpenFileName() const {
|
||||
std::wstring filters;
|
||||
@ -188,15 +335,10 @@ private:
|
||||
return filters;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> m_filters;
|
||||
std::wstring m_defExtension;
|
||||
int m_defFilter;
|
||||
std::vector<WCHAR> m_filename;
|
||||
std::vector<std::string> m_filenames;
|
||||
std::wstring m_initialDir;
|
||||
std::wstring m_title;
|
||||
bool m_save;
|
||||
bool m_multipleSelection;
|
||||
};
|
||||
|
||||
NativeDialogsWin32::NativeDialogsWin32()
|
||||
|
Loading…
x
Reference in New Issue
Block a user