Make sure only *W functions from Win32 API are called.

After this commit, SolveSpace always uses UTF-8 strings internally
(represented as char* and std::string) on every OS, for everything:
UI labels, paths and user input. OS X always uses UTF-8; on Windows,
strings are converted at boundary; and on Linux/BSD/etc SolveSpace
refuses to start unless the current locale is UTF-8. This will
negatively affect literally no one.

This commit does not bring forth any user-visible improvement yet;
in order to correctly handle Unicode in filenames, it is still
necessary to change the fopen calls to _wfopen on Windows.
pull/4/head
whitequark 2015-12-27 15:51:28 +08:00
parent 5c9c32cfc7
commit 97a9b4743e
5 changed files with 209 additions and 131 deletions

View File

@ -35,7 +35,9 @@ if(WIN32)
-D_WIN32_IE=_WIN32_WINNT -D_WIN32_IE=_WIN32_WINNT
-DISOLATION_AWARE_ENABLED=1 -DISOLATION_AWARE_ENABLED=1
-DWIN32=1 -DWIN32=1
-DWIN32_LEAN_AND_MEAN=1) -DWIN32_LEAN_AND_MEAN=1
-DUNICODE
-D_UNICODE)
endif() endif()
if(MINGW) if(MINGW)

View File

@ -1470,7 +1470,21 @@ void ExitNow(void) {
}; };
int main(int argc, char** argv) { int main(int argc, char** argv) {
/* If we don't call this, gtk_init will set the C standard library /* It would in principle be possible to judiciously use
Glib::filename_{from,to}_utf8, but it's not really worth
the effort.
The setlocale() call is necessary for Glib::get_charset()
to detect the system character set; otherwise it thinks
it is always ANSI_X3.4-1968.
We set it back to C after all. */
setlocale(LC_ALL, "");
if(!Glib::get_charset()) {
std::cerr << "Sorry, only UTF-8 locales are supported." << std::endl;
return 1;
}
setlocale(LC_ALL, "C");
/* If we don't do this, gtk_init will set the C standard library
locale, and printf will format floats using ",". We will then locale, and printf will format floats using ",". We will then
fail to parse these. Also, many text window lines will become fail to parse these. Also, many text window lines will become
ambiguous. */ ambiguous. */
@ -1500,7 +1514,8 @@ int main(int argc, char** argv) {
<< std::endl; << std::endl;
} }
SS.OpenFile(argv[1]); /* Make sure the argument is valid UTF-8. */
SS.OpenFile(Glib::ustring(argv[1]));
} }
main.run(*GW); main.run(*GW);

View File

@ -107,6 +107,13 @@ typedef UCHAR uint8_t;
typedef CHAR int8_t; typedef CHAR int8_t;
#endif #endif
#if defined(WIN32)
std::string Narrow(const wchar_t *s);
std::wstring Widen(const char *s);
std::string Narrow(const std::wstring &s);
std::wstring Widen(const std::string &s);
#endif
inline double Random(double vmax) { inline double Random(double vmax) {
return (vmax*rand()) / RAND_MAX; return (vmax*rand()) / RAND_MAX;
} }

View File

