more leaks plugged and more *_OPTIONAL
[dia.git] / app / navigation.c
blob1660a5dd9f913a946684bab83fc6ffd94f2cfaf3
1 /* Dia -- a diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
4 * $Id: navigation.c,v 1.2 2004/01/06 19:37:31 hans Exp $
6 * navigation.c : a navigation popup window to browse large diagrams.
7 * Copyright (C) 2003 Luc Pionchon
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 #include <gtk/gtk.h>
27 #include "diagram.h"
28 #include "display.h"
29 #include "diagdkrenderer.h"
31 #include "navigation.h"
34 #define THUMBNAIL_MAX_SIZE 150 /*(pixels) this may be a preference*/
37 struct _NavigationWindow
39 GtkWidget * popup_window;
41 /*popup size (drawing_area)*/
42 int width;
43 int height;
44 int max_size;
46 gboolean is_first_expose;
48 /*miniframe*/
49 int frame_w;
50 int frame_h;
51 GdkGC * gc;
52 GdkCursor * cursor;
54 /*factors to translate thumbnail coordinates to adjustement values*/
55 gdouble hadj_coef;
56 gdouble vadj_coef;
58 /*diagram thumbnail's buffer*/
59 GdkPixmap * buffer;
61 /*display to navigate*/
62 DDisplay * ddisp;
65 typedef struct _NavigationWindow NavigationWindow;
67 static NavigationWindow _nav;
68 static NavigationWindow * nav = &_nav;
71 #define DIAGRAM_OFFSET 1 /*(diagram's unit) so we can see the little green boxes :)*/
72 #define FRAME_THICKNESS 2 /*(pixels)*/
73 #define STD_CURSOR_MIN 16 /*(pixels)*/
76 static void on_button_navigation_popup_pressed (GtkButton * button, gpointer _ddisp);
77 static void on_button_navigation_popup_released (GtkButton * button, gpointer unused);
79 static void reset_sc_adj (GtkAdjustment * adj, gdouble lower, gdouble upper, gdouble page);
81 static gboolean on_da_expose_event (GtkWidget * widget, GdkEventExpose * event, gpointer unused);
82 static gboolean on_da_motion_notify_event (GtkWidget * widget, GdkEventMotion * event, gpointer unused);
83 static gboolean on_da_button_release_event (GtkWidget * widget, GdkEventButton * event, gpointer popup_window);
86 static char * nav_xpm[] = {
87 "10 10 2 1",
88 " c None",
89 ". c #000000",
90 " .. ",
91 " .... ",
92 " .. ",
93 " . .. . ",
94 "..........",
95 "..........",
96 " . .. . ",
97 " .. ",
98 " .... ",
99 " .. "
102 GtkWidget *
103 navigation_popup_new (DDisplay *ddisp)
105 GtkWidget * button;
107 GtkWidget * image;
108 GdkPixmap * pixmap;
109 GdkBitmap * mask = NULL;
111 button = gtk_button_new ();
112 gtk_container_set_border_width (GTK_CONTAINER (button), 0);
113 gtk_button_set_relief (GTK_BUTTON(button), GTK_RELIEF_NONE);
114 g_signal_connect (G_OBJECT (button), "pressed",
115 G_CALLBACK (on_button_navigation_popup_pressed), ddisp);
116 /*if you are fast, the button catches it before the drawing_area:*/
117 g_signal_connect (G_OBJECT (button), "released",
118 G_CALLBACK (on_button_navigation_popup_released), NULL);
120 pixmap = gdk_pixmap_colormap_create_from_xpm_d(NULL,
121 gtk_widget_get_colormap(button),
122 &mask,
123 &(button->style->bg[GTK_STATE_NORMAL]),
124 nav_xpm);
126 image = gtk_image_new_from_pixmap (pixmap, mask);
127 g_object_unref(pixmap);
128 g_object_unref(mask);
130 gtk_container_add (GTK_CONTAINER (button), image);
131 gtk_widget_show(image);
134 return button;
138 static void
139 on_button_navigation_popup_pressed (GtkButton * button, gpointer _ddisp)
141 GtkWidget * popup_window;
142 GtkWidget * frame;
144 GtkWidget * drawing_area;
146 DiagramData * data;
148 Rectangle rect;/*diagram's extents*/
149 real zoom;/*zoom factor for thumbnail rendering*/
152 /*--Retrieve the diagram's data*/
153 nav->ddisp = (DDisplay *) _ddisp;
154 data = nav->ddisp->diagram->data;
156 /*--Calculate sizes*/
158 int canvas_width, canvas_height;/*pixels*/
159 int diagram_width, diagram_height;/*pixels*/
160 GtkAdjustment * adj;
162 nav->max_size = THUMBNAIL_MAX_SIZE;
164 /*size: Diagram <--> thumbnail*/
165 rect.top = data->extents.top - DIAGRAM_OFFSET;
166 rect.left = data->extents.left - DIAGRAM_OFFSET;
167 rect.bottom = data->extents.bottom + DIAGRAM_OFFSET + 1;
168 rect.right = data->extents.right + DIAGRAM_OFFSET + 1;
170 zoom = nav->max_size / MAX( (rect.right - rect.left) , (rect.bottom - rect.top) );
172 nav->width = MIN( nav->max_size, (rect.right - rect.left) * zoom);
173 nav->height = MIN( nav->max_size, (rect.bottom - rect.top) * zoom);
175 /*size: display canvas <--> frame cursor*/
176 diagram_width = (int) ddisplay_transform_length (nav->ddisp, (rect.right - rect.left));
177 diagram_height = (int) ddisplay_transform_length (nav->ddisp, (rect.bottom - rect.top));
179 canvas_width = nav->ddisp->canvas->allocation.width;
180 canvas_height = nav->ddisp->canvas->allocation.height;
182 nav->frame_w = nav->width * canvas_width / diagram_width;
183 nav->frame_h = nav->height * canvas_height / diagram_height;
185 /*reset adjustements to diagram size,*/
186 /*(dia allows to grow the canvas bigger than the diagram's actual size) */
187 /*and store the ratio thumbnail/adjustement(speedup on motion)*/
188 adj = nav->ddisp->hsbdata;
189 reset_sc_adj (adj, rect.left, rect.right, canvas_width / nav->ddisp->zoom_factor);
190 nav->hadj_coef = (adj->upper - adj->page_size - adj->lower) / (nav->width - nav->frame_w);
192 adj = nav->ddisp->vsbdata;
193 reset_sc_adj (adj, rect.top, rect.bottom, canvas_height / nav->ddisp->zoom_factor);
194 nav->vadj_coef = (adj->upper - adj->page_size - adj->lower) / (nav->height - nav->frame_h);
197 /*--GUI*/
198 /*popup window, and cute frame*/
199 popup_window = gtk_window_new (GTK_WINDOW_POPUP);
200 nav->popup_window = popup_window;
201 gtk_window_set_position (GTK_WINDOW (popup_window), GTK_WIN_POS_MOUSE);
203 frame = gtk_frame_new (NULL);
204 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_OUT);
206 /*drawing area*/
207 drawing_area = gtk_drawing_area_new ();
208 gtk_drawing_area_size (GTK_DRAWING_AREA(drawing_area), nav->width, nav->height);
210 gtk_widget_set_events (drawing_area, 0
211 | GDK_EXPOSURE_MASK
212 | GDK_POINTER_MOTION_MASK
213 | GDK_BUTTON_RELEASE_MASK
216 g_signal_connect (G_OBJECT (drawing_area), "expose_event",
217 G_CALLBACK (on_da_expose_event), NULL);
218 g_signal_connect (G_OBJECT (drawing_area), "motion_notify_event",
219 G_CALLBACK (on_da_motion_notify_event), NULL);
220 g_signal_connect (G_OBJECT (drawing_area), "button_release_event",
221 G_CALLBACK (on_da_button_release_event), NULL);
223 /*packing*/
224 gtk_container_add (GTK_CONTAINER (frame), drawing_area);
225 gtk_container_add (GTK_CONTAINER (popup_window), frame);
226 gtk_widget_show (drawing_area);
227 gtk_widget_show (frame);
228 gtk_widget_show (popup_window);
230 /*miniframe style*/
231 nav->gc = gdk_gc_new (drawing_area->window);
232 gdk_gc_set_line_attributes (nav->gc,
233 FRAME_THICKNESS,
234 GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER);
236 /*cursor*/
237 if(MIN(nav->frame_h, nav->frame_w) > STD_CURSOR_MIN){
238 nav->cursor = gdk_cursor_new (GDK_FLEUR);
240 else{/*the miniframe is very small, so we use a minimalist cursor*/
241 unsigned char cursor_none_data[] = { 0x00 };
242 GdkBitmap * bitmap;
243 GdkColor fg = { 0, 65535, 65535, 65535};
244 GdkColor bg = { 0, 0, 0, 0 };
246 bitmap = gdk_bitmap_create_from_data(NULL, cursor_none_data, 1, 1);
247 nav->cursor = gdk_cursor_new_from_pixmap(bitmap, bitmap, &fg, &bg, 1, 1);
248 g_object_unref(bitmap);
251 /*grab the pointer*/
252 gdk_pointer_grab (drawing_area->window, TRUE,
253 GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK,
254 drawing_area->window,
255 nav->cursor,
256 GDK_CURRENT_TIME);
258 /*buffer to draw the thumbnail on*/
259 nav->buffer = gdk_pixmap_new (drawing_area->window,
260 nav->width, nav->height, -1);
261 gdk_draw_rectangle (nav->buffer,
262 drawing_area->style->black_gc, TRUE,
263 0, 0, nav->width, nav->height);
265 {/*--Render the thumbnail*/
266 DiaGdkRenderer *renderer;
267 GdkColor color;
269 renderer = g_object_new (DIA_TYPE_GDK_RENDERER, NULL);
270 renderer->transform = dia_transform_new (&rect, &zoom);
271 renderer->pixmap = nav->buffer;/*render on the thumbnail buffer*/
272 renderer->gc = gdk_gc_new (nav->buffer);
274 /*Background color*/
275 color_convert (&data->bg_color, &color);
276 gdk_gc_set_foreground (renderer->gc, &color);
277 gdk_draw_rectangle (renderer->pixmap, renderer->gc, 1, 0, 0, nav->width, nav->height);
279 /*render the data*/
280 data_render (data, DIA_RENDERER (renderer), NULL, NULL, NULL);
282 g_object_ref (renderer->pixmap);
283 g_object_unref (renderer);
286 nav->is_first_expose = TRUE;/*set to request to draw the miniframe*/
290 /* resets adjustement to diagram size */
291 static void
292 reset_sc_adj (GtkAdjustment * adj, gdouble lower, gdouble upper, gdouble page)
294 adj->page_size = page;
296 adj->lower = lower;
297 adj->upper = upper;
299 if (adj->value < lower) adj->value = lower;
300 if (adj->value > (upper - page)) adj->value = upper - page;
302 gtk_adjustment_changed(adj);
306 static gboolean
307 on_da_expose_event (GtkWidget * widget, GdkEventExpose * event, gpointer unused)
309 /*refresh the part outdated by the event*/
310 gdk_draw_pixmap (widget->window,
311 widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
312 GDK_PIXMAP(nav->buffer),
313 event->area.x, event->area.y,
314 event->area.x, event->area.y,
315 event->area.width, event->area.height);
317 /*the first time, display the current display's state*/
318 if(nav->is_first_expose){
320 GtkAdjustment * adj;
321 int x, y;
323 adj = nav->ddisp->hsbdata;
324 x = (adj->value - adj->lower) / (adj->upper - adj->lower) * (nav->width) +1;
326 adj = nav->ddisp->vsbdata;
327 y = (adj->value - adj->lower) / (adj->upper - adj->lower) * (nav->height) +1;
329 /*draw directly on the window, do not buffer the miniframe*/
330 gdk_draw_rectangle (widget->window,
331 nav->gc, FALSE,
332 x, y, nav->frame_w, nav->frame_h);
334 nav->is_first_expose = FALSE;
336 return FALSE;
340 static gboolean
341 on_da_motion_notify_event (GtkWidget * drawing_area, GdkEventMotion * event, gpointer unused)
343 GtkAdjustment * adj;
344 gboolean value_changed;
346 int w = nav->frame_w;
347 int h = nav->frame_h;
349 int x, y;/*top left of the miniframe*/
351 /* Don't try to move if there's no room for it.*/
352 if (w >= nav->width-1 && h >= nav->height-1) return FALSE;
354 x = CLAMP (event->x - w/2 , 0, nav->width - w);
355 y = CLAMP (event->y - h/2 , 0, nav->height - h);
357 adj = nav->ddisp->hsbdata;
358 value_changed = FALSE;
359 if (w/2 <= event->x && event->x <= (nav->width - w/2)){
360 adj->value = adj->lower + x * nav->hadj_coef;
361 value_changed = TRUE;
363 else if (x == 0 && adj->value != adj->lower){/*you've been too fast! :)*/
364 adj->value = adj->lower;
365 value_changed = TRUE;
367 else if (x == (nav->width - w) && adj->value != (adj->upper - adj->page_size)){/*idem*/
368 adj->value = adj->upper - adj->page_size;
369 value_changed = TRUE;
371 if (value_changed) gtk_adjustment_value_changed(adj);
373 adj = nav->ddisp->vsbdata;
374 value_changed = FALSE;
375 if (h/2 <= event->y && event->y <= (nav->height - h/2)){
376 adj->value = adj->lower + y * nav->vadj_coef;
377 value_changed = TRUE;
379 else if (y == 0 && adj->value != adj->lower){/*you've been too fast! :)*/
380 adj->value = adj->lower;
381 value_changed = TRUE;
383 else if (y == (nav->height - h) && adj->value != (adj->upper - adj->page_size)){/*idem*/
384 adj->value = adj->upper - adj->page_size;
385 value_changed = TRUE;
387 if (value_changed) gtk_adjustment_value_changed(adj);
390 /*--Draw the miniframe*/
391 /*refresh from the buffer*/
392 gdk_draw_pixmap (drawing_area->window,
393 drawing_area->style->fg_gc[GTK_WIDGET_STATE (drawing_area)],
394 GDK_PIXMAP(nav->buffer),
395 0, 0, 0, 0, nav->width, nav->height);
396 /*draw directly on the window, do not buffer the miniframe*/
397 gdk_draw_rectangle (drawing_area->window,
398 nav->gc, FALSE,
399 x, y, w, h);
400 return FALSE;
404 static gboolean
405 on_da_button_release_event (GtkWidget * widget, GdkEventButton * event, gpointer unused)
407 g_object_unref (nav->buffer);
408 g_object_unref (nav->gc);
409 gdk_cursor_unref (nav->cursor);
411 gtk_widget_destroy (nav->popup_window);
413 /*returns the focus on the canvas*/
414 gtk_widget_grab_focus(nav->ddisp->canvas);
415 return FALSE;
418 static void
419 on_button_navigation_popup_released (GtkButton * button, gpointer z)
421 on_da_button_release_event (NULL, NULL, NULL);