[gtk+/wip/native-file-chooser: 9/11] GtkFileChooserNative: Fallback and win32 implementation



commit e0ad8a849fb2516b496fa17f6ea88bce13dbd6ca
Author: Alexander Larsson <alexl redhat com>
Date:   Thu Oct 29 15:06:57 2015 +0100

    GtkFileChooserNative: Fallback and win32 implementation
    
    This is a subclass on GtkNativeDialog that uses GtkFileChooserDialog
    as a fallback, but also has support for the win32 file chooser dialog.

 docs/reference/gtk/gtk-docs.sgml     |    1 +
 docs/reference/gtk/gtk3-sections.txt |   19 +
 gtk/Makefile.am                      |    4 +
 gtk/gtk.h                            |    1 +
 gtk/gtkfilechooserdialog.c           |    7 +-
 gtk/gtkfilechoosernative.c           |  692 ++++++++++++++++++++++++++++++
 gtk/gtkfilechoosernative.h           |   56 +++
 gtk/gtkfilechoosernativeprivate.h    |   53 +++
 gtk/gtkfilechoosernativewin32.c      |  776 ++++++++++++++++++++++++++++++++++
 9 files changed, 1608 insertions(+), 1 deletions(-)
---
diff --git a/docs/reference/gtk/gtk-docs.sgml b/docs/reference/gtk/gtk-docs.sgml
index f7776a2..6fef95c 100644
--- a/docs/reference/gtk/gtk-docs.sgml
+++ b/docs/reference/gtk/gtk-docs.sgml
@@ -204,6 +204,7 @@
       <xi:include href="xml/gtkfilechooser.xml" />
       <xi:include href="xml/gtkfilechooserbutton.xml" />
       <xi:include href="xml/gtknativedialog.xml" />
+      <xi:include href="xml/gtkfilechoosernative.xml" />
       <xi:include href="xml/gtkfilechooserdialog.xml" />
       <xi:include href="xml/gtkfilechooserwidget.xml" />
       <xi:include href="xml/gtkfilefilter.xml" />
diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt
index 4301012..858c0c7 100644
--- a/docs/reference/gtk/gtk3-sections.txt
+++ b/docs/reference/gtk/gtk3-sections.txt
@@ -1433,6 +1433,25 @@ gtk_file_chooser_get_type
 </SECTION>
 
 <SECTION>
+<FILE>gtkfilechoosernative</FILE>
+<TITLE>GtkFileChooserNative</TITLE>
+gtk_file_chooser_native_new
+gtk_file_chooser_native_get_accept_label
+gtk_file_chooser_native_set_accept_label
+gtk_file_chooser_native_get_cancel_label
+gtk_file_chooser_native_set_cancel_label
+<SUBSECTION Standard>
+GTK_FILE_CHOOSER_NATIVE
+GTK_IS_FILE_CHOOSER_NATIVE
+GTK_TYPE_FILE_CHOOSER_NATIVE
+GTK_FILE_CHOOSER_NATIVE_CLASS
+GTK_IS_FILE_CHOOSER_NATIVE_CLASS
+GTK_FILE_CHOOSER_NATIVE_GET_CLASS
+<SUBSECTION Private>
+gtk_file_chooser_native_get_type
+</SECTION>
+
+<SECTION>
 <FILE>gtkfilechooserdialog</FILE>
 <TITLE>GtkFileChooserDialog</TITLE>
 GtkFileChooserDialog
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 84f3ffe..db6f55d 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -180,6 +180,7 @@ gtk_public_h_sources =              \
        gtkfilechooser.h        \
        gtkfilechooserbutton.h  \
        gtkfilechooserdialog.h  \
+       gtkfilechoosernative.h  \
        gtkfilechooserwidget.h  \
        gtkfilefilter.h         \
        gtkfixed.h              \
@@ -440,6 +441,7 @@ gtk_private_h_sources =             \
        gtkfilechooserembed.h   \
        gtkfilechooserentry.h   \
        gtkfilechooserprivate.h \
+       gtkfilechoosernativeprivate.h   \
        gtkfilechooserwidgetprivate.h   \
        gtkfilechooserutils.h   \
        gtkfilefilterprivate.h  \
@@ -696,6 +698,7 @@ gtk_base_c_sources =                \
        gtkfilechooserdialog.c  \
        gtkfilechooserembed.c   \
        gtkfilechooserentry.c   \
+       gtkfilechoosernative.c  \
        gtkfilechooserutils.c   \
        gtkfilechooserwidget.c  \
        gtkfilefilter.c         \
@@ -940,6 +943,7 @@ endif
 gtk_os_win32_c_sources = \
        gtkprint-win32.c                \
        gtkprintoperation-win32.c       \
+       gtkfilechoosernativewin32.c     \
        gtkwin32.c
 if OS_WIN32
 gtk_private_h_sources += gtkprint-win32.h
diff --git a/gtk/gtk.h b/gtk/gtk.h
index dc57f42..2c0fc21 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -97,6 +97,7 @@
 #include <gtk/gtkfilechooser.h>
 #include <gtk/gtkfilechooserbutton.h>
 #include <gtk/gtkfilechooserdialog.h>
+#include <gtk/gtkfilechoosernative.h>
 #include <gtk/gtkfilechooserwidget.h>
 #include <gtk/gtkfilefilter.h>
 #include <gtk/gtkflowbox.h>
diff --git a/gtk/gtkfilechooserdialog.c b/gtk/gtkfilechooserdialog.c
index d969394..e82bb0d 100644
--- a/gtk/gtkfilechooserdialog.c
+++ b/gtk/gtkfilechooserdialog.c
@@ -45,7 +45,7 @@
  * SECTION:gtkfilechooserdialog
  * @Short_description: A file chooser dialog, suitable for “File/Open” or “File/Save” commands
  * @Title: GtkFileChooserDialog
- * @See_also: #GtkFileChooser, #GtkDialog
+ * @See_also: #GtkFileChooser, #GtkDialog, GtkFileChooserNative
  *
  * #GtkFileChooserDialog is a dialog box suitable for use with
  * “File/Open” or “File/Save as” commands.  This widget works by
@@ -58,6 +58,11 @@
  * own.  Instead, you should use the functions that work on a
  * #GtkFileChooser.
  *
+ * If you want to integrate well with the platform you should use the
+ * #GtkFileChooserNative API, which will use a platform-specific
+ * dialog if available and fall back to GtkFileChooserDialog
+ * otherwise.
+ *
  * ## Typical usage ## {#gtkfilechooser-typical-usage}
  *
  * In the simplest of cases, you can the following code to use
