1 /* grr_board_view - GTK+ widget for displaying an rr_board
3 * Copyright © 2003 Carl Worth
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.
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.
24 * Author: Carl Worth <carl@theworths.org>
36 #include <gtk/gtkmain.h>
37 #include <gtk/gtksignal.h>
40 #include "grr_board_view.h"
42 #define SCROLL_DELAY_LENGTH 300
43 #define GRR_BOARD_VIEW_DEFAULT_SIZE 100
45 #define GRR_SVG_ASSUMED_WIDTH 32.0
46 #define GRR_SVG_ASSUMED_HEIGHT 32.0
48 #define GRR_CELL_SVG "svg/cell.svg"
49 #define GRR_WALL_SVG "svg/wall.svg"
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"
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"
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"
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"
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"
76 #define GRR_TARGET_WHIRL_SVG "svg/target_whirl.svg"
79 /* Forward declarations */
82 grr_sprintf_alloc (char **str, const char *fmt, ...);
85 grr_sprintf_alloc_va (char **str, const char *fmt, va_list ap);
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);
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);
110 static GtkWidgetClass *parent_class = NULL;
113 grr_board_view_get_type ()
115 static GType view_type = 0;
119 static const GTypeInfo view_info =
121 sizeof (grr_board_view_class_t),
124 (GClassInitFunc) grr_board_view_class_init,
127 sizeof (grr_board_view_t),
129 (GInstanceInitFunc) grr_board_view_init,
132 view_type = g_type_register_static (GTK_TYPE_WIDGET, "grr_board_view", &view_info, 0);
139 grr_board_view_class_init (grr_board_view_class_t *class)
141 GtkObjectClass *object_class;
142 GtkWidgetClass *widget_class;
144 object_class = (GtkObjectClass*) class;
145 widget_class = (GtkWidgetClass*) class;
147 parent_class = gtk_type_class (gtk_widget_get_type ());
149 object_class->destroy = grr_board_view_destroy;
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;
161 grr_board_view_init (grr_board_view_t *view)
171 grr_board_view_new (rr_board_t *board)
173 grr_board_view_t *view;
175 view = g_object_new (grr_board_view_get_type (), NULL);
179 grr_board_view_update (view);
181 return GTK_WIDGET (view);
185 grr_board_view_set_client (grr_board_view_t *view, rr_client_t *client)
187 g_return_if_fail (view != NULL);
189 view->client = client;
193 grr_board_view_destroy (GtkObject *object)
195 grr_board_view_t *view;
197 g_return_if_fail (object != NULL);
198 g_return_if_fail (GRR_IS_BOARD_VIEW (object));
200 view = GRR_BOARD_VIEW (object);
205 if (GTK_OBJECT_CLASS (parent_class)->destroy)
206 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
210 grr_board_view_realize (GtkWidget *widget)
212 grr_board_view_t *view;
213 GdkWindowAttr attributes;
214 gint attributes_mask;
216 g_return_if_fail (widget != NULL);
217 g_return_if_fail (GRR_IS_BOARD_VIEW (widget));
219 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
220 view = GRR_BOARD_VIEW (widget);
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);
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);
238 widget->style = gtk_style_attach (widget->style, widget->window);
240 gdk_window_set_user_data (widget->window, widget);
242 gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
246 grr_board_view_size_request (GtkWidget *widget,
247 GtkRequisition *requisition)
249 requisition->width = GRR_BOARD_VIEW_DEFAULT_SIZE;
250 requisition->height = GRR_BOARD_VIEW_DEFAULT_SIZE;
254 grr_board_view_size_allocate (GtkWidget *widget,
255 GtkAllocation *allocation)
257 grr_board_view_t *view;
259 g_return_if_fail (widget != NULL);
260 g_return_if_fail (GRR_IS_BOARD_VIEW (widget));
261 g_return_if_fail (allocation != NULL);
263 widget->allocation = *allocation;
264 view = GRR_BOARD_VIEW (widget);
266 if (GTK_WIDGET_REALIZED (widget)) {
267 gdk_window_move_resize (widget->window,
268 allocation->x, allocation->y,
269 allocation->width, allocation->height);
274 grr_svg_draw (XrState *xrs, char *svg)
276 xsvg_status_t status;
279 status = xsvg_create (&xsvg);
281 fprintf (stderr, "xsvg_create error\n");
284 status = xsvg_parse_file (xsvg, svg);
286 fprintf (stderr, "xsvg_parse_file error parsing %s\n", svg);
289 xsvg_render (xsvg, xrs);
291 fprintf (stderr, "xsvg_render error\n");
298 grr_wall_draw (XrState *xrs, rr_wall_t wall)
300 char *wall_svg = GRR_WALL_SVG;
302 if (wall == RR_WALL_NONE)
305 if (wall & RR_WALL_ABOVE) {
306 grr_svg_draw (xrs, wall_svg);
308 if (wall & RR_WALL_LEFT) {
310 XrRotate (xrs, M_PI_2);
311 grr_svg_draw (xrs, wall_svg);
314 if (wall & RR_WALL_RIGHT) {
316 XrTranslate (xrs, GRR_SVG_ASSUMED_WIDTH, 0);
317 XrRotate (xrs, M_PI_2);
318 grr_svg_draw (xrs, wall_svg);
321 if (wall & RR_WALL_BELOW) {
323 XrTranslate (xrs, 0, GRR_SVG_ASSUMED_HEIGHT);
324 grr_svg_draw (xrs, wall_svg);
330 grr_target_draw (XrState *xrs, rr_target_t target)
334 if (target == RR_TARGET_NONE)
338 case RR_TARGET_BLUE_CIRCLE:
339 target_svg = GRR_TARGET_BLUE_CIRCLE_SVG;
341 case RR_TARGET_BLUE_OCTAGON:
342 target_svg = GRR_TARGET_BLUE_OCTAGON_SVG;
344 case RR_TARGET_BLUE_SQUARE:
345 target_svg = GRR_TARGET_BLUE_SQUARE_SVG;
347 case RR_TARGET_BLUE_TRIANGLE:
348 target_svg = GRR_TARGET_BLUE_TRIANGLE_SVG;
350 case RR_TARGET_GREEN_CIRCLE:
351 target_svg = GRR_TARGET_GREEN_CIRCLE_SVG;
353 case RR_TARGET_GREEN_OCTAGON:
354 target_svg = GRR_TARGET_GREEN_OCTAGON_SVG;
356 case RR_TARGET_GREEN_SQUARE:
357 target_svg = GRR_TARGET_GREEN_SQUARE_SVG;
359 case RR_TARGET_GREEN_TRIANGLE:
360 target_svg = GRR_TARGET_GREEN_TRIANGLE_SVG;
362 case RR_TARGET_RED_CIRCLE:
363 target_svg = GRR_TARGET_RED_CIRCLE_SVG;
365 case RR_TARGET_RED_OCTAGON:
366 target_svg = GRR_TARGET_RED_OCTAGON_SVG;
368 case RR_TARGET_RED_SQUARE:
369 target_svg = GRR_TARGET_RED_SQUARE_SVG;
371 case RR_TARGET_RED_TRIANGLE:
372 target_svg = GRR_TARGET_RED_TRIANGLE_SVG;
374 case RR_TARGET_YELLOW_CIRCLE:
375 target_svg = GRR_TARGET_YELLOW_CIRCLE_SVG;
377 case RR_TARGET_YELLOW_OCTAGON:
378 target_svg = GRR_TARGET_YELLOW_OCTAGON_SVG;
380 case RR_TARGET_YELLOW_SQUARE:
381 target_svg = GRR_TARGET_YELLOW_SQUARE_SVG;
383 case RR_TARGET_YELLOW_TRIANGLE:
384 target_svg = GRR_TARGET_YELLOW_TRIANGLE_SVG;
386 case RR_TARGET_WHIRL:
387 target_svg = GRR_TARGET_WHIRL_SVG;
393 grr_svg_draw (xrs, target_svg);
397 grr_robot_draw (XrState *xrs, rr_robot_t robot)
401 if (robot == RR_ROBOT_NONE)
406 robot_svg = GRR_ROBOT_BLUE_SVG;
409 robot_svg = GRR_ROBOT_GREEN_SVG;
412 robot_svg = GRR_ROBOT_RED_SVG;
414 case RR_ROBOT_YELLOW:
415 robot_svg = GRR_ROBOT_YELLOW_SVG;
421 grr_svg_draw (xrs, robot_svg);
425 grr_cell_draw (XrState *xrs, rr_cell_t cell, rr_target_t goal,
426 int width, int height)
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));
443 XrTranslate (xrs, xpad, ypad);
445 (width - 2*xpad) / GRR_SVG_ASSUMED_WIDTH,
446 (height - 2*ypad) / GRR_SVG_ASSUMED_HEIGHT);
448 target = RR_CELL_GET_TARGET (cell);
449 grr_target_draw (xrs, target);
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) {
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);
470 XrTranslate (xrs, xpad, ypad);
472 (width - 2*xpad) / GRR_SVG_ASSUMED_WIDTH,
473 (height - 2*ypad) / GRR_SVG_ASSUMED_HEIGHT);
475 grr_robot_draw (xrs, RR_CELL_GET_ROBOT (cell));
482 grr_board_view_expose (GtkWidget *widget,
483 GdkEventExpose *event)
485 grr_board_view_t *view;
490 GdkDrawable *real_drawable;
493 rr_target_t goal_target;
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);
499 if (event->count > 0)
502 view = GRR_BOARD_VIEW (widget);
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);
510 /* Ignore GTK+ and use Xr for drawing. */
512 XrSetTargetDrawable (xrs, dpy, drawable);
513 XrTranslate (xrs, -x_off, -y_off);
515 rr_board_get_size (board, &view->board_width, &view->board_height);
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;
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;
527 XrTranslate (xrs, view->board_pad_x, view->board_pad_y);
529 goal_target = rr_board_get_goal_target (board);
531 /* Draw cell targets */
532 for (j=0; j < view->board_height; j++) {
533 for (i=0; i < view->board_width; i++) {
535 XrTranslate (xrs, i * view->cell_width, j * view->cell_height);
537 rr_board_get_cell (board, i, j),
539 view->cell_width, view->cell_height);
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);
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);
561 /* Draw goal target in center of board */
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);
573 for (j=0; j < view->board_height; j++) {
574 for (i=0; i < view->board_width; i++) {
576 XrTranslate (xrs, i * view->cell_width, j * view->cell_height);
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)));
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)
595 *grid_x = (pointer_x - view->board_pad_x) / view->cell_width;
596 *grid_y = (pointer_y - view->board_pad_y) / view->cell_height;
601 grr_board_view_button_press (GtkWidget *widget,
602 GdkEventButton *event)
604 grr_board_view_t *view;
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);
612 view = GRR_BOARD_VIEW (widget);
614 grr_board_view_pointer_coords_to_grid (view,
618 cell = rr_board_get_cell (view->board, x, y);
619 if (!view->button && RR_CELL_GET_ROBOT (cell)) {
621 gtk_grab_add (widget);
623 view->button = event->button;
624 view->drag_robot = RR_CELL_GET_ROBOT (cell);
626 grr_board_view_update_mouse (view, event->x, event->y);
633 grr_board_view_button_release (GtkWidget *widget,
634 GdkEventButton *event)
636 grr_board_view_t *view;
640 int robot_x, robot_y;
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);
647 view = GRR_BOARD_VIEW (widget);
649 if (view->button == event->button) {
650 gtk_grab_remove (widget);
654 grr_board_view_pointer_coords_to_grid (view, event->x, event->y, &x, &y);
656 rr_board_find_robot (view->board, view->drag_robot, &robot_x, &robot_y);
659 if (dx == 0 && dy == 0)
662 if (abs(dx) > abs(dy))
673 switch (view->drag_robot) {
683 case RR_ROBOT_YELLOW:
693 grr_sprintf_alloc (&move_str, "%s %s", robot, dir);
694 if (move_str == NULL)
696 rr_client_move (view->client, move_str);
704 grr_board_view_motion_notify (GtkWidget *widget,
705 GdkEventMotion *event)
707 grr_board_view_t *view;
708 GdkModifierType mods;
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);
715 view = GRR_BOARD_VIEW (widget);
717 if (view->button != 0) {
721 if (event->is_hint || (event->window != widget->window))
722 gdk_window_get_pointer (widget->window, &x, &y, &mods);
724 switch (view->button) {
726 mask = GDK_BUTTON1_MASK;
729 mask = GDK_BUTTON2_MASK;
732 mask = GDK_BUTTON3_MASK;
740 grr_board_view_update_mouse (view, x,y);
747 grr_board_view_update_mouse (grr_board_view_t *view, gint x, gint y)
749 g_return_if_fail (view != NULL);
750 g_return_if_fail (GRR_IS_BOARD_VIEW (view));
752 /* XXX: Should draw a robot here */
756 grr_board_view_update (grr_board_view_t *view)
758 g_return_if_fail (view != NULL);
759 g_return_if_fail (GRR_IS_BOARD_VIEW (view));
761 gtk_widget_queue_draw (GTK_WIDGET (view));
765 grr_sprintf_alloc (char **str, const char *fmt, ...)
771 ret = grr_sprintf_alloc_va (str, fmt, ap);
777 /* ripped more or less straight out of PRINTF(3) */
779 grr_sprintf_alloc_va (char **str, const char *fmt, va_list ap)
782 /* Guess we need no more than 100 bytes. */
785 if ((*str = malloc (size)) == NULL)
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)
793 /* Else try again with more space. */
794 if (n > -1) /* glibc 2.1 */
795 size = n+1; /* precisely what is needed */
797 size *= 2; /* twice the old size */
798 new_str = realloc(*str, size);
799 if (new_str == NULL) {