forked from AuroraMiddleware/gtk
GtkFilechooserNative: add macOS support
Based on the Win32 implementation, as well as the macOS file chooser from https://github.com/GNOME/gedit/blob/master/gedit/gedit-file-chooser-dialog-osx.[ch] Not fully tested yet, but working properly so far. TODO: filter support, extra widget (label), documentation... https://bugzilla.gnome.org/show_bug.cgi?id=784723
This commit is contained in:
parent
a9a25e2e84
commit
ff2c5e3820
@ -1025,6 +1025,7 @@ gtk_use_quartz_c_sources = \
|
||||
gtkmountoperation-stub.c \
|
||||
gtkapplication-quartz.c \
|
||||
gtkapplication-quartz-menu.c \
|
||||
gtkfilechoosernativequartz.c \
|
||||
gtkquartz.c
|
||||
gtk_use_stub_c_sources = \
|
||||
gtkmountoperation-stub.c
|
||||
|
@ -192,6 +192,7 @@
|
||||
enum {
|
||||
MODE_FALLBACK,
|
||||
MODE_WIN32,
|
||||
MODE_QUARTZ,
|
||||
MODE_PORTAL,
|
||||
};
|
||||
|
||||
@ -697,6 +698,7 @@ gtk_file_chooser_native_get_files (GtkFileChooser *chooser)
|
||||
{
|
||||
case MODE_PORTAL:
|
||||
case MODE_WIN32:
|
||||
case MODE_QUARTZ:
|
||||
return g_slist_copy_deep (self->custom_files, (GCopyFunc)g_object_ref, NULL);
|
||||
|
||||
case MODE_FALLBACK:
|
||||
@ -717,6 +719,11 @@ gtk_file_chooser_native_show (GtkNativeDialog *native)
|
||||
self->mode = MODE_WIN32;
|
||||
#endif
|
||||
|
||||
#ifdef GDK_WINDOWING_QUARTZ
|
||||
if (gtk_file_chooser_native_quartz_show (self))
|
||||
self->mode = MODE_QUARTZ;
|
||||
#endif
|
||||
|
||||
if (self->mode == MODE_FALLBACK &&
|
||||
gtk_file_chooser_native_portal_show (self))
|
||||
self->mode = MODE_PORTAL;
|
||||
@ -738,6 +745,11 @@ gtk_file_chooser_native_hide (GtkNativeDialog *native)
|
||||
case MODE_WIN32:
|
||||
#ifdef GDK_WINDOWING_WIN32
|
||||
gtk_file_chooser_native_win32_hide (self);
|
||||
#endif
|
||||
break;
|
||||
case MODE_QUARTZ:
|
||||
#ifdef GDK_WINDOWING_QUARTZ
|
||||
gtk_file_chooser_native_quartz_hide (self);
|
||||
#endif
|
||||
break;
|
||||
case MODE_PORTAL:
|
||||
|
@ -57,6 +57,9 @@ struct _GtkFileChooserNative
|
||||
gboolean gtk_file_chooser_native_win32_show (GtkFileChooserNative *self);
|
||||
void gtk_file_chooser_native_win32_hide (GtkFileChooserNative *self);
|
||||
|
||||
gboolean gtk_file_chooser_native_quartz_show (GtkFileChooserNative *self);
|
||||
void gtk_file_chooser_native_quartz_hide (GtkFileChooserNative *self);
|
||||
|
||||
gboolean gtk_file_chooser_native_portal_show (GtkFileChooserNative *self);
|
||||
void gtk_file_chooser_native_portal_hide (GtkFileChooserNative *self);
|
||||
|
||||
|
431
gtk/gtkfilechoosernativequartz.c
Normal file
431
gtk/gtkfilechoosernativequartz.c
Normal file
@ -0,0 +1,431 @@
|
||||
/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
|
||||
/* GTK - The GIMP Toolkit
|
||||
* gtkfilechoosernativequartz.c: Quartz Native File selector dialog
|
||||
* Copyright (C) 2017, Tom Schoonjans
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkfilechoosernativeprivate.h"
|
||||
#include "gtknativedialogprivate.h"
|
||||
|
||||
#include "gtkprivate.h"
|
||||
#include "gtkfilechooserdialog.h"
|
||||
#include "gtkfilechooserprivate.h"
|
||||
#include "gtkfilechooserwidget.h"
|
||||
#include "gtkfilechooserwidgetprivate.h"
|
||||
#include "gtkfilechooserutils.h"
|
||||
#include "gtkfilechooserembed.h"
|
||||
#include "gtkfilesystem.h"
|
||||
#include "gtksizerequest.h"
|
||||
#include "gtktypebuiltins.h"
|
||||
#include "gtkintl.h"
|
||||
#include "gtksettings.h"
|
||||
#include "gtktogglebutton.h"
|
||||
#include "gtkstylecontext.h"
|
||||
#include "gtkheaderbar.h"
|
||||
#include "gtklabel.h"
|
||||
#include "gtkfilechooserentry.h"
|
||||
#include "gtkfilefilterprivate.h"
|
||||
|
||||
#include "quartz/gdkquartz.h"
|
||||
|
||||
typedef struct {
|
||||
GtkFileChooserNative *self;
|
||||
|
||||
NSSavePanel *panel;
|
||||
NSWindow *parent;
|
||||
gboolean skip_response;
|
||||
gboolean save;
|
||||
gboolean folder;
|
||||
gboolean create_folders;
|
||||
gboolean modal;
|
||||
gboolean overwrite_confirmation;
|
||||
gboolean select_multiple;
|
||||
gboolean show_hidden;
|
||||
gboolean running;
|
||||
|
||||
char *accept_label;
|
||||
char *cancel_label;
|
||||
char *title;
|
||||
|
||||
GSList *shortcut_uris;
|
||||
|
||||
GFile *current_folder;
|
||||
GFile *current_file;
|
||||
char *current_name;
|
||||
|
||||
NSArray<NSString *> *filters;
|
||||
|
||||
GSList *files;
|
||||
int response;
|
||||
} FileChooserQuartzData;
|
||||
|
||||
static GFile *
|
||||
ns_url_to_g_file (NSURL *url)
|
||||
{
|
||||
if (url == nil)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_file_new_for_uri ([[url absoluteString] UTF8String]);
|
||||
}
|
||||
|
||||
static GSList *
|
||||
chooser_get_files (FileChooserQuartzData *data)
|
||||
{
|
||||
|
||||
GSList *ret = NULL;
|
||||
|
||||
if (!data->save)
|
||||
{
|
||||
NSArray *urls;
|
||||
gint i;
|
||||
|
||||
urls = [(NSOpenPanel *)data->panel URLs];
|
||||
|
||||
for (i = 0; i < [urls count]; i++)
|
||||
{
|
||||
NSURL *url;
|
||||
|
||||
url = (NSURL *)[urls objectAtIndex:i];
|
||||
ret = g_slist_prepend (ret, ns_url_to_g_file (url));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GFile *file;
|
||||
|
||||
file = ns_url_to_g_file ([data->panel URL]);
|
||||
|
||||
if (file != NULL)
|
||||
{
|
||||
ret = g_slist_prepend (ret, file);
|
||||
}
|
||||
}
|
||||
|
||||
return g_slist_reverse (ret);
|
||||
}
|
||||
|
||||
static void
|
||||
chooser_set_current_folder (FileChooserQuartzData *data,
|
||||
GFile *folder)
|
||||
{
|
||||
|
||||
if (folder != NULL)
|
||||
{
|
||||
gchar *uri;
|
||||
|
||||
uri = g_file_get_uri (folder);
|
||||
[data->panel setDirectoryURL:[NSURL URLWithString:[NSString stringWithUTF8String:uri]]];
|
||||
g_free (uri);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
chooser_set_current_name (FileChooserQuartzData *data,
|
||||
const gchar *name)
|
||||
{
|
||||
|
||||
if (name != NULL)
|
||||
[data->panel setNameFieldStringValue:[NSString stringWithUTF8String:name]];
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
filechooser_quartz_data_free (FileChooserQuartzData *data)
|
||||
{
|
||||
int i;
|
||||
if (data->filters)
|
||||
{
|
||||
}
|
||||
|
||||
g_clear_object (&data->current_folder);
|
||||
g_clear_object (&data->current_file);
|
||||
g_free (data->current_name);
|
||||
|
||||
g_slist_free_full (data->shortcut_uris, g_free);
|
||||
g_slist_free_full (data->files, g_object_unref);
|
||||
if (data->self)
|
||||
g_object_unref (data->self);
|
||||
g_free (data->accept_label);
|
||||
g_free (data->cancel_label);
|
||||
g_free (data->title);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
filechooser_quartz_launch (FileChooserQuartzData *data)
|
||||
{
|
||||
|
||||
// GTK_FILE_CHOOSER_ACTION_SAVE and GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER
|
||||
if (data->save)
|
||||
{
|
||||
NSSavePanel *panel = [[NSSavePanel savePanel] retain];
|
||||
|
||||
/*if ([panel respondsToSelector:@selector(setShowsTagField:)])
|
||||
{
|
||||
[(id<CanSetShowsTagField>)panel setShowsTagField:NO];
|
||||
}
|
||||
*/
|
||||
|
||||
if (!data->folder && !data->create_folders)
|
||||
{
|
||||
[panel setCanCreateDirectories:NO];
|
||||
}
|
||||
else
|
||||
{
|
||||
[panel setCanCreateDirectories:YES];
|
||||
}
|
||||
|
||||
data->panel = panel;
|
||||
}
|
||||
// GTK_FILE_CHOOSER_ACTION_OPEN and GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
|
||||
else
|
||||
{
|
||||
NSOpenPanel *panel = [[NSOpenPanel openPanel] retain];
|
||||
|
||||
if (data->select_multiple)
|
||||
{
|
||||
[panel setAllowsMultipleSelection:YES];
|
||||
}
|
||||
if (data->folder)
|
||||
{
|
||||
[panel setCanChooseDirectories:YES];
|
||||
[panel setCanChooseFiles:NO];
|
||||
}
|
||||
else
|
||||
{
|
||||
[panel setCanChooseDirectories:NO];
|
||||
[panel setCanChooseFiles:YES];
|
||||
}
|
||||
|
||||
data->panel = panel;
|
||||
}
|
||||
|
||||
[data->panel setReleasedWhenClosed:YES];
|
||||
|
||||
if (data->show_hidden)
|
||||
{
|
||||
[data->panel setShowsHiddenFiles:YES];
|
||||
}
|
||||
|
||||
if (data->accept_label)
|
||||
[data->panel setPrompt:[NSString stringWithUTF8String:data->accept_label]];
|
||||
|
||||
if (data->title)
|
||||
[data->panel setTitle:[NSString stringWithUTF8String:data->title]];
|
||||
|
||||
if (data->current_file)
|
||||
{
|
||||
GFile *folder;
|
||||
gchar *name;
|
||||
|
||||
folder = g_file_get_parent (data->current_file);
|
||||
name = g_file_get_basename (data->current_file);
|
||||
|
||||
chooser_set_current_folder (data, folder);
|
||||
chooser_set_current_name (data, name);
|
||||
|
||||
g_object_unref (folder);
|
||||
g_free (name);
|
||||
}
|
||||
|
||||
if (data->current_folder)
|
||||
{
|
||||
chooser_set_current_folder (data, data->current_folder);
|
||||
}
|
||||
|
||||
if (data->current_name)
|
||||
{
|
||||
chooser_set_current_name (data, data->current_name);
|
||||
}
|
||||
|
||||
if (data->filters)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
data->response = GTK_RESPONSE_CANCEL;
|
||||
|
||||
|
||||
void (^handler)(NSInteger ret) = ^(NSInteger result) {
|
||||
|
||||
if (result == NSFileHandlingPanelOKButton)
|
||||
{
|
||||
// get selected files and update data->files
|
||||
data->response = GTK_RESPONSE_ACCEPT;
|
||||
data->files = chooser_get_files (data);
|
||||
}
|
||||
|
||||
GtkFileChooserNative *self = data->self;
|
||||
|
||||
self->mode_data = NULL;
|
||||
|
||||
if (!data->skip_response)
|
||||
{
|
||||
g_slist_free_full (self->custom_files, g_object_unref);
|
||||
self->custom_files = data->files;
|
||||
data->files = NULL;
|
||||
|
||||
_gtk_native_dialog_emit_response (GTK_NATIVE_DIALOG (data->self),
|
||||
data->response);
|
||||
}
|
||||
// free data!
|
||||
filechooser_quartz_data_free (data);
|
||||
};
|
||||
|
||||
if (data->parent != NULL && data->modal)
|
||||
{
|
||||
[data->panel setLevel:NSModalPanelWindowLevel];
|
||||
[data->panel beginSheetModalForWindow:data->parent completionHandler:handler];
|
||||
}
|
||||
else
|
||||
{
|
||||
[data->panel setLevel:NSModalPanelWindowLevel];
|
||||
[data->panel beginWithCompletionHandler:handler];
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
strip_mnemonic (const gchar *s)
|
||||
{
|
||||
gchar *escaped;
|
||||
gchar *ret = NULL;
|
||||
|
||||
if (s == NULL)
|
||||
return NULL;
|
||||
|
||||
escaped = g_markup_escape_text (s, -1);
|
||||
pango_parse_markup (escaped, -1, '_', NULL, &ret, NULL, NULL);
|
||||
|
||||
if (ret != NULL)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
return g_strdup (s);
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_file_chooser_native_quartz_show (GtkFileChooserNative *self)
|
||||
{
|
||||
FileChooserQuartzData *data;
|
||||
GtkWindow *transient_for;
|
||||
GtkFileChooserAction action;
|
||||
|
||||
guint update_preview_signal;
|
||||
GSList *filters, *l;
|
||||
int n_filters, i;
|
||||
|
||||
if (gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (self)) != NULL)
|
||||
return FALSE;
|
||||
|
||||
update_preview_signal = g_signal_lookup ("update-preview", GTK_TYPE_FILE_CHOOSER);
|
||||
if (g_signal_has_handler_pending (self, update_preview_signal, 0, TRUE))
|
||||
return FALSE;
|
||||
|
||||
data = g_new0 (FileChooserQuartzData, 1);
|
||||
|
||||
// examine filters! TODO
|
||||
|
||||
self->mode_data = data;
|
||||
data->self = g_object_ref (self);
|
||||
|
||||
data->create_folders = gtk_file_chooser_get_create_folders( GTK_FILE_CHOOSER (self));
|
||||
|
||||
// shortcut_folder_uris support seems difficult if not impossible
|
||||
|
||||
// mnemonics are not supported on macOS, so remove the underscores
|
||||
data->accept_label = strip_mnemonic (self->accept_label);
|
||||
// cancel button is not present in macOS filechooser dialogs!
|
||||
// data->cancel_label = strip_mnemonic (self->cancel_label);
|
||||
|
||||
action = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (self->dialog));
|
||||
|
||||
if (action == GTK_FILE_CHOOSER_ACTION_SAVE ||
|
||||
action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
|
||||
data->save = TRUE;
|
||||
|
||||
if (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
|
||||
action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
|
||||
data->folder = TRUE;
|
||||
|
||||
if ((action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
|
||||
action == GTK_FILE_CHOOSER_ACTION_OPEN) &&
|
||||
gtk_file_chooser_get_select_multiple (GTK_FILE_CHOOSER (self->dialog)))
|
||||
data->select_multiple = TRUE;
|
||||
|
||||
// overwrite confirmation appears to be always on
|
||||
if (gtk_file_chooser_get_do_overwrite_confirmation (GTK_FILE_CHOOSER (self->dialog)))
|
||||
data->overwrite_confirmation = TRUE;
|
||||
|
||||
// showsHiddenFiles??
|
||||
if (gtk_file_chooser_get_show_hidden (GTK_FILE_CHOOSER (self->dialog)))
|
||||
data->show_hidden = TRUE;
|
||||
|
||||
transient_for = gtk_native_dialog_get_transient_for (GTK_NATIVE_DIALOG (self));
|
||||
if (transient_for)
|
||||
{
|
||||
gtk_widget_realize (GTK_WIDGET (transient_for));
|
||||
data->parent = gdk_quartz_window_get_nswindow (gtk_widget_get_window (GTK_WIDGET (transient_for)));
|
||||
|
||||
if (gtk_native_dialog_get_modal (GTK_NATIVE_DIALOG (self)))
|
||||
data->modal = TRUE;
|
||||
}
|
||||
|
||||
data->title =
|
||||
g_strdup (gtk_native_dialog_get_title (GTK_NATIVE_DIALOG (self)));
|
||||
|
||||
if (self->current_file)
|
||||
data->current_file = g_object_ref (self->current_file);
|
||||
else
|
||||
{
|
||||
if (self->current_folder)
|
||||
data->current_folder = g_object_ref (self->current_folder);
|
||||
|
||||
if (action == GTK_FILE_CHOOSER_ACTION_SAVE ||
|
||||
action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
|
||||
data->current_name = g_strdup (self->current_name);
|
||||
}
|
||||
|
||||
return filechooser_quartz_launch(data);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_file_chooser_native_quartz_hide (GtkFileChooserNative *self)
|
||||
{
|
||||
FileChooserQuartzData *data = self->mode_data;
|
||||
|
||||
/* This is always set while dialog visible */
|
||||
g_assert (data != NULL);
|
||||
|
||||
data->skip_response = TRUE;
|
||||
if (data->panel == NULL)
|
||||
return;
|
||||
|
||||
[data->panel orderBack:nil];
|
||||
[data->panel close];
|
||||
if (data->parent)
|
||||
[data->parent orderFront:nil];
|
||||
data->panel = NULL;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user