3f0244b9b0d6d91e57f0ebfcb266243db8ad67cf
[claws.git] / src / gtk / w32_filesel.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2016 The Claws Mail Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #define UNICODE
25 #define _UNICODE
26
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <gdk/gdkwin32.h>
30 #include <pthread.h>
31
32 #include <windows.h>
33 #include <shlobj.h>
34
35 #include "claws.h"
36 #include "alertpanel.h"
37 #include "manage_window.h"
38 #include "utils.h"
39
40 static OPENFILENAME o;
41 static BROWSEINFO b;
42
43 /* Since running the native dialogs in the same thread stops GTK+
44  * loop from redrawing other windows on the background, we need
45  * to run the dialogs in a separate thread. */
46
47 /* TODO: There's a lot of code repeat in this file, it could be
48  * refactored to be neater. */
49
50 struct _WinChooserCtx {
51         void *data;
52         gboolean return_value;
53         PIDLIST_ABSOLUTE return_value_pidl;
54         gboolean done;
55 };
56
57 typedef struct _WinChooserCtx WinChooserCtx;
58
59 static void *threaded_GetOpenFileName(void *arg)
60 {
61         WinChooserCtx *ctx = (WinChooserCtx *)arg;
62
63         g_return_val_if_fail(ctx != NULL, NULL);
64         g_return_val_if_fail(ctx->data != NULL, NULL);
65
66         ctx->return_value = GetOpenFileName(ctx->data);
67         ctx->done = TRUE;
68
69         return NULL;
70 }
71
72 static void *threaded_GetSaveFileName(void *arg)
73 {
74         WinChooserCtx *ctx = (WinChooserCtx *)arg;
75
76         g_return_val_if_fail(ctx != NULL, NULL);
77         g_return_val_if_fail(ctx->data != NULL, NULL);
78
79         ctx->return_value = GetSaveFileName(ctx->data);
80         ctx->done = TRUE;
81
82         return NULL;
83 }
84
85 static void *threaded_SHBrowseForFolder(void *arg)
86 {
87         WinChooserCtx *ctx = (WinChooserCtx *)arg;
88
89         g_return_val_if_fail(ctx != NULL, NULL);
90         g_return_val_if_fail(ctx->data != NULL, NULL);
91
92         ctx->return_value_pidl = SHBrowseForFolder(ctx->data);
93
94         ctx->done = TRUE;
95
96         return NULL;
97 }
98
99 gchar *filesel_select_file_open(const gchar *title, const gchar *path)
100 {
101         gboolean ret;
102         gchar *str;
103         gunichar2 *path16, *title16;
104         glong conv_items;
105         GError *error = NULL;
106         WinChooserCtx *ctx;
107 #ifdef USE_PTHREAD
108         pthread_t pt;
109 #endif
110
111         /* Path needs to be converted to UTF-16, so that the native chooser
112          * can understand it. */
113         path16 = g_utf8_to_utf16(path, -1, NULL, &conv_items, &error);
114         if (error != NULL) {
115                 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
116                                 error->message);
117                 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
118                 g_error_free(error);
119                 return NULL;
120         }
121
122         /* Chooser dialog title needs to be UTF-16 as well. */
123         title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
124         if (error != NULL) {
125                 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
126                 g_error_free(error);
127         }
128
129         o.lStructSize = sizeof(OPENFILENAME);
130         if (focus_window != NULL)
131                 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
132         else
133                 o.hwndOwner = NULL;
134         o.hInstance = NULL;
135         o.lpstrFilter = NULL;
136         o.lpstrCustomFilter = NULL;
137         o.nFilterIndex = 0;
138         o.lpstrFile = g_malloc0(MAXPATHLEN);
139         o.nMaxFile = MAXPATHLEN;
140         o.lpstrFileTitle = NULL;
141         o.lpstrInitialDir = path16;
142         o.lpstrTitle = title16;
143         o.Flags = OFN_LONGNAMES;
144
145         ctx = g_new0(WinChooserCtx, 1);
146         ctx->data = &o;
147         ctx->done = FALSE;
148
149 #ifdef USE_PTHREAD
150         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetOpenFileName,
151                                 (void *)ctx) != 0) {
152                 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
153                 threaded_GetOpenFileName(ctx);
154         } else {
155                 while (!ctx->done) {
156                         claws_do_idle();
157                 }
158                 pthread_join(pt, NULL);
159         }
160         ret = ctx->return_value;
161 #else
162         debug_print("No threads available, continuing unthreaded.\n");
163         ret = GetOpenFileName(&o);
164 #endif
165
166         g_free(path16);
167         g_free(title16);
168         g_free(ctx);
169
170         if (!ret) {
171                 g_free(o.lpstrFile);
172                 return NULL;
173         }
174
175         /* Now convert the returned file path back from UTF-16. */
176         str = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, NULL, &error);
177         if (error != NULL) {
178                 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
179                                 error->message);
180                 debug_print("returned file path conversion to UTF-8 failed\n");
181                 g_error_free(error);
182         }
183
184         g_free(o.lpstrFile);
185         return str;
186 }
187
188 /* TODO: Allow selecting of multiple files with OFN_ALLOWMULTISELECT
189  * flag and parsing the long string with returned file names. */
190 GList *filesel_select_multiple_files_open(const gchar *title)
191 {
192         GList *file_list = NULL;
193         gchar *ret = filesel_select_file_open(title, NULL);
194
195         if (ret != NULL)
196                 file_list = g_list_append(file_list, ret);
197
198         return file_list;
199 }
200
201 gchar *filesel_select_file_open_with_filter(const gchar *title, const gchar *path,
202                               const gchar *filter)
203 {
204         gboolean ret;
205         gchar *win_filter = NULL, *str;
206         gunichar2 *path16, *title16, *win_filter16 = NULL;
207         glong conv_items;
208         GError *error = NULL;
209         WinChooserCtx *ctx;
210 #ifdef USE_PTHREAD
211         pthread_t pt;
212 #endif
213
214         /* Path needs to be converted to UTF-16, so that the native chooser
215          * can understand it. */
216         path16 = g_utf8_to_utf16(path, -1, NULL, &conv_items, &error);
217         if (error != NULL) {
218                 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
219                                 error->message);
220                 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
221                 g_error_free(error);
222                 return NULL;
223         }
224
225         /* Chooser dialog title needs to be UTF-16 as well. */
226         title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
227         if (error != NULL) {
228                 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
229                 g_error_free(error);
230         }
231
232         o.lStructSize = sizeof(OPENFILENAME);
233         if (focus_window != NULL)
234                 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
235         else
236                 o.hwndOwner = NULL;
237         o.lpstrFilter = NULL;
238         o.lpstrCustomFilter = NULL;
239         o.nFilterIndex = 0;
240         o.lpstrFile = g_malloc0(MAXPATHLEN);
241         o.nMaxFile = MAXPATHLEN;
242         o.lpstrFileTitle = NULL;
243         o.lpstrInitialDir = path16;
244         o.lpstrTitle = title16;
245         o.Flags = OFN_LONGNAMES;
246
247         if (filter != NULL && strlen(filter) > 0) {
248                 win_filter = g_strdup_printf("%s%c%s%c", filter, '\0', filter, '\0');
249                 win_filter16 = g_utf8_to_utf16(win_filter, -1, NULL, NULL, &error);
250                 g_free(win_filter);
251                 if (error != NULL) {
252                         debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
253                         g_error_free(error);
254                 }
255                 o.lpstrFilter = win_filter16;
256                 o.nFilterIndex = 1;
257         }
258
259         ctx = g_new0(WinChooserCtx, 1);
260         ctx->data = &o;
261         ctx->done = FALSE;
262
263 #ifdef USE_PTHREAD
264         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetOpenFileName,
265                                 (void *)ctx) != 0) {
266                 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
267                 threaded_GetOpenFileName(ctx);
268         } else {
269                 while (!ctx->done) {
270                         claws_do_idle();
271                 }
272                 pthread_join(pt, NULL);
273         }
274         ret = ctx->return_value;
275 #else
276         debug_print("No threads available, continuing unthreaded.\n");
277         ret = GetOpenFileName(&o);
278 #endif
279
280         g_free(win_filter16);
281         g_free(path16);
282         g_free(title16);
283         g_free(ctx);
284
285         if (!ret) {
286                 g_free(o.lpstrFile);
287                 return NULL;
288         }
289
290         /* Now convert the returned file path back from UTF-16. */
291         str = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, NULL, &error);
292         if (error != NULL) {
293                 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
294                                 error->message);
295                 debug_print("returned file path conversion to UTF-8 failed\n");
296                 g_error_free(error);
297         }
298
299         g_free(o.lpstrFile);
300         return str;
301 }
302
303 /* TODO: Allow selecting of multiple files with OFN_ALLOWMULTISELECT
304  * flag and parsing the long string with returned file names. */
305 GList *filesel_select_multiple_files_open_with_filter(const gchar *title,
306                 const gchar *path, const gchar *filter)
307 {
308         GList *file_list = NULL;
309         gchar *ret = filesel_select_file_open_with_filter(title, path, filter);
310
311         if (ret != NULL)
312                 file_list = g_list_append(file_list, ret);
313
314         return file_list;
315 }
316
317 gchar *filesel_select_file_save(const gchar *title, const gchar *path)
318 {
319         gboolean ret;
320         gchar *str;
321         gunichar2 *path16, *title16;
322         glong conv_items;
323         GError *error = NULL;
324         WinChooserCtx *ctx;
325 #ifdef USE_PTHREAD
326         pthread_t pt;
327 #endif
328
329         /* Path needs to be converted to UTF-16, so that the native chooser
330          * can understand it. */
331         path16 = g_utf8_to_utf16(path, -1, NULL, &conv_items, &error);
332         if (error != NULL) {
333                 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
334                                 error->message);
335                 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
336                 g_error_free(error);
337                 return NULL;
338         }
339
340         /* Chooser dialog title needs to be UTF-16 as well. */
341         title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
342         if (error != NULL) {
343                 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
344                 g_error_free(error);
345         }
346
347         o.lStructSize = sizeof(OPENFILENAME);
348         if (focus_window != NULL)
349                 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
350         else
351                 o.hwndOwner = NULL;
352         o.lpstrFilter = NULL;
353         o.lpstrCustomFilter = NULL;
354         o.lpstrFile = g_malloc0(MAXPATHLEN);
355         if (path16 != NULL)
356                 memcpy(o.lpstrFile, path16, conv_items * sizeof(gunichar2));
357         o.nMaxFile = MAXPATHLEN;
358         o.lpstrFileTitle = NULL;
359         o.lpstrInitialDir = path16;
360         o.lpstrTitle = title16;
361         o.Flags = OFN_LONGNAMES;
362
363         ctx = g_new0(WinChooserCtx, 1);
364         ctx->data = &o;
365         ctx->return_value = FALSE;
366         ctx->done = FALSE;
367
368 #ifdef USE_PTHREAD
369         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetSaveFileName,
370                                 (void *)ctx) != 0) {
371                 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
372                 threaded_GetSaveFileName(ctx);
373         } else {
374                 while (!ctx->done) {
375                         claws_do_idle();
376                 }
377                 pthread_join(pt, NULL);
378         }
379         ret = ctx->return_value;
380 #else
381         debug_print("No threads available, continuing unthreaded.\n");
382         ret = GetSaveFileName(&o);
383 #endif
384
385         g_free(path16);
386         g_free(title16);
387         g_free(ctx);
388
389         if (!ret) {
390                 g_free(o.lpstrFile);
391                 return NULL;
392         }
393
394         /* Now convert the returned file path back from UTF-16. */
395         str = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, NULL, &error);
396         if (error != NULL) {
397                 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
398                                 error->message);
399                 debug_print("returned file path conversion to UTF-8 failed\n");
400                 g_error_free(error);
401         }
402
403         g_free(o.lpstrFile);
404         return str;
405 }
406
407 gchar *filesel_select_file_open_folder(const gchar *title, const gchar *path)
408 {
409         PIDLIST_ABSOLUTE pidl;
410         gchar *str;
411         gunichar2 *path16, *title16;
412         glong conv_items;
413         PIDLIST_ABSOLUTE ppidl;
414         GError *error = NULL;
415         WinChooserCtx *ctx;
416 #ifdef USE_PTHREAD
417         pthread_t pt;
418 #endif
419
420         /* Path needs to be converted to UTF-16, so that the native chooser
421          * can understand it. */
422         path16 = g_utf8_to_utf16(path, -1, NULL, &conv_items, &error);
423         if (error != NULL) {
424                 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
425                                 error->message);
426                 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
427                 g_error_free(error);
428                 return NULL;
429         } else {
430                 /* Get a PIDL_ABSOLUTE for b.pidlRoot. */
431                 if (SHParseDisplayName(path16, NULL, &ppidl, 0, NULL) == S_OK) {
432                         b.pidlRoot = ppidl;
433                 }
434         }
435         g_free(path16);
436
437         /* Chooser dialog title needs to be UTF-16 as well. */
438         title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
439         if (error != NULL) {
440                 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
441                 g_error_free(error);
442         }
443
444         if (focus_window != NULL)
445                 b.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
446         else
447                 b.hwndOwner = NULL;
448         b.pszDisplayName = g_malloc(MAXPATHLEN);
449         b.lpszTitle = title16;
450         b.ulFlags = 0;
451         b.lpfn = NULL;
452
453         CoInitialize(NULL);
454
455         ctx = g_new0(WinChooserCtx, 1);
456         ctx->data = &b;
457         ctx->done = FALSE;
458
459 #ifdef USE_PTHREAD
460         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_SHBrowseForFolder,
461                                 (void *)ctx) != 0) {
462                 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
463                 threaded_SHBrowseForFolder(ctx);
464         } else {
465                 while (!ctx->done) {
466                         claws_do_idle();
467                 }
468                 pthread_join(pt, NULL);
469         }
470         pidl = ctx->return_value_pidl;
471 #else
472         debug_print("No threads available, continuing unthreaded.\n");
473         pidl = SHBrowseForFolder(&b);
474 #endif
475
476         if (pidl == NULL) {
477                 CoUninitialize();
478                 g_free(b.pszDisplayName);
479                 g_free(ctx);
480                 return NULL;
481         }
482
483         g_free(title16);
484
485         /* Now convert the returned file path back from UTF-16. */
486         /* Unfortunately, there is no field in BROWSEINFO struct to indicate
487          * actual length of string in pszDisplayName, so we have to assume
488          * the string is null-terminated. */
489         str = g_utf16_to_utf8(b.pszDisplayName, -1, NULL, NULL, &error);
490         if (error != NULL) {
491                 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
492                                 error->message);
493                 debug_print("returned file path conversion to UTF-8 failed\n");
494                 g_error_free(error);
495         }
496         g_free(b.pszDisplayName);
497
498         CoTaskMemFree(pidl);
499         CoUninitialize();
500         g_free(ctx);
501
502         return str;
503 }
504
505 gchar *filesel_select_file_save_folder(const gchar *title, const gchar *path)
506 {
507         return filesel_select_file_open_folder(title, path);
508 }