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