Use native file and folder selection dialogs on Windows.
[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 #include <glib.h>
25 #include <gdk/gdkwin32.h>
26 #include <pthread.h>
27
28 #include <windows.h>
29 #include <shlobj.h>
30
31 #include "claws.h"
32 #include "manage_window.h"
33 #include "utils.h"
34
35 static OPENFILENAME o;
36 static BROWSEINFO b;
37
38 /* Since running the native dialogs in the same thread stops GTK+
39  * loop from redrawing other windows on the background, we need
40  * to run the dialogs in a separate thread. */
41
42 /* TODO: There's a lot of code repeat in this file, it could be
43  * refactored to be neater. */
44
45 struct _WinChooserCtx {
46         void *data;
47         gboolean return_value;
48         PIDLIST_ABSOLUTE return_value_pidl;
49         gboolean done;
50 };
51
52 typedef struct _WinChooserCtx WinChooserCtx;
53
54 static void *threaded_GetOpenFileName(void *arg)
55 {
56         WinChooserCtx *ctx = (WinChooserCtx *)arg;
57
58         g_return_val_if_fail(ctx != NULL, NULL);
59         g_return_val_if_fail(ctx->data != NULL, NULL);
60
61         ctx->return_value = GetOpenFileName(ctx->data);
62         ctx->done = TRUE;
63
64         return NULL;
65 }
66
67 static void *threaded_GetSaveFileName(void *arg)
68 {
69         WinChooserCtx *ctx = (WinChooserCtx *)arg;
70
71         g_return_val_if_fail(ctx != NULL, NULL);
72         g_return_val_if_fail(ctx->data != NULL, NULL);
73
74         ctx->return_value = GetSaveFileName(ctx->data);
75         ctx->done = TRUE;
76
77         return NULL;
78 }
79
80 static void *threaded_SHBrowseForFolder(void *arg)
81 {
82         WinChooserCtx *ctx = (WinChooserCtx *)arg;
83
84         g_return_val_if_fail(ctx != NULL, NULL);
85         g_return_val_if_fail(ctx->data != NULL, NULL);
86
87         ctx->return_value_pidl = SHBrowseForFolder(ctx->data);
88
89         ctx->done = TRUE;
90
91         return NULL;
92 }
93
94 gchar *filesel_select_file_open(const gchar *title, const gchar *path)
95 {
96         gboolean ret;
97         gchar *str;
98         WinChooserCtx *ctx;
99 #ifdef USE_PTHREAD
100         pthread_t pt;
101 #endif
102
103         o.lStructSize = sizeof(OPENFILENAME);
104         if (focus_window != NULL)
105                 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
106         else
107                 o.hwndOwner = NULL;
108         o.hInstance = NULL;
109         o.lpstrFilter = NULL;
110         o.lpstrCustomFilter = NULL;
111         o.nFilterIndex = 0;
112         o.lpstrFile = g_malloc0(MAXPATHLEN);
113         o.nMaxFile = MAXPATHLEN;
114         o.lpstrFileTitle = NULL;
115         o.lpstrInitialDir = path;
116         o.lpstrTitle = title;
117         o.Flags = OFN_LONGNAMES;
118
119         ctx = g_new0(WinChooserCtx, 1);
120         ctx->data = &o;
121         ctx->done = FALSE;
122
123 #ifdef USE_PTHREAD
124         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetOpenFileName,
125                                 (void *)ctx) != 0) {
126                 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
127                 threaded_GetOpenFileName(ctx);
128         } else {
129                 while (!ctx->done) {
130                         claws_do_idle();
131                 }
132                 pthread_join(pt, NULL);
133         }
134         ret = ctx->return_value;
135 #else
136         debug_print("No threads available, continuing unthreaded.\n");
137         ret = GetOpenFileName(&o);
138 #endif
139
140         g_free(ctx);
141
142         if (!ret) {
143                 g_free(o.lpstrFile);
144                 return NULL;
145         }
146
147         str = g_strndup(o.lpstrFile, strlen(o.lpstrFile));
148         g_free(o.lpstrFile);
149         return str;
150 }
151
152 /* TODO: Allow selecting of multiple files with OFN_ALLOWMULTISELECT
153  * flag and parsing the long string with returned file names. */
154 GList *filesel_select_multiple_files_open(const gchar *title)
155 {
156         GList *file_list = NULL;
157         gchar *ret = filesel_select_file_open(title, NULL);
158
159         if (ret != NULL)
160                 file_list = g_list_append(file_list, ret);
161
162         return file_list;
163 }
164
165 gchar *filesel_select_file_open_with_filter(const gchar *title, const gchar *path,
166                               const gchar *filter)
167 {
168         gboolean ret;
169         gchar *win_filter = NULL, *str;
170         WinChooserCtx *ctx;
171 #ifdef USE_PTHREAD
172         pthread_t pt;
173 #endif
174
175         o.lStructSize = sizeof(OPENFILENAME);
176         if (focus_window != NULL)
177                 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
178         else
179                 o.hwndOwner = NULL;
180         o.lpstrFilter = NULL;
181         o.lpstrCustomFilter = NULL;
182         o.nFilterIndex = 0;
183         o.lpstrFile = g_malloc0(MAXPATHLEN);
184         o.nMaxFile = MAXPATHLEN;
185         o.lpstrFileTitle = NULL;
186         o.lpstrInitialDir = path;
187         o.lpstrTitle = title;
188         o.Flags = OFN_LONGNAMES;
189
190         if (filter != NULL && strlen(filter) > 0) {
191                 win_filter = g_strdup_printf("%s%c%s%c", filter, '\0', filter, '\0');
192                 o.lpstrFilter = win_filter;
193                 o.nFilterIndex = 1;
194         }
195
196         ctx = g_new0(WinChooserCtx, 1);
197         ctx->data = &o;
198         ctx->done = FALSE;
199
200 #ifdef USE_PTHREAD
201         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetOpenFileName,
202                                 (void *)ctx) != 0) {
203                 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
204                 threaded_GetOpenFileName(ctx);
205         } else {
206                 while (!ctx->done) {
207                         claws_do_idle();
208                 }
209                 pthread_join(pt, NULL);
210         }
211         ret = ctx->return_value;
212 #else
213         debug_print("No threads available, continuing unthreaded.\n");
214         ret = GetOpenFileName(&o);
215 #endif
216
217         g_free(win_filter);
218         g_free(ctx);
219
220         if (!ret) {
221                 g_free(o.lpstrFile);
222                 return NULL;
223         }
224
225         str = g_strndup(o.lpstrFile, strlen(o.lpstrFile));
226         g_free(o.lpstrFile);
227         return str;
228 }
229
230 /* TODO: Allow selecting of multiple files with OFN_ALLOWMULTISELECT
231  * flag and parsing the long string with returned file names. */
232 GList *filesel_select_multiple_files_open_with_filter(const gchar *title,
233                 const gchar *path, const gchar *filter)
234 {
235         GList *file_list = NULL;
236         gchar *ret = filesel_select_file_open_with_filter(title, path, filter);
237
238         if (ret != NULL)
239                 file_list = g_list_append(file_list, ret);
240
241         return file_list;
242 }
243
244 gchar *filesel_select_file_save(const gchar *title, const gchar *path)
245 {
246         gboolean ret;
247         gchar *str;
248         WinChooserCtx *ctx;
249 #ifdef USE_PTHREAD
250         pthread_t pt;
251 #endif
252
253         o.lStructSize = sizeof(OPENFILENAME);
254         if (focus_window != NULL)
255                 o.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
256         else
257                 o.hwndOwner = NULL;
258         o.lpstrFilter = NULL;
259         o.lpstrCustomFilter = NULL;
260         o.lpstrFile = g_malloc0(MAXPATHLEN);
261         o.nMaxFile = MAXPATHLEN;
262         o.lpstrFileTitle = NULL;
263         o.lpstrInitialDir = path;
264         o.lpstrTitle = title;
265         o.Flags = OFN_LONGNAMES;
266
267         ctx = g_new0(WinChooserCtx, 1);
268         ctx->data = &o;
269         ctx->return_value = FALSE;
270         ctx->done = FALSE;
271
272 #ifdef USE_PTHREAD
273         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_GetSaveFileName,
274                                 (void *)ctx) != 0) {
275                 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
276                 threaded_GetSaveFileName(ctx);
277         } else {
278                 while (!ctx->done) {
279                         claws_do_idle();
280                 }
281                 pthread_join(pt, NULL);
282         }
283         ret = ctx->return_value;
284 #else
285         debug_print("No threads available, continuing unthreaded.\n");
286         ret = GetSaveFileName(&o);
287 #endif
288
289         g_free(ctx);
290
291         if (!ret) {
292                 g_free(o.lpstrFile);
293                 return NULL;
294         }
295
296         str = g_strndup(o.lpstrFile, strlen(o.lpstrFile));
297         g_free(o.lpstrFile);
298         return str;
299 }
300
301 gchar *filesel_select_file_open_folder(const gchar *title, const gchar *path)
302 {
303         PIDLIST_ABSOLUTE pidl;
304         gchar *str;
305         WinChooserCtx *ctx;
306 #ifdef USE_PTHREAD
307         pthread_t pt;
308 #endif
309
310         if (focus_window != NULL)
311                 b.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(focus_window));
312         else
313                 b.hwndOwner = NULL;
314         b.pidlRoot = NULL; /* TODO: get a PIDLIST from path and use it. */
315         b.pszDisplayName = g_malloc(MAXPATHLEN);
316         b.lpszTitle = title;
317         b.ulFlags = 0;
318         b.lpfn = NULL;
319
320         CoInitialize(NULL);
321
322         ctx = g_new0(WinChooserCtx, 1);
323         ctx->data = &b;
324         ctx->done = FALSE;
325
326 #ifdef USE_PTHREAD
327         if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, threaded_SHBrowseForFolder,
328                                 (void *)ctx) != 0) {
329                 debug_print("Couldn't run in a thread, continuing unthreaded.\n");
330                 threaded_SHBrowseForFolder(ctx);
331         } else {
332                 while (!ctx->done) {
333                         claws_do_idle();
334                 }
335                 pthread_join(pt, NULL);
336         }
337         pidl = ctx->return_value_pidl;
338 #else
339         debug_print("No threads available, continuing unthreaded.\n");
340         pidl = SHBrowseForFolder(&b);
341 #endif
342
343         if (pidl == NULL) {
344                 CoUninitialize();
345                 g_free(b.pszDisplayName);
346                 g_free(ctx);
347                 return NULL;
348         }
349
350         str = g_strndup(b.pszDisplayName, strlen(b.pszDisplayName));
351         g_free(b.pszDisplayName);
352
353         CoTaskMemFree(pidl);
354         CoUninitialize();
355         g_free(ctx);
356
357         return str;
358 }
359
360 gchar *filesel_select_file_save_folder(const gchar *title, const gchar *path)
361 {
362         return filesel_select_file_open_folder(title, path);
363 }