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;