diff --git a/gtk/gtkfilechoosernative.c b/gtk/gtkfilechoosernative.c
new file mode 100644
index 0000000..4e37a3e
--- /dev/null
+++ b/gtk/gtkfilechoosernative.c
@@ -0,0 +1,692 @@
+/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
+/* GTK - The GIMP Toolkit
+ * gtkfilechoosernative.c: Native File selector dialog
+ * Copyright (C) 2015, Red Hat, Inc.
+ *
+ * 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"
+
+/**
+ * SECTION:gtkfilechoosernative
+ * @Short_description: A native file chooser dialog, suitable for “File/Open” or “File/Save” commands
+ * @Title: GtkFileChooserNative
+ * @See_also: #GtkFileChooser, #GtkNativeDialog, #GtkFileChooserDialog
+ *
+ * #GtkFileChooserNative is an abstraction of a dialog box suitable
+ * for use with “File/Open” or “File/Save as” commands. By default, this
+ * just uses a #GtkFileChooserDialog to implement the actual dialog.
+ * However, on certain platforms, such as Windows, the native platform
+ * file chooser is uses instead.
+ *
+ * While the API of #GtkFileChooserNative closely mirrors #GtkFileChooserDialog, the main
+ * difference is that there is no access to any #GtkWindow or #GtkWidget for the dialog.
+ * This is required, as there may not be one in the case of a platform native dialog.
+ * Showing, hiding and running the dialog is handled by the #GtkNativeDialog functions.
+ *
+ * ## Typical usage ## {#gtkfilechoosernative-typical-usage}
+ *
+ * In the simplest of cases, you can the following code to use
+ * #GtkFileChooserDialog to select a file for opening:
+ *
+ * |[
+ * GtkFileChooserNative *native;
+ * GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
+ * gint res;
+ *
+ * native = gtk_file_chooser_native_new ("Open File",
+ *                                       parent_window,
+ *                                       action,
+ *                                       "_Open",
+ *                                       "_Cancel");
+ *
+ * res = gtk_native_dialog_run (GTK_NATIVE_DIALOG (native));
+ * if (res == GTK_RESPONSE_ACCEPT)
+ *   {
+ *     char *filename;
+ *     GtkFileChooser *chooser = GTK_FILE_CHOOSER (native);
+ *     filename = gtk_file_chooser_get_filename (chooser);
+ *     open_file (filename);
+ *     g_free (filename);
+ *   }
+ *
+ * g_object_unref (native);
+ * ]|
+ *
+ * To use a dialog for saving, you can use this:
+ *
+ * |[
+ * GtkFileChooserNative *native;
+ * GtkFileChooser *chooser;
+ * GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE;
+ * gint res;
+ *
+ * native = gtk_file_chooser_native_new ("Save File",
+ *                                       parent_window,
+ *                                       action,
+ *                                       "_Save",
+ *                                       "_Cancel");
+ * chooser = GTK_FILE_CHOOSER (native);
+ *
+ * gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
+ *
+ * if (user_edited_a_new_document)
+ *   gtk_file_chooser_set_current_name (chooser,
+ *                                      _("Untitled document"));
+ * else
+ *   gtk_file_chooser_set_filename (chooser,
+ *                                  existing_filename);
+ *
+ * res = gtk_native_dialog_run (GTK_NATIVE_DIALOG (native));
+ * if (res == GTK_RESPONSE_ACCEPT)
+ *   {
+ *     char *filename;
+ *
+ *     filename = gtk_file_chooser_get_filename (chooser);
+ *     save_to_file (filename);
+ *     g_free (filename);
+ *   }
+ *
+ * g_object_unref (native);
+ * ]|
+ *
+ * For more information on how to best set up a file dialog, see #GtkFileChooserDialog.
+ *
+ * ## Response Codes ## {#gtkfilechooserdialognative-responses}
+ *
+ * #GtkFileChooserNative inherits from #GtkNativeDialog, which means it
+ * will return #GTK_RESPONSE_ACCEPT if the user accepted, and
+ * #GTK_RESPONSE_CANCEL if he pressed cancel. It can also return
+ * #GTK_RESPONSE_DELETE_EVENT if the window was unexpectedly closed.
+ *
+ * ## Differences from #GtkFileChooserDialog ##  {#gtkfilechooserdialognative-differences}
+ *
+ * There are a few things in the GtkFileChooser API that are not
+ * possible to use with #GtkFileChooserNative, as such use would
+ * prohibit the use of a native dialog.
+ *
+ * There is no support for the signals that are emitted when the user
+ * navigates in the dialog, including:
+ * * #GtkFileChooser::current-folder-changed
+ * * #GtkFileChooser::selection-changed
+ * * #GtkFileChooser::file-activated
+ * * #GtkFileChooser::confirm-overwrite
+ *
+ * You can also not use the methods that directly control user navigation:
+ * * gtk_file_chooser_unselect_filename()
+ * * gtk_file_chooser_select_all()
+ * * gtk_file_chooser_unselect_all()
+ *
+ * If you need any of the above you will have to use #GtkFileChooserDialog directly.
+ *
+ * No operations that change the the dialog work while the dialog is
+ * visible. Set all the properties that are required before showing the dialog.
+ *
+ * ## Win32 details ## {#gtkfilechooserdialognative-win32}
+ *
+ * On windows the IFileDialog implementation (added in Windows Vista) is
+ * used. It supports many of the features that #GtkFileChooserDialog
+ * does, but there are some things it does not handle:
+ *
+ * * Extra widgets added with gtk_file_chooser_set_extra_widget().
+ *
+ * * Use of custom previews by connecting to #GtkFileChooser::update-preview.
+ *
+ * * Any #GtkFileFilter added using a mimetype or custom filter.
+ *
+ * If any of these features are used the regular #GtkFileChooserDialog
+ * will be used in place of the native one.
+ */
+
+enum {
+  MODE_FALLBACK,
+  MODE_WIN32,
+};
+
+enum {
+  PROP_0,
+  PROP_ACCEPT_LABEL,
+  PROP_CANCEL_LABEL,
+  LAST_ARG,
+};
+
+static GParamSpec *native_props[LAST_ARG] = { NULL, };
+
+static void    _gtk_file_chooser_native_iface_init   (GtkFileChooserIface  *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkFileChooserNative, gtk_file_chooser_native, GTK_TYPE_NATIVE_DIALOG,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER,
+                                                _gtk_file_chooser_native_iface_init))
+
+
+/**
+ * gtk_file_chooser_native_get_accept_label:
+ * @self: a #GtFileChooserNative
+ *
+ * Retrieves the custom label text for the accept button.
+ *
+ * Returns: The custom label, or %NULL for the default. This string is
+ * owned by GTK+ and should not be modified or freed
+ *
+ * Since: 3.20
+ **/
+const char *
+gtk_file_chooser_native_get_accept_label (GtkFileChooserNative *self)
+{
+  g_return_val_if_fail (GTK_IS_FILE_CHOOSER_NATIVE (self), NULL);
+
+  return self->accept_label;
+}
+
+/**
+ * gtk_file_chooser_native_set_accept_label:
+ * @self: a #GtFileChooserNative
+ * @accept_label: (allow-none): custom label or %NULL for the default
+ *
+ * Sets the custom label text for the accept button.
+ *
+ * If characters in @label are preceded by an underscore, they are underlined.
+ * If you need a literal underscore character in a label, use “__” (two
+ * underscores). The first underlined character represents a keyboard
+ * accelerator called a mnemonic.
+ * Pressing Alt and that key activates the button.
+ *
+ * Since: 3.20
+ **/
+void
+gtk_file_chooser_native_set_accept_label (GtkFileChooserNative *self,
+                                          const char           *accept_label)
+{
+  g_return_if_fail (GTK_IS_FILE_CHOOSER_NATIVE (self));
+
+  g_free (self->accept_label);
+  self->accept_label = g_strdup (accept_label);
+
+  g_object_notify_by_pspec (G_OBJECT (self), native_props[PROP_ACCEPT_LABEL]);
+}
+
+/**
+ * gtk_file_chooser_native_get_cancel_label:
+ * @self: a #GtFileChooserNative
+ *
+ * Retrieves the custom label text for the cancel button.
+ *
+ * Returns: The custom label, or %NULL for the default. This string is
+ * owned by GTK+ and should not be modified or freed
+ *
+ * Since: 3.20
+ **/
+const char *
+gtk_file_chooser_native_get_cancel_label (GtkFileChooserNative *self)
+{
+  g_return_val_if_fail (GTK_IS_FILE_CHOOSER_NATIVE (self), NULL);
+
+  return self->cancel_label;
+}
+
+/**
+ * gtk_file_chooser_native_set_cancel_label:
+ * @self: a #GtFileChooserNative
+ * @cancel_label: (allow-none): custom label or %NULL for the default
+ *
+ * Sets the custom label text for the cancel button.
+ *
+ * If characters in @label are preceded by an underscore, they are underlined.
+ * If you need a literal underscore character in a label, use “__” (two
+ * underscores). The first underlined character represents a keyboard
+ * accelerator called a mnemonic.
+ * Pressing Alt and that key activates the button.
+ *
+ * Since: 3.20
+ **/
+void
+gtk_file_chooser_native_set_cancel_label (GtkFileChooserNative *self,
+                                         const char           *cancel_label)
+{
+  g_return_if_fail (GTK_IS_FILE_CHOOSER_NATIVE (self));
+
+  g_free (self->cancel_label);
+  self->cancel_label = g_strdup (cancel_label);
+
+  g_object_notify_by_pspec (G_OBJECT (self), native_props[PROP_CANCEL_LABEL]);
+}
+
+static void
+gtk_file_chooser_native_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+
+{
+  GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (object);
+
+  switch (prop_id)
+    {
+    case PROP_ACCEPT_LABEL:
+      gtk_file_chooser_native_set_accept_label (self, g_value_get_string (value));
+      break;
+
+    case PROP_CANCEL_LABEL:
+      gtk_file_chooser_native_set_cancel_label (self, g_value_get_string (value));
+      break;
+
+    default:
+      g_object_set_property (G_OBJECT (self->dialog), pspec->name, value);
+      break;
+    }
+}
+
+static void
+gtk_file_chooser_native_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+  GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (object);
+
+  switch (prop_id)
+    {
+    case PROP_ACCEPT_LABEL:
+      g_value_set_string (value, self->accept_label);
+      break;
+
+    case PROP_CANCEL_LABEL:
+      g_value_set_string (value, self->cancel_label);
+      break;
+
+    default:
+      g_object_get_property (G_OBJECT (self->dialog), pspec->name, value);
+      break;
+    }
+}
+
+static void
+gtk_file_chooser_native_finalize (GObject *object)
+{
+  GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (object);
+
+  g_clear_pointer (&self->current_name, g_free);
+  g_clear_object (&self->current_file);
+  g_clear_object (&self->current_folder);
+
+  g_clear_pointer (&self->accept_label, g_free);
+  g_clear_pointer (&self->cancel_label, g_free);
+  gtk_widget_destroy (self->dialog);
+
+  g_slist_free_full (self->custom_files, g_object_unref);
+
+  G_OBJECT_CLASS (gtk_file_chooser_native_parent_class)->finalize (object);
+}
+
+static gint
+override_delete_handler (GtkDialog *dialog,
+                         GdkEventAny *event,
+                         gpointer data)
+{
+  return TRUE; /* Do not destroy */
+}
+
+static char *
+translate_mnemonics (const char *src)
+{
+  GString *s;
+  const char *p;
+
+  if (src == NULL)
+    return NULL;
+  
+  s = g_string_new_len ("", strlen (src) + 1);
+      
+  for (p = src; *p; p++)
+    {
+      if (*p == '_')
+        {
+          /* __ is _ escaped */
+          if (*(p+1) == '_')
+            {
+              g_string_append_c (s, '_');
+              p++;
+            }
+          else
+            g_string_append_c (s, '&');
+        }
+      else if (*p == '&')
+        {
+          /* Win32 needs ampersands double-escaped */
+          g_string_append (s, "&&&");
+        }
+      else
+        g_string_append_c (s, *p);
+    }
+
+  return g_string_free (s, FALSE);
+}
+
+static void
+gtk_file_chooser_native_init (GtkFileChooserNative *self)
+{
+  /* We always create a File chooser dialog and delegate all properties to it.
+   * This way we can reuse that store, plus we always have a dialog we can use
+   * in case something makes the native one not work (like the custom widgets) */
+  self->dialog = g_object_new (GTK_TYPE_FILE_CHOOSER_DIALOG, NULL);
+  self->cancel_button = gtk_dialog_add_button (GTK_DIALOG (self->dialog), _("_Cancel"), GTK_RESPONSE_CANCEL);
+  self->accept_button = gtk_dialog_add_button (GTK_DIALOG (self->dialog), _("_Open"), GTK_RESPONSE_ACCEPT);
+
+  gtk_dialog_set_default_response (GTK_DIALOG (self->dialog),
+                                   GTK_RESPONSE_ACCEPT);
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+  gtk_dialog_set_alternative_button_order (GTK_DIALOG (self->dialog),
+                                           GTK_RESPONSE_ACCEPT,
+                                           GTK_RESPONSE_CANCEL,
+                                           -1);
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+  /* We don't want to destroy on delete event, instead we hide in the response cb */
+  g_signal_connect (self->dialog,
+                    "delete-event",
+                    G_CALLBACK (override_delete_handler),
+                    NULL);
+
+  /* This is used, instead of the standard delegate, to ensure that signals are not delegated. */
+  g_object_set_qdata (G_OBJECT (self), GTK_FILE_CHOOSER_DELEGATE_QUARK, self->dialog);
+}
+
+/**
+ * gtk_file_chooser_native_new:
+ * @title: (allow-none): Title of the native, or %NULL
+ * @parent: (allow-none): Transient parent of the native, or %NULL
+ * @action: Open or save mode for the dialog
+ * @accept_label: (allow-none): text to go in the accept button, or %NULL for the default
+ * @cancel_label: (allow-none): text to go in the cancel button, or %NULL for the default
+ *
+ * Creates a new #GtkFileChooserNative.
+ *
+ * Returns: a new #GtkFileChooserNative
+ *
+ * Since: 3.20
+ **/
+GtkFileChooserNative *
+gtk_file_chooser_native_new (const gchar          *title,
+                             GtkWindow            *parent,
+                             GtkFileChooserAction  action,
+                             const gchar          *accept_label,
+                             const gchar          *cancel_label)
+{
+  GtkFileChooserNative *result;
+
+  result = g_object_new (GTK_TYPE_FILE_CHOOSER_NATIVE,
+                         "title", title,
+                         "action", action,
+                         "transient-for", parent,
+                         "accept-label", accept_label,
+                         "cancel-label", cancel_label,
+                         NULL);
+
+  return result;
+}
+
+static void
+dialog_response_cb (GtkDialog *dialog,
+                    gint response_id,
+                    gpointer data)
+{
+  GtkFileChooserNative *self = data;
+
+  g_signal_handlers_disconnect_by_func (self->dialog, dialog_response_cb, self);
+  gtk_widget_hide (self->dialog);
+
+  _gtk_native_dialog_emit_response (GTK_NATIVE_DIALOG (self), response_id);
+}
+
+static void
+dialog_update_preview_cb (GtkFileChooser *file_chooser,
+                          gpointer data)
+{
+  g_signal_emit_by_name (data, "update-preview");
+}
+
+static void
+show_dialog (GtkFileChooserNative *self)
+{
+  GtkFileChooserAction action;
+  const char *accept_label, *cancel_label;
+
+  action = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (self->dialog));
+
+  accept_label = self->accept_label;
+  if (accept_label == NULL)
+    accept_label = (action == GTK_FILE_CHOOSER_ACTION_SAVE) ? _("_Save") :  _("_Open");
+
+  gtk_button_set_label (GTK_BUTTON (self->accept_button), accept_label);
+
+  cancel_label = self->cancel_label;
+  if (cancel_label == NULL)
+    cancel_label = _("_Cancel");
+
+  gtk_button_set_label (GTK_BUTTON (self->cancel_button), cancel_label);
+
+  gtk_window_set_title (GTK_WINDOW (self->dialog),
+                        gtk_native_dialog_get_title (GTK_NATIVE_DIALOG (self)));
+
+  gtk_window_set_transient_for (GTK_WINDOW (self->dialog),
+                                gtk_native_dialog_get_transient_for (GTK_NATIVE_DIALOG (self)));
+
+  gtk_window_set_modal (GTK_WINDOW (self->dialog),
+                        gtk_native_dialog_get_modal (GTK_NATIVE_DIALOG (self)));
+
+  g_signal_connect (self->dialog,
+                    "response",
+                    G_CALLBACK (dialog_response_cb),
+                    self);
+
+  g_signal_connect (self->dialog,
+                    "update-preview",
+                    G_CALLBACK (dialog_update_preview_cb),
+                    self);
+
+  gtk_window_present (GTK_WINDOW (self->dialog));
+}
+
+static void
+hide_dialog (GtkFileChooserNative *self)
+{
+  g_signal_handlers_disconnect_by_func (self->dialog, dialog_response_cb, self);
+  g_signal_handlers_disconnect_by_func (self->dialog, dialog_update_preview_cb, self);
+  gtk_widget_hide (self->dialog);
+}
+
+static gboolean
+gtk_file_chooser_native_set_current_folder (GtkFileChooser    *chooser,
+                                            GFile             *file,
+                                            GError           **error)
+{
+  GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (chooser);
+  gboolean res;
+
+  res = gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (self->dialog),
+                                                  file, error);
+
+
+  if (res)
+    {
+      g_set_object (&self->current_folder, file);
+
+      g_clear_object (&self->current_file);
+    }
+
+  return res;
+}
+
+static gboolean
+gtk_file_chooser_native_select_file (GtkFileChooser    *chooser,
+                                     GFile             *file,
+                                     GError           **error)
+{
+  GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (chooser);
+  gboolean res;
+
+  res = gtk_file_chooser_select_file (GTK_FILE_CHOOSER (self->dialog),
+                                      file, error);
+
+  if (res)
+    {
+      g_set_object (&self->current_file, file);
+
+      g_clear_object (&self->current_folder);
+      g_clear_pointer (&self->current_name, g_free);
+    }
+
+  return res;
+}
+
+static void
+gtk_file_chooser_native_set_current_name (GtkFileChooser    *chooser,
+                                          const gchar       *name)
+{
+  GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (chooser);
+
+  gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (self->dialog), name);
+
+  g_clear_pointer (&self->current_name, g_free);
+  self->current_name = g_strdup (name);
+
+  g_clear_object (&self->current_file);
+}
+
+static GSList *
+gtk_file_chooser_native_get_files (GtkFileChooser *chooser)
+{
+  GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (chooser);
+
+  switch (self->mode)
+    {
+    case MODE_WIN32:
+      return g_slist_copy_deep (self->custom_files, (GCopyFunc)g_object_ref, NULL);
+
+    case MODE_FALLBACK:
+    default:
+      return gtk_file_chooser_get_files (GTK_FILE_CHOOSER (self->dialog));
+    }
+}
+
+static void
+gtk_file_chooser_native_show (GtkNativeDialog *native)
+{
+  GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (native);
+
+  self->mode = MODE_FALLBACK;
+
+#ifdef GDK_WINDOWING_WIN32
+  if (gtk_file_chooser_native_win32_show (self))
+    self->mode = MODE_WIN32;
+#endif
+
+  if (self->mode == MODE_FALLBACK)
+    show_dialog (self);
+}
+
+static void
+gtk_file_chooser_native_hide (GtkNativeDialog *native)
+{
+  GtkFileChooserNative *self = GTK_FILE_CHOOSER_NATIVE (native);
+
+  switch (self->mode)
+    {
+    case MODE_FALLBACK:
+      hide_dialog (self);
+      break;
+    case MODE_WIN32:
+#ifdef GDK_WINDOWING_WIN32
+      gtk_file_chooser_native_win32_hide (self);
+#endif
+      break;
+    }
+}
+
+static void
+gtk_file_chooser_native_class_init (GtkFileChooserNativeClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+  GtkNativeDialogClass *native_dialog_class = GTK_NATIVE_DIALOG_CLASS (class);
+
+  gobject_class->finalize = gtk_file_chooser_native_finalize;
+  gobject_class->set_property = gtk_file_chooser_native_set_property;
+  gobject_class->get_property = gtk_file_chooser_native_get_property;
+
+  native_dialog_class->show = gtk_file_chooser_native_show;
+  native_dialog_class->hide = gtk_file_chooser_native_hide;
+
+  _gtk_file_chooser_install_properties (gobject_class);
+
+  /**
+   * GtkFileChooserNative:accept-label:
+   *
+   * The text used for the label on the accept button in the dialog, or
+   * %NULL to use the default text.
+   */
+ native_props[PROP_ACCEPT_LABEL] =
+      g_param_spec_string ("accept-label",
+                           P_("Accept label"),
+                           P_("The label on the accept button"),
+                           NULL,
+                           GTK_PARAM_READWRITE);
+
+  /**
+   * GtkFileChooserNative:cancel-label:
+   *
+   * The text used for the label on the cancel button in the dialog, or
+   * %NULL to use the default text.
+   */
+  native_props[PROP_CANCEL_LABEL] =
+      g_param_spec_string ("cancel-label",
+                           P_("Cancel label"),
+                           P_("The label on the cancel button"),
+                           NULL,
+                           GTK_PARAM_READWRITE);
+
+  g_object_class_install_properties (gobject_class, LAST_ARG, native_props);
+}
+
+static void
+_gtk_file_chooser_native_iface_init (GtkFileChooserIface *iface)
+{
+  _gtk_file_chooser_delegate_iface_init (iface);
+  iface->select_file = gtk_file_chooser_native_select_file;
+  iface->set_current_name = gtk_file_chooser_native_set_current_name;
+  iface->set_current_folder = gtk_file_chooser_native_set_current_folder;
+  iface->get_files = gtk_file_chooser_native_get_files;
+}
diff --git a/gtk/gtkfilechoosernative.h b/gtk/gtkfilechoosernative.h
new file mode 100644
index 0000000..c2c21d7
--- /dev/null
+++ b/gtk/gtkfilechoosernative.h
@@ -0,0 +1,56 @@
+/* GTK - The GIMP Toolkit
+ * gtkfilechoosernative.h: Native File selector dialog
+ * Copyright (C) 2015, Red Hat, Inc.
+ *
+ * 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/>.
+ */
+
+#ifndef __GTK_FILE_CHOOSER_NATIVE_H__
+#define __GTK_FILE_CHOOSER_NATIVE_H__
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtkfilechooser.h>
+#include <gtk/gtknativedialog.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_FILE_CHOOSER_NATIVE             (gtk_file_chooser_native_get_type ())
+
+GDK_AVAILABLE_IN_3_20
+G_DECLARE_FINAL_TYPE (GtkFileChooserNative, gtk_file_chooser_native, GTK, FILE_CHOOSER_NATIVE, 
GtkNativeDialog)
+
+GDK_AVAILABLE_IN_3_20
+GtkFileChooserNative *gtk_file_chooser_native_new (const gchar          *title,
+                                                   GtkWindow            *parent,
+                                                   GtkFileChooserAction  action,
+                                                   const gchar          *accept_label,
+                                                   const gchar          *cancel_label);
+
+GDK_AVAILABLE_IN_3_20
+const char *gtk_file_chooser_native_get_accept_label (GtkFileChooserNative *self);
+GDK_AVAILABLE_IN_3_20
+void        gtk_file_chooser_native_set_accept_label (GtkFileChooserNative *self,
+                                                      const char           *accept_label);
+GDK_AVAILABLE_IN_3_20
+const char *gtk_file_chooser_native_get_cancel_label (GtkFileChooserNative *self);
+GDK_AVAILABLE_IN_3_20
+void        gtk_file_chooser_native_set_cancel_label (GtkFileChooserNative *self,
+                                                      const char           *cancel_label);
+
+G_END_DECLS
+
+#endif /* __GTK_FILE_CHOOSER_NATIVE_H__ */
diff --git a/gtk/gtkfilechoosernativeprivate.h b/gtk/gtkfilechoosernativeprivate.h
new file mode 100644
index 0000000..6e271ff
--- /dev/null
+++ b/gtk/gtkfilechoosernativeprivate.h
@@ -0,0 +1,53 @@
+/* GTK - The GIMP Toolkit
+ * gtkfilechoosernativeprivate.h: Native File selector dialog
+ * Copyright (C) 2015, Red Hat, Inc.
+ *
+ * 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/>.
+ */
+
+#ifndef __GTK_FILE_CHOOSER_NATIVE_PRIVATE_H__
+#define __GTK_FILE_CHOOSER_NATIVE_PRIVATE_H__
+
+#include <gtk/gtkfilechoosernative.h>
+
+G_BEGIN_DECLS
+
+struct _GtkFileChooserNative
+{
+  GtkNativeDialog parent_instance;
+
+  char *accept_label;
+  char *cancel_label;
+
+  int mode;
+  GSList *custom_files;
+
+  GFile *current_folder;
+  GFile *current_file;
+  char *current_name;
+
+  /* Fallback mode */
+  GtkWidget *dialog;
+  GtkWidget *accept_button;
+  GtkWidget *cancel_button;
+
+  gpointer mode_data;
+};
+
+gboolean gtk_file_chooser_native_win32_show (GtkFileChooserNative *self);
+void gtk_file_chooser_native_win32_hide (GtkFileChooserNative *self);
+
+G_END_DECLS
+
+#endif /* __GTK_FILE_CHOOSER_NATIVE_PRIVATE_H__ */
diff --git a/gtk/gtkfilechoosernativewin32.c b/gtk/gtkfilechoosernativewin32.c
new file mode 100644
index 0000000..2a88a8d
--- /dev/null
+++ b/gtk/gtkfilechoosernativewin32.c
@@ -0,0 +1,776 @@
+/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
+/* GTK - The GIMP Toolkit
+ * gtkfilechoosernativewin32.c: Win32 Native File selector dialog
+ * Copyright (C) 2015, Red Hat, Inc.
+ *
+ * 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"
+
+/* Vista or newer */
+#define _WIN32_WINNT 0x0600
+#define WINVER _WIN32_WINNT
+#define NTDDI_VERSION NTDDI_VISTA
+#define COBJMACROS
+
+#include "win32/gdkwin32.h"
+#include <shlobj.h>
+#include <windows.h>
+
+typedef struct {
+  GtkFileChooserNative *self;
+  IFileDialogEvents *events;
+
+  HWND parent;
+  gboolean skip_response;
+  gboolean save;
+  gboolean folder;
+  gboolean modal;
+  gboolean overwrite_confirmation;
+  gboolean select_multiple;
+  gboolean show_hidden;
+
+  char *accept_label;
+  char *cancel_label;
+  char *title;
+
+  GSList *shortcut_uris;
+
+  GFile *current_folder;
+  GFile *current_file;
+  char *current_name;
+
+  COMDLG_FILTERSPEC *filters;
+
+  GSList *files;
+  int response;
+} FilechooserWin32ThreadData;
+
+static void
+g_warning_hr (const char *msg, HRESULT hr)
+{
+  char *errmsg;
+  errmsg = g_win32_error_message (hr);
+  g_warning ("%s: %s\n", msg, errmsg);
+  g_free (errmsg);
+}
+
+/* {3CAFD12E-82AE-4184-8309-848C0104B4DC} */
+static const GUID myIID_IFileDialogEvents =
+{ 0x3cafd12e, 0x82ae, 0x4184, { 0x83, 0x9, 0x84, 0x8c, 0x1, 0x4, 0xb4, 0xdc } };
+
+/* Protects access to dialog_hwnd, do_close and ref_count */
+G_LOCK_DEFINE_STATIC(FileDialogEvents);
+
+typedef struct {
+  IFileDialogEvents iFileDialogEvents;
+  int ref_count;
+  gboolean enable_owner;
+  gboolean got_hwnd;
+  HWND dialog_hwnd;
+  gboolean do_close; /* Set if hide was called before dialog_hwnd was set */
+} FileDialogEvents;
+
+
+static ULONG STDMETHODCALLTYPE
+ifiledialogevents_AddRef (IFileDialogEvents *self)
+{
+  FileDialogEvents *events = (FileDialogEvents *)self;
+  ULONG ref_count;
+
+  G_LOCK (FileDialogEvents);
+  ref_count = ++events->ref_count;
+  G_UNLOCK (FileDialogEvents);
+
+  return ref_count;
+}
+
+static ULONG STDMETHODCALLTYPE
+ifiledialogevents_Release (IFileDialogEvents *self)
+{
+  FileDialogEvents *events = (FileDialogEvents *)self;
+  int ref_count;
+
+  G_LOCK (FileDialogEvents);
+  ref_count = --events->ref_count;
+  G_UNLOCK (FileDialogEvents);
+
+  if (ref_count == 0)
+    g_free (self);
+
+  return ref_count;
+}
+
+static HRESULT STDMETHODCALLTYPE
+ifiledialogevents_QueryInterface (IFileDialogEvents *self,
+                                  REFIID       riid,
+                                  LPVOID      *ppvObject)
+{
+  if (IsEqualIID (riid, &IID_IUnknown) ||
+      IsEqualIID (riid, &myIID_IFileDialogEvents))
+    {
+      *ppvObject = self;
+      IUnknown_AddRef ((IUnknown *)self);
+      return NOERROR;
+    }
+  else
+    {
+      *ppvObject = NULL;
+      return E_NOINTERFACE;
+    }
+}
+
+static HRESULT STDMETHODCALLTYPE
+ifiledialogevents_OnFileOk (IFileDialogEvents *self,
+                            IFileDialog *pfd)
+{
+  return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+ifiledialogevents_OnFolderChanging (IFileDialogEvents *self,
+                                    IFileDialog *pfd,
+                                    IShellItem *psiFolder)
+{
+  return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+ifiledialogevents_OnFolderChange (IFileDialogEvents *self,
+                                  IFileDialog *pfd)
+{
+  FileDialogEvents *events = (FileDialogEvents *)self;
+  IOleWindow *olew = NULL;
+  HWND dialog_hwnd;
+  HRESULT hr;
+
+  if (!events->got_hwnd)
+    {
+      events->got_hwnd = TRUE;
+
+      hr = IFileDialog_QueryInterface (pfd, &IID_IOleWindow, &olew);
+      if (SUCCEEDED (hr))
+        {
+          hr = IOleWindow_GetWindow (olew, &dialog_hwnd);
+          if (SUCCEEDED (hr))
+            {
+              G_LOCK (FileDialogEvents);
+              events->dialog_hwnd = dialog_hwnd;
+              if (events->do_close)
+                SendMessage (events->dialog_hwnd, WM_CLOSE, 0, 0);
+              G_UNLOCK (FileDialogEvents);
+            }
+          else
+            g_warning_hr ("Can't get HWND", hr);
+
+          hr = IOleWindow_Release (olew);
+          if (FAILED (hr))
+            g_warning_hr ("Can't unref IOleWindow", hr);
+        }
+      else
+        g_warning_hr ("Can't get IOleWindow", hr);
+
+      if (events->enable_owner && events->dialog_hwnd)
+        {
+          HWND owner = GetWindow (events->dialog_hwnd, GW_OWNER);
+          if (owner)
+            EnableWindow (owner, TRUE);
+        }
+    }
+
+  return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+ifiledialogevents_OnSelectionChange (IFileDialogEvents * self,
+                                     IFileDialog *pfd)
+{
+  return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+ifiledialogevents_OnShareViolation (IFileDialogEvents * self,
+                                    IFileDialog *pfd,
+                                    IShellItem *psi,
+                                    FDE_SHAREVIOLATION_RESPONSE *pResponse)
+{
+  return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE
+ifiledialogevents_OnTypeChange (IFileDialogEvents * self,
+                                IFileDialog *pfd)
+{
+  return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+ifiledialogevents_OnOverwrite (IFileDialogEvents * self,
+                               IFileDialog *pfd,
+                               IShellItem *psi,
+                               FDE_OVERWRITE_RESPONSE *pResponse)
+{
+  return E_NOTIMPL;
+}
+
+static IFileDialogEventsVtbl ifde_vtbl = {
+  ifiledialogevents_QueryInterface,
+  ifiledialogevents_AddRef,
+  ifiledialogevents_Release,
+  ifiledialogevents_OnFileOk,
+  ifiledialogevents_OnFolderChanging,
+  ifiledialogevents_OnFolderChange,
+  ifiledialogevents_OnSelectionChange,
+  ifiledialogevents_OnShareViolation,
+  ifiledialogevents_OnTypeChange,
+  ifiledialogevents_OnOverwrite
+};
+
+file_dialog_events_send_close (IFileDialogEvents *self)
+{
+  FileDialogEvents *events = (FileDialogEvents *)self;
+
+  G_LOCK (FileDialogEvents);
+
+  if (events->dialog_hwnd)
+    SendMessage (events->dialog_hwnd, WM_CLOSE, 0, 0);
+  else
+    events->do_close = TRUE;
+
+  G_UNLOCK (FileDialogEvents);
+}
+
+static IFileDialogEvents *
+file_dialog_events_new (gboolean enable_owner)
+{
+  FileDialogEvents *events;
+
+  events = g_new0 (FileDialogEvents, 1);
+  events->iFileDialogEvents.lpVtbl = &ifde_vtbl;
+  events->ref_count = 1;
+  events->enable_owner = enable_owner;
+
+  return &events->iFileDialogEvents;
+}
+
+static void
+filechooser_win32_thread_data_free (FilechooserWin32ThreadData *data)
+{
+  int i;
+  if (data->filters)
+    {
+      for (i = 0; data->filters[i].pszName != NULL; i++)
+        {
+          g_free ((char *)data->filters[i].pszName);
+          g_free ((char *)data->filters[i].pszSpec);
+        }
+      g_free (data->filters);
+    }
+
+  if (data->events)
+    IFileDialogEvents_Release (data->events);
+
+  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_win32_thread_done (gpointer _data)
+{
+  FilechooserWin32ThreadData *data = _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);
+    }
+
+  filechooser_win32_thread_data_free (data);
+
+  return FALSE;
+}
+
+static void
+data_add_shell_item (FilechooserWin32ThreadData *data,
+                     IShellItem *item)
+{
+  HRESULT hr;
+  PWSTR urlw = NULL;
+  char *url;
+
+  hr = IShellItem_GetDisplayName (item, SIGDN_URL, &urlw);
+  if (SUCCEEDED (hr))
+    {
+      url = g_utf16_to_utf8 (urlw, -1, NULL, NULL, NULL);
+      CoTaskMemFree (urlw);
+      data->files = g_slist_prepend (data->files, g_file_new_for_uri (url));
+      data->response = GTK_RESPONSE_ACCEPT;
+      g_free (url);
+    }
+}
+
+static IShellItem *
+get_shell_item_for_uri (const char *uri)
+{
+  IShellItem *item;
+  HRESULT hr;
+  gunichar2 *uri_w = g_utf8_to_utf16 (uri, -1, NULL, NULL, NULL);
+
+  hr = SHCreateItemFromParsingName(uri_w, 0, &IID_IShellItem, &item);
+  if (SUCCEEDED (hr))
+    return item;
+  else
+    g_warning_hr ("Can't create shell item from shortcut", hr);
+
+  return NULL;
+}
+
+static IShellItem *
+get_shell_item_for_file (GFile *file)
+{
+  char *uri;
+  IShellItem *item;
+
+  uri = g_file_get_uri (file);
+  item = get_shell_item_for_uri (uri);
+  g_free (uri);
+
+  return item;
+}
+
+static gpointer
+filechooser_win32_thread (gpointer _data)
+{
+  FilechooserWin32ThreadData *data = _data;
+  HRESULT hr;
+  IFileDialog *pfd = NULL;
+  IFileDialog2 *pfd2 = NULL;
+  gboolean res = FALSE;
+  DWORD flags;
+  HWND parent = NULL;
+  HWND dialog_hwnd;
+  GtkWindow *transient_for;
+  DWORD cookie;
+  GSList *l;
+
+  CoInitializeEx (NULL, COINIT_APARTMENTTHREADED);
+
+  if (data->save && !data->folder)
+    hr = CoCreateInstance (&CLSID_FileSaveDialog,
+                           NULL, CLSCTX_INPROC_SERVER,
+                           &IID_IFileSaveDialog, &pfd);
+  else
+    hr = CoCreateInstance (&CLSID_FileOpenDialog,
+                           NULL, CLSCTX_INPROC_SERVER,
+                           &IID_IFileOpenDialog, &pfd);
+
+  if (FAILED (hr))
+    g_error ("Can't create FileOpenDialog: %s\n", g_win32_error_message (hr));
+
+  hr = IFileDialog_GetOptions (pfd, &flags);
+  if (FAILED (hr))
+    g_error ("Can't get FileDialog options: %s\n", g_win32_error_message (hr));
+
+  flags |= FOS_FORCEFILESYSTEM;
+
+  if (data->folder)
+    flags |= FOS_PICKFOLDERS;
+
+  if (data->folder && data->save)
+    flags &= ~(FOS_FILEMUSTEXIST);
+
+  if (data->select_multiple)
+    flags |= FOS_ALLOWMULTISELECT;
+
+  if (data->show_hidden)
+    flags |= FOS_FORCESHOWHIDDEN;
+
+  if (data->overwrite_confirmation)
+    flags |= FOS_OVERWRITEPROMPT;
+  else
+    flags &= ~(FOS_OVERWRITEPROMPT);
+
+  hr = IFileDialog_SetOptions (pfd, flags);
+  if (FAILED (hr))
+    g_error ("Can't set FileDialog options: %s\n", g_win32_error_message (hr));
+
+  if (data->title)
+    {
+      gunichar2 *label = g_utf8_to_utf16 (data->title, -1,
+                                        NULL, NULL, NULL);
+      IFileDialog_SetTitle (pfd, label);
+      g_free (label);
+    }
+
+  if (data->accept_label)
+    {
+      gunichar2 *label = g_utf8_to_utf16 (data->accept_label, -1,
+                                        NULL, NULL, NULL);
+      IFileDialog_SetOkButtonLabel (pfd, label);
+      g_free (label);
+    }
+
+  if (data->cancel_label)
+    {
+      gunichar2 *label = g_utf8_to_utf16 (data->cancel_label, -1,
+                                        NULL, NULL, NULL);
+      hr = IFileDialog_QueryInterface (pfd, &IID_IFileDialog2, &pfd2);
+      if (SUCCEEDED (hr))
+        {
+          IFileDialog2_SetCancelButtonLabel (pfd2, label);
+          IFileDialog2_Release (pfd2);
+        }
+      g_free (label);
+    }
+
+  for (l = data->shortcut_uris; l != NULL; l = l->next)
+    {
+      IShellItem *item = get_shell_item_for_uri (l->data);
+      if (item)
+        {
+          hr = IFileDialog_AddPlace (pfd, item, FDAP_BOTTOM);
+          if (FAILED (hr))
+            g_warning_hr ("Can't add dialog shortcut", hr);
+          IShellItem_Release (item);
+        }
+    }
+
+  if (data->current_file)
+    {
+      IFileSaveDialog *pfsd;
+      hr = IFileDialog_QueryInterface (pfd, &IID_IFileSaveDialog, &pfsd);
+      if (SUCCEEDED (hr))
+        {
+          IShellItem *item = get_shell_item_for_file (data->current_file);
+          if (item)
+            {
+              hr = IFileSaveDialog_SetSaveAsItem (pfsd, item);
+              if (FAILED (hr))
+                g_warning_hr ("Can't set save as item", hr);
+              IShellItem_Release (item);
+            }
+          IFileSaveDialog_Release (pfsd);
+        }
+    }
+
+  if (data->current_folder)
+    {
+      IShellItem *item = get_shell_item_for_file (data->current_folder);
+      if (item)
+        {
+          hr = IFileDialog_SetFolder (pfd, item);
+          if (FAILED (hr))
+            g_warning_hr ("Can't set folder", hr);
+          IShellItem_Release (item);
+        }
+    }
+
+  if (data->current_name)
+    {
+      gunichar2 *name = g_utf8_to_utf16 (data->current_name, -1, NULL, NULL, NULL);
+      hr = IFileDialog_SetFileName (pfd, name);
+      if (FAILED (hr))
+        g_warning_hr ("Can't set file name", hr);
+      g_free (name);
+    }
+
+  if (data->filters)
+    {
+      int n;
+      for (n = 0; data->filters[n].pszName != NULL; n++)
+        {}
+      hr = IFileDialog_SetFileTypes (pfd, n, data->filters);
+      if (FAILED (hr))
+        g_warning_hr ("Can't set file types", hr);
+    }
+
+  data->response = GTK_RESPONSE_CANCEL;
+
+  hr = IFileDialog_Advise (pfd, data->events, &cookie);
+  if (FAILED (hr))
+    g_error ("Can't Advise FileDialog: %s\n", g_win32_error_message (hr));
+
+  hr = IFileDialog_Show (pfd, data->parent);
+  if (SUCCEEDED (hr))
+    {
+      IFileOpenDialog *pfod = NULL;
+      hr = IFileDialog_QueryInterface (pfd,&IID_IFileOpenDialog, &pfod);
+
+      if (SUCCEEDED (hr))
+        {
+          IShellItemArray *res;
+          DWORD i, count;
+
+          hr = IFileOpenDialog_GetResults (pfod, &res);
+          if (FAILED (hr))
+            g_error ("Can't get FileOpenDialog results: %s\n", g_win32_error_message (hr));
+
+          hr = IShellItemArray_GetCount (res, &count);
+          if (FAILED (hr))
+            g_error ("Can't get FileOpenDialog count: %s\n", g_win32_error_message (hr));
+
+          for (i = 0; i < count; i++)
+            {
+              IShellItem *item;
+              hr = IShellItemArray_GetItemAt (res, i, &item);
+              if (FAILED (hr))
+                g_error ("Can't get item at %d: %s\n", i, g_win32_error_message (hr));
+              data_add_shell_item (data, item);
+              IShellItem_Release (item);
+            }
+          IShellItemArray_Release (res);
+
+          IFileOpenDialog_Release (pfod);
+        }
+      else
+        {
+          IShellItem *item;
+          hr = IFileDialog_GetResult (pfd, &item);
+          if (FAILED (hr))
+            g_error ("Can't get FileDialog result: %s\n", g_win32_error_message (hr));
+
+          data_add_shell_item (data, item);
+          IShellItem_Release (item);
+        }
+    }
+
+  hr = IFileDialog_Unadvise (pfd, cookie);
+  if (FAILED (hr))
+    g_error ("Can't Unadvise FileDialog: %s\n", g_win32_error_message (hr));
+
+  IFileDialog_Release ((IUnknown *)pfd);
+
+  g_main_context_invoke (NULL,
+                         filechooser_win32_thread_done,
+                         data);
+
+  return NULL;
+}
+
+static gboolean
+file_filter_to_win32 (GtkFileFilter *filter,
+                      COMDLG_FILTERSPEC *spec)
+{
+  const char *name;
+  char **patterns;
+  char *pattern_list;
+
+  patterns = _gtk_file_filter_get_as_patterns (filter);
+  if (patterns == NULL)
+    return FALSE;
+
+  pattern_list = g_strjoinv (";", patterns);
+  g_strfreev (patterns);
+
+  name = gtk_file_filter_get_name (filter);
+  if (name == NULL)
+    name = pattern_list;
+  spec->pszName = g_utf8_to_utf16 (name, -1, NULL, NULL, NULL);
+  spec->pszSpec = g_utf8_to_utf16 (pattern_list, -1, NULL, NULL, NULL);
+
+  g_free (pattern_list);
+
+  return TRUE;
+}
+
+static char *
+translate_mnemonics (const char *src)
+{
+  GString *s;
+  const char *p;
+
+  if (src == NULL)
+    return NULL;
+  
+  s = g_string_new_len ("", strlen (src) + 1);
+      
+  for (p = src; *p; p++)
+    {
+      if (*p == '_')
+        {
+          /* __ is _ escaped */
+          if (*(p+1) == '_')
+            {
+              g_string_append_c (s, '_');
+              p++;
+            }
+          else
+            g_string_append_c (s, '&');
+        }
+      else if (*p == '&')
+        {
+          /* Win32 needs ampersands double-escaped */
+          g_string_append (s, "&&&");
+        }
+      else
+        g_string_append_c (s, *p);
+    }
+
+  return g_string_free (s, FALSE);
+}
+
+gboolean
+gtk_file_chooser_native_win32_show (GtkFileChooserNative *self)
+{
+  GThread *thread;
+  FilechooserWin32ThreadData *data;
+  GtkWindow *transient_for;
+  GtkFileChooserAction action;
+  guint update_preview_signal;
+  GSList *filters, *l;
+  int n_filters, i;
+  COMDLG_FILTERSPEC *win32_filters;
+
+  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 (FilechooserWin32ThreadData, 1);
+
+  filters = gtk_file_chooser_list_filters (GTK_FILE_CHOOSER (self));
+  n_filters = g_slist_length (filters);
+  if (n_filters > 0)
+    {
+      data->filters = g_new0 (COMDLG_FILTERSPEC, n_filters + 1);
+
+      for (l = filters, i = 0; l != NULL; l = l->next, i++)
+        {
+          if (!file_filter_to_win32 (l->data, &data->filters[i]))
+            {
+              filechooser_win32_thread_data_free (data);
+              return FALSE;
+            }
+        }
+    }
+
+  self->mode_data = data;
+  data->self = g_object_ref (self);
+
+  data->shortcut_uris =
+    gtk_file_chooser_list_shortcut_folder_uris (GTK_FILE_CHOOSER (self->dialog));
+
+  data->accept_label = translate_mnemonics (self->accept_label);
+  data->cancel_label = translate_mnemonics (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;
+
+  if (gtk_file_chooser_get_do_overwrite_confirmation (GTK_FILE_CHOOSER (self->dialog)))
+    data->overwrite_confirmation = TRUE;
+
+  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_win32_window_get_handle (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);
+    }
+
+  data->events = file_dialog_events_new (!data->modal);
+
+  thread = g_thread_new ("win32 filechooser", filechooser_win32_thread, data);
+  if (thread == NULL)
+    {
+      filechooser_win32_thread_data_free (data);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+void
+gtk_file_chooser_native_win32_hide (GtkFileChooserNative *self)
+{
+  FilechooserWin32ThreadData *data = self->mode_data;
+
+  /* This is always set while dialog visible */
+  g_assert (data != NULL);
+
+  data->skip_response = TRUE;
+  file_dialog_events_send_close (data->events);
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]