]> git.cworth.org Git - grrobot/blob - src/grr_board_view.c
0903ff1e95597ee85980b018c8c5adea5afe6878
[grrobot] / src / grr_board_view.c
1 /* grr_board_view - GTK+ widget for displaying an rr_board
2  *
3  * Copyright © 2003 Carl Worth
4  *
5  * Permission to use, copy, modify, distribute, and sell this software
6  * and its documentation for any purpose is hereby granted without
7  * fee, provided that the above copyright notice appear in all copies
8  * and that both that copyright notice and this permission notice
9  * appear in supporting documentation, and that the name of Carl Worth
10  * not be used in advertising or publicity pertaining to distribution
11  * of the software without specific, written prior permission.
12  * Carl Worth makes no representations about the suitability of this
13  * software for any purpose.  It is provided "as is" without express
14  * or implied warranty.
15  * 
16  * CARL WORTH DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
18  * NO EVENT SHALL CARL WORTH BE LIABLE FOR ANY SPECIAL, INDIRECT OR
19  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
20  * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
21  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
22  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23  *
24  * Author: Carl Worth <carl@theworths.org>
25  */
26
27 #include <math.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <stdarg.h>
32
33 #include <Xr.h>
34 #include <xsvg.h>
35
36 #include <gtk/gtkmain.h>
37 #include <gtk/gtksignal.h>
38 #include <gdk/gdkx.h>
39
40 #include "grr_board_view.h"
41
42 #define SCROLL_DELAY_LENGTH  300
43 #define GRR_BOARD_VIEW_DEFAULT_SIZE 100
44
45 #define GRR_SVG_ASSUMED_WIDTH 32.0
46 #define GRR_SVG_ASSUMED_HEIGHT 32.0
47
48 #define GRR_CELL_SVG "svg/cell.svg"
49 #define GRR_WALL_SVG "svg/wall.svg"
50
51 #define GRR_ROBOT_BLUE_SVG "svg/robot_blue.svg"
52 #define GRR_ROBOT_GREEN_SVG "svg/robot_green.svg"
53 #define GRR_ROBOT_RED_SVG "svg/robot_red.svg"
54 #define GRR_ROBOT_YELLOW_SVG "svg/robot_yellow.svg"
55
56 #define GRR_TARGET_BLUE_CIRCLE_SVG "svg/target_blue_circle.svg"
57 #define GRR_TARGET_BLUE_OCTAGON_SVG "svg/target_blue_octagon.svg"
58 #define GRR_TARGET_BLUE_SQUARE_SVG "svg/target_blue_square.svg"
59 #define GRR_TARGET_BLUE_TRIANGLE_SVG "svg/target_blue_triangle.svg"
60
61 #define GRR_TARGET_GREEN_CIRCLE_SVG "svg/target_green_circle.svg"
62 #define GRR_TARGET_GREEN_OCTAGON_SVG "svg/target_green_octagon.svg"
63 #define GRR_TARGET_GREEN_SQUARE_SVG "svg/target_green_square.svg"
64 #define GRR_TARGET_GREEN_TRIANGLE_SVG "svg/target_green_triangle.svg"
65
66 #define GRR_TARGET_RED_CIRCLE_SVG "svg/target_red_circle.svg"
67 #define GRR_TARGET_RED_OCTAGON_SVG "svg/target_red_octagon.svg"
68 #define GRR_TARGET_RED_SQUARE_SVG "svg/target_red_square.svg"
69 #define GRR_TARGET_RED_TRIANGLE_SVG "svg/target_red_triangle.svg"
70
71 #define GRR_TARGET_YELLOW_CIRCLE_SVG "svg/target_yellow_circle.svg"
72 #define GRR_TARGET_YELLOW_OCTAGON_SVG "svg/target_yellow_octagon.svg"
73 #define GRR_TARGET_YELLOW_SQUARE_SVG "svg/target_yellow_square.svg"
74 #define GRR_TARGET_YELLOW_TRIANGLE_SVG "svg/target_yellow_triangle.svg"
75
76 #define GRR_TARGET_WHIRL_SVG "svg/target_whirl.svg"
77
78
79 /* Forward declarations */
80
81 static int
82 grr_sprintf_alloc (char **str, const char *fmt, ...);
83
84 static int
85 grr_sprintf_alloc_va (char **str, const char *fmt, va_list ap);
86
87
88 static void grr_board_view_class_init               (grr_board_view_class_t    *klass);
89 static void grr_board_view_init                     (grr_board_view_t         *view);
90 static void grr_board_view_destroy                  (GtkObject        *object);
91 static void grr_board_view_realize                  (GtkWidget        *widget);
92 static void grr_board_view_size_request             (GtkWidget      *widget,
93                                                      GtkRequisition *requisition);
94 static void grr_board_view_size_allocate            (GtkWidget     *widget,
95                                                      GtkAllocation *allocation);
96 static gint grr_board_view_expose                   (GtkWidget        *widget,
97                                                      GdkEventExpose   *event);
98 static gint grr_board_view_button_press             (GtkWidget        *widget,
99                                                      GdkEventButton   *event);
100 static gint grr_board_view_button_release           (GtkWidget        *widget,
101                                                      GdkEventButton   *event);
102 static gint grr_board_view_motion_notify            (GtkWidget        *widget,
103                                                      GdkEventMotion   *event);
104
105 static void grr_board_view_update_mouse             (grr_board_view_t *view, gint x, gint y);
106 static void grr_board_view_update                   (grr_board_view_t *view);
107
108 /* Local data */
109
110 static GtkWidgetClass *parent_class = NULL;
111
112 GType
113 grr_board_view_get_type ()
114 {
115   static GType view_type = 0;
116
117   if (!view_type)
118     {
119       static const GTypeInfo view_info =
120       {
121         sizeof (grr_board_view_class_t),
122         NULL,
123         NULL,
124         (GClassInitFunc) grr_board_view_class_init,
125         NULL,
126         NULL,
127         sizeof (grr_board_view_t),
128         0,
129         (GInstanceInitFunc) grr_board_view_init,
130       };
131
132       view_type = g_type_register_static (GTK_TYPE_WIDGET, "grr_board_view", &view_info, 0);
133     }
134
135   return view_type;
136 }
137
138 static void
139 grr_board_view_class_init (grr_board_view_class_t *class)
140 {
141   GtkObjectClass *object_class;
142   GtkWidgetClass *widget_class;
143
144   object_class = (GtkObjectClass*) class;
145   widget_class = (GtkWidgetClass*) class;
146
147   parent_class = gtk_type_class (gtk_widget_get_type ());
148
149   object_class->destroy = grr_board_view_destroy;
150
151   widget_class->realize = grr_board_view_realize;
152   widget_class->expose_event = grr_board_view_expose;
153   widget_class->size_request = grr_board_view_size_request;
154   widget_class->size_allocate = grr_board_view_size_allocate;
155   widget_class->button_press_event = grr_board_view_button_press;
156   widget_class->button_release_event = grr_board_view_button_release;
157   widget_class->motion_notify_event = grr_board_view_motion_notify;
158 }
159
160 static void
161 grr_board_view_init (grr_board_view_t *view)
162 {
163     view->board = NULL;
164     view->owns_board = 0;
165     view->client = NULL;
166     
167     view->button = 0;
168     view->timer = 0;
169 }
170
171 GtkWidget*
172 grr_board_view_new (rr_board_t *board)
173 {
174     grr_board_view_t *view;
175     
176     view = g_object_new (grr_board_view_get_type (), NULL);
177
178     if (board == NULL) {
179         board = rr_board_create (16, 16);
180         view->owns_board = 1;
181     }
182
183     grr_board_view_set_board (view, board);
184
185     return GTK_WIDGET (view);
186 }
187
188 static void
189 grr_board_view_destroy (GtkObject *object)
190 {
191     grr_board_view_t *view;
192
193     g_return_if_fail (object != NULL);
194     g_return_if_fail (GRR_IS_BOARD_VIEW (object));
195
196     view = GRR_BOARD_VIEW (object);
197
198     rr_board_destroy (view->board);
199
200     if (view->board && view->owns_board) {
201         rr_board_destroy (view->board);
202         view->board = NULL;
203     }
204     
205     if (GTK_OBJECT_CLASS (parent_class)->destroy)
206         (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
207 }
208
209 void
210 grr_board_view_set_board (grr_board_view_t *view,
211                           rr_board_t *board)
212 {
213     g_return_if_fail (view != NULL);
214
215     view->board = board;
216
217     grr_board_view_update (view);
218 }
219
220 void
221 grr_board_view_set_client (grr_board_view_t *view,
222                            rr_client_t *client)
223 {
224     g_return_if_fail (view != NULL);
225
226     view->client = client;
227 }
228
229 static void
230 grr_board_view_realize (GtkWidget *widget)
231 {
232     grr_board_view_t *view;
233     GdkWindowAttr attributes;
234     gint attributes_mask;
235
236     g_return_if_fail (widget != NULL);
237     g_return_if_fail (GRR_IS_BOARD_VIEW (widget));
238   
239     GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
240     view = GRR_BOARD_VIEW (widget);
241
242     attributes.x = widget->allocation.x;
243     attributes.y = widget->allocation.y;
244     attributes.width = widget->allocation.width;
245     attributes.height = widget->allocation.height;
246     attributes.wclass = GDK_INPUT_OUTPUT;
247     attributes.window_type = GDK_WINDOW_CHILD;
248     attributes.event_mask = gtk_widget_get_events (widget) | 
249         GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | 
250         GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
251         GDK_POINTER_MOTION_HINT_MASK;
252     attributes.visual = gtk_widget_get_visual (widget);
253     attributes.colormap = gtk_widget_get_colormap (widget);
254     
255     attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
256     widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
257     
258     widget->style = gtk_style_attach (widget->style, widget->window);
259     
260     gdk_window_set_user_data (widget->window, widget);
261
262     gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
263 }
264
265 static void 
266 grr_board_view_size_request (GtkWidget      *widget,
267                        GtkRequisition *requisition)
268 {
269     requisition->width = GRR_BOARD_VIEW_DEFAULT_SIZE;
270     requisition->height = GRR_BOARD_VIEW_DEFAULT_SIZE;
271 }
272
273 static void
274 grr_board_view_size_allocate (GtkWidget     *widget,
275                         GtkAllocation *allocation)
276 {
277     grr_board_view_t *view;
278
279     g_return_if_fail (widget != NULL);
280     g_return_if_fail (GRR_IS_BOARD_VIEW (widget));
281     g_return_if_fail (allocation != NULL);
282
283     widget->allocation = *allocation;
284     view = GRR_BOARD_VIEW (widget);
285
286     if (GTK_WIDGET_REALIZED (widget)) {
287         gdk_window_move_resize (widget->window,
288                                 allocation->x, allocation->y,
289                                 allocation->width, allocation->height);
290     }
291 }
292
293 static void
294 grr_svg_draw (XrState *xrs, char *svg)
295 {
296     xsvg_status_t status;
297     xsvg_t *xsvg;
298
299     status = xsvg_create (&xsvg);
300     if (status) {
301         fprintf (stderr, "xsvg_create error\n");
302         return;
303     }
304     status = xsvg_parse_file (xsvg, svg);
305     if (status) {
306         fprintf (stderr, "xsvg_parse_file error parsing %s\n", svg);
307         return;
308     }
309     xsvg_render (xsvg, xrs);
310     if (status) {
311         fprintf (stderr, "xsvg_render error\n");
312         return;
313     }
314     xsvg_destroy (xsvg);
315 }
316
317 static void
318 grr_wall_draw (XrState *xrs, rr_wall_t wall)
319 {
320     char *wall_svg = GRR_WALL_SVG;
321
322     if (wall == RR_WALL_NONE)
323         return;
324
325     if (wall & RR_WALL_ABOVE) {
326         grr_svg_draw (xrs, wall_svg);
327     }
328     if (wall & RR_WALL_LEFT) {
329         XrSave (xrs);
330         XrRotate (xrs, M_PI_2);
331         grr_svg_draw (xrs, wall_svg);
332         XrRestore (xrs);
333     }
334     if (wall & RR_WALL_RIGHT) {
335         XrSave (xrs);
336         XrTranslate (xrs, GRR_SVG_ASSUMED_WIDTH, 0);
337         XrRotate (xrs, M_PI_2);
338         grr_svg_draw (xrs, wall_svg);
339         XrRestore (xrs);
340     }
341     if (wall & RR_WALL_BELOW) {
342         XrSave (xrs);
343         XrTranslate (xrs, 0, GRR_SVG_ASSUMED_HEIGHT);
344         grr_svg_draw (xrs, wall_svg);
345         XrRestore (xrs);
346     }
347 }
348
349 static void
350 grr_target_draw (XrState *xrs, rr_target_t target)
351 {
352     char *target_svg;
353
354     if (target == RR_TARGET_NONE)
355         return;
356
357     switch (target) {
358     case RR_TARGET_BLUE_CIRCLE:
359         target_svg = GRR_TARGET_BLUE_CIRCLE_SVG;
360         break;
361     case RR_TARGET_BLUE_OCTAGON:
362         target_svg = GRR_TARGET_BLUE_OCTAGON_SVG;
363         break;
364     case RR_TARGET_BLUE_SQUARE:
365         target_svg = GRR_TARGET_BLUE_SQUARE_SVG;
366         break;
367     case RR_TARGET_BLUE_TRIANGLE:
368         target_svg = GRR_TARGET_BLUE_TRIANGLE_SVG;
369         break;
370     case RR_TARGET_GREEN_CIRCLE:
371         target_svg = GRR_TARGET_GREEN_CIRCLE_SVG;
372         break;
373     case RR_TARGET_GREEN_OCTAGON:
374         target_svg = GRR_TARGET_GREEN_OCTAGON_SVG;
375         break;
376     case RR_TARGET_GREEN_SQUARE:
377         target_svg = GRR_TARGET_GREEN_SQUARE_SVG;
378         break;
379     case RR_TARGET_GREEN_TRIANGLE:
380         target_svg = GRR_TARGET_GREEN_TRIANGLE_SVG;
381         break;
382     case RR_TARGET_RED_CIRCLE:
383         target_svg = GRR_TARGET_RED_CIRCLE_SVG;
384         break;
385     case RR_TARGET_RED_OCTAGON:
386         target_svg = GRR_TARGET_RED_OCTAGON_SVG;
387         break;
388     case RR_TARGET_RED_SQUARE:
389         target_svg = GRR_TARGET_RED_SQUARE_SVG;
390         break;
391     case RR_TARGET_RED_TRIANGLE:
392         target_svg = GRR_TARGET_RED_TRIANGLE_SVG;
393         break;
394     case RR_TARGET_YELLOW_CIRCLE:
395         target_svg = GRR_TARGET_YELLOW_CIRCLE_SVG;
396         break;
397     case RR_TARGET_YELLOW_OCTAGON:
398         target_svg = GRR_TARGET_YELLOW_OCTAGON_SVG;
399         break;
400     case RR_TARGET_YELLOW_SQUARE:
401         target_svg = GRR_TARGET_YELLOW_SQUARE_SVG;
402         break;
403     case RR_TARGET_YELLOW_TRIANGLE:
404         target_svg = GRR_TARGET_YELLOW_TRIANGLE_SVG;
405         break;
406     case RR_TARGET_WHIRL:
407         target_svg = GRR_TARGET_WHIRL_SVG;
408         break;
409     default:
410         return;
411     }
412
413     grr_svg_draw (xrs, target_svg);
414 }
415
416 static void
417 grr_robot_draw (XrState *xrs, rr_robot_t robot)
418 {
419     char *robot_svg;
420
421     if (robot == RR_ROBOT_NONE)
422         return;
423
424     switch (robot) {
425     case RR_ROBOT_BLUE:
426         robot_svg = GRR_ROBOT_BLUE_SVG;
427         break;
428     case RR_ROBOT_GREEN:
429         robot_svg = GRR_ROBOT_GREEN_SVG;
430         break;
431     case RR_ROBOT_RED:
432         robot_svg = GRR_ROBOT_RED_SVG;
433         break;
434     case RR_ROBOT_YELLOW:
435         robot_svg = GRR_ROBOT_YELLOW_SVG;
436         break;
437     default:
438         return;
439     }
440
441     grr_svg_draw (xrs, robot_svg);
442 }
443
444 static void
445 grr_cell_draw (XrState *xrs, rr_cell_t cell, rr_target_t goal,
446                int width, int height)
447 {
448     int xpad, ypad;
449     rr_target_t target;
450
451     XrSave (xrs);
452     XrScale (xrs,
453              width / GRR_SVG_ASSUMED_WIDTH,
454              height / GRR_SVG_ASSUMED_HEIGHT);
455     grr_svg_draw (xrs, GRR_CELL_SVG);
456     grr_wall_draw (xrs, RR_CELL_GET_WALLS (cell));
457     XrRestore (xrs);
458
459     xpad = width / 5;
460     ypad = width / 5;
461
462     XrSave (xrs);
463     XrTranslate (xrs, xpad, ypad);
464     XrScale (xrs,
465              (width - 2*xpad)  / GRR_SVG_ASSUMED_WIDTH,
466              (height - 2*ypad) / GRR_SVG_ASSUMED_HEIGHT);
467
468     target = RR_CELL_GET_TARGET (cell);
469     grr_target_draw (xrs, target);
470     XrRestore (xrs);
471
472     /* This is a hack, (it obscures the cell background in addition to
473        the target). The real way to do this is to draw the target
474        itself with less opacity. */
475     if (target && target != goal) {
476         XrSave (xrs);
477         XrScale (xrs,
478                  width / GRR_SVG_ASSUMED_WIDTH,
479                  height / GRR_SVG_ASSUMED_HEIGHT);
480         XrRectangle (xrs, 0, 0,
481                      GRR_SVG_ASSUMED_WIDTH,
482                      GRR_SVG_ASSUMED_HEIGHT);
483         XrSetRGBColor (xrs, 1, 1, 1);
484         XrSetAlpha (xrs, 0.75);
485         XrFill (xrs);
486         XrRestore (xrs);
487     }
488
489     XrSave (xrs);
490     XrTranslate (xrs, xpad, ypad);
491     XrScale (xrs,
492              (width - 2*xpad)  / GRR_SVG_ASSUMED_WIDTH,
493              (height - 2*ypad) / GRR_SVG_ASSUMED_HEIGHT);
494
495     grr_robot_draw (xrs, RR_CELL_GET_ROBOT (cell));
496
497     XrRestore (xrs);
498
499 }
500
501 static gint
502 grr_board_view_expose (GtkWidget      *widget,
503                        GdkEventExpose *event)
504 {
505     grr_board_view_t *view;
506     Display *dpy;
507     Drawable drawable;
508     XrState *xrs;
509     GdkDrawable *real_drawable;
510     gint x_off, y_off;
511     int i, j;
512     rr_target_t goal_target;
513
514     g_return_val_if_fail (widget != NULL, FALSE);
515     g_return_val_if_fail (GRR_IS_BOARD_VIEW (widget), FALSE);
516     g_return_val_if_fail (event != NULL, FALSE);
517
518     if (event->count > 0)
519         return FALSE;
520
521     view = GRR_BOARD_VIEW (widget);
522
523     /* Unabstract X from GTK+ */
524     gdk_window_get_internal_paint_info (widget->window, &real_drawable, &x_off, &y_off);
525     dpy = gdk_x11_drawable_get_xdisplay (real_drawable);
526     drawable = gdk_x11_drawable_get_xid (real_drawable);
527
528     /* Ignore GTK+ and use Xr for drawing. */
529     xrs = XrCreate ();
530     XrSetTargetDrawable (xrs, dpy, drawable);
531     XrTranslate (xrs, -x_off, -y_off);
532
533     rr_board_get_size (view->board, &view->board_width, &view->board_height);
534
535     view->cell_width = widget->allocation.width / view->board_width;
536     if (view->cell_width == 0)
537         view->cell_width = 1;
538     view->cell_height = widget->allocation.height / view->board_height;
539     if (view->cell_height == 0)
540         view->cell_height = 1;
541
542     view->board_pad_x = (widget->allocation.width - view->board_width * view->cell_width) / 2;
543     view->board_pad_y = (widget->allocation.height - view->board_height * view->cell_height) / 2;
544
545     XrTranslate (xrs, view->board_pad_x, view->board_pad_y);
546
547     goal_target = rr_board_get_goal_target (view->board);
548
549     /* Draw cell targets */
550     for (j=0; j < view->board_height; j++) {
551         for (i=0; i < view->board_width; i++) {
552             XrSave (xrs);
553             XrTranslate (xrs, i * view->cell_width, j * view->cell_height);
554             grr_cell_draw (xrs,
555                            rr_board_get_cell (view->board, i, j),
556                            goal_target,
557                            view->cell_width, view->cell_height);
558             XrRestore (xrs);
559         }
560     }
561
562     /* Draw grid. */
563     XrSave (xrs);
564     XrSetRGBColor (xrs, .75, .75, 1);
565     XrSetLineWidth (xrs, 1);
566     for (j=0; j < view->board_height; j++) {
567         XrMoveTo (xrs, 0, j * view->cell_height + 0.5);
568         XrRelLineTo (xrs, view->board_width * view->cell_width, 0);
569     }
570     for (i=0; i < view->board_width; i++) {
571         XrMoveTo (xrs, i * view->cell_width + 0.5, 0);
572         XrRelLineTo (xrs, 0, view->board_height * view->cell_height);
573     }
574     XrStroke (xrs);
575     XrRestore (xrs);
576
577     /* Draw goal target in center of board */
578     XrSave (xrs);
579     XrTranslate (xrs,
580                  (view->board_width / 2 - 1) * view->cell_width,
581                  (view->board_height / 2 - 1) * view->cell_height);
582     grr_cell_draw (xrs, goal_target, goal_target,
583                    view->cell_width * 2, view->cell_height * 2);
584     XrRestore (xrs);
585
586     /* Draw walls */
587     for (j=0; j < view->board_height; j++) {
588         for (i=0; i < view->board_width; i++) {
589             XrSave (xrs);
590             XrTranslate (xrs, i * view->cell_width, j * view->cell_height);
591             XrScale (xrs,
592                      view->cell_width / GRR_SVG_ASSUMED_WIDTH,
593                      view->cell_height / GRR_SVG_ASSUMED_HEIGHT);
594             grr_wall_draw (xrs, RR_CELL_GET_WALLS (rr_board_get_cell(view->board, i, j)));
595             XrRestore (xrs);
596         }
597     }
598     
599     XrDestroy (xrs);
600
601     return FALSE;
602 }
603
604 static void
605 grr_board_view_pointer_coords_to_grid (grr_board_view_t *view,
606                                        int pointer_x, int pointer_y,
607                                        int *grid_x, int *grid_y)
608 {
609     *grid_x = (pointer_x - view->board_pad_x) / view->cell_width;
610     *grid_y = (pointer_y - view->board_pad_y) / view->cell_height;
611 }
612
613
614 static gint
615 grr_board_view_button_press (GtkWidget      *widget,
616                        GdkEventButton *event)
617 {
618     grr_board_view_t *view;
619     rr_cell_t cell;
620     int x, y;
621
622     g_return_val_if_fail (widget != NULL, FALSE);
623     g_return_val_if_fail (GRR_IS_BOARD_VIEW (widget), FALSE);
624     g_return_val_if_fail (event != NULL, FALSE);
625     
626     view = GRR_BOARD_VIEW (widget);
627
628     grr_board_view_pointer_coords_to_grid (view,
629                                            event->x, event->y,
630                                            &x, &y);
631   
632     cell = rr_board_get_cell (view->board, x, y);
633     if (!view->button && RR_CELL_GET_ROBOT (cell)) {
634
635         gtk_grab_add (widget);
636
637         view->button = event->button;
638         view->drag_x = x;
639         view->drag_y = y;
640         view->drag_robot = RR_CELL_GET_ROBOT (cell);
641
642         grr_board_view_update_mouse (view, event->x, event->y);
643     }
644
645     return FALSE;
646 }
647
648 static gint
649 grr_board_view_button_release (GtkWidget      *widget,
650                           GdkEventButton *event)
651 {
652     grr_board_view_t *view;
653     const char *dir;
654     const char *robot;
655     int x, y;
656     int dx, dy;
657
658     g_return_val_if_fail (widget != NULL, FALSE);
659     g_return_val_if_fail (GRR_IS_BOARD_VIEW (widget), FALSE);
660     g_return_val_if_fail (event != NULL, FALSE);
661
662     view = GRR_BOARD_VIEW (widget);
663
664     if (view->button == event->button) {
665       gtk_grab_remove (widget);
666
667       view->button = 0;
668
669       grr_board_view_pointer_coords_to_grid (view, event->x, event->y, &x, &y);
670
671       dx = x - view->drag_x;
672       dy = y - view->drag_y;
673       if (dx == 0 && dy == 0)
674           return FALSE;
675
676       if (abs(dx) > abs(dy))
677           if (x > view->drag_x)
678               dir = "east";
679           else
680               dir = "west";
681       else
682           if (y > view->drag_y)
683               dir = "south";
684           else
685               dir = "north";
686
687       switch (view->drag_robot) {
688       case RR_ROBOT_BLUE:
689           robot = "blue";
690           break;
691       case RR_ROBOT_GREEN:
692           robot = "green";
693           break;
694       case RR_ROBOT_RED:
695           robot = "RED";
696           break;
697       case RR_ROBOT_YELLOW:
698           robot = "YELLOW";
699           break;
700       default:
701           return FALSE;
702       }
703
704       if (view->client) {
705           char *move_str;
706
707           grr_sprintf_alloc (&move_str, "%s %s", robot, dir);
708           if (move_str == NULL)
709               return FALSE;
710           rr_client_move (view->client, move_str);
711           free (move_str);
712       }
713     }
714
715   return FALSE;
716 }
717
718 static gint
719 grr_board_view_motion_notify (GtkWidget      *widget,
720                          GdkEventMotion *event)
721 {
722     grr_board_view_t *view;
723     GdkModifierType mods;
724     gint x, y, mask;
725     
726     g_return_val_if_fail (widget != NULL, FALSE);
727     g_return_val_if_fail (GRR_IS_BOARD_VIEW (widget), FALSE);
728     g_return_val_if_fail (event != NULL, FALSE);
729     
730     view = GRR_BOARD_VIEW (widget);
731     
732     if (view->button != 0) {
733         x = event->x;
734         y = event->y;
735
736         if (event->is_hint || (event->window != widget->window))
737             gdk_window_get_pointer (widget->window, &x, &y, &mods);
738         
739         switch (view->button) {
740         case 1:
741             mask = GDK_BUTTON1_MASK;
742             break;
743         case 2:
744             mask = GDK_BUTTON2_MASK;
745             break;
746         case 3:
747             mask = GDK_BUTTON3_MASK;
748             break;
749         default:
750             mask = 0;
751             break;
752         }
753
754         if (mods & mask)
755             grr_board_view_update_mouse (view, x,y);
756     }
757
758     return FALSE;
759 }
760
761 static void
762 grr_board_view_update_mouse (grr_board_view_t *view, gint x, gint y)
763 {
764     g_return_if_fail (view != NULL);
765     g_return_if_fail (GRR_IS_BOARD_VIEW (view));
766
767     /* XXX: Should draw a robot here */
768 }
769
770 static void
771 grr_board_view_update (grr_board_view_t *view)
772 {
773     g_return_if_fail (view != NULL);
774     g_return_if_fail (GRR_IS_BOARD_VIEW (view));
775
776     gtk_widget_queue_draw (GTK_WIDGET (view));
777 }
778
779 static int
780 grr_sprintf_alloc (char **str, const char *fmt, ...)
781 {
782     int ret;
783     va_list ap;
784
785     va_start(ap, fmt);
786     ret = grr_sprintf_alloc_va (str, fmt, ap);
787     va_end(ap);
788
789     return ret;
790 }
791
792 /* ripped more or less straight out of PRINTF(3) */
793 static int
794 grr_sprintf_alloc_va (char **str, const char *fmt, va_list ap)
795 {
796     char *new_str;
797     /* Guess we need no more than 100 bytes. */
798     int n, size = 100;
799  
800     if ((*str = malloc (size)) == NULL)
801         return -1;
802     while (1) {
803         /* Try to print in the allocated space. */
804         n = vsnprintf (*str, size, fmt, ap);
805         /* If that worked, return the size. */
806         if (n > -1 && n < size)
807             return n;
808         /* Else try again with more space. */
809         if (n > -1)    /* glibc 2.1 */
810             size = n+1; /* precisely what is needed */
811         else           /* glibc 2.0 */
812             size *= 2;  /* twice the old size */
813         new_str = realloc(*str, size);
814         if (new_str == NULL) {
815             free(*str);
816             *str = NULL;
817             return -1;
818         }
819         *str = new_str;
820     }
821 }