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