1 /*
2  * Copyright (c) 2004-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/mfspinbutton.h>
28 #include <agar/gui/window.h>
29 
30 #include <string.h>
31 
32 static void SelectedUnit(AG_Event *);
33 static void InitUnitSystem(AG_MFSpinbutton *, const char *);
34 
35 AG_MFSpinbutton *
AG_MFSpinbuttonNew(void * parent,Uint flags,const char * unit,const char * sep,const char * label)36 AG_MFSpinbuttonNew(void *parent, Uint flags, const char *unit, const char *sep,
37     const char *label)
38 {
39 	AG_MFSpinbutton *fsu;
40 
41 	fsu = Malloc(sizeof(AG_MFSpinbutton));
42 	AG_ObjectInit(fsu, &agMFSpinbuttonClass);
43 	fsu->sep = sep;
44 	fsu->flags |= flags;
45 	if (!(flags & AG_MFSPINBUTTON_NOHFILL))	{ AG_ExpandHoriz(fsu); }
46 	if (  flags & AG_MFSPINBUTTON_VFILL)	{ AG_ExpandVert(fsu); }
47 
48 	if (label != NULL) {
49 		AG_TextboxSetLabelS(fsu->input, label);
50 	}
51 	if (unit != NULL) {
52 		fsu->units = AG_UComboNew(fsu, 0);
53 		AG_SetEvent(fsu->units, "ucombo-selected",
54 		    SelectedUnit, "%p", fsu);
55 		InitUnitSystem(fsu, unit);
56 		AG_WidgetSetFocusable(fsu->units, 0);
57 	}
58 
59 	AG_ObjectAttach(parent, fsu);
60 	return (fsu);
61 }
62 
63 static Uint32
UpdateTimeout(AG_Timer * to,AG_Event * event)64 UpdateTimeout(AG_Timer *to, AG_Event *event)
65 {
66 	AG_MFSpinbutton *fsu = AG_SELF();
67 
68 	if (!AG_WidgetIsFocused(fsu)) {
69 		AG_MFSpinbuttonUpdate(fsu);
70 	}
71 	return (to->ival);
72 }
73 
74 static void
OnShow(AG_Event * event)75 OnShow(AG_Event *event)
76 {
77 	AG_MFSpinbutton *fsu = AG_SELF();
78 	AG_Variable *Vx, *Vy;
79 
80 	if ((fsu->flags & AG_MFSPINBUTTON_EXCL) == 0) {
81 		AG_AddTimer(fsu, &fsu->updateTo, 250, UpdateTimeout, NULL);
82 	}
83 	if ((Vx = AG_GetVariableLocked(fsu, "xvalue")) == NULL) {
84 		fsu->xvalue = 0.0;
85 		Vx = AG_BindDouble(fsu, "xvalue", &fsu->xvalue);
86 		if (Vx == NULL) {
87 			return;
88 		}
89 		AG_LockVariable(Vx);
90 	}
91 	if ((Vy = AG_GetVariableLocked(fsu, "yvalue")) == NULL) {
92 		fsu->yvalue = 0.0;
93 		Vy = AG_BindDouble(fsu, "yvalue", &fsu->yvalue);
94 		if (Vy == NULL) {
95 			return;
96 		}
97 		AG_LockVariable(Vy);
98 	}
99 	if (Vx->type != Vy->type) {
100 		AG_FatalError("MFSpinbutton xvalue/yvalue types disagree");
101 	}
102 	switch (Vx->type) {
103 	case AG_VARIABLE_P_FLOAT:
104 		if (!AG_Defined(fsu, "min")) {
105 			fsu->minFlt = -AG_FLT_MAX+1;
106 			AG_BindFloat(fsu, "min", &fsu->minFlt);
107 		}
108 		if (!AG_Defined(fsu, "max")) {
109 			fsu->maxFlt = +AG_FLT_MAX-1;
110 			AG_BindFloat(fsu, "max", &fsu->maxFlt);
111 		}
112 		break;
113 	case AG_VARIABLE_P_DOUBLE:
114 		if (!AG_Defined(fsu, "min")) {
115 			fsu->min = -AG_DBL_MAX+1;
116 			AG_BindDouble(fsu, "min", &fsu->min);
117 		}
118 		if (!AG_Defined(fsu, "max")) {
119 			fsu->max = +AG_DBL_MAX-1;
120 			AG_BindDouble(fsu, "max", &fsu->max);
121 		}
122 		break;
123 	default:
124 		break;
125 	}
126 	AG_MFSpinbuttonUpdate(fsu);
127 }
128 
129 /* Update the input text from the binding values. */
130 void
AG_MFSpinbuttonUpdate(AG_MFSpinbutton * fsu)131 AG_MFSpinbuttonUpdate(AG_MFSpinbutton *fsu)
132 {
133 	char sx[64], sy[64], s[128];
134 	AG_Variable *valueb;
135 	void *value;
136 
137 	/* Get X value */
138 	valueb = AG_GetVariable(fsu, "xvalue", &value);
139 	switch (AG_VARIABLE_TYPE(valueb)) {
140 	case AG_VARIABLE_DOUBLE:
141 		Snprintf(sx, sizeof(sx), fsu->format,
142 		    AG_Base2Unit(*(double *)value, fsu->unit));
143 		break;
144 	case AG_VARIABLE_FLOAT:
145 		Snprintf(sx, sizeof(sx), fsu->format,
146 		    AG_Base2Unit(*(float *)value, fsu->unit));
147 		break;
148 	case AG_VARIABLE_INT:
149 		StrlcpyInt(sx, *(int *)value, sizeof(sx));
150 		break;
151 	case AG_VARIABLE_UINT:
152 		StrlcpyUint(sx, *(Uint *)value, sizeof(sx));
153 		break;
154 	case AG_VARIABLE_UINT8:
155 		StrlcpyUint(sx, (unsigned)(*(Uint8 *)value), sizeof(sx));
156 		break;
157 	case AG_VARIABLE_SINT8:
158 		StrlcpyInt(sx, (int)(*(Sint8 *)value), sizeof(sx));
159 		break;
160 	case AG_VARIABLE_UINT16:
161 		StrlcpyUint(sx, (unsigned)(*(Uint16 *)value), sizeof(sx));
162 		break;
163 	case AG_VARIABLE_SINT16:
164 		StrlcpyInt(sx, (int)(*(Sint16 *)value), sizeof(sx));
165 		break;
166 	case AG_VARIABLE_UINT32:
167 		StrlcpyUint(sx, (unsigned)(*(Uint32 *)value), sizeof(sx));
168 		break;
169 	case AG_VARIABLE_SINT32:
170 		StrlcpyInt(sx, (int)(*(Sint32 *)value), sizeof(sx));
171 		break;
172 	default:
173 		break;
174 	}
175 	AG_UnlockVariable(valueb);
176 
177 	/* Get Y value */
178 	valueb = AG_GetVariable(fsu, "yvalue", &value);
179 	switch (AG_VARIABLE_TYPE(valueb)) {
180 	case AG_VARIABLE_DOUBLE:
181 		Snprintf(sy, sizeof(sy), fsu->format,
182 		    AG_Base2Unit(*(double *)value, fsu->unit));
183 		break;
184 	case AG_VARIABLE_FLOAT:
185 		Snprintf(sy, sizeof(sy), fsu->format,
186 		    AG_Base2Unit(*(float *)value, fsu->unit));
187 		break;
188 	case AG_VARIABLE_INT:
189 		StrlcpyInt(sy, *(int *)value, sizeof(sy));
190 		break;
191 	case AG_VARIABLE_UINT:
192 		StrlcpyUint(sy, *(Uint *)value, sizeof(sy));
193 		break;
194 	case AG_VARIABLE_UINT8:
195 		StrlcpyUint(sy, (unsigned)(*(Uint8 *)value), sizeof(sy));
196 		break;
197 	case AG_VARIABLE_SINT8:
198 		StrlcpyInt(sy, (int)(*(Sint8 *)value), sizeof(sy));
199 		break;
200 	case AG_VARIABLE_UINT16:
201 		StrlcpyUint(sy, (unsigned)(*(Uint16 *)value), sizeof(sy));
202 		break;
203 	case AG_VARIABLE_SINT16:
204 		StrlcpyInt(sy, (int)(*(Sint16 *)value), sizeof(sy));
205 		break;
206 	case AG_VARIABLE_UINT32:
207 		StrlcpyUint(sy, (unsigned)(*(Uint32 *)value), sizeof(sy));
208 		break;
209 	case AG_VARIABLE_SINT32:
210 		StrlcpyInt(sy, (int)(*(Sint32 *)value), sizeof(sy));
211 		break;
212 	default:
213 		break;
214 	}
215 	Strlcpy(s, sx, sizeof(s));
216 	Strlcat(s, fsu->sep, sizeof(s));
217 	Strlcat(s, sy, sizeof(s));
218 	if (strcmp(s, fsu->inTxt) != 0) {
219 		AG_TextboxSetString(fsu->input, s);
220 	}
221 	AG_UnlockVariable(valueb);
222 }
223 
224 static void
KeyDown(AG_Event * event)225 KeyDown(AG_Event *event)
226 {
227 	AG_MFSpinbutton *fsu = AG_SELF();
228 	int keysym = AG_INT(1);
229 
230 	switch (keysym) {
231 	case AG_KEY_LEFT:
232 		AG_MFSpinbuttonAddValue(fsu, "xvalue", -fsu->inc);
233 		break;
234 	case AG_KEY_RIGHT:
235 		AG_MFSpinbuttonAddValue(fsu, "xvalue", fsu->inc);
236 		break;
237 	case AG_KEY_UP:
238 		AG_MFSpinbuttonAddValue(fsu, "yvalue", -fsu->inc);
239 		break;
240 	case AG_KEY_DOWN:
241 		AG_MFSpinbuttonAddValue(fsu, "yvalue", fsu->inc);
242 		break;
243 	default:
244 		break;
245 	}
246 }
247 
248 static void
TextChanged(AG_Event * event)249 TextChanged(AG_Event *event)
250 {
251 	AG_MFSpinbutton *fsu = AG_PTR(1);
252 	int unfocus = AG_INT(2);
253 	char inTxt[128];
254 	char *tp = &inTxt[0], *s;
255 
256 	AG_ObjectLock(fsu);
257 	Strlcpy(inTxt, fsu->inTxt, sizeof(inTxt));
258 
259 	if ((s = AG_Strsep(&tp, fsu->sep)) != NULL) {
260 		AG_MFSpinbuttonSetValue(fsu, "xvalue",
261 		    strtod(s, NULL)*fsu->unit->divider);
262 	}
263 	if ((s = AG_Strsep(&tp, fsu->sep)) != NULL) {
264 		AG_MFSpinbuttonSetValue(fsu, "yvalue",
265 		    strtod(s, NULL)*fsu->unit->divider);
266 	}
267 
268 	AG_PostEvent(NULL, fsu, "mfspinbutton-return", NULL);
269 
270 	if (unfocus)
271 		AG_WidgetUnfocus(fsu->input);
272 
273 	AG_MFSpinbuttonUpdate(fsu);
274 	AG_ObjectUnlock(fsu);
275 }
276 
277 static void
DecrementY(AG_Event * event)278 DecrementY(AG_Event *event)
279 {
280 	AG_MFSpinbutton *fsu = AG_PTR(1);
281 
282 	AG_ObjectLock(fsu);
283 	AG_MFSpinbuttonAddValue(fsu, "yvalue", -fsu->inc);
284 	AG_ObjectUnlock(fsu);
285 }
286 
287 static void
IncrementY(AG_Event * event)288 IncrementY(AG_Event *event)
289 {
290 	AG_MFSpinbutton *fsu = AG_PTR(1);
291 
292 	AG_ObjectLock(fsu);
293 	AG_MFSpinbuttonAddValue(fsu, "yvalue", fsu->inc);
294 	AG_ObjectUnlock(fsu);
295 }
296 
297 static void
DecrementX(AG_Event * event)298 DecrementX(AG_Event *event)
299 {
300 	AG_MFSpinbutton *fsu = AG_PTR(1);
301 
302 	AG_ObjectLock(fsu);
303 	AG_MFSpinbuttonAddValue(fsu, "xvalue", -fsu->inc);
304 	AG_ObjectUnlock(fsu);
305 }
306 
307 static void
IncrementX(AG_Event * event)308 IncrementX(AG_Event *event)
309 {
310 	AG_MFSpinbutton *fsu = AG_PTR(1);
311 
312 	AG_ObjectLock(fsu);
313 	AG_MFSpinbuttonAddValue(fsu, "xvalue", fsu->inc);
314 	AG_ObjectUnlock(fsu);
315 }
316 
317 /* Widget must be locked. */
318 static void
UpdateUnitSelector(AG_MFSpinbutton * fsu)319 UpdateUnitSelector(AG_MFSpinbutton *fsu)
320 {
321 	AG_ButtonTextS(fsu->units->button, AG_UnitAbbr(fsu->unit));
322 	AG_MFSpinbuttonUpdate(fsu);
323 }
324 
325 static void
SelectedUnit(AG_Event * event)326 SelectedUnit(AG_Event *event)
327 {
328 	AG_MFSpinbutton *fsu = AG_PTR(1);
329 	AG_TlistItem *ti = AG_PTR(2);
330 
331 	AG_ObjectLock(fsu);
332 	fsu->unit = (const AG_Unit *)ti->p1;
333 	UpdateUnitSelector(fsu);
334 	AG_ObjectUnlock(fsu);
335 }
336 
337 static void
InitUnitSystem(AG_MFSpinbutton * fsu,const char * unit_key)338 InitUnitSystem(AG_MFSpinbutton *fsu, const char *unit_key)
339 {
340 	const AG_Unit *unit = NULL;
341 	const AG_Unit *ugroup = NULL;
342 	int found = 0;
343 	int i;
344 
345 	for (i = 0; i < agnUnitGroups; i++) {
346 		ugroup = agUnitGroups[i];
347 		for (unit = &ugroup[0]; unit->key != NULL; unit++) {
348 			if (strcmp(unit->key, unit_key) == 0) {
349 				found++;
350 				break;
351 			}
352 		}
353 		if (found)
354 			break;
355 	}
356 	if (!found) {
357 		AG_FatalError("AG_MFSpinbutton: No such unit: %s", unit_key);
358 	}
359 	fsu->unit = unit;
360 	UpdateUnitSelector(fsu);
361 
362 	AG_ObjectLock(fsu->units->list);
363 	AG_TlistDeselectAll(fsu->units->list);
364 	for (unit = &ugroup[0]; unit->key != NULL; unit++) {
365 		AG_TlistItem *it;
366 
367 		it = AG_TlistAddPtr(fsu->units->list, NULL, _(unit->name),
368 		    (void *)unit);
369 		if (unit == fsu->unit)
370 			it->selected++;
371 	}
372 	AG_TlistSizeHintLargest(fsu->units->list, 5);
373 	AG_ObjectUnlock(fsu->units->list);
374 }
375 
376 static void
Init(void * obj)377 Init(void *obj)
378 {
379 	AG_MFSpinbutton *fsu = obj;
380 	AG_Button *b[4];
381 	int i;
382 
383 	WIDGET(fsu)->flags |= AG_WIDGET_FOCUSABLE|
384 	                      AG_WIDGET_TABLE_EMBEDDABLE;
385 
386 	fsu->inc = 1.0;
387 	fsu->writeable = 1;
388 	fsu->sep = ",";
389 	fsu->inTxt[0] = '\0';
390 	Strlcpy(fsu->format, "%.02f", sizeof(fsu->format));
391 
392 	fsu->input = AG_TextboxNewS(fsu, AG_TEXTBOX_EXCL, NULL);
393 	AG_TextboxBindASCII(fsu->input, fsu->inTxt, sizeof(fsu->inTxt));
394 	AG_TextboxSizeHint(fsu->input, "888.88");
395 
396 	fsu->unit = AG_FindUnit("identity");
397 	fsu->units = NULL;
398 
399 	fsu->xincbu = b[0] = AG_ButtonNewS(fsu, AG_BUTTON_REPEAT, _("+"));
400 	fsu->xdecbu = b[1] = AG_ButtonNewS(fsu, AG_BUTTON_REPEAT, _("-"));
401 	fsu->yincbu = b[2] = AG_ButtonNewS(fsu, AG_BUTTON_REPEAT, _("+"));
402 	fsu->ydecbu = b[3] = AG_ButtonNewS(fsu, AG_BUTTON_REPEAT, _("-"));
403 	AG_SetEvent(fsu->xincbu, "button-pushed", IncrementX, "%p", fsu);
404 	AG_SetEvent(fsu->xdecbu, "button-pushed", DecrementX, "%p", fsu);
405 	AG_SetEvent(fsu->yincbu, "button-pushed", IncrementY, "%p", fsu);
406 	AG_SetEvent(fsu->ydecbu, "button-pushed", DecrementY, "%p", fsu);
407 	for (i = 0; i < 4; i++) {
408 		AG_ButtonSetPadding(b[i], 0,0,0,0);
409 		AG_LabelSetPadding(b[i]->lbl, 0,0,0,0);
410 		AG_WidgetSetFocusable(b[i], 0);
411 	}
412 
413 	AG_InitTimer(&fsu->updateTo, "update", 0);
414 
415 	AG_AddEvent(fsu, "widget-shown", OnShow, NULL);
416 	AG_SetEvent(fsu, "key-down", KeyDown, NULL);
417 	AG_SetEvent(fsu->input, "textbox-return", TextChanged, "%p,%i",fsu,1);
418 	AG_SetEvent(fsu->input, "textbox-changed", TextChanged, "%p,%i",fsu,0);
419 }
420 
421 static void
SizeRequest(void * obj,AG_SizeReq * r)422 SizeRequest(void *obj, AG_SizeReq *r)
423 {
424 	AG_MFSpinbutton *fsu = obj;
425 	AG_SizeReq rChld, rYinc, rYdec;
426 
427 	AG_WidgetSizeReq(fsu->input, &rChld);
428 	r->w = rChld.w;
429 	r->h = rChld.h;
430 	if (fsu->units != NULL) {
431 		AG_WidgetSizeReq(fsu->units, &rChld);
432 		r->w += rChld.w+4;
433 	}
434 	AG_WidgetSizeReq(fsu->xdecbu, &rChld);
435 	r->w += rChld.w;
436 	AG_WidgetSizeReq(fsu->xincbu, &rChld);
437 	r->w += rChld.w;
438 	AG_WidgetSizeReq(fsu->yincbu, &rYinc);
439 	AG_WidgetSizeReq(fsu->ydecbu, &rYdec);
440 	r->w += MAX(rYinc.w,rYdec.w);
441 }
442 
443 static int
SizeAllocate(void * obj,const AG_SizeAlloc * a)444 SizeAllocate(void *obj, const AG_SizeAlloc *a)
445 {
446 	AG_MFSpinbutton *fsu = obj;
447 	int szBtn = a->h/2;
448 	int wUnitBox = (fsu->units != NULL) ? 25 : 0;
449 	int x = 0, y = 0;
450 	AG_SizeAlloc aChld;
451 
452 	if (a->w < szBtn*3 + wUnitBox + 4)
453 		return (-1);
454 
455 	/* Input textbox */
456 	aChld.x = x;
457 	aChld.y = y;
458 	aChld.w = a->w - 2 - wUnitBox - 2 - szBtn*3;
459 	aChld.h = a->h;
460 	AG_WidgetSizeAlloc(fsu->input, &aChld);
461 	x += aChld.w + 2;
462 
463 	/* Unit selector */
464 	if (fsu->units != NULL) {
465 		aChld.x = x;
466 		aChld.y = y;
467 		aChld.w = wUnitBox;
468 		aChld.h = a->h;
469 		AG_WidgetSizeAlloc(fsu->units, &aChld);
470 		x += aChld.w + 2;
471 	}
472 
473 	/* Increment buttons */
474 	aChld.w = szBtn;
475 	aChld.h = szBtn;
476 	aChld.x = x;
477 	aChld.y = y + szBtn/2;
478 	AG_WidgetSizeAlloc(fsu->xdecbu, &aChld);
479 	aChld.x = x + szBtn*2;
480 	aChld.y = y + szBtn/2;
481 	AG_WidgetSizeAlloc(fsu->xincbu, &aChld);
482 	aChld.x = x + szBtn;
483 	aChld.y = y;
484 	AG_WidgetSizeAlloc(fsu->ydecbu, &aChld);
485 	aChld.x = x + szBtn;
486 	aChld.y = y + szBtn;
487 	AG_WidgetSizeAlloc(fsu->yincbu, &aChld);
488 	return (0);
489 }
490 
491 static void
Draw(void * obj)492 Draw(void *obj)
493 {
494 	AG_MFSpinbutton *fsu = obj;
495 	AG_Variable *xvalueb, *yvalueb;
496 	double *xvalue, *yvalue;
497 
498 	AG_WidgetDraw(fsu->input);
499 	if (fsu->units != NULL) { AG_WidgetDraw(fsu->units); }
500 	AG_WidgetDraw(fsu->xincbu);
501 	AG_WidgetDraw(fsu->yincbu);
502 	AG_WidgetDraw(fsu->xdecbu);
503 	AG_WidgetDraw(fsu->ydecbu);
504 
505 	xvalueb = AG_GetVariable(fsu, "xvalue", &xvalue);
506 	yvalueb = AG_GetVariable(fsu, "yvalue", &yvalue);
507 
508 	Snprintf(fsu->inTxt, sizeof(fsu->inTxt), fsu->format,
509 	    *xvalue/fsu->unit->divider,
510 	    *yvalue/fsu->unit->divider);
511 
512 	AG_UnlockVariable(xvalueb);
513 	AG_UnlockVariable(yvalueb);
514 }
515 
516 void
AG_MFSpinbuttonAddValue(AG_MFSpinbutton * fsu,const char * which,double inc)517 AG_MFSpinbuttonAddValue(AG_MFSpinbutton *fsu, const char *which, double inc)
518 {
519 	AG_Variable *valueb, *minb, *maxb;
520 	void *value;
521 	double *min, *max;
522 
523 	AG_ObjectLock(fsu);
524 
525 	inc *= fsu->unit->divider;
526 	valueb = AG_GetVariable(fsu, which, &value);
527 	minb = AG_GetVariable(fsu, "min", &min);
528 	maxb = AG_GetVariable(fsu, "max", &max);
529 
530 	switch (AG_VARIABLE_TYPE(valueb)) {
531 	case AG_VARIABLE_DOUBLE:
532 		*(double *)value = *(double *)value+inc < *min ? *min :
533 		                   *(double *)value+inc > *max ? *max :
534 				   *(double *)value+inc;
535 		break;
536 	case AG_VARIABLE_FLOAT:
537 		*(float *)value = *(float *)value+inc < *min ? *min :
538 		                  *(float *)value+inc > *max ? *max :
539 				  *(float *)value+inc;
540 		break;
541 	default:
542 		break;
543 	}
544 	AG_PostEvent(NULL, fsu, "mfspinbutton-changed", "%s", which);
545 
546 	AG_UnlockVariable(valueb);
547 	AG_UnlockVariable(minb);
548 	AG_UnlockVariable(maxb);
549 
550 	AG_MFSpinbuttonUpdate(fsu);
551 	AG_ObjectUnlock(fsu);
552 }
553 
554 void
AG_MFSpinbuttonSetValue(AG_MFSpinbutton * fsu,const char * which,double nvalue)555 AG_MFSpinbuttonSetValue(AG_MFSpinbutton *fsu, const char *which,
556     double nvalue)
557 {
558 	AG_Variable *valueb, *minb, *maxb;
559 	void *value;
560 	double *min, *max;
561 
562 	AG_ObjectLock(fsu);
563 
564 	valueb = AG_GetVariable(fsu, which, &value);
565 	minb = AG_GetVariable(fsu, "min", &min);
566 	maxb = AG_GetVariable(fsu, "max", &max);
567 
568 	switch (AG_VARIABLE_TYPE(valueb)) {
569 	case AG_VARIABLE_DOUBLE:
570 		*(double *)value = nvalue < *min ? *min :
571 		                   nvalue > *max ? *max :
572 				   nvalue;
573 		break;
574 	case AG_VARIABLE_FLOAT:
575 		*(float *)value = nvalue < *min ? *min :
576 		                  nvalue > *max ? *max :
577 				  (float)nvalue;
578 		break;
579 	default:
580 		break;
581 	}
582 	AG_PostEvent(NULL, fsu, "mfspinbutton-changed", "%s", which);
583 
584 	AG_UnlockVariable(valueb);
585 	AG_UnlockVariable(minb);
586 	AG_UnlockVariable(maxb);
587 
588 	AG_MFSpinbuttonUpdate(fsu);
589 	AG_ObjectUnlock(fsu);
590 }
591 
592 void
AG_MFSpinbuttonSetMin(AG_MFSpinbutton * fsu,double nmin)593 AG_MFSpinbuttonSetMin(AG_MFSpinbutton *fsu, double nmin)
594 {
595 	AG_Variable *minb;
596 	void *min;
597 
598 	AG_ObjectLock(fsu);
599 	minb = AG_GetVariable(fsu, "min", &min);
600 	switch (AG_VARIABLE_TYPE(minb)) {
601 	case AG_VARIABLE_DOUBLE:
602 		*(double *)min = nmin;
603 		break;
604 	case AG_VARIABLE_FLOAT:
605 		*(float *)min = (float)nmin;
606 		break;
607 	default:
608 		break;
609 	}
610 	AG_UnlockVariable(minb);
611 	AG_ObjectUnlock(fsu);
612 }
613 
614 void
AG_MFSpinbuttonSetMax(AG_MFSpinbutton * fsu,double nmax)615 AG_MFSpinbuttonSetMax(AG_MFSpinbutton *fsu, double nmax)
616 {
617 	AG_Variable *maxb;
618 	void *max;
619 
620 	AG_ObjectLock(fsu);
621 	maxb = AG_GetVariable(fsu, "max", &max);
622 	switch (AG_VARIABLE_TYPE(maxb)) {
623 	case AG_VARIABLE_DOUBLE:
624 		*(double *)max = nmax;
625 		break;
626 	case AG_VARIABLE_FLOAT:
627 		*(float *)max = (float)nmax;
628 		break;
629 	default:
630 		break;
631 	}
632 	AG_UnlockVariable(maxb);
633 	AG_ObjectUnlock(fsu);
634 }
635 
636 void
AG_MFSpinbuttonSetIncrement(AG_MFSpinbutton * fsu,double inc)637 AG_MFSpinbuttonSetIncrement(AG_MFSpinbutton *fsu, double inc)
638 {
639 	AG_ObjectLock(fsu);
640 	fsu->inc = inc;
641 	AG_ObjectUnlock(fsu);
642 }
643 
644 void
AG_MFSpinbuttonSetPrecision(AG_MFSpinbutton * fsu,const char * mode,int precision)645 AG_MFSpinbuttonSetPrecision(AG_MFSpinbutton *fsu, const char *mode,
646     int precision)
647 {
648 	AG_ObjectLock(fsu);
649 	Snprintf(fsu->format, sizeof(fsu->format), "%%.%d%s", precision, mode);
650 	AG_MFSpinbuttonUpdate(fsu);
651 	AG_ObjectUnlock(fsu);
652 }
653 
654 void
AG_MFSpinbuttonSelectUnit(AG_MFSpinbutton * fsu,const char * uname)655 AG_MFSpinbuttonSelectUnit(AG_MFSpinbutton *fsu, const char *uname)
656 {
657 	AG_TlistItem *it;
658 
659 	AG_ObjectLock(fsu);
660 	AG_ObjectLock(fsu->units->list);
661 	AG_TlistDeselectAll(fsu->units->list);
662 	TAILQ_FOREACH(it, &fsu->units->list->items, items) {
663 		const AG_Unit *u = it->p1;
664 
665 		if (strcmp(u->key, uname) == 0) {
666 			it->selected++;
667 			fsu->unit = u;
668 			UpdateUnitSelector(fsu);
669 			break;
670 		}
671 	}
672 	AG_ObjectUnlock(fsu->units->list);
673 	AG_ObjectUnlock(fsu);
674 }
675 
676 void
AG_MFSpinbuttonSetWriteable(AG_MFSpinbutton * fsu,int writeable)677 AG_MFSpinbuttonSetWriteable(AG_MFSpinbutton *fsu, int writeable)
678 {
679 	AG_ObjectLock(fsu);
680 	fsu->writeable = writeable;
681 	if (writeable) {
682 		AG_WidgetEnable(fsu->xincbu);
683 		AG_WidgetEnable(fsu->xdecbu);
684 		AG_WidgetEnable(fsu->yincbu);
685 		AG_WidgetEnable(fsu->ydecbu);
686 		AG_WidgetEnable(fsu->input);
687 	} else {
688 		AG_WidgetDisable(fsu->xincbu);
689 		AG_WidgetDisable(fsu->xdecbu);
690 		AG_WidgetDisable(fsu->yincbu);
691 		AG_WidgetDisable(fsu->ydecbu);
692 		AG_WidgetDisable(fsu->input);
693 	}
694 	AG_ObjectUnlock(fsu);
695 	AG_Redraw(fsu);
696 }
697 
698 void
AG_MFSpinbuttonSetRange(AG_MFSpinbutton * fsu,double min,double max)699 AG_MFSpinbuttonSetRange(AG_MFSpinbutton *fsu, double min, double max)
700 {
701 	AG_ObjectLock(fsu);
702 	AG_MFSpinbuttonSetMin(fsu, min);
703 	AG_MFSpinbuttonSetMax(fsu, max);
704 	AG_ObjectUnlock(fsu);
705 }
706 
707 AG_WidgetClass agMFSpinbuttonClass = {
708 	{
709 		"Agar(Widget:MFSpinbutton)",
710 		sizeof(AG_MFSpinbutton),
711 		{ 0,0 },
712 		Init,
713 		NULL,			/* free */
714 		NULL,			/* destroy */
715 		NULL,			/* load */
716 		NULL,			/* save */
717 		NULL			/* edit */
718 	},
719 	Draw,
720 	SizeRequest,
721 	SizeAllocate
722 };
723