using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Platform; using Avalonia.Platform.Interop; using static Avalonia.X11.NativeDialogs.Glib; using static Avalonia.X11.NativeDialogs.Gtk; // ReSharper disable AccessToModifiedClosure namespace Avalonia.X11.NativeDialogs { class GtkSystemDialog : ISystemDialogImpl { private Task _initialized; private unsafe Task ShowDialog(string title, IWindowImpl parent, GtkFileChooserAction action, bool multiSelect, string initialFileName) { IntPtr dlg; using (var name = new Utf8Buffer(title)) dlg = gtk_file_chooser_dialog_new(name, IntPtr.Zero, action, IntPtr.Zero); UpdateParent(dlg, parent); if (multiSelect) gtk_file_chooser_set_select_multiple(dlg, true); gtk_window_set_modal(dlg, true); var tcs = new TaskCompletionSource(); List disposables = null; void Dispose() { // ReSharper disable once PossibleNullReferenceException foreach (var d in disposables) d.Dispose(); disposables.Clear(); } disposables = new List { ConnectSignal(dlg, "close", delegate { tcs.TrySetResult(null); Dispose(); return false; }), ConnectSignal(dlg, "response", (_, resp, __) => { string[] result = null; if (resp == GtkResponseType.Accept) { var resultList = new List(); var gs = gtk_file_chooser_get_filenames(dlg); var cgs = gs; while (cgs != null) { if (cgs->Data != IntPtr.Zero) resultList.Add(Utf8Buffer.StringFromPtr(cgs->Data)); cgs = cgs->Next; } g_slist_free(gs); result = resultList.ToArray(); } gtk_widget_hide(dlg); Dispose(); tcs.TrySetResult(result); return false; }) }; using (var open = new Utf8Buffer("Open")) gtk_dialog_add_button(dlg, open, GtkResponseType.Accept); using (var open = new Utf8Buffer("Cancel")) gtk_dialog_add_button(dlg, open, GtkResponseType.Cancel); if (initialFileName != null) using (var fn = new Utf8Buffer(initialFileName)) gtk_file_chooser_set_filename(dlg, fn); gtk_window_present(dlg); return tcs.Task; } public async Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) { await EnsureInitialized(); return await await RunOnGlibThread( () => ShowDialog(dialog.Title, parent, dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save, (dialog as OpenFileDialog)?.AllowMultiple ?? false, Path.Combine(string.IsNullOrEmpty(dialog.InitialDirectory) ? "" : dialog.InitialDirectory, string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName))); } public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) { await EnsureInitialized(); return await await RunOnGlibThread(async () => { var res = await ShowDialog(dialog.Title, parent, GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory); return res?.FirstOrDefault(); }); } async Task EnsureInitialized() { if (_initialized == null) _initialized = StartGtk(); if (!(await _initialized)) throw new Exception("Unable to initialize GTK on separate thread"); } void UpdateParent(IntPtr chooser, IWindowImpl parentWindow) { var xid = parentWindow.Handle.Handle; gtk_widget_realize(chooser); var window = gtk_widget_get_window(chooser); var parent = GetForeignWindow(xid); if (window != IntPtr.Zero && parent != IntPtr.Zero) gdk_window_set_transient_for(window, parent); } } }