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