utility.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. namespace hiro {
  2. static const uint Windows2000 = 0x0500;
  3. static const uint WindowsXP = 0x0501;
  4. static const uint WindowsVista = 0x0600;
  5. static const uint Windows7 = 0x0601;
  6. static auto Button_CustomDraw(HWND, PAINTSTRUCT&, bool, bool, bool, unsigned, const Font&, const image&, Orientation, const string&) -> void;
  7. static auto OsVersion() -> uint {
  8. OSVERSIONINFO versionInfo{0};
  9. versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  10. GetVersionEx(&versionInfo);
  11. return (versionInfo.dwMajorVersion << 8) + (versionInfo.dwMajorVersion << 0);
  12. }
  13. static auto CreateBitmap(HDC hdc, uint width, uint height, uint32_t*& data) -> HBITMAP {
  14. BITMAPINFO info{};
  15. info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  16. info.bmiHeader.biWidth = width;
  17. info.bmiHeader.biHeight = -(int)height; //bitmaps are stored upside down unless we negate height
  18. info.bmiHeader.biPlanes = 1;
  19. info.bmiHeader.biBitCount = 32;
  20. info.bmiHeader.biCompression = BI_RGB;
  21. info.bmiHeader.biSizeImage = width * height * sizeof(uint32_t);
  22. void* bits = nullptr;
  23. auto bitmap = CreateDIBSection(hdc, &info, DIB_RGB_COLORS, &bits, nullptr, 0);
  24. data = (uint32_t*)bits;
  25. return bitmap;
  26. }
  27. static auto CreateBitmap(image icon) -> HBITMAP {
  28. icon.alphaMultiply(); //Windows AlphaBlend() requires premultiplied image data
  29. icon.transform();
  30. uint32_t* data = nullptr;
  31. auto hdc = GetDC(nullptr);
  32. auto bitmap = CreateBitmap(hdc, icon.width(), icon.height(), data);
  33. memory::copy(data, icon.data(), icon.size());
  34. ReleaseDC(nullptr, hdc);
  35. return bitmap;
  36. }
  37. static auto CreateRGB(const Color& color) -> COLORREF {
  38. return RGB(color.red(), color.green(), color.blue());
  39. }
  40. static auto DropPaths(WPARAM wparam) -> vector<string> {
  41. auto dropList = HDROP(wparam);
  42. auto fileCount = DragQueryFile(dropList, ~0u, nullptr, 0);
  43. vector<string> paths;
  44. for(auto n : range(fileCount)) {
  45. auto length = DragQueryFile(dropList, n, nullptr, 0);
  46. auto buffer = new wchar_t[length + 1];
  47. if(DragQueryFile(dropList, n, buffer, length + 1)) {
  48. string path = (const char*)utf8_t(buffer);
  49. path.transform("\\", "/");
  50. if(directory::exists(path) && !path.endsWith("/")) path.append("/");
  51. paths.append(path);
  52. }
  53. delete[] buffer;
  54. }
  55. return paths;
  56. }
  57. static auto WINAPI EnumVisibleChildWindowsProc(HWND hwnd, LPARAM lparam) -> BOOL {
  58. auto children = (vector<HWND>*)lparam;
  59. if(IsWindowVisible(hwnd)) children->append(hwnd);
  60. return true;
  61. }
  62. static auto EnumVisibleChildWindows(HWND hwnd) -> vector<HWND> {
  63. vector<HWND> children;
  64. EnumChildWindows(hwnd, EnumVisibleChildWindowsProc, (LPARAM)&children);
  65. return children;
  66. }
  67. static auto GetWindowZOrder(HWND hwnd) -> unsigned {
  68. uint z = 0;
  69. for(HWND next = hwnd; next != nullptr; next = GetWindow(next, GW_HWNDPREV)) z++;
  70. return z;
  71. }
  72. static auto ImageList_Append(HIMAGELIST imageList, image icon, unsigned scale) -> void {
  73. if(icon) {
  74. icon.scale(scale, scale);
  75. } else {
  76. icon.allocate(scale, scale);
  77. icon.fill(GetSysColor(COLOR_WINDOW));
  78. }
  79. HBITMAP bitmap = CreateBitmap(icon);
  80. ImageList_Add(imageList, bitmap, nullptr);
  81. DeleteObject(bitmap);
  82. }
  83. //post message only if said message is not already pending in the queue
  84. static auto PostMessageOnce(HWND hwnd, UINT id, WPARAM wparam, LPARAM lparam) -> void {
  85. MSG msg;
  86. if(!PeekMessage(&msg, hwnd, id, id, PM_NOREMOVE)) {
  87. PostMessage(hwnd, id, wparam, lparam);
  88. }
  89. }
  90. static auto ScrollEvent(HWND hwnd, WPARAM wparam) -> unsigned {
  91. SCROLLINFO info;
  92. memset(&info, 0, sizeof(SCROLLINFO));
  93. info.cbSize = sizeof(SCROLLINFO);
  94. info.fMask = SIF_ALL;
  95. GetScrollInfo(hwnd, SB_CTL, &info);
  96. switch(LOWORD(wparam)) {
  97. case SB_LEFT: info.nPos = info.nMin; break;
  98. case SB_RIGHT: info.nPos = info.nMax; break;
  99. case SB_LINELEFT: info.nPos--; break;
  100. case SB_LINERIGHT: info.nPos++; break;
  101. case SB_PAGELEFT: info.nPos -= info.nMax >> 3; break;
  102. case SB_PAGERIGHT: info.nPos += info.nMax >> 3; break;
  103. case SB_THUMBTRACK: info.nPos = info.nTrackPos; break;
  104. }
  105. info.fMask = SIF_POS;
  106. SetScrollInfo(hwnd, SB_CTL, &info, TRUE);
  107. //Windows may clamp position to scrollbar range
  108. GetScrollInfo(hwnd, SB_CTL, &info);
  109. return info.nPos;
  110. }
  111. static auto CALLBACK Default_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
  112. return DefWindowProc(hwnd, msg, wparam, lparam);
  113. }
  114. //separate because PopupMenu HWND does not contain GWLP_USERDATA pointing at Window needed for Shared_windowProc
  115. static auto CALLBACK Menu_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
  116. switch(msg) {
  117. case WM_MENUCOMMAND: {
  118. MENUITEMINFO mii{sizeof(MENUITEMINFO)};
  119. mii.fMask = MIIM_DATA;
  120. GetMenuItemInfo((HMENU)lparam, wparam, true, &mii);
  121. auto object = (mObject*)mii.dwItemData;
  122. if(!object) break;
  123. #if defined(Hiro_MenuItem)
  124. if(auto menuItem = dynamic_cast<mMenuItem*>(object)) {
  125. return menuItem->self()->onActivate(), false;
  126. }
  127. #endif
  128. #if defined(Hiro_MenuCheckItem)
  129. if(auto menuCheckItem = dynamic_cast<mMenuCheckItem*>(object)) {
  130. return menuCheckItem->self()->onToggle(), false;
  131. }
  132. #endif
  133. #if defined(Hiro_MenuRadioItem)
  134. if(auto menuRadioItem = dynamic_cast<mMenuRadioItem*>(object)) {
  135. return menuRadioItem->self()->onActivate(), false;
  136. }
  137. #endif
  138. break;
  139. }
  140. }
  141. return DefWindowProc(hwnd, msg, wparam, lparam);
  142. }
  143. static auto CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
  144. if(Application::state().quit) return DefWindowProc(hwnd, msg, wparam, lparam);
  145. auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
  146. if(!object) return DefWindowProc(hwnd, msg, wparam, lparam);
  147. auto window = dynamic_cast<mWindow*>(object);
  148. if(!window) window = object->parentWindow(true);
  149. if(!window) return DefWindowProc(hwnd, msg, wparam, lparam);
  150. auto pWindow = window->self();
  151. if(!pWindow) return DefWindowProc(hwnd, msg, wparam, lparam);
  152. switch(msg) {
  153. case WM_CTLCOLORBTN:
  154. case WM_CTLCOLOREDIT:
  155. case WM_CTLCOLORSTATIC: {
  156. auto object = (mObject*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA);
  157. if(!object) break;
  158. //allow custom colors for various widgets
  159. //note that this happens always: default colors are black text on a white background, unless overridden
  160. //this intentionally overrides the default behavior of Windows to paint disabled controls with the window background color
  161. #if defined(Hiro_Window) && defined(Hiro_TabFrame)
  162. if(!object->parentTabFrame(true) && window->self()->hbrush) {
  163. SetBkColor((HDC)wparam, window->self()->hbrushColor);
  164. return (LRESULT)window->self()->hbrush;
  165. }
  166. #endif
  167. #if defined(Hiro_HexEdit)
  168. if(auto hexEdit = dynamic_cast<mHexEdit*>(object)) {
  169. if(auto background = hexEdit->backgroundColor()) SetBkColor((HDC)wparam, CreateRGB(background));
  170. if(auto foreground = hexEdit->foregroundColor()) SetTextColor((HDC)wparam, CreateRGB(foreground));
  171. return (LRESULT)hexEdit->self()->backgroundBrush;
  172. }
  173. #endif
  174. #if defined(Hiro_LineEdit)
  175. if(auto lineEdit = dynamic_cast<mLineEdit*>(object)) {
  176. if(auto background = lineEdit->backgroundColor()) SetBkColor((HDC)wparam, CreateRGB(background));
  177. if(auto foreground = lineEdit->foregroundColor()) SetTextColor((HDC)wparam, CreateRGB(foreground));
  178. return (LRESULT)lineEdit->self()->backgroundBrush;
  179. }
  180. #endif
  181. #if defined(Hiro_TextEdit)
  182. if(auto textEdit = dynamic_cast<mTextEdit*>(object)) {
  183. if(auto background = textEdit->backgroundColor()) SetBkColor((HDC)wparam, CreateRGB(background));
  184. if(auto foreground = textEdit->foregroundColor()) SetTextColor((HDC)wparam, CreateRGB(foreground));
  185. return (LRESULT)textEdit->self()->backgroundBrush;
  186. }
  187. #endif
  188. break;
  189. }
  190. case WM_GETMINMAXINFO: {
  191. auto info = (LPMINMAXINFO)lparam;
  192. auto frameMargin = pWindow->frameMargin();
  193. if(auto minimumSize = window->state.minimumSize) {
  194. info->ptMinTrackSize.x = minimumSize.width() + frameMargin.width();
  195. info->ptMinTrackSize.y = minimumSize.height() + frameMargin.height();
  196. }
  197. if(auto maximumSize = window->state.maximumSize) {
  198. info->ptMaxTrackSize.x = maximumSize.width() + frameMargin.width();
  199. info->ptMaxTrackSize.y = maximumSize.height() + frameMargin.height();
  200. }
  201. break;
  202. }
  203. case WM_MENUCOMMAND: {
  204. return Menu_windowProc(hwnd, msg, wparam, lparam);
  205. }
  206. case WM_COMMAND: {
  207. if(!lparam) break;
  208. auto object = (mObject*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA);
  209. if(!object) break;
  210. #if defined(Hiro_Button)
  211. if(auto button = dynamic_cast<mButton*>(object)) {
  212. return button->self()->onActivate(), false;
  213. }
  214. #endif
  215. #if defined(Hiro_CheckButton)
  216. if(auto checkButton = dynamic_cast<mCheckButton*>(object)) {
  217. return checkButton->self()->onToggle(), false;
  218. }
  219. #endif
  220. #if defined(Hiro_CheckLabel)
  221. if(auto checkLabel = dynamic_cast<mCheckLabel*>(object)) {
  222. return checkLabel->self()->onToggle(), false;
  223. }
  224. #endif
  225. #if defined(Hiro_ComboButton)
  226. if(auto comboButton = dynamic_cast<mComboButton*>(object)) {
  227. if(HIWORD(wparam) == CBN_SELCHANGE) {
  228. return comboButton->self()->onChange(), false;
  229. }
  230. }
  231. #endif
  232. #if defined(Hiro_LineEdit)
  233. if(auto lineEdit = dynamic_cast<mLineEdit*>(object)) {
  234. if(HIWORD(wparam) == EN_CHANGE) {
  235. return lineEdit->self()->onChange(), false;
  236. }
  237. }
  238. #endif
  239. #if defined(Hiro_RadioButton)
  240. if(auto radioButton = dynamic_cast<mRadioButton*>(object)) {
  241. return radioButton->self()->onActivate(), false;
  242. }
  243. #endif
  244. #if defined(Hiro_RadioLabel)
  245. if(auto radioLabel = dynamic_cast<mRadioLabel*>(object)) {
  246. return radioLabel->self()->onActivate(), false;
  247. }
  248. #endif
  249. #if defined(Hiro_TextEdit)
  250. if(auto textEdit = dynamic_cast<mTextEdit*>(object)) {
  251. if(HIWORD(wparam) == EN_CHANGE) {
  252. return textEdit->self()->onChange(), false;
  253. }
  254. }
  255. #endif
  256. break;
  257. }
  258. case WM_NOTIFY: {
  259. //Widgets inside a TabFrame must be parented to it rather than the Window.
  260. //This is critical for proper inheritance of styles and message passing.
  261. //However, by doing this, some WM_NOTIFY messages end up being sent to both
  262. //the TabFrame and the Window; while others are only sent to the TabFrame.
  263. //To save code, hiro uses a shared callback for both of these cases.
  264. //So when a message is sent to both, we ignore the TabFrame message.
  265. bool isWindowCallback = (object == window);
  266. auto header = (LPNMHDR)lparam;
  267. auto object = (mObject*)GetWindowLongPtr((HWND)header->hwndFrom, GWLP_USERDATA);
  268. if(!object) break;
  269. #if defined(Hiro_TableView)
  270. if(auto tableView = dynamic_cast<mTableView*>(object)) {
  271. if(header->code == LVN_ITEMACTIVATE) {
  272. tableView->self()->onActivate(lparam);
  273. break;
  274. }
  275. if(header->code == LVN_ITEMCHANGED) {
  276. tableView->self()->onChange(lparam);
  277. break;
  278. }
  279. if(header->code == LVN_COLUMNCLICK) {
  280. if(isWindowCallback) tableView->self()->onSort(lparam);
  281. break;
  282. }
  283. if(header->code == NM_CLICK || header->code == NM_DBLCLK) {
  284. //onToggle performs the test to ensure the TableViewItem clicked was checkable
  285. if(isWindowCallback) tableView->self()->onToggle(lparam);
  286. break;
  287. }
  288. if(header->code == NM_RCLICK) {
  289. if(isWindowCallback) tableView->self()->onContext(lparam);
  290. break;
  291. }
  292. if(header->code == NM_CUSTOMDRAW) {
  293. return tableView->self()->onCustomDraw(lparam);
  294. }
  295. }
  296. #endif
  297. #if defined(Hiro_TabFrame)
  298. if(auto tabFrame = dynamic_cast<mTabFrame*>(object)) {
  299. if(header->code == TCN_SELCHANGE) {
  300. tabFrame->self()->onChange();
  301. break;
  302. }
  303. }
  304. #endif
  305. break;
  306. }
  307. case WM_SIZE: {
  308. bool maximized = IsZoomed(pWindow->hwnd);
  309. bool minimized = IsIconic(pWindow->hwnd);
  310. window->state.maximized = maximized;
  311. window->state.minimized = minimized;
  312. //todo: call Window::doSize() ?
  313. break;
  314. }
  315. case WM_HSCROLL:
  316. case WM_VSCROLL: {
  317. if(!lparam) break;
  318. auto object = (mObject*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA);
  319. if(!object) break;
  320. #if defined(Hiro_HorizontalScrollBar)
  321. if(auto horizontalScrollBar = dynamic_cast<mHorizontalScrollBar*>(object)) {
  322. return horizontalScrollBar->self()->onChange(wparam), true;
  323. }
  324. #endif
  325. #if defined(Hiro_HorizontalSlider)
  326. if(auto horizontalSlider = dynamic_cast<mHorizontalSlider*>(object)) {
  327. return horizontalSlider->self()->onChange(), true;
  328. }
  329. #endif
  330. #if defined(Hiro_VerticalScrollBar)
  331. if(auto verticalScrollBar = dynamic_cast<mVerticalScrollBar*>(object)) {
  332. return verticalScrollBar->self()->onChange(wparam), true;
  333. }
  334. #endif
  335. #if defined(Hiro_VerticalSlider)
  336. if(auto verticalSlider = dynamic_cast<mVerticalSlider*>(object)) {
  337. return verticalSlider->self()->onChange(), true;
  338. }
  339. #endif
  340. break;
  341. }
  342. //catch mouse events over disabled windows
  343. case WM_MOUSEMOVE:
  344. case WM_MOUSELEAVE:
  345. case WM_MOUSEHOVER: {
  346. POINT p{};
  347. GetCursorPos(&p);
  348. ScreenToClient(hwnd, &p);
  349. for(auto window : EnumVisibleChildWindows(hwnd)) {
  350. if(auto widget = (mWidget*)GetWindowLongPtr(window, GWLP_USERDATA)) {
  351. auto geometry = widget->geometry();
  352. if(p.x < geometry.x()) continue;
  353. if(p.y < geometry.y()) continue;
  354. if(p.x >= geometry.x() + geometry.width ()) continue;
  355. if(p.y >= geometry.y() + geometry.height()) continue;
  356. if(msg == WM_MOUSEMOVE) {
  357. TRACKMOUSEEVENT event{sizeof(TRACKMOUSEEVENT)};
  358. event.hwndTrack = hwnd;
  359. event.dwFlags = TME_LEAVE | TME_HOVER;
  360. event.dwHoverTime = pToolTip::Delay;
  361. TrackMouseEvent(&event);
  362. POINT p{};
  363. GetCursorPos(&p);
  364. widget->self()->doMouseMove(p.x, p.y);
  365. if(auto toolTip = pApplication::state().toolTip) {
  366. toolTip->windowProc(hwnd, msg, wparam, lparam);
  367. }
  368. }
  369. if(msg == WM_MOUSELEAVE) {
  370. widget->self()->doMouseLeave();
  371. }
  372. if(msg == WM_MOUSEHOVER) {
  373. widget->self()->doMouseHover();
  374. }
  375. }
  376. }
  377. break;
  378. }
  379. #if defined(Hiro_TableView)
  380. case AppMessage::TableView_doPaint: {
  381. if(auto tableView = (mTableView*)lparam) {
  382. if(auto self = tableView->self()) {
  383. InvalidateRect(self->hwnd, nullptr, true);
  384. }
  385. }
  386. break;
  387. }
  388. case AppMessage::TableView_onActivate: {
  389. if(auto tableView = (mTableView*)lparam) {
  390. if(auto self = tableView->self()) {
  391. tableView->doActivate(self->activateCell);
  392. }
  393. }
  394. break;
  395. }
  396. case AppMessage::TableView_onChange: {
  397. if(auto tableView = (mTableView*)lparam) {
  398. tableView->doChange();
  399. }
  400. }
  401. #endif
  402. }
  403. return CallWindowProc(windowProc, hwnd, msg, wparam, lparam);
  404. }
  405. }