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)
164 view->owns_board = 0;
172 grr_board_view_new (rr_board_t *board)
174 grr_board_view_t *view;
176 view = g_object_new (grr_board_view_get_type (), NULL);
179 board = rr_board_create (16, 16);
180 view->owns_board = 1;
183 grr_board_view_set_board (view, board);
185 return GTK_WIDGET (view);
189 grr_board_view_destroy (GtkObject *object)
191 grr_board_view_t *view;
193 g_return_if_fail (object != NULL);
194 g_return_if_fail (GRR_IS_BOARD_VIEW (object));
196 view = GRR_BOARD_VIEW (object);
198 rr_board_destroy (view->board);
200 if (view->board && view->owns_board) {
201 rr_board_destroy (view->board);
205 if (GTK_OBJECT_CLASS (parent_class)->destroy)
206 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
210 grr_board_view_set_board (grr_board_view_t *view,
213 g_return_if_fail (view != NULL);
217 grr_board_view_update (view);
221 grr_board_view_set_client (grr_board_view_t *view,
224 g_return_if_fail (view != NULL);
226 view->client = client;
230 grr_board_view_realize (GtkWidget *widget)
232 grr_board_view_t *view;
233 GdkWindowAttr attributes;
234 gint attributes_mask;
236 g_return_if_fail (widget != NULL);
237 g_return_if_fail (GRR_IS_BOARD_VIEW (widget));
239 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
240 view = GRR_BOARD_VIEW (widget);
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);
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);
258 widget->style = gtk_style_attach (widget->style, widget->window);
260 gdk_window_set_user_data (widget->window, widget);
262 gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
266 grr_board_view_size_request (GtkWidget *widget,
267 GtkRequisition *requisition)
269 requisition->width = GRR_BOARD_VIEW_DEFAULT_SIZE;
270 requisition->height = GRR_BOARD_VIEW_DEFAULT_SIZE;
274 grr_board_view_size_allocate (GtkWidget *widget,
275 GtkAllocation *allocation)
277 grr_board_view_t *view;
279 g_return_if_fail (widget != NULL);
280 g_return_if_fail (GRR_IS_BOARD_VIEW (widget));
281 g_return_if_fail (allocation != NULL);
283 widget->allocation = *allocation;
284 view = GRR_BOARD_VIEW (widget);
286 if (GTK_WIDGET_REALIZED (widget)) {
287 gdk_window_move_resize (widget->window,
288 allocation->x, allocation->y,
289 allocation->width, allocation->height);
294 grr_svg_draw (XrState *xrs, char *svg)
296 xsvg_status_t status;
299 status = xsvg_create (&xsvg);
301 fprintf (stderr, "xsvg_create error\n");
304 status = xsvg_parse_file (xsvg, svg);
306 fprintf (stderr, "xsvg_parse_file error parsing %s\n", svg);
309 xsvg_render (xsvg, xrs);
311 fprintf (stderr, "xsvg_render error\n");
318 grr_wall_draw (XrState *xrs, rr_wall_t wall)
320 char *wall_svg = GRR_WALL_SVG;
322 if (wall == RR_WALL_NONE)
325 if (wall & RR_WALL_ABOVE) {
326 grr_svg_draw (xrs, wall_svg);
328 if (wall & RR_WALL_LEFT) {
330 XrRotate (xrs, M_PI_2);
331 grr_svg_draw (xrs, wall_svg);
334 if (wall & RR_WALL_RIGHT) {
336 XrTranslate (xrs, GRR_SVG_ASSUMED_WIDTH, 0);
337 XrRotate (xrs, M_PI_2);
338 grr_svg_draw (xrs, wall_svg);
341 if (wall & RR_WALL_BELOW) {
343 XrTranslate (xrs, 0, GRR_SVG_ASSUMED_HEIGHT);
344 grr_svg_draw (xrs, wall_svg);
350 grr_target_draw (XrState *xrs, rr_target_t target)
354 if (target == RR_TARGET_NONE)
358 case RR_TARGET_BLUE_CIRCLE:
359 target_svg = GRR_TARGET_BLUE_CIRCLE_SVG;
361 case RR_TARGET_BLUE_OCTAGON:
362 target_svg = GRR_TARGET_BLUE_OCTAGON_SVG;
364 case RR_TARGET_BLUE_SQUARE:
365 target_svg = GRR_TARGET_BLUE_SQUARE_SVG;
367 case RR_TARGET_BLUE_TRIANGLE:
368 target_svg = GRR_TARGET_BLUE_TRIANGLE_SVG;
370 case RR_TARGET_GREEN_CIRCLE:
371 target_svg = GRR_TARGET_GREEN_CIRCLE_SVG;
373 case RR_TARGET_GREEN_OCTAGON:
374 target_svg = GRR_TARGET_GREEN_OCTAGON_SVG;
376 case RR_TARGET_GREEN_SQUARE:
377 target_svg = GRR_TARGET_GREEN_SQUARE_SVG;
379 case RR_TARGET_GREEN_TRIANGLE:
380 target_svg = GRR_TARGET_GREEN_TRIANGLE_SVG;
382 case RR_TARGET_RED_CIRCLE:
383 target_svg = GRR_TARGET_RED_CIRCLE_SVG;
385 case RR_TARGET_RED_OCTAGON:
386 target_svg = GRR_TARGET_RED_OCTAGON_SVG;
388 case RR_TARGET_RED_SQUARE:
389 target_svg = GRR_TARGET_RED_SQUARE_SVG;
391 case RR_TARGET_RED_TRIANGLE:
392 target_svg = GRR_TARGET_RED_TRIANGLE_SVG;
394 case RR_TARGET_YELLOW_CIRCLE:
395 target_svg = GRR_TARGET_YELLOW_CIRCLE_SVG;
397 case RR_TARGET_YELLOW_OCTAGON:
398 target_svg = GRR_TARGET_YELLOW_OCTAGON_SVG;
400 case RR_TARGET_YELLOW_SQUARE:
401 target_svg = GRR_TARGET_YELLOW_SQUARE_SVG;
403 case RR_TARGET_YELLOW_TRIANGLE:
404 target_svg = GRR_TARGET_YELLOW_TRIANGLE_SVG;
406 case RR_TARGET_WHIRL:
407 target_svg = GRR_TARGET_WHIRL_SVG;
413 grr_svg_draw (xrs, target_svg);
417 grr_robot_draw (XrState *xrs, rr_robot_t robot)
421 if (robot == RR_ROBOT_NONE)
426 robot_svg = GRR_ROBOT_BLUE_SVG;
429 robot_svg = GRR_ROBOT_GREEN_SVG;
432 robot_svg = GRR_ROBOT_RED_SVG;
434 case RR_ROBOT_YELLOW:
435 robot_svg = GRR_ROBOT_YELLOW_SVG;
441 grr_svg_draw (xrs, robot_svg);
445 grr_cell_draw (XrState *xrs, rr_cell_t cell, rr_target_t goal,
446 int width, int height)
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));
463 XrTranslate (xrs, xpad, ypad);
465 (width - 2*xpad) / GRR_SVG_ASSUMED_WIDTH,
466 (height - 2*ypad) / GRR_SVG_ASSUMED_HEIGHT);
468 target = RR_CELL_GET_TARGET (cell);
469 grr_target_draw (xrs, target);
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) {
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);
490 XrTranslate (xrs, xpad, ypad);
492 (width - 2*xpad) / GRR_SVG_ASSUMED_WIDTH,
493 (height - 2*ypad) / GRR_SVG_ASSUMED_HEIGHT);
495 grr_robot_draw (xrs, RR_CELL_GET_ROBOT (cell));
502 grr_board_view_expose (GtkWidget *widget,
503 GdkEventExpose *event)
505 grr_board_view_t *view;
509 GdkDrawable *real_drawable;
512 rr_target_t goal_target;
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);
518 if (event->count > 0)
521 view = GRR_BOARD_VIEW (widget);
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);
528 /* Ignore GTK+ and use Xr for drawing. */
530 XrSetTargetDrawable (xrs, dpy, drawable);
531 XrTranslate (xrs, -x_off, -y_off);
533 rr_board_get_size (view->board, &view->board_width, &view->board_height);
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;
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;
545 XrTranslate (xrs, view->board_pad_x, view->board_pad_y);
547 goal_target = rr_board_get_goal_target (view->board);
549 /* Draw cell targets */
550 for (j=0; j < view->board_height; j++) {
551 for (i=0; i < view->board_width; i++) {
553 XrTranslate (xrs, i * view->cell_width, j * view->cell_height);
555 rr_board_get_cell (view->board, i, j),
557 view->cell_width, view->cell_height);
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);
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);
577 /* Draw goal target in center of board */
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);
587 for (j=0; j < view->board_height; j++) {
588 for (i=0; i < view->board_width; i++) {
590 XrTranslate (xrs, i * view->cell_width, j * view->cell_height);
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)));
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)
609 *grid_x = (pointer_x - view->board_pad_x) / view->cell_width;
610 *grid_y = (pointer_y - view->board_pad_y) / view->cell_height;
615 grr_board_view_button_press (GtkWidget *widget,
616 GdkEventButton *event)
618 grr_board_view_t *view;
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);
626 view = GRR_BOARD_VIEW (widget);
628 grr_board_view_pointer_coords_to_grid (view,
632 cell = rr_board_get_cell (view->board, x, y);
633 if (!view->button && RR_CELL_GET_ROBOT (cell)) {
635 gtk_grab_add (widget);
637 view->button = event->button;
640 view->drag_robot = RR_CELL_GET_ROBOT (cell);
642 grr_board_view_update_mouse (view, event->x, event->y);
649 grr_board_view_button_release (GtkWidget *widget,
650 GdkEventButton *event)
652 grr_board_view_t *view;
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);
662 view = GRR_BOARD_VIEW (widget);
664 if (view->button == event->button) {
665 gtk_grab_remove (widget);
669 grr_board_view_pointer_coords_to_grid (view, event->x, event->y, &x, &y);
671 dx = x - view->drag_x;
672 dy = y - view->drag_y;
673 if (dx == 0 && dy == 0)
676 if (abs(dx) > abs(dy))
677 if (x > view->drag_x)
682 if (y > view->drag_y)
687 switch (view->drag_robot) {
697 case RR_ROBOT_YELLOW:
707 grr_sprintf_alloc (&move_str, "%s %s", robot, dir);
708 if (move_str == NULL)
710 rr_client_move (view->client, move_str);
719 grr_board_view_motion_notify (GtkWidget *widget,
720 GdkEventMotion *event)
722 grr_board_view_t *view;
723 GdkModifierType mods;
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);
730 view = GRR_BOARD_VIEW (widget);
732 if (view->button != 0) {
736 if (event->is_hint || (event->window != widget->window))
737 gdk_window_get_pointer (widget->window, &x, &y, &mods);
739 switch (view->button) {
741 mask = GDK_BUTTON1_MASK;
744 mask = GDK_BUTTON2_MASK;
747 mask = GDK_BUTTON3_MASK;
755 grr_board_view_update_mouse (view, x,y);
762 grr_board_view_update_mouse (grr_board_view_t *view, gint x, gint y)
764 g_return_if_fail (view != NULL);
765 g_return_if_fail (GRR_IS_BOARD_VIEW (view));
767 /* XXX: Should draw a robot here */
771 grr_board_view_update (grr_board_view_t *view)
773 g_return_if_fail (view != NULL);
774 g_return_if_fail (GRR_IS_BOARD_VIEW (view));
776 gtk_widget_queue_draw (GTK_WIDGET (view));
780 grr_sprintf_alloc (char **str, const char *fmt, ...)
786 ret = grr_sprintf_alloc_va (str, fmt, ap);
792 /* ripped more or less straight out of PRINTF(3) */
794 grr_sprintf_alloc_va (char **str, const char *fmt, va_list ap)
797 /* Guess we need no more than 100 bytes. */
800 if ((*str = malloc (size)) == NULL)
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)
808 /* Else try again with more space. */
809 if (n > -1) /* glibc 2.1 */
810 size = n+1; /* precisely what is needed */
812 size *= 2; /* twice the old size */
813 new_str = realloc(*str, size);
814 if (new_str == NULL) {