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 gchar *filesel_select_file_open(const gchar *title, const gchar *path)
103 gunichar2 *path16, *title16;
105 GError *error = NULL;
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);
115 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
117 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
122 /* Chooser dialog title needs to be UTF-16 as well. */
123 title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
125 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
129 o.lStructSize = sizeof(OPENFILENAME);
130 if (focus_window != NULL)
131 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
135 o.lpstrFilter = NULL;
136 o.lpstrCustomFilter = NULL;
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;
145 ctx = g_new0(WinChooserCtx, 1);
150 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetOpenFileName,
152 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
153 threaded_GetOpenFileName(ctx);
158 pthread_join(pt, NULL);
160 ret = ctx->return_value;
162 debug_print("No threads available, continuing unthreaded.\n");
163 ret = GetOpenFileName(&o);
175 /* Now convert the returned file path back from UTF-16. */
176 str = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, NULL, &error);
178 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
180 debug_print("returned file path conversion to UTF-8 failed\n");
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)
192 GList *file_list = NULL;
193 gchar *ret = filesel_select_file_open(title, NULL);
196 file_list = g_list_append(file_list, ret);
201 gchar *filesel_select_file_open_with_filter(const gchar *title, const gchar *path,
206 gchar *win_filter16 = NULL;
207 gunichar2 *path16, *title16, *filter16;
210 GError *error = NULL;
216 /* Path needs to be converted to UTF-16, so that the native chooser
217 * can understand it. */
218 path16 = g_utf8_to_utf16(path, -1, NULL, NULL, &error);
220 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
222 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
227 /* Chooser dialog title needs to be UTF-16 as well. */
228 title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
230 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
234 o.lStructSize = sizeof(OPENFILENAME);
235 if (focus_window != NULL)
236 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
239 o.lpstrFilter = NULL;
240 o.lpstrCustomFilter = NULL;
242 o.lpstrFile = g_malloc0(MAXPATHLEN);
243 o.nMaxFile = MAXPATHLEN;
244 o.lpstrFileTitle = NULL;
245 o.lpstrInitialDir = path16;
246 o.lpstrTitle = title16;
247 o.Flags = OFN_LONGNAMES;
249 if (filter != NULL && strlen(filter) > 0) {
250 debug_print("Setting filter '%s'\n", filter);
251 filter16 = g_utf8_to_utf16(filter, -1, NULL, &conv_items, &error);
252 /* We're creating a UTF16 (2 bytes for each character) string:
253 * "filter\0filter\0\0"
254 * As g_utf8_to_utf16() will stop on first null byte, even if
255 * we pass string length in its second argument, we have to
256 * construct this string manually.
257 * conv_items contains number of UTF16 characters of our filter.
258 * Therefore we need enough bytes to store the filter string twice
259 * and three null chars. */
260 sz = sizeof(gunichar2);
261 win_filter16 = g_malloc0(conv_items*sz*2 + sz*3);
262 memcpy(win_filter16, filter16, conv_items*sz);
263 memcpy(win_filter16 + conv_items*sz + sz, filter16, conv_items*sz);
267 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
270 o.lpstrFilter = (LPCTSTR)win_filter16;
274 ctx = g_new0(WinChooserCtx, 1);
279 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetOpenFileName,
281 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
282 threaded_GetOpenFileName(ctx);
287 pthread_join(pt, NULL);
289 ret = ctx->return_value;
291 debug_print("No threads available, continuing unthreaded.\n");
292 ret = GetOpenFileName(&o);
295 g_free(win_filter16);
305 /* Now convert the returned file path back from UTF-16. */
306 str = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, NULL, &error);
308 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
310 debug_print("returned file path conversion to UTF-8 failed\n");
318 /* TODO: Allow selecting of multiple files with OFN_ALLOWMULTISELECT
319 * flag and parsing the long string with returned file names. */
320 GList *filesel_select_multiple_files_open_with_filter(const gchar *title,
321 const gchar *path, const gchar *filter)
323 GList *file_list = NULL;
324 gchar *ret = filesel_select_file_open_with_filter(title, path, filter);
327 file_list = g_list_append(file_list, ret);
332 gchar *filesel_select_file_save(const gchar *title, const gchar *path)
335 gchar *str, *filename = NULL;
336 gunichar2 *filename16, *path16, *title16;
338 GError *error = NULL;
344 /* Find the filename part, if any */
345 if (path[strlen(path)-1] == G_DIR_SEPARATOR) {
347 } else if ((filename = strrchr(path, G_DIR_SEPARATOR)) != NULL) {
350 filename = (char *) path;
353 /* Convert it to UTF-16. */
354 filename16 = g_utf8_to_utf16(filename, -1, NULL, &conv_items, &error);
356 alertpanel_error(_("Could not convert attachment name to UTF-16:\n\n%s"),
358 debug_print("filename '%s' conversion to UTF-16 failed\n", filename);
363 /* Path needs to be converted to UTF-16, so that the native chooser
364 * can understand it. */
365 path16 = g_utf8_to_utf16(path, -1, NULL, NULL, &error);
367 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
369 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
375 /* Chooser dialog title needs to be UTF-16 as well. */
376 title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
378 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
382 o.lStructSize = sizeof(OPENFILENAME);
383 if (focus_window != NULL)
384 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
387 o.lpstrFilter = NULL;
388 o.lpstrCustomFilter = NULL;
389 o.lpstrFile = g_malloc0(MAXPATHLEN);
391 memcpy(o.lpstrFile, filename16, conv_items * sizeof(gunichar2));
392 o.nMaxFile = MAXPATHLEN;
393 o.lpstrFileTitle = NULL;
394 o.lpstrInitialDir = path16;
395 o.lpstrTitle = title16;
396 o.Flags = OFN_LONGNAMES;
398 ctx = g_new0(WinChooserCtx, 1);
400 ctx->return_value = FALSE;
404 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetSaveFileName,
406 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
407 threaded_GetSaveFileName(ctx);
412 pthread_join(pt, NULL);
414 ret = ctx->return_value;
416 debug_print("No threads available, continuing unthreaded.\n");
417 ret = GetSaveFileName(&o);
430 /* Now convert the returned file path back from UTF-16. */
431 str = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, NULL, &error);
433 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
435 debug_print("returned file path conversion to UTF-8 failed\n");
443 /* This callback function is used to set the folder browse dialog
444 * selection from filesel_select_file_open_folder() to set
445 * chosen starting folder ("path" argument to that function. */
446 static int CALLBACK _open_folder_callback(HWND hwnd, UINT uMsg,
447 LPARAM lParam, LPARAM lpData)
449 if (uMsg != BFFM_INITIALIZED)
452 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
456 gchar *filesel_select_file_open_folder(const gchar *title, const gchar *path)
458 PIDLIST_ABSOLUTE pidl;
460 gunichar2 *path16, *title16;
462 GError *error = NULL;
468 /* Path needs to be converted to UTF-16, so that the native chooser
469 * can understand it. */
470 path16 = g_utf8_to_utf16(path, -1, NULL, &conv_items, &error);
472 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
474 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
479 /* Chooser dialog title needs to be UTF-16 as well. */
480 title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
482 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
486 if (focus_window != NULL)
487 b.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
490 b.pszDisplayName = g_malloc(MAXPATHLEN);
491 b.lpszTitle = title16;
494 b.lpfn = _open_folder_callback;
495 b.lParam = (LPARAM)path16;
499 ctx = g_new0(WinChooserCtx, 1);
504 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_SHBrowseForFolder,
506 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
507 threaded_SHBrowseForFolder(ctx);
512 pthread_join(pt, NULL);
514 pidl = ctx->return_value_pidl;
516 debug_print("No threads available, continuing unthreaded.\n");
517 pidl = SHBrowseForFolder(&b);
520 g_free(b.pszDisplayName);
530 path16 = malloc(MAX_PATH);
531 if (!SHGetPathFromIDList(pidl, path16)) {
539 /* Now convert the returned file path back from UTF-16. */
540 /* Unfortunately, there is no field in BROWSEINFO struct to indicate
541 * actual length of string in pszDisplayName, so we have to assume
542 * the string is null-terminated. */
543 str = g_utf16_to_utf8(path16, -1, NULL, NULL, &error);
545 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
547 debug_print("returned file path conversion to UTF-8 failed\n");
558 gchar *filesel_select_file_save_folder(const gchar *title, const gchar *path)
560 return filesel_select_file_open_folder(title, path);