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:
David Capello 2017-12-21 16:34:17 -03:00
parent d088ee0f38
commit 5811bba2b1
12 changed files with 552 additions and 280 deletions

View File

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

View File

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

View File

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

View File

@ -736,7 +736,7 @@ void* Alleg4Display::nativeHandle()
#elif defined __APPLE__
return get_osx_window();
#else
return nullptr;
return (void*)_xwin.window;
#endif
}

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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