/* Cockos SWELL (Simple/Small Win32 Emulation Layer for Linux/OSX) Copyright (C) 2006 and later, Cockos, Inc. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. This file provides basic windows menu API */ #ifndef SWELL_PROVIDED_BY_APP #include "swell.h" #include "swell-menugen.h" #include "swell-internal.h" #include "../ptrlist.h" #include "../wdlcstring.h" static bool MenuIsStringType(const MENUITEMINFO *inf) { return inf->fType == MFT_STRING || inf->fType == MFT_RADIOCHECK; } HMENU__ *HMENU__::Duplicate() { HMENU__ *p = new HMENU__; int x; for (x = 0; x < items.GetSize(); x ++) { MENUITEMINFO *s = items.Get(x); MENUITEMINFO *inf = (MENUITEMINFO*)calloc(sizeof(MENUITEMINFO),1); *inf = *s; if (inf->dwTypeData && MenuIsStringType(inf)) inf->dwTypeData=strdup(inf->dwTypeData); if (inf->hSubMenu) inf->hSubMenu = inf->hSubMenu->Duplicate(); p->items.Add(inf); } return p; } void HMENU__::freeMenuItem(void *p) { MENUITEMINFO *inf = (MENUITEMINFO *)p; if (!inf) return; if (inf->hSubMenu) inf->hSubMenu->Release(); if (MenuIsStringType(inf)) free(inf->dwTypeData); free(inf); } static MENUITEMINFO *GetMenuItemByID(HMENU menu, int id, bool searchChildren=true) { if (WDL_NOT_NORMALLY(!menu)) return 0; int x; for (x = 0; x < menu->items.GetSize(); x ++) if (menu->items.Get(x)->wID == (UINT)id) return menu->items.Get(x); if (searchChildren) for (x = 0; x < menu->items.GetSize(); x ++) { if (menu->items.Get(x)->hSubMenu) { MENUITEMINFO *ret = GetMenuItemByID(menu->items.Get(x)->hSubMenu,id,true); if (ret) return ret; } } return 0; } bool SetMenuItemText(HMENU hMenu, int idx, int flag, const char *text) { MENUITEMINFO *item = WDL_NORMALLY(hMenu) ? ((flag & MF_BYPOSITION) ? hMenu->items.Get(idx) : GetMenuItemByID(hMenu,idx)) : NULL; if (!item) return false; if (MenuIsStringType(item)) free(item->dwTypeData); else item->fType = MFT_STRING; item->dwTypeData=strdup(text?text:""); return true; } bool EnableMenuItem(HMENU hMenu, int idx, int en) { MENUITEMINFO *item = WDL_NORMALLY(hMenu) ? ((en & MF_BYPOSITION) ? hMenu->items.Get(idx) : GetMenuItemByID(hMenu,idx)) : NULL; if (!item) return false; int mask = MF_ENABLED|MF_DISABLED|MF_GRAYED; item->fState &= ~mask; item->fState |= en&mask; return true; } bool CheckMenuItem(HMENU hMenu, int idx, int chk) { MENUITEMINFO *item = WDL_NORMALLY(hMenu) ? ((chk & MF_BYPOSITION) ? hMenu->items.Get(idx) : GetMenuItemByID(hMenu,idx)) : NULL; if (!item) return false; int mask = MF_CHECKED; item->fState &= ~mask; item->fState |= chk&mask; return true; } HMENU SWELL_GetCurrentMenu() { return NULL; // not osx } void SWELL_SetCurrentMenu(HMENU hmenu) { } HMENU GetSubMenu(HMENU hMenu, int pos) { MENUITEMINFO *item = WDL_NORMALLY(hMenu) ? hMenu->items.Get(pos) : NULL; if (item) return item->hSubMenu; return 0; } int GetMenuItemCount(HMENU hMenu) { if (WDL_NORMALLY(hMenu)) return hMenu->items.GetSize(); return 0; } int GetMenuItemID(HMENU hMenu, int pos) { MENUITEMINFO *item = WDL_NORMALLY(hMenu) ? hMenu->items.Get(pos) : NULL; if (!item) { WDL_ASSERT(pos==0); // do not assert if GetMenuItemID(0) is called on an empty menu return -1; } if (item->hSubMenu) return -1; return item->wID; } bool SetMenuItemModifier(HMENU hMenu, int idx, int flag, int code, unsigned int mask) { return false; } HMENU CreatePopupMenu() { return new HMENU__; } HMENU CreatePopupMenuEx(const char *title) { return CreatePopupMenu(); } void DestroyMenu(HMENU hMenu) { if (WDL_NORMALLY(hMenu)) hMenu->Release(); } int AddMenuItem(HMENU hMenu, int pos, const char *name, int tagid) { if (WDL_NOT_NORMALLY(!hMenu)) return -1; MENUITEMINFO *inf = (MENUITEMINFO*)calloc(1,sizeof(MENUITEMINFO)); inf->wID = tagid; inf->fType = MFT_STRING; inf->dwTypeData = strdup(name?name:""); hMenu->items.Insert(pos,inf); return 0; } bool DeleteMenu(HMENU hMenu, int idx, int flag) { if (WDL_NOT_NORMALLY(!hMenu)) return false; if (flag&MF_BYPOSITION) { if (hMenu->items.Get(idx)) { hMenu->items.Delete(idx,true,HMENU__::freeMenuItem); return true; } return false; } else { int x; int cnt=0; for (x=0;xitems.GetSize(); x ++) { if (!hMenu->items.Get(x)->hSubMenu && hMenu->items.Get(x)->wID == (UINT)idx) { hMenu->items.Delete(x--,true,HMENU__::freeMenuItem); cnt++; } } if (!cnt) { for (x=0;xitems.GetSize(); x ++) { if (hMenu->items.Get(x)->hSubMenu) cnt += DeleteMenu(hMenu->items.Get(x)->hSubMenu,idx,flag)?1:0; } } return !!cnt; } } BOOL SetMenuItemInfo(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi) { if (WDL_NOT_NORMALLY(!hMenu)) return 0; MENUITEMINFO *item = byPos ? hMenu->items.Get(pos) : GetMenuItemByID(hMenu, pos, true); if (!item) return 0; if ((mi->fMask & MIIM_SUBMENU) && mi->hSubMenu != item->hSubMenu) { if (item->hSubMenu) item->hSubMenu->Release(); item->hSubMenu = mi->hSubMenu; } if (mi->fMask & MIIM_TYPE) { const bool wasString = MenuIsStringType(item), isString = MenuIsStringType(mi); if (wasString != isString) { if (wasString) free(item->dwTypeData); item->dwTypeData=NULL; } if (mi->fType == MFT_BITMAP) item->dwTypeData = mi->dwTypeData; else if (isString && mi->dwTypeData) { free(item->dwTypeData); item->dwTypeData = strdup(mi->dwTypeData); } item->fType = mi->fType; } if (mi->fMask & MIIM_STATE) item->fState = mi->fState; if (mi->fMask & MIIM_ID) item->wID = mi->wID; if (mi->fMask & MIIM_DATA) item->dwItemData = mi->dwItemData; if ((mi->fMask & MIIM_BITMAP) && mi->cbSize >= sizeof(*mi)) item->hbmpItem = mi->hbmpItem; return true; } BOOL GetMenuItemInfo(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi) { if (WDL_NOT_NORMALLY(!hMenu)) return 0; MENUITEMINFO *item = byPos ? hMenu->items.Get(pos) : GetMenuItemByID(hMenu, pos, true); if (!item) return 0; if (mi->fMask & MIIM_TYPE) { mi->fType = item->fType; if (MenuIsStringType(mi) && mi->dwTypeData && mi->cch) { lstrcpyn_safe(mi->dwTypeData,item->dwTypeData?item->dwTypeData:"",mi->cch); } else if (item->fType == MFT_BITMAP) mi->dwTypeData = item->dwTypeData; } if (mi->fMask & MIIM_DATA) mi->dwItemData = item->dwItemData; if (mi->fMask & MIIM_STATE) mi->fState = item->fState; if (mi->fMask & MIIM_ID) mi->wID = item->wID; if (mi->fMask & MIIM_SUBMENU) mi->hSubMenu = item->hSubMenu; if ((mi->fMask & MIIM_BITMAP) && mi->cbSize >= sizeof(*mi)) mi->hbmpItem = item->hbmpItem; return 1; } void SWELL_InsertMenu(HMENU menu, int pos, unsigned int flag, UINT_PTR idx, const char *str) { MENUITEMINFO mi={sizeof(mi),MIIM_ID|MIIM_STATE|MIIM_TYPE,MFT_STRING, (flag & ~MF_BYPOSITION),(flag&MF_POPUP) ? 0 : (UINT)idx,NULL,NULL,NULL,0,(char *)str}; if (flag&MF_POPUP) { mi.hSubMenu = (HMENU)idx; mi.fMask |= MIIM_SUBMENU; mi.fState &= ~MF_POPUP; } if (flag&MF_SEPARATOR) { mi.fMask=MIIM_TYPE; mi.fType=MFT_SEPARATOR; mi.fState &= ~MF_SEPARATOR; } if (flag&MF_BITMAP) { mi.fType=MFT_BITMAP; mi.fState &= ~MF_BITMAP; } InsertMenuItem(menu,pos,(flag&MF_BYPOSITION) ? TRUE : FALSE, &mi); } void InsertMenuItem(HMENU hMenu, int pos, BOOL byPos, MENUITEMINFO *mi) { if (WDL_NOT_NORMALLY(!hMenu)) return; int ni=hMenu->items.GetSize(); if (!byPos) { int x; for (x=0;xitems.Get(x)->wID != (UINT)pos; x++); pos = x; } if (pos < 0 || pos > ni) pos=ni; MENUITEMINFO *inf = (MENUITEMINFO*)calloc(sizeof(MENUITEMINFO),1); inf->fType = mi->fType; if (MenuIsStringType(inf)) { inf->dwTypeData = strdup(mi->dwTypeData?mi->dwTypeData:""); } else if (mi->fType == MFT_BITMAP) { inf->dwTypeData = mi->dwTypeData; } else if (mi->fType == MFT_SEPARATOR) { } if (mi->fMask&MIIM_SUBMENU) inf->hSubMenu = mi->hSubMenu; if (mi->fMask & MIIM_STATE) inf->fState = mi->fState; if (mi->fMask & MIIM_DATA) inf->dwItemData = mi->dwItemData; if (mi->fMask & MIIM_ID) inf->wID = mi->wID; if ((mi->fMask & MIIM_BITMAP) && mi->cbSize >= sizeof(*mi)) inf->hbmpItem = mi->hbmpItem; hMenu->items.Insert(pos,inf); } void SWELL_SetMenuDestination(HMENU menu, HWND hwnd) { // only needed for Cocoa } extern RECT g_trackpopup_yroot; static POINT m_trackingPt, m_trackingPt2; static int m_trackingMouseFlag; static int m_trackingFlags,m_trackingRet; static HWND m_trackingPar; static WDL_PtrList m_trackingMenus; // each HWND as userdata = HMENU int swell_delegate_menu_message(HWND src, LPARAM lParam, int msg, bool screencoords) { static bool _reent; if (_reent) return 0; _reent = true; POINT sp = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; if (!screencoords) ClientToScreen(src,&sp); for (int x = m_trackingMenus.GetSize()-1; x>=0; x--) { HWND sw = m_trackingMenus.Get(x); if (!sw) continue; if (sw == src) break; // stop searching (don't delegate to parent) RECT r; GetWindowRect(sw,&r); if (PtInRect(&r,sp)) { POINT p = sp; ScreenToClient(sw,&p); SendMessage(sw,msg,0,MAKELPARAM(p.x,p.y)); _reent = false; return 1; } } _reent = false; return 0; } bool swell_isOSwindowmenu(SWELL_OSWINDOW osw) { int x = m_trackingMenus.GetSize(); if (osw) while (--x>=0) { HWND__ *p = m_trackingMenus.Get(x); if (p->m_oswindow == osw) return true; } return false; } int menuBarNavigate(int dir); // -1 if no menu bar active, 0 if did nothing, 1 if navigated HWND GetFocusIncludeMenus(void); static DWORD swell_menu_ignore_mousemove_from; static LRESULT WINAPI submenuWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static int lcol, rcol, mcol, top_margin, separator_ht, text_ht_pad, bitmap_ht_pad, scroll_margin, item_bm_pad; if (!lcol) { lcol=SWELL_UI_SCALE(30); rcol=SWELL_UI_SCALE(18); mcol=SWELL_UI_SCALE(10); top_margin=SWELL_UI_SCALE(4); separator_ht=SWELL_UI_SCALE(8); text_ht_pad=SWELL_UI_SCALE(4); bitmap_ht_pad=SWELL_UI_SCALE(4); scroll_margin=SWELL_UI_SCALE(10); item_bm_pad = SWELL_UI_SCALE(4); } switch (uMsg) { case WM_CREATE: hwnd->m_classname = "__SWELL_MENU"; hwnd->m_style = WS_CHILD; m_trackingMenus.Add(hwnd); SetWindowLongPtr(hwnd,GWLP_USERDATA,lParam); if (m_trackingPar && !(m_trackingFlags&TPM_NONOTIFY)) SendMessage(m_trackingPar,WM_INITMENUPOPUP,(WPARAM)lParam,0); { HDC hdc = GetDC(hwnd); HMENU__ *menu = (HMENU__*)lParam; int ht = 0, wid=SWELL_UI_SCALE(100),wid2=0; int x; for (x=0; x < menu->items.GetSize(); x++) { MENUITEMINFO *inf = menu->items.Get(x); BITMAP bm2={0,}; if (inf->hbmpItem) GetObject(inf->hbmpItem,sizeof(bm2),&bm2); if (MenuIsStringType(inf)) { RECT r={0,}; const char *str = inf->dwTypeData; if (!str || !*str) str="XXXXX"; const char *pt2 = strstr(str,"\t"); DrawText(hdc,str,pt2 ? (int)(pt2-str) : -1,&r,DT_CALCRECT|DT_SINGLELINE); if (r.bottom < bm2.bmHeight) r.bottom = bm2.bmHeight; if (bm2.bmWidth) r.right += bm2.bmWidth + item_bm_pad; if (r.right > wid) wid=r.right; ht += r.bottom + text_ht_pad; if (pt2) { r.right=r.left; DrawText(hdc,pt2+1,-1,&r,DT_CALCRECT|DT_SINGLELINE); if (r.right > wid2) wid2=r.right; } } else if (inf->fType == MFT_BITMAP) { BITMAP bm={16,16}; if (inf->dwTypeData) GetObject((HBITMAP)inf->dwTypeData,sizeof(bm),&bm); if (bm.bmHeight < bm2.bmHeight) bm.bmHeight = bm2.bmHeight; if (bm2.bmWidth) bm.bmWidth += bm2.bmWidth + item_bm_pad; if (bm.bmWidth > wid) wid = bm.bmWidth; ht += bm.bmHeight + bitmap_ht_pad; } else { // treat as separator, ignore bm2 ht += separator_ht; } } wid+=lcol+rcol + (wid2?wid2+mcol:0); ReleaseDC(hwnd,hdc); const RECT ref={m_trackingPt.x, m_trackingPt.y, m_trackingPt.x, m_trackingPt.y }; RECT vp, tr={m_trackingPt.x,m_trackingPt.y, m_trackingPt.x+wid+SWELL_UI_SCALE(4),m_trackingPt.y+ht+top_margin * 2}; SWELL_GetViewPort(&vp,&ref,true); vp.bottom -= 8; if (g_trackpopup_yroot.bottom > g_trackpopup_yroot.top && g_trackpopup_yroot.bottom > vp.top && g_trackpopup_yroot.top < vp.bottom) { const int req_h = ht*9/8+32; if (vp.bottom - g_trackpopup_yroot.bottom < req_h && g_trackpopup_yroot.top - vp.top >= req_h) { vp.bottom = g_trackpopup_yroot.top; } } if (tr.bottom > vp.bottom) { tr.top += vp.bottom-tr.bottom; tr.bottom=vp.bottom; } if (tr.right > vp.right) { if ((vp.right - m_trackingPt2.x) < (m_trackingPt2.x - vp.left)) { tr.left = m_trackingPt2.x - (tr.right-tr.left); tr.right = m_trackingPt2.x; } else { tr.left += vp.right-tr.right; tr.right=vp.right; } } if (tr.left < vp.left) { tr.right += vp.left-tr.left; tr.left=vp.left; } if (tr.top < vp.top) { tr.bottom += vp.top-tr.top; tr.top=vp.top; } if (tr.bottom > vp.bottom) tr.bottom=vp.bottom; if (tr.right > vp.right) tr.right=vp.right; SetWindowPos(hwnd,NULL,tr.left,tr.top,tr.right-tr.left,tr.bottom-tr.top,SWP_NOZORDER); hwnd->m_extra[0] = 0; // Y scroll offset hwnd->m_extra[1] = 0; // is currently autoscrolling to sel_vis hwnd->m_extra[2] = 0; // remaining items out of view, if any } ShowWindow(hwnd,SW_SHOW); SetFocus(hwnd); SetTimer(hwnd,1,100,NULL); SetTimer(hwnd,2,15,NULL); break; case WM_PAINT: { PAINTSTRUCT ps; if (BeginPaint(hwnd,&ps)) { RECT cr; GetClientRect(hwnd,&cr); HBRUSH br = CreateSolidBrush(g_swell_ctheme.menu_bg); HBRUSH br2 = CreateSolidBrushAlpha(g_swell_ctheme.menu_scroll,0.5f); HBRUSH br3 = CreateSolidBrush(g_swell_ctheme.menu_scroll_arrow); HBRUSH br_submenu_arrow = CreateSolidBrush(g_swell_ctheme.menu_submenu_arrow); HBRUSH br_submenu_arrow_sel = CreateSolidBrush(g_swell_ctheme.menu_submenu_arrow_sel); HPEN pen = CreatePen(PS_SOLID,0,g_swell_ctheme.menu_shadow); HPEN pen2 = CreatePen(PS_SOLID,0,g_swell_ctheme.menu_hilight); HGDIOBJ oldbr = SelectObject(ps.hdc,br); HGDIOBJ oldpen = SelectObject(ps.hdc,pen2); Rectangle(ps.hdc,cr.left,cr.top,cr.right,cr.bottom); SetBkMode(ps.hdc,TRANSPARENT); HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); int ypos = top_margin; int extra_left = 0; bool want_autoscroll_down = false; const int ni = menu->items.GetSize(); int x; for (x=wdl_max((int)hwnd->m_extra[0],0); x < ni; x++) { if (ypos >= cr.bottom) { extra_left = ni-x; break; } MENUITEMINFO *inf = menu->items.Get(x); if (WDL_NOT_NORMALLY(!inf)) break; RECT r={lcol,ypos,cr.right, }; bool dis = !!(inf->fState & MF_GRAYED); BITMAP bm={16,16}, bm2={0,}; if (inf->hbmpItem) GetObject(inf->hbmpItem,sizeof(bm2),&bm2); if (MenuIsStringType(inf)) { const char *str = inf->dwTypeData; if (!str || !*str) str="XXXXX"; RECT mr={0,}; DrawText(ps.hdc,str,-1,&mr,DT_CALCRECT|DT_SINGLELINE); ypos += wdl_max(mr.bottom,bm2.bmHeight) + text_ht_pad; r.bottom = ypos; } else if (inf->fType == MFT_BITMAP) { if (inf->dwTypeData) GetObject((HBITMAP)inf->dwTypeData,sizeof(bm),&bm); ypos += wdl_max(bm.bmHeight,bm2.bmHeight) + bitmap_ht_pad; r.bottom = ypos; } else { dis=true; ypos += separator_ht; r.bottom = ypos; } if (x == menu->sel_vis && !dis) { HBRUSH brs=CreateSolidBrush(g_swell_ctheme.menu_bg_sel); RECT r2=r; r2.left = cr.left + 1; r2.right = r2.right - 1; FillRect(ps.hdc,&r2,brs); DeleteObject(brs); SetTextColor(ps.hdc,g_swell_ctheme.menu_text_sel); } else { SetTextColor(ps.hdc, dis ? g_swell_ctheme.menu_text_disabled : g_swell_ctheme.menu_text); } if (bm2.bmWidth) { RECT tr = r; tr.right = tr.left + bm2.bmWidth; DrawImageInRect(ps.hdc,inf->hbmpItem,&tr); r.left += bm2.bmWidth + item_bm_pad; } if (MenuIsStringType(inf)) { const char *str = inf->dwTypeData; if (!str) str=""; const char *pt2 = strstr(str,"\t"); if (*str) { DrawText(ps.hdc,str,pt2 ? (int)(pt2-str) : -1,&r,DT_VCENTER|DT_SINGLELINE); if (pt2) { RECT tr=r; tr.right-=rcol; DrawText(ps.hdc,pt2+1,-1,&tr,DT_VCENTER|DT_SINGLELINE|DT_RIGHT); } } } else if (inf->fType == MFT_BITMAP) { if (inf->dwTypeData) { RECT tr = r; tr.top += bitmap_ht_pad/2; tr.right = tr.left + bm.bmWidth; tr.bottom = tr.top + bm.bmHeight; DrawImageInRect(ps.hdc,(HBITMAP)inf->dwTypeData,&tr); } } else { SelectObject(ps.hdc,pen2); int margin = rcol / 2; int y = r.top/2+r.bottom/2, left = cr.left+margin, right = r.right-margin; MoveToEx(ps.hdc,left,y,NULL); LineTo(ps.hdc,right,y); SelectObject(ps.hdc,pen); y++; MoveToEx(ps.hdc,left,y,NULL); LineTo(ps.hdc,right,y); } if (inf->hSubMenu) { const int sz = (r.bottom-r.top)/5, xp = r.right - rcol/2 - sz, yp = (r.top + r.bottom)/2; POINT pts[3] = { {xp, yp-sz}, {xp, yp+sz}, {xp + sz,yp} }; HGDIOBJ oldPen = SelectObject(ps.hdc,GetStockObject(NULL_PEN)); if (x == menu->sel_vis && !dis) SelectObject(ps.hdc,br_submenu_arrow_sel); else SelectObject(ps.hdc,br_submenu_arrow); Polygon(ps.hdc,pts,3); SelectObject(ps.hdc,oldPen); } if (inf->fState&MF_CHECKED) { int col; if (x == menu->sel_vis && !dis) col = g_swell_ctheme.menu_text_sel; else col = dis ? g_swell_ctheme.menu_text_disabled : g_swell_ctheme.menu_text; HPEN tpen = CreatePen(PS_SOLID,0, col); HBRUSH tbr = CreateSolidBrush(col); HGDIOBJ oldBrush = SelectObject(ps.hdc,tbr); HGDIOBJ oldPen = SelectObject(ps.hdc,tpen); const int sz = (wdl_min(lcol, r.bottom-r.top) - SWELL_UI_SCALE(10)); const int xo = SWELL_UI_SCALE(4), yo = (r.bottom+r.top)/2 - sz/2; if (inf->fType&MFT_RADIOCHECK) { Ellipse(ps.hdc, xo, yo, xo+sz, yo+sz); } else { static const unsigned char coords[12] = { 0, 76, 12, 64, 40, 92, 40, 118, 116, 16, 128, 28, }; for (int pass=0;pass<2;pass++) { POINT pts[4]; for (int i=0;i<4; i ++) { pts[i].x = lcol/2 + sz * ((int)coords[i*2+pass*4] - 64) / 128; pts[i].y = (r.bottom+r.top)/2 + sz * ((int)coords[i*2+pass*4+1] - 64) / 128; } Polygon(ps.hdc,pts,4); } } SelectObject(ps.hdc,oldPen); SelectObject(ps.hdc,oldBrush); DeleteObject(tpen); DeleteObject(tbr); } if ((r.top+ypos)/2 > cr.bottom) { extra_left = ni-x; } if (ypos > cr.bottom && x == menu->sel_vis) { want_autoscroll_down = true; extra_left = ni-x; } } hwnd->m_extra[1] = want_autoscroll_down || x <= menu->sel_vis; hwnd->m_extra[2] = extra_left; // lower scroll indicator int mid=(cr.right-cr.left)/2; SelectObject(ps.hdc,GetStockObject(NULL_PEN)); SelectObject(ps.hdc,br3); POINT pts[3]; const int smm = SWELL_UI_SCALE(2); const int smh = scroll_margin-smm*2; if (extra_left>0) { RECT fr = {cr.left, cr.bottom-scroll_margin, cr.right,cr.bottom}; FillRect(ps.hdc,&fr,br2); pts[0].x = mid; pts[0].y = cr.bottom - smm; pts[1].x = mid-smh; pts[1].y = pts[0].y - smh; pts[2].x = mid+smh; pts[2].y = pts[1].y; Polygon(ps.hdc,pts,3); } // upper scroll indicator if (hwnd->m_extra[0] > 0) { RECT fr = {cr.left, cr.top, cr.right, cr.top+scroll_margin}; FillRect(ps.hdc,&fr,br2); pts[0].x = mid; pts[0].y = cr.top + smm; pts[1].x = mid-smh; pts[1].y = pts[0].y + smh; pts[2].x = mid+smh; pts[2].y = pts[1].y; Polygon(ps.hdc,pts,3); } SelectObject(ps.hdc,oldbr); SelectObject(ps.hdc,oldpen); DeleteObject(br); DeleteObject(br2); DeleteObject(br3); DeleteObject(br_submenu_arrow); DeleteObject(br_submenu_arrow_sel); DeleteObject(pen); DeleteObject(pen2); EndPaint(hwnd,&ps); } } break; case WM_TIMER: if (wParam==1) { HWND h = GetFocusIncludeMenus(); if (h!=hwnd) { int a = h ? m_trackingMenus.Find(h) : -1; if (a<0 || a < m_trackingMenus.Find(hwnd)) { if (m_trackingMouseFlag && m_trackingMenus.Get(0)) { SetFocus(m_trackingMenus.Get(0)); m_trackingMouseFlag=0; } else DestroyWindow(hwnd); } } } else if (wParam == 2) { // menu scroll RECT tr; GetWindowRect(hwnd,&tr); POINT curM; GetCursorPos(&curM); const bool xmatch = (curM.x >= tr.left && curM.x < tr.right); if (xmatch || (hwnd->m_extra[1] && hwnd->m_extra[2])) { HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); int xFirst = wdl_max(hwnd->m_extra[0],0); const bool ymatch = curM.y >= tr.bottom-scroll_margin && curM.y < tr.bottom+scroll_margin; if (hwnd->m_extra[2] && (hwnd->m_extra[1] || ymatch)) { if (hwnd->m_extra[1] && menu->sel_vis > 0) { const int li = menu->items.GetSize() - (int)hwnd->m_extra[2]; const int incr = (menu->sel_vis - li)/2 + 2; xFirst += wdl_max(incr,1); } else xFirst++; hwnd->m_extra[0]=xFirst; hwnd->m_extra[1]=hwnd->m_extra[2]=0; if (ymatch) menu->sel_vis=-1; InvalidateRect(hwnd,NULL,FALSE); } else if (xFirst > 0 && curM.y >= tr.top-scroll_margin && curM.y < tr.top+scroll_margin) { hwnd->m_extra[0]=--xFirst; menu->sel_vis=-1; InvalidateRect(hwnd,NULL,FALSE); } } } else if (wParam == 5) { KillTimer(hwnd,5); RECT r; POINT pt; GetCursorPos(&pt); ScreenToClient(hwnd,&pt); GetClientRect(hwnd,&r); if (PtInRect(&r,pt)) SendMessage(hwnd,WM_USER+100,4,pt.y); } break; case WM_KEYUP: return 1; case WM_MOUSEWHEEL: { RECT tr; GetWindowRect(hwnd,&tr); POINT curM; GetCursorPos(&curM); const bool xmatch = (curM.x >= tr.left && curM.x < tr.right); const bool ymatch = curM.y >= tr.bottom-scroll_margin && curM.y < tr.bottom+scroll_margin; if (!xmatch || !ymatch) // if mouse is over scroll area, ignore { int amt = ((short)HIWORD(wParam))/-40; const int xFirst = wdl_max(hwnd->m_extra[0],0); if (amt > hwnd->m_extra[2]) amt = (int) hwnd->m_extra[2]; if (amt < 0 ? xFirst > 0 : amt > 0) { hwnd->m_extra[0]=wdl_max(xFirst+amt,0); if (amt>0) { hwnd->m_extra[2] -= amt; if (hwnd->m_extra[2]<0) hwnd->m_extra[2]=0; } HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); menu->sel_vis=-1; // clear selection InvalidateRect(hwnd,NULL,FALSE); } } } return 1; case WM_KEYDOWN: if (wParam >= VK_SPACE && wParam < 0x80) { // ignore mousemoves immediately after most keys swell_menu_ignore_mousemove_from = GetTickCount(); } if (wParam == VK_ESCAPE || wParam == VK_LEFT) { HWND l = m_trackingMenus.Get(m_trackingMenus.Find(hwnd)-1); if (l) SetFocus(l); else { if (wParam != VK_LEFT || menuBarNavigate(-1) < 0) DestroyWindow(hwnd); } } else if (wParam == VK_RETURN || wParam == VK_RIGHT) { HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); if (wParam == VK_RIGHT) { MENUITEMINFO *inf = menu->items.Get(menu->sel_vis); if (!inf || !inf->hSubMenu) { menuBarNavigate(1); return 1; } } SendMessage(hwnd,WM_USER+100,1,menu->sel_vis); } else if (wParam == VK_UP || wParam == VK_PRIOR) { HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); int l = menu->sel_vis; for (int i= wParam == VK_UP ? 0 : 9; i>=0; i--) { int mc = menu->items.GetSize(); while (mc--) { if (l<1) { if (wParam != VK_UP) break; l = menu->items.GetSize(); } MENUITEMINFO *inf = menu->items.Get(--l); if (!inf) break; if (!(inf->fState & MF_GRAYED) && inf->fType != MFT_SEPARATOR) { menu->sel_vis=l; break; } } } if (menu->sel_vis < hwnd->m_extra[0]) hwnd->m_extra[0] = menu->sel_vis; InvalidateRect(hwnd,NULL,FALSE); } else if (wParam == VK_DOWN || wParam == VK_NEXT) { HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); int l = menu->sel_vis; const int n =menu->items.GetSize()-1; for (int i = wParam == VK_DOWN ? 0 : 9; i>=0; i--) { int mc = n+1; while (mc--) { if (l>=n) { if (wParam != VK_DOWN) break; l=-1; hwnd->m_extra[0]=0; } MENUITEMINFO *inf = menu->items.Get(++l); if (!inf) break; if (!(inf->fState & MF_GRAYED) && inf->fType != MFT_SEPARATOR) { menu->sel_vis=l; break; } } } InvalidateRect(hwnd,NULL,FALSE); } else if (wParam == VK_END) { HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); int l = menu->items.GetSize(); while (l > 0) { MENUITEMINFO *inf = menu->items.Get(--l); if (!inf) break; if (!(inf->fState & MF_GRAYED) && inf->fType != MFT_SEPARATOR) { menu->sel_vis=l; break; } } if (menu->sel_vis < hwnd->m_extra[0]) hwnd->m_extra[0] = menu->sel_vis; InvalidateRect(hwnd,NULL,FALSE); } else if (wParam == VK_HOME) { HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); int l = 0; while (l < menu->items.GetSize()) { MENUITEMINFO *inf = menu->items.Get(l++); if (!inf) break; if (!(inf->fState & MF_GRAYED) && inf->fType != MFT_SEPARATOR) { menu->sel_vis=l-1; break; } } if (menu->sel_vis < hwnd->m_extra[0]) hwnd->m_extra[0] = menu->sel_vis; InvalidateRect(hwnd,NULL,FALSE); } else if ((lParam & FVIRTKEY) && ( (wParam >= 'A' && wParam <= 'Z') || (wParam >= '0' && wParam <= '9'))) { HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); const int n=menu->items.GetSize(); int offs = menu->sel_vis+1; if (offs<0||offs>=n) offs=0; int matchcnt=0; for(int x=0;xitems.Get(offs); if (MenuIsStringType(inf) && !(inf->fState & MF_GRAYED) && inf->dwTypeData) { const char *p = inf->dwTypeData; bool is_prefix_mode = xsel_vis); } break; } if (is_prefix_mode) while (*p) { if (*p++ == '&') { if (*p != '&') break; p++; } } if (*p > 0 && (WPARAM)toupper(*p) == wParam) { if (!matchcnt++) { menu->sel_vis = offs; if (menu->sel_vis < hwnd->m_extra[0]) hwnd->m_extra[0] = menu->sel_vis; InvalidateRect(hwnd,NULL,FALSE); } if (!is_prefix_mode) break; } } if (++offs >= n) offs=0; } } return 1; case WM_DESTROY: { int a = m_trackingMenus.Find(hwnd); m_trackingMenus.Delete(a); if (m_trackingMenus.Get(a)) DestroyWindow(m_trackingMenus.Get(a)); RemoveProp(hwnd,"SWELL_MenuOwner"); } break; case WM_USER+100: if (wParam == 1 || wParam == 2 || wParam == 3 || wParam == 4) { int which = (int) lParam; int item_ypos = which; HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); int ht = top_margin; HDC hdc=GetDC(hwnd); if (wParam > 1) which = -1; else item_ypos = 0; for (int x=wdl_max(hwnd->m_extra[0],0); x < (menu->items.GetSize()); x++) { if (wParam == 1 && which == x) { item_ypos = ht; break; } MENUITEMINFO *inf = menu->items.Get(x); int lastht = ht; BITMAP bm2={0,}; if (inf->hbmpItem) GetObject(inf->hbmpItem,sizeof(bm2),&bm2); if (MenuIsStringType(inf)) { RECT r={0,}; const char *str = inf->dwTypeData; if (!str || !*str) str="XXXXX"; const char *pt2 = strstr(str,"\t"); DrawText(hdc,str,pt2 ? (int)(pt2-str) : -1,&r,DT_CALCRECT|DT_SINGLELINE); ht += wdl_max(r.bottom,bm2.bmHeight) + text_ht_pad; } else if (inf->fType == MFT_BITMAP) { BITMAP bm={16,16}; if (inf->dwTypeData) GetObject((HBITMAP)inf->dwTypeData,sizeof(bm),&bm); ht += wdl_max(bm.bmHeight,bm2.bmHeight) + bitmap_ht_pad; } else { ht += separator_ht; } if (wParam > 1 && item_ypos < ht) { item_ypos = lastht; which = x; if (wParam == 4 && inf->hSubMenu) { HWND nextmenu = m_trackingMenus.Get(m_trackingMenus.Find(hwnd)+1); if (!nextmenu || GetWindowLongPtr(nextmenu,GWLP_USERDATA) != (LPARAM)inf->hSubMenu) { wParam = 1; // activate if not already visible menu->sel_vis = which; } } break; } } ReleaseDC(hwnd,hdc); if (wParam == 4) { MENUITEMINFO *inf = menu->items.Get(which); if (inf) { HWND next = m_trackingMenus.Get(m_trackingMenus.Find(hwnd)+1); if (next && (!inf->hSubMenu || (LPARAM)inf->hSubMenu != GetWindowLongPtr(next,GWLP_USERDATA))) DestroyWindow(next); } menu->sel_vis = which; return 0; } if (wParam == 3) { if (menu->sel_vis != which) { SetTimer(hwnd,5,300,NULL); menu->sel_vis = which; } return 0; } MENUITEMINFO *inf = menu->items.Get(which); if (inf) { if (inf->fState&MF_GRAYED){ } else if (inf->hSubMenu) { const int nextidx = m_trackingMenus.Find(hwnd)+1; HWND hh = m_trackingMenus.Get(nextidx); inf->hSubMenu->sel_vis=-1; if (hh) { m_trackingMenus.Delete(nextidx); int a = m_trackingMenus.GetSize(); while (a > nextidx) DestroyWindow(m_trackingMenus.Get(--a)); } else { hh = new HWND__(NULL,0,NULL,"menu",false,submenuWndProc,NULL, hwnd); SetProp(hh,"SWELL_MenuOwner",GetProp(hwnd,"SWELL_MenuOwner")); } RECT r; GetClientRect(hwnd,&r); m_trackingPt.x=r.right; m_trackingPt.y=item_ypos; m_trackingPt2.x=r.left; m_trackingPt2.y=item_ypos; ClientToScreen(hwnd,&m_trackingPt); ClientToScreen(hwnd,&m_trackingPt2); submenuWndProc(hh, WM_CREATE,0,(LPARAM)inf->hSubMenu); InvalidateRect(hwnd,NULL,FALSE); } else if (inf->wID) m_trackingRet = inf->wID; } } return 0; case WM_MOUSEMOVE: { if ((GetTickCount() - swell_menu_ignore_mousemove_from)<200) return 0; if (swell_delegate_menu_message(hwnd, lParam,uMsg, false)) return 0; RECT r; GetClientRect(hwnd,&r); HMENU__ *menu = (HMENU__*)GetWindowLongPtr(hwnd,GWLP_USERDATA); const int oldsel = menu->sel_vis; if (GET_X_LPARAM(lParam)>=r.left && GET_X_LPARAM(lParam)top_margin ) { int a = m_trackingMenus.Find(hwnd); if (a>=0) { // step back through and make sure each menu has the correct item selected HMENU__ *m = menu; while (--a>=0) { HWND h = m_trackingMenus.Get(a); if (WDL_NOT_NORMALLY(!h)) break; HMENU__ *m2 = (HMENU__*)GetWindowLongPtr(h,GWLP_USERDATA); if (WDL_NOT_NORMALLY(!m2)) break; MENUITEMINFO *s = m2->items.Get(m2->sel_vis); if (!s || s->hSubMenu != m) { // find m in menu, select, invalidate for (int x = 0; x < m2->items.GetSize(); x ++) { s = m2->items.Get(x); if (s && s->hSubMenu == m) { m2->sel_vis = x; InvalidateRect(h,NULL,FALSE); break; } } } m = m2; } } int mode = 3; // 4 was old-style super-fast menus SendMessage(hwnd,WM_USER+100,mode,GET_Y_LPARAM(lParam)); } else menu->sel_vis = -1; if (oldsel != menu->sel_vis) InvalidateRect(hwnd,NULL,FALSE); } return 0; case WM_LBUTTONUP: case WM_RBUTTONUP: { if (swell_delegate_menu_message(hwnd, lParam, uMsg, false)) return 0; RECT r; GetClientRect(hwnd,&r); if (GET_X_LPARAM(lParam)>=r.left && GET_X_LPARAM(lParam)top_margin ) { SendMessage(hwnd,WM_USER+100,2,GET_Y_LPARAM(lParam)); return 0; } else DestroyWindow(hwnd); } return 0; } return DefWindowProc(hwnd,uMsg,wParam,lParam); } bool PopupMenuIsActive(void) { return m_trackingMenus.GetSize()>0; } bool DestroyPopupMenus() { if (!m_trackingMenus.GetSize()) return false; DestroyWindow(m_trackingMenus.Get(0)); return true; } SWELL_OSWINDOW swell_ignore_focus_oswindow; DWORD swell_ignore_focus_oswindow_until; int TrackPopupMenu(HMENU hMenu, int flags, int xpos, int ypos, int resvd, HWND hwnd, const RECT *r) { if (WDL_NOT_NORMALLY(!hMenu) || m_trackingMenus.GetSize()) return 0; ReleaseCapture(); hMenu->Retain(); m_trackingPar=hwnd; m_trackingFlags=flags; m_trackingRet=-1; m_trackingPt2.x=m_trackingPt.x=xpos; m_trackingPt2.y=m_trackingPt.y=ypos; m_trackingMouseFlag = 0; if (GetAsyncKeyState(VK_LBUTTON)) m_trackingMouseFlag |= 1; if (GetAsyncKeyState(VK_RBUTTON)) m_trackingMouseFlag |= 2; if (GetAsyncKeyState(VK_MBUTTON)) m_trackingMouseFlag |= 4; // HWND oldFoc = GetFocus(); // bool oldFoc_child = oldFoc && (IsChild(hwnd,oldFoc) || oldFoc == hwnd || oldFoc==GetParent(hwnd)); if (hwnd) { hwnd->Retain(); swell_ignore_focus_oswindow = hwnd->m_oswindow; swell_ignore_focus_oswindow_until = GetTickCount()+500; } if (r && r->left == (1<<30) && r->top == (1<<30) && !r->right) hMenu->sel_vis = r->bottom; else hMenu->sel_vis=-1; if (!resvd || resvd == 0xbeee) swell_menu_ignore_mousemove_from = GetTickCount(); HWND hh=new HWND__(NULL,0,NULL,"menu",false,submenuWndProc,NULL, hwnd); submenuWndProc(hh,WM_CREATE,0,(LPARAM)hMenu); SetProp(hh,"SWELL_MenuOwner",(HANDLE)hwnd); while (m_trackingRet<0 && m_trackingMenus.GetSize()) { void SWELL_RunMessageLoop(); SWELL_RunMessageLoop(); Sleep(10); } int x=m_trackingMenus.GetSize()-1; while (x>=0) { HWND h = m_trackingMenus.Get(x); m_trackingMenus.Delete(x); if (h) DestroyWindow(h); x--; } // if (oldFoc_child) SetFocus(oldFoc); if (!(flags&TPM_RETURNCMD) && m_trackingRet>0) SendMessage(hwnd,WM_COMMAND,m_trackingRet,0); if (hwnd) hwnd->Release(); swell_ignore_focus_oswindow = NULL; hMenu->Release(); m_trackingPar = NULL; if (flags & TPM_RETURNCMD) return m_trackingRet>0?m_trackingRet:0; return (resvd|1)!=0xbeef || m_trackingRet>0; } void SWELL_Menu_AddMenuItem(HMENU hMenu, const char *name, int idx, unsigned int flags) { MENUITEMINFO mi={sizeof(mi),MIIM_ID|MIIM_STATE|MIIM_TYPE,MFT_STRING, (UINT)((flags)?MFS_GRAYED:0),(UINT)idx,NULL,NULL,NULL,0,(char *)name}; if (!name) { mi.fType = MFT_SEPARATOR; mi.fMask&=~(MIIM_STATE|MIIM_ID); } InsertMenuItem(hMenu,GetMenuItemCount(hMenu),TRUE,&mi); } SWELL_MenuResourceIndex *SWELL_curmodule_menuresource_head; // todo: move to per-module thingy static SWELL_MenuResourceIndex *resById(SWELL_MenuResourceIndex *head, const char *resid) { SWELL_MenuResourceIndex *p=head; while (p) { if (p->resid == resid) return p; p=p->_next; } return 0; } HMENU SWELL_LoadMenu(SWELL_MenuResourceIndex *head, const char *resid) { SWELL_MenuResourceIndex *p; if (!(p=resById(head,resid))) return 0; HMENU hMenu=CreatePopupMenu(); if (hMenu) p->createFunc(hMenu); return hMenu; } HMENU SWELL_DuplicateMenu(HMENU menu) { if (WDL_NOT_NORMALLY(!menu)) return 0; return menu->Duplicate(); } BOOL SetMenu(HWND hwnd, HMENU menu) { if (WDL_NOT_NORMALLY(!hwnd)) return 0; HMENU oldmenu = hwnd->m_menu; hwnd->m_menu = menu; if (!hwnd->m_parent && !!hwnd->m_menu != !!oldmenu) { WNDPROC oldwc = hwnd->m_wndproc; hwnd->m_wndproc = DefWindowProc; RECT r; GetWindowRect(hwnd,&r); if (oldmenu) r.bottom -= g_swell_ctheme.menubar_height; // hack: we should WM_NCCALCSIZE before and after, really else r.bottom += g_swell_ctheme.menubar_height; SetWindowPos(hwnd,NULL,0,0,r.right-r.left,r.bottom-r.top,SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE); hwnd->m_wndproc = oldwc; // resize } return TRUE; } HMENU GetMenu(HWND hwnd) { if (WDL_NOT_NORMALLY(!hwnd)) return 0; return hwnd->m_menu; } void DrawMenuBar(HWND hwnd) { if (WDL_NORMALLY(hwnd) && hwnd->m_menu) { RECT r; GetClientRect(hwnd,&r); r.top = - g_swell_ctheme.menubar_height; r.bottom=0; InvalidateRect(hwnd,&r,FALSE); } } // copied from swell-menu.mm, can have a common impl someday int SWELL_GenerateMenuFromList(HMENU hMenu, const void *_list, int listsz) { SWELL_MenuGen_Entry *list = (SWELL_MenuGen_Entry *)_list; const int l1=strlen(SWELL_MENUGEN_POPUP_PREFIX); while (listsz>0) { int cnt=1; if (!list->name) SWELL_Menu_AddMenuItem(hMenu,NULL,-1,0); else if (!strcmp(list->name,SWELL_MENUGEN_ENDPOPUP)) return list + 1 - (SWELL_MenuGen_Entry *)_list; else if (!strncmp(list->name,SWELL_MENUGEN_POPUP_PREFIX,l1)) { MENUITEMINFO mi={sizeof(mi),MIIM_SUBMENU|MIIM_STATE|MIIM_TYPE,MFT_STRING,0,0,CreatePopupMenuEx(list->name+l1),NULL,NULL,0,(char *)list->name+l1}; cnt += SWELL_GenerateMenuFromList(mi.hSubMenu,list+1,listsz-1); InsertMenuItem(hMenu,GetMenuItemCount(hMenu),TRUE,&mi); } else SWELL_Menu_AddMenuItem(hMenu,list->name,list->idx,list->flags); list+=cnt; listsz -= cnt; } return list + 1 - (SWELL_MenuGen_Entry *)_list; } #endif