/*
* Copyright (c) 2004-2007 Hypertriton, Inc.
* 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.
*/
#include
#include "menu.h"
#include "primitive.h"
#include "label.h"
#include "button.h"
#include
#include
AG_Menu *agAppMenu = NULL;
AG_Window *agAppMenuWin = NULL;
AG_Mutex agAppMenuLock;
AG_Menu *
AG_MenuNew(void *parent, Uint flags)
{
AG_Menu *m;
m = Malloc(sizeof(AG_Menu));
AG_ObjectInit(m, &agMenuClass);
m->flags |= flags;
if (flags & AG_MENU_HFILL) { AG_ExpandHoriz(m); }
if (flags & AG_MENU_VFILL) { AG_ExpandVert(m); }
AG_ObjectAttach(parent, m);
return (m);
}
AG_Menu *
AG_MenuNewGlobal(Uint flags)
{
AG_Menu *m;
m = Malloc(sizeof(AG_Menu));
AG_ObjectInit(m, &agMenuClass);
m->flags |= (flags|AG_MENU_GLOBAL);
AG_MutexLock(&agAppMenuLock);
if (agAppMenu != NULL) {
AG_ViewDetach(agAppMenuWin);
AG_ObjectDestroy(agAppMenu);
}
agAppMenu = m;
AG_MenuSetPadding(m, 4, 4, -1, -1);
if (flags & AG_MENU_VFILL) {
AG_ExpandVert(m);
}
AG_ExpandHoriz(m);
agAppMenuWin = AG_WindowNewNamed(AG_WINDOW_PLAIN|AG_WINDOW_KEEPBELOW|
AG_WINDOW_DENYFOCUS|
AG_WINDOW_HMAXIMIZE,
"_agAppMenu");
AG_ObjectAttach(agAppMenuWin, m);
AG_WindowSetPadding(agAppMenuWin, 0, 0, 0, 0);
AG_WindowShow(agAppMenuWin);
AG_MutexUnlock(&agAppMenuLock);
return (m);
}
AG_Window *
AG_MenuExpand(AG_Menu *m, AG_MenuItem *item, int x, int y)
{
AG_Window *win;
AG_MenuUpdateItem(item);
if (item->nsubitems == 0)
return (NULL);
win = AG_WindowNew(AG_WINDOW_NOTITLE|AG_WINDOW_NOBORDERS|
AG_WINDOW_DENYFOCUS|AG_WINDOW_KEEPABOVE);
AG_WindowSetCaption(win, "win-popup");
AG_WindowSetPadding(win, 0, 0, 0, 0);
item->view = Malloc(sizeof(AG_MenuView));
AG_ObjectInit(item->view, &agMenuViewClass);
item->view->panel = win;
item->view->pmenu = m;
item->view->pitem = item;
AG_ObjectAttach(win, item->view);
AG_WindowShow(win);
AG_WindowSetGeometry(win, x, y, -1, -1);
return (win);
}
void
AG_MenuCollapse(AG_Menu *m, AG_MenuItem *item)
{
int i;
if (item == NULL)
return;
AG_ObjectLock(m);
for (i = 0; i < item->nsubitems; i++) {
if (item->subitems[i].nsubitems > 0)
AG_MenuCollapse(m, &item->subitems[i]);
}
item->sel_subitem = NULL;
if (item->view != NULL &&
item->view->panel != NULL) {
AG_ViewDetach(item->view->panel);
item->view = NULL;
}
AG_ObjectUnlock(m);
}
void
AG_MenuSetPadding(AG_Menu *m, int lPad, int rPad, int tPad, int bPad)
{
AG_ObjectLock(m);
if (lPad != -1) { m->lPad = lPad; }
if (rPad != -1) { m->rPad = rPad; }
if (tPad != -1) { m->tPad = tPad; }
if (bPad != -1) { m->bPad = bPad; }
AG_ObjectUnlock(m);
}
void
AG_MenuSetLabelPadding(AG_Menu *m, int lPad, int rPad, int tPad, int bPad)
{
AG_ObjectLock(m);
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_ObjectUnlock(m);
}
static void
MouseButtonDown(AG_Event *event)
{
AG_Menu *m = AG_SELF();
int x = AG_INT(2);
int y = AG_INT(3);
int i;
if (m->root == NULL)
return;
for (i = 0; i < m->root->nsubitems; i++) {
AG_MenuItem *item = &m->root->subitems[i];
int lbl = (item->lblEnabled!=-1) ? item->lblEnabled :
(item->lblDisabled!=-1) ? item->lblDisabled :
-1;
int wLbl, hLbl;
if (lbl == -1) { continue; }
wLbl = WSURFACE(m,lbl)->w + m->lPadLbl + m->rPadLbl;
hLbl = WSURFACE(m,lbl)->h + m->tPadLbl + m->bPadLbl;
if (x >= item->x &&
x < (item->x + wLbl) &&
y >= item->y &&
y < (item->y + m->itemh)) {
if (m->itemSel == item) {
AG_MenuCollapse(m, item);
m->itemSel = NULL;
m->selecting = 0;
} else {
if (m->itemSel != NULL) {
AG_MenuCollapse(m, m->itemSel);
}
m->itemSel = item;
AG_MenuExpand(m, item,
WIDGET(m)->rView.x1+item->x,
WIDGET(m)->rView.y1+item->y+hLbl+m->bPad-1);
m->selecting = 1;
}
break;
}
}
}
#if 0
static void
MouseButtonUp(AG_Event *event)
{
AG_Menu *m = AG_SELF();
int button = AG_INT(1);
int x = AG_INT(2);
int y = AG_INT(3);
if (m->itemSel != NULL && m->sel_subitem == NULL &&
x >= m->itemSel->x &&
x < (m->itemSel->x + WSURFACE(m,m->itemSel->label)->w) &&
y >= m->itemSel->y &&
y < (m->itemSel->y + m->itemh)) {
m->itemSel = NULL;
}
}
#endif
static void
MouseMotion(AG_Event *event)
{
AG_Menu *m = AG_SELF();
int x = AG_INT(1);
int y = AG_INT(2);
int i;
if (!m->selecting || y < 0 || y >= HEIGHT(m)-1)
return;
if (x < 0 && m->itemSel != NULL) {
AG_MenuCollapse(m, m->itemSel);
m->itemSel = NULL;
return;
}
if (m->root == NULL) {
return;
}
for (i = 0; i < m->root->nsubitems; i++) {
AG_MenuItem *item = &m->root->subitems[i];
int lbl = (item->lblEnabled != -1) ? item->lblEnabled :
(item->lblDisabled != -1) ? item->lblDisabled :
-1;
int wLbl, hLbl;
if (lbl == -1) { continue; }
wLbl = WSURFACE(m,lbl)->w + m->lPadLbl + m->rPadLbl;
hLbl = WSURFACE(m,lbl)->h + m->tPadLbl + m->bPadLbl;
if (x >= item->x &&
x < (item->x + wLbl) &&
y >= item->y &&
y < (item->y + m->itemh)) {
if (item != m->itemSel) {
if (m->itemSel != NULL) {
AG_MenuCollapse(m, m->itemSel);
}
m->itemSel = item;
AG_MenuExpand(m, item,
WIDGET(m)->rView.x1+item->x,
WIDGET(m)->rView.y1+item->y+hLbl+m->bPad-1);
}
break;
}
}
#if 0
if (i == m->root->nsubitems && m->itemSel != NULL) {
AG_MenuCollapse(m, m->itemSel);
m->itemSel = NULL;
}
#endif
}
static void
Attached(AG_Event *event)
{
AG_Widget *pwid = AG_SENDER();
AG_Window *pwin;
/* Adjust the top padding of the parent window if any. */
if ((pwin = AG_WidgetParentWindow(pwid)) != NULL) {
AG_WindowSetPadding(pwin, -1, -1, 0, pwin->bPad);
}
}
/* Generic constructor for menu items. Menu must be locked. */
static AG_MenuItem *
CreateItem(AG_MenuItem *pitem, const char *text, AG_Surface *icon)
{
AG_MenuItem *mi;
if (pitem != NULL) {
if (pitem->subitems == NULL) {
pitem->subitems = Malloc(sizeof(AG_MenuItem));
} else {
pitem->subitems = Realloc(pitem->subitems,
(pitem->nsubitems+1) *
sizeof(AG_MenuItem));
}
mi = &pitem->subitems[pitem->nsubitems++];
mi->pmenu = pitem->pmenu;
mi->pitem = pitem;
mi->y = pitem->nsubitems*mi->pmenu->itemh - mi->pmenu->itemh;
mi->state = mi->pmenu->curState;
} else {
mi = Malloc(sizeof(AG_MenuItem));
mi->pmenu = NULL;
mi->pitem = NULL;
mi->y = 0;
mi->state = 1;
}
mi->subitems = NULL;
mi->nsubitems = 0;
mi->view = NULL;
mi->sel_subitem = NULL;
mi->key_equiv = 0;
mi->key_mod = 0;
mi->clickFn = NULL;
mi->poll = NULL;
mi->bind_type = AG_MENU_NO_BINDING;
mi->bind_flags = 0;
mi->bind_invert = 0;
mi->bind_lock = NULL;
mi->text = Strdup((text != NULL) ? text : "");
mi->lblEnabled = -1;
mi->lblDisabled = -1;
mi->value = -1;
mi->flags = 0;
mi->icon = -1;
mi->tbButton = NULL;
if (icon != NULL) {
if (pitem != NULL) {
/* Request that the parent allocate space for icons. */
pitem->flags |= AG_MENU_ITEM_ICONS;
}
/* TODO: NODUP */
mi->iconSrc = AG_DupSurface(icon);
} else {
mi->iconSrc = NULL;
}
/* If this is the application menu, resize its window. */
if (mi->pmenu != NULL && (mi->pmenu->flags & AG_MENU_GLOBAL)) {
AG_SizeReq rMenu;
AG_WidgetSizeReq(mi->pmenu, &rMenu);
AG_WindowSetGeometry(agAppMenuWin, 0, 0, agView->w, rMenu.h);
}
return (mi);
}
static void
Init(void *obj)
{
AG_Menu *m = obj;
WIDGET(m)->flags |= AG_WIDGET_UNFOCUSED_MOTION|
AG_WIDGET_UNFOCUSED_BUTTONUP|
AG_WIDGET_NOSPACING;
m->flags = 0;
m->lPad = 5;
m->rPad = 5;
m->tPad = 2;
m->bPad = 2;
m->lPadLbl = 6;
m->rPadLbl = 7;
m->tPadLbl = 3;
m->bPadLbl = 3;
m->r = AG_RECT(0,0,0,0);
m->curToolbar = NULL;
m->curState = 1;
m->selecting = 0;
m->itemSel = NULL;
m->itemh = agTextFontHeight + m->tPadLbl + m->bPadLbl;
m->root = CreateItem(NULL, NULL, NULL);
m->root->pmenu = m;
AG_SetEvent(m, "window-mousebuttondown", MouseButtonDown, NULL);
#if 0
AG_SetEvent(m, "window-mousebuttonup", MouseButtonUp, NULL);
#endif
AG_SetEvent(m, "window-mousemotion", MouseMotion, NULL);
AG_SetEvent(m, "attached", Attached, NULL);
}
/* Change the icon associated with a menu item. */
void
AG_MenuSetIcon(AG_MenuItem *mi, AG_Surface *icon)
{
AG_ObjectLock(mi->pmenu);
if (mi->icon == -1) {
mi->icon = (icon != NULL) ?
AG_WidgetMapSurface(mi->pmenu, AG_DupSurface(icon)) : -1;
} else {
AG_WidgetReplaceSurface(mi->pmenu, mi->icon,
icon != NULL ? AG_DupSurface(icon) : NULL);
}
AG_ObjectUnlock(mi->pmenu);
}
/* Change menu item text. */
void
AG_MenuSetLabel(AG_MenuItem *mi, const char *fmt, ...)
{
va_list ap;
AG_ObjectLock(mi->pmenu);
va_start(ap, fmt);
Free(mi->text);
Vasprintf(&mi->text, fmt, ap);
va_end(ap);
if (mi->lblEnabled != -1) {
mi->lblEnabled = -1;
AG_WidgetUnmapSurface(mi->pmenu, mi->lblEnabled);
}
if (mi->lblDisabled != -1) {
mi->lblDisabled = -1;
AG_WidgetUnmapSurface(mi->pmenu, mi->lblDisabled);
}
AG_ObjectUnlock(mi->pmenu);
}
/* Create a menu separator. */
AG_MenuItem *
AG_MenuSeparator(AG_MenuItem *pitem)
{
AG_MenuItem *mi;
AG_ObjectLock(pitem->pmenu);
mi = CreateItem(pitem, NULL, NULL);
mi->flags |= AG_MENU_ITEM_NOSELECT|AG_MENU_ITEM_SEPARATOR;
if (pitem->pmenu->curToolbar != NULL)
AG_ToolbarSeparator(pitem->pmenu->curToolbar);
AG_ObjectUnlock(pitem->pmenu);
return (mi);
}
/* Create a menu section label. */
AG_MenuItem *
AG_MenuSection(AG_MenuItem *pitem, const char *fmt, ...)
{
char text[AG_LABEL_MAX];
AG_MenuItem *mi;
va_list ap;
va_start(ap, fmt);
Vsnprintf(text, sizeof(text), fmt, ap);
va_end(ap);
AG_ObjectLock(pitem->pmenu);
mi = CreateItem(pitem, text, NULL);
mi->flags |= AG_MENU_ITEM_NOSELECT;
AG_ObjectUnlock(pitem->pmenu);
return (mi);
}
/* Create a dynamically-updated menu item. */
AG_MenuItem *
AG_MenuDynamicItem(AG_MenuItem *pitem, const char *text, AG_Surface *icon,
AG_EventFn fn, const char *fmt, ...)
{
AG_Menu *m = pitem->pmenu;
AG_MenuItem *mi;
AG_ObjectLock(m);
mi = CreateItem(pitem, text, icon);
mi->poll = AG_SetEvent(m, NULL, fn, NULL);
AG_EVENT_GET_ARGS(mi->poll, fmt);
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, AG_Surface *icon,
SDLKey key, SDLMod kmod, AG_EventFn fn, const char *fmt, ...)
{
AG_Menu *m = pitem->pmenu;
AG_MenuItem *mi;
AG_ObjectLock(pitem->pmenu);
mi = CreateItem(pitem, text, icon);
mi->key_equiv = key;
mi->key_mod = kmod;
mi->poll = AG_SetEvent(m, NULL, fn, NULL);
AG_EVENT_GET_ARGS(mi->poll, fmt);
AG_ObjectUnlock(pitem->pmenu);
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_ObjectLock(mi->pmenu);
if (mi->poll != NULL) {
AG_UnsetEvent(mi->pmenu, mi->poll->name);
}
mi->poll = AG_SetEvent(mi->pmenu, NULL, fn, NULL);
AG_EVENT_GET_ARGS(mi->poll, fmt);
AG_ObjectUnlock(mi->pmenu);
}
/* Create a menu item without any associated action. */
AG_MenuItem *
AG_MenuNode(AG_MenuItem *pitem, const char *text, AG_Surface *icon)
{
AG_MenuItem *node;
AG_ObjectLock(pitem->pmenu);
node = CreateItem(pitem, text, icon);
AG_ObjectUnlock(pitem->pmenu);
return (node);
}
static AG_Button *
CreateToolbarButton(AG_MenuItem *mi, AG_Surface *icon, const char *text)
{
AG_Menu *m = mi->pmenu;
AG_Button *bu;
if (icon != NULL) {
bu = AG_ButtonNew(m->curToolbar->rows[0], 0, NULL);
AG_ButtonSurface(bu, icon);
} else {
bu = AG_ButtonNew(m->curToolbar->rows[0], 0, text);
}
AG_ButtonSetFocusable(bu, 0);
m->curToolbar->nButtons++;
mi->tbButton = bu;
return (bu);
}
static __inline__ AG_Button *
CreateToolbarButtonBool(AG_MenuItem *mi, AG_Surface *icon, const char *text,
int inv)
{
AG_Button *bu;
bu = CreateToolbarButton(mi, icon, text);
AG_ButtonSetSticky(bu, 1);
AG_ButtonInvertState(bu, inv);
return (bu);
}
/* Create a menu item associated with a function. */
AG_MenuItem *
AG_MenuAction(AG_MenuItem *pitem, const char *text, AG_Surface *icon,
AG_EventFn fn, const char *fmt, ...)
{
AG_MenuItem *mi;
AG_ObjectLock(pitem->pmenu);
mi = CreateItem(pitem, text, icon);
mi->clickFn = AG_SetEvent(pitem->pmenu, NULL, fn, NULL);
AG_EVENT_GET_ARGS(mi->clickFn, fmt);
if (pitem->pmenu->curToolbar != NULL) {
AG_Event *buEv;
mi->tbButton = CreateToolbarButton(pitem, icon, text);
buEv = AG_SetEvent(mi->tbButton, "button-pushed", fn, NULL);
AG_EVENT_GET_ARGS(buEv, fmt);
}
AG_ObjectUnlock(pitem->pmenu);
return (mi);
}
AG_MenuItem *
AG_MenuActionKb(AG_MenuItem *pitem, const char *text, AG_Surface *icon,
SDLKey key, SDLMod kmod, AG_EventFn fn, const char *fmt, ...)
{
AG_MenuItem *mi;
AG_ObjectLock(pitem->pmenu);
mi = CreateItem(pitem, text, icon);
mi->key_equiv = key;
mi->key_mod = kmod;
mi->clickFn = AG_SetEvent(pitem->pmenu, NULL, fn, NULL);
AG_EVENT_GET_ARGS(mi->clickFn, fmt);
if (pitem->pmenu->curToolbar != NULL) {
AG_Event *buEv;
mi->tbButton = CreateToolbarButton(pitem, icon, text);
buEv = AG_SetEvent(mi->tbButton, "button-pushed", fn, NULL);
AG_EVENT_GET_ARGS(buEv, fmt);
}
AG_ObjectUnlock(pitem->pmenu);
return (mi);
}
AG_MenuItem *
AG_MenuTool(AG_MenuItem *pitem, AG_Toolbar *tbar, const char *text,
AG_Surface *icon, SDLKey key, SDLMod kmod, void (*fn)(AG_Event *),
const char *fmt, ...)
{
AG_MenuItem *mi;
AG_Button *bu;
AG_Event *btn_ev;
AG_ObjectLock(pitem->pmenu);
AG_ObjectLock(tbar);
if (icon != NULL) {
bu = AG_ButtonNew(tbar->rows[0], 0, NULL);
AG_ButtonSurface(bu, icon);
} else {
bu = AG_ButtonNew(tbar->rows[0], 0, text);
}
AG_ButtonSetFocusable(bu, 0);
btn_ev = AG_SetEvent(bu, "button-pushed", fn, NULL);
AG_EVENT_GET_ARGS(btn_ev, fmt);
tbar->nButtons++;
mi = CreateItem(pitem, text, icon);
mi->key_equiv = key;
mi->key_mod = kmod;
mi->clickFn = AG_SetEvent(pitem->pmenu, NULL, fn, NULL);
AG_EVENT_GET_ARGS(mi->clickFn, fmt);
AG_ObjectUnlock(tbar);
AG_ObjectUnlock(pitem->pmenu);
return (mi);
}
AG_MenuItem *
AG_MenuIntBoolMp(AG_MenuItem *pitem, const char *text, AG_Surface *icon,
int *pBool, int inv, AG_Mutex *lock)
{
AG_MenuItem *mi;
AG_ObjectLock(pitem->pmenu);
mi = CreateItem(pitem, text, icon);
mi->bind_type = AG_MENU_INT_BOOL;
mi->bind_p = (void *)pBool;
mi->bind_invert = inv;
mi->bind_lock = lock;
if (pitem->pmenu->curToolbar != NULL) {
mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv);
AG_BindIntMp(mi->tbButton, "state", pBool, lock);
AG_ButtonInvertState(mi->tbButton, inv);
}
AG_ObjectUnlock(pitem->pmenu);
return (mi);
}
AG_MenuItem *
AG_MenuInt8BoolMp(AG_MenuItem *pitem, const char *text, AG_Surface *icon,
Uint8 *pBool, int inv, AG_Mutex *lock)
{
AG_MenuItem *mi;
AG_ObjectLock(pitem->pmenu);
mi = CreateItem(pitem, text, icon);
mi->bind_type = AG_MENU_INT8_BOOL;
mi->bind_p = (void *)pBool;
mi->bind_invert = inv;
mi->bind_lock = lock;
if (pitem->pmenu->curToolbar != NULL) {
mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv);
AG_BindUint8Mp(mi->tbButton, "state", pBool, lock);
AG_ButtonInvertState(mi->tbButton, inv);
}
AG_ObjectUnlock(pitem->pmenu);
return (mi);
}
AG_MenuItem *
AG_MenuIntFlagsMp(AG_MenuItem *pitem, const char *text, AG_Surface *icon,
int *pFlags, int flags, int inv, AG_Mutex *lock)
{
AG_MenuItem *mi;
AG_ObjectLock(pitem->pmenu);
mi = CreateItem(pitem, text, icon);
mi->bind_type = AG_MENU_INT_FLAGS;
mi->bind_p = (void *)pFlags;
mi->bind_flags = flags;
mi->bind_invert = inv;
mi->bind_lock = lock;
if (pitem->pmenu->curToolbar != NULL) {
mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv);
AG_BindFlagMp(mi->tbButton, "state", (Uint *)pFlags,
(Uint)flags, lock);
AG_ButtonInvertState(mi->tbButton, inv);
}
AG_ObjectUnlock(pitem->pmenu);
return (mi);
}
AG_MenuItem *
AG_MenuInt8FlagsMp(AG_MenuItem *pitem, const char *text, AG_Surface *icon,
Uint8 *pFlags, Uint8 flags, int inv, AG_Mutex *lock)
{
AG_MenuItem *mi;
AG_ObjectLock(pitem->pmenu);
mi = CreateItem(pitem, text, icon);
mi->bind_type = AG_MENU_INT8_FLAGS;
mi->bind_p = (void *)pFlags;
mi->bind_flags = flags;
mi->bind_invert = inv;
mi->bind_lock = lock;
if (pitem->pmenu->curToolbar != NULL) {
mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv);
AG_BindFlag8Mp(mi->tbButton, "state", pFlags, flags, lock);
AG_ButtonInvertState(mi->tbButton, inv);
}
AG_ObjectUnlock(pitem->pmenu);
return (mi);
}
AG_MenuItem *
AG_MenuInt16FlagsMp(AG_MenuItem *pitem, const char *text, AG_Surface *icon,
Uint16 *pFlags, Uint16 flags, int inv, AG_Mutex *lock)
{
AG_MenuItem *mi;
AG_ObjectLock(pitem->pmenu);
mi = CreateItem(pitem, text, icon);
mi->bind_type = AG_MENU_INT16_FLAGS;
mi->bind_p = (void *)pFlags;
mi->bind_flags = flags;
mi->bind_invert = inv;
mi->bind_lock = lock;
if (pitem->pmenu->curToolbar != NULL) {
mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv);
AG_BindFlag16Mp(mi->tbButton, "state", pFlags, flags, lock);
AG_ButtonInvertState(mi->tbButton, inv);
}
AG_ObjectUnlock(pitem->pmenu);
return (mi);
}
AG_MenuItem *
AG_MenuInt32FlagsMp(AG_MenuItem *pitem, const char *text, AG_Surface *icon,
Uint32 *pFlags, Uint32 flags, int inv, AG_Mutex *lock)
{
AG_MenuItem *mi;
AG_ObjectLock(pitem->pmenu);
mi = CreateItem(pitem, text, icon);
mi->bind_type = AG_MENU_INT32_FLAGS;
mi->bind_p = (void *)pFlags;
mi->bind_flags = flags;
mi->bind_invert = inv;
mi->bind_lock = lock;
if (pitem->pmenu->curToolbar != NULL) {
mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv);
AG_BindFlag32Mp(mi->tbButton, "state", pFlags, flags, lock);
AG_ButtonInvertState(mi->tbButton, inv);
}
AG_ObjectUnlock(pitem->pmenu);
return (mi);
}
void
AG_MenuSetIntBoolMp(AG_MenuItem *mi, int *pBool, int inv, AG_Mutex *lock)
{
AG_ObjectLock(mi->pmenu);
mi->bind_type = AG_MENU_INT_BOOL;
mi->bind_p = (void *)pBool;
mi->bind_invert = inv;
mi->bind_lock = lock;
if (mi->tbButton != NULL) {
AG_BindIntMp(mi->tbButton, "state", pBool, lock);
AG_ButtonInvertState(mi->tbButton, inv);
AG_ButtonSetSticky(mi->tbButton, 1);
}
AG_ObjectUnlock(mi->pmenu);
}
void
AG_MenuSetIntFlagsMp(AG_MenuItem *mi, int *pFlags, int flags, int inv,
AG_Mutex *lock)
{
AG_ObjectLock(mi->pmenu);
mi->bind_type = AG_MENU_INT_FLAGS;
mi->bind_p = (void *)pFlags;
mi->bind_flags = flags;
mi->bind_invert = inv;
mi->bind_lock = lock;
if (mi->tbButton != NULL) {
AG_BindFlag(mi->tbButton, "state", (Uint *)pFlags, (Uint)flags);
AG_ButtonInvertState(mi->tbButton, inv);
AG_ButtonSetSticky(mi->tbButton, 1);
}
AG_ObjectUnlock(mi->pmenu);
}
/* Free the subitems of an item. */
void
AG_MenuItemFreeChildren(AG_MenuItem *mi)
{
int i;
AG_ObjectLock(mi->pmenu);
for (i = 0; i < mi->nsubitems; i++) {
AG_MenuItemFree(&mi->subitems[i]);
}
Free(mi->subitems);
mi->subitems = NULL;
mi->nsubitems = 0;
AG_ObjectUnlock(mi->pmenu);
}
/* Free an item as well as its subitems. */
void
AG_MenuItemFree(AG_MenuItem *mi)
{
AG_ObjectLock(mi->pmenu);
AG_MenuItemFreeChildren(mi);
if (mi->lblEnabled != -1) {
AG_WidgetUnmapSurface(mi->pmenu, mi->lblEnabled);
mi->lblEnabled = -1;
}
if (mi->lblDisabled != -1) {
AG_WidgetUnmapSurface(mi->pmenu, mi->lblDisabled);
mi->lblDisabled = -1;
}
if (mi->icon != -1) {
AG_WidgetUnmapSurface(mi->pmenu, mi->icon);
mi->icon = -1;
}
Free(mi->text);
AG_ObjectUnlock(mi->pmenu);
}
static void
Destroy(void *p)
{
AG_Menu *m = p;
if (m->root != NULL)
AG_MenuItemFree(m->root);
}
void
AG_MenuUpdateItem(AG_MenuItem *mi)
{
AG_ObjectLock(mi->pmenu);
if (mi->poll != NULL) {
AG_MenuItemFreeChildren(mi);
AG_PostEvent(mi, mi->pmenu, mi->poll->name, NULL);
}
AG_ObjectUnlock(mi->pmenu);
}
void
AG_MenuState(AG_MenuItem *mi, int state)
{
AG_ObjectLock(mi->pmenu);
mi->pmenu->curState = state;
AG_ObjectUnlock(mi->pmenu);
}
void
AG_MenuToolbar(AG_MenuItem *mi, AG_Toolbar *tb)
{
AG_ObjectLock(mi->pmenu);
mi->pmenu->curToolbar = tb;
AG_ObjectUnlock(mi->pmenu);
}
static void
Draw(void *obj)
{
AG_Menu *m = obj;
int lbl, wLbl, hLbl;
int i;
STYLE(m)->MenuRootBackground(m);
if (m->root == NULL)
return;
AG_PushClipRect(m, m->r);
for (i = 0; i < m->root->nsubitems; i++) {
AG_MenuItem *item = &m->root->subitems[i];
if (item->state) {
if (item->lblEnabled == -1) {
AG_TextColor(MENU_TXT_COLOR);
item->lblEnabled = (item->text == NULL) ? -1 :
AG_WidgetMapSurface(m,
AG_TextRender(item->text));
}
lbl = item->lblEnabled;
} else {
if (item->lblDisabled == -1) {
AG_TextColor(MENU_TXT_DISABLED_COLOR);
item->lblDisabled = (item->text == NULL) ? -1 :
AG_WidgetMapSurface(m,
AG_TextRender(item->text));
}
lbl = item->lblDisabled;
}
wLbl = WSURFACE(m,lbl)->w;
hLbl = WSURFACE(m,lbl)->h;
if (item == m->itemSel) {
STYLE(m)->MenuRootSelectedItemBackground(m,
AG_RECT(item->x,
item->y,
m->lPadLbl + wLbl + m->rPadLbl,
m->tPadLbl + hLbl + m->bPadLbl));
}
AG_WidgetBlitSurface(m, lbl,
item->x + m->lPadLbl,
item->y + m->tPadLbl);
}
AG_PopClipRect();
}
static void
GetItemSize(AG_MenuItem *item, int *w, int *h)
{
AG_Menu *m = item->pmenu;
int lbl;
if (item->lblEnabled != -1) {
lbl = item->lblEnabled;
} else if (item->lblDisabled != -1) {
lbl = item->lblDisabled;
} 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;
}
static void
SizeRequest(void *obj, AG_SizeReq *r)
{
AG_Menu *m = obj;
int i, x, y;
int wLbl, hLbl;
x = m->lPad;
y = m->tPad;
r->h = 0;
r->w = x;
if (m->root == NULL) {
return;
}
for (i = 0; i < m->root->nsubitems; i++) {
GetItemSize(&m->root->subitems[i], &wLbl, &hLbl);
if (r->h == 0) {
r->h = m->tPad+hLbl+m->bPad;
}
if (x+wLbl > agView->w) { /* Wrap */
x = m->lPad;
y += hLbl;
r->h += hLbl+m->bPad;
}
if (r->w < MIN(x+wLbl,agView->w)) {
r->w = MIN(x+wLbl,agView->w);
}
x += wLbl;
}
}
static int
SizeAllocate(void *obj, const AG_SizeAlloc *a)
{
AG_Menu *m = obj;
int wLbl, hLbl;
int x, y, i;
if (a->w < (m->lPad + m->rPad) ||
a->h < (m->tPad + m->bPad)) {
return (-1);
}
m->r.x = m->lPad;
m->r.y = m->tPad;
m->r.w = a->w - m->rPad;
m->r.h = a->h - m->bPad;
if (m->root == NULL) {
return (-1);
}
x = m->lPad;
y = m->tPad;
for (i = 0; i < m->root->nsubitems; i++) {
AG_MenuItem *item = &m->root->subitems[i];
GetItemSize(item, &wLbl, &hLbl);
item->x = x;
item->y = y;
if (x+wLbl > a->w) {
item->x = m->lPad;
item->y += hLbl;
y += hLbl;
}
x += wLbl;
}
return (0);
}
AG_PopupMenu *
AG_PopupNew(void *pwid)
{
AG_Widget *wid = pwid;
AG_PopupMenu *pm;
pm = Malloc(sizeof(AG_PopupMenu));
pm->menu = AG_MenuNew(NULL, 0);
pm->item = pm->menu->itemSel = AG_MenuAddItem(pm->menu, NULL);
/* XXX redundant */
pm->win = NULL;
AG_ObjectLock(wid);
SLIST_INSERT_HEAD(&wid->menus, pm, menus);
AG_ObjectUnlock(wid);
return (pm);
}
void
AG_PopupShow(AG_PopupMenu *pm)
{
int x, y;
AG_ObjectLock(pm->menu);
AG_PopupHide(pm);
SDL_GetMouseState(&x, &y);
pm->win = AG_MenuExpand(pm->menu, pm->item, x, y);
AG_ObjectUnlock(pm->menu);
}
void
AG_PopupShowAt(AG_PopupMenu *pm, int x, int y)
{
AG_ObjectLock(pm->menu);
AG_PopupHide(pm);
pm->win = AG_MenuExpand(pm->menu, pm->item, x, y);
AG_ObjectUnlock(pm->menu);
}
void
AG_PopupHide(AG_PopupMenu *pm)
{
AG_ObjectLock(pm->menu);
if (pm->win != NULL) {
AG_MenuCollapse(pm->menu, pm->item);
pm->win = NULL;
}
AG_ObjectUnlock(pm->menu);
}
void
AG_PopupDestroy(void *pWid, AG_PopupMenu *pm)
{
if (pWid != NULL) {
AG_ObjectLock(pWid);
SLIST_REMOVE(&WIDGET(pWid)->menus, pm, ag_popup_menu, menus);
AG_ObjectUnlock(pWid);
}
if (pm->menu != NULL) {
AG_MenuCollapse(pm->menu, pm->item);
AG_ObjectDestroy(pm->menu);
}
pm->menu = NULL;
pm->item = NULL;
pm->win = NULL;
Free(pm);
}
AG_WidgetClass agMenuClass = {
{
"Agar(Widget:Menu)",
sizeof(AG_Menu),
{ 0,0 },
Init,
NULL, /* free */
Destroy,
NULL, /* load */
NULL, /* save */
NULL /* edit */
},
Draw,
SizeRequest,
SizeAllocate
};