1 /*
2  * Copyright (c) 2004-2015 Hypertriton, Inc. <http://hypertriton.com/>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
18  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23  * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #include <agar/core/core.h>
27 #include <agar/gui/menu.h>
28 #include <agar/gui/primitive.h>
29 #include <agar/gui/label.h>
30 #include <agar/gui/button.h>
31 
32 #include <stdarg.h>
33 #include <string.h>
34 
35 AG_Menu *agAppMenu = NULL;
36 AG_Window *agAppMenuWin = NULL;
37 AG_Mutex agAppMenuLock;
38 
39 /* Initialize global application menu data; called from AG_InitGUI(). */
40 void
AG_InitAppMenu(void)41 AG_InitAppMenu(void)
42 {
43 	AG_MutexInitRecursive(&agAppMenuLock);
44 	agAppMenu = NULL;
45 	agAppMenuWin = NULL;
46 }
47 
48 /* Cleanup global application menu data; called from AG_DestroyGUI(). */
49 void
AG_DestroyAppMenu(void)50 AG_DestroyAppMenu(void)
51 {
52 	agAppMenu = NULL;
53 	agAppMenuWin = NULL;
54 	AG_MutexDestroy(&agAppMenuLock);
55 }
56 
57 /* Create a new Menu widget. */
58 AG_Menu *
AG_MenuNew(void * parent,Uint flags)59 AG_MenuNew(void *parent, Uint flags)
60 {
61 	AG_Menu *m;
62 
63 	m = Malloc(sizeof(AG_Menu));
64 	AG_ObjectInit(m, &agMenuClass);
65 
66 	m->flags |= flags;
67 
68 	if (flags & AG_MENU_HFILL) { AG_ExpandHoriz(m); }
69 	if (flags & AG_MENU_VFILL) { AG_ExpandVert(m); }
70 
71 	AG_ObjectAttach(parent, m);
72 	return (m);
73 }
74 
75 /* Create a new global application menu. */
76 AG_Menu *
AG_MenuNewGlobal(Uint flags)77 AG_MenuNewGlobal(Uint flags)
78 {
79 	AG_Menu *m;
80 	AG_Window *win;
81 	Uint wMax, hMax;
82 	Uint wFlags = AG_WINDOW_KEEPBELOW|AG_WINDOW_DENYFOCUS;
83 
84 	AG_MutexLock(&agAppMenuLock);
85 	if (agAppMenu != NULL)
86 		goto exists;
87 
88 	if (agDriverSw) {
89 		wFlags |= AG_WINDOW_PLAIN|AG_WINDOW_HMAXIMIZE;
90 	}
91 	win = AG_WindowNewNamedS(wFlags, "_agAppMenu");
92 	win->wmType = AG_WINDOW_WM_DOCK;
93 	if (win == NULL) {
94 		goto exists;
95 	}
96 	AG_WindowSetPadding(win, 0, 0, 0, 0);
97 	AG_WindowSetCaptionS(win, agProgName != NULL ? agProgName : "agarapp");
98 
99 	m = AG_MenuNew(win, flags);
100 	m->style = AG_MENU_GLOBAL;
101 	AG_MenuSetPadding(m, 4, 4, -1, -1);
102 	AG_ExpandHoriz(m);
103 
104 	agAppMenu = m;
105 	agAppMenuWin = win;
106 
107 	if (agDriverSw) {
108 		AG_GetDisplaySize(WIDGET(win)->drv, &wMax, &hMax);
109 		AG_WindowSetGeometryAligned(win, AG_WINDOW_TC, wMax, -1);
110 	} else {
111 		AG_GetDisplaySize(WIDGET(win)->drv, &wMax, &hMax);
112 		AG_WindowSetGeometryAligned(win, AG_WINDOW_TL, wMax/3, -1);
113 	}
114 	AG_WindowShow(win);
115 
116 	AG_MutexUnlock(&agAppMenuLock);
117 	return (m);
118 exists:
119 	AG_SetError("Application menu is already defined");
120 	AG_MutexUnlock(&agAppMenuLock);
121 	return (NULL);
122 }
123 
124 static void
MenuCollapseAll(AG_Event * event)125 MenuCollapseAll(AG_Event *event)
126 {
127 	AG_Menu *m = AG_PTR(1);
128 	AG_MenuCollapseAll(m);
129 }
130 
131 /*
132  * Expand an AG_MenuItem. Create a window containing the AG_MenuView
133  * at coordinates x1,y1 (relative to widget parent).
134  *
135  * The associated AG_Menu object must be locked.
136  */
137 AG_Window *
AG_MenuExpand(void * parent,AG_MenuItem * mi,int x1,int y1)138 AG_MenuExpand(void *parent, AG_MenuItem *mi, int x1, int y1)
139 {
140 	AG_Window *win, *winParent;
141 	AG_Menu *m;
142 	int x = x1;
143 	int y = y1;
144 
145 	if (parent != NULL) {
146 		if (AG_OfClass(parent, "AG_Widget:AG_MenuView")) {
147 			m = ((AG_MenuView *)parent)->pmenu;
148 		} else if (AG_OfClass(parent, "AG_Widget:AG_Menu")) {
149 			m = parent;
150 		} else {
151 			m = mi->pmenu;
152 		}
153 		x += WIDGET(parent)->rView.x1;
154 		y += WIDGET(parent)->rView.y1;
155 		if ((winParent = WIDGET(parent)->window) == NULL) {
156 			AG_FatalError("AG_MenuExpand: %s has no window", OBJECT(parent)->name);
157 		}
158 		if (WIDGET(winParent)->drv != NULL &&
159 		    AGDRIVER_MULTIPLE(WIDGET(winParent)->drv)) {
160 			/* Convert to absolute coordinates */
161 			x += WIDGET(winParent)->x;
162 			y += WIDGET(winParent)->y;
163 		}
164 	} else {
165 		m = mi->pmenu;
166 		winParent = NULL;
167 	}
168 
169 	AG_MenuUpdateItem(mi);
170 
171 	if (mi->nSubItems == 0)
172 		return (NULL);
173 
174 	if (mi->view != NULL) {
175 		win = WIDGET(mi->view)->window;
176 		AG_WindowSetGeometry(win, x, y, -1, -1);
177 		AG_WindowShow(win);
178 		return (win);
179 	}
180 
181 	win = AG_WindowNew(
182 	    AG_WINDOW_MODAL|AG_WINDOW_NOTITLE|AG_WINDOW_NOBORDERS|
183 	    AG_WINDOW_NORESIZE|AG_WINDOW_DENYFOCUS|AG_WINDOW_KEEPABOVE);
184 	if (win == NULL) {
185 		return (NULL);
186 	}
187 	win->wmType = (m->style == AG_MENU_DROPDOWN) ?
188 	              AG_WINDOW_WM_DROPDOWN_MENU :
189 		      AG_WINDOW_WM_POPUP_MENU;
190 	AG_ObjectSetName(win, "_Popup-%s",
191 	    (parent != NULL) ? OBJECT(parent)->name : "generic");
192 	AG_WindowSetPadding(win, 0, 0, 0, 0);
193 
194 	AG_SetEvent(win, "window-modal-close", MenuCollapseAll, "%p", m);
195 	AG_SetEvent(win, "window-close", MenuCollapseAll, "%p", m);
196 
197 	mi->view = Malloc(sizeof(AG_MenuView));
198 	AG_ObjectInit(mi->view, &agMenuViewClass);
199 	mi->view->pmenu = m;
200 	mi->view->pitem = mi;
201 	AG_ObjectAttach(win, mi->view);
202 
203 	if (winParent != NULL) {
204 		AG_WindowAttach(winParent, win);
205 		AG_WindowMakeTransient(winParent, win);
206 		AG_WindowPin(winParent, win);
207 	}
208 	AG_WindowSetGeometry(win, x, y, -1,-1);
209 	AG_WindowShow(win);
210 	return (win);
211 }
212 
213 /*
214  * Collapse the window displaying the specified item and its sub-menus
215  * (if any).
216  */
217 void
AG_MenuCollapse(AG_MenuItem * mi)218 AG_MenuCollapse(AG_MenuItem *mi)
219 {
220 	AG_Menu *m;
221 	AG_MenuItem *miSub;
222 
223 	if (mi == NULL || mi->view == NULL || (m = mi->pmenu) == NULL)
224 		return;
225 
226 	AG_ObjectLock(m);
227 
228 	TAILQ_FOREACH(miSub, &mi->subItems, items)
229 		AG_MenuCollapse(miSub);
230 
231 	if (mi->view != NULL) {
232 		AG_WindowHide(WIDGET(mi->view)->window);
233 	}
234 	mi->sel_subitem = NULL;
235 
236 	AG_ObjectUnlock(m);
237 }
238 
239 static void
CollapseAll(AG_Menu * m,AG_MenuItem * mi)240 CollapseAll(AG_Menu *m, AG_MenuItem *mi)
241 {
242 	AG_MenuItem *miSub;
243 
244 	TAILQ_FOREACH(miSub, &mi->subItems, items) {
245 		CollapseAll(m, miSub);
246 	}
247 	if (mi->view != NULL)
248 		AG_MenuCollapse(mi);
249 }
250 
251 /*
252  * Collapse the window displaying the contents of an item as well as
253  * all expanded MenuView windows, up to the menu root.
254  */
255 void
AG_MenuCollapseAll(AG_Menu * m)256 AG_MenuCollapseAll(AG_Menu *m)
257 {
258 	AG_ObjectLock(m);
259 
260 	CollapseAll(m, m->root);
261 	m->itemSel = NULL;
262 	m->selecting = 0;
263 
264 	AG_ObjectUnlock(m);
265 }
266 
267 void
AG_MenuSetPadding(AG_Menu * m,int lPad,int rPad,int tPad,int bPad)268 AG_MenuSetPadding(AG_Menu *m, int lPad, int rPad, int tPad, int bPad)
269 {
270 	AG_ObjectLock(m);
271 	if (lPad != -1) { m->lPad = lPad; }
272 	if (rPad != -1) { m->rPad = rPad; }
273 	if (tPad != -1) { m->tPad = tPad; }
274 	if (bPad != -1) { m->bPad = bPad; }
275 	AG_ObjectUnlock(m);
276 	AG_Redraw(m);
277 }
278 
279 void
AG_MenuSetLabelPadding(AG_Menu * m,int lPad,int rPad,int tPad,int bPad)280 AG_MenuSetLabelPadding(AG_Menu *m, int lPad, int rPad, int tPad, int bPad)
281 {
282 	AG_ObjectLock(m);
283 	if (lPad != -1) { m->lPadLbl = lPad; }
284 	if (rPad != -1) { m->rPadLbl = rPad; }
285 	if (tPad != -1) { m->tPadLbl = tPad; }
286 	if (bPad != -1) { m->bPadLbl = bPad; }
287 	AG_ObjectUnlock(m);
288 	AG_Redraw(m);
289 }
290 
291 static __inline__ int
IntersectItem(AG_MenuItem * mi,int x,int y,int * hLbl)292 IntersectItem(AG_MenuItem *mi, int x, int y, int *hLbl)
293 {
294 	AG_Menu *m = mi->pmenu;
295 	int lbl, wLbl;
296 
297 	lbl = (mi->lblMenu[1] != -1) ? mi->lblMenu[1] :
298 	      (mi->lblMenu[0] != -1) ? mi->lblMenu[0] :
299 	      -1;
300 	if (lbl != -1) {
301 		wLbl = WSURFACE(m,lbl)->w + m->lPadLbl + m->rPadLbl;
302 		*hLbl = WSURFACE(m,lbl)->h + m->tPadLbl + m->bPadLbl;
303 	} else {
304 		wLbl = 0;
305 		*hLbl = 0;
306 	}
307 	return (x >= mi->x && x < (mi->x + wLbl) &&
308 		y >= mi->y && y < (mi->y + m->itemh));
309 }
310 
311 static void
MouseButtonDown(AG_Event * event)312 MouseButtonDown(AG_Event *event)
313 {
314 	AG_Menu *m = AG_SELF();
315 	int x = AG_INT(2);
316 	int y = AG_INT(3);
317 	AG_MenuItem *mi;
318 	int hLbl;
319 
320 	if (m->root == NULL)
321 		return;
322 
323 	TAILQ_FOREACH(mi, &m->root->subItems, items) {
324 		if (!IntersectItem(mi, x, y, &hLbl)) {
325 			continue;
326 		}
327 	    	if (m->itemSel == mi) {
328 			AG_MenuCollapse(mi);
329 			m->itemSel = NULL;
330 			m->selecting = 0;
331 		} else {
332 			if (m->itemSel != NULL) {
333 				AG_MenuCollapse(m->itemSel);
334 			}
335 			m->itemSel = mi;
336 			AG_MenuExpand(m, mi,
337 			    mi->x,
338 			    mi->y + hLbl + m->bPad - 1);
339 			m->selecting = 1;
340 		}
341 		AG_Redraw(m);
342 		break;
343 	}
344 }
345 
346 static void
MouseMotion(AG_Event * event)347 MouseMotion(AG_Event *event)
348 {
349 	AG_Menu *m = AG_SELF();
350 	int x = AG_INT(1);
351 	int y = AG_INT(2);
352 	AG_MenuItem *mi;
353 	int hLbl;
354 
355 	if (!m->selecting || y < 0 || y >= HEIGHT(m)-1 ||
356 	    m->root == NULL)
357 		return;
358 
359 	TAILQ_FOREACH(mi, &m->root->subItems, items) {
360 		if (!IntersectItem(mi, x, y, &hLbl)) {
361 			continue;
362 		}
363 	    	if (mi != m->itemSel) {
364 			if (m->itemSel != NULL) {
365 				AG_MenuCollapse(m->itemSel);
366 			}
367 			m->itemSel = mi;
368 			AG_MenuExpand(m, mi,
369 			    mi->x,
370 			    mi->y + hLbl + m->bPad - 1);
371 		}
372 		AG_Redraw(m);
373 		break;
374 	}
375 }
376 
377 static void
Attached(AG_Event * event)378 Attached(AG_Event *event)
379 {
380 	AG_Widget *pwid = AG_SENDER();
381 	AG_Window *pwin;
382 
383 	if ((pwin = AG_ParentWindow(pwid)) != NULL)
384 		AG_WindowSetPadding(pwin, -1, -1, 0, pwin->bPad);
385 }
386 
387 /* Generic constructor for menu items. Menu must be locked. */
388 static AG_MenuItem *
CreateItem(AG_MenuItem * miParent,const char * text,const AG_Surface * icon)389 CreateItem(AG_MenuItem *miParent, const char *text, const AG_Surface *icon)
390 {
391 	AG_Menu *m;
392 	AG_MenuItem *mi;
393 
394 	mi = Malloc(sizeof(AG_MenuItem));
395 	mi->parent = miParent;
396 	mi->stateFn = NULL;
397 
398 	if (miParent != NULL) {
399 		m = mi->pmenu = miParent->pmenu;
400 		mi->y = miParent->nSubItems*m->itemh - m->itemh;
401 		mi->state = m->curState;
402 
403 		TAILQ_INSERT_TAIL(&miParent->subItems, mi, items);
404 		miParent->nSubItems++;
405 	} else {
406 		m = mi->pmenu = NULL;
407 		mi->y = 0;
408 		mi->state = 1;
409 	}
410 	mi->view = NULL;
411 	mi->sel_subitem = NULL;
412 	mi->key_equiv = 0;
413 	mi->key_mod = 0;
414 	mi->clickFn = NULL;
415 	mi->poll = NULL;
416 	mi->bind_type = AG_MENU_NO_BINDING;
417 	mi->bind_flags = 0;
418 	mi->bind_invert = 0;
419 	mi->bind_lock = NULL;
420 	mi->text = Strdup((text != NULL) ? text : "");
421 	mi->lblMenu[0] = -1;
422 	mi->lblMenu[1] = -1;
423 	mi->lblView[0] = -1;
424 	mi->lblView[1] = -1;
425 	mi->value = -1;
426 	mi->flags = 0;
427 	mi->icon = -1;
428 	mi->tbButton = NULL;
429 	TAILQ_INIT(&mi->subItems);
430 	mi->nSubItems = 0;
431 
432 	if (icon != NULL) {
433 		if (miParent != NULL) {
434 			/* Request that the parent allocate space for icons. */
435 			miParent->flags |= AG_MENU_ITEM_ICONS;
436 		}
437 		/* TODO: NODUP */
438 		mi->iconSrc = AG_SurfaceDup(icon);
439 	} else {
440 		mi->iconSrc = NULL;
441 	}
442 	if (m != NULL) {
443 		if ((m->style == AG_MENU_GLOBAL) &&
444 		    agDriverSw != NULL && agAppMenuWin != NULL) {
445 			Uint wMax, hMax;
446 			AG_SizeReq rMenu;
447 
448 			AG_GetDisplaySize(agDriverSw, &wMax, &hMax);
449 			AG_WidgetSizeReq(m, &rMenu);
450 			AG_WindowSetGeometry(agAppMenuWin, 0, 0, wMax, rMenu.h);
451 		}
452 		AG_Redraw(m);
453 	}
454 	return (mi);
455 }
456 
457 static void
OnFontChange(AG_Event * event)458 OnFontChange(AG_Event *event)
459 {
460 	AG_Menu *m = AG_SELF();
461 	AG_Font *font = WIDGET(m)->font;
462 	AG_MenuItem *mi;
463 	int j;
464 
465 	TAILQ_FOREACH(mi, &m->root->subItems, items) {
466 		for (j = 0; j < 2; j++) {
467 			if (mi->lblMenu[j] != -1) {
468 				AG_WidgetUnmapSurface(m, mi->lblMenu[j]);
469 				mi->lblMenu[j] = -1;
470 			}
471 		}
472 	}
473 	m->itemh = font->height + m->tPadLbl + m->bPadLbl;
474 }
475 
476 static void
Init(void * obj)477 Init(void *obj)
478 {
479 	AG_Menu *m = obj;
480 
481 	WIDGET(m)->flags |= AG_WIDGET_UNFOCUSED_MOTION|
482 	                    AG_WIDGET_UNFOCUSED_BUTTONUP|
483 	                    AG_WIDGET_NOSPACING|
484 			    AG_WIDGET_USE_TEXT;
485 
486 	m->flags = 0;
487 	m->lPad = 5;
488 	m->rPad = 5;
489 	m->tPad = 2;
490 	m->bPad = 2;
491 	m->lPadLbl = 6;
492 	m->rPadLbl = 7;
493 	m->tPadLbl = 3;
494 	m->bPadLbl = 3;
495 	m->r = AG_RECT(0,0,0,0);
496 
497 	m->curToolbar = NULL;
498 	m->curState = 1;
499 	m->selecting = 0;
500 	m->itemSel = NULL;
501 	m->itemh = agTextFontHeight + m->tPadLbl + m->bPadLbl;
502 	m->style = AG_MENU_DROPDOWN;
503 
504 	m->root = CreateItem(NULL, NULL, NULL);
505 	m->root->pmenu = m;
506 
507 	AG_SetEvent(m, "mouse-button-down", MouseButtonDown, NULL);
508 	AG_SetEvent(m, "mouse-motion", MouseMotion, NULL);
509 	AG_AddEvent(m, "attached", Attached, NULL);
510 	AG_AddEvent(m, "font-changed", OnFontChange, NULL);
511 }
512 
513 /* Change the icon associated with a menu item. */
514 void
AG_MenuSetIcon(AG_MenuItem * mi,const AG_Surface * iconSrc)515 AG_MenuSetIcon(AG_MenuItem *mi, const AG_Surface *iconSrc)
516 {
517 	AG_Menu *m = mi->pmenu;
518 
519 	AG_ObjectLock(m);
520 	if (mi->iconSrc != NULL) {
521 		AG_SurfaceFree(mi->iconSrc);
522 	}
523 	mi->iconSrc = iconSrc != NULL ? AG_SurfaceDup(iconSrc) : NULL;
524 
525 	if (mi->icon != -1 &&
526 	    mi->parent != NULL &&
527 	    mi->parent->view != NULL) {
528 		AG_WidgetUnmapSurface(mi->parent->view, mi->icon);
529 		mi->icon = -1;
530 	}
531 	AG_ObjectUnlock(m);
532 	AG_Redraw(m);
533 }
534 
535 /* Unmap cached Menu/MenuView label surfaces for the specified item. */
536 static void
InvalidateLabelSurfaces(AG_MenuItem * mi)537 InvalidateLabelSurfaces(AG_MenuItem *mi)
538 {
539 	int i;
540 
541 	for (i = 0; i < 2; i++) {
542 		if (mi->lblMenu[i] != -1) {
543 			AG_WidgetUnmapSurface(mi->pmenu, mi->lblMenu[i]);
544 			mi->lblMenu[i] = -1;
545 		}
546 		if (mi->lblView[i] != -1 &&
547 		    mi->parent != NULL &&
548 		    mi->parent->view != NULL) {
549 			AG_WidgetUnmapSurface(mi->parent->view, mi->lblView[i]);
550 			mi->lblView[i] = -1;
551 		}
552 	}
553 }
554 
555 /* Change menu item text (format string). */
556 void
AG_MenuSetLabel(AG_MenuItem * mi,const char * fmt,...)557 AG_MenuSetLabel(AG_MenuItem *mi, const char *fmt, ...)
558 {
559 	AG_Menu *m = mi->pmenu;
560 	va_list ap;
561 
562 	AG_ObjectLock(m);
563 
564 	va_start(ap, fmt);
565 	Free(mi->text);
566 	Vasprintf(&mi->text, fmt, ap);
567 	va_end(ap);
568 
569 	InvalidateLabelSurfaces(mi);
570 
571 	AG_ObjectUnlock(m);
572 	AG_Redraw(m);
573 }
574 
575 /* Change menu item text (C string). */
576 void
AG_MenuSetLabelS(AG_MenuItem * mi,const char * s)577 AG_MenuSetLabelS(AG_MenuItem *mi, const char *s)
578 {
579 	AG_Menu *m = mi->pmenu;
580 
581 	AG_ObjectLock(m);
582 
583 	Free(mi->text);
584 	mi->text = Strdup(s);
585 	InvalidateLabelSurfaces(mi);
586 
587 	AG_ObjectUnlock(m);
588 	AG_Redraw(m);
589 }
590 
591 /* Create a menu separator. */
592 AG_MenuItem *
AG_MenuSeparator(AG_MenuItem * pitem)593 AG_MenuSeparator(AG_MenuItem *pitem)
594 {
595 	AG_MenuItem *mi;
596 
597 	AG_ObjectLock(pitem->pmenu);
598 
599 	mi = CreateItem(pitem, NULL, NULL);
600 	mi->flags |= AG_MENU_ITEM_NOSELECT|AG_MENU_ITEM_SEPARATOR;
601 
602 	if (pitem->pmenu->curToolbar != NULL)
603 		AG_ToolbarSeparator(pitem->pmenu->curToolbar);
604 
605 	AG_ObjectUnlock(pitem->pmenu);
606 	return (mi);
607 }
608 
609 /* Create a menu section label (format string). */
610 AG_MenuItem *
AG_MenuSection(AG_MenuItem * pitem,const char * fmt,...)611 AG_MenuSection(AG_MenuItem *pitem, const char *fmt, ...)
612 {
613 	char text[AG_LABEL_MAX];
614 	AG_MenuItem *mi;
615 	va_list ap;
616 
617 	va_start(ap, fmt);
618 	Vsnprintf(text, sizeof(text), fmt, ap);
619 	va_end(ap);
620 
621 	AG_ObjectLock(pitem->pmenu);
622 	mi = CreateItem(pitem, text, NULL);
623 	mi->flags |= AG_MENU_ITEM_NOSELECT;
624 	AG_ObjectUnlock(pitem->pmenu);
625 	return (mi);
626 }
627 
628 /* Create a menu section label (C string). */
629 AG_MenuItem *
AG_MenuSectionS(AG_MenuItem * pitem,const char * label)630 AG_MenuSectionS(AG_MenuItem *pitem, const char *label)
631 {
632 	AG_MenuItem *mi;
633 
634 	AG_ObjectLock(pitem->pmenu);
635 	mi = CreateItem(pitem, label, NULL);
636 	mi->flags |= AG_MENU_ITEM_NOSELECT;
637 	AG_ObjectUnlock(pitem->pmenu);
638 	return (mi);
639 }
640 
641 /* Create a dynamically-updated menu item. */
642 AG_MenuItem *
AG_MenuDynamicItem(AG_MenuItem * pitem,const char * text,const AG_Surface * icon,AG_EventFn fn,const char * fmt,...)643 AG_MenuDynamicItem(AG_MenuItem *pitem, const char *text, const AG_Surface *icon,
644     AG_EventFn fn, const char *fmt, ...)
645 {
646 	AG_Menu *m = pitem->pmenu;
647 	AG_MenuItem *mi;
648 
649 	AG_ObjectLock(m);
650 	mi = CreateItem(pitem, text, icon);
651 	mi->poll = AG_SetEvent(m, NULL, fn, NULL);
652 	AG_EVENT_GET_ARGS(mi->poll, fmt);
653 	AG_ObjectUnlock(m);
654 	return (mi);
655 }
656 
657 /* Create a dynamically-updated menu item with a keyboard binding. */
658 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,...)659 AG_MenuDynamicItemKb(AG_MenuItem *pitem, const char *text, const AG_Surface *icon,
660     AG_KeySym key, AG_KeyMod kmod, AG_EventFn fn, const char *fmt, ...)
661 {
662 	AG_Menu *m = pitem->pmenu;
663 	AG_MenuItem *mi;
664 
665 	AG_ObjectLock(pitem->pmenu);
666 	mi = CreateItem(pitem, text, icon);
667 	mi->key_equiv = key;
668 	mi->key_mod = kmod;
669 	mi->poll = AG_SetEvent(m, NULL, fn, NULL);
670 	AG_EVENT_GET_ARGS(mi->poll, fmt);
671 	AG_ObjectUnlock(pitem->pmenu);
672 	return (mi);
673 }
674 
675 /* Set a dynamic update function for an existing menu item. */
676 void
AG_MenuSetPollFn(AG_MenuItem * mi,AG_EventFn fn,const char * fmt,...)677 AG_MenuSetPollFn(AG_MenuItem *mi, AG_EventFn fn, const char *fmt, ...)
678 {
679 	AG_Menu *m = mi->pmenu;
680 
681 	AG_ObjectLock(m);
682 	if (mi->poll != NULL) {
683 		AG_UnsetEvent(m, mi->poll->name);
684 	}
685 	mi->poll = AG_SetEvent(m, NULL, fn, NULL);
686 	AG_EVENT_GET_ARGS(mi->poll, fmt);
687 	AG_ObjectUnlock(m);
688 }
689 
690 /* Create a menu item without any associated action. */
691 AG_MenuItem *
AG_MenuNode(AG_MenuItem * pitem,const char * text,const AG_Surface * icon)692 AG_MenuNode(AG_MenuItem *pitem, const char *text, const AG_Surface *icon)
693 {
694 	AG_MenuItem *node;
695 
696 	AG_ObjectLock(pitem->pmenu);
697 	node = CreateItem(pitem, text, icon);
698 	AG_ObjectUnlock(pitem->pmenu);
699 	return (node);
700 }
701 
702 static AG_Button *
CreateToolbarButton(AG_MenuItem * mi,const AG_Surface * icon,const char * text)703 CreateToolbarButton(AG_MenuItem *mi, const AG_Surface *icon, const char *text)
704 {
705 	AG_Menu *m = mi->pmenu;
706 	AG_Button *bu;
707 
708 	if (icon != NULL) {
709 		bu = AG_ButtonNewS(m->curToolbar->rows[0], 0, NULL);
710 		AG_ButtonSurface(bu, icon);
711 	} else {
712 		bu = AG_ButtonNewS(m->curToolbar->rows[0], 0, text);
713 	}
714 	AG_ButtonSetFocusable(bu, 0);
715 	m->curToolbar->nButtons++;
716 	mi->tbButton = bu;
717 	return (bu);
718 }
719 
720 static __inline__ AG_Button *
CreateToolbarButtonBool(AG_MenuItem * mi,const AG_Surface * icon,const char * text,int inv)721 CreateToolbarButtonBool(AG_MenuItem *mi, const AG_Surface *icon, const char *text,
722     int inv)
723 {
724 	AG_Button *bu;
725 
726 	bu = CreateToolbarButton(mi, icon, text);
727 	AG_ButtonSetSticky(bu, 1);
728 	AG_ButtonInvertState(bu, inv);
729 	return (bu);
730 }
731 
732 /* Create a menu item associated with a function. */
733 AG_MenuItem *
AG_MenuAction(AG_MenuItem * pitem,const char * text,const AG_Surface * icon,AG_EventFn fn,const char * fmt,...)734 AG_MenuAction(AG_MenuItem *pitem, const char *text, const AG_Surface *icon,
735     AG_EventFn fn, const char *fmt, ...)
736 {
737 	AG_MenuItem *mi;
738 
739 	AG_ObjectLock(pitem->pmenu);
740 	mi = CreateItem(pitem, text, icon);
741 	mi->clickFn = AG_SetEvent(pitem->pmenu, NULL, fn, NULL);
742 	AG_EVENT_GET_ARGS(mi->clickFn, fmt);
743 	if (pitem->pmenu->curToolbar != NULL) {
744 		AG_Event *buEv;
745 		mi->tbButton = CreateToolbarButton(pitem, icon, text);
746 		buEv = AG_SetEvent(mi->tbButton, "button-pushed", fn, NULL);
747 		AG_EVENT_GET_ARGS(buEv, fmt);
748 	}
749 	AG_ObjectUnlock(pitem->pmenu);
750 	return (mi);
751 }
752 
753 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,...)754 AG_MenuActionKb(AG_MenuItem *pitem, const char *text, const AG_Surface *icon,
755     AG_KeySym key, AG_KeyMod kmod, AG_EventFn fn, const char *fmt, ...)
756 {
757 	AG_MenuItem *mi;
758 
759 	AG_ObjectLock(pitem->pmenu);
760 	mi = CreateItem(pitem, text, icon);
761 	mi->key_equiv = key;
762 	mi->key_mod = kmod;
763 	mi->clickFn = AG_SetEvent(pitem->pmenu, NULL, fn, NULL);
764 	AG_EVENT_GET_ARGS(mi->clickFn, fmt);
765 
766 	if (pitem->pmenu->curToolbar != NULL) {
767 		AG_Event *buEv;
768 		mi->tbButton = CreateToolbarButton(pitem, icon, text);
769 		buEv = AG_SetEvent(mi->tbButton, "button-pushed", fn, NULL);
770 		AG_EVENT_GET_ARGS(buEv, fmt);
771 	}
772 	AG_ObjectUnlock(pitem->pmenu);
773 	return (mi);
774 }
775 
776 AG_MenuItem *
AG_MenuTool(AG_MenuItem * pitem,AG_Toolbar * tbar,const char * text,const AG_Surface * icon,AG_KeySym key,AG_KeyMod kmod,void (* fn)(AG_Event *),const char * fmt,...)777 AG_MenuTool(AG_MenuItem *pitem, AG_Toolbar *tbar, const char *text,
778     const AG_Surface *icon, AG_KeySym key, AG_KeyMod kmod,
779     void (*fn)(AG_Event *), const char *fmt, ...)
780 {
781 	AG_MenuItem *mi;
782 	AG_Button *bu;
783 	AG_Event *btn_ev;
784 
785 	AG_ObjectLock(pitem->pmenu);
786 	AG_ObjectLock(tbar);
787 
788 	if (icon != NULL) {
789 		bu = AG_ButtonNewS(tbar->rows[0], 0, NULL);
790 		AG_ButtonSurface(bu, icon);
791 	} else {
792 		bu = AG_ButtonNewS(tbar->rows[0], 0, text);
793 	}
794 	AG_ButtonSetFocusable(bu, 0);
795 	btn_ev = AG_SetEvent(bu, "button-pushed", fn, NULL);
796 	AG_EVENT_GET_ARGS(btn_ev, fmt);
797 	tbar->nButtons++;
798 
799 	mi = CreateItem(pitem, text, icon);
800 	mi->key_equiv = key;
801 	mi->key_mod = kmod;
802 	mi->clickFn = AG_SetEvent(pitem->pmenu, NULL, fn, NULL);
803 	AG_EVENT_GET_ARGS(mi->clickFn, fmt);
804 
805 	AG_ObjectUnlock(tbar);
806 	AG_ObjectUnlock(pitem->pmenu);
807 	return (mi);
808 }
809 
810 AG_MenuItem *
AG_MenuIntBoolMp(AG_MenuItem * pitem,const char * text,const AG_Surface * icon,int * pBool,int inv,AG_Mutex * lock)811 AG_MenuIntBoolMp(AG_MenuItem *pitem, const char *text, const AG_Surface *icon,
812     int *pBool, int inv, AG_Mutex *lock)
813 {
814 	AG_MenuItem *mi;
815 
816 	AG_ObjectLock(pitem->pmenu);
817 	mi = CreateItem(pitem, text, icon);
818 	mi->bind_type = AG_MENU_INT_BOOL;
819 	mi->bind_p = (void *)pBool;
820 	mi->bind_invert = inv;
821 	mi->bind_lock = lock;
822 	if (pitem->pmenu->curToolbar != NULL) {
823 		mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv);
824 		AG_BindIntMp(mi->tbButton, "state", pBool, lock);
825 		AG_ButtonInvertState(mi->tbButton, inv);
826 	}
827 	AG_ObjectUnlock(pitem->pmenu);
828 	return (mi);
829 }
830 
831 AG_MenuItem *
AG_MenuInt8BoolMp(AG_MenuItem * pitem,const char * text,const AG_Surface * icon,Uint8 * pBool,int inv,AG_Mutex * lock)832 AG_MenuInt8BoolMp(AG_MenuItem *pitem, const char *text, const AG_Surface *icon,
833     Uint8 *pBool, int inv, AG_Mutex *lock)
834 {
835 	AG_MenuItem *mi;
836 
837 	AG_ObjectLock(pitem->pmenu);
838 	mi = CreateItem(pitem, text, icon);
839 	mi->bind_type = AG_MENU_INT8_BOOL;
840 	mi->bind_p = (void *)pBool;
841 	mi->bind_invert = inv;
842 	mi->bind_lock = lock;
843 	if (pitem->pmenu->curToolbar != NULL) {
844 		mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv);
845 		AG_BindUint8Mp(mi->tbButton, "state", pBool, lock);
846 		AG_ButtonInvertState(mi->tbButton, inv);
847 	}
848 	AG_ObjectUnlock(pitem->pmenu);
849 	return (mi);
850 }
851 
852 AG_MenuItem *
AG_MenuIntFlagsMp(AG_MenuItem * pitem,const char * text,const AG_Surface * icon,int * pFlags,int flags,int inv,AG_Mutex * lock)853 AG_MenuIntFlagsMp(AG_MenuItem *pitem, const char *text, const AG_Surface *icon,
854     int *pFlags, int flags, int inv, AG_Mutex *lock)
855 {
856 	AG_MenuItem *mi;
857 
858 	AG_ObjectLock(pitem->pmenu);
859 	mi = CreateItem(pitem, text, icon);
860 	mi->bind_type = AG_MENU_INT_FLAGS;
861 	mi->bind_p = (void *)pFlags;
862 	mi->bind_flags = flags;
863 	mi->bind_invert = inv;
864 	mi->bind_lock = lock;
865 	if (pitem->pmenu->curToolbar != NULL) {
866 		mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv);
867 		AG_BindFlagMp(mi->tbButton, "state", (Uint *)pFlags,
868 		    (Uint)flags, lock);
869 		AG_ButtonInvertState(mi->tbButton, inv);
870 	}
871 	AG_ObjectUnlock(pitem->pmenu);
872 	return (mi);
873 }
874 
875 AG_MenuItem *
AG_MenuInt8FlagsMp(AG_MenuItem * pitem,const char * text,const AG_Surface * icon,Uint8 * pFlags,Uint8 flags,int inv,AG_Mutex * lock)876 AG_MenuInt8FlagsMp(AG_MenuItem *pitem, const char *text, const AG_Surface *icon,
877     Uint8 *pFlags, Uint8 flags, int inv, AG_Mutex *lock)
878 {
879 	AG_MenuItem *mi;
880 
881 	AG_ObjectLock(pitem->pmenu);
882 	mi = CreateItem(pitem, text, icon);
883 	mi->bind_type = AG_MENU_INT8_FLAGS;
884 	mi->bind_p = (void *)pFlags;
885 	mi->bind_flags = flags;
886 	mi->bind_invert = inv;
887 	mi->bind_lock = lock;
888 	if (pitem->pmenu->curToolbar != NULL) {
889 		mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv);
890 		AG_BindFlag8Mp(mi->tbButton, "state", pFlags, flags, lock);
891 		AG_ButtonInvertState(mi->tbButton, inv);
892 	}
893 	AG_ObjectUnlock(pitem->pmenu);
894 	return (mi);
895 }
896 
897 AG_MenuItem *
AG_MenuInt16FlagsMp(AG_MenuItem * pitem,const char * text,const AG_Surface * icon,Uint16 * pFlags,Uint16 flags,int inv,AG_Mutex * lock)898 AG_MenuInt16FlagsMp(AG_MenuItem *pitem, const char *text, const AG_Surface *icon,
899     Uint16 *pFlags, Uint16 flags, int inv, AG_Mutex *lock)
900 {
901 	AG_MenuItem *mi;
902 
903 	AG_ObjectLock(pitem->pmenu);
904 	mi = CreateItem(pitem, text, icon);
905 	mi->bind_type = AG_MENU_INT16_FLAGS;
906 	mi->bind_p = (void *)pFlags;
907 	mi->bind_flags = flags;
908 	mi->bind_invert = inv;
909 	mi->bind_lock = lock;
910 	if (pitem->pmenu->curToolbar != NULL) {
911 		mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv);
912 		AG_BindFlag16Mp(mi->tbButton, "state", pFlags, flags, lock);
913 		AG_ButtonInvertState(mi->tbButton, inv);
914 	}
915 	AG_ObjectUnlock(pitem->pmenu);
916 	return (mi);
917 }
918 
919 AG_MenuItem *
AG_MenuInt32FlagsMp(AG_MenuItem * pitem,const char * text,const AG_Surface * icon,Uint32 * pFlags,Uint32 flags,int inv,AG_Mutex * lock)920 AG_MenuInt32FlagsMp(AG_MenuItem *pitem, const char *text, const AG_Surface *icon,
921     Uint32 *pFlags, Uint32 flags, int inv, AG_Mutex *lock)
922 {
923 	AG_MenuItem *mi;
924 
925 	AG_ObjectLock(pitem->pmenu);
926 	mi = CreateItem(pitem, text, icon);
927 	mi->bind_type = AG_MENU_INT32_FLAGS;
928 	mi->bind_p = (void *)pFlags;
929 	mi->bind_flags = flags;
930 	mi->bind_invert = inv;
931 	mi->bind_lock = lock;
932 	if (pitem->pmenu->curToolbar != NULL) {
933 		mi->tbButton = CreateToolbarButtonBool(pitem, icon, text, inv);
934 		AG_BindFlag32Mp(mi->tbButton, "state", pFlags, flags, lock);
935 		AG_ButtonInvertState(mi->tbButton, inv);
936 	}
937 	AG_ObjectUnlock(pitem->pmenu);
938 	return (mi);
939 }
940 
941 void
AG_MenuSetIntBoolMp(AG_MenuItem * mi,int * pBool,int inv,AG_Mutex * lock)942 AG_MenuSetIntBoolMp(AG_MenuItem *mi, int *pBool, int inv, AG_Mutex *lock)
943 {
944 	AG_ObjectLock(mi->pmenu);
945 	mi->bind_type = AG_MENU_INT_BOOL;
946 	mi->bind_p = (void *)pBool;
947 	mi->bind_invert = inv;
948 	mi->bind_lock = lock;
949 	if (mi->tbButton != NULL) {
950 		AG_BindIntMp(mi->tbButton, "state", pBool, lock);
951 		AG_ButtonInvertState(mi->tbButton, inv);
952 		AG_ButtonSetSticky(mi->tbButton, 1);
953 	}
954 	AG_ObjectUnlock(mi->pmenu);
955 }
956 
957 void
AG_MenuSetIntFlagsMp(AG_MenuItem * mi,int * pFlags,int flags,int inv,AG_Mutex * lock)958 AG_MenuSetIntFlagsMp(AG_MenuItem *mi, int *pFlags, int flags, int inv,
959     AG_Mutex *lock)
960 {
961 	AG_ObjectLock(mi->pmenu);
962 	mi->bind_type = AG_MENU_INT_FLAGS;
963 	mi->bind_p = (void *)pFlags;
964 	mi->bind_flags = flags;
965 	mi->bind_invert = inv;
966 	mi->bind_lock = lock;
967 	if (mi->tbButton != NULL) {
968 		AG_BindFlag(mi->tbButton, "state", (Uint *)pFlags, (Uint)flags);
969 		AG_ButtonInvertState(mi->tbButton, inv);
970 		AG_ButtonSetSticky(mi->tbButton, 1);
971 	}
972 	AG_ObjectUnlock(mi->pmenu);
973 }
974 
975 /*
976  * Free a menu item's children.
977  * The parent AG_Menu must be locked.
978  */
979 static void
AG_MenuItemFreeChildren(AG_MenuItem * mi)980 AG_MenuItemFreeChildren(AG_MenuItem *mi)
981 {
982 	AG_MenuItem *miSub, *miSubNext;
983 
984 	for (miSub = TAILQ_FIRST(&mi->subItems);
985 	     miSub != TAILQ_END(&mi->subItems);
986 	     miSub = miSubNext) {
987 		miSubNext = TAILQ_NEXT(miSub, items);
988 		AG_MenuItemFree(miSub);
989 	}
990 	TAILQ_INIT(&mi->subItems);
991 	mi->nSubItems = 0;
992 }
993 
994 /*
995  * Free a menu item and its children.
996  * The parent AG_Menu must be locked.
997  */
998 void
AG_MenuItemFree(AG_MenuItem * mi)999 AG_MenuItemFree(AG_MenuItem *mi)
1000 {
1001 	AG_MenuItemFreeChildren(mi);
1002 
1003 	if (mi->iconSrc != NULL) {
1004 		AG_SurfaceFree(mi->iconSrc);
1005 	}
1006 	Free(mi->text);
1007 	free(mi);
1008 }
1009 
1010 /* Delete a menu item and its children. */
1011 void
AG_MenuDel(AG_MenuItem * mi)1012 AG_MenuDel(AG_MenuItem *mi)
1013 {
1014 	AG_Menu *m = mi->pmenu;
1015 	AG_MenuItem *miParent = mi->parent;
1016 
1017 	AG_ObjectLock(m);
1018 
1019 	TAILQ_REMOVE(&miParent->subItems, mi, items);
1020 	miParent->nSubItems--;
1021 	AG_MenuItemFree(mi);
1022 
1023 	AG_ObjectUnlock(m);
1024 }
1025 
1026 static void
Destroy(void * p)1027 Destroy(void *p)
1028 {
1029 	AG_Menu *m = p;
1030 
1031 	if (m->root != NULL)
1032 		AG_MenuItemFree(m->root);
1033 }
1034 
1035 void
AG_MenuUpdateItem(AG_MenuItem * mi)1036 AG_MenuUpdateItem(AG_MenuItem *mi)
1037 {
1038 	AG_Menu *m = mi->pmenu;
1039 
1040 	AG_ObjectLock(m);
1041 	if (mi->poll != NULL) {
1042 		InvalidateLabelSurfaces(mi);
1043 		AG_MenuItemFreeChildren(mi);
1044 		AG_PostEventByPtr(mi, m, mi->poll, NULL);
1045 	}
1046 	AG_ObjectUnlock(m);
1047 }
1048 
1049 void
AG_MenuState(AG_MenuItem * mi,int state)1050 AG_MenuState(AG_MenuItem *mi, int state)
1051 {
1052 	AG_Menu *m = mi->pmenu;
1053 
1054 	AG_ObjectLock(m);
1055 	m->curState = state;
1056 	AG_ObjectUnlock(m);
1057 	AG_Redraw(m);
1058 }
1059 
1060 void
AG_MenuToolbar(AG_MenuItem * mi,AG_Toolbar * tb)1061 AG_MenuToolbar(AG_MenuItem *mi, AG_Toolbar *tb)
1062 {
1063 	AG_Menu *m = mi->pmenu;
1064 
1065 	AG_ObjectLock(m);
1066 	m->curToolbar = tb;
1067 	AG_ObjectUnlock(m);
1068 }
1069 
1070 static void
Draw(void * obj)1071 Draw(void *obj)
1072 {
1073 	AG_Menu *m = obj;
1074 	AG_MenuItem *mi;
1075 	int lbl, wLbl, hLbl;
1076 
1077 	AG_DrawBox(m,
1078 	    AG_RECT(0, 0, WIDTH(m), HEIGHT(m)), 1,
1079 	    WCOLOR(m,0));
1080 
1081 	if (m->root == NULL)
1082 		return;
1083 
1084 	AG_PushClipRect(m, m->r);
1085 
1086 	TAILQ_FOREACH(mi, &m->root->subItems, items) {
1087 		int activeState = mi->stateFn ? mi->stateFn->fn.fnInt(mi->stateFn) :
1088 		                                mi->state;
1089 		if (activeState) {
1090 			if (mi->lblMenu[1] == -1) {
1091 				AG_TextColor(WCOLOR(m,TEXT_COLOR));
1092 				mi->lblMenu[1] = (mi->text == NULL) ? -1 :
1093 				    AG_WidgetMapSurface(m, AG_TextRender(mi->text));
1094 			}
1095 			lbl = mi->lblMenu[1];
1096 		} else {
1097 			if (mi->lblMenu[0] == -1) {
1098 				AG_TextColor(WCOLOR_DIS(m,TEXT_COLOR));
1099 				mi->lblMenu[0] = (mi->text == NULL) ? -1 :
1100 				    AG_WidgetMapSurface(m, AG_TextRender(mi->text));
1101 			}
1102 			lbl = mi->lblMenu[0];
1103 		}
1104 		wLbl = WSURFACE(m,lbl)->w;
1105 		hLbl = WSURFACE(m,lbl)->h;
1106 		if (mi == m->itemSel) {
1107 			AG_DrawRect(m,
1108 			    AG_RECT(mi->x, mi->y,
1109 	    		            m->lPadLbl + wLbl + m->rPadLbl,
1110 	    		            m->tPadLbl + hLbl + m->bPadLbl),
1111 			    WCOLOR_SEL(m,0));
1112 		}
1113 		AG_WidgetBlitSurface(m, lbl,
1114 		    mi->x + m->lPadLbl,
1115 		    mi->y + m->tPadLbl);
1116 	}
1117 
1118 	AG_PopClipRect(m);
1119 }
1120 
1121 static void
GetItemSize(AG_MenuItem * item,int * w,int * h)1122 GetItemSize(AG_MenuItem *item, int *w, int *h)
1123 {
1124 	AG_Menu *m = item->pmenu;
1125 	int lbl;
1126 
1127 	if (item->lblMenu[1] != -1) {
1128 		lbl = item->lblMenu[1];
1129 	} else if (item->lblMenu[0] != -1) {
1130 		lbl = item->lblMenu[0];
1131 	} else {
1132 		lbl = -1;
1133 	}
1134 	if (lbl != -1) {
1135 		*w = WSURFACE(m,lbl)->w;
1136 		*h = WSURFACE(m,lbl)->h;
1137 	} else {
1138 		AG_TextSize(item->text, w, h);
1139 	}
1140 	(*w) += m->lPadLbl + m->rPadLbl;
1141 	(*h) += m->tPadLbl + m->bPadLbl;
1142 }
1143 
1144 static void
SizeRequest(void * obj,AG_SizeReq * r)1145 SizeRequest(void *obj, AG_SizeReq *r)
1146 {
1147 	AG_Menu *m = obj;
1148 	AG_Driver *drv = WIDGET(m)->drv;
1149 	AG_MenuItem *mi;
1150 	int x, y, wLbl, hLbl;
1151 	Uint wView, hView;
1152 
1153 	x = m->lPad;
1154 	y = m->tPad;
1155 	r->h = 0;
1156 	r->w = x;
1157 
1158 	AG_GetDisplaySize(drv, &wView, &hView);
1159 
1160 	if (m->root == NULL) {
1161 		return;
1162 	}
1163 	TAILQ_FOREACH(mi, &m->root->subItems, items) {
1164 		GetItemSize(mi, &wLbl, &hLbl);
1165 		if (r->h == 0) {
1166 			r->h = m->tPad+hLbl+m->bPad;
1167 		}
1168 		if (x+wLbl > wView) {			/* Wrap */
1169 			x = m->lPad;
1170 			y += hLbl;
1171 			r->h += hLbl+m->bPad;
1172 		}
1173 		if (r->w < MIN(x+wLbl,wView)) {
1174 			r->w = MIN(x+wLbl,wView);
1175 		}
1176 		x += wLbl;
1177 	}
1178 }
1179 
1180 static int
SizeAllocate(void * obj,const AG_SizeAlloc * a)1181 SizeAllocate(void *obj, const AG_SizeAlloc *a)
1182 {
1183 	AG_Menu *m = obj;
1184 	AG_MenuItem *mi;
1185 	int wLbl, hLbl;
1186 	int x, y;
1187 
1188 	if (a->w < (m->lPad + m->rPad) ||
1189 	    a->h < (m->tPad + m->bPad)) {
1190 		return (-1);
1191 	}
1192 	m->r.x = m->lPad;
1193 	m->r.y = m->tPad;
1194 	m->r.w = a->w - m->rPad;
1195 	m->r.h = a->h - m->bPad;
1196 
1197 	if (m->root == NULL) {
1198 		return (-1);
1199 	}
1200 	x = m->lPad;
1201 	y = m->tPad;
1202 	TAILQ_FOREACH(mi, &m->root->subItems, items) {
1203 		GetItemSize(mi, &wLbl, &hLbl);
1204 		mi->x = x;
1205 		mi->y = y;
1206 		if (x+wLbl > a->w) {
1207 			mi->x = m->lPad;
1208 			mi->y += hLbl;
1209 			y += hLbl;
1210 		}
1211 		x += wLbl;
1212 	}
1213 	return (0);
1214 }
1215 
1216 AG_PopupMenu *
AG_PopupNew(void * obj)1217 AG_PopupNew(void *obj)
1218 {
1219 	AG_Widget *wid = obj;
1220 	AG_PopupMenu *pm;
1221 
1222 	pm = Malloc(sizeof(AG_PopupMenu));
1223 	pm->widget = wid;
1224 	pm->menu = AG_MenuNew(NULL, 0);
1225 	pm->menu->style = AG_MENU_POPUP;
1226 	pm->root = AG_MenuNode(pm->menu->root, NULL, NULL);
1227 	pm->menu->itemSel = pm->root;
1228 	pm->win = NULL;
1229 #ifdef AG_LEGACY
1230 	pm->item = pm->root;
1231 #endif
1232 	return (pm);
1233 }
1234 
1235 void
AG_PopupShow(AG_PopupMenu * pm)1236 AG_PopupShow(AG_PopupMenu *pm)
1237 {
1238 	AG_Driver *drv;
1239 	AG_Window *winParent;
1240 	int x = 0, y = 0;
1241 
1242 	AG_LockVFS(pm->widget);
1243 
1244 	if ((drv = WIDGET(pm->widget)->drv) != NULL &&
1245 	    (winParent = AG_ParentWindow(pm->widget)) != NULL) {
1246 	    	x = drv->mouse->x;
1247 	    	y = drv->mouse->y;
1248 		if (AGDRIVER_SINGLE(drv)) {
1249 			x -= WIDGET(winParent)->x;
1250 			y -= WIDGET(winParent)->y;
1251 		}
1252 		AG_ObjectLock(pm->menu);
1253 		pm->win = AG_MenuExpand(winParent, pm->root, x, y);
1254 		AG_ObjectUnlock(pm->menu);
1255 	}
1256 
1257 	AG_UnlockVFS(pm->widget);
1258 }
1259 
1260 void
AG_PopupShowAt(AG_PopupMenu * pm,int x,int y)1261 AG_PopupShowAt(AG_PopupMenu *pm, int x, int y)
1262 {
1263 	AG_LockVFS(pm->widget);
1264 	AG_ObjectLock(pm->menu);
1265 	pm->win = AG_MenuExpand(pm->widget, pm->root, x, y);
1266 	AG_ObjectUnlock(pm->menu);
1267 	AG_UnlockVFS(pm->widget);
1268 }
1269 
1270 static void
PopupHideAll(AG_MenuItem * mi)1271 PopupHideAll(AG_MenuItem *mi)
1272 {
1273 	AG_MenuItem *miSub;
1274 
1275 	if (mi->view != NULL) {
1276 		AG_WindowHide(WIDGET(mi->view)->window);
1277 	}
1278 	TAILQ_FOREACH(miSub, &mi->subItems, items)
1279 		PopupHideAll(miSub);
1280 }
1281 
1282 void
AG_PopupHide(AG_PopupMenu * pm)1283 AG_PopupHide(AG_PopupMenu *pm)
1284 {
1285 	AG_ObjectLock(pm->menu);
1286 	PopupHideAll(pm->root);
1287 	AG_ObjectUnlock(pm->menu);
1288 }
1289 
1290 void
AG_PopupDestroy(AG_PopupMenu * pm)1291 AG_PopupDestroy(AG_PopupMenu *pm)
1292 {
1293 	AG_ObjectDestroy(pm->menu);
1294 	free(pm);
1295 }
1296 
1297 AG_WidgetClass agMenuClass = {
1298 	{
1299 		"Agar(Widget:Menu)",
1300 		sizeof(AG_Menu),
1301 		{ 0,0 },
1302 		Init,
1303 		NULL,			/* free */
1304 		Destroy,
1305 		NULL,			/* load */
1306 		NULL,			/* save */
1307 		NULL			/* edit */
1308 	},
1309 	Draw,
1310 	SizeRequest,
1311 	SizeAllocate
1312 };
1313