]> git.cworth.org Git - grrobot/blob - src/grr_board_view.c
Fixed layout of text entry widget to fixed height
[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     XrSetRGBColor (xrs, .75, .75, 1);
547     XrSetLineWidth (xrs, 1);
548     for (j=0; j < view->board_height; j++) {
549         XrMoveTo (xrs, 0, j * view->cell_height + 0.5);
550         XrRelLineTo (xrs, view->board_width * view->cell_width, 0);
551     }
552     for (i=0; i < view->board_width; i++) {
553         XrMoveTo (xrs, i * view->cell_width + 0.5, 0);
554         XrRelLineTo (xrs, 0, view->board_height * view->cell_height);
555     }
556     XrStroke (xrs);
557     XrRestore (xrs);
558
559     /* Draw goal target in center of board */
560     XrSave (xrs);
561     XrTranslate (xrs,
562                  (view->board_width / 2 - 1) * view->cell_width,
563                  (view->board_height / 2 - 1) * view->cell_height);
564     grr_cell_draw (xrs, goal_target, goal_target,
565                    view->cell_width * 2, view->cell_height * 2);
566     XrRestore (xrs);
567
568     /* Draw walls */
569     for (j=0; j < view->board_height; j++) {
570         for (i=0; i < view->board_width; i++) {
571             XrSave (xrs);
572             XrTranslate (xrs, i * view->cell_width, j * view->cell_height);
573             XrScale (xrs,
574                      view->cell_width / GRR_SVG_ASSUMED_WIDTH,
575                      view->cell_height / GRR_SVG_ASSUMED_HEIGHT);
576             grr_wall_draw (xrs, RR_CELL_GET_WALLS (rr_board_get_cell(board, i, j)));
577             XrRestore (xrs);
578         }
579     }
580     
581     XrDestroy (xrs);
582
583     return FALSE;
584 }
585
586 static void
587 grr_board_view_pointer_coords_to_grid (grr_board_view_t *view,
588                                        int pointer_x, int pointer_y,
589                                        int *grid_x, int *grid_y)
590 {
591     *grid_x = (pointer_x - view->board_pad_x) / view->cell_width;
592     *grid_y = (pointer_y - view->board_pad_y) / view->cell_height;
593 }
594
595
596 static gint
597 grr_board_view_button_press (GtkWidget      *widget,
598                        GdkEventButton *event)
599 {
600     grr_board_view_t *view;
601     rr_cell_t cell;
602     int x, y;
603
604     g_return_val_if_fail (widget != NULL, FALSE);
605     g_return_val_if_fail (GRR_IS_BOARD_VIEW (widget), FALSE);
606     g_return_val_if_fail (event != NULL, FALSE);
607     
608     view = GRR_BOARD_VIEW (widget);
609
610     grr_board_view_pointer_coords_to_grid (view,
611                                            event->x, event->y,
612                                            &x, &y);
613   
614     cell = rr_board_get_cell (view->board, x, y);
615     if (!view->button && RR_CELL_GET_ROBOT (cell)) {
616
617         gtk_grab_add (widget);
618
619         view->button = event->button;
620         view->drag_x = x;
621         view->drag_y = y;
622         view->drag_robot = RR_CELL_GET_ROBOT (cell);
623
624         grr_board_view_update_mouse (view, event->x, event->y);
625     }
626
627     return FALSE;
628 }
629
630 static gint
631 grr_board_view_button_release (GtkWidget      *widget,
632                           GdkEventButton *event)
633 {
634     grr_board_view_t *view;
635     const char *dir;
636     const char *robot;
637     int x, y;
638     int dx, dy;
639
640     g_return_val_if_fail (widget != NULL, FALSE);
641     g_return_val_if_fail (GRR_IS_BOARD_VIEW (widget), FALSE);
642     g_return_val_if_fail (event != NULL, FALSE);
643
644     view = GRR_BOARD_VIEW (widget);
645
646     if (view->button == event->button) {
647       gtk_grab_remove (widget);
648
649       view->button = 0;
650
651       grr_board_view_pointer_coords_to_grid (view, event->x, event->y, &x, &y);
652
653       dx = x - view->drag_x;
654       dy = y - view->drag_y;
655       if (dx == 0 && dy == 0)
656           return FALSE;
657
658       if (abs(dx) > abs(dy))
659           if (x > view->drag_x)
660               dir = "east";
661           else
662               dir = "west";
663       else
664           if (y > view->drag_y)
665               dir = "south";
666           else
667               dir = "north";
668
669       switch (view->drag_robot) {
670       case RR_ROBOT_BLUE:
671           robot = "blue";
672           break;
673       case RR_ROBOT_GREEN:
674           robot = "green";
675           break;
676       case RR_ROBOT_RED:
677           robot = "RED";
678           break;
679       case RR_ROBOT_YELLOW:
680           robot = "YELLOW";
681           break;
682       default:
683           return FALSE;
684       }
685
686       if (view->client) {
687           char *move_str;
688
689           grr_sprintf_alloc (&move_str, "%s %s", robot, dir);
690           if (move_str == NULL)
691               return FALSE;
692           rr_client_move (view->client, move_str);
693           free (move_str);
694       }
695     }
696
697   return FALSE;
698 }
699
700 static gint
701 grr_board_view_motion_notify (GtkWidget      *widget,
702                          GdkEventMotion *event)
703 {
704     grr_board_view_t *view;
705     GdkModifierType mods;
706     gint x, y, mask;
707     
708     g_return_val_if_fail (widget != NULL, FALSE);
709     g_return_val_if_fail (GRR_IS_BOARD_VIEW (widget), FALSE);
710     g_return_val_if_fail (event != NULL, FALSE);
711     
712     view = GRR_BOARD_VIEW (widget);
713     
714     if (view->button != 0) {
715         x = event->x;
716         y = event->y;
717
718         if (event->is_hint || (event->window != widget->window))
719             gdk_window_get_pointer (widget->window, &x, &y, &mods);
720         
721         switch (view->button) {
722         case 1:
723             mask = GDK_BUTTON1_MASK;
724             break;
725         case 2:
726             mask = GDK_BUTTON2_MASK;
727             break;
728         case 3:
729             mask = GDK_BUTTON3_MASK;
730             break;
731         default:
732             mask = 0;
733             break;
734         }
735
736         if (mods & mask)
737             grr_board_view_update_mouse (view, x,y);
738     }
739
740     return FALSE;
741 }
742
743 static void
744 grr_board_view_update_mouse (grr_board_view_t *view, gint x, gint y)
745 {
746     g_return_if_fail (view != NULL);
747     g_return_if_fail (GRR_IS_BOARD_VIEW (view));
748
749     /* XXX: Should draw a robot here */
750 }
751
752 static void
753 grr_board_view_update (grr_board_view_t *view)
754 {
755     g_return_if_fail (view != NULL);
756     g_return_if_fail (GRR_IS_BOARD_VIEW (view));
757
758     gtk_widget_queue_draw (GTK_WIDGET (view));
759 }
760
761 static int
762 grr_sprintf_alloc (char **str, const char *fmt, ...)
763 {
764     int ret;
765     va_list ap;
766
767     va_start(ap, fmt);
768     ret = grr_sprintf_alloc_va (str, fmt, ap);
769     va_end(ap);
770
771     return ret;
772 }
773
774 /* ripped more or less straight out of PRINTF(3) */
775 static int
776 grr_sprintf_alloc_va (char **str, const char *fmt, va_list ap)
777 {
778     char *new_str;
779     /* Guess we need no more than 100 bytes. */
780     int n, size = 100;
781  
782     if ((*str = malloc (size)) == NULL)
783         return -1;
784     while (1) {
785         /* Try to print in the allocated space. */
786         n = vsnprintf (*str, size, fmt, ap);
787         /* If that worked, return the size. */
788         if (n > -1 && n < size)
789             return n;
790         /* Else try again with more space. */
791         if (n > -1)    /* glibc 2.1 */
792             size = n+1; /* precisely what is needed */
793         else           /* glibc 2.0 */
794             size *= 2;  /* twice the old size */
795         new_str = realloc(*str, size);
796         if (new_str == NULL) {
797             free(*str);
798             *str = NULL;
799             return -1;
800         }
801         *str = new_str;
802     }
803 }