1037 lines
27 KiB
C
1037 lines
27 KiB
C
/*
|
|
* win32.c - this file is part of Geany, a fast and lightweight IDE
|
|
*
|
|
* Copyright 2005 The Geany contributors
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
/*
|
|
* Special functions for the win32 platform, to provide native dialogs.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
/* Need Windows XP for SHGetFolderPathAndSubDirW */
|
|
#define _WIN32_WINNT 0x0501
|
|
/* Needed for SHGFP_TYPE */
|
|
#define _WIN32_IE 0x0500
|
|
|
|
#include "win32.h"
|
|
|
|
#ifdef G_OS_WIN32
|
|
|
|
#include "dialogs.h"
|
|
#include "document.h"
|
|
#include "editor.h"
|
|
#include "filetypes.h"
|
|
#include "project.h"
|
|
#include "support.h"
|
|
#include "ui_utils.h"
|
|
#include "utils.h"
|
|
|
|
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <windows.h>
|
|
#include <commdlg.h>
|
|
#include <shellapi.h>
|
|
#include <shlobj.h>
|
|
|
|
#include <glib/gstdio.h>
|
|
#include <gdk/gdkwin32.h>
|
|
|
|
|
|
/* The timer handle used to refresh windows below modal native dialogs. If
|
|
* ever more than one dialog can be shown at a time, this needs to be changed
|
|
* to be for specific dialogs. */
|
|
static UINT_PTR dialog_timer = 0;
|
|
|
|
|
|
G_INLINE_FUNC void win32_dialog_reset_timer(HWND hwnd)
|
|
{
|
|
if (G_UNLIKELY(dialog_timer != 0))
|
|
{
|
|
KillTimer(hwnd, dialog_timer);
|
|
dialog_timer = 0;
|
|
}
|
|
}
|
|
|
|
|
|
VOID CALLBACK
|
|
win32_dialog_update_main_window(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
|
|
{
|
|
gint i;
|
|
|
|
/* Pump the main window loop a bit, but not enough to lock-up.
|
|
* The typical `while(gtk_events_pending()) gtk_main_iteration();`
|
|
* loop causes the entire operating system to lock-up. */
|
|
for (i = 0; i < 4 && gtk_events_pending(); i++)
|
|
gtk_main_iteration();
|
|
|
|
/* Cancel any pending timers since we just did an update */
|
|
win32_dialog_reset_timer(hwnd);
|
|
}
|
|
|
|
|
|
G_INLINE_FUNC UINT_PTR win32_dialog_queue_main_window_redraw(HWND dlg, UINT msg,
|
|
WPARAM wParam, LPARAM lParam, gboolean postpone)
|
|
{
|
|
switch (msg)
|
|
{
|
|
/* Messages that likely mean the window below a dialog needs to be re-drawn. */
|
|
case WM_WINDOWPOSCHANGED:
|
|
case WM_MOVE:
|
|
case WM_SIZE:
|
|
case WM_THEMECHANGED:
|
|
if (postpone)
|
|
{
|
|
win32_dialog_reset_timer(dlg);
|
|
dialog_timer = SetTimer(dlg, 0, 33 /* around 30fps */, win32_dialog_update_main_window);
|
|
}
|
|
else
|
|
win32_dialog_update_main_window(dlg, msg, wParam, lParam);
|
|
break;
|
|
}
|
|
return 0; /* always let the default proc handle it */
|
|
}
|
|
|
|
|
|
/* This function is called for OPENFILENAME lpfnHook function and it establishes
|
|
* a timer that is reset each time which will update the main window loop eventually. */
|
|
UINT_PTR CALLBACK win32_dialog_explorer_hook_proc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
return win32_dialog_queue_main_window_redraw(dlg, msg, wParam, lParam, TRUE);
|
|
}
|
|
|
|
|
|
/* This function is called for old-school win32 dialogs that accept a proper
|
|
* lpfnHook function for all messages, it doesn't use a timer. */
|
|
UINT_PTR CALLBACK win32_dialog_hook_proc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
return win32_dialog_queue_main_window_redraw(dlg, msg, wParam, lParam, FALSE);
|
|
}
|
|
|
|
|
|
static wchar_t *get_file_filters(void)
|
|
{
|
|
gchar *string;
|
|
guint i, j, len;
|
|
static wchar_t title[4096];
|
|
GString *str = g_string_sized_new(100);
|
|
GString *all_patterns = g_string_sized_new(100);
|
|
GSList *node;
|
|
gchar *tmp;
|
|
|
|
/* create meta file filter "All files" */
|
|
g_string_append_printf(str, "%s\t*\t", _("All files"));
|
|
/* create meta file filter "All Source" (skip GEANY_FILETYPES_NONE) */
|
|
for (i = GEANY_FILETYPES_NONE + 1; i < filetypes_array->len; i++)
|
|
{
|
|
for (j = 0; filetypes[i]->pattern[j] != NULL; j++)
|
|
{
|
|
g_string_append(all_patterns, filetypes[i]->pattern[j]);
|
|
g_string_append_c(all_patterns, ';');
|
|
}
|
|
}
|
|
g_string_append_printf(str, "%s\t%s\t", _("All Source"), all_patterns->str);
|
|
g_string_free(all_patterns, TRUE);
|
|
/* add 'usual' filetypes */
|
|
foreach_slist(node, filetypes_by_title)
|
|
{
|
|
GeanyFiletype *ft = node->data;
|
|
|
|
if (G_UNLIKELY(ft->id == GEANY_FILETYPES_NONE))
|
|
continue;
|
|
tmp = g_strjoinv(";", ft->pattern);
|
|
g_string_append_printf(str, "%s\t%s\t", ft->title, tmp);
|
|
g_free(tmp);
|
|
}
|
|
g_string_append_c(str, '\t'); /* the final \0 byte to mark the end of the string */
|
|
string = str->str;
|
|
g_string_free(str, FALSE);
|
|
|
|
/* replace all "\t"s by \0 */
|
|
len = strlen(string);
|
|
g_strdelimit(string, "\t", '\0');
|
|
g_assert(string[len - 1] == 0x0);
|
|
MultiByteToWideChar(CP_UTF8, 0, string, len, title, G_N_ELEMENTS(title));
|
|
g_free(string);
|
|
|
|
return title;
|
|
}
|
|
|
|
|
|
static wchar_t *get_file_filter_all_files(void)
|
|
{
|
|
guint len;
|
|
static wchar_t title[4096];
|
|
gchar *filter;
|
|
|
|
/* create meta file filter "All files" */
|
|
filter = g_strdup_printf("%s\t*\t", _("All files"));
|
|
|
|
len = strlen(filter);
|
|
g_strdelimit(filter, "\t", '\0');
|
|
g_assert(filter[len - 1] == 0x0);
|
|
MultiByteToWideChar(CP_UTF8, 0, filter, len, title, G_N_ELEMENTS(title));
|
|
g_free(filter);
|
|
|
|
return title;
|
|
}
|
|
|
|
|
|
static wchar_t *get_filters(gboolean project_files)
|
|
{
|
|
gchar *string;
|
|
gint len;
|
|
static wchar_t title[1024];
|
|
|
|
if (project_files)
|
|
{
|
|
string = g_strconcat(_("Geany project files"), "\t", "*." GEANY_PROJECT_EXT, "\t",
|
|
_("All files"), "\t", "*", "\t", NULL);
|
|
}
|
|
else
|
|
{
|
|
string = g_strconcat(_("Executables"), "\t", "*.exe;*.bat;*.cmd", "\t",
|
|
_("All files"), "\t", "*", "\t", NULL);
|
|
}
|
|
|
|
/* replace all "\t"s by \0 */
|
|
len = strlen(string);
|
|
g_strdelimit(string, "\t", '\0');
|
|
g_assert(string[len - 1] == 0x0);
|
|
MultiByteToWideChar(CP_UTF8, 0, string, len, title, G_N_ELEMENTS(title));
|
|
g_free(string);
|
|
|
|
return title;
|
|
}
|
|
|
|
|
|
/* Converts the given UTF-8 filename or directory name into something usable for Windows and
|
|
* returns the directory part of the given filename. */
|
|
static wchar_t *get_dir_for_path(const gchar *utf8_filename)
|
|
{
|
|
static wchar_t w_dir[MAX_PATH];
|
|
gchar *result;
|
|
|
|
if (g_file_test(utf8_filename, G_FILE_TEST_IS_DIR))
|
|
result = (gchar*) utf8_filename;
|
|
else
|
|
result = g_path_get_dirname(utf8_filename);
|
|
|
|
MultiByteToWideChar(CP_UTF8, 0, result, -1, w_dir, G_N_ELEMENTS(w_dir));
|
|
|
|
if (result != utf8_filename)
|
|
g_free(result);
|
|
|
|
return w_dir;
|
|
}
|
|
|
|
|
|
/* Callback function for setting the initial directory of the folder open dialog. This could also
|
|
* be done with BROWSEINFO.pidlRoot and SHParseDisplayName but SHParseDisplayName is not available
|
|
* on systems below Windows XP. So, we go the hard way by creating a callback which will set up the
|
|
* folder when the dialog is initialised. Yeah, I like Windows. */
|
|
INT CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData)
|
|
{
|
|
win32_dialog_hook_proc(hwnd, uMsg, lp, pData);
|
|
switch (uMsg)
|
|
{
|
|
case BFFM_INITIALIZED:
|
|
{
|
|
SendMessageW(hwnd, BFFM_SETSELECTIONW, TRUE, pData);
|
|
break;
|
|
}
|
|
case BFFM_SELCHANGED:
|
|
{
|
|
/* set the status window to the currently selected path. */
|
|
static wchar_t szDir[MAX_PATH];
|
|
if (SHGetPathFromIDListW((LPITEMIDLIST) lp, szDir))
|
|
{
|
|
SendMessageW(hwnd, BFFM_SETSTATUSTEXTW, 0, (LPARAM) szDir);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Shows a folder selection dialog.
|
|
* initial_dir is expected in UTF-8
|
|
* The selected folder name is returned. */
|
|
gchar *win32_show_folder_dialog(GtkWidget *parent, const gchar *title, const gchar *initial_dir)
|
|
{
|
|
BROWSEINFOW bi;
|
|
LPITEMIDLIST pidl;
|
|
gchar *result = NULL;
|
|
wchar_t fname[MAX_PATH];
|
|
wchar_t w_title[512];
|
|
|
|
MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
|
|
|
|
if (parent == NULL)
|
|
parent = main_widgets.window;
|
|
|
|
memset(&bi, 0, sizeof bi);
|
|
bi.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(parent));
|
|
bi.pidlRoot = NULL;
|
|
bi.lpszTitle = w_title;
|
|
bi.lpfn = BrowseCallbackProc;
|
|
bi.lParam = (LPARAM) get_dir_for_path(initial_dir);
|
|
bi.ulFlags = BIF_DONTGOBELOWDOMAIN | BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT | BIF_USENEWUI;
|
|
|
|
pidl = SHBrowseForFolderW(&bi);
|
|
|
|
/* convert the strange Windows folder list item something into an usual path string ;-) */
|
|
if (pidl != NULL)
|
|
{
|
|
if (SHGetPathFromIDListW(pidl, fname))
|
|
{
|
|
result = g_malloc0(MAX_PATH * 2);
|
|
WideCharToMultiByte(CP_UTF8, 0, fname, -1, result, MAX_PATH * 2, NULL, NULL);
|
|
}
|
|
CoTaskMemFree(pidl);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Shows a file open dialog.
|
|
* If allow_new_file is set, the file to be opened doesn't have to exist.
|
|
* initial_dir is expected in UTF-8
|
|
* The selected file name is returned.
|
|
* If project_file_filter is set, the file open dialog will have a file filter for Geany project
|
|
* files, a filter for executables otherwise. */
|
|
gchar *win32_show_project_open_dialog(GtkWidget *parent, const gchar *title,
|
|
const gchar *initial_dir, gboolean allow_new_file,
|
|
gboolean project_file_filter)
|
|
{
|
|
OPENFILENAMEW of;
|
|
gint retval;
|
|
wchar_t fname[MAX_PATH];
|
|
wchar_t w_title[512];
|
|
gchar *filename;
|
|
|
|
fname[0] = '\0';
|
|
|
|
MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
|
|
|
|
if (parent == NULL)
|
|
parent = main_widgets.window;
|
|
|
|
/* initialise file dialog info struct */
|
|
memset(&of, 0, sizeof of);
|
|
of.lStructSize = sizeof of;
|
|
of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(parent));
|
|
of.lpstrFilter = get_filters(project_file_filter);
|
|
|
|
of.lpstrCustomFilter = NULL;
|
|
of.nFilterIndex = 0;
|
|
of.lpstrFile = fname;
|
|
of.lpstrInitialDir = get_dir_for_path(initial_dir);
|
|
of.nMaxFile = 2048;
|
|
of.lpstrFileTitle = NULL;
|
|
of.lpstrTitle = w_title;
|
|
of.lpstrDefExt = L"";
|
|
of.Flags = OFN_PATHMUSTEXIST | OFN_EXPLORER | OFN_HIDEREADONLY |
|
|
OFN_ENABLEHOOK | OFN_ENABLESIZING;
|
|
of.lpfnHook = win32_dialog_explorer_hook_proc;
|
|
if (! allow_new_file)
|
|
of.Flags |= OFN_FILEMUSTEXIST;
|
|
|
|
retval = GetOpenFileNameW(&of);
|
|
|
|
if (! retval)
|
|
{
|
|
if (CommDlgExtendedError())
|
|
{
|
|
gchar *error;
|
|
error = g_strdup_printf("File dialog box error (%x)", (int)CommDlgExtendedError());
|
|
win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
|
|
g_free(error);
|
|
}
|
|
return NULL;
|
|
}
|
|
/* convert the resulting filename into UTF-8 (from whatever encoding it has at this moment) */
|
|
filename = g_malloc0(MAX_PATH * 2);
|
|
WideCharToMultiByte(CP_UTF8, 0, fname, -1, filename, MAX_PATH * 2, NULL, NULL);
|
|
|
|
return filename;
|
|
}
|
|
|
|
|
|
/* initial_dir can be NULL to use the current working directory.
|
|
* Returns: TRUE if the dialog was not cancelled. */
|
|
gboolean win32_show_document_open_dialog(GtkWindow *parent, const gchar *title, const gchar *initial_dir)
|
|
{
|
|
OPENFILENAMEW of;
|
|
gint retval;
|
|
guint x;
|
|
gchar tmp[MAX_PATH];
|
|
wchar_t fname[MAX_PATH];
|
|
wchar_t w_dir[MAX_PATH];
|
|
wchar_t w_title[512];
|
|
|
|
fname[0] = '\0';
|
|
|
|
if (initial_dir != NULL)
|
|
MultiByteToWideChar(CP_UTF8, 0, initial_dir, -1, w_dir, G_N_ELEMENTS(w_dir));
|
|
|
|
MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
|
|
|
|
/* initialise file dialog info struct */
|
|
memset(&of, 0, sizeof of);
|
|
of.lStructSize = sizeof of;
|
|
of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(GTK_WIDGET(parent)));
|
|
of.lpstrFilter = get_file_filters();
|
|
|
|
of.lpstrCustomFilter = NULL;
|
|
of.nFilterIndex = GEANY_FILETYPES_NONE + 1;
|
|
of.lpstrFile = fname;
|
|
of.lpstrInitialDir = (initial_dir != NULL) ? w_dir : NULL;
|
|
of.nMaxFile = 2048;
|
|
of.lpstrFileTitle = NULL;
|
|
of.lpstrTitle = w_title;
|
|
of.lpstrDefExt = L"";
|
|
of.Flags = OFN_ALLOWMULTISELECT | OFN_FILEMUSTEXIST | OFN_EXPLORER |
|
|
OFN_ENABLEHOOK | OFN_ENABLESIZING;
|
|
of.lpfnHook = win32_dialog_explorer_hook_proc;
|
|
|
|
retval = GetOpenFileNameW(&of);
|
|
|
|
if (!retval)
|
|
{
|
|
if (CommDlgExtendedError())
|
|
{
|
|
gchar error[100];
|
|
g_snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError());
|
|
win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
x = of.nFileOffset - 1;
|
|
if (x != wcslen(fname))
|
|
{ /* open a single file */
|
|
WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL);
|
|
document_open_file(tmp, of.Flags & OFN_READONLY, NULL, NULL);
|
|
}
|
|
else
|
|
{ /* open multiple files */
|
|
gchar file_name[MAX_PATH];
|
|
gchar dir_name[MAX_PATH];
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, fname, of.nFileOffset,
|
|
dir_name, sizeof(dir_name), NULL, NULL);
|
|
for (; ;)
|
|
{
|
|
if (! fname[x])
|
|
{
|
|
if (! fname[x + 1])
|
|
break;
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, fname + x + 1, -1,
|
|
tmp, sizeof(tmp), NULL, NULL);
|
|
g_snprintf(file_name, 511, "%s\\%s", dir_name, tmp);
|
|
|
|
/* convert the resulting filename into UTF-8 */
|
|
document_open_file(file_name, of.Flags & OFN_READONLY, NULL, NULL);
|
|
}
|
|
x++;
|
|
}
|
|
}
|
|
return (retval != 0);
|
|
}
|
|
|
|
|
|
gchar *win32_show_document_save_as_dialog(GtkWindow *parent, const gchar *title,
|
|
GeanyDocument *doc)
|
|
{
|
|
OPENFILENAMEW of;
|
|
gint retval;
|
|
gchar tmp[MAX_PATH];
|
|
wchar_t w_file[MAX_PATH];
|
|
wchar_t w_title[512];
|
|
int n;
|
|
|
|
w_file[0] = '\0';
|
|
|
|
/* Convert the name of the file for of.lpstrFile */
|
|
n = MultiByteToWideChar(CP_UTF8, 0, DOC_FILENAME(doc), -1, w_file, G_N_ELEMENTS(w_file));
|
|
|
|
/* If creating a new file name, convert and append the extension if any */
|
|
if (! doc->file_name && doc->file_type && doc->file_type->extension &&
|
|
n + 1 < (int)G_N_ELEMENTS(w_file))
|
|
{
|
|
w_file[n - 1] = L'.';
|
|
MultiByteToWideChar(CP_UTF8, 0, doc->file_type->extension, -1, &w_file[n],
|
|
G_N_ELEMENTS(w_file) - n - 1);
|
|
}
|
|
|
|
MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
|
|
|
|
/* initialise file dialog info struct */
|
|
memset(&of, 0, sizeof of);
|
|
of.lStructSize = sizeof of;
|
|
of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(GTK_WIDGET(parent)));
|
|
|
|
of.lpstrFilter = get_file_filter_all_files();
|
|
of.lpstrCustomFilter = NULL;
|
|
of.nFilterIndex = 0;
|
|
|
|
of.lpstrFile = w_file;
|
|
of.nMaxFile = 2048;
|
|
of.lpstrFileTitle = NULL;
|
|
of.lpstrTitle = w_title;
|
|
of.lpstrDefExt = L"";
|
|
of.Flags = OFN_OVERWRITEPROMPT | OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLESIZING;
|
|
of.lpfnHook = win32_dialog_explorer_hook_proc;
|
|
retval = GetSaveFileNameW(&of);
|
|
|
|
if (! retval)
|
|
{
|
|
if (CommDlgExtendedError())
|
|
{
|
|
gchar *error = g_strdup_printf(
|
|
"File dialog box error (%x)", (gint) CommDlgExtendedError());
|
|
win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
|
|
g_free(error);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, w_file, -1, tmp, sizeof(tmp), NULL, NULL);
|
|
|
|
return g_strdup(tmp);
|
|
}
|
|
|
|
|
|
/* initial_dir can be NULL to use the current working directory.
|
|
* Returns: the selected filename */
|
|
gchar *win32_show_file_dialog(GtkWindow *parent, const gchar *title, const gchar *initial_file)
|
|
{
|
|
OPENFILENAMEW of;
|
|
gint retval;
|
|
gchar tmp[MAX_PATH];
|
|
wchar_t w_file[MAX_PATH];
|
|
wchar_t w_title[512];
|
|
|
|
w_file[0] = '\0';
|
|
|
|
if (initial_file != NULL)
|
|
MultiByteToWideChar(CP_UTF8, 0, initial_file, -1, w_file, G_N_ELEMENTS(w_file));
|
|
|
|
MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
|
|
|
|
/* initialise file dialog info struct */
|
|
memset(&of, 0, sizeof of);
|
|
of.lStructSize = sizeof of;
|
|
of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(GTK_WIDGET(parent)));
|
|
|
|
of.lpstrFile = w_file;
|
|
of.nMaxFile = 2048;
|
|
of.lpstrFileTitle = NULL;
|
|
of.lpstrTitle = w_title;
|
|
of.lpstrDefExt = L"";
|
|
of.Flags = OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLESIZING;
|
|
of.lpfnHook = win32_dialog_explorer_hook_proc;
|
|
retval = GetOpenFileNameW(&of);
|
|
|
|
if (! retval)
|
|
{
|
|
if (CommDlgExtendedError())
|
|
{
|
|
gchar *error = g_strdup_printf(
|
|
"File dialog box error (%x)", (gint) CommDlgExtendedError());
|
|
win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
|
|
g_free(error);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, w_file, -1, tmp, sizeof(tmp), NULL, NULL);
|
|
|
|
return g_strdup(tmp);
|
|
}
|
|
|
|
|
|
void win32_show_font_dialog(void)
|
|
{
|
|
gint retval;
|
|
CHOOSEFONT cf;
|
|
LOGFONT lf; /* logical font structure */
|
|
|
|
memset(&lf, 0, sizeof lf);
|
|
/* TODO: init lf members */
|
|
|
|
memset(&cf, 0, sizeof cf);
|
|
cf.lStructSize = sizeof cf;
|
|
cf.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(main_widgets.window));
|
|
cf.lpLogFont = &lf;
|
|
/* support CF_APPLY? */
|
|
cf.Flags = CF_NOSCRIPTSEL | CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_ENABLEHOOK;
|
|
cf.lpfnHook = win32_dialog_hook_proc;
|
|
|
|
retval = ChooseFont(&cf);
|
|
|
|
if (retval)
|
|
{
|
|
gchar *editorfont = g_strdup_printf("%s %d", lf.lfFaceName, (cf.iPointSize / 10));
|
|
ui_set_editor_font(editorfont);
|
|
g_free(editorfont);
|
|
}
|
|
}
|
|
|
|
|
|
void win32_show_color_dialog(const gchar *colour)
|
|
{
|
|
CHOOSECOLOR cc;
|
|
static COLORREF acr_cust_clr[16];
|
|
static DWORD rgb_current;
|
|
gchar *hex = g_malloc0(12);
|
|
GeanyDocument *doc = document_get_current();
|
|
|
|
/* Initialize CHOOSECOLOR */
|
|
memset(&cc, 0, sizeof cc);
|
|
cc.lStructSize = sizeof(cc);
|
|
cc.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(main_widgets.window));
|
|
cc.lpCustColors = (LPDWORD) acr_cust_clr;
|
|
cc.rgbResult = (colour != NULL) ? utils_parse_color_to_bgr(colour) : 0;
|
|
cc.Flags = CC_FULLOPEN | CC_RGBINIT | CC_ENABLEHOOK;
|
|
cc.lpfnHook = win32_dialog_hook_proc;
|
|
|
|
if (ChooseColor(&cc))
|
|
{
|
|
rgb_current = cc.rgbResult;
|
|
g_snprintf(hex, 11, "#%02X%02X%02X",
|
|
GetRValue(rgb_current), GetGValue(rgb_current), GetBValue(rgb_current));
|
|
editor_insert_color(doc->editor, hex);
|
|
}
|
|
g_free(hex);
|
|
}
|
|
|
|
|
|
void win32_show_pref_file_dialog(GtkEntry *item)
|
|
{
|
|
OPENFILENAMEW of;
|
|
gint retval, len;
|
|
wchar_t fname[MAX_PATH];
|
|
gchar tmp[MAX_PATH];
|
|
gchar **field, *filename;
|
|
|
|
fname[0] = '\0';
|
|
|
|
/* cut the options from the command line */
|
|
field = g_strsplit(gtk_entry_get_text(GTK_ENTRY(item)), " ", 2);
|
|
if (field[0])
|
|
{
|
|
filename = g_find_program_in_path(field[0]);
|
|
if (filename != NULL && g_file_test(filename, G_FILE_TEST_EXISTS))
|
|
{
|
|
MultiByteToWideChar(CP_UTF8, 0, filename, -1, fname, G_N_ELEMENTS(fname));
|
|
g_free(filename);
|
|
}
|
|
}
|
|
|
|
/* initialize file dialog info struct */
|
|
memset(&of, 0, sizeof of);
|
|
of.lStructSize = sizeof of;
|
|
of.hwndOwner = GDK_WINDOW_HWND(gtk_widget_get_window(ui_widgets.prefs_dialog));
|
|
|
|
of.lpstrFilter = get_filters(FALSE);
|
|
of.lpstrCustomFilter = NULL;
|
|
of.nFilterIndex = 1;
|
|
|
|
of.lpstrFile = fname;
|
|
of.nMaxFile = 2048;
|
|
of.lpstrFileTitle = NULL;
|
|
/*of.lpstrInitialDir = g_get_home_dir();*/
|
|
of.lpstrInitialDir = NULL;
|
|
of.lpstrTitle = NULL;
|
|
of.lpstrDefExt = L"exe";
|
|
of.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_EXPLORER |
|
|
OFN_ENABLEHOOK | OFN_ENABLESIZING;
|
|
of.lpfnHook = win32_dialog_explorer_hook_proc;
|
|
retval = GetOpenFileNameW(&of);
|
|
|
|
if (!retval)
|
|
{
|
|
if (CommDlgExtendedError())
|
|
{
|
|
gchar error[100];
|
|
g_snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError());
|
|
win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
|
|
}
|
|
g_strfreev(field);
|
|
return;
|
|
}
|
|
|
|
len = WideCharToMultiByte(CP_UTF8, 0, fname, -1, tmp, sizeof(tmp), NULL, NULL);
|
|
if ((of.nFileOffset - 1) != len)
|
|
{
|
|
if (g_strv_length(field) > 1)
|
|
/* add the command line args of the old command */
|
|
/** TODO this fails badly when the old command contained spaces, we need quoting here */
|
|
filename = g_strconcat(tmp, " ", field[1], NULL);
|
|
else
|
|
{
|
|
filename = g_strdup(tmp);
|
|
}
|
|
gtk_entry_set_text(GTK_ENTRY(item), filename);
|
|
g_free(filename);
|
|
}
|
|
g_strfreev(field);
|
|
}
|
|
|
|
|
|
/* Creates a native Windows message box of the given type and returns always TRUE
|
|
* or FALSE representing th pressed Yes or No button.
|
|
* If type is not GTK_MESSAGE_QUESTION, it returns always TRUE. */
|
|
gboolean win32_message_dialog(GtkWidget *parent, GtkMessageType type, const gchar *msg)
|
|
{
|
|
gboolean ret = TRUE;
|
|
gint rc;
|
|
guint t;
|
|
const gchar *title;
|
|
HWND parent_hwnd = NULL;
|
|
static wchar_t w_msg[512];
|
|
static wchar_t w_title[512];
|
|
|
|
switch (type)
|
|
{
|
|
case GTK_MESSAGE_ERROR:
|
|
{
|
|
t = MB_OK | MB_ICONERROR;
|
|
title = _("Error");
|
|
break;
|
|
}
|
|
case GTK_MESSAGE_QUESTION:
|
|
{
|
|
t = MB_YESNO | MB_ICONQUESTION;
|
|
title = _("Question");
|
|
break;
|
|
}
|
|
case GTK_MESSAGE_WARNING:
|
|
{
|
|
t = MB_OK | MB_ICONWARNING;
|
|
title = _("Warning");
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
t = MB_OK | MB_ICONINFORMATION;
|
|
title = _("Information");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* convert the Unicode chars to wide chars */
|
|
/** TODO test if LANG == C then possibly skip conversion => g_win32_getlocale() */
|
|
MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, G_N_ELEMENTS(w_msg));
|
|
MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));
|
|
|
|
if (parent != NULL)
|
|
parent_hwnd = GDK_WINDOW_HWND(gtk_widget_get_window(parent));
|
|
else if (main_widgets.window != NULL)
|
|
parent_hwnd = GDK_WINDOW_HWND(gtk_widget_get_window(main_widgets.window));
|
|
|
|
/* display the message box */
|
|
rc = MessageBoxW(parent_hwnd, w_msg, w_title, t);
|
|
|
|
if (type == GTK_MESSAGE_QUESTION && rc != IDYES)
|
|
ret = FALSE;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Little wrapper for _waccess(), returns errno or 0 if there was no error */
|
|
gint win32_check_write_permission(const gchar *dir)
|
|
{
|
|
static wchar_t w_dir[MAX_PATH];
|
|
MultiByteToWideChar(CP_UTF8, 0, dir, -1, w_dir, G_N_ELEMENTS(w_dir));
|
|
if (_waccess(w_dir, R_OK | W_OK) != 0)
|
|
return errno;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Just a simple wrapper function to open a browser window */
|
|
void win32_open_browser(const gchar *uri)
|
|
{
|
|
gint ret;
|
|
if (strncmp(uri, "file://", 7) == 0)
|
|
{
|
|
uri += 7;
|
|
if (strchr(uri, ':') != NULL)
|
|
{
|
|
while (*uri == '/')
|
|
uri++;
|
|
}
|
|
}
|
|
ret = (gint) ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOWNORMAL);
|
|
if (ret <= 32)
|
|
{
|
|
gchar *err = g_win32_error_message(GetLastError());
|
|
ui_set_statusbar(TRUE, _("Failed to open URI \"%s\": %s"), uri, err);
|
|
g_warning("ShellExecute failed opening \"%s\" (code %d): %s", uri, ret, err);
|
|
g_free(err);
|
|
}
|
|
}
|
|
|
|
|
|
static FILE *open_std_handle(DWORD handle, const char *mode)
|
|
{
|
|
HANDLE lStdHandle;
|
|
int hConHandle;
|
|
FILE *fp;
|
|
|
|
lStdHandle = GetStdHandle(handle);
|
|
if (lStdHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
gchar *err = g_win32_error_message(GetLastError());
|
|
g_warning("GetStdHandle(%ld) failed: %s", (long)handle, err);
|
|
g_free(err);
|
|
return NULL;
|
|
}
|
|
hConHandle = _open_osfhandle((intptr_t)lStdHandle, _O_TEXT);
|
|
if (hConHandle == -1)
|
|
{
|
|
gchar *err = g_win32_error_message(GetLastError());
|
|
g_warning("_open_osfhandle(handle(%ld), _O_TEXT) failed: %s", (long)handle, err);
|
|
g_free(err);
|
|
return NULL;
|
|
}
|
|
fp = _fdopen(hConHandle, mode);
|
|
if (! fp)
|
|
{
|
|
gchar *err = g_win32_error_message(GetLastError());
|
|
g_warning("_fdopen(%d, \"%s\") failed: %s", hConHandle, mode, err);
|
|
g_free(err);
|
|
return NULL;
|
|
}
|
|
if (setvbuf(fp, NULL, _IONBF, 0) != 0)
|
|
{
|
|
gchar *err = g_win32_error_message(GetLastError());
|
|
g_warning("setvbuf(%p, NULL, _IONBF, 0) failed: %s", fp, err);
|
|
g_free(err);
|
|
fclose(fp);
|
|
return NULL;
|
|
}
|
|
|
|
return fp;
|
|
}
|
|
|
|
|
|
static void debug_setup_console(void)
|
|
{
|
|
static const WORD MAX_CONSOLE_LINES = 500;
|
|
CONSOLE_SCREEN_BUFFER_INFO coninfo;
|
|
FILE *fp;
|
|
|
|
/* allocate a console for this app */
|
|
AllocConsole();
|
|
|
|
/* set the screen buffer to be big enough to let us scroll text */
|
|
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
|
|
coninfo.dwSize.Y = MAX_CONSOLE_LINES;
|
|
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
|
|
|
|
/* redirect unbuffered STDOUT to the console */
|
|
fp = open_std_handle(STD_OUTPUT_HANDLE, "w");
|
|
if (fp)
|
|
*stdout = *fp;
|
|
|
|
/* redirect unbuffered STDERR to the console */
|
|
fp = open_std_handle(STD_ERROR_HANDLE, "w");
|
|
if (fp)
|
|
*stderr = *fp;
|
|
|
|
/* redirect unbuffered STDIN to the console */
|
|
fp = open_std_handle(STD_INPUT_HANDLE, "r");
|
|
if (fp)
|
|
*stdin = *fp;
|
|
}
|
|
|
|
|
|
void win32_init_debug_code(void)
|
|
{
|
|
if (app->debug_mode)
|
|
{
|
|
/* create a console window to get log messages on Windows,
|
|
* especially useful when generating tags files */
|
|
debug_setup_console();
|
|
}
|
|
}
|
|
|
|
|
|
/* expands environment placeholders in @str. input and output is in UTF-8 */
|
|
gchar *win32_expand_environment_variables(const gchar *str)
|
|
{
|
|
wchar_t *cmdline = g_utf8_to_utf16(str, -1, NULL, NULL, NULL);
|
|
wchar_t expCmdline[32768]; /* 32768 is the limit for ExpandEnvironmentStrings() */
|
|
gchar *expanded = NULL;
|
|
|
|
if (cmdline && ExpandEnvironmentStringsW(cmdline, expCmdline, sizeof(expCmdline)) != 0)
|
|
expanded = g_utf16_to_utf8(expCmdline, -1, NULL, NULL, NULL);
|
|
|
|
g_free(cmdline);
|
|
|
|
return expanded ? expanded : g_strdup(str);
|
|
}
|
|
|
|
|
|
/* From GDK (they got it from MS Knowledge Base article Q130698) */
|
|
static gboolean resolve_link(HWND hWnd, wchar_t *link, gchar **lpszPath)
|
|
{
|
|
WIN32_FILE_ATTRIBUTE_DATA wfad;
|
|
HRESULT hres;
|
|
IShellLinkW *pslW = NULL;
|
|
IPersistFile *ppf = NULL;
|
|
LPVOID pslWV = NULL;
|
|
LPVOID ppfV = NULL;
|
|
|
|
/* Check if the file is empty first because IShellLink::Resolve for some reason succeeds
|
|
* with an empty file and returns an empty "link target". (#524151) */
|
|
if (!GetFileAttributesExW(link, GetFileExInfoStandard, &wfad) ||
|
|
(wfad.nFileSizeHigh == 0 && wfad.nFileSizeLow == 0))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
/* Assume failure to start with: */
|
|
*lpszPath = 0;
|
|
|
|
CoInitialize(NULL);
|
|
|
|
hres = CoCreateInstance(
|
|
&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, &pslWV);
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
/* The IShellLink interface supports the IPersistFile interface.
|
|
* Get an interface pointer to it. */
|
|
pslW = (IShellLinkW*) pslWV;
|
|
hres = pslW->lpVtbl->QueryInterface(pslW, &IID_IPersistFile, &ppfV);
|
|
}
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
/* Load the file. */
|
|
ppf = (IPersistFile*) ppfV;
|
|
hres = ppf->lpVtbl->Load(ppf, link, STGM_READ);
|
|
}
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
/* Resolve the link by calling the Resolve() interface function. */
|
|
hres = pslW->lpVtbl->Resolve(pslW, hWnd, SLR_ANY_MATCH | SLR_NO_UI);
|
|
}
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
wchar_t wtarget[MAX_PATH];
|
|
|
|
hres = pslW->lpVtbl->GetPath(pslW, wtarget, MAX_PATH, NULL, 0);
|
|
if (SUCCEEDED(hres))
|
|
*lpszPath = g_utf16_to_utf8(wtarget, -1, NULL, NULL, NULL);
|
|
}
|
|
|
|
if (ppf)
|
|
ppf->lpVtbl->Release(ppf);
|
|
|
|
if (pslW)
|
|
pslW->lpVtbl->Release(pslW);
|
|
|
|
return SUCCEEDED(hres);
|
|
}
|
|
|
|
|
|
/* Checks whether file_name is a Windows shortcut. file_name is expected in UTF-8 encoding.
|
|
* If file_name is a Windows shortcut, it returns the target in UTF-8 encoding.
|
|
* If it is not a shortcut, it returns a newly allocated copy of file_name. */
|
|
gchar *win32_get_shortcut_target(const gchar *file_name)
|
|
{
|
|
gchar *path = NULL;
|
|
wchar_t *wfilename = g_utf8_to_utf16(file_name, -1, NULL, NULL, NULL);
|
|
HWND hWnd = NULL;
|
|
|
|
if (main_widgets.window != NULL)
|
|
{
|
|
GdkWindow *window = gtk_widget_get_window(main_widgets.window);
|
|
if (window != NULL)
|
|
hWnd = GDK_WINDOW_HWND(window);
|
|
}
|
|
|
|
resolve_link(hWnd, wfilename, &path);
|
|
g_free(wfilename);
|
|
|
|
if (path == NULL)
|
|
return g_strdup(file_name);
|
|
else
|
|
return path;
|
|
}
|
|
|
|
|
|
void win32_set_working_directory(const gchar *dir)
|
|
{
|
|
SetCurrentDirectory(dir);
|
|
}
|
|
|
|
|
|
gchar *win32_get_installation_dir(void)
|
|
{
|
|
return g_win32_get_package_installation_directory_of_module(NULL);
|
|
}
|
|
|
|
|
|
gchar *win32_get_user_config_dir(void)
|
|
{
|
|
HRESULT hr;
|
|
wchar_t path[MAX_PATH];
|
|
|
|
hr = SHGetFolderPathAndSubDirW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, L"geany", path);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// GLib always uses UTF-8 for filename encoding on Windows
|
|
int u8_size = WideCharToMultiByte(CP_UTF8, 0, path, -1, NULL, 0, NULL, NULL);
|
|
if (u8_size > 0)
|
|
{
|
|
gchar *u8_path = g_malloc0(u8_size + 1);
|
|
if (u8_path != NULL &&
|
|
WideCharToMultiByte(CP_UTF8, 0, path, -1, u8_path, u8_size, NULL, NULL))
|
|
{
|
|
return u8_path;
|
|
}
|
|
}
|
|
}
|
|
|
|
// glib fallback
|
|
g_warning("Failed to retrieve Windows config dir, falling back to default");
|
|
return g_build_filename(g_get_user_config_dir(), "geany", NULL);
|
|
}
|
|
|
|
#endif
|