1 /*
2 * Copyright (c) 2002-2012 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/combo.h>
28 #include <agar/gui/primitive.h>
29
30 AG_Combo *
AG_ComboNew(void * parent,Uint flags,const char * fmt,...)31 AG_ComboNew(void *parent, Uint flags, const char *fmt, ...)
32 {
33 char s[AG_LABEL_MAX];
34 va_list ap;
35
36 if (fmt != NULL) {
37 va_start(ap, fmt);
38 Vsnprintf(s, sizeof(s), fmt, ap);
39 va_end(ap);
40 return AG_ComboNewS(parent, flags, s);
41 } else {
42 return AG_ComboNewS(parent, flags, NULL);
43 }
44 }
45
46 AG_Combo *
AG_ComboNewS(void * parent,Uint flags,const char * label)47 AG_ComboNewS(void *parent, Uint flags, const char *label)
48 {
49 AG_Combo *com;
50
51 com = Malloc(sizeof(AG_Combo));
52 AG_ObjectInit(com, &agComboClass);
53 com->flags |= flags;
54
55 if (label != NULL) {
56 AG_TextboxSetLabelS(com->tbox, label);
57 }
58 if (flags & AG_COMBO_ANY_TEXT) { AG_WidgetDisable(com->tbox); }
59 if (flags & AG_COMBO_TREE) { com->list->flags |= AG_TLIST_TREE; }
60 if (flags & AG_COMBO_POLL) { com->list->flags |= AG_TLIST_POLL; }
61 if (flags & AG_COMBO_SCROLLTOSEL) {
62 com->list->flags |= AG_TLIST_SCROLLTOSEL;
63 }
64 if (flags & AG_COMBO_HFILL) { AG_ExpandHoriz(com); }
65 if (flags & AG_COMBO_VFILL) { AG_ExpandVert(com); }
66
67 AG_ObjectAttach(parent, com);
68 return (com);
69 }
70
71 static void
Collapse(AG_Combo * com)72 Collapse(AG_Combo *com)
73 {
74 if (com->panel == NULL) {
75 return;
76 }
77 com->wSaved = WIDTH(com->panel);
78 com->hSaved = HEIGHT(com->panel);
79
80 AG_ObjectDetach(com->list);
81 AG_ObjectDetach(com->panel);
82 com->panel = NULL;
83
84 AG_SetInt(com->button, "state", 0);
85 }
86
87 static void
ModalClose(AG_Event * event)88 ModalClose(AG_Event *event)
89 {
90 AG_Combo *com = AG_PTR(1);
91
92 if (com->panel != NULL)
93 Collapse(com);
94 }
95
96 static void
Expand(AG_Event * event)97 Expand(AG_Event *event)
98 {
99 AG_Combo *com = AG_PTR(1);
100 AG_Driver *drv = WIDGET(com)->drv;
101 int expand = AG_INT(2);
102 AG_SizeReq rList;
103 int x, y, w, h;
104 Uint wView, hView;
105
106 if (expand) {
107 com->panel = AG_WindowNew(
108 AG_WINDOW_NOTITLE|AG_WINDOW_DENYFOCUS|AG_WINDOW_KEEPABOVE|
109 AG_WINDOW_MODAL);
110 com->panel->wmType = AG_WINDOW_WM_COMBO;
111 AG_WindowSetPadding(com->panel, 0,0,0,0);
112 AG_ObjectSetName(com->panel, "_ComboPopup");
113 AG_ObjectAttach(com->panel, com->list);
114 if (WIDGET(com)->window != NULL) {
115 AG_WindowAttach(WIDGET(com)->window, com->panel);
116 AG_WindowMakeTransient(WIDGET(com)->window, com->panel);
117 AG_WindowPin(WIDGET(com)->window, com->panel);
118 }
119
120 if (com->wSaved > 0) {
121 w = com->wSaved;
122 h = com->hSaved;
123 } else {
124 if (com->wPreList != -1 && com->hPreList != -1) {
125 AG_TlistSizeHintPixels(com->list,
126 com->wPreList, com->hPreList);
127 }
128 AG_WidgetSizeReq(com->list, &rList);
129 w = rList.w + com->panel->wBorderSide*2;
130 h = rList.h + com->panel->wBorderBot;
131 }
132 x = WIDGET(com)->rView.x2 - w;
133 y = WIDGET(com)->rView.y1;
134
135 AG_GetDisplaySize(WIDGET(com)->drv, &wView, &hView);
136 if (x+w > wView) { w = wView - x; }
137 if (y+h > hView) { h = hView - y; }
138
139 if (AGDRIVER_CLASS(drv)->wm == AG_WM_MULTIPLE &&
140 WIDGET(com)->window != NULL) {
141 x += WIDGET(WIDGET(com)->window)->x;
142 y += WIDGET(WIDGET(com)->window)->y;
143 }
144 if (x < 0) { x = 0; }
145 if (y < 0) { y = 0; }
146 if (w < 4 || h < 4) {
147 Collapse(com);
148 return;
149 }
150 AG_SetEvent(com->panel, "window-modal-close",
151 ModalClose, "%p", com);
152 AG_WindowSetGeometry(com->panel, x,y, w,h);
153 AG_WindowShow(com->panel);
154 } else {
155 Collapse(com);
156 }
157 }
158
159 /* Select a combo item based on its pointer. */
160 AG_TlistItem *
AG_ComboSelectPointer(AG_Combo * com,void * p)161 AG_ComboSelectPointer(AG_Combo *com, void *p)
162 {
163 AG_TlistItem *it;
164
165 AG_ObjectLock(com->list);
166 if ((it = AG_TlistSelectPtr(com->list, p)) != NULL) {
167 AG_TextboxSetString(com->tbox, it->text);
168 }
169 AG_ObjectUnlock(com->list);
170 return (it);
171 }
172
173 /* Select a combo item based on its text. */
174 AG_TlistItem *
AG_ComboSelectText(AG_Combo * com,const char * text)175 AG_ComboSelectText(AG_Combo *com, const char *text)
176 {
177 AG_TlistItem *it;
178
179 AG_ObjectLock(com->list);
180 if ((it = AG_TlistSelectText(com->list, text)) != NULL) {
181 AG_TextboxSetString(com->tbox, it->text);
182 }
183 AG_ObjectUnlock(com->list);
184 return (it);
185 }
186
187 void
AG_ComboSelect(AG_Combo * com,AG_TlistItem * it)188 AG_ComboSelect(AG_Combo *com, AG_TlistItem *it)
189 {
190 AG_ObjectLock(com->list);
191 AG_TextboxSetString(com->tbox, it->text);
192 AG_TlistSelect(com->list, it);
193 AG_ObjectUnlock(com->list);
194 }
195
196 static void
SelectedItem(AG_Event * event)197 SelectedItem(AG_Event *event)
198 {
199 AG_Tlist *tl = AG_SELF();
200 AG_Combo *com = AG_PTR(1);
201 AG_TlistItem *ti;
202
203 AG_ObjectLock(tl);
204 if ((ti = AG_TlistSelectedItem(tl)) != NULL) {
205 AG_TextboxSetString(com->tbox, ti->text);
206 AG_PostEvent(NULL, com, "combo-selected", "%p", ti);
207 }
208 AG_ObjectUnlock(tl);
209 Collapse(com);
210 }
211
212 static void
Return(AG_Event * event)213 Return(AG_Event *event)
214 {
215 AG_Textbox *tbox = AG_SELF();
216 AG_Combo *com = AG_PTR(1);
217 char *text;
218
219 AG_ObjectLock(com->list);
220 text = tbox->text->ent[0].buf;
221
222 if ((com->flags & AG_COMBO_ANY_TEXT) == 0) {
223 AG_TlistItem *it;
224
225 if (text[0] != '\0' &&
226 (it = AG_TlistSelectText(com->list, text)) != NULL) {
227 AG_TextboxSetString(com->tbox, it->text);
228 AG_PostEvent(NULL, com, "combo-selected", "%p", it);
229 } else {
230 AG_TlistDeselectAll(com->list);
231 AG_TextboxSetString(com->tbox, "");
232 AG_PostEvent(NULL, com, "combo-text-unknown", "%s",
233 text);
234 }
235 } else {
236 AG_TlistDeselectAll(com->list);
237 AG_PostEvent(NULL, com, "combo-text-entry", "%s", text);
238 }
239
240 AG_ObjectUnlock(com->list);
241 }
242
243 static void
OnDetach(AG_Event * event)244 OnDetach(AG_Event *event)
245 {
246 AG_Combo *com = AG_SELF();
247
248 if (com->panel != NULL) {
249 AG_ObjectDetach(com->list);
250 AG_ObjectDetach(com->panel);
251 com->panel = NULL;
252 }
253 }
254
255 static void
Init(void * obj)256 Init(void *obj)
257 {
258 AG_Combo *com = obj;
259
260 WIDGET(com)->flags |= AG_WIDGET_TABLE_EMBEDDABLE;
261
262 com->flags = 0;
263 com->panel = NULL;
264 com->wSaved = 0;
265 com->hSaved = 0;
266 com->wPreList = -1;
267 com->hPreList = -1;
268
269 com->tbox = AG_TextboxNewS(com, AG_TEXTBOX_COMBO|AG_TEXTBOX_EXCL, NULL);
270 com->button = AG_ButtonNewS(com, AG_BUTTON_STICKY, _(" ... "));
271 AG_ButtonSetPadding(com->button, 0,0,0,0);
272 AG_LabelSetPadding(com->button->lbl, 0,0,0,0);
273 AG_WidgetSetFocusable(com->button, 0);
274
275 com->list = Malloc(sizeof(AG_Tlist));
276 AG_ObjectInit(com->list, &agTlistClass);
277 AG_Expand(com->list);
278
279 AG_SetEvent(com, "detached", OnDetach, NULL);
280 AG_SetEvent(com->button, "button-pushed", Expand, "%p", com);
281 AG_SetEvent(com->list, "tlist-changed", SelectedItem, "%p", com);
282 AG_SetEvent(com->tbox, "textbox-return", Return, "%p", com);
283 AG_WidgetForwardFocus(com, com->tbox);
284 }
285
286 void
AG_ComboSizeHint(AG_Combo * com,const char * text,int h)287 AG_ComboSizeHint(AG_Combo *com, const char *text, int h)
288 {
289 AG_TextSize(text, &com->wPreList, NULL);
290 com->hPreList = h;
291 }
292
293 void
AG_ComboSizeHintPixels(AG_Combo * com,int w,int h)294 AG_ComboSizeHintPixels(AG_Combo *com, int w, int h)
295 {
296 com->wPreList = w;
297 com->hPreList = h;
298 }
299
300 void
AG_ComboSetButtonText(AG_Combo * com,const char * text)301 AG_ComboSetButtonText(AG_Combo *com, const char *text)
302 {
303 AG_ButtonTextS(com->button, text);
304 }
305
306 void
AG_ComboSetButtonSurface(AG_Combo * com,AG_Surface * su)307 AG_ComboSetButtonSurface(AG_Combo *com, AG_Surface *su)
308 {
309 AG_ButtonSurface(com->button, su);
310 }
311
312 void
AG_ComboSetButtonSurfaceNODUP(AG_Combo * com,AG_Surface * su)313 AG_ComboSetButtonSurfaceNODUP(AG_Combo *com, AG_Surface *su)
314 {
315 AG_ButtonSurfaceNODUP(com->button, su);
316 }
317
318 static void
Destroy(void * p)319 Destroy(void *p)
320 {
321 AG_Combo *com = p;
322
323 AG_ObjectDestroy(com->list);
324 }
325
326 static void
Draw(void * obj)327 Draw(void *obj)
328 {
329 AG_Combo *com = obj;
330
331 AG_WidgetDraw(com->tbox);
332 AG_WidgetDraw(com->button);
333 }
334
335 static void
SizeRequest(void * obj,AG_SizeReq * r)336 SizeRequest(void *obj, AG_SizeReq *r)
337 {
338 AG_Combo *com = obj;
339 AG_SizeReq rChld;
340
341 AG_WidgetSizeReq(com->tbox, &rChld);
342 r->w = rChld.w;
343 r->h = rChld.h;
344 AG_WidgetSizeReq(com->button, &rChld);
345 r->w += rChld.w;
346 if (r->h < rChld.h) { r->h = rChld.h; }
347 }
348
349 static int
SizeAllocate(void * obj,const AG_SizeAlloc * a)350 SizeAllocate(void *obj, const AG_SizeAlloc *a)
351 {
352 AG_Combo *com = obj;
353 AG_SizeReq rBtn;
354 AG_SizeAlloc aChld;
355
356 AG_WidgetSizeReq(com->button, &rBtn);
357 if (a->w < rBtn.w) {
358 return (-1);
359 }
360 aChld.x = 0;
361 aChld.y = 0;
362 aChld.w = a->w - rBtn.w - 1;
363 aChld.h = a->h;
364 AG_WidgetSizeAlloc(com->tbox, &aChld);
365 aChld.x = aChld.w + 1;
366 aChld.w = rBtn.w;
367 AG_WidgetSizeAlloc(com->button, &aChld);
368 return (0);
369 }
370
371 AG_WidgetClass agComboClass = {
372 {
373 "Agar(Widget:Combo)",
374 sizeof(AG_Combo),
375 { 0,0 },
376 Init,
377 NULL, /* free */
378 Destroy,
379 NULL, /* load */
380 NULL, /* save */
381 NULL /* edit */
382 },
383 Draw,
384 SizeRequest,
385 SizeAllocate
386 };
387