1 /*
2  *      win32.c - this file is part of Geany, a fast and lightweight IDE
3  *
4  *      Copyright 2005 The Geany contributors
5  *
6  *      This program is free software; you can redistribute it and/or modify
7  *      it under the terms of the GNU General Public License as published by
8  *      the Free Software Foundation; either version 2 of the License, or
9  *      (at your option) any later version.
10  *
11  *      This program is distributed in the hope that it will be useful,
12  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *      GNU General Public License for more details.
15  *
16  *      You should have received a copy of the GNU General Public License along
17  *      with this program; if not, write to the Free Software Foundation, Inc.,
18  *      51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 
21 /*
22  * Special functions for the win32 platform, to provide native dialogs.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28 
29 /* Need Windows XP for SHGetFolderPathAndSubDirW */
30 #define _WIN32_WINNT 0x0501
31 /* Needed for SHGFP_TYPE */
32 #define _WIN32_IE 0x0500
33 
34 #include "win32.h"
35 
36 #ifdef G_OS_WIN32
37 
38 #include "dialogs.h"
39 #include "document.h"
40 #include "editor.h"
41 #include "filetypes.h"
42 #include "project.h"
43 #include "support.h"
44 #include "ui_utils.h"
45 #include "utils.h"
46 
47 #include <ctype.h>
48 #include <fcntl.h>
49 #include <io.h>
50 #include <math.h>
51 #include <stdlib.h>
52 #include <string.h>
53 
54 #include <windows.h>
55 #include <commdlg.h>
56 #include <shellapi.h>
57 #include <shlobj.h>
58 
59 #include <glib/gstdio.h>
60 #include <gdk/gdkwin32.h>
61 
62 
63 /* The timer handle used to refresh windows below modal native dialogs. If
64  * ever more than one dialog can be shown at a time, this needs to be changed
65  * to be for specific dialogs. */
66 static UINT_PTR dialog_timer = 0;
67 
68 
win32_dialog_reset_timer(HWND hwnd)69 G_INLINE_FUNC void win32_dialog_reset_timer(HWND hwnd)
70 {
71 	if (G_UNLIKELY(dialog_timer != 0))
72 	{
73 		KillTimer(hwnd, dialog_timer);
74 		dialog_timer = 0;
75 	}
76 }
77 
78 
79 VOID CALLBACK
win32_dialog_update_main_window(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)80 win32_dialog_update_main_window(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
81 {
82 	gint i;
83 
84 	/* Pump the main window loop a bit, but not enough to lock-up.
85 	 * The typical `while(gtk_events_pending()) gtk_main_iteration();`
86 	 * loop causes the entire operating system to lock-up. */
87 	for (i = 0; i < 4 && gtk_events_pending(); i++)
88 		gtk_main_iteration();
89 
90 	/* Cancel any pending timers since we just did an update */
91 	win32_dialog_reset_timer(hwnd);
92 }
93 
94 
win32_dialog_queue_main_window_redraw(HWND dlg,UINT msg,WPARAM wParam,LPARAM lParam,gboolean postpone)95 G_INLINE_FUNC UINT_PTR win32_dialog_queue_main_window_redraw(HWND dlg, UINT msg,
96 	WPARAM wParam, LPARAM lParam, gboolean postpone)
97 {
98 	switch (msg)
99 	{
100 		/* Messages that likely mean the window below a dialog needs to be re-drawn. */
101 		case WM_WINDOWPOSCHANGED:
102 		case WM_MOVE:
103 		case WM_SIZE:
104 		case WM_THEMECHANGED:
105 			if (postpone)
106 			{
107 				win32_dialog_reset_timer(dlg);
108 				dialog_timer = SetTimer(dlg, 0, 33 /* around 30fps */, win32_dialog_update_main_window);
109 			}
110 			else
111 				win32_dialog_update_main_window(dlg, msg, wParam, lParam);
112 			break;
113 	}
114 	return 0; /* always let the default proc handle it */
115 }
116 
117 
118 /* This function is called for OPENFILENAME lpfnHook function and it establishes
119  * a timer that is reset each time which will update the main window loop eventually. */
win32_dialog_explorer_hook_proc(HWND dlg,UINT msg,WPARAM wParam,LPARAM lParam)120 UINT_PTR CALLBACK win32_dialog_explorer_hook_proc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam)
121 {
122 	return win32_dialog_queue_main_window_redraw(dlg, msg, wParam, lParam, TRUE);
123 }
124 
125 
126 /* This function is called for old-school win32 dialogs that accept a proper
127  * lpfnHook function for all messages, it doesn't use a timer. */
win32_dialog_hook_proc(HWND dlg,UINT msg,WPARAM wParam,LPARAM lParam)128 UINT_PTR CALLBACK win32_dialog_hook_proc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam)
129 {
130 	return win32_dialog_queue_main_window_redraw(dlg, msg, wParam, lParam, FALSE);
131 }
132 
133 
get_file_filters(void)134 static wchar_t *get_file_filters(void)
135 {
136 	gchar *string;
137 	guint i, j, len;
138 	static wchar_t title[4096];
139 	GString *str = g_string_sized_new(100);
140 	GString *all_patterns = g_string_sized_new(100);
141 	GSList *node;
142 	gchar *tmp;
143 
144 	/* create meta file filter "All files" */
145 	g_string_append_printf(str, "%s\t*\t", _("All files"));
146 	/* create meta file filter "All Source" (skip GEANY_FILETYPES_NONE) */
147 	for (i = GEANY_FILETYPES_NONE + 1; i < filetypes_array->len; i++)
148 	{
149 		for (j = 0; filetypes[i]->pattern[j] != NULL; j++)
150 		{
151 			g_string_append(all_patterns, filetypes[i]->pattern[j]);
152 			g_string_append_c(all_patterns, ';');
153 		}
154 	}
155 	g_string_append_printf(str, "%s\t%s\t", _("All Source"), all_patterns->str);
156 	g_string_free(all_patterns, TRUE);
157 	/* add 'usual' filetypes */
158 	foreach_slist(node, filetypes_by_title)
159 	{
160 		GeanyFiletype *ft = node->data;
161 
162 		if (G_UNLIKELY(ft->id == GEANY_FILETYPES_NONE))
163 			continue;
164 		tmp = g_strjoinv(";", ft->pattern);
165 		g_string_append_printf(str, "%s\t%s\t", ft->title, tmp);
166 		g_free(tmp);
167 	}
168 	g_string_append_c(str, '\t'); /* the final \0 byte to mark the end of the string */
169 	string = str->str;
170 	g_string_free(str, FALSE);
171 
172 	/* replace all "\t"s by \0 */
173 	len = strlen(string);
174 	g_strdelimit(string, "\t", '\0');
175 	g_assert(string[len - 1] == 0x0);
176 	MultiByteToWideChar(CP_UTF8, 0, string, len, title, G_N_ELEMENTS(title));
177 	g_free(string);
178 
179 	return title;
180 }
181 
182 
get_file_filter_all_files(void)183 static wchar_t *get_file_filter_all_files(void)
184 {
185 	guint len;
186 	static wchar_t title[4096];
187 	gchar *filter;
188 
189 	/* create meta file filter "All files" */
190 	filter = g_strdup_printf("%s\t*\t", _("All files"));
191 
192 	len = strlen(filter);
193 	g_strdelimit(filter, "\t", '\0');
194 	g_assert(filter[len - 1] == 0x0);
195 	MultiByteToWideChar(CP_UTF8, 0, filter, len, title, G_N_ELEMENTS(title));
196 	g_free(filter);
197 
198 	return title;
199 }
200 
201 
get_filters(gboolean project_files)202 static wchar_t *get_filters(gboolean project_files)
203 {
204 	gchar *string;
205 	gint len;
206 	static wchar_t title[1024];
207 
208 	if (project_files)
209 	{
210 		string = g_strconcat(_("Geany project files"), "\t", "*." GEANY_PROJECT_EXT, "\t",
211 			_("All files"), "\t", "*", "\t", NULL);
212 	}
213 	else
214 	{
215 		string = g_strconcat(_("Executables"), "\t", "*.exe;*.bat;*.cmd", "\t",
216 			_("All files"), "\t", "*", "\t", NULL);
217 	}
218 
219 	/* replace all "\t"s by \0 */
220 	len = strlen(string);
221 	g_strdelimit(string, "\t", '\0');
222 	g_assert(string[len - 1] == 0x0);
223 	MultiByteToWideChar(CP_UTF8, 0, string, len, title, G_N_ELEMENTS(title));
224 	g_free(string);
225 
226 	return title;
227 }
228 
229 
230 /* Converts the given UTF-8 filename or directory name into something usable for Windows and
231  * returns the directory part of the given filename. */
get_dir_for_path(const gchar * utf8_filename)232 static wchar_t *get_dir_for_path(const gchar *utf8_filename)
233 {
234 	static wchar_t w_dir[MAX_PATH];
235 	gchar *result;
236 
237 	if (g_file_test(utf8_filename, G_FILE_TEST_IS_DIR))
238 		result = (gchar*) utf8_filename;
239 	else
240 		result = g_path_get_dirname(utf8_filename);
241 
242 	MultiByteToWideChar(CP_UTF8, 0, result, -1, w_dir, G_N_ELEMENTS(w_dir));
243 
244 	if (result != utf8_filename)
245 		g_free(result);
246 
247 	return w_dir;
248 }
249 
250 
251 /* Callback function for setting the initial directory of the folder open dialog. This could also
252  * be done with BROWSEINFO.pidlRoot and SHParseDisplayName but SHParseDisplayName is not available
253  * on systems below Windows XP. So, we go the hard way by creating a callback which will set up the
254  * folder when the dialog is initialised. Yeah, I like Windows. */
BrowseCallbackProc(HWND hwnd,UINT uMsg,LPARAM lp,LPARAM pData)255 INT CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData)
256 {
257 	win32_dialog_hook_proc(hwnd, uMsg, lp, pData);
258 	switch (uMsg)
259 	{
260 		case BFFM_INITIALIZED:
261 		{
262 			SendMessageW(hwnd, BFFM_SETSELECTIONW, TRUE, pData);
263 			break;
264 		}
265 		case BFFM_SELCHANGED:
266 		{
267 			/* set the status window to the currently selected path. */
268 			static wchar_t szDir[MAX_PATH];
269 			if (SHGetPathFromIDListW((LPITEMIDLIST) lp, szDir))
270 			{
271 				SendMessageW(hwnd, BFFM_SETSTATUSTEXTW, 0, (LPARAM) szDir);
272 			}
273 			break;
274 		}
275 	}
276 	return 0;
277 }
278 
279 
280 /* Shows a folder selection dialog.
281  * initial_dir is expected in UTF-8
282  * The selected folder name is returned. */
win32_show_folder_dialog(GtkWidget * parent,const gchar * title,const gchar * initial_dir)283 gchar *win32_show_folder_dialog(GtkWidget *parent, const gchar *title, const gchar *initial_dir)
284 {
285 	BROWSEINFOW bi;
286 	LPITEMIDLIST pidl;
287 	gchar *result = NULL;
288 	wchar_t fname[MAX_PATH];
289 	wchar_t w_title[512];
290 
291 	MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
292 
293 	if (parent == NULL)
294 		parent = main_widgets.window;
295 
296 	memset(&bi, 0, sizeof bi);
297 	bi.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(parent));
298 	bi.pidlRoot = NULL;
299 	bi.lpszTitle = w_title;
300 	bi.lpfn = BrowseCallbackProc;
301 	bi.lParam = (LPARAM) get_dir_for_path(initial_dir);
302 	bi.ulFlags = BIF_DONTGOBELOWDOMAIN | BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT | BIF_USENEWUI;
303 
304 	pidl = SHBrowseForFolderW(&bi);
305 
306 	/* convert the strange Windows folder list item something into an usual path string ;-) */
307 	if (pidl != NULL)
308 	{
309 		if (SHGetPathFromIDListW(pidl, fname))
310 		{
311 			result = g_malloc0(MAX_PATH * 2);
312 			WideCharToMultiByte(CP_UTF8, 0, fname, -1, result, MAX_PATH * 2, NULL, NULL);
313 		}
314 		CoTaskMemFree(pidl);
315 	}
316 	return result;
317 }
318 
319 
320 /* Shows a file open dialog.
321  * If allow_new_file is set, the file to be opened doesn't have to exist.
322  * initial_dir is expected in UTF-8
323  * The selected file name is returned.
324  * If project_file_filter is set, the file open dialog will have a file filter for Geany project
325  * files, a filter for executables otherwise. */
win32_show_project_open_dialog(GtkWidget * parent,const gchar * title,const gchar * initial_dir,gboolean allow_new_file,gboolean project_file_filter)326 gchar *win32_show_project_open_dialog(GtkWidget *parent, const gchar *title,
327 								      const gchar *initial_dir, gboolean allow_new_file,
328 								      gboolean project_file_filter)
329 {
330 	OPENFILENAMEW of;
331 	gint retval;
332 	wchar_t fname[MAX_PATH];
333 	wchar_t w_title[512];
334 	gchar *filename;
335 
336 	fname[0] = '\0';
337 
338 	MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
339 
340 	if (parent == NULL)
341 		parent = main_widgets.window;
342 
343 	/* initialise file dialog info struct */
344 	memset(&of, 0, sizeof of);
345 	of.lStructSize = sizeof of;
346 	of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(parent));
347 	of.lpstrFilter = get_filters(project_file_filter);
348 
349 	of.lpstrCustomFilter = NULL;
350 	of.nFilterIndex = 0;
351 	of.lpstrFile = fname;
352 	of.lpstrInitialDir = get_dir_for_path(initial_dir);
353 	of.nMaxFile = 2048;
354 	of.lpstrFileTitle = NULL;
355 	of.lpstrTitle = w_title;
356 	of.lpstrDefExt = L"";
357 	of.Flags = OFN_PATHMUSTEXIST | OFN_EXPLORER | OFN_HIDEREADONLY |
358 		OFN_ENABLEHOOK | OFN_ENABLESIZING;
359 	of.lpfnHook = win32_dialog_explorer_hook_proc;
360 	if (! allow_new_file)
361 		of.Flags |= OFN_FILEMUSTEXIST;
362 
363 	retval = GetOpenFileNameW(&of);
364 
365 	if (! retval)
366 	{
367 		if (CommDlgExtendedError())
368 		{
369 			gchar *error;
370 			error = g_strdup_printf("File dialog box error (%x)", (int)CommDlgExtendedError());
371 			win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
372 			g_free(error);
373 		}
374 		return NULL;
375 	}
376 	/* convert the resulting filename into UTF-8 (from whatever encoding it has at this moment) */
377 	filename = g_malloc0(MAX_PATH * 2);
378 	WideCharToMultiByte(CP_UTF8, 0, fname, -1, filename, MAX_PATH * 2, NULL, NULL);
379 
380 	return filename;
381 }
382 
383 
384 /* initial_dir can be NULL to use the current working directory.
385  * Returns: TRUE if the dialog was not cancelled. */
win32_show_document_open_dialog(GtkWindow * parent,const gchar * title,const gchar * initial_dir)386 gboolean win32_show_document_open_dialog(GtkWindow *parent, const gchar *title, const gchar *initial_dir)
387 {
388 	OPENFILENAMEW of;
389 	gint retval;
390 	guint x;
391 	gchar tmp[MAX_PATH];
392 	wchar_t fname[MAX_PATH];
393 	wchar_t w_dir[MAX_PATH];
394 	wchar_t w_title[512];
395 
396 	fname[0] = '\0';
397 
398 	if (initial_dir != NULL)
399 		MultiByteToWideChar(CP_UTF8, 0, initial_dir, -1, w_dir, G_N_ELEMENTS(w_dir));
400 
401 	MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
402 
403 	/* initialise file dialog info struct */
404 	memset(&of, 0, sizeof of);
405 	of.lStructSize = sizeof of;
406 	of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(GTK_WIDGET(parent)));
407 	of.lpstrFilter = get_file_filters();
408 
409 	of.lpstrCustomFilter = NULL;
410 	of.nFilterIndex = GEANY_FILETYPES_NONE + 1;
411 	of.lpstrFile = fname;
412 	of.lpstrInitialDir = (initial_dir != NULL) ? w_dir : NULL;
413 	of.nMaxFile = 2048;
414 	of.lpstrFileTitle = NULL;
415 	of.lpstrTitle = w_title;
416 	of.lpstrDefExt = L"";
417 	of.Flags = OFN_ALLOWMULTISELECT | OFN_FILEMUSTEXIST | OFN_EXPLORER |
418 		OFN_ENABLEHOOK | OFN_ENABLESIZING;
419 	of.lpfnHook = win32_dialog_explorer_hook_proc;
420 
421 	retval = GetOpenFileNameW(&of);
422 
423 	if (!retval)
424 	{
425 		if (CommDlgExtendedError())
426 		{
427 			gchar error[100];
428 			g_snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError());
429 			win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
430 		}
431 		return FALSE;
432 	}
433 
434 	x = of.nFileOffset - 1;
435 	if (x != wcslen(fname))
436 	{	/* open a single file */
437 		WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL);
438 		document_open_file(tmp, of.Flags & OFN_READONLY, NULL, NULL);
439 	}
440 	else
441 	{	/* open multiple files */
442 		gchar file_name[MAX_PATH];
443 		gchar dir_name[MAX_PATH];
444 
445 		WideCharToMultiByte(CP_UTF8, 0, fname, of.nFileOffset,
446 			dir_name, sizeof(dir_name), NULL, NULL);
447 		for (; ;)
448 		{
449 			if (! fname[x])
450 			{
451 				if (! fname[x + 1])
452 					break;
453 
454 				WideCharToMultiByte(CP_UTF8, 0, fname + x + 1, -1,
455 					tmp, sizeof(tmp), NULL, NULL);
456 				g_snprintf(file_name, 511, "%s\\%s", dir_name, tmp);
457 
458 				/* convert the resulting filename into UTF-8 */
459 				document_open_file(file_name, of.Flags & OFN_READONLY, NULL, NULL);
460 			}
461 			x++;
462 		}
463 	}
464 	return (retval != 0);
465 }
466 
467 
win32_show_document_save_as_dialog(GtkWindow * parent,const gchar * title,GeanyDocument * doc)468 gchar *win32_show_document_save_as_dialog(GtkWindow *parent, const gchar *title,
469 										  GeanyDocument *doc)
470 {
471 	OPENFILENAMEW of;
472 	gint retval;
473 	gchar tmp[MAX_PATH];
474 	wchar_t w_file[MAX_PATH];
475 	wchar_t w_title[512];
476 	int n;
477 
478 	w_file[0] = '\0';
479 
480 	/* Convert the name of the file for of.lpstrFile */
481 	n = MultiByteToWideChar(CP_UTF8, 0, DOC_FILENAME(doc), -1, w_file, G_N_ELEMENTS(w_file));
482 
483 	/* If creating a new file name, convert and append the extension if any */
484 	if (! doc->file_name && doc->file_type && doc->file_type->extension &&
485 		n + 1 < (int)G_N_ELEMENTS(w_file))
486 	{
487 		w_file[n - 1] = L'.';
488 		MultiByteToWideChar(CP_UTF8, 0, doc->file_type->extension, -1, &w_file[n],
489 				G_N_ELEMENTS(w_file) - n - 1);
490 	}
491 
492 	MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
493 
494 	/* initialise file dialog info struct */
495 	memset(&of, 0, sizeof of);
496 	of.lStructSize = sizeof of;
497 	of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(GTK_WIDGET(parent)));
498 
499 	of.lpstrFilter = get_file_filter_all_files();
500 	of.lpstrCustomFilter = NULL;
501 	of.nFilterIndex = 0;
502 
503 	of.lpstrFile = w_file;
504 	of.nMaxFile = 2048;
505 	of.lpstrFileTitle = NULL;
506 	of.lpstrTitle = w_title;
507 	of.lpstrDefExt = L"";
508 	of.Flags = OFN_OVERWRITEPROMPT | OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLESIZING;
509 	of.lpfnHook = win32_dialog_explorer_hook_proc;
510 	retval = GetSaveFileNameW(&of);
511 
512 	if (! retval)
513 	{
514 		if (CommDlgExtendedError())
515 		{
516 			gchar *error = g_strdup_printf(
517 				"File dialog box error (%x)", (gint) CommDlgExtendedError());
518 			win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
519 			g_free(error);
520 		}
521 		return NULL;
522 	}
523 
524 	WideCharToMultiByte(CP_UTF8, 0, w_file, -1, tmp, sizeof(tmp), NULL, NULL);
525 
526 	return g_strdup(tmp);
527 }
528 
529 
530 /* initial_dir can be NULL to use the current working directory.
531  * Returns: the selected filename */
win32_show_file_dialog(GtkWindow * parent,const gchar * title,const gchar * initial_file)532 gchar *win32_show_file_dialog(GtkWindow *parent, const gchar *title, const gchar *initial_file)
533 {
534 	OPENFILENAMEW of;
535 	gint retval;
536 	gchar tmp[MAX_PATH];
537 	wchar_t w_file[MAX_PATH];
538 	wchar_t w_title[512];
539 
540 	w_file[0] = '\0';
541 
542 	if (initial_file != NULL)
543 		MultiByteToWideChar(CP_UTF8, 0, initial_file, -1, w_file, G_N_ELEMENTS(w_file));
544 
545 	MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
546 
547 	/* initialise file dialog info struct */
548 	memset(&of, 0, sizeof of);
549 	of.lStructSize = sizeof of;
550 	of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(GTK_WIDGET(parent)));
551 
552 	of.lpstrFile = w_file;
553 	of.nMaxFile = 2048;
554 	of.lpstrFileTitle = NULL;
555 	of.lpstrTitle = w_title;
556 	of.lpstrDefExt = L"";
557 	of.Flags = OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLESIZING;
558 	of.lpfnHook = win32_dialog_explorer_hook_proc;
559 	retval = GetOpenFileNameW(&of);
560 
561 	if (! retval)
562 	{
563 		if (CommDlgExtendedError())
564 		{
565 			gchar *error = g_strdup_printf(
566 				"File dialog box error (%x)", (gint) CommDlgExtendedError());
567 			win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
568 			g_free(error);
569 		}
570 		return NULL;
571 	}
572 
573 	WideCharToMultiByte(CP_UTF8, 0, w_file, -1, tmp, sizeof(tmp), NULL, NULL);
574 
575 	return g_strdup(tmp);
576 }
577 
578 
win32_show_font_dialog(void)579 void win32_show_font_dialog(void)
580 {
581 	gint retval;
582 	CHOOSEFONT cf;
583 	LOGFONT lf;        /* logical font structure */
584 
585 	memset(&lf, 0, sizeof lf);
586 	/* TODO: init lf members */
587 
588 	memset(&cf, 0, sizeof cf);
589 	cf.lStructSize = sizeof cf;
590 	cf.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(main_widgets.window));
591 	cf.lpLogFont = &lf;
592 	/* support CF_APPLY? */
593 	cf.Flags = CF_NOSCRIPTSEL | CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_ENABLEHOOK;
594 	cf.lpfnHook = win32_dialog_hook_proc;
595 
596 	retval = ChooseFont(&cf);
597 
598 	if (retval)
599 	{
600 		gchar *editorfont = g_strdup_printf("%s %d", lf.lfFaceName, (cf.iPointSize / 10));
601 		ui_set_editor_font(editorfont);
602 		g_free(editorfont);
603 	}
604 }
605 
606 
win32_show_color_dialog(const gchar * colour)607 void win32_show_color_dialog(const gchar *colour)
608 {
609 	CHOOSECOLOR cc;
610 	static COLORREF acr_cust_clr[16];
611 	static DWORD rgb_current;
612 	gchar *hex = g_malloc0(12);
613 	GeanyDocument *doc = document_get_current();
614 
615 	/* Initialize CHOOSECOLOR */
616 	memset(&cc, 0, sizeof cc);
617 	cc.lStructSize = sizeof(cc);
618 	cc.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(main_widgets.window));
619 	cc.lpCustColors = (LPDWORD) acr_cust_clr;
620 	cc.rgbResult = (colour != NULL) ? utils_parse_color_to_bgr(colour) : 0;
621 	cc.Flags = CC_FULLOPEN | CC_RGBINIT | CC_ENABLEHOOK;
622 	cc.lpfnHook = win32_dialog_hook_proc;
623 
624 	if (ChooseColor(&cc))
625 	{
626 		rgb_current = cc.rgbResult;
627 		g_snprintf(hex, 11, "#%02X%02X%02X",
628 			GetRValue(rgb_current), GetGValue(rgb_current), GetBValue(rgb_current));
629 		editor_insert_color(doc->editor, hex);
630 	}
631 	g_free(hex);
632 }
633 
634 
win32_show_pref_file_dialog(GtkEntry * item)635 void win32_show_pref_file_dialog(GtkEntry *item)
636 {
637 	OPENFILENAMEW of;
638 	gint retval, len;
639 	wchar_t fname[MAX_PATH];
640 	gchar tmp[MAX_PATH];
641 	gchar **field, *filename;
642 
643 	fname[0] = '\0';
644 
645 	/* cut the options from the command line */
646 	field = g_strsplit(gtk_entry_get_text(GTK_ENTRY(item)), " ", 2);
647 	if (field[0])
648 	{
649 		filename = g_find_program_in_path(field[0]);
650 		if (filename != NULL && g_file_test(filename, G_FILE_TEST_EXISTS))
651 		{
652 			MultiByteToWideChar(CP_UTF8, 0, filename, -1, fname, G_N_ELEMENTS(fname));
653 			g_free(filename);
654 		}
655 	}
656 
657 	/* initialize file dialog info struct */
658 	memset(&of, 0, sizeof of);
659 	of.lStructSize = sizeof of;
660 	of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(ui_widgets.prefs_dialog));
661 
662 	of.lpstrFilter = get_filters(FALSE);
663 	of.lpstrCustomFilter = NULL;
664 	of.nFilterIndex = 1;
665 
666 	of.lpstrFile = fname;
667 	of.nMaxFile = 2048;
668 	of.lpstrFileTitle = NULL;
669 	/*of.lpstrInitialDir = g_get_home_dir();*/
670 	of.lpstrInitialDir = NULL;
671 	of.lpstrTitle = NULL;
672 	of.lpstrDefExt = L"exe";
673 	of.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_EXPLORER |
674 		OFN_ENABLEHOOK | OFN_ENABLESIZING;
675 	of.lpfnHook = win32_dialog_explorer_hook_proc;
676 	retval = GetOpenFileNameW(&of);
677 
678 	if (!retval)
679 	{
680 		if (CommDlgExtendedError())
681 		{
682 			gchar error[100];
683 			g_snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError());
684 			win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
685 		}
686 		g_strfreev(field);
687 		return;
688 	}
689 
690 	len = WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL);
691 	if ((of.nFileOffset - 1) != len)
692 	{
693 		if (g_strv_length(field) > 1)
694 			/* add the command line args of the old command */
695 			/** TODO this fails badly when the old command contained spaces, we need quoting here */
696 			filename = g_strconcat(tmp, " ", field[1], NULL);
697 		else
698 		{
699 			filename = g_strdup(tmp);
700 		}
701 		gtk_entry_set_text(GTK_ENTRY(item), filename);
702 		g_free(filename);
703 	}
704 	g_strfreev(field);
705 }
706 
707 
708 /* Creates a native Windows message box of the given type and returns always TRUE
709  * or FALSE representing th pressed Yes or No button.
710  * If type is not GTK_MESSAGE_QUESTION, it returns always TRUE. */
win32_message_dialog(GtkWidget * parent,GtkMessageType type,const gchar * msg)711 gboolean win32_message_dialog(GtkWidget *parent, GtkMessageType type, const gchar *msg)
712 {
713 	gboolean ret = TRUE;
714 	gint rc;
715 	guint t;
716 	const gchar *title;
717 	HWND parent_hwnd = NULL;
718 	static wchar_t w_msg[512];
719 	static wchar_t w_title[512];
720 
721 	switch (type)
722 	{
723 		case GTK_MESSAGE_ERROR:
724 		{
725 			t = MB_OK | MB_ICONERROR;
726 			title = _("Error");
727 			break;
728 		}
729 		case GTK_MESSAGE_QUESTION:
730 		{
731 			t = MB_YESNO | MB_ICONQUESTION;
732 			title = _("Question");
733 			break;
734 		}
735 		case GTK_MESSAGE_WARNING:
736 		{
737 			t = MB_OK | MB_ICONWARNING;
738 			title = _("Warning");
739 			break;
740 		}
741 		default:
742 		{
743 			t = MB_OK | MB_ICONINFORMATION;
744 			title = _("Information");
745 			break;
746 		}
747 	}
748 
749 	/* convert the Unicode chars to wide chars */
750 	/** TODO test if LANG == C then possibly skip conversion => g_win32_getlocale() */
751 	MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, G_N_ELEMENTS(w_msg));
752 	MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
753 
754 	if (parent != NULL)
755 		parent_hwnd = GDK_WINDOW_HWND(gtk_widget_get_window(parent));
756 	else if (main_widgets.window != NULL)
757 		parent_hwnd = GDK_WINDOW_HWND(gtk_widget_get_window(main_widgets.window));
758 
759 	/* display the message box */
760 	rc = MessageBoxW(parent_hwnd, w_msg, w_title, t);
761 
762 	if (type == GTK_MESSAGE_QUESTION && rc != IDYES)
763 		ret = FALSE;
764 
765 	return ret;
766 }
767 
768 
769 /* Little wrapper for _waccess(), returns errno or 0 if there was no error */
win32_check_write_permission(const gchar * dir)770 gint win32_check_write_permission(const gchar *dir)
771 {
772 	static wchar_t w_dir[MAX_PATH];
773 	MultiByteToWideChar(CP_UTF8, 0, dir, -1, w_dir, G_N_ELEMENTS(w_dir));
774 	if (_waccess(w_dir, R_OK | W_OK) != 0)
775 		return errno;
776 	else
777 		return 0;
778 }
779 
780 
781 /* Just a simple wrapper function to open a browser window */
win32_open_browser(const gchar * uri)782 void win32_open_browser(const gchar *uri)
783 {
784 	gint ret;
785 	if (strncmp(uri, "file://", 7) == 0)
786 	{
787 		uri += 7;
788 		if (strchr(uri, ':') != NULL)
789 		{
790 			while (*uri == '/')
791 				uri++;
792 		}
793 	}
794 	ret = (gint) ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOWNORMAL);
795 	if (ret <= 32)
796 	{
797 		gchar *err = g_win32_error_message(GetLastError());
798 		ui_set_statusbar(TRUE, _("Failed to open URI \"%s\": %s"), uri, err);
799 		g_warning("ShellExecute failed opening \"%s\" (code %d): %s", uri, ret, err);
800 		g_free(err);
801 	}
802 }
803 
804 
open_std_handle(DWORD handle,const char * mode)805 static FILE *open_std_handle(DWORD handle, const char *mode)
806 {
807 	HANDLE lStdHandle;
808 	int hConHandle;
809 	FILE *fp;
810 
811 	lStdHandle = GetStdHandle(handle);
812 	if (lStdHandle == INVALID_HANDLE_VALUE)
813 	{
814 		gchar *err = g_win32_error_message(GetLastError());
815 		g_warning("GetStdHandle(%ld) failed: %s", (long)handle, err);
816 		g_free(err);
817 		return NULL;
818 	}
819 	hConHandle = _open_osfhandle((intptr_t)lStdHandle, _O_TEXT);
820 	if (hConHandle == -1)
821 	{
822 		gchar *err = g_win32_error_message(GetLastError());
823 		g_warning("_open_osfhandle(handle(%ld), _O_TEXT) failed: %s", (long)handle, err);
824 		g_free(err);
825 		return NULL;
826 	}
827 	fp = _fdopen(hConHandle, mode);
828 	if (! fp)
829 	{
830 		gchar *err = g_win32_error_message(GetLastError());
831 		g_warning("_fdopen(%d, \"%s\") failed: %s", hConHandle, mode, err);
832 		g_free(err);
833 		return NULL;
834 	}
835 	if (setvbuf(fp, NULL, _IONBF, 0) != 0)
836 	{
837 		gchar *err = g_win32_error_message(GetLastError());
838 		g_warning("setvbuf(%p, NULL, _IONBF, 0) failed: %s", fp, err);
839 		g_free(err);
840 		fclose(fp);
841 		return NULL;
842 	}
843 
844 	return fp;
845 }
846 
847 
debug_setup_console(void)848 static void debug_setup_console(void)
849 {
850 	static const WORD MAX_CONSOLE_LINES = 500;
851 	CONSOLE_SCREEN_BUFFER_INFO coninfo;
852 	FILE	*fp;
853 
854 	/* allocate a console for this app */
855 	AllocConsole();
856 
857 	/* set the screen buffer to be big enough to let us scroll text */
858 	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
859 	coninfo.dwSize.Y = MAX_CONSOLE_LINES;
860 	SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
861 
862 	/* redirect unbuffered STDOUT to the console */
863 	fp = open_std_handle(STD_OUTPUT_HANDLE, "w");
864 	if (fp)
865 		*stdout = *fp;
866 
867 	/* redirect unbuffered STDERR to the console */
868 	fp = open_std_handle(STD_ERROR_HANDLE, "w");
869 	if (fp)
870 		*stderr = *fp;
871 
872 	/* redirect unbuffered STDIN to the console */
873 	fp = open_std_handle(STD_INPUT_HANDLE, "r");
874 	if (fp)
875 		*stdin = *fp;
876 }
877 
878 
win32_init_debug_code(void)879 void win32_init_debug_code(void)
880 {
881 	if (app->debug_mode)
882 	{
883 		/* create a console window to get log messages on Windows,
884 		 * especially useful when generating tags files */
885 		debug_setup_console();
886 	}
887 }
888 
889 
890 /* expands environment placeholders in @str.  input and output is in UTF-8 */
win32_expand_environment_variables(const gchar * str)891 gchar *win32_expand_environment_variables(const gchar *str)
892 {
893 	wchar_t *cmdline = g_utf8_to_utf16(str, -1, NULL, NULL, NULL);
894 	wchar_t expCmdline[32768]; /* 32768 is the limit for ExpandEnvironmentStrings() */
895 	gchar *expanded = NULL;
896 
897 	if (cmdline && ExpandEnvironmentStringsW(cmdline, expCmdline, sizeof(expCmdline)) != 0)
898 		expanded = g_utf16_to_utf8(expCmdline, -1, NULL, NULL, NULL);
899 
900 	g_free(cmdline);
901 
902 	return expanded ? expanded : g_strdup(str);
903 }
904 
905 
906 /* From GDK (they got it from MS Knowledge Base article Q130698) */
resolve_link(HWND hWnd,wchar_t * link,gchar ** lpszPath)907 static gboolean resolve_link(HWND hWnd, wchar_t *link, gchar **lpszPath)
908 {
909 	WIN32_FILE_ATTRIBUTE_DATA wfad;
910 	HRESULT hres;
911 	IShellLinkW *pslW = NULL;
912 	IPersistFile *ppf = NULL;
913 	LPVOID pslWV = NULL;
914 	LPVOID ppfV = NULL;
915 
916 	/* Check if the file is empty first because IShellLink::Resolve for some reason succeeds
917 	 * with an empty file and returns an empty "link target". (#524151) */
918 	if (!GetFileAttributesExW(link, GetFileExInfoStandard, &wfad) ||
919 		(wfad.nFileSizeHigh == 0 && wfad.nFileSizeLow == 0))
920 	{
921 	  return FALSE;
922 	}
923 
924 	/* Assume failure to start with: */
925 	*lpszPath = 0;
926 
927 	CoInitialize(NULL);
928 
929 	hres = CoCreateInstance(
930 		&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, &pslWV);
931 
932 	if (SUCCEEDED(hres))
933 	{
934 		/* The IShellLink interface supports the IPersistFile interface.
935 		 * Get an interface pointer to it. */
936 		pslW = (IShellLinkW*) pslWV;
937 		hres = pslW->lpVtbl->QueryInterface(pslW, &IID_IPersistFile, &ppfV);
938 	}
939 
940 	if (SUCCEEDED(hres))
941 	{
942 		/* Load the file. */
943 		ppf = (IPersistFile*) ppfV;
944 		hres = ppf->lpVtbl->Load(ppf, link, STGM_READ);
945 	}
946 
947 	if (SUCCEEDED(hres))
948 	{
949 		/* Resolve the link by calling the Resolve() interface function. */
950 		hres = pslW->lpVtbl->Resolve(pslW, hWnd, SLR_ANY_MATCH | SLR_NO_UI);
951 	}
952 
953 	if (SUCCEEDED(hres))
954 	{
955 		wchar_t wtarget[MAX_PATH];
956 
957 		hres = pslW->lpVtbl->GetPath(pslW, wtarget, MAX_PATH, NULL, 0);
958 		if (SUCCEEDED(hres))
959 			*lpszPath = g_utf16_to_utf8(wtarget, -1, NULL, NULL, NULL);
960 	}
961 
962 	if (ppf)
963 		ppf->lpVtbl->Release(ppf);
964 
965 	if (pslW)
966 		pslW->lpVtbl->Release(pslW);
967 
968 	return SUCCEEDED(hres);
969 }
970 
971 
972 /* Checks whether file_name is a Windows shortcut. file_name is expected in UTF-8 encoding.
973  * If file_name is a Windows shortcut, it returns the target in UTF-8 encoding.
974  * If it is not a shortcut, it returns a newly allocated copy of file_name. */
win32_get_shortcut_target(const gchar * file_name)975 gchar *win32_get_shortcut_target(const gchar *file_name)
976 {
977 	gchar *path = NULL;
978 	wchar_t *wfilename = g_utf8_to_utf16(file_name, -1, NULL, NULL, NULL);
979 	HWND hWnd = NULL;
980 
981 	if (main_widgets.window != NULL)
982 	{
983 		GdkWindow *window = gtk_widget_get_window(main_widgets.window);
984 		if (window != NULL)
985 			hWnd = GDK_WINDOW_HWND(window);
986 	}
987 
988 	resolve_link(hWnd, wfilename, &path);
989 	g_free(wfilename);
990 
991 	if (path == NULL)
992 		return g_strdup(file_name);
993 	else
994 		return path;
995 }
996 
997 
win32_set_working_directory(const gchar * dir)998 void win32_set_working_directory(const gchar *dir)
999 {
1000 	SetCurrentDirectory(dir);
1001 }
1002 
1003 
win32_get_installation_dir(void)1004 gchar *win32_get_installation_dir(void)
1005 {
1006 	return g_win32_get_package_installation_directory_of_module(NULL);
1007 }
1008 
1009 
win32_get_user_config_dir(void)1010 gchar *win32_get_user_config_dir(void)
1011 {
1012 	HRESULT hr;
1013 	wchar_t path[MAX_PATH];
1014 
1015 	hr = SHGetFolderPathAndSubDirW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, L"geany", path);
1016 	if (SUCCEEDED(hr))
1017 	{
1018 		// GLib always uses UTF-8 for filename encoding on Windows
1019 		int u8_size = WideCharToMultiByte(CP_UTF8, 0, path, -1, NULL, 0, NULL, NULL);
1020 		if (u8_size > 0)
1021 		{
1022 			gchar *u8_path = g_malloc0(u8_size + 1);
1023 			if (u8_path != NULL &&
1024 				WideCharToMultiByte(CP_UTF8, 0, path, -1, u8_path, u8_size, NULL, NULL))
1025 			{
1026 				return u8_path;
1027 			}
1028 		}
1029 	}
1030 
1031 	// glib fallback
1032 	g_warning("Failed to retrieve Windows config dir, falling back to default");
1033 	return g_build_filename(g_get_user_config_dir(), "geany", NULL);
1034 }
1035 
1036 #endif
1037