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*sz + sz, 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, PTHREAD_CREATE_JOINABLE, 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(title, path, 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;
249 o.lpstrFile = g_malloc0(MAXPATHLEN);
250 if (!_file_open_dialog(title, path, filter, TRUE)) {
255 /* Now convert the returned directory and file names back from UTF-16.
256 * The content of o.lpstrFile is:
257 * "directory\0file\0file\0...\0file\0\0" for multiple files selected,
258 * "fullfilepath\0" for single file. */
259 dir = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, NULL, &error);
261 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
263 debug_print("returned file path conversion to UTF-8 failed\n");
267 f = o.lpstrFile + g_utf8_strlen(dir, -1) + 1;
270 file = g_utf16_to_utf8(f, o.nMaxFile, NULL, NULL, &error);
272 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
274 debug_print("returned file path conversion to UTF-8 failed\n");
278 if (file == NULL || strlen(file) == 0) {
283 debug_print("Selected file '%s%c%s'\n",
284 dir, G_DIR_SEPARATOR, file);
285 file_list = g_list_append(file_list,
286 g_strconcat(dir, G_DIR_SEPARATOR_S, file, NULL));
288 f = f + g_utf8_strlen(file, -1) + 1;
292 if (file_list == NULL) {
293 debug_print("Selected single file '%s'\n", dir);
294 file_list = g_list_append(file_list, dir);
303 gchar *filesel_select_file_open(const gchar *title, const gchar *path)
305 return filesel_select_file_open_with_filter(title, path, NULL);
308 GList *filesel_select_multiple_files_open(const gchar *title, const gchar *path)
310 return filesel_select_multiple_files_open_with_filter(title, path, NULL);
313 gchar *filesel_select_file_save(const gchar *title, const gchar *path)
316 gchar *str, *filename = NULL;
317 gunichar2 *filename16, *path16, *title16;
319 GError *error = NULL;
325 /* Find the filename part, if any */
326 if (path == NULL || path[strlen(path)-1] == G_DIR_SEPARATOR) {
328 } else if ((filename = strrchr(path, G_DIR_SEPARATOR)) != NULL) {
331 filename = (char *) path;
334 /* Convert it to UTF-16. */
335 filename16 = g_utf8_to_utf16(filename, -1, NULL, &conv_items, &error);
337 alertpanel_error(_("Could not convert attachment name to UTF-16:\n\n%s"),
339 debug_print("filename '%s' conversion to UTF-16 failed\n", filename);
344 /* Path needs to be converted to UTF-16, so that the native chooser
345 * can understand it. */
346 path16 = g_utf8_to_utf16(path, -1, NULL, NULL, &error);
348 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
350 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
356 /* Chooser dialog title needs to be UTF-16 as well. */
357 title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
359 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
363 o.lStructSize = sizeof(OPENFILENAME);
364 if (focus_window != NULL)
365 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
368 o.lpstrFilter = NULL;
369 o.lpstrCustomFilter = NULL;
370 o.lpstrFile = g_malloc0(MAXPATHLEN);
372 memcpy(o.lpstrFile, filename16, conv_items * sizeof(gunichar2));
373 o.nMaxFile = MAXPATHLEN;
374 o.lpstrFileTitle = NULL;
375 o.lpstrInitialDir = path16;
376 o.lpstrTitle = title16;
377 o.Flags = OFN_LONGNAMES | OFN_EXPLORER;
379 ctx = g_new0(WinChooserCtx, 1);
381 ctx->return_value = FALSE;
385 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetSaveFileName,
387 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
388 threaded_GetSaveFileName(ctx);
393 pthread_join(pt, NULL);
395 ret = ctx->return_value;
397 debug_print("No threads available, continuing unthreaded.\n");
398 ret = GetSaveFileName(&o);
411 /* Now convert the returned file path back from UTF-16. */
412 str = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, NULL, &error);
414 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
416 debug_print("returned file path conversion to UTF-8 failed\n");
424 /* This callback function is used to set the folder browse dialog
425 * selection from filesel_select_file_open_folder() to set
426 * chosen starting folder ("path" argument to that function. */
427 static int CALLBACK _open_folder_callback(HWND hwnd, UINT uMsg,
428 LPARAM lParam, LPARAM lpData)
430 if (uMsg != BFFM_INITIALIZED)
433 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
437 gchar *filesel_select_file_open_folder(const gchar *title, const gchar *path)
439 PIDLIST_ABSOLUTE pidl;
441 gunichar2 *path16, *title16;
443 GError *error = NULL;
449 /* Path needs to be converted to UTF-16, so that the native chooser
450 * can understand it. */
451 path16 = g_utf8_to_utf16(path ? path : "",
452 -1, NULL, &conv_items, &error);
454 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
456 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
461 /* Chooser dialog title needs to be UTF-16 as well. */
462 title16 = g_utf8_to_utf16(title ? title : "",
463 -1, NULL, NULL, &error);
465 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
469 if (focus_window != NULL)
470 b.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
473 b.pszDisplayName = g_malloc(MAXPATHLEN);
474 b.lpszTitle = title16;
477 b.lpfn = _open_folder_callback;
478 b.lParam = (LPARAM)path16;
482 ctx = g_new0(WinChooserCtx, 1);
487 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_SHBrowseForFolder,
489 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
490 threaded_SHBrowseForFolder(ctx);
495 pthread_join(pt, NULL);
497 pidl = ctx->return_value_pidl;
499 debug_print("No threads available, continuing unthreaded.\n");
500 pidl = SHBrowseForFolder(&b);
503 g_free(b.pszDisplayName);
513 path16 = malloc(MAX_PATH);
514 if (!SHGetPathFromIDList(pidl, path16)) {
522 /* Now convert the returned file path back from UTF-16. */
523 /* Unfortunately, there is no field in BROWSEINFO struct to indicate
524 * actual length of string in pszDisplayName, so we have to assume
525 * the string is null-terminated. */
526 str = g_utf16_to_utf8(path16, -1, NULL, NULL, &error);
528 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
530 debug_print("returned file path conversion to UTF-8 failed\n");
541 gchar *filesel_select_file_save_folder(const gchar *title, const gchar *path)
543 return filesel_select_file_open_folder(title, path);