2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2016 The Claws Mail Team
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.
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.
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/>.
28 #include <glib/gi18n.h>
29 #include <gdk/gdkwin32.h>
36 #include "alertpanel.h"
37 #include "manage_window.h"
40 static OPENFILENAME o
;
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. */
47 /* TODO: There's a lot of code repeat in this file, it could be
48 * refactored to be neater. */
50 struct _WinChooserCtx
{
52 gboolean return_value
;
53 PIDLIST_ABSOLUTE return_value_pidl
;
57 typedef struct _WinChooserCtx WinChooserCtx
;
59 static void *threaded_GetOpenFileName(void *arg
)
61 WinChooserCtx
*ctx
= (WinChooserCtx
*)arg
;
63 g_return_val_if_fail(ctx
!= NULL
, NULL
);
64 g_return_val_if_fail(ctx
->data
!= NULL
, NULL
);
66 ctx
->return_value
= GetOpenFileName(ctx
->data
);
72 static void *threaded_GetSaveFileName(void *arg
)
74 WinChooserCtx
*ctx
= (WinChooserCtx
*)arg
;
76 g_return_val_if_fail(ctx
!= NULL
, NULL
);
77 g_return_val_if_fail(ctx
->data
!= NULL
, NULL
);
79 ctx
->return_value
= GetSaveFileName(ctx
->data
);
85 static void *threaded_SHBrowseForFolder(void *arg
)
87 WinChooserCtx
*ctx
= (WinChooserCtx
*)arg
;
89 g_return_val_if_fail(ctx
!= NULL
, NULL
);
90 g_return_val_if_fail(ctx
->data
!= NULL
, NULL
);
92 ctx
->return_value_pidl
= SHBrowseForFolder(ctx
->data
);
99 /* This function handles calling GetOpenFilename(), using
100 * global static variable o.
101 * It expects o.lpstrFile to point to an already allocated buffer,
102 * of size at least MAXPATHLEN. */
103 static const gboolean
_file_open_dialog(const gchar
*path
, const gchar
*title
,
104 const gchar
*filter
, const gboolean multi
)
107 gunichar2
*path16
= NULL
;
108 gunichar2
*title16
= NULL
;
109 gunichar2
*filter16
= NULL
;
110 gunichar2
*win_filter16
= NULL
;
111 glong conv_items
, sz
;
112 GError
*error
= NULL
;
118 /* Path needs to be converted to UTF-16, so that the native chooser
119 * can understand it. */
120 path16
= g_utf8_to_utf16(path
? path
: "",
121 -1, NULL
, NULL
, &error
);
123 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
125 debug_print("file path '%s' conversion to UTF-16 failed\n", path
);
131 /* Chooser dialog title needs to be UTF-16 as well. */
132 title16
= g_utf8_to_utf16(title
? title
: "",
133 -1, NULL
, NULL
, &error
);
135 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title
);
140 o
.lStructSize
= sizeof(OPENFILENAME
);
141 if (focus_window
!= NULL
)
142 o
.hwndOwner
= GDK_WINDOW_HWND(gtk_widget_get_window(focus_window
));
146 o
.lpstrFilter
= NULL
;
147 o
.lpstrCustomFilter
= NULL
;
149 o
.nMaxFile
= MAXPATHLEN
;
150 o
.lpstrFileTitle
= NULL
;
151 o
.lpstrInitialDir
= path16
;
152 o
.lpstrTitle
= title16
;
154 o
.Flags
= OFN_LONGNAMES
| OFN_EXPLORER
| OFN_ALLOWMULTISELECT
;
156 o
.Flags
= OFN_LONGNAMES
| OFN_EXPLORER
;
158 if (filter
!= NULL
&& strlen(filter
) > 0) {
159 debug_print("Setting filter '%s'\n", filter
);
160 filter16
= g_utf8_to_utf16(filter
, -1, NULL
, &conv_items
, &error
);
161 /* We're creating a UTF16 (2 bytes for each character) string:
162 * "filter\0filter\0\0"
163 * As g_utf8_to_utf16() will stop on first null byte, even if
164 * we pass string length in its second argument, we have to
165 * construct this string manually.
166 * conv_items contains number of UTF16 characters of our filter.
167 * Therefore we need enough bytes to store the filter string twice
168 * and three null chars. */
169 sz
= sizeof(gunichar2
);
170 win_filter16
= g_malloc0(conv_items
*sz
*2 + sz
*3);
171 memcpy(win_filter16
, filter16
, conv_items
*sz
);
172 memcpy(win_filter16
+ conv_items
+ 1, filter16
, conv_items
*sz
);
176 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title
);
180 o
.lpstrFilter
= (LPCTSTR
)win_filter16
;
184 ctx
= g_new0(WinChooserCtx
, 1);
189 if (pthread_create(&pt
, NULL
, threaded_GetOpenFileName
,
191 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
192 threaded_GetOpenFileName(ctx
);
197 pthread_join(pt
, NULL
);
199 ret
= ctx
->return_value
;
201 debug_print("No threads available, continuing unthreaded.\n");
202 ret
= GetOpenFileName(&o
);
205 g_free(win_filter16
);
206 if (path16
!= NULL
) {
215 gchar
*filesel_select_file_open_with_filter(const gchar
*title
, const gchar
*path
,
219 GError
*error
= NULL
;
221 o
.lpstrFile
= g_malloc0(MAXPATHLEN
);
222 if (!_file_open_dialog(path
, title
, filter
, FALSE
)) {
227 /* Now convert the returned file path back from UTF-16. */
228 str
= g_utf16_to_utf8(o
.lpstrFile
, o
.nMaxFile
, NULL
, NULL
, &error
);
230 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
232 debug_print("returned file path conversion to UTF-8 failed\n");
240 GList
*filesel_select_multiple_files_open_with_filter(const gchar
*title
,
241 const gchar
*path
, const gchar
*filter
)
243 GList
*file_list
= NULL
;
247 GError
*error
= NULL
;
250 o
.lpstrFile
= g_malloc0(MAXPATHLEN
);
251 if (!_file_open_dialog(path
, title
, filter
, TRUE
)) {
256 /* Now convert the returned directory and file names back from UTF-16.
257 * The content of o.lpstrFile is:
258 * "directory0file0file0...0file00" for multiple files selected,
259 * "fullfilepath0" for single file. */
260 str
= g_utf16_to_utf8(o
.lpstrFile
, -1, &items_read
, NULL
, &error
);
263 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
265 debug_print("returned file path conversion to UTF-8 failed\n");
270 /* The part before the first null char is always a full path. If it is
271 * a path to a file, then only this one file has been selected,
272 * and we can bail out early. */
273 if (g_file_test(str
, G_FILE_TEST_IS_REGULAR
)) {
274 debug_print("Selected one file: '%s'\n", str
);
275 file_list
= g_list_append(file_list
, g_strdup(str
));
280 /* So the path was to a directory. We need to parse more after
281 * the fist null char, until we get to two null chars in a row. */
284 debug_print("Selected multiple files in dir '%s'\n", dir
);
288 while (items_read
> 0) {
289 str
= g_utf16_to_utf8(f
, -1, &items_read
, NULL
, &error
);
291 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
293 debug_print("returned file path conversion to UTF-8 failed\n");
299 if (items_read
> 0) {
300 debug_print("selected file '%s'\n", str
);
301 file_list
= g_list_append(file_list
,
302 g_strconcat(dir
, G_DIR_SEPARATOR_S
, str
, NULL
));
314 gchar
*filesel_select_file_open(const gchar
*title
, const gchar
*path
)
316 return filesel_select_file_open_with_filter(title
, path
, NULL
);
319 GList
*filesel_select_multiple_files_open(const gchar
*title
, const gchar
*path
)
321 return filesel_select_multiple_files_open_with_filter(title
, path
, NULL
);
324 gchar
*filesel_select_file_save(const gchar
*title
, const gchar
*path
)
327 gchar
*str
, *filename
= NULL
;
328 gunichar2
*filename16
, *path16
, *title16
;
330 GError
*error
= NULL
;
336 /* Find the filename part, if any */
337 if (path
== NULL
|| path
[strlen(path
)-1] == G_DIR_SEPARATOR
) {
339 } else if ((filename
= strrchr(path
, G_DIR_SEPARATOR
)) != NULL
) {
342 filename
= (char *) path
;
345 /* Convert it to UTF-16. */
346 filename16
= g_utf8_to_utf16(filename
, -1, NULL
, &conv_items
, &error
);
348 alertpanel_error(_("Could not convert attachment name to UTF-16:\n\n%s"),
350 debug_print("filename '%s' conversion to UTF-16 failed\n", filename
);
355 /* Path needs to be converted to UTF-16, so that the native chooser
356 * can understand it. */
357 path16
= g_utf8_to_utf16(path
, -1, NULL
, NULL
, &error
);
359 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
361 debug_print("file path '%s' conversion to UTF-16 failed\n", path
);
367 /* Chooser dialog title needs to be UTF-16 as well. */
368 title16
= g_utf8_to_utf16(title
, -1, NULL
, NULL
, &error
);
370 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title
);
374 o
.lStructSize
= sizeof(OPENFILENAME
);
375 if (focus_window
!= NULL
)
376 o
.hwndOwner
= GDK_WINDOW_HWND(gtk_widget_get_window(focus_window
));
379 o
.lpstrFilter
= NULL
;
380 o
.lpstrCustomFilter
= NULL
;
381 o
.lpstrFile
= g_malloc0(MAXPATHLEN
);
383 memcpy(o
.lpstrFile
, filename16
, conv_items
* sizeof(gunichar2
));
384 o
.nMaxFile
= MAXPATHLEN
;
385 o
.lpstrFileTitle
= NULL
;
386 o
.lpstrInitialDir
= path16
;
387 o
.lpstrTitle
= title16
;
388 o
.Flags
= OFN_LONGNAMES
| OFN_EXPLORER
;
390 ctx
= g_new0(WinChooserCtx
, 1);
392 ctx
->return_value
= FALSE
;
396 if (pthread_create(&pt
, NULL
, threaded_GetSaveFileName
,
398 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
399 threaded_GetSaveFileName(ctx
);
404 pthread_join(pt
, NULL
);
406 ret
= ctx
->return_value
;
408 debug_print("No threads available, continuing unthreaded.\n");
409 ret
= GetSaveFileName(&o
);
422 /* Now convert the returned file path back from UTF-16. */
423 str
= g_utf16_to_utf8(o
.lpstrFile
, o
.nMaxFile
, NULL
, NULL
, &error
);
425 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
427 debug_print("returned file path conversion to UTF-8 failed\n");
435 /* This callback function is used to set the folder browse dialog
436 * selection from filesel_select_file_open_folder() to set
437 * chosen starting folder ("path" argument to that function. */
438 static int CALLBACK
_open_folder_callback(HWND hwnd
, UINT uMsg
,
439 LPARAM lParam
, LPARAM lpData
)
441 if (uMsg
!= BFFM_INITIALIZED
)
444 SendMessage(hwnd
, BFFM_SETSELECTION
, TRUE
, lpData
);
448 gchar
*filesel_select_file_open_folder(const gchar
*title
, const gchar
*path
)
450 PIDLIST_ABSOLUTE pidl
;
452 gunichar2
*path16
, *title16
;
454 GError
*error
= NULL
;
460 /* Path needs to be converted to UTF-16, so that the native chooser
461 * can understand it. */
462 path16
= g_utf8_to_utf16(path
? path
: "",
463 -1, NULL
, &conv_items
, &error
);
465 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
467 debug_print("file path '%s' conversion to UTF-16 failed\n", path
);
472 /* Chooser dialog title needs to be UTF-16 as well. */
473 title16
= g_utf8_to_utf16(title
? title
: "",
474 -1, NULL
, NULL
, &error
);
476 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title
);
480 if (focus_window
!= NULL
)
481 b
.hwndOwner
= GDK_WINDOW_HWND(gtk_widget_get_window(focus_window
));
484 b
.pszDisplayName
= g_malloc(MAXPATHLEN
);
485 b
.lpszTitle
= title16
;
488 b
.lpfn
= _open_folder_callback
;
489 b
.lParam
= (LPARAM
)path16
;
493 ctx
= g_new0(WinChooserCtx
, 1);
498 if (pthread_create(&pt
, NULL
, threaded_SHBrowseForFolder
,
500 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
501 threaded_SHBrowseForFolder(ctx
);
506 pthread_join(pt
, NULL
);
508 pidl
= ctx
->return_value_pidl
;
510 debug_print("No threads available, continuing unthreaded.\n");
511 pidl
= SHBrowseForFolder(&b
);
514 g_free(b
.pszDisplayName
);
524 path16
= malloc(MAX_PATH
);
525 if (!SHGetPathFromIDList(pidl
, path16
)) {
533 /* Now convert the returned file path back from UTF-16. */
534 /* Unfortunately, there is no field in BROWSEINFO struct to indicate
535 * actual length of string in pszDisplayName, so we have to assume
536 * the string is null-terminated. */
537 str
= g_utf16_to_utf8(path16
, -1, NULL
, NULL
, &error
);
539 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
541 debug_print("returned file path conversion to UTF-8 failed\n");
552 gchar
*filesel_select_file_save_folder(const gchar
*title
, const gchar
*path
)
554 return filesel_select_file_open_folder(title
, path
);