/* * Copyright (c) 2004-2023 Julien Nadeau Carriere * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Menu bar widget (and base AG_Menu(3) API). */ #include #ifdef AG_WIDGETS #include #include #include #include #include #include /* #define DEBUG_EXPAND */ static int agMenuCounter = 0; AG_Menu *agAppMenu = NULL; /* Global application menu (SW mode) */ AG_Window *agAppMenuWin = NULL; /* Application menu window */ static void GetItemSize(AG_MenuItem *_Nonnull, int *_Nonnull, int *_Nonnull); /* Create a new Menu widget. */ AG_Menu * AG_MenuNew(void *parent, Uint flags) { AG_Menu *m; m = Malloc(sizeof(AG_Menu)); AG_ObjectInit(m, &agMenuClass); if (flags & AG_MENU_HFILL) { WIDGET(m)->flags |= AG_WIDGET_HFILL; } if (flags & AG_MENU_VFILL) { WIDGET(m)->flags |= AG_WIDGET_VFILL; } m->flags |= flags; AG_ObjectAttach(parent, m); return (m); } /* Create a new global application menu. */ AG_Menu * AG_MenuNewGlobal(Uint flags) { AG_Menu *m; AG_Window *win; Uint wMax, hMax; Uint wFlags = AG_WINDOW_KEEPBELOW | AG_WINDOW_DENYFOCUS; if (agAppMenu) goto exists; if (agDriverSw) { wFlags |= AG_WINDOW_PLAIN | AG_WINDOW_HMAXIMIZE; } if ((win = AG_WindowNewNamedS(wFlags, "_agAppMenu")) == NULL) { goto exists; } win->wmType = AG_WINDOW_WM_DOCK; if (win == NULL) { goto exists; } AG_SetPadding(win, "0"); AG_WindowSetCaptionS(win, agProgName ? agProgName : "agarapp"); m = AG_MenuNew(win, flags); m->style = AG_MENU_GLOBAL; /* AG_SetPadding(m, "4 0 4 0") */ WIDGET(m)->flags |= AG_WIDGET_HFILL; agAppMenu = m; agAppMenuWin = win; if (agDriverSw) { AG_GetDisplaySize(WIDGET(win)->drv, &wMax, &hMax); AG_WindowSetGeometryAligned(win, AG_WINDOW_TC, wMax, -1); } else { AG_GetDisplaySize(WIDGET(win)->drv, &wMax, &hMax); AG_WindowSetGeometryAligned(win, AG_WINDOW_TL, wMax/3, -1); } AG_WindowShow(win); return (m); exists: AG_SetErrorS("appMenu exists"); return (NULL); } static void OnWindowClose(AG_Event *_Nonnull event) { AG_Menu *m = AG_MENU_PTR(1); #ifdef DEBUG_EXPAND Debug(m, "Collapse (by `window-close')\n"); #endif AG_MenuCollapseAll(m); } static void OnWindowShown(AG_Event *_Nonnull event) { AG_Window *win = AG_WINDOW_SELF(); AG_Driver *drv = WIDGET(win)->drv; if (AGDRIVER_CLASS(drv)->setMouseAutoCapture != NULL) AGDRIVER_CLASS(drv)->setMouseAutoCapture(drv, 0); /* Disable */ } static void OnWindowHidden(AG_Event *_Nonnull event) { AG_Window *win = AG_WINDOW_SELF(); AG_Driver *drv = WIDGET(win)->drv; if (AGDRIVER_CLASS(drv)->setMouseAutoCapture != NULL) AGDRIVER_CLASS(drv)->setMouseAutoCapture(drv, -1); /* Reset */ } static void OnMouseButtonDown(AG_Event *_Nonnull event) { AG_Window *win = AG_WINDOW_SELF(); AG_Menu *m = AG_MENU_PTR(1); AG_Driver *drv = WIDGET(m)->drv; const int x = AG_INT(2); const int y = AG_INT(3); if (AGDRIVER_CLASS(drv)->setMouseAutoCapture != NULL) AGDRIVER_CLASS(drv)->setMouseAutoCapture(drv, 0); /* Disable */ if (x < 0 || y < 0 || x > WIDTH(win) || y > HEIGHT(win)) { #ifdef DEBUG_EXPAND Debug(m, "Collapse (clicked %d,%d)\n", x,y); #endif AG_MenuCollapseAll(m); } } /* * Test whether x,y intersects a menu item (and also store the height of * the item's label in hLbl). */ static __inline__ int IntersectItem(AG_MenuItem *_Nonnull mi, int x, int y, int *_Nonnull hLbl) { AG_Menu *m = mi->pmenu; int lbl, wLbl; lbl = (mi->lblMenu[1] != -1) ? mi->lblMenu[1] : (mi->lblMenu[0] != -1) ? mi->lblMenu[0] : -1; if (lbl != -1) { wLbl = WSURFACE(m,lbl)->w + m->lPadLbl + m->rPadLbl; *hLbl = WSURFACE(m,lbl)->h + m->tPadLbl + m->bPadLbl + 1; } else { wLbl = 0; *hLbl = 0; } return (x >= mi->x && x < (mi->x + wLbl) && y >= mi->y && y < (mi->y + m->itemh)); } static void MouseMotion(void *obj, int x, int y, int dx, int dy) { AG_Menu *m = obj; AG_MenuItem *mi; const int bPad = WIDGET(m)->paddingBottom - 1; int hLbl; if (!m->selecting || y < 0 || y >= HEIGHT(m)-1) return; TAILQ_FOREACH(mi, &m->root->subItems, items) { if (!IntersectItem(mi, x,y, &hLbl)) { continue; } if (mi != m->itemSel) { if (m->itemSel) { AG_MenuCollapse(m->itemSel); } m->itemSel = mi; AG_MenuExpand(m, mi, mi->x, mi->y + hLbl + bPad); } AG_Redraw(m); break; } } /* * Update a dynamic menu item's contents. * The parent menu must be locked. */ static void UpdatePolledItem(AG_MenuItem *mi) { AG_Menu *m = mi->pmenu; AG_MENUITEM_IS_VALID(mi); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_MenuInvalidateLabels(mi); AG_MenuFreeSubitems(mi); AG_PostEventByPtr(m, mi->poll, "%p", mi); #if 0 if (mi->view && (win = WIDGET(mi->view)->window)) { AG_ObjectDetach(win); mi->view = NULL; } #endif } /* * Expand an AG_MenuItem. Create a window containing the AG_MenuView * at coordinates x1,y1 (relative to widget parent). * * If not null, parent can be either an AG_Menu or an AG_MenuView. * Where unspecified, the parent AG_Menu of the MenuItem is used. * The associated AG_Menu object must be locked. */ AG_Window * AG_MenuExpand(void *parent, AG_MenuItem *mi, int x1, int y1) { AG_Window *win, *winParent; AG_MenuView *mv; AG_Menu *m; int x=x1, y=y1; Uint winFlags; #ifdef AG_DEBUG int debugLvlSave; #endif AG_MENUITEM_IS_VALID(mi); if (parent) { if (AG_MENUVIEW_ISA(parent)) { m = ((AG_MenuView *)parent)->pmenu; } else if (AG_MENU_ISA(parent)) { m = parent; } else { m = mi->pmenu; } if (!AG_OBJECT_VALID(m) || !AG_MENU_ISA(m)) return (NULL); /* Convert to absolute coordinates for AG_WindowSetGeometry() */ x += WIDGET(parent)->rView.x1; y += WIDGET(parent)->rView.y1; if ((winParent = WIDGET(parent)->window) == NULL || !AG_OBJECT_VALID(winParent) || !AG_WINDOW_ISA(winParent)) return (NULL); if (WIDGET(winParent)->drv != NULL && AGDRIVER_MULTIPLE(WIDGET(winParent)->drv)) { x += WIDGET(winParent)->x; y += WIDGET(winParent)->y; } } else { m = mi->pmenu; winParent = NULL; } AG_ObjectLock(m); if (mi->poll) /* Dynamically-generated */ UpdatePolledItem(mi); if (mi->nSubItems == 0) goto out; if ((mv = mi->view) != NULL) { /* Cached */ if (!AG_OBJECT_VALID(mv) || !AG_MENUVIEW_ISA(mv)) { mi->view = NULL; goto out; } win = WIDGET(mv)->window; if (win != NULL && AG_OBJECT_VALID(win) && AG_WINDOW_ISA(win)) { #ifdef DEBUG_EXPAND Debug(m, "Expand [%s] -> %s (cached)\n", mi->text, OBJECT(win)->name); #endif #if 1 /* * Temporary workaround for sizing problem with recovered * cached windows. */ AG_MenuInvalidateLabels(mi); AG_ObjectDetach(win); mi->view = NULL; #else AG_MenuInvalidateLabels(mi); AG_WindowSetGeometry(win, x,y, -1,-1); AG_WindowRaise(win); AG_WindowShow(win); AG_ObjectUnlock(m); return (win); #endif } } winFlags = AG_WINDOW_MODAL | AG_WINDOW_NOTITLE | AG_WINDOW_NOBORDERS | AG_WINDOW_NORESIZE | AG_WINDOW_DENYFOCUS; if (agDriverSw) winFlags |= AG_WINDOW_NOBACKGROUND; if ((win = AG_WindowNew(winFlags)) == NULL) { goto out; } win->wmType = (m->style == AG_MENU_DROPDOWN) ? AG_WINDOW_WM_DROPDOWN_MENU : AG_WINDOW_WM_POPUP_MENU; AG_ObjectSetName(win, "_menu%u", agMenuCounter++); Debug_Mute(debugLvlSave); AG_SetPadding(win, "0"); #ifdef DEBUG_EXPAND Debug(m, "Expand [%s] -> " AGSI_BR_RED "%s" AGSI_RST " (new)\n", mi->text, OBJECT(win)->name); #endif /* Collapse if user clicks outside of the window boundaries. */ WIDGET(win)->flags |= AG_WIDGET_UNFOCUSED_BUTTONDOWN; AG_SetEvent(win, "mouse-button-down", OnMouseButtonDown, "%p", m); AG_SetEvent(win, "window-close", OnWindowClose, "%p", m); AG_AddEvent(win, "window-shown", OnWindowShown, "%p", m); AG_AddEvent(win, "window-hidden", OnWindowHidden, "%p", m); mi->view = mv = Malloc(sizeof(AG_MenuView)); AG_ObjectInit(mv, &agMenuViewClass); mv->pmenu = m; mv->pitem = mi; AG_ObjectAttach(win, mv); /* * Apply the "color" style attribute normally associated with the * AG_MenuView class to the background of the menu expansion window * so we can avoid a color fill and handle "padding" properly. */ if ((m->flags & AG_MENU_NO_COLOR_BG) == 0) { AG_StyleSheet *css = (WIDGET(mv)->css) ? WIDGET(m)->css : &agDefaultCSS; char *cssData; if (AG_LookupStyleSheet(css, mv, "color", &cssData) && cssData[0] != '\0') { AG_Color cMenu; AG_ColorFromString(&cMenu, cssData, NULL); #if (AG_MODEL == AG_LARGE) AG_SetStyleF(win, "background-color", "rgb16(%d,%d,%d)", cMenu.r, cMenu.g, cMenu.b); #else AG_SetStyleF(win, "background-color", "rgb(%d,%d,%d)", cMenu.r, cMenu.g, cMenu.b); #endif } } Debug_Unmute(debugLvlSave); if (winParent) { AG_WindowAttach(winParent, win); AG_WindowMakeTransient(winParent, win); } AG_WindowSetGeometry(win, x,y, WIDGET(win)->w, WIDGET(win)->h); AG_WindowShow(win); AG_ObjectUnlock(m); return (win); out: AG_ObjectUnlock(m); return (NULL); } /* * Collapse the window displaying the specified item and its sub-menus * (if any). */ void AG_MenuCollapse(AG_MenuItem *mi) { AG_Menu *m; AG_MenuItem *miSub; if (mi == NULL || mi->view == NULL || (m = mi->pmenu) == NULL) return; AG_MENUITEM_IS_VALID(mi); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); TAILQ_FOREACH(miSub, &mi->subItems, items) AG_MenuCollapse(miSub); if (mi->view != NULL) { AG_Window *win = WIDGET(mi->view)->window; if (win != NULL) AG_WindowHide(win); } mi->sel_subitem = NULL; AG_ObjectUnlock(m); } static void CollapseAll(AG_MenuItem *_Nonnull mi) { AG_MenuItem *miSub; AG_MENUITEM_IS_VALID(mi); TAILQ_FOREACH(miSub, &mi->subItems, items) { CollapseAll(miSub); } if (mi->view) { AG_MenuCollapse(mi); } AG_MenuInvalidateLabels(mi); } /* * Collapse the window displaying the contents of an item as well as * all expanded MenuView windows, up to the menu root. */ void AG_MenuCollapseAll(AG_Menu *m) { AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); CollapseAll(m->root); m->itemSel = NULL; m->selecting = 0; AG_ObjectUnlock(m); } void AG_MenuSetLabelPadding(AG_Menu *m, int lPad, int rPad, int tPad, int bPad) { AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); if (lPad != -1) { m->lPadLbl = lPad; } if (rPad != -1) { m->rPadLbl = rPad; } if (tPad != -1) { m->tPadLbl = tPad; } if (bPad != -1) { m->bPadLbl = bPad; } AG_Redraw(m); } static void MouseButtonDown(void *obj, AG_MouseButton button, int x, int y) { AG_Menu *m = obj; const int bPad = WIDGET(m)->paddingBottom; AG_MenuItem *mi; int hLbl; TAILQ_FOREACH(mi, &m->root->subItems, items) { if (!IntersectItem(mi, x,y, &hLbl)) { continue; } if (m->itemSel == mi) { AG_MenuCollapseAll(m); m->itemSel = NULL; m->selecting = 0; } else { if (m->itemSel) { AG_MenuCollapse(m->itemSel); } m->itemSel = mi; AG_MenuExpand(m, mi, mi->x, mi->y + hLbl + bPad); m->selecting = 1; } AG_Redraw(m); break; } } /* Parent Menu (if any) must be locked. */ static AG_MenuItem *_Nonnull CreateItem(AG_MenuItem *_Nullable miParent, const char *_Nullable text, const AG_Surface *_Nullable icon) { AG_Menu *pmenu = (miParent) ? miParent->pmenu : NULL; AG_MenuItem *mi; mi = Malloc(sizeof(AG_MenuItem)); mi->tag = agNonObjectSignature; mi->text = (text) ? Strdup(text) : Strdup(""); mi->lblMenu[1] = mi->lblMenu[0] = -1; mi->lblView[2] = mi->lblView[1] = mi->lblView[0] = -1; mi->icon = (icon) ? AG_SurfaceDup(icon) : NULL; /* TODO NODUP */ mi->value = -1; mi->stateFn = NULL; mi->state = (pmenu) ? pmenu->curState : 1; mi->key_equiv = 0; mi->key_mod = 0; mi->x = 0; mi->y = (miParent && pmenu) ? (miParent->nSubItems * pmenu->itemh) - pmenu->itemh : 0; mi->flags = 0; mi->clickFn = NULL; mi->poll = NULL; mi->bind_type = AG_MENU_NO_BINDING; mi->bind_flags = 0; #ifdef AG_THREADS mi->bind_lock = NULL; #endif mi->view = NULL; mi->pmenu = pmenu; mi->sel_subitem = NULL; mi->tbButton = NULL; mi->parent = miParent; TAILQ_INIT(&mi->subItems); mi->nSubItems = 0; if (miParent) { if (icon) { miParent->flags |= AG_MENU_ITEM_ICONS; } TAILQ_INSERT_TAIL(&miParent->subItems, mi, items); miParent->nSubItems++; if (pmenu && (pmenu->style == AG_MENU_GLOBAL) && agDriverSw && agAppMenuWin) { Uint wMax, hMax; AG_SizeReq rMenu; AG_GetDisplaySize(agDriverSw, &wMax, &hMax); AG_WidgetSizeReq(pmenu, &rMenu); AG_WindowSetGeometry(agAppMenuWin, 0,0, wMax, rMenu.h); AG_Redraw(pmenu); } } return (mi); } static void FontChanged(AG_Event *_Nonnull event) { AG_Menu *m = AG_MENU_SELF(); const AG_Font *font = WFONT(m); AG_MenuInvalidateLabels(m->root); m->itemh = font->lineskip + m->tPadLbl + m->bPadLbl; } static __inline__ _Const_Attribute int KeyModMatch(AG_KeyMod miMod, AG_KeyMod kmod) { if (miMod == 0 || (miMod & kmod)) { return (1); } if ((miMod & AG_KEYMOD_CTRL_SHIFT) && (kmod & AG_KEYMOD_CTRL) && (kmod & AG_KEYMOD_SHIFT)) { return (1); } if ((miMod & AG_KEYMOD_CTRL_ALT) && (kmod & AG_KEYMOD_CTRL) && (kmod & AG_KEYMOD_ALT)) { return (1); } return (0); } static int KeyDown_Item(AG_MenuItem *mi, AG_KeySym ksym, AG_KeyMod kmod) { AG_Menu *m = mi->pmenu; AG_MenuItem *miSub; int miState; TAILQ_FOREACH(miSub, &mi->subItems, items) { if (KeyDown_Item(miSub, ksym, kmod)) /* Recurse */ return (1); } if (mi->key_equiv == 0 || mi->key_equiv != ksym || !KeyModMatch(mi->key_mod, kmod)) return (0); /* Honor disabled state */ miState = mi->state; if (mi->stateFn != NULL) { AG_PostEventByPtr(m, mi->stateFn, "%p", &miState); } if (!miState) return (0); if (mi->clickFn != NULL) { AG_MenuCollapseAll(m); if (mi->clickFn->fn != NULL) { Debug(m, "Keyboard call (\"%s\")\n", mi->text); mi->clickFn->fn(mi->clickFn); return (1); } } else if (mi->bind_type != AG_MENU_NO_BINDING) { #ifdef AG_THREADS if (mi->bind_lock) { AG_MutexLock(mi->bind_lock); } #endif AG_MenuBoolToggle(mi); #ifdef AG_THREADS if (mi->bind_lock) { AG_MutexUnlock(mi->bind_lock); } #endif Debug(m, "Keyboard toggle (\"%s\") <- %s\n", mi->text, AG_MenuBoolGet(mi) ? "TRUE" : "FALSE"); if ((m->flags & AG_MENU_NO_BOOL_MSG) == 0) { AG_TextTmsg(AG_MSG_INFO, (m->flags & AG_MENU_FAST_BOOL_MSG) ? 400 : 1000, "[%s] " AGSI_ALGUE AGSI_L_ARROW " %s", mi->text, AG_MenuBoolGet(mi) ? _("True") : _("False")); } return (1); } return (0); } static void KeyDown(void *obj, AG_KeySym ks, AG_KeyMod kmod, AG_Char ch) { AG_Menu *m = obj; if (m->style != AG_MENU_GLOBAL && (WIDGET(m)->window == NULL || !AG_WindowIsFocused(WIDGET(m)->window))) return; KeyDown_Item(m->root, ks, kmod); } static void Init(void *_Nonnull obj) { AG_Menu *m = obj; WIDGET(m)->flags |= AG_WIDGET_UNFOCUSED_MOTION | AG_WIDGET_UNFOCUSED_BUTTONUP | AG_WIDGET_UNFOCUSED_KEYDOWN | AG_WIDGET_USE_TEXT; m->flags = 0; m->style = AG_MENU_DROPDOWN; m->root = CreateItem(NULL, NULL, NULL); m->root->pmenu = m; m->selecting = 0; m->lPadLbl=6; m->rPadLbl=7; m->tPadLbl=3; m->bPadLbl=3; m->itemh = agTextFontLineSkip + m->tPadLbl + m->bPadLbl; m->curState = 1; m->itemSel = NULL; m->curToolbar = NULL; m->r.x = 0; m->r.y = 0; m->r.w = 0; m->r.h = 0; AG_AddEvent(m, "font-changed", FontChanged, NULL); AG_AddEvent(m, "palette-changed", FontChanged, NULL); } /* Change the icon associated with a menu item. */ void AG_MenuSetIcon(AG_MenuItem *mi, const AG_Surface *icon) { AG_Menu *m = mi->pmenu; AG_MENUITEM_IS_VALID(mi); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); if (mi->icon) { AG_SurfaceFree(mi->icon); } mi->icon = icon ? AG_SurfaceDup(icon) : NULL; AG_MenuInvalidateLabels(mi); AG_Redraw(m); AG_ObjectUnlock(m); } /* Change menu item text (format string). */ void AG_MenuSetLabel(AG_MenuItem *mi, const char *fmt, ...) { AG_Menu *m = mi->pmenu; va_list ap; AG_MENUITEM_IS_VALID(mi); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); va_start(ap, fmt); Free(mi->text); Vasprintf(&mi->text, fmt, ap); va_end(ap); AG_MenuInvalidateLabels(mi); AG_Redraw(m); AG_ObjectUnlock(m); } /* Change menu item text (C string). */ void AG_MenuSetLabelS(AG_MenuItem *mi, const char *s) { AG_Menu *m = mi->pmenu; AG_MENUITEM_IS_VALID(mi); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); Free(mi->text); mi->text = Strdup(s); AG_MenuInvalidateLabels(mi); AG_Redraw(m); AG_ObjectUnlock(m); } /* Create a menu separator. */ AG_MenuItem * AG_MenuSeparator(AG_MenuItem *pitem) { AG_MenuItem *mi; AG_Menu *m = pitem->pmenu; AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi = CreateItem(pitem, NULL, NULL); mi->flags |= (AG_MENU_ITEM_NOSELECT | AG_MENU_ITEM_SEPARATOR); if (m->curToolbar) { AG_ToolbarSeparator(m->curToolbar); } AG_ObjectUnlock(m); return (mi); } /* Create a menu section label (format string). */ AG_MenuItem * AG_MenuSection(AG_MenuItem *pitem, const char *fmt, ...) { AG_Menu *m = pitem->pmenu; AG_MenuItem *mi; char *text; va_list ap; va_start(ap, fmt); Vasprintf(&text, fmt, ap); va_end(ap); AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi = CreateItem(pitem, text, NULL); mi->flags |= AG_MENU_ITEM_NOSELECT; AG_ObjectUnlock(m); free(text); return (mi); } /* Create a menu section label (C string). */ AG_MenuItem * AG_MenuSectionS(AG_MenuItem *pitem, const char *label) { AG_Menu *m = pitem->pmenu; AG_MenuItem *mi; AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi = CreateItem(pitem, label, NULL); mi->flags |= AG_MENU_ITEM_NOSELECT; AG_ObjectUnlock(m); return (mi); } /* Create a dynamically-updated menu item. */ AG_MenuItem * AG_MenuDynamicItem(AG_MenuItem *pitem, const char *text, const AG_Surface *icon, AG_EventFn fn, const char *fmt, ...) { AG_Menu *m = pitem->pmenu; AG_MenuItem *mi; AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi = CreateItem(pitem, text, icon); mi->poll = AG_SetEvent(m, NULL, fn, NULL); if (fmt) { va_list ap; va_start(ap, fmt); AG_EventGetArgs(mi->poll, fmt, ap); va_end(ap); } AG_ObjectUnlock(m); return (mi); } /* Create a dynamically-updated menu item with a keyboard binding. */ AG_MenuItem * AG_MenuDynamicItemKb(AG_MenuItem *pitem, const char *text, const AG_Surface *icon, AG_KeySym key, AG_KeyMod kmod, AG_EventFn fn, const char *fmt, ...) { AG_Menu *m = pitem->pmenu; AG_MenuItem *mi; AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi = CreateItem(pitem, text, icon); mi->key_equiv = key; mi->key_mod = kmod; mi->poll = AG_SetEvent(m, NULL, fn, NULL); if (fmt) { va_list ap; va_start(ap, fmt); AG_EventGetArgs(mi->poll, fmt, ap); va_end(ap); } AG_ObjectUnlock(m); return (mi); } /* Set a dynamic update function for an existing menu item. */ void AG_MenuSetPollFn(AG_MenuItem *mi, AG_EventFn fn, const char *fmt, ...) { AG_Menu *m = mi->pmenu; AG_MENUITEM_IS_VALID(mi); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); if (mi->poll) { AG_UnsetEvent(m, mi->poll->name); } mi->poll = AG_SetEvent(m, NULL, fn, NULL); if (fmt) { va_list ap; va_start(ap, fmt); AG_EventGetArgs(mi->poll, fmt, ap); va_end(ap); } AG_ObjectUnlock(m); } /* Create a menu item without any associated action. */ AG_MenuItem * AG_MenuNode(AG_MenuItem *pitem, const char *text, const AG_Surface *icon) { AG_Menu *m = pitem->pmenu; AG_MenuItem *node; AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); node = CreateItem(pitem, text, icon); AG_ObjectUnlock(m); return (node); } static AG_Button *_Nonnull CreateToolbarButton(AG_MenuItem *_Nonnull mi, const AG_Surface *_Nullable icon, const char *_Nullable text) { AG_Menu *m = mi->pmenu; AG_Button *bu; AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); if (icon) { bu = AG_ButtonNewS(m->curToolbar->rows[0], AG_BUTTON_NO_FOCUS, NULL); AG_ButtonSurface(bu, icon); } else { bu = AG_ButtonNewS(m->curToolbar->rows[0], AG_BUTTON_NO_FOCUS, text); } m->curToolbar->nButtons++; mi->tbButton = bu; return (bu); } static __inline__ AG_Button *_Nonnull CreateToolbarButtonBool(AG_MenuItem *_Nonnull mi, const AG_Surface *_Nullable icon, const char *_Nullable text, int inv) { AG_Button *bu; bu = CreateToolbarButton(mi, icon, text); AG_ButtonSetSticky(bu, 1); AG_ButtonSetInverted(bu, inv); return (bu); } /* Create a menu item associated with a function. */ AG_MenuItem * AG_MenuAction(AG_MenuItem *pitem, const char *text, const AG_Surface *icon, AG_EventFn fn, const char *fmt, ...) { AG_Menu *m = pitem->pmenu; AG_MenuItem *mi; AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi = CreateItem(pitem, text, icon); mi->clickFn = AG_SetEvent(m, NULL, fn, NULL); if (fmt) { va_list ap; va_start(ap, fmt); AG_EventGetArgs(mi->clickFn, fmt, ap); va_end(ap); } if (m->curToolbar) { AG_Event *buEv; mi->tbButton = CreateToolbarButton(pitem, icon, text); buEv = AG_SetEvent(mi->tbButton, "button-pushed", fn, NULL); if (fmt) { va_list ap; va_start(ap, fmt); AG_EventGetArgs(buEv, fmt, ap); va_end(ap); } } AG_ObjectUnlock(m); return (mi); } AG_MenuItem * AG_MenuActionKb(AG_MenuItem *pitem, const char *text, const AG_Surface *icon, AG_KeySym key, AG_KeyMod kmod, AG_EventFn fn, const char *fmt, ...) { AG_Menu *m = pitem->pmenu; AG_MenuItem *mi; AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi = CreateItem(pitem, text, icon); mi->key_equiv = key; mi->key_mod = kmod; mi->clickFn = AG_SetEvent(m, NULL, fn, NULL); if (fmt) { va_list ap; va_start(ap, fmt); AG_EventGetArgs(mi->clickFn, fmt, ap); va_end(ap); } if (m->curToolbar) { AG_Event *buEv; mi->tbButton = CreateToolbarButton(pitem, icon, text); buEv = AG_SetEvent(mi->tbButton, "button-pushed", fn, NULL); if (fmt) { va_list ap; va_start(ap, fmt); AG_EventGetArgs(buEv, fmt, ap); va_end(ap); } } AG_ObjectUnlock(m); return (mi); } AG_MenuItem * AG_MenuTool(AG_MenuItem *pitem, AG_Toolbar *toolbar, const char *text, const AG_Surface *icon, AG_KeySym key, AG_KeyMod kmod, void (*fn)(AG_Event *), const char *fmt, ...) { AG_Menu *m = pitem->pmenu; AG_MenuItem *mi; AG_Button *bu; AG_Event *btn_ev; AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); AG_OBJECT_ISA(toolbar, "AG_Widget:AG_Box:AG_Toolbar:*"); AG_ObjectLock(toolbar); if (icon) { bu = AG_ButtonNewS(toolbar->rows[0], AG_BUTTON_NO_FOCUS, NULL); AG_ButtonSurface(bu, icon); } else { bu = AG_ButtonNewS(toolbar->rows[0], AG_BUTTON_NO_FOCUS, text); } btn_ev = AG_SetEvent(bu, "button-pushed", fn, NULL); if (fmt) { va_list ap; va_start(ap, fmt); AG_EventGetArgs(btn_ev, fmt, ap); va_end(ap); } toolbar->nButtons++; mi = CreateItem(pitem, text, icon); mi->key_equiv = key; mi->key_mod = kmod; mi->clickFn = AG_SetEvent(m, NULL, fn, NULL); if (fmt) { va_list ap; va_start(ap, fmt); AG_EventGetArgs(mi->clickFn, fmt, ap); va_end(ap); } AG_ObjectUnlock(toolbar); AG_ObjectUnlock(m); return (mi); } AG_MenuItem * AG_MenuIntBoolMp(AG_MenuItem *pitem, const char *text, const AG_Surface *icon, int *pBool, int inv, AG_Mutex *lock) { AG_Menu *m = pitem->pmenu; AG_MenuItem *mi; AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi = CreateItem(pitem, text, icon); mi->bind_type = AG_MENU_INT_BOOL; mi->bind_p = (void *)pBool; if (inv) { mi->flags |= AG_MENU_ITEM_INVERTED; } #ifdef AG_THREADS mi->bind_lock = lock; #endif if (m->curToolbar) { mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv); #ifdef AG_THREADS if (lock) { AG_BindIntMp(mi->tbButton, "state", pBool, lock); } else #endif { AG_BindInt(mi->tbButton, "state", pBool); } AG_ButtonSetInverted(mi->tbButton, inv); } AG_ObjectUnlock(m); return (mi); } AG_MenuItem * AG_MenuInt8BoolMp(AG_MenuItem *pitem, const char *text, const AG_Surface *icon, Uint8 *pBool, int inv, AG_Mutex *lock) { AG_Menu *m = pitem->pmenu; AG_MenuItem *mi; AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi = CreateItem(pitem, text, icon); mi->bind_type = AG_MENU_INT8_BOOL; mi->bind_p = (void *)pBool; if (inv) { mi->flags |= AG_MENU_ITEM_INVERTED; } #ifdef AG_THREADS mi->bind_lock = lock; #endif if (m->curToolbar) { mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv); #ifdef AG_THREADS if (lock) { AG_BindUint8Mp(mi->tbButton, "state", pBool, lock); } else #endif { AG_BindUint8(mi->tbButton, "state", pBool); } AG_ButtonSetInverted(mi->tbButton, inv); } AG_ObjectUnlock(m); return (mi); } AG_MenuItem * AG_MenuIntFlagsMp(AG_MenuItem *pitem, const char *text, const AG_Surface *icon, int *pFlags, int flags, int inv, AG_Mutex *lock) { AG_Menu *m = pitem->pmenu; AG_MenuItem *mi; AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi = CreateItem(pitem, text, icon); mi->bind_type = AG_MENU_INT_FLAGS; mi->bind_flags = flags; mi->bind_p = (void *)pFlags; if (inv) { mi->flags |= AG_MENU_ITEM_INVERTED; } #ifdef AG_THREADS mi->bind_lock = lock; #endif if (m->curToolbar) { mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv); #ifdef AG_THREADS if (lock) { AG_BindFlagMp(mi->tbButton, "state", (Uint *)pFlags, (Uint)flags, lock); } else #endif { AG_BindFlag(mi->tbButton, "state", (Uint *)pFlags, (Uint)flags); } AG_ButtonSetInverted(mi->tbButton, inv); } AG_ObjectUnlock(m); return (mi); } AG_MenuItem * AG_MenuInt8FlagsMp(AG_MenuItem *pitem, const char *text, const AG_Surface *icon, Uint8 *pFlags, Uint8 flags, int inv, AG_Mutex *lock) { AG_Menu *m = pitem->pmenu; AG_MenuItem *mi; AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi = CreateItem(pitem, text, icon); mi->bind_type = AG_MENU_INT8_FLAGS; mi->bind_flags = flags; mi->bind_p = (void *)pFlags; if (inv) { mi->flags |= AG_MENU_ITEM_INVERTED; } #ifdef AG_THREADS mi->bind_lock = lock; #endif if (m->curToolbar) { mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv); #ifdef AG_THREADS if (lock) { AG_BindFlag8Mp(mi->tbButton, "state", pFlags, flags, lock); } else #endif { AG_BindFlag8(mi->tbButton, "state", pFlags, flags); } AG_ButtonSetInverted(mi->tbButton, inv); } AG_ObjectUnlock(m); return (mi); } AG_MenuItem * AG_MenuInt16FlagsMp(AG_MenuItem *pitem, const char *text, const AG_Surface *icon, Uint16 *pFlags, Uint16 flags, int inv, AG_Mutex *lock) { AG_Menu *m = pitem->pmenu; AG_MenuItem *mi; AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi = CreateItem(pitem, text, icon); mi->bind_type = AG_MENU_INT16_FLAGS; mi->bind_flags = flags; mi->bind_p = (void *)pFlags; if (inv) { mi->flags |= AG_MENU_ITEM_INVERTED; } # ifdef AG_THREADS mi->bind_lock = lock; # endif if (m->curToolbar) { mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv); # ifdef AG_THREADS if (lock) { AG_BindFlag16Mp(mi->tbButton, "state", pFlags, flags, lock); } else # endif { AG_BindFlag16(mi->tbButton, "state", pFlags, flags); } AG_ButtonSetInverted(mi->tbButton, inv); } AG_ObjectUnlock(m); return (mi); } AG_MenuItem * AG_MenuInt32FlagsMp(AG_MenuItem *pitem, const char *text, const AG_Surface *icon, Uint32 *pFlags, Uint32 flags, int inv, AG_Mutex *lock) { AG_Menu *m = pitem->pmenu; AG_MenuItem *mi; AG_MENUITEM_IS_VALID(pitem); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi = CreateItem(pitem, text, icon); mi->bind_type = AG_MENU_INT32_FLAGS; mi->bind_flags = flags; mi->bind_p = (void *)pFlags; if (inv) { mi->flags |= AG_MENU_ITEM_INVERTED; } # ifdef AG_THREADS mi->bind_lock = lock; # endif if (m->curToolbar) { mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv); # ifdef AG_THREADS if (lock) { AG_BindFlag32Mp(mi->tbButton, "state", pFlags, flags, lock); } else # endif { AG_BindFlag32(mi->tbButton, "state", pFlags, flags); } AG_ButtonSetInverted(mi->tbButton, inv); } AG_ObjectUnlock(m); return (mi); } void AG_MenuSetIntBoolMp(AG_MenuItem *mi, int *pBool, int inv, AG_Mutex *lock) { AG_Menu *m = mi->pmenu; AG_MENUITEM_IS_VALID(mi); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi->bind_type = AG_MENU_INT_BOOL; mi->bind_p = (void *)pBool; if (inv) { mi->flags |= AG_MENU_ITEM_INVERTED; } #ifdef AG_THREADS mi->bind_lock = lock; #endif if (mi->tbButton) { #ifdef AG_THREADS if (lock) { AG_BindIntMp(mi->tbButton, "state", pBool, lock); } else #endif { AG_BindInt(mi->tbButton, "state", pBool); } AG_ButtonSetInverted(mi->tbButton, inv); AG_ButtonSetSticky(mi->tbButton, 1); } AG_ObjectUnlock(m); } void AG_MenuSetIntFlagsMp(AG_MenuItem *mi, int *pFlags, int flags, int inv, AG_Mutex *lock) { AG_Menu *m = mi->pmenu; AG_MENUITEM_IS_VALID(mi); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); mi->bind_type = AG_MENU_INT_FLAGS; mi->bind_flags = flags; mi->bind_p = (void *)pFlags; if (inv) { mi->flags |= AG_MENU_ITEM_INVERTED; } #ifdef AG_THREADS mi->bind_lock = lock; #endif if (mi->tbButton) { #ifdef AG_THREADS if (lock) { AG_BindFlagMp(mi->tbButton, "state", (Uint *)pFlags, (Uint)flags, lock); } else #endif { AG_BindFlag(mi->tbButton, "state", (Uint *)pFlags, (Uint)flags); } AG_ButtonSetInverted(mi->tbButton, inv); AG_ButtonSetSticky(mi->tbButton, 1); } AG_ObjectUnlock(m); } /* * Free a menu item's children. * The parent AG_Menu must be locked. */ void AG_MenuFreeSubitems(AG_MenuItem *_Nonnull mi) { AG_MenuItem *miSub, *miSubNext; AG_MENUITEM_IS_VALID(mi); for (miSub = TAILQ_FIRST(&mi->subItems); miSub != TAILQ_END(&mi->subItems); miSub = miSubNext) { miSubNext = TAILQ_NEXT(miSub, items); AG_MenuItemFree(miSub); } TAILQ_INIT(&mi->subItems); mi->nSubItems = 0; } /* * Free a menu item and its children. * The parent AG_Menu must be locked. */ void AG_MenuItemFree(AG_MenuItem *mi) { AG_MenuItem *miSub, *miSubNext; mi->tag = 0; for (miSub = TAILQ_FIRST(&mi->subItems); miSub != TAILQ_END(&mi->subItems); miSub = miSubNext) { miSubNext = TAILQ_NEXT(miSub, items); AG_MenuItemFree(miSub); /* Recurse */ } if (mi->icon) { AG_SurfaceFree(mi->icon); } Free(mi->text); free(mi); } /* Delete a menu item and its children. */ void AG_MenuDel(AG_MenuItem *mi) { AG_Menu *m = mi->pmenu; AG_MenuItem *miParent = mi->parent; AG_MENUITEM_IS_VALID(mi); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); TAILQ_REMOVE(&miParent->subItems, mi, items); miParent->nSubItems--; AG_MenuItemFree(mi); AG_ObjectUnlock(m); } static void Destroy(void *_Nonnull p) { AG_Menu *m = p; AG_MenuItemFree(m->root); } static void InvalidateLabels(AG_MenuItem *_Nonnull mi) { AG_MenuItem *miSub; int i; AG_MENUITEM_IS_VALID(mi); for (i = 0; i < 2; i++) { if (mi->lblMenu[i] != -1) { AG_OBJECT_ISA(mi->pmenu, "AG_Widget:AG_Menu:*"); AG_WidgetUnmapSurface(mi->pmenu, mi->lblMenu[i]); mi->lblMenu[i] = -1; } } for (i = 0; i < 3; i++) { if (mi->lblView[i] != -1) { AG_MenuView *mv; if (mi->parent && (mv = mi->parent->view)) { AG_OBJECT_ISA(mv, "AG_Widget:AG_MenuView:*"); AG_WidgetUnmapSurface(mv, mi->lblView[i]); mi->lblView[i] = -1; } } } TAILQ_FOREACH(miSub, &mi->subItems, items) InvalidateLabels(miSub); /* Recurse */ } /* * Invalidate rendered surfaces generated by mi and its children (if any). * The parent Menu must be locked. */ void AG_MenuInvalidateLabels(AG_MenuItem *_Nonnull mi) { AG_MenuItem *miSub; AG_MENUITEM_IS_VALID(mi); TAILQ_FOREACH(miSub, &mi->subItems, items) InvalidateLabels(mi); } void AG_MenuState(AG_MenuItem *mi, int state) { AG_Menu *m = mi->pmenu; AG_MENUITEM_IS_VALID(mi); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); m->curState = state; } /* * Return the current boolean value of an item. * The menu must be locked. */ int AG_MenuBoolGet(AG_MenuItem *mi) { int val = 0; switch (mi->bind_type) { case AG_MENU_INT_BOOL: val = *(int *)mi->bind_p; break; case AG_MENU_INT8_BOOL: val = *(Uint8 *)mi->bind_p; break; case AG_MENU_INT_FLAGS: val = *(int *)mi->bind_p & mi->bind_flags; break; case AG_MENU_INT8_FLAGS: val = *(Uint8 *)mi->bind_p & mi->bind_flags; break; case AG_MENU_INT16_FLAGS: val = *(Uint16 *)mi->bind_p & mi->bind_flags; break; case AG_MENU_INT32_FLAGS: val = *(Uint32 *)mi->bind_p & mi->bind_flags; break; default: break; } return (mi->flags & AG_MENU_ITEM_INVERTED) ? !val : val; } /* * Toggle the boolean value of an item. * The menu must be locked. */ void AG_MenuBoolToggle(AG_MenuItem *mi) { switch (mi->bind_type) { case AG_MENU_INT_BOOL: { int *boolp = (int *)mi->bind_p; *boolp = !(*boolp); break; } case AG_MENU_INT8_BOOL: { Uint8 *boolp = (Uint8 *) mi->bind_p; *boolp = !(*boolp); break; } case AG_MENU_INT_FLAGS: { int *flags = (int *)mi->bind_p; AG_INVFLAGS(*flags, mi->bind_flags); break; } case AG_MENU_INT8_FLAGS: { Uint8 *flags = (Uint8 *)mi->bind_p; AG_INVFLAGS(*flags, mi->bind_flags); break; } case AG_MENU_INT16_FLAGS: { Uint16 *flags = (Uint16 *)mi->bind_p; AG_INVFLAGS(*flags, mi->bind_flags); break; } case AG_MENU_INT32_FLAGS: { Uint32 *flags = (Uint32 *)mi->bind_p; AG_INVFLAGS(*flags, mi->bind_flags); break; } case AG_MENU_NO_BINDING: default: break; } } void AG_MenuToolbar(AG_MenuItem *mi, AG_Toolbar *tb) { AG_Menu *m = mi->pmenu; AG_MENUITEM_IS_VALID(mi); AG_OBJECT_ISA(m, "AG_Widget:AG_Menu:*"); AG_ObjectLock(m); m->curToolbar = tb; AG_ObjectUnlock(m); } static void Draw(void *_Nonnull obj) { AG_Menu *m = obj; AG_MenuItem *mi; int lbl, wLbl, hLbl; AG_DrawBoxRaised(m, &WIDGET(m)->r, &WCOLOR(m,FG_COLOR)); AG_PushBlendingMode(m, AG_ALPHA_SRC, AG_ALPHA_ONE_MINUS_SRC); AG_PushClipRect(m, &m->r); TAILQ_FOREACH(mi, &m->root->subItems, items) { int activeState = mi->state; if (mi->stateFn) { AG_PostEventByPtr(m, mi->stateFn, "%p", &activeState); } if (activeState) { if (mi->lblMenu[1] == -1) { AG_TextColor(&WCOLOR_DEFAULT(m, TEXT_COLOR)); mi->lblMenu[1] = (mi->text == NULL) ? -1 : AG_WidgetMapSurface(m, AG_TextRender(mi->text)); } lbl = mi->lblMenu[1]; } else { if (mi->lblMenu[0] == -1) { AG_TextColor(&WCOLOR_DISABLED(m, TEXT_COLOR)); mi->lblMenu[0] = (mi->text == NULL) ? -1 : AG_WidgetMapSurface(m, AG_TextRender(mi->text)); } lbl = mi->lblMenu[0]; } wLbl = WSURFACE(m,lbl)->w; hLbl = WSURFACE(m,lbl)->h; if (mi == m->itemSel) { AG_Rect r; r.x = mi->x; r.y = mi->y; r.w = m->lPadLbl + wLbl + m->rPadLbl; r.h = m->tPadLbl + hLbl + m->bPadLbl; AG_DrawRect(m, &r, &WCOLOR(m, SELECTION_COLOR)); } AG_WidgetBlitSurface(m, lbl, mi->x + m->lPadLbl, mi->y + m->tPadLbl); } AG_PopClipRect(m); AG_PopBlendingMode(m); } static void SizeRequest(void *_Nonnull obj, AG_SizeReq *_Nonnull r) { AG_Menu *m = obj; const int lPad = WIDGET(m)->paddingLeft; const int tPad = WIDGET(m)->paddingTop; const int bPad = WIDGET(m)->paddingBottom; AG_Driver *drv = WIDGET(m)->drv; AG_MenuItem *mi; int x, wLbl, hLbl; Uint wView, hView; x = lPad; r->h = 0; r->w = x; AG_GetDisplaySize(drv, &wView, &hView); TAILQ_FOREACH(mi, &m->root->subItems, items) { GetItemSize(mi, &wLbl, &hLbl); if (r->h == 0) { r->h = tPad + hLbl + bPad; } if (x+wLbl > wView) { /* Wrap */ x = lPad; r->h += hLbl + bPad; } if (r->w < MIN(x+wLbl,wView)) { r->w = MIN(x+wLbl,wView); } x += wLbl; } } static int SizeAllocate(void *_Nonnull obj, const AG_SizeAlloc *_Nonnull a) { AG_Menu *m = obj; const int tPad = WIDGET(m)->paddingTop; const int rPad = WIDGET(m)->paddingRight; const int bPad = WIDGET(m)->paddingBottom; const int lPad = WIDGET(m)->paddingLeft; AG_MenuItem *mi; int wLbl, hLbl; int x, y; if (a->w < (lPad + rPad) || a->h < (tPad + bPad)) { return (-1); } m->r.x = lPad; m->r.y = tPad; m->r.w = a->w - rPad; m->r.h = a->h - bPad; x = lPad; y = tPad; TAILQ_FOREACH(mi, &m->root->subItems, items) { GetItemSize(mi, &wLbl, &hLbl); mi->x = x; mi->y = y; if (x+wLbl > a->w) { mi->x = lPad; mi->y += hLbl; y += hLbl; } x += wLbl; } return (0); } static void GetItemSize(AG_MenuItem *_Nonnull item, int *_Nonnull w, int *_Nonnull h) { AG_Menu *m = item->pmenu; int lbl; if (item->lblMenu[1] != -1) { lbl = item->lblMenu[1]; } else if (item->lblMenu[0] != -1) { lbl = item->lblMenu[0]; } else { lbl = -1; } if (lbl != -1) { *w = WSURFACE(m,lbl)->w; *h = WSURFACE(m,lbl)->h; } else { AG_TextSize(item->text, w, h); } (*w) += m->lPadLbl + m->rPadLbl; (*h) += m->tPadLbl + m->bPadLbl; } AG_PopupMenu * AG_PopupNew(void *obj) { AG_Widget *wid = obj; AG_PopupMenu *pm; pm = Malloc(sizeof(AG_PopupMenu)); pm->widget = wid; pm->menu = AG_MenuNew(NULL, 0); pm->menu->style = AG_MENU_POPUP; pm->root = AG_MenuNode(pm->menu->root, NULL, NULL); pm->menu->itemSel = pm->root; pm->win = NULL; return (pm); } void AG_PopupShow(AG_PopupMenu *pm) { AG_Driver *drv; AG_Window *winParent; int x = 0, y = 0; AG_LockVFS(pm->widget); if ((drv = WIDGET(pm->widget)->drv) != NULL && (winParent = AG_ParentWindow(pm->widget)) != NULL) { x = drv->mouse->x; y = drv->mouse->y; if (AGDRIVER_SINGLE(drv)) { x -= WIDGET(winParent)->x; y -= WIDGET(winParent)->y; } AG_ObjectLock(pm->menu); pm->win = AG_MenuExpand(winParent, pm->root, x, y); AG_ObjectUnlock(pm->menu); } AG_UnlockVFS(pm->widget); } void AG_PopupShowAt(AG_PopupMenu *pm, int x, int y) { AG_LockVFS(pm->widget); AG_ObjectLock(pm->menu); pm->win = AG_MenuExpand(pm->widget, pm->root, x, y); AG_ObjectUnlock(pm->menu); AG_UnlockVFS(pm->widget); } static void PopupHideAll(AG_MenuItem *_Nonnull mi) { AG_MenuItem *miSub; if (mi->view) { AG_WindowHide(WIDGET(mi->view)->window); } TAILQ_FOREACH(miSub, &mi->subItems, items) PopupHideAll(miSub); } void AG_PopupHide(AG_PopupMenu *pm) { AG_ObjectLock(pm->menu); PopupHideAll(pm->root); AG_ObjectUnlock(pm->menu); } void AG_PopupDestroy(AG_PopupMenu *pm) { AG_ObjectDestroy(pm->menu); free(pm); } #ifdef AG_TYPE_SAFETY /* * Accessor for AG_MENUITEM_PTR() and AG_cMENUITEM_PTR(). */ AG_MenuItem * AG_MenuGetItemPtr(const AG_Event *event, int idx, int isConst) { const AG_Variable *V = &event->argv[idx]; if (idx > event->argc || V->type != AG_VARIABLE_POINTER) { AG_GenericMismatch("by AG_MENUITEM_PTR(idx)"); } if (isConst) { if ((V->info.pFlags & AG_VARIABLE_P_READONLY) == 0) AG_FatalError("AG_cMENUITEM_PTR() argument isn't const. " "Did you mean AG_MENUITEM_PTR()?"); } else { if (V->info.pFlags & AG_VARIABLE_P_READONLY) AG_FatalError("AG_MENUITEM_PTR() argument is const. " "Did you mean AG_cMENUITEM_PTR()?"); } if (V->data.p == NULL) { return (NULL); } if (!AG_MENUITEM_VALID(V->data.p)) { AG_GenericMismatch("by AG_MENUITEM_PTR(tag)"); } return (V->data.p); } #endif /* AG_TYPE_SAFETY */ #ifdef AG_LEGACY void AG_MenuSetPadding(AG_Menu *m, int lPad, int rPad, int tPad, int bPad) { AG_SetStyleF(m, "padding", "%d %d %d %d", (tPad != -1) ? tPad : 0, (rPad != -1) ? rPad : 0, (bPad != -1) ? bPad : 0, (lPad != -1) ? lPad : 0); } #endif /* LEGACY */ AG_WidgetClass agMenuClass = { { "Agar(Widget:Menu)", sizeof(AG_Menu), { 1,0, AGC_MENU, 0xE024 }, Init, NULL, /* reset */ Destroy, NULL, /* load */ NULL, /* save */ NULL /* edit */ }, Draw, SizeRequest, SizeAllocate, MouseButtonDown, NULL, /* mouse_button_up */ MouseMotion, KeyDown, NULL, /* key_up */ NULL, /* touch */ NULL, /* ctrl */ NULL /* joy */ }; #endif /* AG_WIDGETS */