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(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(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(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(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(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(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(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(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(object)) { ((Item*)object)->p.onActivate(); return FALSE; } if(dynamic_cast(object)) { ((CheckItem*)object)->p.onToggle(); return FALSE; } if(dynamic_cast(object)) { ((RadioItem*)object)->p.onActivate(); return FALSE; } if(dynamic_cast(object)) { ((Button*)object)->p.onActivate(); return FALSE; } if(dynamic_cast(object)) { ((CheckButton*)object)->p.onToggle(); return FALSE; } if(dynamic_cast(object)) { ((CheckLabel*)object)->p.onToggle(); return FALSE; } if(dynamic_cast(object) && HIWORD(wparam) == CBN_SELCHANGE) { ((ComboButton*)object)->p.onChange(); return FALSE; } if(dynamic_cast(object) && HIWORD(wparam) == EN_CHANGE) { ((LineEdit*)object)->p.onChange(); return FALSE; } if(dynamic_cast(object)) { ((RadioButton*)object)->p.onActivate(); return FALSE; } if(dynamic_cast(object)) { ((RadioLabel*)object)->p.onActivate(); return FALSE; } if(dynamic_cast(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(object) && ((LPNMHDR)lparam)->code == LVN_ITEMACTIVATE) { ((ListView*)object)->p.onActivate(lparam); break; } if(dynamic_cast(object) && ((LPNMHDR)lparam)->code == LVN_ITEMCHANGED) { ((ListView*)object)->p.onChange(lparam); break; } if(dynamic_cast(object) && ((LPNMHDR)lparam)->code == NM_CUSTOMDRAW) { return ((ListView*)object)->p.onCustomDraw(lparam); } if(dynamic_cast(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(object)) { ((HorizontalScroller*)object)->p.onChange(wparam); return TRUE; } if(dynamic_cast(object)) { ((VerticalScroller*)object)->p.onChange(wparam); return TRUE; } if(dynamic_cast(object)) { ((HorizontalSlider*)object)->p.onChange(); return TRUE; } if(dynamic_cast(object)) { ((VerticalSlider*)object)->p.onChange(); return TRUE; } break; } } return windowProc(hwnd, msg, wparam, lparam); } }