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
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.
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)*/
46 gboolean is_first_expose
;
54 /*factors to translate thumbnail coordinates to adjustement values*/
58 /*diagram thumbnail's buffer*/
61 /*display to navigate*/
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
[] = {
103 navigation_popup_new (DDisplay
*ddisp
)
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
),
123 &(button
->style
->bg
[GTK_STATE_NORMAL
]),
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
);
139 on_button_navigation_popup_pressed (GtkButton
* button
, gpointer _ddisp
)
141 GtkWidget
* popup_window
;
144 GtkWidget
* drawing_area
;
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*/
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
);
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
);
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
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
);
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
);
232 nav
->gc
= gdk_gc_new (drawing_area
->window
);
233 gdk_gc_set_line_attributes (nav
->gc
,
235 GDK_LINE_SOLID
, GDK_CAP_BUTT
, GDK_JOIN_MITER
);
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 };
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
);
253 gdk_pointer_grab (drawing_area
->window
, TRUE
,
254 GDK_BUTTON_RELEASE_MASK
| GDK_BUTTON_MOTION_MASK
,
255 drawing_area
->window
,
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
;
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
);
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
);
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 */
293 reset_sc_adj (GtkAdjustment
* adj
, gdouble lower
, gdouble upper
, gdouble page
)
295 adj
->page_size
= page
;
300 if (adj
->value
< lower
) adj
->value
= lower
;
301 if (adj
->value
> (upper
- page
)) adj
->value
= upper
- page
;
303 gtk_adjustment_changed(adj
);
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
){
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
,
333 x
, y
, nav
->frame_w
, nav
->frame_h
);
335 nav
->is_first_expose
= FALSE
;
342 on_da_motion_notify_event (GtkWidget
* drawing_area
, GdkEventMotion
* event
, gpointer unused
)
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
,
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.
412 g_object_unref (nav
->buffer
);
416 g_object_unref (nav
->gc
);
420 gdk_cursor_unref (nav
->cursor
);
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
);
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
);