64ef243c14538f5b17fd1906052a81b562cd13c7
[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 | OFN_EXPLORER;
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 GList *filesel_select_multiple_files_open(const gchar *title)
189 {
190         GList *file_list = NULL;
191         gboolean ret;
192         gunichar2 *title16;
193         glong conv_items;
194         GError *error = NULL;
195         WinChooserCtx *ctx;
196 #ifdef USE_PTHREAD
197         pthread_t pt;
198 #endif
199
200         /* Chooser dialog title needs to be UTF-16 as well. */
201         title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
202         if (error != NULL) {
203                 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
204                 g_error_free(error);
205         }
206
207         o.lStructSize = sizeof(OPENFILENAME);
208         if (focus_window != NULL)
209                 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
210         else
211                 o.hwndOwner = NULL;
212         o.hInstance = NULL;
213         o.lpstrFilter = NULL;
214         o.lpstrCustomFilter = NULL;
215         o.nFilterIndex = 0;
216         o.lpstrFile = g_malloc0(MAXPATHLEN);
217         o.nMaxFile = MAXPATHLEN;
218         o.lpstrFileTitle = NULL;
219         o.lpstrTitle = title16;
220         o.Flags = OFN_LONGNAMES | OFN_EXPLORER | OFN_ALLOWMULTISELECT;
221
222         ctx = g_new0(WinChooserCtx, 1);
223         ctx->data = &o;
224         ctx->done = FALSE;
225
226 #ifdef USE_PTHREAD
227         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetOpenFileName,
228                                 (void *)ctx) != 0) {
229                 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
230                 threaded_GetOpenFileName(ctx);
231         } else {
232                 while (!ctx->done) {
233                         claws_do_idle();
234                 }
235                 pthread_join(pt, NULL);
236         }
237         ret = ctx->return_value;
238 #else
239         debug_print("No threads available, continuing unthreaded.\n");
240         ret = GetOpenFileName(&o);
241 #endif
242
243         g_free(title16);
244         g_free(ctx);
245
246         if (!ret) {
247                 g_free(o.lpstrFile);
248                 return NULL;
249         }
250
251         /* Now convert the returned file path back from UTF-16. */
252         gchar *dir = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, &conv_items, &error);
253         if (error != NULL) {
254                 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
255                                 error->message);
256                 debug_print("returned file path conversion to UTF-8 failed\n");
257                 g_error_free(error);
258         }
259
260         gunichar2 *f = o.lpstrFile + g_utf8_strlen(dir, -1) + 1;
261
262         do {
263                 gchar *file = g_utf16_to_utf8(f, o.nMaxFile, NULL, &conv_items, &error);
264                 if (error != NULL) {
265                         alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
266                                         error->message);
267                         debug_print("returned file path conversion to UTF-8 failed\n");
268                         g_error_free(error);
269                 }
270
271                 if (file == NULL || strlen(file) == 0) {
272                         g_free(file);
273                         break;
274                 }
275
276                 debug_print("Selected file '%s%c%s'\n",
277                                 dir, G_DIR_SEPARATOR, file);
278                 file_list = g_list_append(file_list,
279                                 g_strconcat(dir, G_DIR_SEPARATOR_S, file, NULL));
280
281                 f = f + g_utf8_strlen(file, -1) + 1;
282                 g_free(file);
283         } while (TRUE);
284
285         if (file_list == NULL) {
286                 debug_print("Selected single file '%s'\n", dir);
287                 file_list = g_list_append(file_list, dir);
288         } else {
289                 g_free(dir);
290         }
291         g_free(o.lpstrFile);
292
293         return file_list;
294 }
295
296 gchar *filesel_select_file_open_with_filter(const gchar *title, const gchar *path,
297                               const gchar *filter)
298 {
299         gboolean ret;
300         gchar *str;
301         gchar *win_filter16 = NULL;
302         gunichar2 *path16, *title16, *filter16;
303         glong conv_items;
304         guint sz;
305         GError *error = NULL;
306         WinChooserCtx *ctx;
307 #ifdef USE_PTHREAD
308         pthread_t pt;
309 #endif
310
311         /* Path needs to be converted to UTF-16, so that the native chooser
312          * can understand it. */
313         path16 = g_utf8_to_utf16(path, -1, NULL, NULL, &error);
314         if (error != NULL) {
315                 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
316                                 error->message);
317                 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
318                 g_error_free(error);
319                 return NULL;
320         }
321
322         /* Chooser dialog title needs to be UTF-16 as well. */
323         title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
324         if (error != NULL) {
325                 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
326                 g_error_free(error);
327         }
328
329         o.lStructSize = sizeof(OPENFILENAME);
330         if (focus_window != NULL)
331                 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
332         else
333                 o.hwndOwner = NULL;
334         o.lpstrFilter = NULL;
335         o.lpstrCustomFilter = NULL;
336         o.nFilterIndex = 0;
337         o.lpstrFile = g_malloc0(MAXPATHLEN);
338         o.nMaxFile = MAXPATHLEN;
339         o.lpstrFileTitle = NULL;
340         o.lpstrInitialDir = path16;
341         o.lpstrTitle = title16;
342         o.Flags = OFN_LONGNAMES | OFN_EXPLORER;
343
344         if (filter != NULL && strlen(filter) > 0) {
345                 debug_print("Setting filter '%s'\n", filter);
346                 filter16 = g_utf8_to_utf16(filter, -1, NULL, &conv_items, &error);
347                 /* We're creating a UTF16 (2 bytes for each character) string:
348                  * "filter\0filter\0\0"
349                  * As g_utf8_to_utf16() will stop on first null byte, even if
350                  * we pass string length in its second argument, we have to
351                  * construct this string manually.
352                  * conv_items contains number of UTF16 characters of our filter.
353                  * Therefore we need enough bytes to store the filter string twice
354                  * and three null chars. */
355                 sz = sizeof(gunichar2);
356                 win_filter16 = g_malloc0(conv_items*sz*2 + sz*3);
357                 memcpy(win_filter16, filter16, conv_items*sz);
358                 memcpy(win_filter16 + conv_items*sz + sz, filter16, conv_items*sz);
359                 g_free(filter16);
360
361                 if (error != NULL) {
362                         debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
363                         g_error_free(error);
364                 }
365                 o.lpstrFilter = (LPCTSTR)win_filter16;
366                 o.nFilterIndex = 1;
367         }
368
369         ctx = g_new0(WinChooserCtx, 1);
370         ctx->data = &o;
371         ctx->done = FALSE;
372
373 #ifdef USE_PTHREAD
374         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetOpenFileName,
375                                 (void *)ctx) != 0) {
376                 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
377                 threaded_GetOpenFileName(ctx);
378         } else {
379                 while (!ctx->done) {
380                         claws_do_idle();
381                 }
382                 pthread_join(pt, NULL);
383         }
384         ret = ctx->return_value;
385 #else
386         debug_print("No threads available, continuing unthreaded.\n");
387         ret = GetOpenFileName(&o);
388 #endif
389
390         g_free(win_filter16);
391         g_free(path16);
392         g_free(title16);
393         g_free(ctx);
394
395         if (!ret) {
396                 g_free(o.lpstrFile);
397                 return NULL;
398         }
399
400         /* Now convert the returned file path back from UTF-16. */
401         str = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, NULL, &error);
402         if (error != NULL) {
403                 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
404                                 error->message);
405                 debug_print("returned file path conversion to UTF-8 failed\n");
406                 g_error_free(error);
407         }
408
409         g_free(o.lpstrFile);
410         return str;
411 }
412
413 GList *filesel_select_multiple_files_open_with_filter(const gchar *title,
414                 const gchar *path, const gchar *filter)
415 {
416         GList *file_list = NULL;
417         gboolean ret;
418         gchar *win_filter16 = NULL;
419         gunichar2 *path16, *title16, *filter16;
420         glong conv_items;
421         guint sz;
422         GError *error = NULL;
423         WinChooserCtx *ctx;
424 #ifdef USE_PTHREAD
425         pthread_t pt;
426 #endif
427
428         /* Path needs to be converted to UTF-16, so that the native chooser
429          * can understand it. */
430         path16 = g_utf8_to_utf16(path, -1, NULL, NULL, &error);
431         if (error != NULL) {
432                 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
433                                 error->message);
434                 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
435                 g_error_free(error);
436                 return NULL;
437         }
438
439         /* Chooser dialog title needs to be UTF-16 as well. */
440         title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
441         if (error != NULL) {
442                 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
443                 g_error_free(error);
444         }
445
446         o.lStructSize = sizeof(OPENFILENAME);
447         if (focus_window != NULL)
448                 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
449         else
450                 o.hwndOwner = NULL;
451         o.lpstrFilter = NULL;
452         o.lpstrCustomFilter = NULL;
453         o.nFilterIndex = 0;
454         o.lpstrFile = g_malloc0(MAXPATHLEN);
455         o.nMaxFile = MAXPATHLEN;
456         o.lpstrFileTitle = NULL;
457         o.lpstrInitialDir = path16;
458         o.lpstrTitle = title16;
459         o.Flags = OFN_LONGNAMES | OFN_EXPLORER | OFN_ALLOWMULTISELECT;
460
461         if (filter != NULL && strlen(filter) > 0) {
462                 debug_print("Setting filter '%s'\n", filter);
463                 filter16 = g_utf8_to_utf16(filter, -1, NULL, &conv_items, &error);
464                 /* We're creating a UTF16 (2 bytes for each character) string:
465                  * "filter\0filter\0\0"
466                  * As g_utf8_to_utf16() will stop on first null byte, even if
467                  * we pass string length in its second argument, we have to
468                  * construct this string manually.
469                  * conv_items contains number of UTF16 characters of our filter.
470                  * Therefore we need enough bytes to store the filter string twice
471                  * and three null chars. */
472                 sz = sizeof(gunichar2);
473                 win_filter16 = g_malloc0(conv_items*sz*2 + sz*3);
474                 memcpy(win_filter16, filter16, conv_items*sz);
475                 memcpy(win_filter16 + conv_items*sz + sz, filter16, conv_items*sz);
476                 g_free(filter16);
477
478                 if (error != NULL) {
479                         debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
480                         g_error_free(error);
481                 }
482                 o.lpstrFilter = (LPCTSTR)win_filter16;
483                 o.nFilterIndex = 1;
484         }
485
486         ctx = g_new0(WinChooserCtx, 1);
487         ctx->data = &o;
488         ctx->done = FALSE;
489
490 #ifdef USE_PTHREAD
491         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetOpenFileName,
492                                 (void *)ctx) != 0) {
493                 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
494                 threaded_GetOpenFileName(ctx);
495         } else {
496                 while (!ctx->done) {
497                         claws_do_idle();
498                 }
499                 pthread_join(pt, NULL);
500         }
501         ret = ctx->return_value;
502 #else
503         debug_print("No threads available, continuing unthreaded.\n");
504         ret = GetOpenFileName(&o);
505 #endif
506
507         g_free(win_filter16);
508         g_free(path16);
509         g_free(title16);
510         g_free(ctx);
511
512         if (!ret) {
513                 g_free(o.lpstrFile);
514                 return NULL;
515         }
516
517         /* Now convert the returned file path back from UTF-16. */
518         gchar *dir = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, &conv_items, &error);
519         if (error != NULL) {
520                 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
521                                 error->message);
522                 debug_print("returned file path conversion to UTF-8 failed\n");
523                 g_error_free(error);
524         }
525
526         gunichar2 *f = o.lpstrFile + g_utf8_strlen(dir, -1) + 1;
527
528         do {
529                 gchar *file = g_utf16_to_utf8(f, o.nMaxFile, NULL, &conv_items, &error);
530                 if (error != NULL) {
531                         alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
532                                         error->message);
533                         debug_print("returned file path conversion to UTF-8 failed\n");
534                         g_error_free(error);
535                 }
536
537                 if (file == NULL || strlen(file) == 0) {
538                         g_free(file);
539                         break;
540                 }
541
542                 debug_print("Selected file '%s%c%s'\n",
543                                 dir, G_DIR_SEPARATOR, file);
544                 file_list = g_list_append(file_list,
545                                 g_strconcat(dir, G_DIR_SEPARATOR_S, file, NULL));
546
547                 f = f + g_utf8_strlen(file, -1) + 1;
548                 g_free(file);
549         } while (TRUE);
550
551         if (file_list == NULL) {
552                 debug_print("Selected single file '%s'\n", dir);
553                 file_list = g_list_append(file_list, dir);
554         } else {
555                 g_free(dir);
556         }
557         g_free(o.lpstrFile);
558
559         return file_list;
560 }
561
562 gchar *filesel_select_file_save(const gchar *title, const gchar *path)
563 {
564         gboolean ret;
565         gchar *str, *filename = NULL;
566         gunichar2 *filename16, *path16, *title16;
567         glong conv_items;
568         GError *error = NULL;
569         WinChooserCtx *ctx;
570 #ifdef USE_PTHREAD
571         pthread_t pt;
572 #endif
573
574         /* Find the filename part, if any */
575         if (path[strlen(path)-1] == G_DIR_SEPARATOR) {
576                 filename = "";
577         } else if ((filename = strrchr(path, G_DIR_SEPARATOR)) != NULL) {
578                 filename++;
579         } else {
580                 filename = (char *) path;
581         }
582
583         /* Convert it to UTF-16. */
584         filename16 = g_utf8_to_utf16(filename, -1, NULL, &conv_items, &error);
585         if (error != NULL) {
586                 alertpanel_error(_("Could not convert attachment name to UTF-16:\n\n%s"),
587                                 error->message);
588                 debug_print("filename '%s' conversion to UTF-16 failed\n", filename);
589                 g_error_free(error);
590                 return NULL;
591         }
592
593         /* Path needs to be converted to UTF-16, so that the native chooser
594          * can understand it. */
595         path16 = g_utf8_to_utf16(path, -1, NULL, NULL, &error);
596         if (error != NULL) {
597                 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
598                                 error->message);
599                 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
600                 g_error_free(error);
601                 g_free(filename16);
602                 return NULL;
603         }
604
605         /* Chooser dialog title needs to be UTF-16 as well. */
606         title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
607         if (error != NULL) {
608                 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
609                 g_error_free(error);
610         }
611
612         o.lStructSize = sizeof(OPENFILENAME);
613         if (focus_window != NULL)
614                 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
615         else
616                 o.hwndOwner = NULL;
617         o.lpstrFilter = NULL;
618         o.lpstrCustomFilter = NULL;
619         o.lpstrFile = g_malloc0(MAXPATHLEN);
620         if (path16 != NULL)
621                 memcpy(o.lpstrFile, filename16, conv_items * sizeof(gunichar2));
622         o.nMaxFile = MAXPATHLEN;
623         o.lpstrFileTitle = NULL;
624         o.lpstrInitialDir = path16;
625         o.lpstrTitle = title16;
626         o.Flags = OFN_LONGNAMES | OFN_EXPLORER;
627
628         ctx = g_new0(WinChooserCtx, 1);
629         ctx->data = &o;
630         ctx->return_value = FALSE;
631         ctx->done = FALSE;
632
633 #ifdef USE_PTHREAD
634         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetSaveFileName,
635                                 (void *)ctx) != 0) {
636                 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
637                 threaded_GetSaveFileName(ctx);
638         } else {
639                 while (!ctx->done) {
640                         claws_do_idle();
641                 }
642                 pthread_join(pt, NULL);
643         }
644         ret = ctx->return_value;
645 #else
646         debug_print("No threads available, continuing unthreaded.\n");
647         ret = GetSaveFileName(&o);
648 #endif
649
650         g_free(filename16);
651         g_free(path16);
652         g_free(title16);
653         g_free(ctx);
654
655         if (!ret) {
656                 g_free(o.lpstrFile);
657                 return NULL;
658         }
659
660         /* Now convert the returned file path back from UTF-16. */
661         str = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, NULL, &error);
662         if (error != NULL) {
663                 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
664                                 error->message);
665                 debug_print("returned file path conversion to UTF-8 failed\n");
666                 g_error_free(error);
667         }
668
669         g_free(o.lpstrFile);
670         return str;
671 }
672
673 /* This callback function is used to set the folder browse dialog
674  * selection from filesel_select_file_open_folder() to set
675  * chosen starting folder ("path" argument to that function. */
676 static int CALLBACK _open_folder_callback(HWND hwnd, UINT uMsg,
677                 LPARAM lParam, LPARAM lpData)
678 {
679         if (uMsg != BFFM_INITIALIZED)
680                 return 0;
681
682         SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
683         return 0;
684 }
685
686 gchar *filesel_select_file_open_folder(const gchar *title, const gchar *path)
687 {
688         PIDLIST_ABSOLUTE pidl;
689         gchar *str;
690         gunichar2 *path16, *title16;
691         glong conv_items;
692         GError *error = NULL;
693         WinChooserCtx *ctx;
694 #ifdef USE_PTHREAD
695         pthread_t pt;
696 #endif
697
698         /* Path needs to be converted to UTF-16, so that the native chooser
699          * can understand it. */
700         path16 = g_utf8_to_utf16(path, -1, NULL, &conv_items, &error);
701         if (error != NULL) {
702                 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
703                                 error->message);
704                 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
705                 g_error_free(error);
706                 return NULL;
707         }
708
709         /* Chooser dialog title needs to be UTF-16 as well. */
710         title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
711         if (error != NULL) {
712                 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
713                 g_error_free(error);
714         }
715
716         if (focus_window != NULL)
717                 b.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
718         else
719                 b.hwndOwner = NULL;
720         b.pszDisplayName = g_malloc(MAXPATHLEN);
721         b.lpszTitle = title16;
722         b.ulFlags = 0;
723         b.pidlRoot = NULL;
724         b.lpfn = _open_folder_callback;
725         b.lParam = (LPARAM)path16;
726
727         CoInitialize(NULL);
728
729         ctx = g_new0(WinChooserCtx, 1);
730         ctx->data = &b;
731         ctx->done = FALSE;
732
733 #ifdef USE_PTHREAD
734         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_SHBrowseForFolder,
735                                 (void *)ctx) != 0) {
736                 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
737                 threaded_SHBrowseForFolder(ctx);
738         } else {
739                 while (!ctx->done) {
740                         claws_do_idle();
741                 }
742                 pthread_join(pt, NULL);
743         }
744         pidl = ctx->return_value_pidl;
745 #else
746         debug_print("No threads available, continuing unthreaded.\n");
747         pidl = SHBrowseForFolder(&b);
748 #endif
749
750         g_free(b.pszDisplayName);
751         g_free(title16);
752         g_free(path16);
753
754         if (pidl == NULL) {
755                 CoUninitialize();
756                 g_free(ctx);
757                 return NULL;
758         }
759
760         path16 = malloc(MAX_PATH);
761         if (!SHGetPathFromIDList(pidl, path16)) {
762                 CoTaskMemFree(pidl);
763                 CoUninitialize();
764                 g_free(path16);
765                 g_free(ctx);
766                 return NULL;
767         }
768
769         /* Now convert the returned file path back from UTF-16. */
770         /* Unfortunately, there is no field in BROWSEINFO struct to indicate
771          * actual length of string in pszDisplayName, so we have to assume
772          * the string is null-terminated. */
773         str = g_utf16_to_utf8(path16, -1, NULL, NULL, &error);
774         if (error != NULL) {
775                 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
776                                 error->message);
777                 debug_print("returned file path conversion to UTF-8 failed\n");
778                 g_error_free(error);
779         }
780         CoTaskMemFree(pidl);
781         CoUninitialize();
782         g_free(ctx);
783         g_free(path16);
784
785         return str;
786 }
787
788 gchar *filesel_select_file_save_folder(const gchar *title, const gchar *path)
789 {
790         return filesel_select_file_open_folder(title, path);
791 }