fix bug 4773, 'remove obsolescent AC_C_CONST'
[claws.git] / src / gtk / w32_filesel.c
blob6ec6f570df0b53115ccc481f651249f0f9035fee
1 /*
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/>.
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
24 #define UNICODE
25 #define _UNICODE
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <gdk/gdkwin32.h>
30 #include <pthread.h>
32 #include <windows.h>
33 #include <shlobj.h>
35 #include "claws.h"
36 #include "alertpanel.h"
37 #include "manage_window.h"
38 #include "utils.h"
40 static OPENFILENAME o;
41 static BROWSEINFO b;
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 {
51 void *data;
52 gboolean return_value;
53 PIDLIST_ABSOLUTE return_value_pidl;
54 gboolean done;
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);
67 ctx->done = TRUE;
69 return NULL;
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);
80 ctx->done = TRUE;
82 return NULL;
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);
94 ctx->done = TRUE;
96 return NULL;
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)
106 gboolean ret;
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;
113 WinChooserCtx *ctx;
114 #ifdef USE_PTHREAD
115 pthread_t pt;
116 #endif
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);
122 if (error != NULL) {
123 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
124 error->message);
125 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
126 g_error_free(error);
127 error = NULL;
128 return FALSE;
131 /* Chooser dialog title needs to be UTF-16 as well. */
132 title16 = g_utf8_to_utf16(title ? title : "",
133 -1, NULL, NULL, &error);
134 if (error != NULL) {
135 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
136 g_error_free(error);
137 error = NULL;
140 o.lStructSize = sizeof(OPENFILENAME);
141 if (focus_window != NULL)
142 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
143 else
144 o.hwndOwner = NULL;
145 o.hInstance = NULL;
146 o.lpstrFilter = NULL;
147 o.lpstrCustomFilter = NULL;
148 o.nFilterIndex = 0;
149 o.nMaxFile = MAXPATHLEN;
150 o.lpstrFileTitle = NULL;
151 o.lpstrInitialDir = path16;
152 o.lpstrTitle = title16;
153 if (multi)
154 o.Flags = OFN_LONGNAMES | OFN_EXPLORER | OFN_ALLOWMULTISELECT;
155 else
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);
173 g_free(filter16);
175 if (error != NULL) {
176 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
177 g_error_free(error);
178 error = NULL;
180 o.lpstrFilter = (LPCTSTR)win_filter16;
181 o.nFilterIndex = 1;
184 ctx = g_new0(WinChooserCtx, 1);
185 ctx->data = &o;
186 ctx->done = FALSE;
188 #ifdef USE_PTHREAD
189 if (pthread_create(&pt, NULL, threaded_GetOpenFileName,
190 (void *)ctx) != 0) {
191 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
192 threaded_GetOpenFileName(ctx);
193 } else {
194 while (!ctx->done) {
195 claws_do_idle();
197 pthread_join(pt, NULL);
199 ret = ctx->return_value;
200 #else
201 debug_print("No threads available, continuing unthreaded.\n");
202 ret = GetOpenFileName(&o);
203 #endif
205 g_free(win_filter16);
206 if (path16 != NULL) {
207 g_free(path16);
209 g_free(title16);
210 g_free(ctx);
212 return ret;
215 gchar *filesel_select_file_open_with_filter(const gchar *title, const gchar *path,
216 const gchar *filter)
218 gchar *str = NULL;
219 GError *error = NULL;
221 o.lpstrFile = g_malloc0(MAXPATHLEN);
222 if (!_file_open_dialog(path, title, filter, FALSE)) {
223 g_free(o.lpstrFile);
224 return NULL;
227 /* Now convert the returned file path back from UTF-16. */
228 str = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, NULL, &error);
229 if (error != NULL) {
230 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
231 error->message);
232 debug_print("returned file path conversion to UTF-8 failed\n");
233 g_error_free(error);
236 g_free(o.lpstrFile);
237 return str;
240 GList *filesel_select_multiple_files_open_with_filter(const gchar *title,
241 const gchar *path, const gchar *filter)
243 GList *file_list = NULL;
244 gchar *str = NULL;
245 gchar *dir = NULL;
246 gunichar2 *f;
247 GError *error = NULL;
248 glong n, items_read;
250 o.lpstrFile = g_malloc0(MAXPATHLEN);
251 if (!_file_open_dialog(path, title, filter, TRUE)) {
252 g_free(o.lpstrFile);
253 return NULL;
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);
262 if (error != NULL) {
263 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
264 error->message);
265 debug_print("returned file path conversion to UTF-8 failed\n");
266 g_error_free(error);
267 return NULL;
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));
276 g_free(str);
277 return file_list;
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. */
282 dir = g_strdup(str);
283 g_free(str);
284 debug_print("Selected multiple files in dir '%s'\n", dir);
286 n = items_read + 1;
287 f = &o.lpstrFile[n];
288 while (items_read > 0) {
289 str = g_utf16_to_utf8(f, -1, &items_read, NULL, &error);
290 if (error != NULL) {
291 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
292 error->message);
293 debug_print("returned file path conversion to UTF-8 failed\n");
294 g_error_free(error);
295 g_free(o.lpstrFile);
296 return NULL;
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));
304 g_free(str);
306 n += items_read + 1;
307 f = &o.lpstrFile[n];
310 g_free(o.lpstrFile);
311 return file_list;
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)
326 gboolean ret;
327 gchar *str, *filename = NULL;
328 gunichar2 *filename16, *path16, *title16;
329 glong conv_items;
330 GError *error = NULL;
331 WinChooserCtx *ctx;
332 #ifdef USE_PTHREAD
333 pthread_t pt;
334 #endif
336 /* Find the filename part, if any */
337 if (path == NULL || path[strlen(path)-1] == G_DIR_SEPARATOR) {
338 filename = "";
339 } else if ((filename = strrchr(path, G_DIR_SEPARATOR)) != NULL) {
340 filename++;
341 } else {
342 filename = (char *) path;
345 /* Convert it to UTF-16. */
346 filename16 = g_utf8_to_utf16(filename, -1, NULL, &conv_items, &error);
347 if (error != NULL) {
348 alertpanel_error(_("Could not convert attachment name to UTF-16:\n\n%s"),
349 error->message);
350 debug_print("filename '%s' conversion to UTF-16 failed\n", filename);
351 g_error_free(error);
352 return NULL;
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);
358 if (error != NULL) {
359 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
360 error->message);
361 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
362 g_error_free(error);
363 g_free(filename16);
364 return NULL;
367 /* Chooser dialog title needs to be UTF-16 as well. */
368 title16 = g_utf8_to_utf16(title, -1, NULL, NULL, &error);
369 if (error != NULL) {
370 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
371 g_error_free(error);
374 o.lStructSize = sizeof(OPENFILENAME);
375 if (focus_window != NULL)
376 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
377 else
378 o.hwndOwner = NULL;
379 o.lpstrFilter = NULL;
380 o.lpstrCustomFilter = NULL;
381 o.lpstrFile = g_malloc0(MAXPATHLEN);
382 if (path16 != NULL)
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);
391 ctx->data = &o;
392 ctx->return_value = FALSE;
393 ctx->done = FALSE;
395 #ifdef USE_PTHREAD
396 if (pthread_create(&pt, NULL, threaded_GetSaveFileName,
397 (void *)ctx) != 0) {
398 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
399 threaded_GetSaveFileName(ctx);
400 } else {
401 while (!ctx->done) {
402 claws_do_idle();
404 pthread_join(pt, NULL);
406 ret = ctx->return_value;
407 #else
408 debug_print("No threads available, continuing unthreaded.\n");
409 ret = GetSaveFileName(&o);
410 #endif
412 g_free(filename16);
413 g_free(path16);
414 g_free(title16);
415 g_free(ctx);
417 if (!ret) {
418 g_free(o.lpstrFile);
419 return NULL;
422 /* Now convert the returned file path back from UTF-16. */
423 str = g_utf16_to_utf8(o.lpstrFile, o.nMaxFile, NULL, NULL, &error);
424 if (error != NULL) {
425 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
426 error->message);
427 debug_print("returned file path conversion to UTF-8 failed\n");
428 g_error_free(error);
431 g_free(o.lpstrFile);
432 return str;
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)
442 return 0;
444 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
445 return 0;
448 gchar *filesel_select_file_open_folder(const gchar *title, const gchar *path)
450 PIDLIST_ABSOLUTE pidl;
451 gchar *str;
452 gunichar2 *path16, *title16;
453 glong conv_items;
454 GError *error = NULL;
455 WinChooserCtx *ctx;
456 #ifdef USE_PTHREAD
457 pthread_t pt;
458 #endif
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);
464 if (error != NULL) {
465 alertpanel_error(_("Could not convert file path to UTF-16:\n\n%s"),
466 error->message);
467 debug_print("file path '%s' conversion to UTF-16 failed\n", path);
468 g_error_free(error);
469 return NULL;
472 /* Chooser dialog title needs to be UTF-16 as well. */
473 title16 = g_utf8_to_utf16(title ? title : "",
474 -1, NULL, NULL, &error);
475 if (error != NULL) {
476 debug_print("dialog title '%s' conversion to UTF-16 failed\n", title);
477 g_error_free(error);
480 if (focus_window != NULL)
481 b.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
482 else
483 b.hwndOwner = NULL;
484 b.pszDisplayName = g_malloc(MAXPATHLEN);
485 b.lpszTitle = title16;
486 b.ulFlags = 0;
487 b.pidlRoot = NULL;
488 b.lpfn = _open_folder_callback;
489 b.lParam = (LPARAM)path16;
491 CoInitialize(NULL);
493 ctx = g_new0(WinChooserCtx, 1);
494 ctx->data = &b;
495 ctx->done = FALSE;
497 #ifdef USE_PTHREAD
498 if (pthread_create(&pt, NULL, threaded_SHBrowseForFolder,
499 (void *)ctx) != 0) {
500 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
501 threaded_SHBrowseForFolder(ctx);
502 } else {
503 while (!ctx->done) {
504 claws_do_idle();
506 pthread_join(pt, NULL);
508 pidl = ctx->return_value_pidl;
509 #else
510 debug_print("No threads available, continuing unthreaded.\n");
511 pidl = SHBrowseForFolder(&b);
512 #endif
514 g_free(b.pszDisplayName);
515 g_free(title16);
516 g_free(path16);
518 if (pidl == NULL) {
519 CoUninitialize();
520 g_free(ctx);
521 return NULL;
524 path16 = malloc(MAX_PATH);
525 if (!SHGetPathFromIDList(pidl, path16)) {
526 CoTaskMemFree(pidl);
527 CoUninitialize();
528 g_free(path16);
529 g_free(ctx);
530 return NULL;
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);
538 if (error != NULL) {
539 alertpanel_error(_("Could not convert file path back to UTF-8:\n\n%s"),
540 error->message);
541 debug_print("returned file path conversion to UTF-8 failed\n");
542 g_error_free(error);
544 CoTaskMemFree(pidl);
545 CoUninitialize();
546 g_free(ctx);
547 g_free(path16);
549 return str;
552 gchar *filesel_select_file_save_folder(const gchar *title, const gchar *path)
554 return filesel_select_file_open_folder(title, path);