462 lines
14 KiB
C++
462 lines
14 KiB
C++
|
|
#include "winutil.hpp"
|
|
#include <iostream>
|
|
#include <Windows.h>
|
|
#include <SFML/Graphics/RenderWindow.hpp>
|
|
#include <SFML/Graphics/Image.hpp>
|
|
#include <sstream>
|
|
|
|
extern sf::RenderWindow mainPtr;
|
|
OPENFILENAMEA getParty, getScen, getRsrc, putParty, putScen, putRsrc;
|
|
|
|
// TODO: I'm sure there's a better way to do this (maybe one that's keyboard layout agnostic)
|
|
// The proper way would involve use of the TextEntered event
|
|
char keyToChar(sf::Keyboard::Key key, bool isShift) {
|
|
using kb = sf::Keyboard;
|
|
switch(key) {
|
|
case kb::A: return isShift ? 'A' : 'a';
|
|
case kb::B: return isShift ? 'B' : 'b';
|
|
case kb::C: return isShift ? 'C' : 'c';
|
|
case kb::D: return isShift ? 'D' : 'd';
|
|
case kb::E: return isShift ? 'E' : 'e';
|
|
case kb::F: return isShift ? 'F' : 'f';
|
|
case kb::G: return isShift ? 'G' : 'g';
|
|
case kb::H: return isShift ? 'H' : 'h';
|
|
case kb::I: return isShift ? 'I' : 'i';
|
|
case kb::J: return isShift ? 'J' : 'j';
|
|
case kb::K: return isShift ? 'K' : 'k';
|
|
case kb::L: return isShift ? 'L' : 'l';
|
|
case kb::M: return isShift ? 'M' : 'm';
|
|
case kb::N: return isShift ? 'N' : 'n';
|
|
case kb::O: return isShift ? 'O' : 'o';
|
|
case kb::P: return isShift ? 'P' : 'p';
|
|
case kb::Q: return isShift ? 'Q' : 'q';
|
|
case kb::R: return isShift ? 'R' : 'r';
|
|
case kb::S: return isShift ? 'S' : 's';
|
|
case kb::T: return isShift ? 'T' : 't';
|
|
case kb::U: return isShift ? 'U' : 'u';
|
|
case kb::V: return isShift ? 'V' : 'v';
|
|
case kb::W: return isShift ? 'W' : 'w';
|
|
case kb::X: return isShift ? 'X' : 'x';
|
|
case kb::Y: return isShift ? 'Y' : 'y';
|
|
case kb::Z: return isShift ? 'Z' : 'z';
|
|
case kb::Num1: return isShift ? '!' : '1';
|
|
case kb::Num2: return isShift ? '@' : '2';
|
|
case kb::Num3: return isShift ? '#' : '3';
|
|
case kb::Num4: return isShift ? '$' : '4';
|
|
case kb::Num5: return isShift ? '%' : '5';
|
|
case kb::Num6: return isShift ? '^' : '6';
|
|
case kb::Num7: return isShift ? '&' : '7';
|
|
case kb::Num8: return isShift ? '*' : '8';
|
|
case kb::Num9: return isShift ? '(' : '9';
|
|
case kb::Num0: return isShift ? ')' : '0';
|
|
case kb::Tilde: return isShift ? '~' : '`';
|
|
case kb::Dash: return isShift ? '_' : '-';
|
|
case kb::Equal: return isShift ? '+' : '=';
|
|
case kb::LBracket: return isShift ? '{' : '[';
|
|
case kb::RBracket: return isShift ? '}' : ']';
|
|
case kb::SemiColon: return isShift ? ':' : ';';
|
|
case kb::Quote: return isShift ? '"' : '\'';
|
|
case kb::Comma: return isShift ? '<' : ',';
|
|
case kb::Period: return isShift ? '>' : '.';
|
|
case kb::Slash: return isShift ? '?' : '/';
|
|
case kb::BackSlash: return isShift ? '|' : '\\';
|
|
case kb::Tab: return '\t';
|
|
case kb::Space: return ' ';
|
|
case kb::Return: return '\n';
|
|
case kb::BackSpace: return '\b';
|
|
case kb::Delete: return '\x7f';
|
|
case kb::Numpad0: return '0';
|
|
case kb::Numpad1: return '1';
|
|
case kb::Numpad2: return '2';
|
|
case kb::Numpad3: return '3';
|
|
case kb::Numpad4: return '4';
|
|
case kb::Numpad5: return '5';
|
|
case kb::Numpad6: return '6';
|
|
case kb::Numpad7: return '7';
|
|
case kb::Numpad8: return '8';
|
|
case kb::Numpad9: return '9';
|
|
// TODO: Should have Equal here, but SFML doesn't distinguish between normal and keybad equal :/
|
|
// Ditto for the decimal point.
|
|
case kb::Divide: return '/';
|
|
case kb::Multiply: return '*';
|
|
case kb::Subtract: return '-';
|
|
case kb::Add: return '+';
|
|
default: break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::string get_os_version() {
|
|
// Copied and tweaked from Battle for Wesnoth
|
|
static const std::string base = "Microsoft Windows";
|
|
|
|
OSVERSIONINFOEXA v;
|
|
v.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXA);
|
|
|
|
if(!GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&v))) {
|
|
return base;
|
|
}
|
|
|
|
const DWORD vnum = v.dwMajorVersion * 100 + v.dwMinorVersion;
|
|
std::ostringstream version;
|
|
version << base << ' ';
|
|
|
|
switch(vnum) {
|
|
case 500:
|
|
version << "2000";
|
|
break;
|
|
case 501:
|
|
version << "XP";
|
|
break;
|
|
case 502:
|
|
// This will misidentify XP x64 but who really cares?
|
|
version << "Server 2003";
|
|
break;
|
|
case 600:
|
|
if(v.wProductType == VER_NT_WORKSTATION) {
|
|
version << "Vista";
|
|
} else {
|
|
version << "Server 2008";
|
|
}
|
|
break;
|
|
case 601:
|
|
if(v.wProductType == VER_NT_WORKSTATION) {
|
|
version << "7";
|
|
} else {
|
|
version << "Server 2008 R2";
|
|
}
|
|
break;
|
|
case 602:
|
|
if(v.wProductType == VER_NT_WORKSTATION) {
|
|
version << "8";
|
|
} else {
|
|
version << "Server 2012";
|
|
}
|
|
break;
|
|
case 603:
|
|
if(v.wProductType == VER_NT_WORKSTATION) {
|
|
version << "8.1";
|
|
} else {
|
|
version << "Server 2012 R2";
|
|
}
|
|
break;
|
|
case 1000:
|
|
if(v.wProductType == VER_NT_WORKSTATION) {
|
|
version << "10";
|
|
break;
|
|
} // else fallback to default
|
|
default:
|
|
if(v.wProductType != VER_NT_WORKSTATION) {
|
|
version << "Server";
|
|
}
|
|
}
|
|
|
|
if(v.szCSDVersion && *v.szCSDVersion) {
|
|
version << ' ' << v.szCSDVersion;
|
|
}
|
|
|
|
return version.str();
|
|
}
|
|
|
|
void makeFrontWindow(sf::Window& win) {
|
|
HWND win_handle = win.getSystemHandle();
|
|
BringWindowToTop(win_handle);
|
|
}
|
|
|
|
void setWindowFloating(sf::Window& win, bool floating) {
|
|
HWND win_handle = win.getSystemHandle();
|
|
UINT flags = SWP_NOMOVE | SWP_NOSIZE;
|
|
HWND newPos = floating ? HWND_TOPMOST : HWND_TOP;
|
|
// The flags param specifies that these 0's are ignored.
|
|
SetWindowPos(win_handle, newPos, 0, 0, 0, 0, flags);
|
|
}
|
|
|
|
void init_fileio() {
|
|
OPENFILENAMEA base_dlg;
|
|
memset(&base_dlg, 0, sizeof(OPENFILENAMEA));
|
|
// Common values
|
|
base_dlg.lStructSize = sizeof(OPENFILENAMEA);
|
|
base_dlg.hwndOwner = mainPtr.getSystemHandle();
|
|
base_dlg.nFilterIndex = 1;
|
|
base_dlg.Flags = OFN_DONTADDTORECENT | OFN_ENABLESIZING | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
|
|
getParty = getScen = getRsrc = putParty = putScen = putRsrc = base_dlg;
|
|
// Open saved game dialog
|
|
getParty.lpstrFilter = LPCTSTR("Blades of Exile Saved Games\0*.exg\0Legacy Saved Games\0*.mac;*.boe;*.SAV\0");
|
|
getParty.lpstrTitle = LPCTSTR("Load Game");
|
|
getParty.Flags |= OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
|
|
// Open scenario dialog
|
|
getScen.lpstrFilter = LPCTSTR("Blades of Exile Scenarios\0*.boes\0Legacy or Unpacked Scenarios\0*.exs\0");
|
|
getScen.lpstrTitle = LPCTSTR("Open Scenario");
|
|
getScen.Flags |= OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
|
|
// Import resource dialog
|
|
getRsrc.lpstrTitle = LPCTSTR("Import Resource");
|
|
getRsrc.Flags |= OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
|
|
// Save game dialog
|
|
putParty.lpstrFilter = LPCTSTR("Blades of Exile Saved Games\0*.exg\0");
|
|
putParty.lpstrTitle = LPCTSTR("Save Game");
|
|
putParty.Flags |= OFN_OVERWRITEPROMPT;
|
|
// Save scenario dialog
|
|
putScen.lpstrFilter = LPCTSTR("Blades of Exile Scenario\0*.boes\0");
|
|
putScen.lpstrTitle = LPCTSTR("Save Scenario");
|
|
putScen.Flags |= OFN_OVERWRITEPROMPT;
|
|
// Export resource dialog
|
|
putRsrc.lpstrTitle = LPCTSTR("Export Resource");
|
|
putRsrc.Flags |= OFN_OVERWRITEPROMPT;
|
|
}
|
|
|
|
static std::string runFileDlog(OPENFILENAMEA& dlg, const std::string& file, bool save) {
|
|
size_t sz = std::max<size_t>(MAX_PATH, file.length()) + 1;
|
|
char* path = new char[sz];
|
|
std::copy(file.begin(), file.end(), path);
|
|
std::fill(path + file.length(), path + sz, 0);
|
|
|
|
dlg.lpstrFile = path;
|
|
dlg.nMaxFile = sz - 1;
|
|
dlg.nFileOffset = file.find_last_of('\\');
|
|
dlg.nFileOffset++;
|
|
dlg.nFileExtension = file.find_last_of('.');
|
|
dlg.nFileExtension++;
|
|
|
|
int err;
|
|
if(save) err = GetSaveFileNameA(&dlg);
|
|
else err = GetOpenFileNameA(&dlg);
|
|
if(err == 0) {
|
|
#define CASE(x) case x: std::cerr << "File dialog failed: " #x << std::endl; break
|
|
switch(CommDlgExtendedError()) {
|
|
CASE(CDERR_DIALOGFAILURE);
|
|
CASE(CDERR_FINDRESFAILURE);
|
|
CASE(CDERR_INITIALIZATION);
|
|
CASE(CDERR_LOADRESFAILURE);
|
|
CASE(CDERR_LOADSTRFAILURE);
|
|
CASE(CDERR_LOCKRESFAILURE);
|
|
CASE(CDERR_MEMALLOCFAILURE);
|
|
CASE(CDERR_MEMLOCKFAILURE);
|
|
CASE(CDERR_NOHINSTANCE);
|
|
CASE(CDERR_NOHOOK);
|
|
CASE(CDERR_NOTEMPLATE);
|
|
CASE(CDERR_STRUCTSIZE);
|
|
CASE(FNERR_BUFFERTOOSMALL);
|
|
CASE(FNERR_INVALIDFILENAME);
|
|
CASE(FNERR_SUBCLASSFAILURE);
|
|
}
|
|
#undef CASE
|
|
}
|
|
|
|
std::string result = dlg.lpstrFile;
|
|
dlg.lpstrFile = NULL;
|
|
dlg.nMaxFile = 0;
|
|
delete path;
|
|
dlg.Flags &= ~OFN_EXTENSIONDIFFERENT;
|
|
dlg.Flags &= ~OFN_READONLY;
|
|
return result;
|
|
}
|
|
|
|
fs::path nav_get_party() {
|
|
return runFileDlog(getParty, "Blades of Exile Save.exg", false);
|
|
}
|
|
|
|
fs::path nav_put_party(fs::path def) {
|
|
return runFileDlog(putParty, def.string(), true);
|
|
}
|
|
|
|
fs::path nav_get_scenario() {
|
|
return runFileDlog(getScen, "", false);
|
|
}
|
|
|
|
fs::path nav_put_scenario(fs::path def) {
|
|
return runFileDlog(putScen, def.string(), true);
|
|
}
|
|
|
|
static std::unique_ptr<CHAR, std::default_delete<CHAR[]>> extListToFilter(std::initializer_list<std::string> extList) {
|
|
std::string title, filter;
|
|
if(extList.size() == 0) {
|
|
title = "All files";
|
|
filter = "*.*";
|
|
} else {
|
|
if(*extList.begin() == "png") title = "Image resources";
|
|
else if(*extList.begin() == "wav") title = "Sound resources";
|
|
else title = "Resources";
|
|
bool first = true;
|
|
for(std::string ext : extList) {
|
|
if(!first) filter += ';';
|
|
first = false;
|
|
filter += "*.";
|
|
filter += ext;
|
|
}
|
|
}
|
|
size_t len = title.size() + filter.size() + 3; // The 3 is for the null terminators
|
|
CHAR* str = new CHAR[len];
|
|
std::fill_n(str, len, 0);
|
|
auto end = std::copy_n(title.begin(), title.size(), str);
|
|
end++;
|
|
std::copy_n(filter.begin(), filter.size(), end);
|
|
return std::unique_ptr<CHAR, std::default_delete<CHAR[]>>(str);
|
|
}
|
|
|
|
fs::path nav_get_rsrc(std::initializer_list<std::string> extensions) {
|
|
auto filter = extListToFilter(extensions);
|
|
getRsrc.lpstrFilter = LPCTSTR(filter.get());
|
|
return runFileDlog(getRsrc, "", false);
|
|
}
|
|
|
|
fs::path nav_put_rsrc(std::initializer_list<std::string> extensions, fs::path def) {
|
|
auto filter = extListToFilter(extensions);
|
|
putRsrc.lpstrFilter = LPCTSTR(filter.get());
|
|
return runFileDlog(putRsrc, def.string(), true);
|
|
}
|
|
|
|
void set_clipboard(std::string text) {
|
|
if(text.empty()) return;
|
|
if(!OpenClipboard(mainPtr.getSystemHandle())) return;
|
|
HGLOBAL hData = (char*) GlobalAlloc(GMEM_MOVEABLE, text.length() + 1);
|
|
char* data = (char*)GlobalLock(hData);
|
|
std::copy(text.c_str(), text.c_str() + text.length(), data);
|
|
data[text.length()] = 0;
|
|
GlobalUnlock(hData);
|
|
EmptyClipboard();
|
|
SetClipboardData(CF_TEXT, hData);
|
|
CloseClipboard();
|
|
}
|
|
|
|
std::string get_clipboard() {
|
|
if(!OpenClipboard(NULL)) return "";
|
|
HGLOBAL hData = GetClipboardData(CF_TEXT);
|
|
char* data = (char*) GlobalLock(hData);
|
|
std::string contents(data);
|
|
GlobalUnlock(hData);
|
|
CloseClipboard();
|
|
return contents;
|
|
}
|
|
|
|
static void printErr(std::string msg) {
|
|
char str[256];
|
|
str[255] = 0;
|
|
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, str, 255, NULL);
|
|
std::cerr << msg << ": " << str << std::endl;
|
|
}
|
|
|
|
void set_clipboard_img(sf::Image& img) {
|
|
if(img.getSize().x == 0 && img.getSize().y == 0) return;
|
|
size_t pxSize = img.getSize().x * img.getSize().y * 4;
|
|
HGLOBAL data = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof(BITMAPV5HEADER) + pxSize);
|
|
if(data == NULL) {
|
|
printErr("Error allocating bitmap");
|
|
return;
|
|
}
|
|
BITMAPV5HEADER* info = (BITMAPV5HEADER*) GlobalLock(data);
|
|
if(info == NULL) {
|
|
printErr("Error accessing bitmap");
|
|
GlobalFree(data);
|
|
return;
|
|
}
|
|
info->bV5Size = sizeof(BITMAPV5HEADER);
|
|
info->bV5Width = img.getSize().x;
|
|
info->bV5Height = img.getSize().y;
|
|
info->bV5Height *= -1;
|
|
info->bV5Planes = 1;
|
|
info->bV5BitCount = 32;
|
|
info->bV5Compression = BI_BITFIELDS;
|
|
info->bV5SizeImage = pxSize;
|
|
info->bV5RedMask = 0x000000ff;
|
|
info->bV5GreenMask = 0x0000ff00;
|
|
info->bV5BlueMask = 0x00ff0000;
|
|
info->bV5AlphaMask = 0xff000000;
|
|
std::copy_n(img.getPixelsPtr(), pxSize, reinterpret_cast<sf::Uint8*>(info + 1));
|
|
if(!OpenClipboard(mainPtr.getSystemHandle())) {
|
|
printErr("Error opening clipboard");
|
|
GlobalFree(data);
|
|
return;
|
|
}
|
|
if(!EmptyClipboard()) {
|
|
printErr("Error emptying clipboard");
|
|
GlobalFree(data);
|
|
}
|
|
auto result = SetClipboardData(CF_DIBV5, data);
|
|
if(result == NULL) {
|
|
printErr("Error setting clipboard");
|
|
GlobalFree(data);
|
|
}
|
|
CloseClipboard();
|
|
}
|
|
|
|
std::unique_ptr<sf::Image> get_clipboard_img() {
|
|
if(!OpenClipboard(NULL)) return nullptr;
|
|
HGLOBAL hData = GetClipboardData(CF_DIBV5);
|
|
// TODO: Probably need to ignore all the below stuff and read the image based on the values in the BITMAPV5HEADER...
|
|
// If the bitmap is compressed as PNG or something, I can probably use img.loadFromMemory, otherwise manually decompress?
|
|
HDC dc = CreateCompatibleDC(GetDC(NULL));
|
|
if(dc == NULL) {
|
|
CloseClipboard();
|
|
return nullptr;
|
|
}
|
|
std::unique_ptr<sf::Image> img;
|
|
//HBITMAP bmp = (HBITMAP)GlobalLock(hData);
|
|
BITMAPV5HEADER* hdr = (BITMAPV5HEADER*) GlobalLock(hData);
|
|
if(hdr->bV5BitCount == 32) {
|
|
if(hdr->bV5Compression == BI_RGB || hdr->bV5Compression == BI_BITFIELDS) {
|
|
DWORD32* data = (DWORD32*) (hdr + 1);
|
|
int rShift = 0, gShift = 0, bShift = 0, aShift = 0;
|
|
if(hdr->bV5RedMask)
|
|
rShift = log2(hdr->bV5RedMask & (-hdr->bV5RedMask));
|
|
if(hdr->bV5GreenMask)
|
|
gShift = log2(hdr->bV5GreenMask & (-hdr->bV5GreenMask));
|
|
if(hdr->bV5BlueMask)
|
|
bShift = log2(hdr->bV5BlueMask & (-hdr->bV5BlueMask));
|
|
if(hdr->bV5AlphaMask)
|
|
aShift = log2(hdr->bV5AlphaMask & (-hdr->bV5AlphaMask));
|
|
img.reset(new sf::Image);
|
|
img->create(hdr->bV5Width, hdr->bV5Height);
|
|
for(int y = 0; y < abs(hdr->bV5Height); y++) {
|
|
for(int x = 0; x < hdr->bV5Width; x++) {
|
|
sf::Color clr;
|
|
clr.r = (*data & hdr->bV5RedMask) >> rShift;
|
|
clr.g = (*data & hdr->bV5GreenMask) >> gShift;
|
|
clr.b = (*data & hdr->bV5BlueMask) >> bShift;
|
|
if(hdr->bV5AlphaMask)
|
|
clr.a = (*data & hdr->bV5AlphaMask) >> aShift;
|
|
if(hdr->bV5Height < 0)
|
|
img->setPixel(x, y, clr);
|
|
else img->setPixel(x, hdr->bV5Height - y - 1, clr);
|
|
data++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CLEANUP:
|
|
GlobalUnlock(hData);
|
|
CloseClipboard();
|
|
ReleaseDC(NULL, dc);
|
|
return img;
|
|
}
|
|
|
|
void beep() {
|
|
MessageBeep(-1);
|
|
}
|
|
|
|
void launchURL(std::string url) {
|
|
ShellExecuteA(NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL);
|
|
}
|
|
|
|
// TODO: Implement modal session.
|
|
// It seems that Windows doesn't have anything similar to the Mac modal session, so I might have to somehow simulate it myself.
|
|
void ModalSession::pumpEvents() {
|
|
HWND win_handle = (HWND)session;
|
|
}
|
|
|
|
ModalSession::ModalSession(sf::Window& win, sf::Window& p) : parent(&p) {
|
|
session = win.getSystemHandle();
|
|
EnableWindow(parent->getSystemHandle(), false);
|
|
}
|
|
|
|
ModalSession::~ModalSession() {
|
|
HWND win_handle = (HWND)session;
|
|
EnableWindow(parent->getSystemHandle(), true);
|
|
makeFrontWindow(*parent);
|
|
RedrawWindow(parent->getSystemHandle(), NULL, NULL, RDW_NOCHILDREN | RDW_UPDATENOW);
|
|
}
|
|
|
|
int getMenubarHeight() {
|
|
return GetSystemMetrics(SM_CYMENU);
|
|
}
|