1 /*
2  * Copyright (c) 2005-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 /*
27  * Visualization widget.
28  */
29 
30 #include <agar/core/core.h>
31 #include <agar/gui/gui.h>
32 #include <agar/gui/primitive.h>
33 #include <agar/gui/opengl.h>
34 #include <agar/vg/vg.h>
35 #include <agar/vg/vg_view.h>
36 #include <agar/vg/vg_tools.h>
37 #include <agar/vg/tools.h>
38 
39 #include <stdarg.h>
40 #include <string.h>
41 
42 static const float scaleFactors[] = {
43 	1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f,
44 	12.0f, 14.0f, 16.0f, 18.0f, 20.0f, 22.0f, 24.0f, 26.0f, 28.0f,
45 	30.0f, 40.0f, 50.0f, 60.0f, 70.0f, 80.0f, 90.0f, 100.0f,
46 	200.0f, 300.0f, 400.0f, 600.0f, 700.0f, 800.0f, 900.0f
47 };
48 const int nScaleFactors = sizeof(scaleFactors)/sizeof(scaleFactors[0]);
49 
50 VG_View *
VG_ViewNew(void * parent,VG * vg,Uint flags)51 VG_ViewNew(void *parent, VG *vg, Uint flags)
52 {
53 	VG_View *vv;
54 
55 	vv = Malloc(sizeof(VG_View));
56 	AG_ObjectInit(vv, &vgViewClass);
57 	vv->flags |= flags;
58 	vv->vg = vg;
59 
60 	if (flags & VG_VIEW_HFILL) { AG_ExpandHoriz(vv); }
61 	if (flags & VG_VIEW_VFILL) { AG_ExpandVert(vv); }
62 
63 	AG_ObjectAttach(parent, vv);
64 	return (vv);
65 }
66 
67 static void
MouseMotion(AG_Event * event)68 MouseMotion(AG_Event *event)
69 {
70 	VG_View *vv = AG_SELF();
71 	VG_Tool *tool = VG_CURTOOL(vv);
72 	int xCurs = AG_INT(1);
73 	int yCurs = AG_INT(2);
74 	float xRel = (float)AG_INT(3);
75 	float yRel = (float)AG_INT(4);
76 	int state = AG_INT(5);
77 	float x, y;
78 	VG_Vector vCt;
79 
80 	if (vv->mouse.panning) {
81 		vv->x += (float)xRel;
82 		vv->y += (float)yRel;
83 		AG_Redraw(vv);
84 		return;
85 	}
86 	if (vv->vg == NULL)
87 		return;
88 
89 	VG_GetVGCoords(vv, xCurs,yCurs, &vCt);
90 	x = vCt.x;
91 	y = vCt.y;
92 
93 	if (tool != NULL && tool->ops->mousemotion != NULL) {
94 		if (!(tool->ops->flags & VG_MOUSEMOTION_NOSNAP)) {
95 			VG_ApplyConstraints(vv, &vCt);
96 		}
97 		tool->vCursor = vCt;
98 		AG_Redraw(vv);
99 		if (tool->ops->mousemotion(tool, vCt,
100 		    VG_ScaleVector(1.0f/vv->scale,VGVECTOR(xRel,yRel)),
101 		    state) == 1) {
102 			vv->mouse.x = x;
103 			vv->mouse.y = y;
104 			return;
105 		}
106 	} else {
107 		vv->mouse.x = x;
108 		vv->mouse.y = y;
109 	}
110 }
111 
112 static void
MouseButtonDown(AG_Event * event)113 MouseButtonDown(AG_Event *event)
114 {
115 	VG_View *vv = AG_SELF();
116 	VG_Tool *tool = VG_CURTOOL(vv);
117 	int button = AG_INT(1);
118 	int xCurs = AG_INT(2);
119 	int yCurs = AG_INT(3);
120 	float x, y;
121 	VG_Vector vCt;
122 
123 	if (vv->vg == NULL ||
124 	    AG_ExecMouseAction(vv, AG_ACTION_ON_BUTTONDOWN, button, xCurs, yCurs) == 1)
125 		return;
126 
127 	VG_GetVGCoords(vv, xCurs,yCurs, &vCt);
128 	x = vCt.x;
129 	y = vCt.y;
130 
131 	if (!AG_WidgetIsFocused(vv)) {
132 		AG_WidgetFocus(vv);
133 	}
134 	vv->mouse.x = x;
135 	vv->mouse.y = y;
136 
137 	if (tool != NULL && tool->ops->mousebuttondown != NULL) {
138 		if (!(tool->ops->flags & VG_BUTTONDOWN_NOSNAP)) {
139 			VG_ApplyConstraints(vv, &vCt);
140 		}
141 		AG_Redraw(vv);
142 		if (tool->ops->mousebuttondown(tool, vCt, button) == 1)
143 			return;
144 	}
145 	if (vv->btndown_ev != NULL)
146 		AG_PostEventByPtr(NULL, vv, vv->btndown_ev, "%i,%f,%f",
147 		    button, x, y);
148 }
149 
150 static void
MouseButtonUp(AG_Event * event)151 MouseButtonUp(AG_Event *event)
152 {
153 	VG_View *vv = AG_SELF();
154 	VG_Tool *tool = VG_CURTOOL(vv);
155 	int button = AG_INT(1);
156 	int xCurs = AG_INT(2);
157 	int yCurs = AG_INT(3);
158 	float x, y;
159 	VG_Vector vCt;
160 
161 	if (vv->vg == NULL ||
162 	    AG_ExecMouseAction(vv, AG_ACTION_ON_BUTTONUP, button, xCurs, yCurs) == 1)
163 		return;
164 
165 	VG_GetVGCoords(vv, xCurs,yCurs, &vCt);
166 	x = vCt.x;
167 	y = vCt.y;
168 
169 	if (tool != NULL && tool->ops->mousebuttonup != NULL) {
170 		if (!(tool->ops->flags & VG_BUTTONUP_NOSNAP)) {
171 			VG_ApplyConstraints(vv, &vCt);
172 		}
173 		AG_Redraw(vv);
174 		if (tool->ops->mousebuttonup(tool, vCt, button) == 1)
175 			return;
176 	}
177 	if (vv->btnup_ev != NULL) {
178 		AG_PostEventByPtr(NULL, vv, vv->btnup_ev, "%i,%f,%f",
179 		    button, x, y);
180 		AG_Redraw(vv);
181 	}
182 }
183 
184 static void
KeyDown(AG_Event * event)185 KeyDown(AG_Event *event)
186 {
187 	VG_View *vv = AG_SELF();
188 	VG_Tool *tool = VG_CURTOOL(vv);
189 	int sym = AG_INT(1);
190 	int mod = AG_INT(2);
191 	Uint32 unicode = (Uint)AG_ULONG(3);
192 	VG_ToolCommand *cmd;
193 
194 	if (vv->vg == NULL)
195 		return;
196 	if (AG_ExecKeyAction(vv, AG_ACTION_ON_KEYDOWN, sym, mod))
197 		return;
198 
199 	if (tool == NULL) {
200 		return;
201 	}
202 	AG_Redraw(vv);
203 	if (tool->ops->keydown != NULL &&
204 	    tool->ops->keydown(tool, sym, mod, unicode) == 1) {
205 		return;
206 	}
207 	TAILQ_FOREACH(cmd, &tool->cmds, cmds) {
208 		if (cmd->kSym == sym &&
209 		    (cmd->kMod == AG_KEYMOD_NONE || mod & cmd->kMod)) {
210 			AG_PostEventByPtr(NULL, tool->vgv, cmd->fn, "%p", tool);
211 			AG_Redraw(vv);
212 		}
213 	}
214 }
215 
216 static void
KeyUp(AG_Event * event)217 KeyUp(AG_Event *event)
218 {
219 	VG_View *vv = AG_SELF();
220 	VG_Tool *tool = VG_CURTOOL(vv);
221 	int sym = AG_INT(1);
222 	int mod = AG_INT(2);
223 	Uint32 unicode = (Uint32)AG_ULONG(3);
224 
225 	if (vv->vg == NULL)
226 		return;
227 	if (AG_ExecKeyAction(vv, AG_ACTION_ON_KEYUP, sym, mod))
228 		return;
229 
230 	AG_Redraw(vv);
231 
232 	if (tool != NULL &&
233 	    tool->ops->keyup != NULL)
234 		tool->ops->keyup(tool, sym, mod, unicode);
235 }
236 
237 static void
OnShow(AG_Event * event)238 OnShow(AG_Event *event)
239 {
240 	VG_View *vv = AG_SELF();
241 
242 	vv->x = AGWIDGET(vv)->w/2.0f;
243 	vv->y = AGWIDGET(vv)->h/2.0f;
244 }
245 
246 static void
UpdateGridIntervals(VG_View * vv)247 UpdateGridIntervals(VG_View *vv)
248 {
249 	int i;
250 
251 	for (i = 0; i < vv->nGrids; i++) {
252 		VG_Grid *grid = &vv->grid[i];
253 
254 		grid->ivalView = (int)VG_Rint(((float)grid->ival)/vv->wPixel);
255 
256 		if (grid->ivalView < 6) {
257 			grid->flags |= VG_GRID_UNDERSIZE;
258 		} else {
259 			grid->flags &= ~(VG_GRID_UNDERSIZE);
260 		}
261 	}
262 }
263 
264 static void
ZoomInOut(AG_Event * event)265 ZoomInOut(AG_Event *event)
266 {
267 	VG_View *vv = AG_SELF();
268 	int inc = AG_INT(1);
269 
270 	VG_ViewSetScalePreset(vv, (vv->scaleIdx += inc));
271 	VG_Status(vv, _("Scale: 1:%.0f"), vv->scale);
272 }
273 
274 static void
ZoomInMax(AG_Event * event)275 ZoomInMax(AG_Event *event)
276 {
277 	VG_View *vv = AG_SELF();
278 
279 	VG_ViewSetScalePreset(vv, nScaleFactors-1);
280 	VG_Status(vv, _("Scale: 1:%.0f"), vv->scale);
281 }
282 
283 static void
ZoomOutMax(AG_Event * event)284 ZoomOutMax(AG_Event *event)
285 {
286 	VG_View *vv = AG_SELF();
287 
288 	VG_ViewSetScalePreset(vv, 0);
289 	VG_Status(vv, _("Scale: 1:%.0f"), vv->scale);
290 }
291 
292 static void
SetScale(AG_Event * event)293 SetScale(AG_Event *event)
294 {
295 	VG_View *vv = AG_SELF();
296 	int n = AG_INT(1);
297 
298 	VG_ViewSetScalePreset(vv, n);
299 	VG_Status(vv, _("Scale: 1:%.0f"), vv->scale);
300 }
301 
302 static void
Init(void * obj)303 Init(void *obj)
304 {
305 	VG_View *vv = obj;
306 
307 	WIDGET(vv)->flags |= AG_WIDGET_FOCUSABLE;
308 
309 	vv->flags = 0;
310 	vv->vg = NULL;
311 	vv->draw_ev = NULL;
312 	vv->scale_ev = NULL;
313 	vv->keydown_ev = NULL;
314 	vv->btndown_ev = NULL;
315 	vv->keyup_ev = NULL;
316 	vv->btnup_ev = NULL;
317 	vv->motion_ev = NULL;
318 	vv->x = 0.0f;
319 	vv->y = 0.0f;
320 	vv->scaleIdx = 0;
321 	vv->scale = scaleFactors[0];
322 	vv->scaleMin = 1.0f;
323 	vv->scaleMax = 1e6f;
324 	vv->wPixel = 1.0f;
325 	vv->snap_mode = VG_GRID;
326 	vv->ortho_mode = VG_NO_ORTHO;
327 	vv->mouse.x = 0.0f;
328 	vv->mouse.y = 0.0f;
329 	vv->mouse.panning = 0;
330 	vv->curtool = NULL;
331 	vv->deftool = NULL;
332 	vv->status[0] = '\0';
333 	vv->tCache = AG_TextCacheNew(vv, 64, 2);
334 	vv->editAreas = NULL;
335 	vv->nEditAreas = 0;
336 	vv->r = AG_RECT(0,0,0,0);
337 	TAILQ_INIT(&vv->tools);
338 
339 	vv->nGrids = 0;
340 	VG_ViewSetGrid(vv, 0, VG_GRID_POINTS, 8, VG_GetColorRGB(100,100,100));
341 	VG_ViewSetScale(vv, 0);
342 
343 	AG_AddEvent(vv, "widget-shown", OnShow, NULL);
344 	AG_SetEvent(vv, "mouse-motion", MouseMotion, NULL);
345 	AG_SetEvent(vv, "mouse-button-down", MouseButtonDown, NULL);
346 	AG_SetEvent(vv, "mouse-button-up", MouseButtonUp, NULL);
347 	AG_SetEvent(vv, "key-down", KeyDown, NULL);
348 	AG_SetEvent(vv, "key-up", KeyUp, NULL);
349 
350 	AG_ActionSetInt(vv, "Enable panning",	&vv->mouse.panning, 1);
351 	AG_ActionSetInt(vv, "Disable panning",	&vv->mouse.panning, 0);
352 	AG_ActionFn(vv, "Zoom in",		ZoomInOut, "%i", 1);
353 	AG_ActionFn(vv, "Zoom out",		ZoomInOut, "%i", -1);
354 	AG_ActionFn(vv, "Zoom in maximum",	ZoomInMax, NULL);
355 	AG_ActionFn(vv, "Zoom out maximum",	ZoomOutMax, NULL);
356 	AG_ActionFn(vv, "Scale 1:1",		SetScale, "%i", 0);
357 	AG_ActionFn(vv, "Scale 1:2",		SetScale, "%i", 1);
358 	AG_ActionFn(vv, "Scale 1:3",		SetScale, "%i", 2);
359 	AG_ActionFn(vv, "Scale 1:9",		SetScale, "%i", 8);
360 
361 	AG_ActionOnButtonDown(vv, AG_MOUSE_MIDDLE,		"Enable panning");
362 	AG_ActionOnButtonUp(vv,	  AG_MOUSE_MIDDLE,		"Disable panning");
363 	AG_ActionOnButton(vv,     AG_MOUSE_WHEELUP,		"Zoom in");
364 	AG_ActionOnButton(vv,     AG_MOUSE_WHEELDOWN,		"Zoom out");
365 	AG_ActionOnKeyDown(vv,    AG_KEY_1, AG_KEYMOD_ANY,	"Scale 1:1");
366 	AG_ActionOnKeyDown(vv,    AG_KEY_2, AG_KEYMOD_ANY,	"Scale 1:2");
367 	AG_ActionOnKeyDown(vv,    AG_KEY_3, AG_KEYMOD_ANY,	"Scale 1:3");
368 	AG_ActionOnKeyDown(vv,    AG_KEY_9, AG_KEYMOD_ANY,	"Scale 1:9");
369 
370 #ifdef AG_DEBUG
371 	AG_BindFloat(vv, "x", &vv->x);
372 	AG_BindFloat(vv, "y", &vv->y);
373 	AG_BindFloat(vv, "scale", &vv->scale);
374 	AG_BindFloat(vv, "wPixel", &vv->wPixel);
375 	AG_BindInt(vv, "pointSelRadius", &vv->pointSelRadius);
376 #endif /* AG_DEBUG */
377 }
378 
379 static void
Destroy(void * obj)380 Destroy(void *obj)
381 {
382 	VG_View *vv = obj;
383 
384 	AG_TextCacheDestroy(vv->tCache);
385 }
386 
387 /* Change the VG being displayed. */
388 void
VG_ViewSetVG(VG_View * vv,VG * vg)389 VG_ViewSetVG(VG_View *vv, VG *vg)
390 {
391 	AG_ObjectLock(vv);
392 	if (vv->vg != vg) {
393 		vv->vg = vg;
394 		VG_ViewSelectTool(vv, NULL, NULL);
395 	}
396 	AG_ObjectUnlock(vv);
397 	AG_Redraw(vv);
398 }
399 
400 /* Set the snapping constraint. */
401 void
VG_ViewSetSnapMode(VG_View * vv,enum vg_snap_mode mode)402 VG_ViewSetSnapMode(VG_View *vv, enum vg_snap_mode mode)
403 {
404 	AG_ObjectLock(vv);
405 	vv->snap_mode = mode;
406 	AG_ObjectUnlock(vv);
407 }
408 
409 /* Set the orthogonal constraint. */
410 void
VG_ViewSetOrthoMode(VG_View * vv,enum vg_ortho_mode mode)411 VG_ViewSetOrthoMode(VG_View *vv, enum vg_ortho_mode mode)
412 {
413 	AG_ObjectLock(vv);
414 	vv->ortho_mode = mode;
415 	AG_ObjectUnlock(vv);
416 }
417 
418 /* Set the parameters of the specified grid. */
419 void
VG_ViewSetGrid(VG_View * vv,int idx,enum vg_grid_type type,int ival,VG_Color color)420 VG_ViewSetGrid(VG_View *vv, int idx, enum vg_grid_type type, int ival,
421     VG_Color color)
422 {
423 	if (idx+1 > VG_GRIDS_MAX)
424 		AG_FatalError("Too many grids");
425 
426 	AG_ObjectLock(vv);
427 	vv->grid[idx].type = type;
428 	vv->grid[idx].ival = ival;
429 	vv->grid[idx].ivalView = 0;
430 	vv->grid[idx].color = color;
431 	vv->grid[idx].flags = 0;
432 	if (idx == 0) {
433 		vv->pointSelRadius = vv->grid[0].ival/2;
434 	}
435 	if (idx >= vv->nGrids) {
436 		vv->nGrids = idx+1;
437 	}
438 	UpdateGridIntervals(vv);
439 	AG_ObjectUnlock(vv);
440 	AG_Redraw(vv);
441 }
442 
443 /* Register a "draw" callback. */
444 void
VG_ViewDrawFn(VG_View * vv,AG_EventFn fn,const char * fmt,...)445 VG_ViewDrawFn(VG_View *vv, AG_EventFn fn, const char *fmt, ...)
446 {
447 	AG_ObjectLock(vv);
448 	vv->draw_ev = AG_SetVoidFn(vv, fn, NULL);
449 	AG_EVENT_GET_ARGS(vv->draw_ev, fmt);
450 	AG_ObjectUnlock(vv);
451 }
452 
453 /* Register a "resize" callback. */
454 void
VG_ViewScaleFn(VG_View * vv,AG_EventFn fn,const char * fmt,...)455 VG_ViewScaleFn(VG_View *vv, AG_EventFn fn, const char *fmt, ...)
456 {
457 	AG_ObjectLock(vv);
458 	vv->scale_ev = AG_SetVoidFn(vv, fn, NULL);
459 	AG_EVENT_GET_ARGS(vv->scale_ev, fmt);
460 	AG_ObjectUnlock(vv);
461 }
462 
463 /* Register a keydown callback. */
464 void
VG_ViewKeydownFn(VG_View * vv,AG_EventFn fn,const char * fmt,...)465 VG_ViewKeydownFn(VG_View *vv, AG_EventFn fn, const char *fmt, ...)
466 {
467 	AG_ObjectLock(vv);
468 	vv->keydown_ev = AG_SetEvent(vv, "key-down", fn, NULL);
469 	AG_EVENT_GET_ARGS(vv->keydown_ev, fmt);
470 	AG_ObjectUnlock(vv);
471 }
472 
473 /* Register a keyup callback. */
474 void
VG_ViewKeyupFn(VG_View * vv,AG_EventFn fn,const char * fmt,...)475 VG_ViewKeyupFn(VG_View *vv, AG_EventFn fn, const char *fmt, ...)
476 {
477 	AG_ObjectLock(vv);
478 	vv->keyup_ev = AG_SetEvent(vv, "key-up", fn, NULL);
479 	AG_EVENT_GET_ARGS(vv->keyup_ev, fmt);
480 	AG_ObjectUnlock(vv);
481 }
482 
483 /* Register a mousebuttondown callback. */
484 void
VG_ViewButtondownFn(VG_View * vv,AG_EventFn fn,const char * fmt,...)485 VG_ViewButtondownFn(VG_View *vv, AG_EventFn fn, const char *fmt, ...)
486 {
487 	AG_ObjectLock(vv);
488 	vv->btndown_ev = AG_SetVoidFn(vv, fn, NULL);
489 	AG_EVENT_GET_ARGS(vv->btndown_ev, fmt);
490 	AG_ObjectUnlock(vv);
491 }
492 
493 /* Register a mousebuttonup callback. */
494 void
VG_ViewButtonupFn(VG_View * vv,AG_EventFn fn,const char * fmt,...)495 VG_ViewButtonupFn(VG_View *vv, AG_EventFn fn, const char *fmt, ...)
496 {
497 	AG_ObjectLock(vv);
498 	vv->btnup_ev = AG_SetEvent(vv, "mouse-button-up", fn, NULL);
499 	AG_EVENT_GET_ARGS(vv->btnup_ev, fmt);
500 	AG_ObjectUnlock(vv);
501 }
502 
503 /* Register a mousemotion callback. */
504 void
VG_ViewMotionFn(VG_View * vv,AG_EventFn fn,const char * fmt,...)505 VG_ViewMotionFn(VG_View *vv, AG_EventFn fn, const char *fmt, ...)
506 {
507 	AG_ObjectLock(vv);
508 	vv->motion_ev = AG_SetEvent(vv, "mouse-motion", fn, NULL);
509 	AG_EVENT_GET_ARGS(vv->motion_ev, fmt);
510 	AG_ObjectUnlock(vv);
511 }
512 
513 static void
SizeRequest(void * obj,AG_SizeReq * r)514 SizeRequest(void *obj, AG_SizeReq *r)
515 {
516 	r->w = 320;
517 	r->h = 240;
518 }
519 
520 static int
SizeAllocate(void * obj,const AG_SizeAlloc * a)521 SizeAllocate(void *obj, const AG_SizeAlloc *a)
522 {
523 	VG_View *vv = obj;
524 
525 	if (a->w < 16 || a->h < 16) {
526 		return (-1);
527 	}
528 	vv->r.w = a->w;
529 	vv->r.h = a->h;
530 	return (0);
531 }
532 
533 static __inline__ void
DrawGrid(VG_View * vv,const VG_Grid * grid)534 DrawGrid(VG_View *vv, const VG_Grid *grid)
535 {
536 	int x, x0, y, ival;
537 
538 	if (grid->flags & (VG_GRID_HIDE|VG_GRID_UNDERSIZE))
539 		return;
540 
541 	ival = grid->ivalView;
542 #ifdef HAVE_OPENGL
543 	if (AGDRIVER_CLASS(WIDGET(vv)->drv)->flags & AG_DRIVER_OPENGL) {
544 		x0 = WIDGET(vv)->rView.x1 + (int)(vv->x)%ival;
545 		y = WIDGET(vv)->rView.y1 + (int)(vv->y)%ival;
546 		glBegin(GL_POINTS);
547 		glColor3ub(grid->color.r, grid->color.g, grid->color.b);
548 		for (; y < WIDGET(vv)->rView.y2; y += ival) {
549 			for (x = x0; x < WIDGET(vv)->rView.x2; x += ival)
550 				glVertex2s(x, y);
551 		}
552 		glEnd();
553 	} else
554 #endif
555 	{
556 		AG_Color c;
557 
558 		x0 = (int)(vv->x)%ival;
559 		y = (int)(vv->y)%ival;
560 		c = VG_MapColorRGB(grid->color);
561 		for (; y < WIDGET(vv)->rView.y2; y += ival) {
562 			for (x = x0; x < WIDGET(vv)->rView.x2; x += ival)
563 				AG_PutPixel(vv, x,y, c);
564 		}
565 	}
566 }
567 
568 #ifdef AG_DEBUG
569 static void
DrawNodeExtent(VG_Node * vn,VG_View * vv)570 DrawNodeExtent(VG_Node *vn, VG_View *vv)
571 {
572 	AG_Rect rExt;
573 	VG_Vector a, b;
574 
575 	if (vn->ops->extent == NULL) {
576 		return;
577 	}
578 	vn->ops->extent(vn, vv, &a, &b);
579 
580 	VG_GetViewCoords(vv, a, &rExt.x, &rExt.y);
581 	rExt.w = (int)((b.x - a.x)*vv->scale);
582 	rExt.h = (int)((b.y - a.y)*vv->scale);
583 	AG_DrawRectOutline(vv, rExt, AG_ColorRGB(250,0,0));
584 }
585 #endif /* AG_DEBUG */
586 
587 static void
DrawNode(VG * vg,VG_Node * vn,VG_View * vv)588 DrawNode(VG *vg, VG_Node *vn, VG_View *vv)
589 {
590 	VG_Node *vnChld;
591 	VG_Color colorSave;
592 
593 	VG_PushMatrix(vg);
594 	VG_MultMatrix(&vg->T[vg->nT-1], &vn->T);
595 
596 	VG_FOREACH_CHLD(vnChld, vn, vg_node)
597 		DrawNode(vg, vnChld, vv);
598 #ifdef AG_DEBUG
599 	if (vv->flags & VG_VIEW_EXTENTS)
600 		DrawNodeExtent(vn, vv);
601 #endif
602 	colorSave = vn->color;
603 	if (vn->flags & VG_NODE_SELECTED) {
604 		VG_BlendColors(&vn->color, vg->selectionColor);
605 	}
606 	if (vn->flags & VG_NODE_MOUSEOVER) {
607 		VG_BlendColors(&vn->color, vg->mouseoverColor);
608 	}
609 	vn->ops->draw(vn, vv);
610 	vn->color = colorSave;
611 
612 	VG_PopMatrix(vg);
613 }
614 
615 static void
Draw(void * obj)616 Draw(void *obj)
617 {
618 	VG_View *vv = obj;
619 	VG *vg = vv->vg;
620 	int su, i;
621 
622 	if (vg == NULL)
623 		return;
624 
625 	if (!(vv->flags & VG_VIEW_DISABLE_BG))
626 		AG_DrawRect(vv, vv->r, VG_MapColorRGBA(vg->fillColor));
627 
628 	AG_PushClipRect(vv, vv->r);
629 
630 	if (vv->flags & VG_VIEW_GRID)
631 		for (i = 0; i < vv->nGrids; i++)
632 			DrawGrid(vv, &vv->grid[i]);
633 
634 	VG_Lock(vg);
635 
636 	if (vv->curtool != NULL && vv->curtool->ops->predraw != NULL) {
637 		vv->curtool->ops->predraw(vv->curtool, vv);
638 	}
639 	if (vv->draw_ev != NULL) {
640 		vv->draw_ev->fn.fnVoid(vv->draw_ev);
641 	}
642 	if (vv->curtool != NULL && vv->curtool->ops->postdraw != NULL) {
643 		vv->curtool->ops->postdraw(vv->curtool, vv);
644 	}
645 
646 	DrawNode(vg, vg->root, vv);
647 	VG_Unlock(vg);
648 
649 	if (vv->status[0] != '\0') {
650 		AG_PushTextState();
651 		AG_TextColor(WCOLOR(vg,TEXT_COLOR));
652 		if ((su = AG_TextCacheGet(vv->tCache, vv->status)) != -1) {
653 			AG_WidgetBlitSurface(vv, su,
654 			    0,
655 			    HEIGHT(vv) - WSURFACE(vv,su)->h);
656 		}
657 		AG_PopTextState();
658 	}
659 	AG_PopClipRect(vv);
660 }
661 
662 /* Select a new tool to use. */
663 void
VG_ViewSelectTool(VG_View * vv,void * pTool,void * p)664 VG_ViewSelectTool(VG_View *vv, void *pTool, void *p)
665 {
666 	VG_Tool *ntool = pTool;
667 	int i;
668 
669 	AG_ObjectLock(vv);
670 
671 	if (ntool == NULL || !(ntool->ops->flags & VG_NOEDITCLEAR)) {
672 		for (i = 0; i < vv->nEditAreas; i++) {
673 			AG_ObjectFreeChildren(vv->editAreas[i]);
674 			AG_WidgetUpdate(vv->editAreas[i]);
675 		}
676 	}
677 	if (vv->curtool != NULL) {
678 		if (ntool != NULL && ntool->ops == vv->curtool->ops) {
679 			goto out;
680 		}
681 		if (vv->curtool->editWin != NULL) {
682 			AG_WindowHide(vv->curtool->editWin);
683 		}
684 		if (vv->curtool->ops->deselected != NULL) {
685 			vv->curtool->ops->deselected(ntool, vv);
686 		}
687 		vv->curtool->selected = 0;
688 	}
689 
690 	vv->curtool = ntool;
691 	if (ntool != NULL) {
692 		ntool->selected = 1;
693 		ntool->p = p;
694 		ntool->vgv = vv;
695 
696 		if (ntool->editWin != NULL) {
697 			AG_WindowShow(ntool->editWin);
698 		}
699 		if (ntool->ops->edit != NULL && vv->nEditAreas > 0) {
700 			AG_ObjectAttach(vv->editAreas[0],
701 			    ntool->ops->edit(ntool,vv));
702 			AG_WidgetUpdate(vv->editAreas[0]);
703 		}
704 
705 		VG_Status(vv, _("Tool: %s"), ntool->ops->name);
706 		if (ntool->ops->selected != NULL)
707 			ntool->ops->selected(ntool, vv);
708 	} else {
709 		VG_StatusS(vv, NULL);
710 	}
711 out:
712 	AG_ObjectUnlock(vv);
713 	AG_Redraw(vv);
714 }
715 
716 /* Generic event handler for tool selection. */
717 void
VG_ViewSelectToolEv(AG_Event * event)718 VG_ViewSelectToolEv(AG_Event *event)
719 {
720 	VG_View *vv = AG_PTR(1);
721 
722 	if (!AG_WidgetIsFocused(vv)) {
723 		AG_WidgetFocus(vv);
724 	}
725 	VG_ViewSelectTool(vv, AG_PTR(2), AG_PTR(3));
726 }
727 
728 /* VG_View must be locked */
729 VG_Tool *
VG_ViewFindTool(VG_View * vv,const char * name)730 VG_ViewFindTool(VG_View *vv, const char *name)
731 {
732 	VG_Tool *tool;
733 
734 	TAILQ_FOREACH(tool, &vv->tools, tools) {
735 		if (strcmp(tool->ops->name, name) == 0)
736 			return (tool);
737 	}
738 	AG_SetError("No such tool: %s", name);
739 	return (NULL);
740 }
741 
742 /* VG_View must be locked */
743 VG_Tool *
VG_ViewFindToolByOps(VG_View * vv,const VG_ToolOps * ops)744 VG_ViewFindToolByOps(VG_View *vv, const VG_ToolOps *ops)
745 {
746 	VG_Tool *tool;
747 
748 	TAILQ_FOREACH(tool, &vv->tools, tools) {
749 		if (tool->ops == ops)
750 			return (tool);
751 	}
752 	AG_SetError("No such tool: %p", ops);
753 	return (NULL);
754 }
755 
756 /* Register a new VG_Tool class. */
757 VG_Tool *
VG_ViewRegTool(VG_View * vv,const VG_ToolOps * ops,void * p)758 VG_ViewRegTool(VG_View *vv, const VG_ToolOps *ops, void *p)
759 {
760 	VG_Tool *t;
761 
762 	t = Malloc(ops->len);
763 	t->ops = ops;
764 	t->vgv = vv;
765 	t->p = p;
766 
767 	AG_ObjectLock(vv);
768 	VG_ToolInit(t);
769 	TAILQ_INSERT_TAIL(&vv->tools, t, tools);
770 	AG_ObjectUnlock(vv);
771 	return (t);
772 }
773 
774 /* Set the minimum allowed display scale factor. */
775 void
VG_ViewSetScaleMin(VG_View * vv,float scaleMin)776 VG_ViewSetScaleMin(VG_View *vv, float scaleMin)
777 {
778 	AG_ObjectLock(vv);
779 	vv->scaleMin = MAX(scaleMin,1e-6f);
780 	AG_ObjectUnlock(vv);
781 }
782 
783 /* Set the maximum allowed display scale factor. */
784 void
VG_ViewSetScaleMax(VG_View * vv,float scaleMax)785 VG_ViewSetScaleMax(VG_View *vv, float scaleMax)
786 {
787 	AG_ObjectLock(vv);
788 	vv->scaleMax = scaleMax;
789 	AG_ObjectUnlock(vv);
790 }
791 
792 /* Set the display scale factor explicitely. */
793 void
VG_ViewSetScale(VG_View * vv,float c)794 VG_ViewSetScale(VG_View *vv, float c)
795 {
796 	float scalePrev;
797 
798 	AG_ObjectLock(vv);
799 	scalePrev = vv->scale;
800 
801 	/* Set the specified scaling factor. */
802 	/* vv->scaleIdx = idx; XXX */
803 	vv->scale = c;
804 	if (vv->scale < vv->scaleMin) { vv->scale = vv->scaleMin; }
805 	if (vv->scale > vv->scaleMax) { vv->scale = vv->scaleMax; }
806 
807 	/* Update all values dependent on VG's representation of a pixel. */
808 	vv->wPixel = 1.0/vv->scale;
809 	vv->x *= (vv->scale/scalePrev);
810 	vv->y *= (vv->scale/scalePrev);
811 	vv->pointSelRadius = vv->grid[0].ival/2;
812 	UpdateGridIntervals(vv);
813 
814 	AG_ObjectUnlock(vv);
815 	AG_Redraw(vv);
816 }
817 
818 /* Set the display scale factor by preset index. */
819 void
VG_ViewSetScalePreset(VG_View * vv,int idx)820 VG_ViewSetScalePreset(VG_View *vv, int idx)
821 {
822 	if (idx < 0) { idx = 0; }
823 	else if (idx >= nScaleFactors) { idx = nScaleFactors-1; }
824 	VG_ViewSetScale(vv, scaleFactors[idx]);
825 }
826 
827 /* Set the specified tool as default. */
828 void
VG_ViewSetDefaultTool(VG_View * vv,VG_Tool * tool)829 VG_ViewSetDefaultTool(VG_View *vv, VG_Tool *tool)
830 {
831 	AG_ObjectLock(vv);
832 	vv->deftool = tool;
833 	AG_ObjectUnlock(vv);
834 }
835 
836 /* Set the status line text (C string). */
837 void
VG_StatusS(VG_View * vv,const char * s)838 VG_StatusS(VG_View *vv, const char *s)
839 {
840 	AG_ObjectLock(vv);
841 	if (s != NULL) {
842 		Strlcpy(vv->status, s, sizeof(vv->status));
843 	} else {
844 		vv->status[0] = '\0';
845 	}
846 	AG_ObjectUnlock(vv);
847 	AG_Redraw(vv);
848 }
849 
850 /* Set the status line text (format string). */
851 void
VG_Status(VG_View * vv,const char * fmt,...)852 VG_Status(VG_View *vv, const char *fmt, ...)
853 {
854 	va_list ap;
855 
856 	AG_ObjectLock(vv);
857 	if (fmt != NULL) {
858 		va_start(ap, fmt);
859 		Vsnprintf(vv->status, sizeof(vv->status), fmt, ap);
860 		va_end(ap);
861 	} else {
862 		vv->status[0] = '\0';
863 	}
864 	AG_ObjectUnlock(vv);
865 	AG_Redraw(vv);
866 }
867 
868 /* Register a new container widget to associate with the tool. */
869 Uint
VG_AddEditArea(VG_View * vv,void * widget)870 VG_AddEditArea(VG_View *vv, void *widget)
871 {
872 	Uint name;
873 
874 	AG_ObjectLock(vv);
875 	vv->editAreas = Realloc(vv->editAreas, (vv->nEditAreas+1) *
876 	                                       sizeof(AG_Widget *));
877 	name = vv->nEditAreas++;
878 	vv->editAreas[name] = widget;
879 	AG_ObjectUnlock(vv);
880 	return (name);
881 }
882 
883 /* Destroy widgets attached to all edit areas. */
884 void
VG_ClearEditAreas(VG_View * vv)885 VG_ClearEditAreas(VG_View *vv)
886 {
887 	Uint i;
888 
889 	AG_ObjectLock(vv);
890 	for (i = 0; i < vv->nEditAreas; i++) {
891 		AG_Widget *editArea = vv->editAreas[i];
892 
893 		AG_ObjectFreeChildren(editArea);
894 		AG_WidgetUpdate(editArea);
895 		AG_WidgetHideAll(editArea);
896 	}
897 	AG_ObjectUnlock(vv);
898 }
899 
900 /* Create GUI elements for editing the parameters of vn. */
901 void
VG_EditNode(VG_View * vv,Uint editArea,VG_Node * vn)902 VG_EditNode(VG_View *vv, Uint editArea, VG_Node *vn)
903 {
904 	void *wEdit;
905 
906 	if (vv->nEditAreas > editArea &&
907 	    vn->ops->edit != NULL &&
908 	    (wEdit = vn->ops->edit(vn, vv)) != NULL) {
909 		AG_ObjectFreeChildren(vv->editAreas[editArea]);
910 		AG_ObjectAttach(vv->editAreas[editArea], wEdit);
911 		AG_WidgetUpdate(vv->editAreas[editArea]);
912 		AG_WidgetShowAll(vv->editAreas[editArea]);
913 	}
914 }
915 
916 /* Render a mapped surface at the specified coordinates and rotation. */
917 void
VG_DrawSurface(VG_View * vv,int x,int y,float degs,int su)918 VG_DrawSurface(VG_View *vv, int x, int y, float degs, int su)
919 {
920 #ifdef HAVE_OPENGL
921 	if (AGDRIVER_CLASS(WIDGET(vv)->drv)->flags & AG_DRIVER_OPENGL) {
922 		glPushMatrix();
923 		glTranslatef((float)(AGWIDGET(vv)->rView.x1 + x),
924 		             (float)(AGWIDGET(vv)->rView.y1 + y),
925 			     0.0f);
926 		if (degs != 0.0f) {
927 			glRotatef(degs, 0.0f, 0.0f, 1.0f);
928 		}
929 		AG_WidgetBlitSurfaceGL(vv, su,
930 		    WSURFACE(vv,su)->w,
931 		    WSURFACE(vv,su)->h);
932 		glPopMatrix();
933 	} else
934 #endif /* HAVE_OPENGL */
935 	{
936 		AG_WidgetBlitSurface(vv, su, x, y);
937 	}
938 }
939 
940 AG_WidgetClass vgViewClass = {
941 	{
942 		"Agar(Widget):VG(View)",
943 		sizeof(VG_View),
944 		{ 0,0 },
945 		Init,
946 		NULL,		/* free */
947 		Destroy,
948 		NULL,		/* load */
949 		NULL,		/* save */
950 		NULL		/* edit */
951 	},
952 	Draw,
953 	SizeRequest,
954 	SizeAllocate
955 };
956