diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index eb9d460959..7908a0fcb3 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -64,7 +64,7 @@ - + diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs b/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs index 9cd9c5cd8b..9ead1d2cb3 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs @@ -48,5 +48,15 @@ namespace Avalonia.Gtk3.Interop { } + + class GtkDialog : GtkWindow + { + + } + + class GtkFileChooser : GtkDialog + { + + } } diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 25c10fd32b..be7a4e5117 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -95,6 +95,17 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_move(GtkWindow gtkWindow, int x, int y); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate GtkFileChooser gtk_file_chooser_dialog_new(Utf8Buffer title, GtkWindow parent, GtkFileChooserAction action, IntPtr ignore); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public unsafe delegate GSList* gtk_file_chooser_get_filenames(GtkFileChooser chooser); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_file_chooser_set_select_multiple(GtkFileChooser chooser, bool allow); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_file_chooser_set_filename(GtkFileChooser chooser, Utf8Buffer file); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_dialog_add_button(GtkDialog raw, Utf8Buffer button_text, GtkResponseType response_id); + @@ -203,6 +214,8 @@ namespace Avalonia.Gtk3.Interop public delegate ulong g_timeout_add(uint interval, timeout_callback callback, IntPtr data); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public delegate ulong g_free(IntPtr data); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] + public unsafe delegate void g_slist_free(GSList* data); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gio)] public delegate GInputStream g_memory_input_stream_new_from_data(IntPtr ptr, IntPtr len, IntPtr destroyCallback); @@ -212,6 +225,9 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool signal_generic(IntPtr gtkWidget, IntPtr userData); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool signal_dialog_response(IntPtr gtkWidget, GtkResponseType response, IntPtr userData); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool signal_onevent(IntPtr gtkWidget, IntPtr ev, IntPtr userData); @@ -247,11 +263,17 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_window_set_default_size GtkWindowSetDefaultSize; public static D.gtk_window_get_position GtkWindowGetPosition; public static D.gtk_window_move GtkWindowMove; + public static D.gtk_file_chooser_dialog_new GtkFileChooserDialogNew; + public static D.gtk_file_chooser_set_select_multiple GtkFileChooserSetSelectMultiple; + public static D.gtk_file_chooser_set_filename GtkFileChooserSetFilename; + public static D.gtk_file_chooser_get_filenames GtkFileChooserGetFilenames; + public static D.gtk_dialog_add_button GtkDialogAddButton; public static D.g_object_unref GObjectUnref; public static D.g_signal_connect_object GSignalConnectObject; public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; public static D.g_timeout_add GTimeoutAdd; public static D.g_free GFree; + public static D.g_slist_free GSlistFree; public static D.g_memory_input_stream_new_from_data GMemoryInputStreamNewFromData; public static D.gtk_widget_set_double_buffered GtkWidgetSetDoubleBuffered; public static D.gtk_widget_set_events GtkWidgetSetEvents; @@ -467,6 +489,13 @@ namespace Avalonia.Gtk3.Interop public guint is_modifier; } + [StructLayout(LayoutKind.Sequential)] + unsafe struct GSList + { + public IntPtr Data; + public GSList* Next; + } + [Flags] public enum GdkWindowState { @@ -481,6 +510,29 @@ namespace Avalonia.Gtk3.Interop Ttiled = 256 } + public enum GtkResponseType + { + Help = -11, + Apply = -10, + No = -9, + Yes = -8, + Close = -7, + Cancel = -6, + Ok = -5, + DeleteEvent = -4, + Accept = -3, + Reject = -2, + None = -1, + } + + public enum GtkFileChooserAction + { + Open, + Save, + SelectFolder, + CreateFolder, + } + [StructLayout(LayoutKind.Sequential)] struct GdkGeometry { diff --git a/src/Gtk/Avalonia.Gtk3/Stubs.cs b/src/Gtk/Avalonia.Gtk3/Stubs.cs deleted file mode 100644 index 6d89384838..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Stubs.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Controls; -using Avalonia.Controls.Platform; -using Avalonia.Input.Platform; -using Avalonia.Platform; - -//TODO: This file should be empty once everything is implemented - -namespace Avalonia.Gtk3 -{ - class SystemDialogStub : ISystemDialogImpl - { - public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) => Task.FromResult(new string[0]); - - public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) - => Task.FromResult((string) null); - } -} diff --git a/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs b/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs new file mode 100644 index 0000000000..19d135492a --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Gtk3.Interop; +using Avalonia.Input.Platform; +using Avalonia.Platform; + +//TODO: This file should be empty once everything is implemented + +namespace Avalonia.Gtk3 +{ + class SystemDialogStub : ISystemDialogImpl + { + + unsafe static Task ShowDialog(string title, GtkWindow parent, GtkFileChooserAction action, + bool multiselect, string initialFileName) + { + GtkFileChooser dlg; + using (var name = title != null ? new Utf8Buffer(title) : null) + dlg = Native.GtkFileChooserDialogNew(name, parent, action, IntPtr.Zero); + if (multiselect) + Native.GtkFileChooserSetSelectMultiple(dlg, true); + + Native.GtkWindowSetModal(dlg, true); + var tcs = new TaskCompletionSource(); + List disposables = null; + Action dispose = () => + { + foreach (var d in disposables) + d.Dispose(); + disposables.Clear(); + }; + disposables = new List + { + Signal.Connect(dlg, "close", delegate + { + tcs.TrySetResult(null); + dispose(); + return false; + }), + Signal.Connect(dlg, "response", (_, resp, __)=> + { + string[] result = null; + if (resp == GtkResponseType.Accept) + { + var rlst = new List(); + var gs = Native.GtkFileChooserGetFilenames(dlg); + var cgs = gs; + while (cgs != null) + { + if (cgs->Data != IntPtr.Zero) + rlst.Add(Utf8Buffer.StringFromPtr(cgs->Data)); + cgs = cgs->Next; + } + Native.GSlistFree(gs); + result = rlst.ToArray(); + } + Native.GtkWidgetHide(dlg); + dispose(); + tcs.TrySetResult(result); + return false; + }), + dlg + }; + using (var open = new Utf8Buffer("Open")) + Native.GtkDialogAddButton(dlg, open, GtkResponseType.Accept); + using (var open = new Utf8Buffer("Cancel")) + Native.GtkDialogAddButton(dlg, open, GtkResponseType.Cancel); + if(initialFileName!=null) + using (var fn = new Utf8Buffer(initialFileName)) + Native.GtkFileChooserSetFilename(dlg, fn); + Native.GtkWindowPresent(dlg); + return tcs.Task; + } + + public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) + { + return ShowDialog(dialog.Title, ((TopLevelImpl) parent)?.GtkWidget, + dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save, + (dialog as OpenFileDialog)?.AllowMultiple ?? false, dialog.InitialFileName); + } + + public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + var res = await ShowDialog(dialog.Title, ((TopLevelImpl) parent)?.GtkWidget, + GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory); + return res?.FirstOrDefault(); + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs index e24e435c08..3884b19ae3 100644 --- a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs @@ -14,7 +14,7 @@ namespace Avalonia.Gtk3 { abstract class TopLevelImpl : ITopLevelImpl, IPlatformHandle { - protected readonly GtkWindow GtkWidget; + public readonly GtkWindow GtkWidget; private IInputRoot _inputRoot; private readonly GtkImContext _imContext; private readonly FramebufferManager _framebuffer;