Parent Directory | Revision Log
Applied Rolfs patches
1 | /* |
2 | * Copyright (C) 2008 Till Harbaum <till@harbaum.org>. |
3 | * |
4 | * This file is part of OSM2Go. |
5 | * |
6 | * OSM2Go is free software: you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation, either version 3 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * OSM2Go is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License |
17 | * along with OSM2Go. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #ifndef USE_GOOCANVAS |
21 | #error "Config error!" |
22 | #endif |
23 | |
24 | #include "appdata.h" |
25 | |
26 | /* ------------------- creating and destroying the canvas ----------------- */ |
27 | |
28 | static gint canvas_destroy_event(GtkWidget *widget, gpointer data) { |
29 | canvas_t *canvas = data; |
30 | g_free(canvas); |
31 | return FALSE; |
32 | } |
33 | |
34 | /* create a new canvas */ |
35 | canvas_t *canvas_new(void) { |
36 | canvas_t *canvas = g_new0(canvas_t, 1); |
37 | |
38 | canvas->widget = goo_canvas_new(); |
39 | |
40 | #if 0 // overlay test |
41 | /* create a static root item */ |
42 | GooCanvasItem *item = goo_canvas_ellipse_new(NULL, |
43 | 20.0, 20.0, |
44 | 10.0, 10.0, |
45 | "line-width", 2.0, |
46 | "stroke-color", "blue", |
47 | "fill-color", "yellow", |
48 | NULL); |
49 | |
50 | goo_canvas_set_static_root_item(GOO_CANVAS(canvas->widget), item); |
51 | #endif |
52 | |
53 | g_object_set_data(G_OBJECT(canvas->widget), "canvas-pointer", canvas); |
54 | |
55 | g_object_set(G_OBJECT(canvas->widget), |
56 | "anchor", GTK_ANCHOR_CENTER, |
57 | NULL); |
58 | |
59 | GooCanvasItem *root = goo_canvas_get_root_item(GOO_CANVAS(canvas->widget)); |
60 | |
61 | /* create the groups */ |
62 | canvas_group_t group; |
63 | for(group = 0; group < CANVAS_GROUPS; group++) |
64 | canvas->group[group] = goo_canvas_group_new(root, NULL); |
65 | |
66 | |
67 | gtk_signal_connect(GTK_OBJECT(canvas->widget), |
68 | "destroy", G_CALLBACK(canvas_destroy_event), canvas); |
69 | |
70 | return canvas; |
71 | } |
72 | |
73 | GtkWidget *canvas_get_widget(canvas_t *canvas) { |
74 | return canvas->widget; |
75 | } |
76 | |
77 | /* ------------------------ accessing the canvas ---------------------- */ |
78 | |
79 | void canvas_set_background(canvas_t *canvas, canvas_color_t bg_color) { |
80 | g_object_set(G_OBJECT(canvas->widget), |
81 | "background-color-rgb", bg_color >> 8, |
82 | NULL); |
83 | } |
84 | |
85 | void canvas_set_antialias(canvas_t *canvas, gboolean antialias) { |
86 | GooCanvasItem *root = goo_canvas_get_root_item(GOO_CANVAS(canvas->widget)); |
87 | g_object_set(G_OBJECT(root), "antialias", |
88 | antialias?CAIRO_ANTIALIAS_DEFAULT:CAIRO_ANTIALIAS_NONE, NULL); |
89 | } |
90 | |
91 | void canvas_window2world(canvas_t *canvas, |
92 | gint x, gint y, gint *wx, gint *wy) { |
93 | double sx = x, sy = y; |
94 | goo_canvas_convert_from_pixels(GOO_CANVAS(canvas->widget), &sx, &sy); |
95 | *wx = sx; *wy = sy; |
96 | } |
97 | |
98 | canvas_item_t *canvas_get_item_at(canvas_t *canvas, gint x, gint y) { |
99 | #ifdef CANVAS_CUSTOM_ITEM_AT |
100 | return canvas_item_info_get_at(canvas, x, y); |
101 | #else |
102 | GList *list = |
103 | goo_canvas_get_items_at(GOO_CANVAS(canvas->widget), x, y, TRUE); |
104 | |
105 | GList *litem = g_list_first(list); |
106 | while(litem) { |
107 | canvas_item_t *item = (canvas_item_t*)litem->data; |
108 | |
109 | /* only return objects that have a "user data" attached */ |
110 | if(g_object_get_data(G_OBJECT(item), "user data")) { |
111 | /* check if this is in one of the "selectable" groups */ |
112 | canvas_item_t *parent = goo_canvas_item_get_parent(item); |
113 | canvas_group_t group; |
114 | for(group=0;group<CANVAS_GROUPS;group++) { |
115 | if((CANVAS_SELECTABLE & (1<<group)) && |
116 | (canvas->group[group] == parent)) { |
117 | g_list_free(list); |
118 | return item; |
119 | } |
120 | } |
121 | } |
122 | litem = g_list_next(litem); |
123 | } |
124 | |
125 | g_list_free(list); |
126 | return NULL; |
127 | #endif |
128 | } |
129 | |
130 | void canvas_set_zoom(canvas_t *canvas, gdouble zoom) { |
131 | goo_canvas_set_scale(GOO_CANVAS(canvas->widget), zoom); |
132 | } |
133 | |
134 | gdouble canvas_get_zoom(canvas_t *canvas) { |
135 | return goo_canvas_get_scale(GOO_CANVAS(canvas->widget)); |
136 | } |
137 | |
138 | gdouble canvas_get_viewport_width(canvas_t *canvas, canvas_unit_t unit) { |
139 | // Canvas viewport dimensions |
140 | |
141 | GtkAllocation *a = &(canvas->widget)->allocation; |
142 | if(unit == CANVAS_UNIT_PIXEL) return a->width; |
143 | |
144 | /* convert to meters by dividing by zoom */ |
145 | gdouble zoom = goo_canvas_get_scale(GOO_CANVAS(canvas->widget)); |
146 | return a->width / zoom; |
147 | } |
148 | |
149 | gdouble canvas_get_viewport_height(canvas_t *canvas, canvas_unit_t unit) { |
150 | // Canvas viewport dimensions |
151 | GtkAllocation *a = &(canvas->widget)->allocation; |
152 | if(unit == CANVAS_UNIT_PIXEL) return a->height; |
153 | |
154 | /* convert to meters by dividing by zoom */ |
155 | gdouble zoom = goo_canvas_get_scale(GOO_CANVAS(canvas->widget)); |
156 | return a->height / zoom; |
157 | } |
158 | |
159 | /* get scroll position in meters/pixels */ |
160 | void canvas_scroll_get(canvas_t *canvas, canvas_unit_t unit, |
161 | gint *sx, gint *sy) { |
162 | gdouble zoom = goo_canvas_get_scale(GOO_CANVAS(canvas->widget)); |
163 | |
164 | GtkAdjustment *hadj = ((struct _GooCanvas*)(canvas->widget))->hadjustment; |
165 | GtkAdjustment *vadj = ((struct _GooCanvas*)(canvas->widget))->vadjustment; |
166 | |
167 | gdouble hs = gtk_adjustment_get_value(hadj); |
168 | gdouble vs = gtk_adjustment_get_value(vadj); |
169 | goo_canvas_convert_from_pixels(GOO_CANVAS(canvas->widget), &hs, &vs); |
170 | |
171 | /* convert to position relative to screen center */ |
172 | hs += canvas->widget->allocation.width/(2*zoom); |
173 | vs += canvas->widget->allocation.height/(2*zoom); |
174 | |
175 | if(unit == CANVAS_UNIT_PIXEL) { |
176 | /* make values zoom independant */ |
177 | *sx = hs * zoom; |
178 | *sy = vs * zoom; |
179 | } else { |
180 | *sx = hs; |
181 | *sy = vs; |
182 | } |
183 | } |
184 | |
185 | /* set scroll position in meters/pixels */ |
186 | void canvas_scroll_to(canvas_t *canvas, canvas_unit_t unit, gint sx, gint sy) { |
187 | gdouble zoom = goo_canvas_get_scale(GOO_CANVAS(canvas->widget)); |
188 | |
189 | if(unit != CANVAS_UNIT_METER) { |
190 | sx /= zoom; sy /= zoom; |
191 | } |
192 | |
193 | /* adjust to screen center */ |
194 | sx -= canvas->widget->allocation.width/(2*zoom); |
195 | sy -= canvas->widget->allocation.height/(2*zoom); |
196 | |
197 | goo_canvas_scroll_to(GOO_CANVAS(canvas->widget), sx, sy); |
198 | } |
199 | |
200 | void canvas_set_bounds(canvas_t *canvas, gint minx, gint miny, |
201 | gint maxx, gint maxy) { |
202 | goo_canvas_set_bounds(GOO_CANVAS(canvas->widget), minx, miny, maxx, maxy); |
203 | } |
204 | |
205 | /* ------------------- creating and destroying objects ---------------- */ |
206 | |
207 | void canvas_erase(canvas_t *canvas, gint group_mask) { |
208 | canvas_group_t group; |
209 | for(group=0;group<CANVAS_GROUPS;group++) { |
210 | |
211 | if(group_mask & (1<<group)) { |
212 | gint children = goo_canvas_item_get_n_children(canvas->group[group]); |
213 | printf("Removing %d children from group %d\n", children, group); |
214 | while(children--) |
215 | goo_canvas_item_remove_child(canvas->group[group], children); |
216 | } |
217 | } |
218 | } |
219 | |
220 | |
221 | canvas_item_t *canvas_circle_new(canvas_t *canvas, canvas_group_t group, |
222 | gint x, gint y, gint radius, gint border, |
223 | canvas_color_t fill_col, canvas_color_t border_col) { |
224 | |
225 | canvas_item_t *item = |
226 | goo_canvas_ellipse_new(canvas->group[group], |
227 | (gdouble) x, (gdouble) y, |
228 | (gdouble) radius, (gdouble) radius, |
229 | "line-width", (double)border, |
230 | "stroke-color-rgba", border_col, |
231 | "fill-color-rgba", fill_col, |
232 | NULL); |
233 | |
234 | #ifdef CANVAS_CUSTOM_ITEM_AT |
235 | if(CANVAS_SELECTABLE & (1<<group)) |
236 | canvas_item_info_attach_circle(canvas, group, item, x, y, radius + border); |
237 | #endif |
238 | |
239 | return item; |
240 | } |
241 | |
242 | canvas_points_t *canvas_points_new(gint points) { |
243 | return goo_canvas_points_new(points); |
244 | } |
245 | |
246 | void canvas_point_set_pos(canvas_points_t *points, gint index, lpos_t *lpos) { |
247 | points->coords[2*index+0] = lpos->x; |
248 | points->coords[2*index+1] = lpos->y; |
249 | } |
250 | |
251 | void canvas_points_free(canvas_points_t *points) { |
252 | goo_canvas_points_unref(points); |
253 | } |
254 | |
255 | gint canvas_points_num(canvas_points_t *points) { |
256 | return points->num_points; |
257 | } |
258 | |
259 | void canvas_point_get_lpos(canvas_points_t *points, gint index, lpos_t *lpos) { |
260 | lpos->x = points->coords[2*index+0]; |
261 | lpos->y = points->coords[2*index+1]; |
262 | } |
263 | |
264 | canvas_item_t *canvas_polyline_new(canvas_t *canvas, canvas_group_t group, |
265 | canvas_points_t *points, gint width, canvas_color_t color) { |
266 | canvas_item_t *item = |
267 | goo_canvas_polyline_new(canvas->group[group], FALSE, 0, |
268 | "points", points, |
269 | "line-width", (double)width, |
270 | "stroke-color-rgba", color, |
271 | "line-join", CAIRO_LINE_JOIN_ROUND, |
272 | "line-cap", CAIRO_LINE_CAP_ROUND, |
273 | NULL); |
274 | |
275 | #ifdef CANVAS_CUSTOM_ITEM_AT |
276 | if(CANVAS_SELECTABLE & (1<<group)) |
277 | canvas_item_info_attach_poly(canvas, group, item, FALSE, points, width); |
278 | #endif |
279 | |
280 | return item; |
281 | } |
282 | |
283 | canvas_item_t *canvas_polygon_new(canvas_t *canvas, canvas_group_t group, |
284 | canvas_points_t *points, gint width, canvas_color_t color, |
285 | canvas_color_t fill) { |
286 | canvas_item_t *item = |
287 | goo_canvas_polyline_new(canvas->group[group], TRUE, 0, |
288 | "points", points, |
289 | "line-width", (double)width, |
290 | "stroke-color-rgba", color, |
291 | "fill-color-rgba", fill, |
292 | "line-join", CAIRO_LINE_JOIN_ROUND, |
293 | "line-cap", CAIRO_LINE_CAP_ROUND, |
294 | NULL); |
295 | |
296 | #ifdef CANVAS_CUSTOM_ITEM_AT |
297 | if(CANVAS_SELECTABLE & (1<<group)) |
298 | canvas_item_info_attach_poly(canvas, group, item, TRUE, points, width); |
299 | #endif |
300 | |
301 | return item; |
302 | } |
303 | |
304 | /* place the image in pix centered on x/y on the canvas */ |
305 | canvas_item_t *canvas_image_new(canvas_t *canvas, canvas_group_t group, |
306 | GdkPixbuf *pix, gint x, gint y, float hscale, float vscale) { |
307 | |
308 | canvas_item_t *item = goo_canvas_image_new(canvas->group[group], pix, |
309 | x/hscale - gdk_pixbuf_get_width(pix)/2, |
310 | y/vscale - gdk_pixbuf_get_height(pix)/2, NULL); |
311 | goo_canvas_item_scale(item, hscale, vscale); |
312 | |
313 | #ifdef CANVAS_CUSTOM_ITEM_AT |
314 | if(CANVAS_SELECTABLE & (1<<group)) { |
315 | gint radius = 0.75 * hscale * MAX(gdk_pixbuf_get_width(pix), gdk_pixbuf_get_height(pix)); /* hscale and vscale are the same */ |
316 | canvas_item_info_attach_circle(canvas, group, item, x, y, radius); |
317 | } |
318 | #endif |
319 | |
320 | return item; |
321 | } |
322 | |
323 | void canvas_item_destroy(canvas_item_t *item) { |
324 | goo_canvas_item_remove(item); |
325 | } |
326 | |
327 | /* ------------------------ accessing items ---------------------- */ |
328 | |
329 | void canvas_item_set_points(canvas_item_t *item, canvas_points_t *points) { |
330 | g_object_set(G_OBJECT(item), "points", points, NULL); |
331 | } |
332 | |
333 | void canvas_item_set_pos(canvas_item_t *item, lpos_t *lpos) { |
334 | g_object_set(G_OBJECT(item), |
335 | "center-x", (gdouble)lpos->x, |
336 | "center-y", (gdouble)lpos->y, |
337 | NULL); |
338 | } |
339 | |
340 | void canvas_item_set_radius(canvas_item_t *item, gint radius) { |
341 | g_object_set(G_OBJECT(item), |
342 | "radius-x", (gdouble)radius, |
343 | "radius-y", (gdouble)radius, |
344 | NULL); |
345 | } |
346 | |
347 | void canvas_item_to_bottom(canvas_item_t *item) { |
348 | |
349 | |
350 | goo_canvas_item_lower(item, NULL); |
351 | #ifdef CANVAS_CUSTOM_ITEM_AT |
352 | canvas_t *canvas = |
353 | g_object_get_data(G_OBJECT(goo_canvas_item_get_canvas(item)), |
354 | "canvas-pointer"); |
355 | |
356 | g_assert(canvas); |
357 | canvas_item_info_push(canvas, item); |
358 | #endif |
359 | } |
360 | |
361 | void canvas_item_set_zoom_max(canvas_item_t *item, float zoom_max) { |
362 | gdouble vis_thres = zoom_max; |
363 | GooCanvasItemVisibility vis |
364 | = GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD; |
365 | if (vis_thres < 0) { |
366 | vis_thres = 0; |
367 | vis = GOO_CANVAS_ITEM_VISIBLE; |
368 | } |
369 | g_object_set(G_OBJECT(item), |
370 | "visibility", vis, |
371 | "visibility-threshold", vis_thres, |
372 | NULL); |
373 | } |
374 | |
375 | void canvas_item_set_dashed(canvas_item_t *item, |
376 | gint line_width, gint dash_length) { |
377 | GooCanvasLineDash *dash; |
378 | if (dash_length <= 0) { |
379 | dash_length = line_width + 1; |
380 | } |
381 | gfloat off_len = dash_length; |
382 | gfloat on_len = dash_length; |
383 | guint cap = CAIRO_LINE_CAP_BUTT; |
384 | if (dash_length > line_width) { |
385 | off_len += ((gfloat)line_width)/2; |
386 | on_len -= ((gfloat)line_width)/2; |
387 | cap = CAIRO_LINE_CAP_ROUND; |
388 | } |
389 | dash = goo_canvas_line_dash_new(2, on_len, off_len, 0); |
390 | goo_canvas_line_dash_ref(dash); |
391 | g_object_set(G_OBJECT(item), |
392 | "line-dash", dash, |
393 | "line-cap", cap, |
394 | NULL); |
395 | } |
396 | |
397 | void canvas_item_set_user_data(canvas_item_t *item, void *data) { |
398 | g_object_set_data(G_OBJECT(item), "user data", data); |
399 | } |
400 | |
401 | void *canvas_item_get_user_data(canvas_item_t *item) { |
402 | return g_object_get_data(G_OBJECT(item), "user data"); |
403 | } |
404 | |
405 | typedef struct { |
406 | GCallback c_handler; |
407 | gpointer data; |
408 | } weak_t; |
409 | |
410 | static void canvas_item_weak_notify(gpointer data, GObject *invalid) { |
411 | weak_t *weak = data; |
412 | |
413 | ((void(*)(GtkWidget*, gpointer))weak->c_handler) (NULL, weak->data); |
414 | g_free(weak); |
415 | } |
416 | |
417 | void canvas_item_destroy_connect(canvas_item_t *item, |
418 | GCallback c_handler, gpointer data) { |
419 | weak_t *weak = g_new(weak_t,1); |
420 | weak->data = data; |
421 | weak->c_handler = c_handler; |
422 | |
423 | g_object_weak_ref(G_OBJECT(item), canvas_item_weak_notify, weak); |
424 | } |
425 | |
426 | void canvas_image_move(canvas_item_t *item, gint x, gint y, |
427 | float hscale, float vscale) { |
428 | |
429 | g_object_set(G_OBJECT(item), |
430 | "x", (gdouble)x / hscale, |
431 | "y", (gdouble)y / vscale, |
432 | NULL); |
433 | } |
434 | |
435 | /* get the polygon/polyway segment a certain coordinate is over */ |
436 | gint canvas_item_get_segment(canvas_item_t *item, gint x, gint y) { |
437 | |
438 | canvas_points_t *points = NULL; |
439 | double line_width = 0; |
440 | |
441 | g_object_get(G_OBJECT(item), |
442 | "points", &points, |
443 | "line-width", &line_width, |
444 | NULL); |
445 | |
446 | if(!points) return -1; |
447 | |
448 | gint retval = -1, i; |
449 | double mindist = 100; |
450 | for(i=0;i<points->num_points-1;i++) { |
451 | |
452 | #define AX (points->coords[2*i+0]) |
453 | #define AY (points->coords[2*i+1]) |
454 | #define BX (points->coords[2*i+2]) |
455 | #define BY (points->coords[2*i+3]) |
456 | #define CX ((double)x) |
457 | #define CY ((double)y) |
458 | |
459 | double len2 = pow(BY-AY,2)+pow(BX-AX,2); |
460 | double m = ((CX-AX)*(BX-AX)+(CY-AY)*(BY-AY)) / len2; |
461 | |
462 | /* this is a possible candidate */ |
463 | if((m >= 0.0) && (m <= 1.0)) { |
464 | |
465 | double n; |
466 | if(fabs(BX-AX) > fabs(BY-AY)) |
467 | n = fabs(sqrt(len2) * (AY+m*(BY-AY)-CY)/(BX-AX)); |
468 | else |
469 | n = fabs(sqrt(len2) * -(AX+m*(BX-AX)-CX)/(BY-AY)); |
470 | |
471 | /* check if this is actually on the line and closer than anything */ |
472 | /* we found so far */ |
473 | if((n <= line_width/2) && (n < mindist)) { |
474 | retval = i; |
475 | mindist = n; |
476 | } |
477 | } |
478 | } |
479 | |
480 | /* the last and first point are identical for polygons in osm2go. */ |
481 | /* goocanvas doesn't need that, but that's how OSM works and it saves */ |
482 | /* us from having to check the last->first connection for polygons */ |
483 | /* seperately */ |
484 | |
485 | return retval; |
486 | } |
487 | |
488 | void canvas_item_get_segment_pos(canvas_item_t *item, gint seg, |
489 | gint *x0, gint *y0, gint *x1, gint *y1) { |
490 | printf("get segment %d of item %p\n", seg, item); |
491 | |
492 | canvas_points_t *points = NULL; |
493 | g_object_get(G_OBJECT(item), "points", &points, NULL); |
494 | |
495 | g_assert(points); |
496 | g_assert(seg < points->num_points-1); |
497 | |
498 | *x0 = points->coords[2 * seg + 0]; |
499 | *y0 = points->coords[2 * seg + 1]; |
500 | *x1 = points->coords[2 * seg + 2]; |
501 | *y1 = points->coords[2 * seg + 3]; |
502 | } |