b=671820 implement async nsIFilePicker::Open and make sync Show close window on response r=roc
authorKarl Tomlinson <karlt+@karlt.net>
Wed, 22 Aug 2012 16:56:55 +1200
changeset 105052 d066131af975ec9a26fb904acdbcf292340047b5
parent 105051 610e570628166960ae06b574a4d72e113ca2d244
child 105053 3e2cbfcaccd444308bf24a7f28e6f4f6284f4013
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersroc
bugs671820
milestone17.0a1
b=671820 implement async nsIFilePicker::Open and make sync Show close window on response r=roc
widget/gtk2/nsFilePicker.cpp
widget/gtk2/nsFilePicker.h
--- a/widget/gtk2/nsFilePicker.cpp
+++ b/widget/gtk2/nsFilePicker.cpp
@@ -30,16 +30,27 @@
 #endif
 
 using namespace mozilla;
 
 #define MAX_PREVIEW_SIZE 180
 
 nsIFile *nsFilePicker::mPrevDisplayDirectory = nullptr;
 
+// Some GObject functions expect functions for gpointer arguments.
+// gpointer is void* but C++ doesn't like casting functions to void*.
+template<class T> static inline gpointer
+FuncToGpointer(T aFunction)
+{
+    return reinterpret_cast<gpointer>
+        (reinterpret_cast<uintptr_t>
+         // This cast just provides a warning if T is not a function.
+         (reinterpret_cast<void (*)()>(aFunction)));
+}
+
 // XXXdholbert -- this function is duplicated in nsPrintDialogGTK.cpp
 // and needs to be unified in some generic utility class.
 static GtkWindow *
 get_gtk_window_for_nsiwidget(nsIWidget *widget)
 {
   // Get native GdkWindow
   GdkWindow *gdk_win = GDK_WINDOW(widget->GetNativeData(NS_NATIVE_WIDGET));
   if (!gdk_win)
@@ -172,16 +183,17 @@ MakeCaseInsensitiveShellGlob(const char*
   return result;
 }
 
 NS_IMPL_ISUPPORTS1(nsFilePicker, nsIFilePicker)
 
 nsFilePicker::nsFilePicker()
   : mMode(nsIFilePicker::modeOpen),
     mSelectedType(0),
+    mRunning(false),
     mAllowURLs(false)
 {
 }
 
 nsFilePicker::~nsFilePicker()
 {
 }
 
@@ -357,16 +369,35 @@ nsFilePicker::GetFiles(nsISimpleEnumerat
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsFilePicker::Show(PRInt16 *aReturn)
 {
   NS_ENSURE_ARG_POINTER(aReturn);
 
+  nsresult rv = Open(nullptr);
+  if (NS_FAILED(rv))
+    return rv;
+
+  while (mRunning) {
+    g_main_context_iteration(nullptr, TRUE);
+  }
+
+  *aReturn = mResult;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::Open(nsIFilePickerShownCallback *aCallback)
+{
+  // Can't show two dialogs concurrently with the same filepicker
+  if (mRunning)
+    return NS_ERROR_NOT_AVAILABLE;
+
   nsXPIDLCString title;
   title.Adopt(ToNewUTF8String(mTitle));
 
   GtkWindow *parent_widget = get_gtk_window_for_nsiwidget(mParentWidget);
 
   GtkFileChooserAction action = GetGtkFileChooserAction(mMode);
   const gchar *accept_button = (action == GTK_FILE_CHOOSER_ACTION_SAVE)
                                ? GTK_STOCK_SAVE : GTK_STOCK_OPEN;
@@ -393,18 +424,23 @@ nsFilePicker::Show(PRInt16 *aReturn)
 #endif
 
   if (action == GTK_FILE_CHOOSER_ACTION_OPEN || action == GTK_FILE_CHOOSER_ACTION_SAVE) {
     GtkWidget *img_preview = gtk_image_new();
     gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(file_chooser), img_preview);
     g_signal_connect(file_chooser, "update-preview", G_CALLBACK(UpdateFilePreviewWidget), img_preview);
   }
 
-  if (parent_widget && parent_widget->group) {
-    gtk_window_group_add_window(parent_widget->group, GTK_WINDOW(file_chooser));
+  GtkWindow *window = GTK_WINDOW(file_chooser);
+  gtk_window_set_modal(window, TRUE);
+  if (parent_widget) {
+    gtk_window_set_destroy_with_parent(window, TRUE);
+    if (parent_widget->group) {
+      gtk_window_group_add_window(parent_widget->group, window);
+    }
   }
 
   NS_ConvertUTF16toUTF8 defaultName(mDefault);
   switch (mMode) {
     case nsIFilePicker::modeOpenMultiple:
       gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_chooser), TRUE);
       break;
     case nsIFilePicker::modeSave:
@@ -470,43 +506,89 @@ nsFilePicker::Show(PRInt16 *aReturn)
 
     // Set the initially selected filter
     if (mSelectedType == i) {
       gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser), filter);
     }
   }
 
   gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_chooser), TRUE);
-  gint response = gtk_dialog_run(GTK_DIALOG(file_chooser));
+
+  mRunning = true;
+  mCallback = aCallback;
+  NS_ADDREF_THIS();
+  g_signal_connect(file_chooser, "response", G_CALLBACK(OnResponse), this);
+  g_signal_connect(file_chooser, "destroy", G_CALLBACK(OnDestroy), this);
+  gtk_widget_show(file_chooser);
+
+  return NS_OK;
+}
 
