]> git.cworth.org Git - grrobot/blob - src/grr_board_view.c
c4cf316e49103c9c158bd5373c9fc237206f9f3b
[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->client = NULL;
165     
166     view->button = 0;
167     view->timer = 0;
168 }
169
170 GtkWidget*
171 grr_board_view_new (rr_board_t *board)
172 {
173     grr_board_view_t *view;
174     
175     view = g_object_new (grr_board_view_get_type (), NULL);
176
177     view->board = board;
178
179     grr_board_view_update (view);
180
181     return GTK_WIDGET (view);
182 }
183
184 void
185 grr_board_view_set_client (grr_board_view_t *view, rr_client_t *client)
186 {
187     g_return_if_fail (view != NULL);
188
189     view->client = client;
190 }
191
192 static void
193 grr_board_view_destroy (GtkObject *object)
194 {
195     grr_board_view_t *view;
196
197     g_return_if_fail (object != NULL);
198     g_return_if_fail (GRR_IS_BOARD_VIEW (object));
199
200     view = GRR_BOARD_VIEW (object);
201
202     view->board = NULL;
203     view->client = NULL;
204     
205     if (GTK_OBJECT_CLASS (parent_class)->destroy)
206         (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
207 }
208
209 static void
210 grr_board_view_realize (GtkWidget *widget)
211 {
212     grr_board_view_t *view;
213     GdkWindowAttr attributes;
214     gint attributes_mask;
215
216     g_return_if_fail (widget != NULL);
217     g_return_if_fail (GRR_IS_BOARD_VIEW (widget));
218   
219     GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
220     view = GRR_BOARD_VIEW (widget);
221
222     attributes.x = widget->allocation.x;
223     attributes.y = widget->allocation.y;
224     attributes.width = widget->allocation.width;
225     attributes.height = widget->allocation.height;
226     attributes.wclass = GDK_INPUT_OUTPUT;
227     attributes.window_type = GDK_WINDOW_CHILD;
228     attributes.event_mask = gtk_widget_get_events (widget) | 
229         GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | 
230         GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
231         GDK_POINTER_MOTION_HINT_MASK;
232     attributes.visual = gtk_widget_get_visual (widget);
233     attributes.colormap = gtk_widget_get_colormap (widget);
234     
235     attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
236     widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
237     
238     widget->style = gtk_style_attach (widget->style, widget->window);
239     
240     gdk_window_set_user_data (widget->window, widget);
241
242     gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
243 }
244
245 static void 
246 grr_board_view_size_request (GtkWidget      *widget,
247                        GtkRequisition *requisition)
248 {
249     requisition->width = GRR_BOARD_VIEW_DEFAULT_SIZE;
250     requisition->height = GRR_BOARD_VIEW_DEFAULT_SIZE;
251 }
252
253 static void
254 grr_board_view_size_allocate (GtkWidget     *widget,
255                         GtkAllocation *allocation)
256 {
257     grr_board_view_t *view;
258
259     g_return_if_fail (widget != NULL);
260     g_return_if_fail (GRR_IS_BOARD_VIEW (widget));
261     g_return_if_fail (allocation != NULL);
262
263     widget->allocation = *allocation;
264     view = GRR_BOARD_VIEW (widget);
265
266     if (GTK_WIDGET_REALIZED (widget)) {
267         gdk_window_move_resize (widget->window,
268                                 allocation->x, allocation->y,
269                                 allocation->width, allocation->height);
270     }
271 }
272
273 static void
274 grr_svg_draw (XrState *xrs, char *svg)
275 {
276     xsvg_status_t status;
277     xsvg_t *xsvg;
278
279     status = xsvg_create (&xsvg);
280     if (status) {
281         fprintf (stderr, "xsvg_create error\n");
282         return;
283     }
284     status = xsvg_parse_file (xsvg, svg);
285     if (status) {
286         fprintf (stderr, "xsvg_parse_file error parsing %s\n", svg);
287         return;
288     }
289     xsvg_render (xsvg, xrs);
290     if (status) {
291         fprintf (stderr, "xsvg_render error\n");
292         return;
293     }
294     xsvg_destroy (xsvg);
295 }
296
297 static void
298 grr_wall_draw (XrState *xrs, rr_wall_t wall)
299 {
300     char *wall_svg = GRR_WALL_SVG;
301
302     if (wall == RR_WALL_NONE)
303         return;
304
305     if (wall & RR_WALL_ABOVE) {
306         grr_svg_draw (xrs, wall_svg);
307     }
308     if (wall & RR_WALL_LEFT) {
309         XrSave (xrs);
310         XrRotate (xrs, M_PI_2);
311         grr_svg_draw (xrs, wall_svg);
312         XrRestore (xrs);
313     }
314     if (wall & RR_WALL_RIGHT) {
315         XrSave (xrs);
316         XrTranslate (xrs, GRR_SVG_ASSUMED_WIDTH, 0);
317         XrRotate (xrs, M_PI_2);
318         grr_svg_draw (xrs, wall_svg);
319         XrRestore (xrs);
320     }
321     if (wall & RR_WALL_BELOW) {
322         XrSave (xrs);
323         XrTranslate (xrs, 0, GRR_SVG_ASSUMED_HEIGHT);
324         grr_svg_draw (xrs, wall_svg);
325         XrRestore (xrs);
326     }
327 }
328
329 static void
330 grr_target_draw (XrState *xrs, rr_target_t target)
331 {
332     char *target_svg;
333
334     if (target == RR_TARGET_NONE)
335         return;
336
337     switch (target) {
338     case RR_TARGET_BLUE_CIRCLE:
339         target_svg = GRR_TARGET_BLUE_CIRCLE_SVG;
340         break;
341     case RR_TARGET_BLUE_OCTAGON:
342         target_svg = GRR_TARGET_BLUE_OCTAGON_SVG;
343         break;
344     case RR_TARGET_BLUE_SQUARE:
345         target_svg = GRR_TARGET_BLUE_SQUARE_SVG;
346         break;
347     case RR_TARGET_BLUE_TRIANGLE:
348         target_svg = GRR_TARGET_BLUE_TRIANGLE_SVG;
349         break;
350     case RR_TARGET_GREEN_CIRCLE:
351         target_svg = GRR_TARGET_GREEN_CIRCLE_SVG;
352         break;
353     case RR_TARGET_GREEN_OCTAGON:
354         target_svg = GRR_TARGET_GREEN_OCTAGON_SVG;
355         break;
356     case RR_TARGET_GREEN_SQUARE:
357         target_svg = GRR_TARGET_GREEN_SQUARE_SVG;
358         break;
359     case RR_TARGET_GREEN_TRIANGLE:
360         target_svg = GRR_TARGET_GREEN_TRIANGLE_SVG;
361         break;
362     case RR_TARGET_RED_CIRCLE:
363         target_svg = GRR_TARGET_RED_CIRCLE_SVG;
364         break;
365     case RR_TARGET_RED_OCTAGON:
366         target_svg = GRR_TARGET_RED_OCTAGON_SVG;
367         break;
368     case RR_TARGET_RED_SQUARE:
369         target_svg = GRR_TARGET_RED_SQUARE_SVG;
370         break;
371     case RR_TARGET_RED_TRIANGLE:
372         target_svg = GRR_TARGET_RED_TRIANGLE_SVG;
373         break;
374     case RR_TARGET_YELLOW_CIRCLE:
375         target_svg = GRR_TARGET_YELLOW_CIRCLE_SVG;
376         break;
377     case RR_TARGET_YELLOW_OCTAGON:
378         target_svg = GRR_TARGET_YELLOW_OCTAGON_SVG;
379         break;
380     case RR_TARGET_YELLOW_SQUARE:
381         target_svg = GRR_TARGET_YELLOW_SQUARE_SVG;
382         break;
383     case RR_TARGET_YELLOW_TRIANGLE:
384         target_svg = GRR_TARGET_YELLOW_TRIANGLE_SVG;
385         break;
386     case RR_TARGET_WHIRL:
387         target_svg = GRR_TARGET_WHIRL_SVG;
388         break;
389     default:
390         return;
391     }
392
393     grr_svg_draw (xrs, target_svg);
394 }
395
396 static void
397 grr_robot_draw (XrState *xrs, rr_robot_t robot)
398 {
399     char *robot_svg;
400
401     if (robot == RR_ROBOT_NONE)
402         return;
403
404     switch (robot) {
405     case RR_ROBOT_BLUE:
406         robot_svg = GRR_ROBOT_BLUE_SVG;
407         break;
408     case RR_ROBOT_GREEN:
409         robot_svg = GRR_ROBOT_GREEN_SVG;
410         break;
411     case RR_ROBOT_RED:
412         robot_svg = GRR_ROBOT_RED_SVG;
413         break;
414     case RR_ROBOT_YELLOW:
415         robot_svg = GRR_ROBOT_YELLOW_SVG;
416         break;
417     default:
418         return;
419     }
420
421     grr_svg_draw (xrs, robot_svg);
422 }
423
424 static void
425 grr_cell_draw (XrState *xrs, rr_cell_t cell, rr_target_t goal,
426                int width, int height)
427 {
428     int xpad, ypad;
429     rr_target_t target;
430
431     XrSave (xrs);
432     XrScale (xrs,
433              width / GRR_SVG_ASSUMED_WIDTH,
434              height / GRR_SVG_ASSUMED_HEIGHT);
435     grr_svg_draw (xrs, GRR_CELL_SVG);
436     grr_wall_draw (xrs, RR_CELL_GET_WALLS (cell));
437     XrRestore (xrs);
438
439     xpad = width / 5;
440     ypad = width / 5;
441
442     XrSave (xrs);
443     XrTranslate (xrs, xpad, ypad);
444     XrScale (xrs,
445              (width - 2*xpad)  / GRR_SVG_ASSUMED_WIDTH,
446              (height - 2*ypad) / GRR_SVG_ASSUMED_HEIGHT);
447
448     target = RR_CELL_GET_TARGET (cell);
449     grr_target_draw (xrs, target);
450     XrRestore (xrs);
451
452     /* This is a hack, (it obscures the cell background in addition to
453        the target). The real way to do this is to draw the target
454        itself with less opacity. */
455     if (target && target != goal) {
456         XrSave (xrs);
457         XrScale (xrs,
458                  width / GRR_SVG_ASSUMED_WIDTH,
459                  height / GRR_SVG_ASSUMED_HEIGHT);
460         XrRectangle (xrs, 0, 0,
461                      GRR_SVG_ASSUMED_WIDTH,
462                      GRR_SVG_ASSUMED_HEIGHT);
463         XrSetRGBColor (xrs, 1, 1, 1);
464         XrSetAlpha (xrs, 0.75);
465         XrFill (xrs);
466         XrRestore (xrs);
467     }
468
469     XrSave (xrs);
470     XrTranslate (xrs, xpad, ypad);
471     XrScale (xrs,
472              (width - 2*xpad)  / GRR_SVG_ASSUMED_WIDTH,
473              (height - 2*ypad) / GRR_SVG_ASSUMED_HEIGHT);
474
475     grr_robot_draw (xrs, RR_CELL_GET_ROBOT (cell));
476
477     XrRestore (xrs);
478
479 }
480
481 static gint
482 grr_board_view_expose (GtkWidget      *widget,
483                        GdkEventExpose *event)
484 {
485     grr_board_view_t *view;
486     rr_board_t *board;
487     Display *dpy;
488     Drawable drawable;
489     XrState *xrs;
490     GdkDrawable *real_drawable;
491     gint x_off, y_off;
492     int i, j;
493     rr_target_t goal_target;
494
495     g_return_val_if_fail (widget != NULL, FALSE);
496     g_return_val_if_fail (GRR_IS_BOARD_VIEW (widget), FALSE);
497     g_return_val_if_fail (event != NULL, FALSE);
498
499     if (event->count > 0)
500         return FALSE;
501
502     view = GRR_BOARD_VIEW (widget);
503     board = view->board;
504
505     /* Unabstract X from GTK+ */
506     gdk_window_get_internal_paint_info (widget->window, &real_drawable, &x_off, &y_off);
507     dpy = gdk_x11_drawable_get_xdisplay (real_drawable);
508     drawable = gdk_x11_drawable_get_xid (real_drawable);
509
510     /* Ignore GTK+ and use Xr for drawing. */
511     xrs = XrCreate ();
512     XrSetTargetDrawable (xrs, dpy, drawable);
513     XrTranslate (xrs, -x_off, -y_off);
514
515     rr_board_get_size (board, &view->board_width, &view->board_height);
516
517     view->cell_width = widget->allocation.width / view->board_width;
518     if (view->cell_width == 0)
519         view->cell_width = 1;
520     view->cell_height = widget->allocation.height / view->board_height;
521     if (view->cell_height == 0)
522         view->cell_height = 1;
523
524     view->board_pad_x = (widget->allocation.width - view->board_width * view->cell_width) / 2;
525     view->board_pad_y = (widget->allocation.height - view->board_height * view->cell_height) / 2;
526
527     XrTranslate (xrs, view->board_pad_x, view->board_pad_y);
528
529     goal_target = rr_board_get_goal_target (board);
530
531     /* Draw cell targets */
532     for (j=0; j < view->board_height; j++) {
533         for (i=0; i < view->board_width; i++) {
534             XrSave (xrs);
535             XrTranslate (xrs, i * view->cell_width, j * view->cell_height);
536             grr_cell_draw (xrs,
537                            rr_board_get_cell (board, i, j),
538                            goal_target,
539                            view->cell_width, view->cell_height);
540             XrRestore (xrs);
541         }
542     }
543
544     /* Draw grid. */
545     XrSave (xrs);
546     {
547         XrSetRGBColor (xrs, .75, .75, 1);
548         XrSetLineWidth (xrs, 1);
549         for (j=0; j < view->board_height; j++) {
550             XrMoveTo (xrs, 0, j * view->cell_height + 0.5);
551             XrRelLineTo (xrs, view->board_width * view->cell_width, 0);
552         }
553         for (i=0; i < view->board_width; i++) {
554             XrMoveTo (xrs, i * view->cell_width + 0.5, 0);
555             XrRelLineTo (xrs, 0, view->board_height * view->cell_height);
556         }
557         XrStroke (xrs);
558     }
559     XrRestore (xrs);
560
561     /* Draw goal target in center of board */
562     XrSave (xrs);
563     {
564         XrTranslate (xrs,
565                      (view->board_width / 2 - 1) * view->cell_width,
566                      (view->board_height / 2 - 1) * view->cell_height);
567         grr_cell_draw (xrs, goal_target, goal_target,
568                        view->cell_width * 2, view->cell_height * 2);
569     }
570     XrRestore (xrs);
571
572     /* Draw walls */
573     for (j=0; j < view->board_height; j++) {
574         for (i=0; i < view->board_width; i++) {
575             XrSave (xrs);
576             XrTranslate (xrs, i * view->cell_width, j * view->cell_height);
577             XrScale (xrs,
578                      view->cell_width / GRR_SVG_ASSUMED_WIDTH,
579                      view->cell_height / GRR_SVG_ASSUMED_HEIGHT);
580             grr_wall_draw (xrs, RR_CELL_GET_WALLS (rr_board_get_cell(board, i, j)));
581             XrRestore (xrs);
582         }
583     }
584     
585     XrDestroy (xrs);
586
587     return FALSE;
588 }
589
590 static void
591 grr_board_view_pointer_coords_to_grid (grr_board_view_t *view,
592                                        int pointer_x, int pointer_y,
593                                        int *grid_x, int *grid_y)
594 {
595     *grid_x = (pointer_x - view->board_pad_x) / view->cell_width;
596     *grid_y = (pointer_y - view->board_pad_y) / view->cell_height;
597 }
598
599
600 static gint
601 grr_board_view_button_press (GtkWidget      *widget,
602                        GdkEventButton *event)
603 {
604     grr_board_view_t *view;
605     rr_cell_t cell;
606     int x, y;
607
608     g_return_val_if_fail (widget != NULL, FALSE);
609     g_return_val_if_fail (GRR_IS_BOARD_VIEW (widget), FALSE);
610     g_return_val_if_fail (event != NULL, FALSE);
611     
612     view = GRR_BOARD_VIEW (widget);
613
614     grr_board_view_pointer_coords_to_grid (view,
615                                            event->x, event->y,
616                                            &x, &y);
617   
618     cell = rr_board_get_cell (view->board, x, y);
619     if (!view->button && RR_CELL_GET_ROBOT (cell)) {
620
621         gtk_grab_add (widget);
622
623         view->button = event->button;
624         view->drag_robot = RR_CELL_GET_ROBOT (cell);
625
626         grr_board_view_update_mouse (view, event->x, event->y);
627     }
628
629     return FALSE;
630 }
631
632 static gint
633 grr_board_view_button_release (GtkWidget      *widget,
634                                GdkEventButton *event)
635 {
636     grr_board_view_t *view;
637     const char *dir;
638     const char *robot;
639     int x, y;
640     int robot_x, robot_y;
641     int dx, dy;
642
643     g_return_val_if_fail (widget != NULL, FALSE);
644     g_return_val_if_fail (GRR_IS_BOARD_VIEW (widget), FALSE);
645     g_return_val_if_fail (event != NULL, FALSE);
646
647     view = GRR_BOARD_VIEW (widget);
648
649     if (view->button == event->button) {
650       gtk_grab_remove (widget);
651       view->button = 0;
652     }
653
654     grr_board_view_pointer_coords_to_grid (view, event->x, event->y, &x, &y);
655
656     rr_board_find_robot (view->board, view->drag_robot, &robot_x, &robot_y);
657     dx = x - robot_x;
658     dy = y - robot_y;
659     if (dx == 0 && dy == 0)
660         return FALSE;
661
662     if (abs(dx) > abs(dy))
663         if (x > robot_x)
664             dir = "east";
665         else
666             dir = "west";
667     else
668         if (y > robot_y)
669             dir = "south";
670         else
671             dir = "north";
672     
673     switch (view->drag_robot) {
674     case RR_ROBOT_BLUE:
675         robot = "blue";
676         break;
677     case RR_ROBOT_GREEN:
678         robot = "green";
679         break;
680     case RR_ROBOT_RED:
681         robot = "RED";
682         break;
683     case RR_ROBOT_YELLOW:
684         robot = "YELLOW";
685         break;
686     default:
687         return FALSE;
688     }
689     
690     if (view->client) {
691         char *move_str;
692         
693         grr_sprintf_alloc (&move_str, "%s %s", robot, dir);
694         if (move_str == NULL)
695             return FALSE;
696         rr_client_move (view->client, move_str);
697         free (move_str);
698     }
699
700   return FALSE;
701 }
702
703 static gint
704 grr_board_view_motion_notify (GtkWidget      *widget,
705                          GdkEventMotion *event)
706 {
707     grr_board_view_t *view;
708     GdkModifierType mods;
709     gint x, y, mask;
710     
711     g_return_val_if_fail (widget != NULL, FALSE);
712     g_return_val_if_fail (GRR_IS_BOARD_VIEW (widget), FALSE);
713     g_return_val_if_fail (event != NULL, FALSE);
714     
715     view = GRR_BOARD_VIEW (widget);
716     
717     if (view->button != 0) {
718         x = event->x;
719         y = event->y;
720
721         if (event->is_hint || (event->window != widget->window))
722             gdk_window_get_pointer (widget->window, &x, &y, &mods);
723         
724         switch (view->button) {
725         case 1:
726             mask = GDK_BUTTON1_MASK;
727             break;
728         case 2:
729             mask = GDK_BUTTON2_MASK;
730             break;
731         case 3:
732             mask = GDK_BUTTON3_MASK;
733             break;
734         default:
735             mask = 0;
736             break;
737         }
738
739         if (mods & mask)
740             grr_board_view_update_mouse (view, x,y);
741     }
742
743     return FALSE;
744 }
745
746 static void
747 grr_board_view_update_mouse (grr_board_view_t *view, gint x, gint y)
748 {
749     g_return_if_fail (view != NULL);
750     g_return_if_fail (GRR_IS_BOARD_VIEW (view));
751
752     /* XXX: Should draw a robot here */
753 }
754
755 static void
756 grr_board_view_update (grr_board_view_t *view)
757 {
758     g_return_if_fail (view != NULL);
759     g_return_if_fail (GRR_IS_BOARD_VIEW (view));
760
761     gtk_widget_queue_draw (GTK_WIDGET (view));
762 }
763
764 static int
765 grr_sprintf_alloc (char **str, const char *fmt, ...)
766 {
767     int ret;
768     va_list ap;
769
770     va_start(ap, fmt);
771     ret = grr_sprintf_alloc_va (str, fmt, ap);
772     va_end(ap);
773
774     return ret;
775 }
776
777 /* ripped more or less straight out of PRINTF(3) */
778 static int
779 grr_sprintf_alloc_va (char **str, const char *fmt, va_list ap)
780 {
781     char *new_str;
782     /* Guess we need no more than 100 bytes. */
783     int n, size = 100;
784  
785     if ((*str = malloc (size)) == NULL)
786         return -1;
787     while (1) {
788         /* Try to print in the allocated space. */
789         n = vsnprintf (*str, size, fmt, ap);
790         /* If that worked, return the size. */
791         if (n > -1 && n < size)
792             return n;
793         /* Else try again with more space. */
794         if (n > -1)    /* glibc 2.1 */
795             size = n+1; /* precisely what is needed */
796         else           /* glibc 2.0 */
797             size *= 2;  /* twice the old size */
798         new_str = realloc(*str, size);
799         if (new_str == NULL) {
800             free(*str);
801             *str = NULL;
802             return -1;
803         }
804         *str = new_str;
805     }
806 }