#include "window.h" #ifdef ARGUI_WINDOWS #undef bind #include #define bind bind_func #include #include //TODO: check if DwmEnableMMCSS does anything useful //force some year-old C code to compile properly as C++ - I decided to switch long ago but still haven't finished. //TODO: //menu_create: check where it's resized if size changes //static bool isxp; void _window_init_shell() { WNDCLASS wc; wc.style=0; wc.lpfnWndProc=DefWindowProc; wc.cbClsExtra=0; wc.cbWndExtra=0; wc.hInstance=GetModuleHandle(NULL); wc.hIcon=LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(1)); wc.hCursor=LoadCursor(NULL, IDC_ARROW); wc.hbrBackground=GetSysColorBrush(COLOR_3DFACE); wc.lpszMenuName=NULL; wc.lpszClassName="arlib"; RegisterClass(&wc); //DWORD version=GetVersion(); //DWORD trueversion=(LOBYTE(LOWORD(version))<<8 | HIBYTE(LOWORD(version))); //isxp=(trueversion<=0x0501); } static HMENU menu_to_hmenu(windowmenu_menu* menu); //static void menu_delete(windowmenu_win32 * This); static void menu_activate(HMENU menu, DWORD pos); static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); namespace { #define WS_BASE WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX // okay microsoft, did I miss anything? #define WS_RESIZABLE (WS_BASE|WS_MAXIMIZEBOX|WS_THICKFRAME) #define WS_NONRESIZ (WS_BASE|WS_BORDER) //static bool _reflow(window * this_); //static void reflow_force(window_win32 * this_); static HWND activedialog; class window_win32; static window_win32 * firstwindow; static window_win32 * modalwindow; class window_win32 : public window { public: //used by modality window_win32 * prev; window_win32 * next; bool modal; //char padding[7]; HWND hwnd; widget_base* contents; unsigned int numchildwin; DWORD lastmousepos; windowmenu_menu* menu; HWND status; int * status_align; int * status_div; uint16_t status_count; uint8_t status_resizegrip_width; bool status_extra_spacing; bool resizable; bool isdialog; bool menuactive;//odd position to reduce padding //uint8_t delayfree;//0=normal, 1=can't free now, 2=free at next opportunity function onclose; /*private*/ void getBorderSizes(unsigned int * width, unsigned int * height) { RECT inner; RECT outer; GetClientRect(this->hwnd, &inner); GetWindowRect(this->hwnd, &outer); if (width) *width=(outer.right-outer.left)-(inner.right); if (height) *height=(outer.bottom-outer.top)-(inner.bottom); if (height && this->status) { RECT statsize; GetClientRect(this->status, &statsize); *height+=(statsize.bottom-statsize.top); } } /*private*/ void resize_stbar(unsigned int width) { if (this->status) { SendMessage(this->status, WM_SIZE, 0,0); unsigned int statuswidth=width; if (this->resizable) { if (!this->status_resizegrip_width) { RECT rect; SendMessage(this->status, SB_GETRECT, 0, (LPARAM)&rect); this->status_resizegrip_width = (uint8_t)(rect.bottom - rect.top - 8);//assume the size grip has the same width as height } statuswidth-=this->status_resizegrip_width; } int * statuspositions=malloc(sizeof(int)*this->status_count); for (int i=0;istatus_count;i++) { statuspositions[i]=statuswidth*(this->status_div[i])/240; } //statuspositions[this->status_count-1]=width; SendMessage(this->status, SB_SETPARTS, (WPARAM)this->status_count, (LPARAM)statuspositions); free(statuspositions); } } void set_is_dialog() { this->isdialog=true; } void set_parent(window * parent_) { window_win32 * parent=(window_win32*)parent_; SetWindowLongPtr(this->hwnd, GWLP_HWNDPARENT, (LONG_PTR)parent->hwnd); } /*private*/ void update_modal() { if (this->modal && IsWindowVisible(this->hwnd)) { //disable all windows if (!modalwindow)//except if they're already disabled because that's a waste of time. { window_win32 * wndw=firstwindow; while (wndw) { if (wndw!=this) EnableWindow(wndw->hwnd, false); wndw=wndw->next; } modalwindow=this; } } else { //we're gone now - if we're the one holding the windows locked, enable them if (this == modalwindow) { window_win32 * wndw=firstwindow; while (wndw) { EnableWindow(wndw->hwnd, true); wndw=wndw->next; } modalwindow=NULL; } } } void set_modal(bool modal) { this->modal=modal; this->update_modal(); } void resize(unsigned int width, unsigned int height) { unsigned int padx; unsigned int pady; this->getBorderSizes(&padx, &pady); SetWindowPos(this->hwnd, NULL, 0, 0, width+padx, height+pady, SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER); this->_reflow(); //because we're setting the window position ourselves, reflow is likely to think the status bar is correct, so we need to fix it ourselves. RECT size; GetClientRect(this->hwnd, &size); this->resize_stbar(size.right); } void set_resizable(bool resizable, function onresize) { if (this->resizable != resizable) { this->resizable=resizable; SetWindowLong(this->hwnd, GWL_STYLE, GetWindowLong(this->hwnd, GWL_STYLE) ^ WS_RESIZABLE^WS_NONRESIZ); this->_reflow(); } } void get_pos(int * x, int * y) { //TODO } void set_pos(int x, int y) { } void set_onmove(function onmove) { } void set_title(const char * title) { /*if (!isxp)*/ SetWindowText(this->hwnd, title); } void set_onclose(function onclose) { this->onclose = onclose; } void set_menu(windowmenu_menu* menu) { delete this->menu; SetMenu(this->hwnd, menu_to_hmenu(menu)); this->menu=menu; } void statusbar_create(int numslots, const int * align, const int * dividerpos) { if (!numslots) { if (this->status) { RECT barsize; GetWindowRect(this->status, &barsize); RECT mainsize; GetWindowRect(this->hwnd, &mainsize); SetWindowPos(this->hwnd, NULL, 0, 0, mainsize.right, mainsize.bottom-(barsize.bottom-barsize.top), SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER); DestroyWindow(this->status); this->status=NULL; } return; } if (!this->status) { INITCOMMONCONTROLSEX initctrls; initctrls.dwSize=sizeof(initctrls); initctrls.dwICC=ICC_BAR_CLASSES; InitCommonControlsEx(&initctrls); this->status=CreateWindow(STATUSCLASSNAME, "", WS_CHILD|WS_VISIBLE|WS_DISABLED, 0,0,0,0, this->hwnd, NULL, GetModuleHandle(NULL), NULL); //SetWindowLong(this->status, GWL_STYLE, GetWindowLong(this->status, GWL_STYLE)|WS_DISABLED);//phoenix says this is needed, or it can get tab focus //EnableWindow(this->status, false);//phoenix says this is needed, or it can get tab focus } free(this->status_align); free(this->status_div); this->status_count=numslots; this->status_align=malloc(sizeof(int)*numslots); this->status_div=malloc(sizeof(int)*numslots); for (int i=0;istatus_align[i]=align[i]; if (istatus_div[i]=dividerpos[i]; else this->status_div[i]=240; } RECT barsize; GetWindowRect(this->status, &barsize); RECT mainsize; GetWindowRect(this->hwnd, &mainsize); SetWindowPos(this->hwnd, NULL, 0, 0, mainsize.right, mainsize.bottom+(barsize.bottom-barsize.top), SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER); this->resize_stbar(barsize.right); } void statusbar_set(int slot, const char * text) { int align=this->status_align[slot]; if (align==0) { SendMessage(this->status, SB_SETTEXT, (WPARAM)slot, (LPARAM)text); } else { int textlen=strlen(text); char * newtext=malloc(1+1+textlen+1+1); newtext[0]='\t'; newtext[1]='\t'; memcpy(newtext+2, text, textlen); newtext[2+textlen]=((align==2)?' ':'\0'); newtext[2+textlen+1]='\0'; SendMessage(this->status, SB_SETTEXT, (WPARAM)slot, (LPARAM)(newtext + ((align==1)?1:0))); free(newtext); } } void replace_contents(widget_base* contents) { delete this->contents; this->contents=contents; this->numchildwin=this->contents->init(this, (uintptr_t)this->hwnd); this->_reflow(); } void set_visible(bool visible) { if (visible) { this->reflow_force(); ShowWindow(this->hwnd, SW_SHOWNORMAL); } else { ShowWindow(this->hwnd, SW_HIDE); } this->update_modal(); } bool is_visible() { return IsWindowVisible(this->hwnd); } void focus() { SetForegroundWindow(this->hwnd); } bool is_active() { return (GetForegroundWindow()==this->hwnd); } bool menu_active() { return (this->menuactive); } ~window_win32() { //if (this->delayfree) //{ // this->delayfree=2; // return; //} if (this->prev) this->prev->next=this->next; else firstwindow=this->next; if (this->next) this->next->prev=this->prev; if (this->modal) { this->set_visible(false); this->update_modal(); } delete this->contents; delete this->menu; DestroyWindow(this->hwnd); //free(this); } uintptr_t _get_handle() { return (uintptr_t)this->hwnd; } bool _reflow() { if (!IsWindowVisible(this->hwnd)) return false; this->reflow_force(); return true; } /*private*/ void reflow_force() { //Resizing our window seems to call the resize callback again. We're not interested, it'll just recurse in irritating ways. static bool recursive=false; if (recursive) return; recursive=true; RECT size; GetClientRect(this->hwnd, &size); RECT statsize; if (this->status) { GetClientRect(this->status, &statsize); size.bottom-=statsize.bottom; } this->contents->measure(); bool badx=(this->contents->width > (unsigned int)size.right || (!this->resizable && this->contents->width != (unsigned int)size.right)); bool bady=(this->contents->height > (unsigned int)size.bottom || (!this->resizable && this->contents->height != (unsigned int)size.bottom)); if (badx) size.right=this->contents->width; if (bady) size.bottom=this->contents->height; if (badx || bady) { unsigned int outerw; unsigned int outerh; this->getBorderSizes(&outerw, &outerh); //we can't defer this, or GetClientRect will get stale data, and we need the actual window size to move the rest of the windows SetWindowPos(this->hwnd, NULL, 0, 0, size.right+outerw, size.bottom+outerh, SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER); unsigned int newouterh; this->getBorderSizes(NULL, &newouterh); if (newouterh != outerh)//changing the width may change the menu between one and two lines, so resize again { SetWindowPos(this->hwnd, NULL, 0, 0, size.right+outerw, size.bottom+newouterh, SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER); } GetClientRect(this->hwnd, &size); if (this->status) size.bottom-=statsize.bottom; this->resize_stbar(size.right); } HDWP hdwp=BeginDeferWindowPos(this->numchildwin); this->contents->place(&hdwp, 0,0, size.right, size.bottom); EndDeferWindowPos(hdwp); recursive=false; } window_win32(widget_base* contents) { this->next=firstwindow; this->prev=NULL; if (this->next) this->next->prev=this; firstwindow=this; this->contents=(widget_base*)contents; this->contents->measure(); //the 6 and 28 are arbitrary; we'll set ourselves to a better size later. Windows' default placement algorithm sucks, anyways. //const char * xpmsg="Do not submit bug reports. Windows XP is unsupported by Microsoft, and unsupported by me."; this->hwnd=CreateWindow("arlib", /*isxp?xpmsg:*/"", WS_NONRESIZ, CW_USEDEFAULT, CW_USEDEFAULT, this->contents->width+6, this->contents->height+28, NULL, NULL, GetModuleHandle(NULL), NULL); SetWindowLongPtr(this->hwnd, GWLP_USERDATA, (LONG_PTR)this); SetWindowLongPtr(this->hwnd, GWLP_WNDPROC, (LONG_PTR)WindowProc); this->numchildwin = this->contents->init((window*)this, (uintptr_t)this->hwnd); this->status=NULL; this->menu=NULL; this->menuactive=false; this->resizable=false; this->onclose=NULL; this->lastmousepos=-1; this->status_resizegrip_width=0; //this->delayfree=0; this->resize(this->contents->width, this->contents->height); } }; } window* window_create(widget_base* contents) { return new window_win32(contents); } static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { window_win32 * This=(window_win32*)GetWindowLongPtr(hwnd, GWLP_USERDATA); switch (uMsg) { case WM_CTLCOLOREDIT: return _window_get_widget_color(uMsg, (HWND)lParam, (HDC)wParam, hwnd); case WM_GETMINMAXINFO: { if (This) { MINMAXINFO* mmi=(MINMAXINFO*)lParam; unsigned int padx; unsigned int pady; This->getBorderSizes(&padx, &pady); mmi->ptMinTrackSize.x=padx+This->contents->width; mmi->ptMinTrackSize.y=pady+This->contents->height; if (!This->resizable) { mmi->ptMaxTrackSize.x=mmi->ptMinTrackSize.x; mmi->ptMaxTrackSize.y=mmi->ptMinTrackSize.y; } } } break; case WM_ACTIVATE: if (LOWORD(wParam) && This->isdialog) activedialog=hwnd; else activedialog=NULL; break; case WM_CLOSE: //case WM_ENDSESSION://this isn't really the most elegant solution, but it should work. //disabling the above because I don't want the possibility of being closed between fopen and fwrite. CloseWindow: { if (This->onclose) { //This->delayfree=1; if (!This->onclose()) { //This->delayfree=0; break; } //if (This->delayfree==2) //{ // This->delayfree=0; // free_((window*)This); // break; //} //This->delayfree=0; } ShowWindow(hwnd, SW_HIDE); } break; case WM_COMMAND: { //printf("COMM=%.8zX,%.8zX\n",wParam,lParam); if (lParam==0) { //what does this 2 mean? It works, but... if (HIWORD(wParam)==0 && LOWORD(wParam)==2) goto CloseWindow; } else { NMHDR nmhdr={(HWND)lParam, LOWORD(wParam), HIWORD(wParam)}; return _window_notify_inner(&nmhdr); } } break; case WM_NOTIFY: { return _window_notify_inner((LPNMHDR)lParam); } break; case WM_MENUCOMMAND: { menu_activate((HMENU)lParam, wParam); } break; case WM_DESTROY: break; case WM_SYSCOMMAND: { //printf("SC=%.4X\n",wParam&0xFFFF); if ((wParam&0xFFF0)==SC_KEYMENU) break;//go away, we don't want automenus. Alt could be hit by accident. //we could eat WM_MOVE and WM_SIZE to nuke the stupid lockups, but that blocks moving the window entirely. //We'll have to mess with threads. goto _default; } //check WM_CONTEXTMENU case WM_SIZE: { if (!This) break;//this one seems to hit only on Wine, but whatever, worth checking. This->_reflow(); if (This->status) PostMessage(This->status, WM_SIZE, wParam, lParam); } break; case WM_ENTERMENULOOP: { This->menuactive=true; } break; case WM_EXITMENULOOP: { This->menuactive=false; } break; _default: default: return DefWindowProcA(hwnd, uMsg, wParam, lParam); } return 0; } static void handlemessage(MSG * msg) { if (activedialog && IsDialogMessage(activedialog, msg)) return; TranslateMessage(msg); DispatchMessage(msg); } void window_run_iter() { MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) handlemessage(&msg); } void window_run_wait() { MSG msg; GetMessage(&msg, NULL, 0, 0); handlemessage(&msg); window_run_iter(); } enum menu_type { mtype_item, mtype_check, mtype_radio, mtype_sep, mtype_sub }; // nothing for 'top' since it doesn't have this struct struct windowmenu::impl { uint8_t type; uint8_t nativepos; //char padding[6]; union { char * text; char ** multitext; // used by radio HMENU menu_in; }; }; static char * menu_transform_name(const char * name) { bool useaccel=(*name=='_'); if (useaccel) name++; unsigned int accelpos=0; unsigned int len=0; for (unsigned int i=0;name[i];i++) { if (name[i]=='&') len++; if (!accelpos && name[i]=='_') accelpos=i; len++; } char * ret=malloc(len+2);//NUL, extra & char * at=ret; for (unsigned int i=0;name[i];i++) { if (name[i]=='&') *(at++)='&'; if (useaccel && i==accelpos) *(at++)='&'; //This is an intentional bug. If it's reported, the user is known to use XP, and will be slapped with a large trout. //(Details: The menu entries have randomly glitched names.) //else if (isxp && rand()%7==0) *(at++)=rand()&255; else *(at++)=name[i]; } *at='\0'; return ret; } static unsigned int menu_get_native_length(windowmenu* menu); static unsigned int menu_get_native_start(windowmenu_menu* menu, unsigned int pos); static void menu_set_enabled(windowmenu* menu, bool enabled) { unsigned int pos = menu->m->nativepos; unsigned int left = menu_get_native_length(menu); while (left--) { MENUITEMINFO info; info.cbSize = sizeof(MENUITEMINFO); info.fMask = MIIM_STATE; GetMenuItemInfo(menu->m->menu_in, pos, TRUE, &info); if (enabled) info.fState &= ~MFS_GRAYED; else info.fState |= MFS_GRAYED; SetMenuItemInfo(menu->m->menu_in, pos, TRUE, &info); pos++; } } struct windowmenu_item::impl { function onactivate; const char * text; // used only before putting it in a menu }; void windowmenu_item::set_enabled(bool enable) { menu_set_enabled(this, enable); } windowmenu_item* windowmenu_item::create(const char * text, function onactivate) { windowmenu_item* menu = new windowmenu_item; menu->m = new windowmenu::impl; menu->m->type = mtype_item; menu->m->text = menu_transform_name(text); menu->mu = new windowmenu_item::impl; menu->mu->onactivate = onactivate; return menu; } windowmenu_item::~windowmenu_item() {} struct windowmenu_check::impl { function onactivate; }; void windowmenu_check::set_enabled(bool enable) { menu_set_enabled(this, enable); } bool windowmenu_check::get_checked() { MENUITEMINFO info; info.cbSize=sizeof(MENUITEMINFO); info.fMask=MIIM_STATE; GetMenuItemInfo(this->m->menu_in, this->m->nativepos, TRUE, &info); return (info.fState & MFS_CHECKED); } void windowmenu_check::set_checked(bool checked) { CheckMenuItem(this->m->menu_in, this->m->nativepos, MF_BYPOSITION | (checked?MF_CHECKED:MF_UNCHECKED)); } windowmenu_check* windowmenu_check::create(const char * text, function onactivate) { windowmenu_check* menu = new windowmenu_check; menu->m = new windowmenu::impl; menu->m->type = mtype_check; menu->m->text = menu_transform_name(text); menu->mu = new windowmenu_check::impl; menu->mu->onactivate = onactivate; return menu; } windowmenu_check::~windowmenu_check() {} struct windowmenu_radio::impl { unsigned int numchildren; unsigned int state; function onactivate; }; void windowmenu_radio::set_enabled(bool enable) { menu_set_enabled(this, enable); } unsigned int windowmenu_radio::get_state() { return this->mu->state; } void windowmenu_radio::set_state(unsigned int state) { this->mu->state = state; CheckMenuRadioItem(this->m->menu_in, this->m->nativepos, this->m->nativepos + this->mu->numchildren-1, this->m->nativepos + state, MF_BYPOSITION); } windowmenu_radio* windowmenu_radio::create(unsigned int numitems, const char * const * texts, function onactivate) { windowmenu_radio* menu = new windowmenu_radio; menu->m = new windowmenu::impl; menu->mu = new windowmenu_radio::impl; menu->m->type = mtype_radio; menu->m->multitext = malloc(sizeof(const char *) * numitems); for (unsigned int i=0;im->multitext[i] = menu_transform_name(texts[i]); } menu->mu->numchildren = numitems; menu->mu->state = 0; menu->mu->onactivate = onactivate; return menu; } windowmenu_radio::~windowmenu_radio() {} struct windowmenu_separator::impl {}; windowmenu_separator* windowmenu_separator::create() { windowmenu_separator* menu = new windowmenu_separator; menu->m = new windowmenu::impl; menu->m->type = mtype_sep; return menu; } windowmenu_separator::~windowmenu_separator() {} struct windowmenu_menu::impl { HMENU container; windowmenu* * children; uint8_t numchildren; //char padding[7]; }; static unsigned int menu_get_native_length(windowmenu* menu) { return (menu->m->type==mtype_radio ? ((windowmenu_radio*)menu)->mu->numchildren : 1); } //can't return children[pos]->nativepos because 'pos' may be the number of entries in the menu //children[pos]->nativepos can also be wrong because it was recently inserted static unsigned int menu_get_native_start(windowmenu_menu* menu, unsigned int pos) { return (pos ? menu->mu->children[pos-1]->m->nativepos + menu_get_native_length(menu->mu->children[pos-1]) : 0); } static void menu_add_item(windowmenu_menu* menu, unsigned int pos, windowmenu* child) { HMENU hmenu = menu->mu->container; unsigned int menupos = menu_get_native_start(menu, pos); child->m->nativepos = menupos; if (child->m->type == mtype_radio) { windowmenu_radio* rchild = (windowmenu_radio*)child; for (unsigned int i=0;imu->numchildren;i++) { char * name = child->m->multitext[i]; InsertMenu(hmenu, menupos, MF_BYPOSITION|MF_STRING, 0, name); free(name); menupos++; } free(child->m->multitext); child->m->menu_in = hmenu; rchild->set_state(0); } else if (child->m->type == mtype_sep) { InsertMenu(hmenu, menupos, MF_BYPOSITION|MF_SEPARATOR, 0, NULL); } else if (child->m->type == mtype_sub) { windowmenu_menu* mchild = (windowmenu_menu*)child; InsertMenu(hmenu, menupos, MF_BYPOSITION|MF_POPUP, (UINT_PTR)mchild->mu->container, mchild->m->text); free(mchild->m->text); } else { InsertMenu(hmenu, menupos, MF_BYPOSITION|MF_STRING, 0, child->m->text); free(child->m->text); } child->m->menu_in = hmenu; } static void menu_renumber(windowmenu_menu* menu, unsigned int start) { unsigned int menupos = menu_get_native_start(menu, start); for (unsigned int i=start;imu->numchildren;i++) { windowmenu* child = menu->mu->children[i]; child->m->nativepos = menupos; menupos += menu_get_native_length(child); } } void windowmenu_menu::insert_child(unsigned int pos, windowmenu* child) { this->mu->numchildren++; this->mu->children=realloc(this->mu->children, sizeof(windowmenu*)*this->mu->numchildren); memmove(this->mu->children+pos+1, this->mu->children+pos, sizeof(windowmenu*)*(this->mu->numchildren-pos-1)); this->mu->children[pos]=(windowmenu*)child; menu_add_item(this, pos, child); //TODO: DrawMenuBar https://msdn.microsoft.com/en-us/library/windows/desktop/ms647633%28v=vs.85%29.aspx menu_renumber(this, pos); } void windowmenu_menu::remove_child(windowmenu* child) { unsigned int menupos = child->m->nativepos; unsigned int remcount = menu_get_native_length(child); delete child; while (remcount--) DeleteMenu(this->mu->container, menupos, MF_BYPOSITION); this->mu->numchildren--; unsigned int i=0; while (this->mu->children[i]!=child) i++; menu_renumber(this, i); } windowmenu_menu* windowmenu_create_submenu_shared(bool istop, const char * text, unsigned int numchildren, windowmenu* const * children) { windowmenu_menu* menu = new windowmenu_menu; if (!istop) { menu->m = new windowmenu::impl; menu->m->type = mtype_sub; menu->m->text = menu_transform_name(text); } menu->mu = new windowmenu_menu::impl; menu->mu->container = (istop ? CreateMenu() : CreatePopupMenu()); menu->mu->numchildren = numchildren; menu->mu->children = malloc(sizeof(windowmenu*)*numchildren); memcpy(menu->mu->children, children, sizeof(windowmenu*)*numchildren); for (unsigned int i=0;iinsert_child(i, children[i]); menu_add_item(menu, i, children[i]); } //MENUINFO menuinf={ .cbSize=sizeof(menuinf), .fMask=MIM_STYLE|MIM_MENUDATA, .dwStyle=MNS_NOTIFYBYPOS/*|MNS_MODELESS*/, .dwMenuData=(DWORD_PTR)This }; MENUINFO menuinf; menuinf.cbSize = sizeof(menuinf); menuinf.fMask = MIM_STYLE|MIM_MENUDATA; menuinf.dwStyle = MNS_NOTIFYBYPOS/*|MNS_MODELESS*/; //MODELESS makes the window border flash in stupid ways when switching between the menus. menuinf.dwMenuData = (DWORD_PTR)menu; SetMenuInfo(menu->mu->container, &menuinf); return menu; } windowmenu_menu* windowmenu_menu::create(const char * text, unsigned int numchildren, windowmenu* const * children) { return windowmenu_create_submenu_shared(false, text, numchildren, children); } windowmenu_menu* windowmenu_menu::create_top(unsigned int numchildren, windowmenu_menu* const * children) { return windowmenu_create_submenu_shared(true, NULL, numchildren, (windowmenu**)children); } windowmenu_menu::~windowmenu_menu() { for (unsigned int i=0;imu->numchildren;i++) delete this->mu->children[i]; free(this->mu->children); } static HMENU menu_to_hmenu(windowmenu_menu* menu) { return menu->mu->container; } static void menu_activate(HMENU hmenu, DWORD pos) { MENUINFO menuinf; menuinf.cbSize = sizeof(menuinf); menuinf.fMask = MIM_MENUDATA; GetMenuInfo(hmenu, &menuinf); windowmenu_menu* menu = (windowmenu_menu*)menuinf.dwMenuData; unsigned int i=0; while (pos >= menu->mu->children[i]->m->nativepos + menu_get_native_length(menu->mu->children[i])) i++; windowmenu* activate = menu->mu->children[i]; if (activate->m->type == mtype_item) { windowmenu_item* item = (windowmenu_item*)activate; item->mu->onactivate(); } if (activate->m->type == mtype_check) { windowmenu_check* check = (windowmenu_check*)activate; MENUITEMINFO info; info.cbSize=sizeof(MENUITEMINFO); info.fMask=MIIM_STATE; GetMenuItemInfo(check->m->menu_in, pos, TRUE, &info); bool state = (info.fState & MFS_CHECKED); state = !state; CheckMenuItem(check->m->menu_in, pos, MF_BYPOSITION | (state?MF_CHECKED:MF_UNCHECKED)); check->mu->onactivate(state); } if (activate->m->type == mtype_radio) { windowmenu_radio* radio = (windowmenu_radio*)activate; unsigned int newstate = pos - radio->m->nativepos; if (newstate == radio->mu->state) return; radio->mu->state = newstate; CheckMenuRadioItem(radio->m->menu_in, radio->m->nativepos, radio->m->nativepos + radio->mu->numchildren-1, radio->m->nativepos + newstate, MF_BYPOSITION); radio->mu->onactivate(newstate); } } #endif