+/* static */ void
+nsFilePicker::OnResponse(GtkWidget* file_chooser, gint response_id,
+                         gpointer user_data)
+{
+  static_cast<nsFilePicker*>(user_data)->
+    Done(file_chooser, response_id);
+}
+
+/* static */ void
+nsFilePicker::OnDestroy(GtkWidget* file_chooser, gpointer user_data)
+{
+  static_cast<nsFilePicker*>(user_data)->
+    Done(file_chooser, GTK_RESPONSE_CANCEL);
+}
+
+void
+nsFilePicker::Done(GtkWidget* file_chooser, gint response)
+{
+  mRunning = false;
+
+  PRInt16 result;
   switch (response) {
     case GTK_RESPONSE_OK:
     case GTK_RESPONSE_ACCEPT:
     ReadValuesFromFileChooser(file_chooser);
-    *aReturn = nsIFilePicker::returnOK;
+    result = nsIFilePicker::returnOK;
     if (mMode == nsIFilePicker::modeSave) {
       nsCOMPtr<nsIFile> file;
       GetFile(getter_AddRefs(file));
       if (file) {
         bool exists = false;
         file->Exists(&exists);
         if (exists)
-          *aReturn = nsIFilePicker::returnReplace;
+          result = nsIFilePicker::returnReplace;
       }
     }
     break;
 
     case GTK_RESPONSE_CANCEL:
     case GTK_RESPONSE_CLOSE:
     case GTK_RESPONSE_DELETE_EVENT:
-    *aReturn = nsIFilePicker::returnCancel;
+    result = nsIFilePicker::returnCancel;
     break;
 
     default:
     NS_WARNING("Unexpected response");
-    *aReturn = nsIFilePicker::returnCancel;
+    result = nsIFilePicker::returnCancel;
     break;
   }
 
+  // A "response" signal won't be sent again but "destroy" will be.
+  g_signal_handlers_disconnect_by_func(file_chooser,
+                                       FuncToGpointer(OnDestroy), this);
+
+  // When response_id is GTK_RESPONSE_DELETE_EVENT or when called from
+  // OnDestroy, the widget would be destroyed anyway but it is fine if
+  // gtk_widget_destroy is called more than once.  gtk_widget_destroy has
+  // requests that any remaining references be released, but the reference
+  // count will not be decremented again if GtkWindow's reference has already
+  // been released.
   gtk_widget_destroy(file_chooser);
 
-  return NS_OK;
+  if (mCallback) {
+    mCallback->Done(result);
+    mCallback = nullptr;
+  } else {
+    mResult = result;
+  }
+  NS_RELEASE_THIS();
 }
--- a/widget/gtk2/nsFilePicker.h
+++ b/widget/gtk2/nsFilePicker.h
@@ -20,42 +20,52 @@ class nsFilePicker : public nsBaseFilePi
 {
 public:
   nsFilePicker();
   virtual ~nsFilePicker();
 
   NS_DECL_ISUPPORTS
 
   // nsIFilePicker (less what's in nsBaseFilePicker)
+  NS_IMETHOD Open(nsIFilePickerShownCallback *aCallback);
   NS_IMETHODIMP AppendFilters(PRInt32 aFilterMask);
   NS_IMETHODIMP AppendFilter(const nsAString& aTitle, const nsAString& aFilter);
   NS_IMETHODIMP SetDefaultString(const nsAString& aString);
   NS_IMETHODIMP GetDefaultString(nsAString& aString);
   NS_IMETHODIMP SetDefaultExtension(const nsAString& aExtension);
   NS_IMETHODIMP GetDefaultExtension(nsAString& aExtension);
   NS_IMETHODIMP GetFilterIndex(PRInt32 *aFilterIndex);
   NS_IMETHODIMP SetFilterIndex(PRInt32 aFilterIndex);
   NS_IMETHODIMP GetFile(nsIFile **aFile);
   NS_IMETHODIMP GetFileURL(nsIURI **aFileURL);
   NS_IMETHODIMP GetFiles(nsISimpleEnumerator **aFiles);
   NS_IMETHODIMP Show(PRInt16 *aReturn);
 
+  // nsBaseFilePicker
   virtual void InitNative(nsIWidget *aParent, const nsAString& aTitle, PRInt16 aMode);
 
   static void Shutdown();
 
 protected:
 
   void ReadValuesFromFileChooser(GtkWidget *file_chooser);
 
+  static void OnResponse(GtkWidget* dialog, gint response_id,
+                         gpointer user_data);
+  static void OnDestroy(GtkWidget* dialog, gpointer user_data);
+  void Done(GtkWidget* dialog, gint response_id);
+
   nsCOMPtr<nsIWidget>    mParentWidget;
+  nsCOMPtr<nsIFilePickerShownCallback> mCallback;
   nsCOMArray<nsIFile> mFiles;
 
   PRInt16   mMode;
   PRInt16   mSelectedType;
+  PRInt16   mResult;
+  bool      mRunning;
   bool      mAllowURLs;
   nsCString mFileURL;
   nsString  mTitle;
   nsString  mDefault;
   nsString  mDefaultExtension;
 
   nsTArray<nsCString> mFilters;
   nsTArray<nsCString> mFilterNames;