1 /* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
2 /* GTK - The GIMP Toolkit
3  * gtkfilechoosernativewin32.c: Win32 Native File selector dialog
4  * Copyright (C) 2015, Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "config.h"
21 
22 /* Vista or newer */
23 #define _WIN32_WINNT 0x0600
24 #define WINVER _WIN32_WINNT
25 #define NTDDI_VERSION NTDDI_VISTA
26 #define COBJMACROS
27 
28 #include "gtkfilechoosernativeprivate.h"
29 #include "gtknativedialogprivate.h"
30 
31 #include "gtkprivate.h"
32 #include "gtkfilechooserdialog.h"
33 #include "gtkfilechooserprivate.h"
34 #include "gtkfilechooserwidget.h"
35 #include "gtkfilechooserwidgetprivate.h"
36 #include "gtkfilechooserutils.h"
37 #include "gtksizerequest.h"
38 #include "gtktypebuiltins.h"
39 #include "gtkintl.h"
40 #include "gtksettings.h"
41 #include "gtktogglebutton.h"
42 #include "gtkheaderbar.h"
43 #include "gtklabel.h"
44 #include "gtkfilefilterprivate.h"
45 #include "gtknative.h"
46 
47 #include "win32/gdkwin32.h"
48 #include <shlobj.h>
49 #include <windows.h>
50 
51 typedef struct {
52   GtkFileChooserNative *self;
53   IFileDialogEvents *events;
54 
55   HWND parent;
56   gboolean skip_response;
57   gboolean save;
58   gboolean folder;
59   gboolean modal;
60   gboolean select_multiple;
61 
62   char *accept_label;
63   char *cancel_label;
64   char *title;
65 
66   GListModel *shortcut_files;
67   GArray *choices_selections;
68 
69   GFile *current_folder;
70   GFile *current_file;
71   char *current_name;
72 
73   COMDLG_FILTERSPEC *filters;
74 
75   GSList *files;
76   int response;
77 } FilechooserWin32ThreadData;
78 
79 static void
g_warning_hr(const char * msg,HRESULT hr)80 g_warning_hr (const char *msg, HRESULT hr)
81 {
82   char *errmsg;
83   errmsg = g_win32_error_message (hr);
84   g_warning ("%s: %s", msg, errmsg);
85   g_free (errmsg);
86 }
87 
88 /* {3CAFD12E-82AE-4184-8309-848C0104B4DC} */
89 static const GUID myIID_IFileDialogEvents =
90 { 0x3cafd12e, 0x82ae, 0x4184, { 0x83, 0x9, 0x84, 0x8c, 0x1, 0x4, 0xb4, 0xdc } };
91 
92 /* Protects access to dialog_hwnd, do_close and ref_count */
93 G_LOCK_DEFINE_STATIC(FileDialogEvents);
94 
95 typedef struct {
96   IFileDialogEvents iFileDialogEvents;
97   int ref_count;
98   gboolean enable_owner;
99   gboolean got_hwnd;
100   HWND dialog_hwnd;
101   gboolean do_close; /* Set if hide was called before dialog_hwnd was set */
102   FilechooserWin32ThreadData *data;
103 } FileDialogEvents;
104 
105 
106 static ULONG STDMETHODCALLTYPE
ifiledialogevents_AddRef(IFileDialogEvents * self)107 ifiledialogevents_AddRef (IFileDialogEvents *self)
108 {
109   FileDialogEvents *events = (FileDialogEvents *)self;
110   ULONG ref_count;
111 
112   G_LOCK (FileDialogEvents);
113   ref_count = ++events->ref_count;
114   G_UNLOCK (FileDialogEvents);
115 
116   return ref_count;
117 }
118 
119 static ULONG STDMETHODCALLTYPE
ifiledialogevents_Release(IFileDialogEvents * self)120 ifiledialogevents_Release (IFileDialogEvents *self)
121 {
122   FileDialogEvents *events = (FileDialogEvents *)self;
123   int ref_count;
124 
125   G_LOCK (FileDialogEvents);
126   ref_count = --events->ref_count;
127   G_UNLOCK (FileDialogEvents);
128 
129   if (ref_count == 0)
130     g_free (self);
131 
132   return ref_count;
133 }
134 
135 static HRESULT STDMETHODCALLTYPE
ifiledialogevents_QueryInterface(IFileDialogEvents * self,REFIID riid,LPVOID * ppvObject)136 ifiledialogevents_QueryInterface (IFileDialogEvents *self,
137                                   REFIID       riid,
138                                   LPVOID      *ppvObject)
139 {
140   if (IsEqualIID (riid, &IID_IUnknown) ||
141       IsEqualIID (riid, &myIID_IFileDialogEvents))
142     {
143       *ppvObject = self;
144       IUnknown_AddRef ((IUnknown *)self);
145       return NOERROR;
146     }
147   else
148     {
149       *ppvObject = NULL;
150       return E_NOINTERFACE;
151     }
152 }
153 
154 static HRESULT STDMETHODCALLTYPE
ifiledialogevents_OnFileOk(IFileDialogEvents * self,IFileDialog * pfd)155 ifiledialogevents_OnFileOk (IFileDialogEvents *self,
156                             IFileDialog *pfd)
157 {
158   return S_OK;
159 }
160 
161 static HRESULT STDMETHODCALLTYPE
ifiledialogevents_OnFolderChanging(IFileDialogEvents * self,IFileDialog * pfd,IShellItem * psiFolder)162 ifiledialogevents_OnFolderChanging (IFileDialogEvents *self,
163                                     IFileDialog *pfd,
164                                     IShellItem *psiFolder)
165 {
166   return S_OK;
167 }
168 
169 static HRESULT STDMETHODCALLTYPE
ifiledialogevents_OnFolderChange(IFileDialogEvents * self,IFileDialog * pfd)170 ifiledialogevents_OnFolderChange (IFileDialogEvents *self,
171                                   IFileDialog *pfd)
172 {
173   FileDialogEvents *events = (FileDialogEvents *)self;
174   IOleWindow *olew = NULL;
175   HWND dialog_hwnd;
176   HRESULT hr;
177 
178   if (!events->got_hwnd)
179     {
180       events->got_hwnd = TRUE;
181 
182       hr = IFileDialog_QueryInterface (pfd, &IID_IOleWindow, (LPVOID *) &olew);
183       if (SUCCEEDED (hr))
184         {
185           hr = IOleWindow_GetWindow (olew, &dialog_hwnd);
186           if (SUCCEEDED (hr))
187             {
188               G_LOCK (FileDialogEvents);
189               events->dialog_hwnd = dialog_hwnd;
190               if (events->do_close)
191                 SendMessage (events->dialog_hwnd, WM_CLOSE, 0, 0);
192               G_UNLOCK (FileDialogEvents);
193             }
194           else
195             g_warning_hr ("Can't get HWND", hr);
196 
197           hr = IOleWindow_Release (olew);
198           if (FAILED (hr))
199             g_warning_hr ("Can't unref IOleWindow", hr);
200         }
201       else
202         g_warning_hr ("Can't get IOleWindow", hr);
203 
204       if (events->enable_owner && events->dialog_hwnd)
205         {
206           HWND owner = GetWindow (events->dialog_hwnd, GW_OWNER);
207           if (owner)
208             EnableWindow (owner, TRUE);
209         }
210     }
211 
212   return S_OK;
213 }
214 
215 static HRESULT STDMETHODCALLTYPE
ifiledialogevents_OnSelectionChange(IFileDialogEvents * self,IFileDialog * pfd)216 ifiledialogevents_OnSelectionChange (IFileDialogEvents * self,
217                                      IFileDialog *pfd)
218 {
219   return S_OK;
220 }
221 
222 static HRESULT STDMETHODCALLTYPE
ifiledialogevents_OnShareViolation(IFileDialogEvents * self,IFileDialog * pfd,IShellItem * psi,FDE_SHAREVIOLATION_RESPONSE * pResponse)223 ifiledialogevents_OnShareViolation (IFileDialogEvents * self,
224                                     IFileDialog *pfd,
225                                     IShellItem *psi,
226                                     FDE_SHAREVIOLATION_RESPONSE *pResponse)
227 {
228   return E_NOTIMPL;
229 }
230 
231 static HRESULT STDMETHODCALLTYPE
ifiledialogevents_OnTypeChange(IFileDialogEvents * self,IFileDialog * pfd)232 ifiledialogevents_OnTypeChange (IFileDialogEvents * self,
233                                 IFileDialog *pfd)
234 {
235   FileDialogEvents *events = (FileDialogEvents *) self;
236   UINT fileType;
237   HRESULT hr = IFileDialog_GetFileTypeIndex (pfd, &fileType);
238   if (FAILED (hr))
239     {
240       g_warning_hr ("Can't get current file type", hr);
241       return S_OK;
242     }
243   fileType--; // fileTypeIndex starts at 1
244   GListModel *filters = gtk_file_chooser_get_filters (GTK_FILE_CHOOSER (events->data->self));
245   GtkFileFilter *filter = g_list_model_get_item (filters, fileType);
246   events->data->self->current_filter = filter;
247   g_object_unref (filter);
248   g_object_unref (filters);
249   g_object_notify (G_OBJECT (events->data->self), "filter");
250   return S_OK;
251 }
252 
253 static HRESULT STDMETHODCALLTYPE
ifiledialogevents_OnOverwrite(IFileDialogEvents * self,IFileDialog * pfd,IShellItem * psi,FDE_OVERWRITE_RESPONSE * pResponse)254 ifiledialogevents_OnOverwrite (IFileDialogEvents * self,
255                                IFileDialog *pfd,
256                                IShellItem *psi,
257                                FDE_OVERWRITE_RESPONSE *pResponse)
258 {
259   return E_NOTIMPL;
260 }
261 
262 static IFileDialogEventsVtbl ifde_vtbl = {
263   ifiledialogevents_QueryInterface,
264   ifiledialogevents_AddRef,
265   ifiledialogevents_Release,
266   ifiledialogevents_OnFileOk,
267   ifiledialogevents_OnFolderChanging,
268   ifiledialogevents_OnFolderChange,
269   ifiledialogevents_OnSelectionChange,
270   ifiledialogevents_OnShareViolation,
271   ifiledialogevents_OnTypeChange,
272   ifiledialogevents_OnOverwrite
273 };
274 
275 static void
file_dialog_events_send_close(IFileDialogEvents * self)276 file_dialog_events_send_close (IFileDialogEvents *self)
277 {
278   FileDialogEvents *events = (FileDialogEvents *)self;
279 
280   G_LOCK (FileDialogEvents);
281 
282   if (events->dialog_hwnd)
283     SendMessage (events->dialog_hwnd, WM_CLOSE, 0, 0);
284   else
285     events->do_close = TRUE;
286 
287   G_UNLOCK (FileDialogEvents);
288 }
289 
290 static IFileDialogEvents *
file_dialog_events_new(gboolean enable_owner,FilechooserWin32ThreadData * data)291 file_dialog_events_new (gboolean enable_owner, FilechooserWin32ThreadData *data)
292 {
293   FileDialogEvents *events;
294 
295   events = g_new0 (FileDialogEvents, 1);
296   events->iFileDialogEvents.lpVtbl = &ifde_vtbl;
297   events->ref_count = 1;
298   events->enable_owner = enable_owner;
299   events->data = data;
300 
301   return &events->iFileDialogEvents;
302 }
303 
304 static void
filechooser_win32_thread_data_free(FilechooserWin32ThreadData * data)305 filechooser_win32_thread_data_free (FilechooserWin32ThreadData *data)
306 {
307   int i;
308   if (data->filters)
309     {
310       for (i = 0; data->filters[i].pszName != NULL; i++)
311         {
312           g_free ((char *)data->filters[i].pszName);
313           g_free ((char *)data->filters[i].pszSpec);
314         }
315       g_free (data->filters);
316     }
317 
318   if (data->events)
319     IFileDialogEvents_Release (data->events);
320 
321   g_clear_object (&data->current_folder);
322   g_clear_object (&data->current_file);
323   g_free (data->current_name);
324 
325   if (data->choices_selections)
326     {
327       g_array_free (data->choices_selections, TRUE);
328       data->choices_selections = NULL;
329     }
330   g_object_unref (data->shortcut_files);
331   g_slist_free_full (data->files, g_object_unref);
332   if (data->self)
333     g_object_unref (data->self);
334   g_free (data->accept_label);
335   g_free (data->cancel_label);
336   g_free (data->title);
337   g_free (data);
338 }
339 
340 static gboolean
filechooser_win32_thread_done(gpointer _data)341 filechooser_win32_thread_done (gpointer _data)
342 {
343   FilechooserWin32ThreadData *data = _data;
344   GtkFileChooserNative *self = data->self;
345   GSList *l;
346 
347   self->mode_data = NULL;
348 
349   for (l = self->choices; l; l = l->next)
350     {
351       GtkFileChooserNativeChoice *choice = (GtkFileChooserNativeChoice*) l->data;
352       int sel = g_array_index (data->choices_selections, int,
353                                 g_slist_position (self->choices, l));
354 
355       if (sel >= 0)
356         {
357           if (choice->options)
358             {
359               g_free (choice->selected);
360               choice->selected = g_strdup (choice->options[sel]);
361             }
362           else
363             {
364               g_free (choice->selected);
365               choice->selected = sel ? g_strdup ("true") : g_strdup ("false");
366             }
367         }
368     }
369 
370   if (!data->skip_response)
371     {
372       g_slist_free_full (self->custom_files, g_object_unref);
373       self->custom_files = g_slist_reverse(data->files);
374       data->files = NULL;
375 
376       _gtk_native_dialog_emit_response (GTK_NATIVE_DIALOG (data->self),
377                                         data->response);
378     }
379 
380   filechooser_win32_thread_data_free (data);
381 
382   return FALSE;
383 }
384 
385 static GFile *
get_file_for_shell_item(IShellItem * item)386 get_file_for_shell_item (IShellItem *item)
387 {
388   HRESULT hr;
389   PWSTR pathw = NULL;
390   char *path;
391   GFile *file;
392 
393   hr = IShellItem_GetDisplayName (item, SIGDN_FILESYSPATH, &pathw);
394   if (SUCCEEDED (hr))
395     {
396       path = g_utf16_to_utf8 (pathw, -1, NULL, NULL, NULL);
397       CoTaskMemFree (pathw);
398       if (path != NULL)
399         {
400           file = g_file_new_for_path (path);
401           g_free (path);
402           return file;
403        }
404     }
405 
406   /* TODO: also support URLs through SIGDN_URL, but Windows URLS are not
407    * RFC 3986 compliant and we'd need to convert them first.
408    */
409 
410   return NULL;
411 }
412 
413 static void
data_add_shell_item(FilechooserWin32ThreadData * data,IShellItem * item)414 data_add_shell_item (FilechooserWin32ThreadData *data,
415                      IShellItem *item)
416 {
417   GFile *file;
418 
419   file = get_file_for_shell_item (item);
420   if (file != NULL)
421     {
422       data->files = g_slist_prepend (data->files, file);
423       data->response = GTK_RESPONSE_ACCEPT;
424     }
425 }
426 
427 static IShellItem *
get_shell_item_for_uri(const char * uri)428 get_shell_item_for_uri (const char *uri)
429 {
430   IShellItem *item;
431   HRESULT hr;
432   gunichar2 *uri_w = g_utf8_to_utf16 (uri, -1, NULL, NULL, NULL);
433 
434   hr = SHCreateItemFromParsingName(uri_w, 0, &IID_IShellItem, (LPVOID *) &item);
435   if (SUCCEEDED (hr))
436     return item;
437   else
438     g_warning_hr ("Can't create shell item from shortcut", hr);
439 
440   return NULL;
441 }
442 
443 static IShellItem *
get_shell_item_for_file(GFile * file)444 get_shell_item_for_file (GFile *file)
445 {
446   char *uri;
447   IShellItem *item;
448 
449   uri = g_file_get_uri (file);
450   item = get_shell_item_for_uri (uri);
451   g_free (uri);
452 
453   return item;
454 }
455 
456 static gpointer
filechooser_win32_thread(gpointer _data)457 filechooser_win32_thread (gpointer _data)
458 {
459   FilechooserWin32ThreadData *data = _data;
460   HRESULT hr;
461   IFileDialog *pfd = NULL;
462   IFileDialog2 *pfd2 = NULL;
463   DWORD flags;
464   DWORD cookie;
465   guint j, n_items;
466 
467   CoInitializeEx (NULL, COINIT_APARTMENTTHREADED);
468 
469   if (data->save && !data->folder)
470     hr = CoCreateInstance (&CLSID_FileSaveDialog,
471                            NULL, CLSCTX_INPROC_SERVER,
472                            &IID_IFileSaveDialog, (LPVOID *) &pfd);
473   else
474     hr = CoCreateInstance (&CLSID_FileOpenDialog,
475                            NULL, CLSCTX_INPROC_SERVER,
476                            &IID_IFileOpenDialog, (LPVOID *) &pfd);
477 
478   if (FAILED (hr))
479     g_error ("Can't create FileOpenDialog: %s", g_win32_error_message (hr));
480 
481   hr = IFileDialog_GetOptions (pfd, &flags);
482   if (FAILED (hr))
483     g_error ("Can't get FileDialog options: %s", g_win32_error_message (hr));
484 
485   flags |= FOS_FORCEFILESYSTEM;
486 
487   if (data->folder)
488     flags |= FOS_PICKFOLDERS;
489 
490   if (data->folder && data->save)
491     flags &= ~(FOS_FILEMUSTEXIST);
492 
493   if (data->select_multiple)
494     flags |= FOS_ALLOWMULTISELECT;
495 
496   flags |= FOS_OVERWRITEPROMPT;
497 
498   hr = IFileDialog_SetOptions (pfd, flags);
499   if (FAILED (hr))
500     g_error ("Can't set FileDialog options: %s", g_win32_error_message (hr));
501 
502   if (data->title)
503     {
504       gunichar2 *label = g_utf8_to_utf16 (data->title, -1,
505                                         NULL, NULL, NULL);
506       IFileDialog_SetTitle (pfd, label);
507       g_free (label);
508     }
509 
510   if (data->accept_label)
511     {
512       gunichar2 *label = g_utf8_to_utf16 (data->accept_label, -1,
513                                         NULL, NULL, NULL);
514       IFileDialog_SetOkButtonLabel (pfd, label);
515       g_free (label);
516     }
517 
518   if (data->cancel_label)
519     {
520       gunichar2 *label = g_utf8_to_utf16 (data->cancel_label, -1,
521                                         NULL, NULL, NULL);
522       hr = IFileDialog_QueryInterface (pfd, &IID_IFileDialog2, (LPVOID *) &pfd2);
523       if (SUCCEEDED (hr))
524         {
525           IFileDialog2_SetCancelButtonLabel (pfd2, label);
526           IFileDialog2_Release (pfd2);
527         }
528       g_free (label);
529     }
530 
531   n_items = g_list_model_get_n_items (data->shortcut_files);
532   for (j = 0; j < n_items; j++)
533     {
534       GFile *file = g_list_model_get_item (data->shortcut_files, j);
535       IShellItem *item = get_shell_item_for_file (file);
536       if (item)
537         {
538           hr = IFileDialog_AddPlace (pfd, item, FDAP_BOTTOM);
539           if (FAILED (hr))
540             g_warning_hr ("Can't add dialog shortcut", hr);
541           IShellItem_Release (item);
542         }
543       g_object_unref (file);
544     }
545 
546   if (data->current_file)
547     {
548       IFileSaveDialog *pfsd;
549       hr = IFileDialog_QueryInterface (pfd, &IID_IFileSaveDialog, (LPVOID *) &pfsd);
550       if (SUCCEEDED (hr))
551         {
552           IShellItem *item = get_shell_item_for_file (data->current_file);
553           if (item)
554             {
555               hr = IFileSaveDialog_SetSaveAsItem (pfsd, item);
556               if (FAILED (hr))
557                 g_warning_hr ("Can't set save as item", hr);
558               IShellItem_Release (item);
559             }
560           IFileSaveDialog_Release (pfsd);
561         }
562     }
563 
564   if (data->current_folder)
565     {
566       IShellItem *item = get_shell_item_for_file (data->current_folder);
567       if (item)
568         {
569           hr = IFileDialog_SetFolder (pfd, item);
570           if (FAILED (hr))
571             g_warning_hr ("Can't set folder", hr);
572           IShellItem_Release (item);
573         }
574     }
575 
576   if (data->current_name)
577     {
578       gunichar2 *name = g_utf8_to_utf16 (data->current_name, -1, NULL, NULL, NULL);
579       hr = IFileDialog_SetFileName (pfd, name);
580       if (FAILED (hr))
581         g_warning_hr ("Can't set file name", hr);
582       g_free (name);
583     }
584 
585   if (data->filters)
586     {
587       int n;
588       for (n = 0; data->filters[n].pszName != NULL; n++)
589         {}
590       hr = IFileDialog_SetFileTypes (pfd, n, data->filters);
591       if (FAILED (hr))
592         g_warning_hr ("Can't set file types", hr);
593 
594       if (data->self->current_filter)
595         {
596           GListModel *filters;
597           guint current_filter_index = GTK_INVALID_LIST_POSITION;
598 
599           filters = gtk_file_chooser_get_filters (GTK_FILE_CHOOSER (data->self));
600           n_items = g_list_model_get_n_items (filters);
601           for (j = 0; j < n_items; j++)
602             {
603               gpointer item = g_list_model_get_item (filters, j);
604               if (item == data->self->current_filter)
605                 {
606                   current_filter_index = j;
607                   g_object_unref (item);
608                   break;
609                 }
610               g_object_unref (item);
611             }
612 	  g_object_unref (filters);
613 
614 	  if (current_filter_index >= 0)
615 	    hr = IFileDialog_SetFileTypeIndex (pfd, current_filter_index + 1);
616 	  else
617 	    hr = IFileDialog_SetFileTypeIndex (pfd, 1);
618         }
619       else
620         {
621 	  hr = IFileDialog_SetFileTypeIndex (pfd, 1);
622         }
623       if (FAILED (hr))
624         g_warning_hr ("Can't set current file type", hr);
625     }
626 
627   if (data->self->choices)
628     {
629       IFileDialogCustomize *pfdc;
630       DWORD dialog_control_id = 0;
631       DWORD dialog_auxiliary_id = (DWORD) g_slist_length (data->self->choices);
632 
633       hr = IFileDialog_QueryInterface (pfd, &IID_IFileDialogCustomize, (LPVOID *) &pfdc);
634       if (SUCCEEDED (hr))
635         {
636           GSList *l;
637 
638           for (l = data->self->choices; l; l = l->next, dialog_control_id++)
639             {
640               GtkFileChooserNativeChoice *choice = (GtkFileChooserNativeChoice*) l->data;
641 
642               if (choice->options)
643                 {
644                   gunichar2 *label = g_utf8_to_utf16 (choice->label, -1, NULL, NULL, NULL);
645                   DWORD sub_id = 0;
646                   char **option;
647 
648                   IFileDialogCustomize_StartVisualGroup (pfdc, dialog_auxiliary_id++, label);
649                   hr = IFileDialogCustomize_AddComboBox (pfdc, dialog_control_id);
650                   if (FAILED (hr))
651                     g_warning_hr ("Can't add choice", hr);
652                   IFileDialogCustomize_EndVisualGroup (pfdc);
653 
654                   for (option = choice->options; *option != NULL; option++, sub_id++)
655                     {
656                       gunichar2 *option_label = g_utf8_to_utf16 (choice->option_labels[sub_id],
657                                                                  -1, NULL, NULL, NULL);
658 
659                       hr = IFileDialogCustomize_AddControlItem (pfdc,
660                                                                 dialog_control_id,
661                                                                 sub_id,
662                                                                 (LPCWSTR) option_label);
663                       if (FAILED (hr))
664                         g_warning_hr ("Can't add choice option", hr);
665 
666                       if (choice->selected && g_str_equal (*option, choice->selected))
667                         IFileDialogCustomize_SetSelectedControlItem (pfdc,
668                                                                      dialog_control_id,
669                                                                      sub_id);
670 
671                       g_free (option_label);
672                     }
673 
674                   g_free (label);
675                 }
676               else
677                 {
678                   gunichar2 *label = g_utf8_to_utf16 (choice->label,
679                                                       -1, NULL, NULL, NULL);
680 
681                   hr = IFileDialogCustomize_AddCheckButton (pfdc,
682                                                             dialog_control_id,
683                                                             label,
684                                                             FALSE);
685                   if (FAILED (hr))
686                     g_warning_hr ("Can't add choice", hr);
687 
688                   if (choice->selected)
689                     IFileDialogCustomize_SetCheckButtonState (pfdc,
690                                                               dialog_control_id,
691                                                               g_str_equal (choice->selected, "true"));
692 
693                   g_free (label);
694                 }
695             }
696 
697           IFileDialogCustomize_Release (pfdc);
698         }
699     }
700 
701   data->response = GTK_RESPONSE_CANCEL;
702 
703   hr = IFileDialog_Advise (pfd, data->events, &cookie);
704   if (FAILED (hr))
705     g_error ("Can't Advise FileDialog: %s", g_win32_error_message (hr));
706 
707   hr = IFileDialog_Show (pfd, data->parent);
708   if (SUCCEEDED (hr))
709     {
710       IFileOpenDialog *pfod = NULL;
711       hr = IFileDialog_QueryInterface (pfd,&IID_IFileOpenDialog, (LPVOID *) &pfod);
712 
713       if (SUCCEEDED (hr))
714         {
715           IShellItemArray *res;
716           DWORD i, count;
717 
718           hr = IFileOpenDialog_GetResults (pfod, &res);
719           if (FAILED (hr))
720             g_error ("Can't get FileOpenDialog results: %s", g_win32_error_message (hr));
721 
722           hr = IShellItemArray_GetCount (res, &count);
723           if (FAILED (hr))
724             g_error ("Can't get FileOpenDialog count: %s", g_win32_error_message (hr));
725 
726           for (i = 0; i < count; i++)
727             {
728               IShellItem *item;
729               hr = IShellItemArray_GetItemAt (res, i, &item);
730               if (FAILED (hr))
731                 g_error ("Can't get item at %lu: %s", i, g_win32_error_message (hr));
732               data_add_shell_item (data, item);
733               IShellItem_Release (item);
734             }
735           IShellItemArray_Release (res);
736 
737           IFileOpenDialog_Release (pfod);
738         }
739       else
740         {
741           IShellItem *item;
742           hr = IFileDialog_GetResult (pfd, &item);
743           if (FAILED (hr))
744             g_error ("Can't get FileDialog result: %s", g_win32_error_message (hr));
745 
746           data_add_shell_item (data, item);
747           IShellItem_Release (item);
748         }
749     }
750 
751   if (data->self->choices)
752     {
753       IFileDialogCustomize *pfdc = NULL;
754 
755       if (data->choices_selections)
756         g_array_free (data->choices_selections, TRUE);
757       data->choices_selections = g_array_sized_new (FALSE, FALSE, sizeof(int),
758                                                     g_slist_length (data->self->choices));
759 
760       hr = IFileDialog_QueryInterface (pfd, &IID_IFileDialogCustomize, (LPVOID *) &pfdc);
761       if (SUCCEEDED (hr))
762         {
763           GSList *l;
764 
765           for (l = data->self->choices; l; l = l->next)
766             {
767               GtkFileChooserNativeChoice *choice = (GtkFileChooserNativeChoice*) l->data;
768               DWORD dialog_item_id = (DWORD) g_slist_position (data->self->choices, l);
769               int val = -1;
770 
771               if (choice->options)
772                 {
773                   DWORD dialog_sub_item_id = 0;
774 
775                   hr = IFileDialogCustomize_GetSelectedControlItem (pfdc,
776                                                                     dialog_item_id,
777                                                                     &dialog_sub_item_id);
778                   if (SUCCEEDED (hr))
779                     val = (int) dialog_sub_item_id;
780                 }
781               else
782                 {
783                   BOOL dialog_check_box_checked = FALSE;
784 
785                   hr = IFileDialogCustomize_GetCheckButtonState (pfdc,
786                                                                  dialog_item_id,
787                                                                  &dialog_check_box_checked);
788                   if (SUCCEEDED (hr))
789                     val = dialog_check_box_checked ? 1 : 0;
790                 }
791 
792               g_array_append_val (data->choices_selections, val);
793             }
794 
795           IFileDialogCustomize_Release (pfdc);
796         }
797     }
798 
799 
800   hr = IFileDialog_Unadvise (pfd, cookie);
801   if (FAILED (hr))
802     g_error ("Can't Unadvise FileDialog: %s", g_win32_error_message (hr));
803 
804   IFileDialog_Release ((IUnknown *)pfd);
805 
806   CoUninitialize();
807 
808   g_main_context_invoke (NULL,
809                          filechooser_win32_thread_done,
810                          data);
811 
812   return NULL;
813 }
814 
815 static gboolean
file_filter_to_win32(GtkFileFilter * filter,COMDLG_FILTERSPEC * spec)816 file_filter_to_win32 (GtkFileFilter *filter,
817                       COMDLG_FILTERSPEC *spec)
818 {
819   const char *name;
820   char **patterns;
821   char *pattern_list;
822 
823   patterns = _gtk_file_filter_get_as_patterns (filter);
824   if (patterns == NULL)
825     return FALSE;
826 
827   pattern_list = g_strjoinv (";", patterns);
828   g_strfreev (patterns);
829 
830   name = gtk_file_filter_get_name (filter);
831   if (name == NULL)
832     name = pattern_list;
833   spec->pszName = g_utf8_to_utf16 (name, -1, NULL, NULL, NULL);
834   spec->pszSpec = g_utf8_to_utf16 (pattern_list, -1, NULL, NULL, NULL);
835 
836   g_free (pattern_list);
837 
838   return TRUE;
839 }
840 
841 static char *
translate_mnemonics(const char * src)842 translate_mnemonics (const char *src)
843 {
844   GString *s;
845   const char *p;
846   char c;
847 
848   if (src == NULL)
849     return NULL;
850 
851   s = g_string_sized_new (strlen (src));
852 
853   for (p = src; *p; p++)
854     {
855       c = *p;
856       switch (c)
857         {
858         case '_':
859           /* __ is _ escaped */
860           if (*(p+1) == '_')
861             {
862               g_string_append_c (s, '_');
863               p++;
864             }
865           else
866             g_string_append_c (s, '&');
867           break;
868         case '&':
869           /* Win32 needs ampersands escaped */
870           g_string_append (s, "&&");
871           break;
872         default:
873           g_string_append_c (s, c);
874         }
875     }
876 
877   return g_string_free (s, FALSE);
878 }
879 
880 gboolean
gtk_file_chooser_native_win32_show(GtkFileChooserNative * self)881 gtk_file_chooser_native_win32_show (GtkFileChooserNative *self)
882 {
883   GThread *thread;
884   FilechooserWin32ThreadData *data;
885   GtkWindow *transient_for;
886   GtkFileChooserAction action;
887   GListModel *filters;
888   guint n_filters, i;
889 
890   data = g_new0 (FilechooserWin32ThreadData, 1);
891 
892   filters = gtk_file_chooser_get_filters (GTK_FILE_CHOOSER (self));
893   n_filters = g_list_model_get_n_items (filters);
894   if (n_filters > 0)
895     {
896       data->filters = g_new0 (COMDLG_FILTERSPEC, n_filters + 1);
897 
898       for (i = 0; i < n_filters; i++)
899         {
900           GtkFileFilter *filter = g_list_model_get_item (filters, i);
901           if (!file_filter_to_win32 (filter, &data->filters[i]))
902             {
903               g_object_unref (filter);
904               g_object_unref (filters);
905               filechooser_win32_thread_data_free (data);
906               return FALSE;
907             }
908           g_object_unref (filter);
909         }
910       self->current_filter = gtk_file_chooser_get_filter (GTK_FILE_CHOOSER (self));
911     }
912   else
913     {
914       self->current_filter = NULL;
915     }
916   g_object_unref (filters);
917 
918   self->mode_data = data;
919   data->self = g_object_ref (self);
920 
921   data->shortcut_files =
922     gtk_file_chooser_get_shortcut_folders (GTK_FILE_CHOOSER (self->dialog));
923 
924   data->accept_label = translate_mnemonics (self->accept_label);
925   data->cancel_label = translate_mnemonics (self->cancel_label);
926 
927   action = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (self->dialog));
928   if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
929     data->save = TRUE;
930 
931   if (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
932     data->folder = TRUE;
933 
934   if ((action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
935        action == GTK_FILE_CHOOSER_ACTION_OPEN) &&
936       gtk_file_chooser_get_select_multiple (GTK_FILE_CHOOSER (self->dialog)))
937     data->select_multiple = TRUE;
938 
939   transient_for = gtk_native_dialog_get_transient_for (GTK_NATIVE_DIALOG (self));
940   if (transient_for)
941     {
942       gtk_widget_realize (GTK_WIDGET (transient_for));
943       data->parent = gdk_win32_surface_get_handle (gtk_native_get_surface (GTK_NATIVE (transient_for)));
944 
945       if (gtk_native_dialog_get_modal (GTK_NATIVE_DIALOG (self)))
946         data->modal = TRUE;
947     }
948 
949   data->title =
950     g_strdup (gtk_native_dialog_get_title (GTK_NATIVE_DIALOG (self)));
951 
952   if (self->current_file)
953     data->current_file = g_object_ref (self->current_file);
954   else
955     {
956       if (self->current_folder)
957         data->current_folder = g_object_ref (self->current_folder);
958 
959       if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
960         data->current_name = g_strdup (self->current_name);
961     }
962 
963   data->events = file_dialog_events_new (!data->modal, data);
964 
965   thread = g_thread_new ("win32 filechooser", filechooser_win32_thread, data);
966   if (thread == NULL)
967     {
968       filechooser_win32_thread_data_free (data);
969       return FALSE;
970     }
971 
972   return TRUE;
973 }
974 
975 void
gtk_file_chooser_native_win32_hide(GtkFileChooserNative * self)976 gtk_file_chooser_native_win32_hide (GtkFileChooserNative *self)
977 {
978   FilechooserWin32ThreadData *data = self->mode_data;
979 
980   /* This is always set while dialog visible */
981   g_assert (data != NULL);
982 
983   data->skip_response = TRUE;
984   file_dialog_events_send_close (data->events);
985 }
986