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);
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);
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);
559 /* Draw goal target in center of board */
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);
569 for (j=0; j < view->board_height; j++) {
570 for (i=0; i < view->board_width; i++) {
572 XrTranslate (xrs, i * view->cell_width, j * view->cell_height);
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)));
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)
591 *grid_x = (pointer_x - view->board_pad_x) / view->cell_width;
592 *grid_y = (pointer_y - view->board_pad_y) / view->cell_height;
597 grr_board_view_button_press (GtkWidget *widget,
598 GdkEventButton *event)
600 grr_board_view_t *view;
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);
608 view = GRR_BOARD_VIEW (widget);
610 grr_board_view_pointer_coords_to_grid (view,
614 cell = rr_board_get_cell (view->board, x, y);
615 if (!view->button && RR_CELL_GET_ROBOT (cell)) {
617 gtk_grab_add (widget);
619 view->button = event->button;
622 view->drag_robot = RR_CELL_GET_ROBOT (cell);
624 grr_board_view_update_mouse (view, event->x, event->y);
631 grr_board_view_button_release (GtkWidget *widget,
632 GdkEventButton *event)
634 grr_board_view_t *view;
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);
644 view = GRR_BOARD_VIEW (widget);
646 if (view->button == event->button) {
647 gtk_grab_remove (widget);
651 grr_board_view_pointer_coords_to_grid (view, event->x, event->y, &x, &y);
653 dx = x - view->drag_x;
654 dy = y - view->drag_y;
655 if (dx == 0 && dy == 0)
658 if (abs(dx) > abs(dy))
659 if (x > view->drag_x)
664 if (y > view->drag_y)
669 switch (view->drag_robot) {
679 case RR_ROBOT_YELLOW:
689 grr_sprintf_alloc (&move_str, "%s %s", robot, dir);
690 if (move_str == NULL)
692 rr_client_move (view->client, move_str);
701 grr_board_view_motion_notify (GtkWidget *widget,
702 GdkEventMotion *event)
704 grr_board_view_t *view;
705 GdkModifierType mods;
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);
712 view = GRR_BOARD_VIEW (widget);
714 if (view->button != 0) {
718 if (event->is_hint || (event->window != widget->window))
719 gdk_window_get_pointer (widget->window, &x, &y, &mods);
721 switch (view->button) {
723 mask = GDK_BUTTON1_MASK;
726 mask = GDK_BUTTON2_MASK;
729 mask = GDK_BUTTON3_MASK;
737 grr_board_view_update_mouse (view, x,y);
744 grr_board_view_update_mouse (grr_board_view_t *view, gint x, gint y)
746 g_return_if_fail (view != NULL);
747 g_return_if_fail (GRR_IS_BOARD_VIEW (view));
749 /* XXX: Should draw a robot here */
753 grr_board_view_update (grr_board_view_t *view)
755 g_return_if_fail (view != NULL);
756 g_return_if_fail (GRR_IS_BOARD_VIEW (view));
758 gtk_widget_queue_draw (GTK_WIDGET (view));
762 grr_sprintf_alloc (char **str, const char *fmt, ...)
768 ret = grr_sprintf_alloc_va (str, fmt, ap);
774 /* ripped more or less straight out of PRINTF(3) */
776 grr_sprintf_alloc_va (char **str, const char *fmt, va_list ap)
779 /* Guess we need no more than 100 bytes. */
782 if ((*str = malloc (size)) == NULL)
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)
790 /* Else try again with more space. */
791 if (n > -1) /* glibc 2.1 */
792 size = n+1; /* precisely what is needed */
794 size *= 2; /* twice the old size */
795 new_str = realloc(*str, size);
796 if (new_str == NULL) {