Just some CVS ignore stuff.
[dia.git] / app / navigation.c
blobfb53fce87b279dcc3e4ff011655bfe309aee181d
1 /* Dia -- a diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
4 * $Id: navigation.c,v 1.3 2005/12/27 17:18:12 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 memset (nav, 0, sizeof(NavigationWindow));
153 /*--Retrieve the diagram's data*/
154 nav->ddisp = (DDisplay *) _ddisp;
155 data = nav->ddisp->diagram->data;
157 /*--Calculate sizes*/
159 int canvas_width, canvas_height;/*pixels*/
160 int diagram_width, diagram_height;/*pixels*/
161 GtkAdjustment * adj;
163 nav->max_size = THUMBNAIL_MAX_SIZE;
165 /*size: Diagram <--> thumbnail*/
166 rect.top = data->extents.top - DIAGRAM_OFFSET;
167 rect.left = data->extents.left - DIAGRAM_OFFSET;
168 rect.bottom = data->extents.bottom + DIAGRAM_OFFSET + 1;
169 rect.right = data->extents.right + DIAGRAM_OFFSET + 1;
171 zoom = nav->max_size / MAX( (rect.right - rect.left) , (rect.bottom - rect.top) );
173 nav->width = MIN( nav->max_size, (rect.right - rect.left) * zoom);
174 nav->height = MIN( nav->max_size, (rect.bottom - rect.top) * zoom);
176 /*size: display canvas <--> frame cursor*/
177 diagram_width = (int) ddisplay_transform_length (nav->ddisp, (rect.right - rect.left));
178 diagram_height = (int) ddisplay_transform_length (nav->ddisp, (rect.bottom - rect.top));
180 canvas_width = nav->ddisp->canvas->allocation.width;
181 canvas_height = nav->ddisp->canvas->allocation.height;
183 nav->frame_w = nav->width * canvas_width / diagram_width;
184 nav->frame_h = nav->height * canvas_height / diagram_height;
186 /*reset adjustements to diagram size,*/
187 /*(dia allows to grow the canvas bigger than the diagram's actual size) */
188 /*and store the ratio thumbnail/adjustement(speedup on motion)*/
189 adj = nav->ddisp->hsbdata;
190 reset_sc_adj (adj, rect.left, rect.right, canvas_width / nav->ddisp->zoom_factor);
191 nav->hadj_coef = (adj->upper - adj->page_size - adj->lower) / (nav->width - nav->frame_w);
193 adj = nav->ddisp->vsbdata;
194 reset_sc_adj (adj, rect.top, rect.bottom, canvas_height / nav->ddisp->zoom_factor);
195 nav->vadj_coef = (adj->upper - adj->page_size - adj->lower) / (nav->height - nav->frame_h);
198 /*--GUI*/
199 /*popup window, and cute frame*/
200 popup_window = gtk_window_new (GTK_WINDOW_POPUP);
201 nav->popup_window = popup_window;
202 gtk_window_set_position (GTK_WINDOW (popup_window), GTK_WIN_POS_MOUSE);
204 frame = gtk_frame_new (NULL);
205 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_OUT);
207 /*drawing area*/
208 drawing_area = gtk_drawing_area_new ();
209 gtk_drawing_area_size (GTK_DRAWING_AREA(drawing_area), nav->width, nav->height);
211 gtk_widget_set_events (drawing_area, 0
212 | GDK_EXPOSURE_MASK
213 | GDK_POINTER_MOTION_MASK
214 | GDK_BUTTON_RELEASE_MASK
217 g_signal_connect (G_OBJECT (drawing_area), "expose_event",
218 G_CALLBACK (on_da_expose_event), NULL);
219 g_signal_connect (G_OBJECT (drawing_area), "motion_notify_event",
220 G_CALLBACK (on_da_motion_notify_event), NULL);
221 g_signal_connect (G_OBJECT (drawing_area), "button_release_event",
222 G_CALLBACK (on_da_button_release_event), NULL);
224 /*packing*/
225 gtk_container_add (GTK_CONTAINER (frame), drawing_area);
226 gtk_container_add (GTK_CONTAINER (popup_window), frame);
227 gtk_widget_show (drawing_area);
228 gtk_widget_show (frame);
229 gtk_widget_show (popup_window);
231 /*miniframe style*/
232 nav->gc = gdk_gc_new (drawing_area->window);
233 gdk_gc_set_line_attributes (nav->gc,
234 FRAME_THICKNESS,
235 GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER);
237 /*cursor*/
238 if(MIN(nav->frame_h, nav->frame_w) > STD_CURSOR_MIN){
239 nav->cursor = gdk_cursor_new (GDK_FLEUR);
241 else{/*the miniframe is very small, so we use a minimalist cursor*/
242 unsigned char cursor_none_data[] = { 0x00 };
243 GdkBitmap * bitmap;
244 GdkColor fg = { 0, 65535, 65535, 65535};
245 GdkColor bg = { 0, 0, 0, 0 };
247 bitmap = gdk_bitmap_create_from_data(NULL, cursor_none_data, 1, 1);
248 nav->cursor = gdk_cursor_new_from_pixmap(bitmap, bitmap, &fg, &bg, 1, 1);
249 g_object_unref(bitmap);
252 /*grab the pointer*/
253 gdk_pointer_grab (drawing_area->window, TRUE,
254 GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK,
255 drawing_area->window,
256 nav->cursor,
257 GDK_CURRENT_TIME);
259 /*buffer to draw the thumbnail on*/
260 nav->buffer = gdk_pixmap_new (drawing_area->window,
261 nav->width, nav->height, -1);
262 gdk_draw_rectangle (nav->buffer,
263 drawing_area->style->black_gc, TRUE,
264 0, 0, nav->width, nav->height);
266 {/*--Render the thumbnail*/
267 DiaGdkRenderer *renderer;
268 GdkColor color;
270 renderer = g_object_new (DIA_TYPE_GDK_RENDERER, NULL);
271 renderer->transform = dia_transform_new (&rect, &zoom);
272 renderer->pixmap = nav->buffer;/*render on the thumbnail buffer*/
273 renderer->gc = gdk_gc_new (nav->buffer);
275 /*Background color*/
276 color_convert (&data->bg_color, &color);
277 gdk_gc_set_foreground (renderer->gc, &color);
278 gdk_draw_rectangle (renderer->pixmap, renderer->gc, 1, 0, 0, nav->width, nav->height);
280 /*render the data*/
281 data_render (data, DIA_RENDERER (renderer), NULL, NULL, NULL);
283 g_object_ref (renderer->pixmap);
284 g_object_unref (renderer);
287 nav->is_first_expose = TRUE;/*set to request to draw the miniframe*/
291 /* resets adjustement to diagram size */
292 static void
293 reset_sc_adj (GtkAdjustment * adj, gdouble lower, gdouble upper, gdouble page)
295 adj->page_size = page;
297 adj->lower = lower;
298 adj->upper = upper;
300 if (adj->value < lower) adj->value = lower;
301 if (adj->value > (upper - page)) adj->value = upper - page;
303 gtk_adjustment_changed(adj);
307 static gboolean
308 on_da_expose_event (GtkWidget * widget, GdkEventExpose * event, gpointer unused)
310 /*refresh the part outdated by the event*/
311 gdk_draw_pixmap (widget->window,
312 widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
313 GDK_PIXMAP(nav->buffer),
314 event->area.x, event->area.y,
315 event->area.x, event->area.y,
316 event->area.width, event->area.height);
318 /*the first time, display the current display's state*/
319 if(nav->is_first_expose){
321 GtkAdjustment * adj;
322 int x, y;
324 adj = nav->ddisp->hsbdata;
325 x = (adj->value - adj->lower) / (adj->upper - adj->lower) * (nav->width) +1;
327 adj = nav->ddisp->vsbdata;
328 y = (adj->value - adj->lower) / (adj->upper - adj->lower) * (nav->height) +1;
330 /*draw directly on the window, do not buffer the miniframe*/
331 gdk_draw_rectangle (widget->window,
332 nav->gc, FALSE,
333 x, y, nav->frame_w, nav->frame_h);
335 nav->is_first_expose = FALSE;
337 return FALSE;
341 static gboolean
342 on_da_motion_notify_event (GtkWidget * drawing_area, GdkEventMotion * event, gpointer unused)
344 GtkAdjustment * adj;
345 gboolean value_changed;
347 int w = nav->frame_w;
348 int h = nav->frame_h;
350 int x, y;/*top left of the miniframe*/
352 /* Don't try to move if there's no room for it.*/
353 if (w >= nav->width-1 && h >= nav->height-1) return FALSE;
355 x = CLAMP (event->x - w/2 , 0, nav->width - w);
356 y = CLAMP (event->y - h/2 , 0, nav->height - h);
358 adj = nav->ddisp->hsbdata;
359 value_changed = FALSE;
360 if (w/2 <= event->x && event->x <= (nav->width - w/2)){
361 adj->value = adj->lower + x * nav->hadj_coef;
362 value_changed = TRUE;
364 else if (x == 0 && adj->value != adj->lower){/*you've been too fast! :)*/
365 adj->value = adj->lower;
366 value_changed = TRUE;
368 else if (x == (nav->width - w) && adj->value != (adj->upper - adj->page_size)){/*idem*/
369 adj->value = adj->upper - adj->page_size;
370 value_changed = TRUE;
372 if (value_changed) gtk_adjustment_value_changed(adj);
374 adj = nav->ddisp->vsbdata;
375 value_changed = FALSE;
376 if (h/2 <= event->y && event->y <= (nav->height - h/2)){
377 adj->value = adj->lower + y * nav->vadj_coef;
378 value_changed = TRUE;
380 else if (y == 0 && adj->value != adj->lower){/*you've been too fast! :)*/
381 adj->value = adj->lower;
382 value_changed = TRUE;
384 else if (y == (nav->height - h) && adj->value != (adj->upper - adj->page_size)){/*idem*/
385 adj->value = adj->upper - adj->page_size;
386 value_changed = TRUE;
388 if (value_changed) gtk_adjustment_value_changed(adj);
391 /*--Draw the miniframe*/
392 /*refresh from the buffer*/
393 gdk_draw_pixmap (drawing_area->window,
394 drawing_area->style->fg_gc[GTK_WIDGET_STATE (drawing_area)],
395 GDK_PIXMAP(nav->buffer),
396 0, 0, 0, 0, nav->width, nav->height);
397 /*draw directly on the window, do not buffer the miniframe*/
398 gdk_draw_rectangle (drawing_area->window,
399 nav->gc, FALSE,
400 x, y, w, h);
401 return FALSE;
405 static gboolean
406 on_da_button_release_event (GtkWidget * widget, GdkEventButton * event, gpointer unused)
408 /* Apparently there are circumstances where this is run twice for one popup
409 * Protected calls to avoid crashing on second pass.
411 if (nav->buffer)
412 g_object_unref (nav->buffer);
413 nav->buffer = NULL;
415 if (nav->gc)
416 g_object_unref (nav->gc);
417 nav->gc = NULL;
419 if (nav->cursor)
420 gdk_cursor_unref (nav->cursor);
421 nav->cursor = NULL;
423 if (nav->popup_window)
424 gtk_widget_destroy (nav->popup_window);
425 nav->popup_window = NULL;
427 /*returns the focus on the canvas*/
428 gtk_widget_grab_focus(nav->ddisp->canvas);
429 return FALSE;
432 static void
433 on_button_navigation_popup_released (GtkButton * button, gpointer z)
435 /* don't popdown before having drawn once */
436 if (!nav->is_first_expose) /* needed for gtk+-2.6.x, but work for 2.6 too. */
437 on_da_button_release_event (NULL, NULL, NULL);