mirror of https://github.com/bsnes-emu/bsnes.git
359 lines
17 KiB
C++
359 lines
17 KiB
C++
namespace phoenix {
|
|
|
|
static const unsigned Windows2000 = 0x0500;
|
|
static const unsigned WindowsXP = 0x0501;
|
|
static const unsigned WindowsVista = 0x0600;
|
|
static const unsigned Windows7 = 0x0601;
|
|
|
|
static unsigned OsVersion() {
|
|
OSVERSIONINFO versionInfo = {0};
|
|
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
|
|
GetVersionEx(&versionInfo);
|
|
return (versionInfo.dwMajorVersion << 8) + (versionInfo.dwMajorVersion << 0);
|
|
}
|
|
|
|
static HBITMAP CreateBitmap(const image& image) {
|
|
HDC hdc = GetDC(0);
|
|
BITMAPINFO bitmapInfo;
|
|
memset(&bitmapInfo, 0, sizeof(BITMAPINFO));
|
|
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bitmapInfo.bmiHeader.biWidth = image.width;
|
|
bitmapInfo.bmiHeader.biHeight = -image.height; //bitmaps are stored upside down unless we negate height
|
|
bitmapInfo.bmiHeader.biPlanes = 1;
|
|
bitmapInfo.bmiHeader.biBitCount = 32;
|
|
bitmapInfo.bmiHeader.biCompression = BI_RGB;
|
|
bitmapInfo.bmiHeader.biSizeImage = image.width * image.height * 4;
|
|
void* bits = nullptr;
|
|
HBITMAP hbitmap = CreateDIBSection(hdc, &bitmapInfo, DIB_RGB_COLORS, &bits, NULL, 0);
|
|
if(bits) memcpy(bits, image.data, image.width * image.height * 4);
|
|
ReleaseDC(0, hdc);
|
|
return hbitmap;
|
|
}
|
|
|
|
static lstring DropPaths(WPARAM wparam) {
|
|
auto dropList = HDROP(wparam);
|
|
auto fileCount = DragQueryFile(dropList, ~0u, nullptr, 0);
|
|
|
|
lstring paths;
|
|
for(unsigned n = 0; n < fileCount; n++) {
|
|
auto length = DragQueryFile(dropList, n, nullptr, 0);
|
|
auto buffer = new wchar_t[length + 1];
|
|
|
|
if(DragQueryFile(dropList, n, buffer, length + 1)) {
|
|
string path = (const char*)utf8_t(buffer);
|
|
path.transform("\\", "/");
|
|
if(directory::exists(path) && !path.endsWith("/")) path.append("/");
|
|
paths.append(path);
|
|
}
|
|
|
|
delete[] buffer;
|
|
}
|
|
|
|
return paths;
|
|
}
|
|
|
|
static Layout* GetParentWidgetLayout(Sizable* sizable) {
|
|
while(sizable) {
|
|
if(sizable->state.parent && dynamic_cast<TabFrame*>(sizable->state.parent)) return (Layout*)sizable;
|
|
sizable = sizable->state.parent;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static Widget* GetParentWidget(Sizable* sizable) {
|
|
while(sizable) {
|
|
if(sizable->state.parent && dynamic_cast<TabFrame*>(sizable->state.parent)) return (Widget*)sizable->state.parent;
|
|
sizable = sizable->state.parent;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static unsigned GetWindowZOrder(HWND hwnd) {
|
|
unsigned z = 0;
|
|
for(HWND next = hwnd; next != NULL; next = GetWindow(next, GW_HWNDPREV)) z++;
|
|
return z;
|
|
}
|
|
|
|
static void ImageList_Append(HIMAGELIST imageList, const nall::image& source, unsigned scale) {
|
|
auto image = source;
|
|
if(image.empty()) {
|
|
image.allocate(scale, scale);
|
|
image.fill(GetSysColor(COLOR_WINDOW));
|
|
}
|
|
image.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0);
|
|
image.scale(scale, scale);
|
|
HBITMAP bitmap = CreateBitmap(image);
|
|
ImageList_Add(imageList, bitmap, NULL);
|
|
DeleteObject(bitmap);
|
|
}
|
|
|
|
static Keyboard::Keycode Keysym(unsigned keysym, unsigned keyflags) {
|
|
#define pressed(keysym) (GetAsyncKeyState(keysym) & 0x8000)
|
|
#define enabled(keysym) (GetKeyState(keysym))
|
|
#define shifted() (pressed(VK_LSHIFT) || pressed(VK_RSHIFT))
|
|
#define extended() (keyflags & (1 << 24))
|
|
|
|
switch(keysym) {
|
|
case VK_ESCAPE: return Keyboard::Keycode::Escape;
|
|
case VK_F1: return Keyboard::Keycode::F1;
|
|
case VK_F2: return Keyboard::Keycode::F2;
|
|
case VK_F3: return Keyboard::Keycode::F3;
|
|
case VK_F4: return Keyboard::Keycode::F4;
|
|
case VK_F5: return Keyboard::Keycode::F5;
|
|
case VK_F6: return Keyboard::Keycode::F6;
|
|
case VK_F7: return Keyboard::Keycode::F7;
|
|
case VK_F8: return Keyboard::Keycode::F8;
|
|
case VK_F9: return Keyboard::Keycode::F9;
|
|
//Keyboard::Keycode::F10 (should be captured under VK_MENU from WM_SYSKEY(UP,DOWN); but this is not working...)
|
|
case VK_F11: return Keyboard::Keycode::F11;
|
|
case VK_F12: return Keyboard::Keycode::F12;
|
|
|
|
//Keyboard::Keycode::PrintScreen
|
|
//Keyboard::Keycode::SysRq
|
|
case VK_SCROLL: return Keyboard::Keycode::ScrollLock;
|
|
case VK_PAUSE: return Keyboard::Keycode::Pause;
|
|
//Keyboard::Keycode::Break
|
|
|
|
case VK_INSERT: return extended() ? Keyboard::Keycode::Insert : Keyboard::Keycode::KeypadInsert;
|
|
case VK_DELETE: return extended() ? Keyboard::Keycode::Delete : Keyboard::Keycode::KeypadDelete;
|
|
case VK_HOME: return extended() ? Keyboard::Keycode::Home : Keyboard::Keycode::KeypadHome;
|
|
case VK_END: return extended() ? Keyboard::Keycode::End : Keyboard::Keycode::KeypadEnd;
|
|
case VK_PRIOR: return extended() ? Keyboard::Keycode::PageUp : Keyboard::Keycode::KeypadPageUp;
|
|
case VK_NEXT: return extended() ? Keyboard::Keycode::PageDown : Keyboard::Keycode::KeypadPageDown;
|
|
|
|
case VK_UP: return extended() ? Keyboard::Keycode::Up : Keyboard::Keycode::KeypadUp;
|
|
case VK_DOWN: return extended() ? Keyboard::Keycode::Down : Keyboard::Keycode::KeypadDown;
|
|
case VK_LEFT: return extended() ? Keyboard::Keycode::Left : Keyboard::Keycode::KeypadLeft;
|
|
case VK_RIGHT: return extended() ? Keyboard::Keycode::Right : Keyboard::Keycode::KeypadRight;
|
|
|
|
case VK_OEM_3: return !shifted() ? Keyboard::Keycode::Grave : Keyboard::Keycode::Tilde;
|
|
case '1': return !shifted() ? Keyboard::Keycode::Number1 : Keyboard::Keycode::Exclamation;
|
|
case '2': return !shifted() ? Keyboard::Keycode::Number2 : Keyboard::Keycode::At;
|
|
case '3': return !shifted() ? Keyboard::Keycode::Number3 : Keyboard::Keycode::Pound;
|
|
case '4': return !shifted() ? Keyboard::Keycode::Number4 : Keyboard::Keycode::Dollar;
|
|
case '5': return !shifted() ? Keyboard::Keycode::Number5 : Keyboard::Keycode::Percent;
|
|
case '6': return !shifted() ? Keyboard::Keycode::Number6 : Keyboard::Keycode::Power;
|
|
case '7': return !shifted() ? Keyboard::Keycode::Number7 : Keyboard::Keycode::Ampersand;
|
|
case '8': return !shifted() ? Keyboard::Keycode::Number8 : Keyboard::Keycode::Asterisk;
|
|
case '9': return !shifted() ? Keyboard::Keycode::Number9 : Keyboard::Keycode::ParenthesisLeft;
|
|
case '0': return !shifted() ? Keyboard::Keycode::Number0 : Keyboard::Keycode::ParenthesisRight;
|
|
case VK_OEM_MINUS: return !shifted() ? Keyboard::Keycode::Minus : Keyboard::Keycode::Underscore;
|
|
case VK_OEM_PLUS: return !shifted() ? Keyboard::Keycode::Equal : Keyboard::Keycode::Plus;
|
|
case VK_BACK: return Keyboard::Keycode::Backspace;
|
|
|
|
case VK_OEM_4: return !shifted() ? Keyboard::Keycode::BracketLeft : Keyboard::Keycode::BraceLeft;
|
|
case VK_OEM_6: return !shifted() ? Keyboard::Keycode::BracketRight : Keyboard::Keycode::BraceRight;
|
|
case VK_OEM_5: return !shifted() ? Keyboard::Keycode::Backslash : Keyboard::Keycode::Pipe;
|
|
case VK_OEM_1: return !shifted() ? Keyboard::Keycode::Semicolon : Keyboard::Keycode::Colon;
|
|
case VK_OEM_7: return !shifted() ? Keyboard::Keycode::Apostrophe : Keyboard::Keycode::Quote;
|
|
case VK_OEM_COMMA: return !shifted() ? Keyboard::Keycode::Comma : Keyboard::Keycode::CaretLeft;
|
|
case VK_OEM_PERIOD: return !shifted() ? Keyboard::Keycode::Period : Keyboard::Keycode::CaretRight;
|
|
case VK_OEM_2: return !shifted() ? Keyboard::Keycode::Slash : Keyboard::Keycode::Question;
|
|
|
|
case VK_TAB: return Keyboard::Keycode::Tab;
|
|
case VK_CAPITAL: return Keyboard::Keycode::CapsLock;
|
|
case VK_RETURN: return !extended() ? Keyboard::Keycode::Return : Keyboard::Keycode::Enter;
|
|
case VK_SHIFT: return !pressed(VK_RSHIFT) ? Keyboard::Keycode::ShiftLeft : Keyboard::Keycode::ShiftRight;
|
|
case VK_CONTROL: return !pressed(VK_RCONTROL) ? Keyboard::Keycode::ControlLeft : Keyboard::Keycode::ControlRight;
|
|
case VK_LWIN: return Keyboard::Keycode::SuperLeft;
|
|
case VK_RWIN: return Keyboard::Keycode::SuperRight;
|
|
case VK_MENU:
|
|
if(keyflags & (1 << 24)) return Keyboard::Keycode::AltRight;
|
|
return Keyboard::Keycode::AltLeft;
|
|
case VK_SPACE: return Keyboard::Keycode::Space;
|
|
case VK_APPS: return Keyboard::Keycode::Menu;
|
|
|
|
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M':
|
|
case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
|
|
if(enabled(VK_CAPITAL)) {
|
|
if(shifted()) {
|
|
return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::a + keysym - 'A');
|
|
} else {
|
|
return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::A + keysym - 'A');
|
|
}
|
|
} else {
|
|
if(shifted()) {
|
|
return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::A + keysym - 'A');
|
|
} else {
|
|
return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::a + keysym - 'A');
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VK_NUMLOCK: return Keyboard::Keycode::NumLock;
|
|
case VK_DIVIDE: return Keyboard::Keycode::Divide;
|
|
case VK_MULTIPLY: return Keyboard::Keycode::Multiply;
|
|
case VK_SUBTRACT: return Keyboard::Keycode::Subtract;
|
|
case VK_ADD: return Keyboard::Keycode::Add;
|
|
case VK_DECIMAL: return Keyboard::Keycode::Point;
|
|
case VK_NUMPAD1: return Keyboard::Keycode::Keypad1;
|
|
case VK_NUMPAD2: return Keyboard::Keycode::Keypad2;
|
|
case VK_NUMPAD3: return Keyboard::Keycode::Keypad3;
|
|
case VK_NUMPAD4: return Keyboard::Keycode::Keypad4;
|
|
case VK_NUMPAD5: return Keyboard::Keycode::Keypad5;
|
|
case VK_NUMPAD6: return Keyboard::Keycode::Keypad6;
|
|
case VK_NUMPAD7: return Keyboard::Keycode::Keypad7;
|
|
case VK_NUMPAD8: return Keyboard::Keycode::Keypad8;
|
|
case VK_NUMPAD9: return Keyboard::Keycode::Keypad9;
|
|
case VK_NUMPAD0: return Keyboard::Keycode::Keypad0;
|
|
|
|
case VK_CLEAR: return Keyboard::Keycode::KeypadCenter;
|
|
}
|
|
|
|
return Keyboard::Keycode::None;
|
|
|
|
#undef pressed
|
|
#undef enabled
|
|
#undef shifted
|
|
#undef extended
|
|
}
|
|
|
|
static unsigned ScrollEvent(HWND hwnd, WPARAM wparam) {
|
|
SCROLLINFO info;
|
|
memset(&info, 0, sizeof(SCROLLINFO));
|
|
info.cbSize = sizeof(SCROLLINFO);
|
|
info.fMask = SIF_ALL;
|
|
GetScrollInfo(hwnd, SB_CTL, &info);
|
|
|
|
switch(LOWORD(wparam)) {
|
|
case SB_LEFT: info.nPos = info.nMin; break;
|
|
case SB_RIGHT: info.nPos = info.nMax; break;
|
|
case SB_LINELEFT: info.nPos--; break;
|
|
case SB_LINERIGHT: info.nPos++; break;
|
|
case SB_PAGELEFT: info.nPos -= info.nMax >> 3; break;
|
|
case SB_PAGERIGHT: info.nPos += info.nMax >> 3; break;
|
|
case SB_THUMBTRACK: info.nPos = info.nTrackPos; break;
|
|
}
|
|
|
|
info.fMask = SIF_POS;
|
|
SetScrollInfo(hwnd, SB_CTL, &info, TRUE);
|
|
|
|
//Windows may clamp position to scroller range
|
|
GetScrollInfo(hwnd, SB_CTL, &info);
|
|
return info.nPos;
|
|
}
|
|
|
|
static LRESULT CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
|
|
Object* object = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
if(object == nullptr) return DefWindowProc(hwnd, msg, wparam, lparam);
|
|
Window& window = dynamic_cast<Window*>(object) ? *(Window*)object : *((Widget*)object)->Sizable::state.window;
|
|
|
|
bool process = true;
|
|
if(!pWindow::modal.empty() && !pWindow::modal.find(&window.p)) process = false;
|
|
if(applicationState.quit) process = false;
|
|
if(process == false) return DefWindowProc(hwnd, msg, wparam, lparam);
|
|
|
|
switch(msg) {
|
|
case WM_CTLCOLORBTN:
|
|
case WM_CTLCOLOREDIT:
|
|
case WM_CTLCOLORSTATIC: {
|
|
Object* object = (Object*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA);
|
|
if(object == nullptr) break;
|
|
//allow custom colors for various widgets
|
|
//note that this happens always: default colors are black text on a white background, unless overridden
|
|
//this intentionally overrides the default behavior of Windows to paint disabled controls with the window background color
|
|
if(dynamic_cast<Console*>(object)) {
|
|
Console& console = *(Console*)object;
|
|
Color& background = console.state.backgroundColor;
|
|
Color& foreground = console.state.foregroundColor;
|
|
SetTextColor((HDC)wparam, RGB(foreground.red, foreground.green, foreground.blue));
|
|
SetBkColor((HDC)wparam, RGB(background.red, background.green, background.blue));
|
|
return (LRESULT)console.p.backgroundBrush;
|
|
} else if(dynamic_cast<HexEdit*>(object)) {
|
|
HexEdit& hexEdit = *(HexEdit*)object;
|
|
Color& background = hexEdit.state.backgroundColor;
|
|
Color& foreground = hexEdit.state.foregroundColor;
|
|
SetTextColor((HDC)wparam, RGB(foreground.red, foreground.green, foreground.blue));
|
|
SetBkColor((HDC)wparam, RGB(background.red, background.green, background.blue));
|
|
return (LRESULT)hexEdit.p.backgroundBrush;
|
|
} else if(dynamic_cast<LineEdit*>(object)) {
|
|
LineEdit& lineEdit = *(LineEdit*)object;
|
|
Color& background = lineEdit.state.backgroundColor;
|
|
Color& foreground = lineEdit.state.foregroundColor;
|
|
SetTextColor((HDC)wparam, RGB(foreground.red, foreground.green, foreground.blue));
|
|
SetBkColor((HDC)wparam, RGB(background.red, background.green, background.blue));
|
|
return (LRESULT)lineEdit.p.backgroundBrush;
|
|
} else if(dynamic_cast<TextEdit*>(object)) {
|
|
TextEdit& textEdit = *(TextEdit*)object;
|
|
Color& background = textEdit.state.backgroundColor;
|
|
Color& foreground = textEdit.state.foregroundColor;
|
|
SetTextColor((HDC)wparam, RGB(foreground.red, foreground.green, foreground.blue));
|
|
SetBkColor((HDC)wparam, RGB(background.red, background.green, background.blue));
|
|
return (LRESULT)textEdit.p.backgroundBrush;
|
|
} else if(!GetParentWidget((Sizable*)object) && window.p.brush) {
|
|
SetBkColor((HDC)wparam, window.p.brushColor);
|
|
return (INT_PTR)window.p.brush;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_DRAWITEM: {
|
|
unsigned id = LOWORD(wparam);
|
|
HWND control = GetDlgItem(hwnd, id);
|
|
Object* object = (Object*)GetWindowLongPtr(control, GWLP_USERDATA);
|
|
if(object == nullptr) break;
|
|
if(dynamic_cast<TabFrame*>(object)) { ((TabFrame*)object)->p.onDrawItem(lparam); return TRUE; }
|
|
break;
|
|
}
|
|
|
|
case WM_COMMAND: {
|
|
unsigned id = LOWORD(wparam);
|
|
HWND control = GetDlgItem(hwnd, id);
|
|
Object* object = control ? (Object*)GetWindowLongPtr(control, GWLP_USERDATA) : pObject::find(id);
|
|
if(object == nullptr) break;
|
|
if(dynamic_cast<Item*>(object)) { ((Item*)object)->p.onActivate(); return FALSE; }
|
|
if(dynamic_cast<CheckItem*>(object)) { ((CheckItem*)object)->p.onToggle(); return FALSE; }
|
|
if(dynamic_cast<RadioItem*>(object)) { ((RadioItem*)object)->p.onActivate(); return FALSE; }
|
|
if(dynamic_cast<Button*>(object)) { ((Button*)object)->p.onActivate(); return FALSE; }
|
|
if(dynamic_cast<CheckButton*>(object)) { ((CheckButton*)object)->p.onToggle(); return FALSE; }
|
|
if(dynamic_cast<CheckLabel*>(object)) { ((CheckLabel*)object)->p.onToggle(); return FALSE; }
|
|
if(dynamic_cast<ComboButton*>(object) && HIWORD(wparam) == CBN_SELCHANGE) { ((ComboButton*)object)->p.onChange(); return FALSE; }
|
|
if(dynamic_cast<LineEdit*>(object) && HIWORD(wparam) == EN_CHANGE) { ((LineEdit*)object)->p.onChange(); return FALSE; }
|
|
if(dynamic_cast<RadioButton*>(object)) { ((RadioButton*)object)->p.onActivate(); return FALSE; }
|
|
if(dynamic_cast<RadioLabel*>(object)) { ((RadioLabel*)object)->p.onActivate(); return FALSE; }
|
|
if(dynamic_cast<TextEdit*>(object) && HIWORD(wparam) == EN_CHANGE) { ((TextEdit*)object)->p.onChange(); return FALSE; }
|
|
break;
|
|
}
|
|
|
|
case WM_NOTIFY: {
|
|
unsigned id = LOWORD(wparam);
|
|
HWND control = GetDlgItem(hwnd, id);
|
|
Object* object = (Object*)GetWindowLongPtr(control, GWLP_USERDATA);
|
|
if(object == nullptr) break;
|
|
if(dynamic_cast<ListView*>(object) && ((LPNMHDR)lparam)->code == LVN_ITEMACTIVATE) { ((ListView*)object)->p.onActivate(lparam); break; }
|
|
if(dynamic_cast<ListView*>(object) && ((LPNMHDR)lparam)->code == LVN_ITEMCHANGED) { ((ListView*)object)->p.onChange(lparam); break; }
|
|
if(dynamic_cast<ListView*>(object) && ((LPNMHDR)lparam)->code == NM_CUSTOMDRAW) { return ((ListView*)object)->p.onCustomDraw(lparam); }
|
|
if(dynamic_cast<TabFrame*>(object) && ((LPNMHDR)lparam)->code == TCN_SELCHANGE) { ((TabFrame*)object)->p.onChange(); break; }
|
|
break;
|
|
}
|
|
|
|
case WM_APP + AppMessage::ListView_onActivate: {
|
|
ListView* listView = (ListView*)lparam;
|
|
if(listView && listView->onActivate) listView->onActivate();
|
|
break;
|
|
}
|
|
|
|
case WM_HSCROLL:
|
|
case WM_VSCROLL: {
|
|
Object* object = nullptr;
|
|
if(lparam) {
|
|
object = (Object*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA);
|
|
} else {
|
|
unsigned id = LOWORD(wparam);
|
|
HWND control = GetDlgItem(hwnd, id);
|
|
object = (Object*)GetWindowLongPtr(control, GWLP_USERDATA);
|
|
}
|
|
if(object == nullptr) break;
|
|
if(dynamic_cast<HorizontalScroller*>(object)) { ((HorizontalScroller*)object)->p.onChange(wparam); return TRUE; }
|
|
if(dynamic_cast<VerticalScroller*>(object)) { ((VerticalScroller*)object)->p.onChange(wparam); return TRUE; }
|
|
if(dynamic_cast<HorizontalSlider*>(object)) { ((HorizontalSlider*)object)->p.onChange(); return TRUE; }
|
|
if(dynamic_cast<VerticalSlider*>(object)) { ((VerticalSlider*)object)->p.onChange(); return TRUE; }
|
|
break;
|
|
}
|
|
}
|
|
|
|
return windowProc(hwnd, msg, wparam, lparam);
|
|
}
|
|
|
|
}
|