@ -90,9 +90,9 @@ static LRESULT CALLBACK MessageProc(HWND hwnd, UINT msg, WPARAM wParam,
col = 0; col = 0;
row++; row++;
} else { } else {
TextOut(hdc, col*SS.TW.CHAR_WIDTH + 10, TextOutW(hdc, col*SS.TW.CHAR_WIDTH + 10,
row*SS.TW.LINE_HEIGHT + 10, row*SS.TW.LINE_HEIGHT + 10,
&(MessageString[i]), 1); Widen(&(MessageString[i])).c_str(), 1);
col++; col++;
} }
} }
@ -107,11 +107,11 @@ static LRESULT CALLBACK MessageProc(HWND hwnd, UINT msg, WPARAM wParam,
return 1; return 1;
} }
HWND CreateWindowClient(DWORD exStyle, const char *className, const char *windowName, HWND CreateWindowClient(DWORD exStyle, const wchar_t *className, const wchar_t *windowName,
DWORD style, int x, int y, int width, int height, HWND parent, DWORD style, int x, int y, int width, int height, HWND parent,
HMENU menu, HINSTANCE instance, void *param) HMENU menu, HINSTANCE instance, void *param)
{ {
HWND h = CreateWindowEx(exStyle, className, windowName, style, x, y, HWND h = CreateWindowExW(exStyle, className, windowName, style, x, y,
width, height, parent, menu, instance, param); width, height, parent, menu, instance, param);
RECT r; RECT r;
@ -128,7 +128,6 @@ void SolveSpace::DoMessageBox(const char *str, int rows, int cols, bool error)
{ {
EnableWindow(GraphicsWnd, false); EnableWindow(GraphicsWnd, false);
EnableWindow(TextWnd, false); EnableWindow(TextWnd, false);
//HWND h = GetForegroundWindow();
// Register the window class for our dialog. // Register the window class for our dialog.
WNDCLASSEX wc = {}; WNDCLASSEX wc = {};
@ -137,7 +136,7 @@ void SolveSpace::DoMessageBox(const char *str, int rows, int cols, bool error)
wc.lpfnWndProc = (WNDPROC)MessageProc; wc.lpfnWndProc = (WNDPROC)MessageProc;
wc.hInstance = Instance; wc.hInstance = Instance;
wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW; wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
wc.lpszClassName = "MessageWnd"; wc.lpszClassName = L"MessageWnd";
wc.lpszMenuName = NULL; wc.lpszMenuName = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = (HICON)LoadImage(Instance, MAKEINTRESOURCE(4000), wc.hIcon = (HICON)LoadImage(Instance, MAKEINTRESOURCE(4000),
@ -153,11 +152,11 @@ void SolveSpace::DoMessageBox(const char *str, int rows, int cols, bool error)
const char *title = error ? "SolveSpace - Error" : "SolveSpace - Message"; const char *title = error ? "SolveSpace - Error" : "SolveSpace - Message";
int width = cols*SS.TW.CHAR_WIDTH + 20, int width = cols*SS.TW.CHAR_WIDTH + 20,
height = rows*SS.TW.LINE_HEIGHT + 60; height = rows*SS.TW.LINE_HEIGHT + 60;
MessageWnd = CreateWindowClient(0, "MessageWnd", title, MessageWnd = CreateWindowClient(0, L"MessageWnd", Widen(title).c_str(),
WS_OVERLAPPED | WS_SYSMENU, WS_OVERLAPPED | WS_SYSMENU,
r.left + 100, r.top + 100, width, height, NULL, NULL, Instance, NULL); r.left + 100, r.top + 100, width, height, NULL, NULL, Instance, NULL);
OkButton = CreateWindowEx(0, WC_BUTTON, "OK", OkButton = CreateWindowExW(0, WC_BUTTON, L"OK",
WS_CHILD | WS_TABSTOP | WS_CLIPSIBLINGS | WS_VISIBLE | BS_DEFPUSHBUTTON, WS_CHILD | WS_TABSTOP | WS_CLIPSIBLINGS | WS_VISIBLE | BS_DEFPUSHBUTTON,
(width - 70)/2, rows*SS.TW.LINE_HEIGHT + 20, (width - 70)/2, rows*SS.TW.LINE_HEIGHT + 20,
70, 25, MessageWnd, NULL, Instance, NULL); 70, 25, MessageWnd, NULL, Instance, NULL);
@ -196,15 +195,15 @@ void SolveSpace::AddContextMenuItem(const char *label, int id)
if(!ContextMenu) ContextMenu = CreatePopupMenu(); if(!ContextMenu) ContextMenu = CreatePopupMenu();
if(id == CONTEXT_SUBMENU) { if(id == CONTEXT_SUBMENU) {
AppendMenu(ContextMenu, MF_STRING | MF_POPUP, AppendMenuW(ContextMenu, MF_STRING | MF_POPUP,
(UINT_PTR)ContextSubmenu, label); (UINT_PTR)ContextSubmenu, Widen(label).c_str());
ContextSubmenu = NULL; ContextSubmenu = NULL;
} else { } else {
HMENU m = ContextSubmenu ? ContextSubmenu : ContextMenu; HMENU m = ContextSubmenu ? ContextSubmenu : ContextMenu;
if(id == CONTEXT_SEPARATOR) { if(id == CONTEXT_SEPARATOR) {
AppendMenu(m, MF_SEPARATOR, 0, ""); AppendMenuW(m, MF_SEPARATOR, 0, L"");
} else { } else {
AppendMenu(m, MF_STRING, id, label); AppendMenuW(m, MF_STRING, id, Widen(label).c_str());
} }
} }
} }
@ -271,7 +270,7 @@ void SolveSpace::GetTextWindowSize(int *w, int *h)
} }
void SolveSpace::OpenWebsite(const char *url) { void SolveSpace::OpenWebsite(const char *url) {
ShellExecute(GraphicsWnd, "open", url, NULL, NULL, SW_SHOWNORMAL); ShellExecuteW(GraphicsWnd, L"open", Widen(url).c_str(), NULL, NULL, SW_SHOWNORMAL);
} }
void SolveSpace::ExitNow(void) { void SolveSpace::ExitNow(void) {
@ -292,11 +291,12 @@ inline int CLAMP(int v, int a, int b) {
static HKEY GetRegistryKey() static HKEY GetRegistryKey()
{ {
HKEY Software; HKEY Software;
if(RegOpenKeyEx(HKEY_CURRENT_USER, "Software", 0, KEY_ALL_ACCESS, &Software) != ERROR_SUCCESS) if(RegOpenKeyExW(HKEY_CURRENT_USER, L"Software", 0,
KEY_ALL_ACCESS, &Software) != ERROR_SUCCESS)
return NULL; return NULL;
HKEY SolveSpace; HKEY SolveSpace;
if(RegCreateKeyEx(Software, "SolveSpace", 0, NULL, 0, if(RegCreateKeyExW(Software, L"SolveSpace", 0, NULL, 0,
KEY_ALL_ACCESS, NULL, &SolveSpace, NULL) != ERROR_SUCCESS) KEY_ALL_ACCESS, NULL, &SolveSpace, NULL) != ERROR_SUCCESS)
return NULL; return NULL;
@ -308,7 +308,7 @@ static HKEY GetRegistryKey()
void SolveSpace::CnfFreezeInt(uint32_t val, const std::string &name) void SolveSpace::CnfFreezeInt(uint32_t val, const std::string &name)
{ {
HKEY SolveSpace = GetRegistryKey(); HKEY SolveSpace = GetRegistryKey();
RegSetValueEx(SolveSpace, &name[0], 0, RegSetValueExW(SolveSpace, &Widen(name)[0], 0,
REG_DWORD, (const BYTE*) &val, sizeof(DWORD)); REG_DWORD, (const BYTE*) &val, sizeof(DWORD));
RegCloseKey(SolveSpace); RegCloseKey(SolveSpace);
} }
@ -317,15 +317,16 @@ void SolveSpace::CnfFreezeFloat(float val, const std::string &name)
static_assert(sizeof(float) == sizeof(DWORD), static_assert(sizeof(float) == sizeof(DWORD),
"sizes of float and DWORD must match"); "sizes of float and DWORD must match");
HKEY SolveSpace = GetRegistryKey(); HKEY SolveSpace = GetRegistryKey();
RegSetValueEx(SolveSpace, &name[0], 0, RegSetValueExW(SolveSpace, &Widen(name)[0], 0,
REG_DWORD, (const BYTE*) &val, sizeof(DWORD)); REG_DWORD, (const BYTE*) &val, sizeof(DWORD));
RegCloseKey(SolveSpace); RegCloseKey(SolveSpace);
} }
void SolveSpace::CnfFreezeString(const std::string &str, const std::string &name) void SolveSpace::CnfFreezeString(const std::string &str, const std::string &name)
{ {
HKEY SolveSpace = GetRegistryKey(); HKEY SolveSpace = GetRegistryKey();
RegSetValueEx(SolveSpace, &name[0], 0, std::wstring strW = Widen(str);
REG_SZ, (const BYTE*) &str[0], str.length() + 1); RegSetValueExW(SolveSpace, &Widen(name)[0], 0,
REG_SZ, (const BYTE*) &strW[0], (strW.length() + 1) * 2);
RegCloseKey(SolveSpace); RegCloseKey(SolveSpace);
} }
static void FreezeWindowPos(HWND hwnd, const std::string &name) static void FreezeWindowPos(HWND hwnd, const std::string &name)
@ -344,7 +345,8 @@ uint32_t SolveSpace::CnfThawInt(uint32_t val, const std::string &name)
{ {
HKEY SolveSpace = GetRegistryKey(); HKEY SolveSpace = GetRegistryKey();
DWORD type, newval, len = sizeof(DWORD); DWORD type, newval, len = sizeof(DWORD);
LONG result = RegQueryValueEx(SolveSpace, &name[0], NULL, &type, (BYTE*) &newval, &len); LONG result = RegQueryValueEx(SolveSpace, &Widen(name)[0], NULL,
&type, (BYTE*) &newval, &len);
RegCloseKey(SolveSpace); RegCloseKey(SolveSpace);
if(result == ERROR_SUCCESS && type == REG_DWORD) if(result == ERROR_SUCCESS && type == REG_DWORD)
@ -357,7 +359,8 @@ float SolveSpace::CnfThawFloat(float val, const std::string &name)
HKEY SolveSpace = GetRegistryKey(); HKEY SolveSpace = GetRegistryKey();
DWORD type, len = sizeof(DWORD); DWORD type, len = sizeof(DWORD);
float newval; float newval;
LONG result = RegQueryValueEx(SolveSpace, &name[0], NULL, &type, (BYTE*) &newval, &len); LONG result = RegQueryValueExW(SolveSpace, &Widen(name)[0], NULL,
&type, (BYTE*) &newval, &len);
RegCloseKey(SolveSpace); RegCloseKey(SolveSpace);
if(result == ERROR_SUCCESS && type == REG_DWORD) if(result == ERROR_SUCCESS && type == REG_DWORD)
@ -369,22 +372,22 @@ std::string SolveSpace::CnfThawString(const std::string &val, const std::string
{ {
HKEY SolveSpace = GetRegistryKey(); HKEY SolveSpace = GetRegistryKey();
DWORD type, len; DWORD type, len;
if(RegQueryValueEx(SolveSpace, &name[0], NULL, if(RegQueryValueExW(SolveSpace, &Widen(name)[0], NULL,
&type, NULL, &len) != ERROR_SUCCESS || type != REG_SZ) { &type, NULL, &len) != ERROR_SUCCESS || type != REG_SZ) {
RegCloseKey(SolveSpace); RegCloseKey(SolveSpace);
return val; return val;
} }
std::string newval; std::wstring newval;
newval.resize(len - 1); newval.resize(len / 2 - 1);
if(RegQueryValueEx(SolveSpace, &name[0], NULL, if(RegQueryValueExW(SolveSpace, &Widen(name)[0], NULL,
NULL, (BYTE*) &newval[0], &len) != ERROR_SUCCESS) { NULL, (BYTE*) &newval[0], &len) != ERROR_SUCCESS) {
RegCloseKey(SolveSpace); RegCloseKey(SolveSpace);
return val; return val;
} }
RegCloseKey(SolveSpace); RegCloseKey(SolveSpace);
return newval; return Narrow(newval);
} }
static void ThawWindowPos(HWND hwnd, const std::string &name) static void ThawWindowPos(HWND hwnd, const std::string &name)
{ {
@ -414,9 +417,9 @@ static void ThawWindowPos(HWND hwnd, const std::string &name)
void SolveSpace::SetCurrentFilename(const std::string &filename) { void SolveSpace::SetCurrentFilename(const std::string &filename) {
if(!filename.empty()) { if(!filename.empty()) {
SetWindowText(GraphicsWnd, ("SolveSpace - " + filename).c_str()); SetWindowTextW(GraphicsWnd, Widen("SolveSpace - " + filename).c_str());
} else { } else {
SetWindowText(GraphicsWnd, "SolveSpace - (not yet saved)"); SetWindowTextW(GraphicsWnd, L"SolveSpace - (not yet saved)");
} }
} }
@ -622,13 +625,19 @@ LRESULT CALLBACK TextWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
return 1; return 1;
} }
static std::string EditControlText(HWND hwnd)
{
std::wstring result;
result.resize(GetWindowTextLength(hwnd));
GetWindowTextW(hwnd, &result[0], result.length() + 1);
return Narrow(result);
}
static bool ProcessKeyDown(WPARAM wParam) static bool ProcessKeyDown(WPARAM wParam)
{ {
if(GraphicsEditControlIsVisible() && wParam != VK_ESCAPE) { if(GraphicsEditControlIsVisible() && wParam != VK_ESCAPE) {
if(wParam == VK_RETURN) { if(wParam == VK_RETURN) {
char s[1024] = {}; SS.GW.EditControlDone(EditControlText(GraphicsEditControl).c_str());
SendMessage(GraphicsEditControl, WM_GETTEXT, 900, (LPARAM)s);
SS.GW.EditControlDone(s);
return true; return true;
} else { } else {
return false; return false;
@ -636,9 +645,7 @@ static bool ProcessKeyDown(WPARAM wParam)
} }
if(TextEditControlIsVisible() && wParam != VK_ESCAPE) { if(TextEditControlIsVisible() && wParam != VK_ESCAPE) {
if(wParam == VK_RETURN) { if(wParam == VK_RETURN) {
char s[1024] = {}; SS.TW.EditControlDone(EditControlText(TextEditControl).c_str());
SendMessage(TextEditControl, WM_GETTEXT, 900, (LPARAM)s);
SS.TW.EditControlDone(s);
} else { } else {
return false; return false;
} }
@ -796,12 +803,12 @@ void SolveSpace::InvalidateText(void)
InvalidateRect(TextWnd, NULL, false); InvalidateRect(TextWnd, NULL, false);
} }
static void ShowEditControl(HWND h, int x, int y, char *s) { static void ShowEditControl(HWND h, int x, int y, const std::wstring &s) {
MoveWindow(h, x, y, EDIT_WIDTH, EDIT_HEIGHT, true); MoveWindow(h, x, y, EDIT_WIDTH, EDIT_HEIGHT, true);
ShowWindow(h, SW_SHOW); ShowWindow(h, SW_SHOW);
if(s) { if(!s.empty()) {
SendMessage(h, WM_SETTEXT, 0, (LPARAM)s); SendMessage(h, WM_SETTEXT, 0, (LPARAM)s.c_str());
SendMessage(h, EM_SETSEL, 0, strlen(s)); SendMessage(h, EM_SETSEL, 0, s.length());
SetFocus(h); SetFocus(h);
} }
} }
@ -809,7 +816,7 @@ void SolveSpace::ShowTextEditControl(int x, int y, char *s)
{ {
if(GraphicsEditControlIsVisible()) return; if(GraphicsEditControlIsVisible()) return;
ShowEditControl(TextEditControl, x, y, s); ShowEditControl(TextEditControl, x, y, Widen(s));
} }
void SolveSpace::HideTextEditControl(void) void SolveSpace::HideTextEditControl(void)
{ {
@ -832,7 +839,7 @@ void SolveSpace::ShowGraphicsEditControl(int x, int y, char *s)
// top left corner // top left corner
y -= 20; y -= 20;
ShowEditControl(GraphicsEditControl, x, y, s); ShowEditControl(GraphicsEditControl, x, y, Widen(s));
} }
void SolveSpace::HideGraphicsEditControl(void) void SolveSpace::HideGraphicsEditControl(void)
{ {
@ -958,67 +965,63 @@ LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam,
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Common dialog routines, to open or save a file. // Common dialog routines, to open or save a file.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
bool SolveSpace::GetOpenFile(std::string &filename, static size_t strlen2(const char *p) {
const char *defExtension, const char *selPattern) const char *s = p;
{ while(*p || (!*p && *(p+1))) p++;
// UNC paths may be arbitrarily long. return p - s + 1;
}
static bool OpenSaveFile(bool isOpen, std::string &filename,
const char *defExtension, const char *selPattern) {
// UNC paths may be as long as 32767 characters.
// Unfortunately, the Get*FileName API does not provide any way to use it // Unfortunately, the Get*FileName API does not provide any way to use it
// except with a preallocated buffer of fixed size, so we use something // except with a preallocated buffer of fixed size, so we use something
// reasonably large. // reasonably large.
char filenameC[0xFFFF] = {}; const int len = 32768;
strncpy(filenameC, filename.c_str(), sizeof(filenameC) - 1); wchar_t filenameC[len] = {};
wcsncpy(filenameC, Widen(filename).c_str(), len - 1);
std::wstring selPatternW = Widen(std::string(selPattern, strlen2(selPattern)));
std::wstring defExtensionW = Widen(defExtension);
OPENFILENAME ofn = {}; OPENFILENAME ofn = {};
ofn.lStructSize = sizeof(ofn); ofn.lStructSize = sizeof(ofn);
ofn.hInstance = Instance; ofn.hInstance = Instance;
ofn.hwndOwner = GraphicsWnd; ofn.hwndOwner = GraphicsWnd;
ofn.lpstrFilter = selPattern; ofn.lpstrFilter = selPatternW.c_str();
ofn.lpstrDefExt = defExtension; ofn.lpstrDefExt = defExtensionW.c_str();
ofn.lpstrFile = filenameC; ofn.lpstrFile = filenameC;
ofn.nMaxFile = sizeof(filenameC); ofn.nMaxFile = len;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
EnableWindow(GraphicsWnd, false); EnableWindow(GraphicsWnd, false);
EnableWindow(TextWnd, false); EnableWindow(TextWnd, false);
BOOL r = GetOpenFileName(&ofn); BOOL r;
if(isOpen) {
r = GetOpenFileNameW(&ofn);
} else {
r = GetSaveFileNameW(&ofn);
}
EnableWindow(TextWnd, true); EnableWindow(TextWnd, true);
EnableWindow(GraphicsWnd, true); EnableWindow(GraphicsWnd, true);
SetForegroundWindow(GraphicsWnd); SetForegroundWindow(GraphicsWnd);
if(r) filename = filenameC; if(r) filename = Narrow(filenameC);
return r ? true : false; return r ? true : false;
} }
bool SolveSpace::GetOpenFile(std::string &filename,
const char *defExtension, const char *selPattern)
{
return OpenSaveFile(true, filename, defExtension, selPattern);
}
bool SolveSpace::GetSaveFile(std::string &filename, bool SolveSpace::GetSaveFile(std::string &filename,
const char *defExtension, const char *selPattern) const char *defExtension, const char *selPattern)
{ {
// See GetOpenFile return OpenSaveFile(false, filename, defExtension, selPattern);
char filenameC[0xFFFF] = {};
strncpy(filenameC, filename.c_str(), sizeof(filenameC) - 1);
OPENFILENAME ofn = {};
ofn.lStructSize = sizeof(ofn);
ofn.hInstance = Instance;
ofn.hwndOwner = GraphicsWnd;
ofn.lpstrFilter = selPattern;
ofn.lpstrDefExt = defExtension;
ofn.lpstrFile = filenameC;
ofn.nMaxFile = sizeof(filenameC);
ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
EnableWindow(GraphicsWnd, false);
EnableWindow(TextWnd, false);
BOOL r = GetSaveFileName(&ofn);
EnableWindow(TextWnd, true);
EnableWindow(GraphicsWnd, true);
SetForegroundWindow(GraphicsWnd);
if(r) filename = filenameC;
return r ? true : false;
} }
int SolveSpace::SaveFileYesNoCancel(void) int SolveSpace::SaveFileYesNoCancel(void)
@ -1026,9 +1029,9 @@ int SolveSpace::SaveFileYesNoCancel(void)
EnableWindow(GraphicsWnd, false); EnableWindow(GraphicsWnd, false);
EnableWindow(TextWnd, false); EnableWindow(TextWnd, false);
int r = MessageBox(GraphicsWnd, int r = MessageBoxW(GraphicsWnd,
"The program has changed since it was last saved.\r\n\r\n" L"The program has changed since it was last saved.\r\n\r\n"
"Do you want to save the changes?", "SolveSpace", L"Do you want to save the changes?", L"SolveSpace",
MB_YESNOCANCEL | MB_ICONWARNING); MB_YESNOCANCEL | MB_ICONWARNING);
EnableWindow(TextWnd, true); EnableWindow(TextWnd, true);
@ -1050,9 +1053,9 @@ int SolveSpace::LoadAutosaveYesNo(void)
EnableWindow(GraphicsWnd, false); EnableWindow(GraphicsWnd, false);
EnableWindow(TextWnd, false); EnableWindow(TextWnd, false);
int r = MessageBox(GraphicsWnd, int r = MessageBoxW(GraphicsWnd,
"An autosave file is availible for this project.\r\n\r\n" L"An autosave file is availible for this project.\r\n\r\n"
"Do you want to load the autosave file instead?", "SolveSpace", L"Do you want to load the autosave file instead?", L"SolveSpace",
MB_YESNO | MB_ICONWARNING); MB_YESNO | MB_ICONWARNING);
EnableWindow(TextWnd, true); EnableWindow(TextWnd, true);
@ -1069,18 +1072,18 @@ int SolveSpace::LoadAutosaveYesNo(void)
void SolveSpace::LoadAllFontFiles(void) void SolveSpace::LoadAllFontFiles(void)
{ {
std::string fontsDir(MAX_PATH, '\0'); std::wstring fontsDir(MAX_PATH, '\0');
fontsDir.resize(GetWindowsDirectory(&fontsDir[0], fontsDir.length())); fontsDir.resize(GetWindowsDirectoryW(&fontsDir[0], fontsDir.length()));
fontsDir += "\\fonts\\"; fontsDir += L"\\fonts\\";
WIN32_FIND_DATA wfd; WIN32_FIND_DATA wfd;
HANDLE h = FindFirstFile((fontsDir + "*.ttf").c_str(), &wfd); HANDLE h = FindFirstFileW((fontsDir + L"*.ttf").c_str(), &wfd);
while(h != INVALID_HANDLE_VALUE) { while(h != INVALID_HANDLE_VALUE) {
TtfFont tf = {}; TtfFont tf = {};
tf.fontFile = fontsDir + wfd.cFileName; tf.fontFile = Narrow(fontsDir) + Narrow(wfd.cFileName);
SS.fonts.l.Add(&tf); SS.fonts.l.Add(&tf);
if(!FindNextFile(h, &wfd)) break; if(!FindNextFileW(h, &wfd)) break;
} }
} }
@ -1128,11 +1131,11 @@ static void DoRecent(HMENU m, int base)
int i, c = 0; int i, c = 0;
for(i = 0; i < MAX_RECENT; i++) { for(i = 0; i < MAX_RECENT; i++) {
if(!RecentFile[i].empty()) { if(!RecentFile[i].empty()) {
AppendMenu(m, MF_STRING, base + i, RecentFile[i].c_str()); AppendMenuW(m, MF_STRING, base + i, Widen(RecentFile[i]).c_str());
c++; c++;
} }
} }
if(c == 0) AppendMenu(m, MF_STRING | MF_GRAYED, 0, "(no recent files)"); if(c == 0) AppendMenuW(m, MF_STRING | MF_GRAYED, 0, L"(no recent files)");
} }
void SolveSpace::RefreshRecentMenus(void) void SolveSpace::RefreshRecentMenus(void)
{ {
@ -1160,23 +1163,23 @@ HMENU CreateGraphicsWindowMenus(void)
if(SS.GW.menu[i].level == 0) { if(SS.GW.menu[i].level == 0) {
m = CreateMenu(); m = CreateMenu();
AppendMenu(top, MF_STRING | MF_POPUP, (UINT_PTR)m, label); AppendMenuW(top, MF_STRING | MF_POPUP, (UINT_PTR)m, Widen(label).c_str());
if(subMenu >= (int)arraylen(SubMenus)) oops(); if(subMenu >= (int)arraylen(SubMenus)) oops();
SubMenus[subMenu] = m; SubMenus[subMenu] = m;
subMenu++; subMenu++;
} else if(SS.GW.menu[i].level == 1) { } else if(SS.GW.menu[i].level == 1) {
if(SS.GW.menu[i].id == GraphicsWindow::MNU_OPEN_RECENT) { if(SS.GW.menu[i].id == GraphicsWindow::MNU_OPEN_RECENT) {
RecentOpenMenu = CreateMenu(); RecentOpenMenu = CreateMenu();
AppendMenu(m, MF_STRING | MF_POPUP, AppendMenuW(m, MF_STRING | MF_POPUP,
(UINT_PTR)RecentOpenMenu, label); (UINT_PTR)RecentOpenMenu, Widen(label).c_str());
} else if(SS.GW.menu[i].id == GraphicsWindow::MNU_GROUP_RECENT) { } else if(SS.GW.menu[i].id == GraphicsWindow::MNU_GROUP_RECENT) {
RecentImportMenu = CreateMenu(); RecentImportMenu = CreateMenu();
AppendMenu(m, MF_STRING | MF_POPUP, AppendMenuW(m, MF_STRING | MF_POPUP,
(UINT_PTR)RecentImportMenu, label); (UINT_PTR)RecentImportMenu, Widen(label).c_str());
} else if(SS.GW.menu[i].label) { } else if(SS.GW.menu[i].label) {
AppendMenu(m, MF_STRING, SS.GW.menu[i].id, label); AppendMenuW(m, MF_STRING, SS.GW.menu[i].id, Widen(label).c_str());
} else { } else {
AppendMenu(m, MF_SEPARATOR, SS.GW.menu[i].id, ""); AppendMenuW(m, MF_SEPARATOR, SS.GW.menu[i].id, L"");
} }
} else oops(); } else oops();
} }
@ -1196,7 +1199,7 @@ static void CreateMainWindows(void)
CS_DBLCLKS; CS_DBLCLKS;
wc.lpfnWndProc = (WNDPROC)GraphicsWndProc; wc.lpfnWndProc = (WNDPROC)GraphicsWndProc;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszClassName = "GraphicsWnd"; wc.lpszClassName = L"GraphicsWnd";
wc.lpszMenuName = NULL; wc.lpszMenuName = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = (HICON)LoadImage(Instance, MAKEINTRESOURCE(4000), wc.hIcon = (HICON)LoadImage(Instance, MAKEINTRESOURCE(4000),
@ -1206,14 +1209,14 @@ static void CreateMainWindows(void)
if(!RegisterClassEx(&wc)) oops(); if(!RegisterClassEx(&wc)) oops();
HMENU top = CreateGraphicsWindowMenus(); HMENU top = CreateGraphicsWindowMenus();
GraphicsWnd = CreateWindowEx(0, "GraphicsWnd", GraphicsWnd = CreateWindowExW(0, L"GraphicsWnd",
"SolveSpace (not yet saved)", L"SolveSpace (not yet saved)",
WS_OVERLAPPED | WS_THICKFRAME | WS_CLIPCHILDREN | WS_MAXIMIZEBOX | WS_OVERLAPPED | WS_THICKFRAME | WS_CLIPCHILDREN | WS_MAXIMIZEBOX |
WS_MINIMIZEBOX | WS_SYSMENU | WS_SIZEBOX | WS_CLIPSIBLINGS, WS_MINIMIZEBOX | WS_SYSMENU | WS_SIZEBOX | WS_CLIPSIBLINGS,
50, 50, 900, 600, NULL, top, Instance, NULL); 50, 50, 900, 600, NULL, top, Instance, NULL);
if(!GraphicsWnd) oops(); if(!GraphicsWnd) oops();
GraphicsEditControl = CreateWindowEx(WS_EX_CLIENTEDGE, WC_EDIT, "", GraphicsEditControl = CreateWindowExW(WS_EX_CLIENTEDGE, WC_EDIT, L"",
WS_CHILD | ES_AUTOHSCROLL | WS_TABSTOP | WS_CLIPSIBLINGS, WS_CHILD | ES_AUTOHSCROLL | WS_TABSTOP | WS_CLIPSIBLINGS,
50, 50, 100, 21, GraphicsWnd, NULL, Instance, NULL); 50, 50, 100, 21, GraphicsWnd, NULL, Instance, NULL);
SendMessage(GraphicsEditControl, WM_SETFONT, (WPARAM)FixedFont, true); SendMessage(GraphicsEditControl, WM_SETFONT, (WPARAM)FixedFont, true);
@ -1223,24 +1226,24 @@ static void CreateMainWindows(void)
wc.style &= ~CS_DBLCLKS; wc.style &= ~CS_DBLCLKS;
wc.lpfnWndProc = (WNDPROC)TextWndProc; wc.lpfnWndProc = (WNDPROC)TextWndProc;
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.lpszClassName = "TextWnd"; wc.lpszClassName = L"TextWnd";
wc.hCursor = NULL; wc.hCursor = NULL;
if(!RegisterClassEx(&wc)) oops(); if(!RegisterClassEx(&wc)) oops();
// We get the desired Alt+Tab behaviour by specifying that the text // We get the desired Alt+Tab behaviour by specifying that the text
// window is a child of the graphics window. // window is a child of the graphics window.
TextWnd = CreateWindowEx(0, TextWnd = CreateWindowExW(0,
"TextWnd", "SolveSpace - Browser", WS_THICKFRAME | WS_CLIPCHILDREN, L"TextWnd", L"SolveSpace - Browser", WS_THICKFRAME | WS_CLIPCHILDREN,
650, 500, 420, 300, GraphicsWnd, (HMENU)NULL, Instance, NULL); 650, 500, 420, 300, GraphicsWnd, (HMENU)NULL, Instance, NULL);
if(!TextWnd) oops(); if(!TextWnd) oops();
TextWndScrollBar = CreateWindowEx(0, WC_SCROLLBAR, "", WS_CHILD | TextWndScrollBar = CreateWindowExW(0, WC_SCROLLBAR, L"", WS_CHILD |
SBS_VERT | SBS_LEFTALIGN | WS_VISIBLE | WS_CLIPSIBLINGS, SBS_VERT | SBS_LEFTALIGN | WS_VISIBLE | WS_CLIPSIBLINGS,
200, 100, 100, 100, TextWnd, NULL, Instance, NULL); 200, 100, 100, 100, TextWnd, NULL, Instance, NULL);
// Force the scrollbar to get resized to the window, // Force the scrollbar to get resized to the window,
TextWndProc(TextWnd, WM_SIZE, 0, 0); TextWndProc(TextWnd, WM_SIZE, 0, 0);
TextEditControl = CreateWindowEx(WS_EX_CLIENTEDGE, WC_EDIT, "", TextEditControl = CreateWindowExW(WS_EX_CLIENTEDGE, WC_EDIT, L"",
WS_CHILD | ES_AUTOHSCROLL | WS_TABSTOP | WS_CLIPSIBLINGS, WS_CHILD | ES_AUTOHSCROLL | WS_TABSTOP | WS_CLIPSIBLINGS,
50, 50, 100, 21, TextWnd, NULL, Instance, NULL); 50, 50, 100, 21, TextWnd, NULL, Instance, NULL);
SendMessage(TextEditControl, WM_SETFONT, (WPARAM)FixedFont, true); SendMessage(TextEditControl, WM_SETFONT, (WPARAM)FixedFont, true);
@ -1304,10 +1307,10 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
InitCommonControls(); InitCommonControls();
// A monospaced font // A monospaced font
FixedFont = CreateFont(SS.TW.CHAR_HEIGHT, SS.TW.CHAR_WIDTH, 0, 0, FixedFont = CreateFontW(SS.TW.CHAR_HEIGHT, SS.TW.CHAR_WIDTH, 0, 0,
FW_REGULAR, false, FW_REGULAR, false,
false, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, false, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, FF_DONTCARE, "Lucida Console"); DEFAULT_QUALITY, FF_DONTCARE, L"Lucida Console");
if(!FixedFont) if(!FixedFont)
FixedFont = (HFONT)GetStockObject(SYSTEM_FONT); FixedFont = (HFONT)GetStockObject(SYSTEM_FONT);
@ -1331,23 +1334,29 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
// Create the heaps for all dynamic memory (AllocTemporary, MemAlloc) // Create the heaps for all dynamic memory (AllocTemporary, MemAlloc)
InitHeaps(); InitHeaps();
// Pull out the Unicode command line.
int argc;
LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc);
// A filename may have been specified on the command line; if so, then // A filename may have been specified on the command line; if so, then
// strip any quotation marks, and make it absolute. // strip any quotation marks, and make it absolute.
std::string filename = lpCmdLine; std::wstring filenameRel, filename;
size_t pos; if(argc >= 2) {
pos = filename.find_last_not_of("\""); filenameRel = argv[1];
if(pos != std::string::npos) if(filenameRel[0] == L'\"' && filenameRel[filenameRel.length() - 1] == L'\"') {
filename.erase(pos + 1); filenameRel.erase(0, 1);
pos = filename.find_first_not_of(" \""); filenameRel.erase(filenameRel.length() - 1, 1);
if(pos != std::string::npos) }
filename.erase(0, pos);
if(!filename.empty()) DWORD len = GetFullPathNameW(&filenameRel[0], 0, NULL, NULL);
filename = GetAbsoluteFilename(filename); filename.resize(len - 1);
GetFullPathNameW(&filenameRel[0], len, &filename[0], NULL);
}
#ifdef HAVE_SPACEWARE #ifdef HAVE_SPACEWARE
// Initialize the SpaceBall, if present. Test if the driver is running // Initialize the SpaceBall, if present. Test if the driver is running
// first, to avoid a long timeout if it's not. // first, to avoid a long timeout if it's not.
HWND swdc = FindWindow("SpaceWare Driver Class", NULL); HWND swdc = FindWindowW(L"SpaceWare Driver Class", NULL);
if(swdc != NULL) { if(swdc != NULL) {
SiOpenData sod; SiOpenData sod;
SiInitialize(); SiInitialize();
@ -1361,7 +1370,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
// Call in to the platform-independent code, and let them do their init // Call in to the platform-independent code, and let them do their init
SS.Init(); SS.Init();
if(!filename.empty()) if(!filename.empty())
SS.OpenFile(filename); SS.OpenFile(Narrow(filename));
// And now it's the message loop. All calls in to the rest of the code // And now it's the message loop. All calls in to the rest of the code
// will be from the wndprocs. // will be from the wndprocs.

View File

@ -19,7 +19,52 @@ void dbp(const char *str, ...)
_vsnprintf(buf, sizeof(buf), str, f); _vsnprintf(buf, sizeof(buf), str, f);
va_end(f); va_end(f);
OutputDebugString(buf); // The native version of OutputDebugString, unlike most others,
// is OutputDebugStringA.
OutputDebugStringA(buf);
}
std::string Narrow(const wchar_t *in)
{
std::string out;
DWORD len = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL);
out.resize(len - 1);
if(!WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], len, NULL, NULL))
oops();
return out;
}
std::string Narrow(const std::wstring &in)
{
if(in == L"") return "";
std::string out;
out.resize(WideCharToMultiByte(CP_UTF8, 0, &in[0], in.length(), NULL, 0, NULL, NULL));
if(!WideCharToMultiByte(CP_UTF8, 0, &in[0], in.length(),
&out[0], out.length(), NULL, NULL))
oops();
return out;
}
std::wstring Widen(const char *in)
{
std::wstring out;
DWORD len = MultiByteToWideChar(CP_UTF8, 0, in, -1, NULL, 0);
out.resize(len - 1);
if(!MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], len))
oops();
return out;
}
std::wstring Widen(const std::string &in)
{
if(in == "") return L"";
std::wstring out;
out.resize(MultiByteToWideChar(CP_UTF8, 0, &in[0], in.length(), NULL, 0));
if(!MultiByteToWideChar(CP_UTF8, 0, &in[0], in.length(), &out[0], out.length()))
oops();
return out;
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------