Parent Directory | Revision Log
No copy'n paste on fremantle
1 | /* |
2 | * Copyright (C) 2008 Till Harbaum <till@harbaum.org>. |
3 | * |
4 | * This file is part of GPXView. |
5 | * |
6 | * GPXView 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 | * GPXView 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 GPXView. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include "gpxview.h" |
21 | |
22 | #define PP_WIDTH 150 |
23 | #define PP_HEIGHT 150 |
24 | |
25 | #define MAX_POS 250 // positions saved per chunk |
26 | |
27 | typedef struct { |
28 | double lat, lon; |
29 | } dpos_t; |
30 | |
31 | typedef struct pos_list { |
32 | int len; |
33 | pos_t pos[MAX_POS]; |
34 | struct pos_list *next; |
35 | } pos_list_t; |
36 | |
37 | typedef struct { |
38 | appdata_t *appdata; |
39 | guint handler_id; |
40 | GtkWidget *area; |
41 | GtkWidget *total_label, *range_label; |
42 | GtkWidget *lat_label, *lon_label; |
43 | GdkPixmap *pixmap; |
44 | pos_list_t *pos_list; |
45 | |
46 | /* values calculated on update */ |
47 | pos_t min, max, mid; |
48 | dpos_t avg; |
49 | double scale; |
50 | int total; |
51 | |
52 | } pp_context_t; |
53 | |
54 | GdkGC *clone_gc(GdkPixmap *pixmap, GtkWidget *widget, char *color) { |
55 | GdkGC *gc = gdk_gc_new(pixmap); |
56 | gdk_gc_copy(gc, widget->style->black_gc); |
57 | GdkColor used_color; |
58 | gdk_color_parse(color, &used_color); // green |
59 | gdk_gc_set_rgb_fg_color(gc, &used_color); |
60 | |
61 | return gc; |
62 | } |
63 | |
64 | static void context_update(pp_context_t *context) { |
65 | /* count stored positions and get min/max values */ |
66 | context->total = 0; |
67 | context->min.lat = context->min.lon = 180.0; |
68 | context->max.lat = context->max.lon = -180.0; |
69 | context->avg.lat = context->avg.lon = 0.0; |
70 | |
71 | pos_list_t *pos_list = context->pos_list; |
72 | while(pos_list) { |
73 | context->total += pos_list->len; |
74 | |
75 | int j; |
76 | for(j=0;j<pos_list->len;j++) { |
77 | if(pos_list->pos[j].lat > context->max.lat) |
78 | context->max.lat = pos_list->pos[j].lat; |
79 | if(pos_list->pos[j].lat < context->min.lat) |
80 | context->min.lat = pos_list->pos[j].lat; |
81 | if(pos_list->pos[j].lon > context->max.lon) |
82 | context->max.lon = pos_list->pos[j].lon; |
83 | if(pos_list->pos[j].lon < context->min.lon) |
84 | context->min.lon = pos_list->pos[j].lon; |
85 | |
86 | context->avg.lat += pos_list->pos[j].lat; |
87 | context->avg.lon += pos_list->pos[j].lon; |
88 | } |
89 | |
90 | pos_list = pos_list->next; |
91 | } |
92 | |
93 | if(!context->total) |
94 | return; |
95 | |
96 | context->avg.lat /= context->total; |
97 | context->avg.lon /= context->total; |
98 | |
99 | #define SCALE 0.9 |
100 | |
101 | context->mid.lat = (context->max.lat+context->min.lat)/2.0; |
102 | context->mid.lon = (context->max.lon+context->min.lon)/2.0; |
103 | float lat_scale = SCALE/(context->max.lat-context->min.lat); |
104 | float lon_scale = SCALE/(context->max.lon-context->min.lon); |
105 | |
106 | context->scale = (lat_scale < lon_scale)?lat_scale:lon_scale; |
107 | } |
108 | |
109 | #define XOFF 1 |
110 | static void gdk_draw_cross(GdkDrawable *drawable, GdkGC *gc, gint x, gint y) { |
111 | gdk_draw_line(drawable, gc, x-XOFF, y-XOFF, x+XOFF, y+XOFF); |
112 | gdk_draw_line(drawable, gc, x-XOFF, y+XOFF, x+XOFF, y-XOFF); |
113 | } |
114 | |
115 | static void pp_draw(GtkWidget *widget, pp_context_t *context) { |
116 | gint width = widget->allocation.width; |
117 | gint height = widget->allocation.height; |
118 | gint diameter = (height < width)?height:width; |
119 | |
120 | gint xcenter = width/2; |
121 | gint ycenter = height/2; |
122 | |
123 | /* erase background */ |
124 | gdk_draw_rectangle(context->pixmap, |
125 | widget->style->bg_gc[GTK_STATE_NORMAL], TRUE, |
126 | 0, 0, width, height); |
127 | |
128 | GdkGC *circle_gc = widget->style->white_gc; |
129 | if(widget->style->bg[GTK_STATE_NORMAL].red + |
130 | widget->style->bg[GTK_STATE_NORMAL].green + |
131 | widget->style->bg[GTK_STATE_NORMAL].blue > 3*60000) { |
132 | circle_gc = gdk_gc_new(context->pixmap); |
133 | gdk_gc_copy(circle_gc, widget->style->black_gc); |
134 | GdkColor lgrey_color; |
135 | gdk_color_parse("#DDDDDD", &lgrey_color); |
136 | gdk_gc_set_rgb_fg_color(circle_gc, &lgrey_color); |
137 | } |
138 | |
139 | gdk_draw_arc(context->pixmap, circle_gc, TRUE, |
140 | xcenter - (SCALE*diameter/2), |
141 | ycenter - (SCALE*diameter/2), |
142 | SCALE*diameter, SCALE*diameter, |
143 | 0, 360*64); |
144 | |
145 | /* make sure data captured so far allows for useful rendering */ |
146 | if(context->total < 2) return; |
147 | if(context->min.lat >= context->max.lat) return; |
148 | if(context->min.lon >= context->max.lon) return; |
149 | if(context->scale > 50000) return; |
150 | |
151 | /* setup required colors */ |
152 | GdkGC *green_gc = clone_gc(context->pixmap, widget, "#008000"); |
153 | GdkGC *red_gc = clone_gc(context->pixmap, widget, "#800000"); |
154 | GdkGC *blue_gc = clone_gc(context->pixmap, widget, "#000080"); |
155 | |
156 | #if 0 |
157 | printf("---------- %f\n", context->scale); |
158 | printf("X: %f->%f->%f Y: %f->%f->%f\n", |
159 | context->min.lon, context->mid.lon, context->max.lon, |
160 | context->min.lat, context->mid.lat, context->max.lat); |
161 | #endif |
162 | |
163 | /* draw all dots */ |
164 | pos_list_t *pos_list = context->pos_list; |
165 | double dscale = context->scale * diameter; |
166 | while(pos_list) { |
167 | int j; |
168 | for(j=0;j<pos_list->len;j++) { |
169 | #if 0 |
170 | printf("%f %f -> y = %f, x = %f\n", |
171 | pos_list->pos[j].lat, pos_list->pos[j].lon, |
172 | (pos_list->pos[j].lat-context->mid.lat)*context->scale, |
173 | (pos_list->pos[j].lon-context->mid.lon)*context->scale); |
174 | #endif |
175 | |
176 | if(pos_list->next || j != pos_list->len-1) |
177 | gdk_draw_cross(context->pixmap, green_gc, |
178 | xcenter + ((pos_list->pos[j].lon-context->mid.lon)*dscale), |
179 | ycenter + ((pos_list->pos[j].lat-context->mid.lat)*dscale)); |
180 | else |
181 | gdk_draw_arc(context->pixmap, red_gc, TRUE, |
182 | xcenter + ((pos_list->pos[j].lon-context->mid.lon)*dscale)-3, |
183 | ycenter + ((pos_list->pos[j].lat-context->mid.lat)*dscale)-3, |
184 | 7, 7, 0, 360*64); |
185 | } |
186 | |
187 | pos_list = pos_list->next; |
188 | } |
189 | #if 0 |
190 | printf("D y = %f, x = %f\n", |
191 | (context->avg.lat-context->mid.lat)*context->scale, |
192 | (context->avg.lon-context->mid.lon)*context->scale); |
193 | #endif |
194 | gdk_draw_arc(context->pixmap, blue_gc, TRUE, |
195 | xcenter + ((context->avg.lon-context->mid.lon)*dscale)-5, |
196 | ycenter + ((context->avg.lat-context->mid.lat)*dscale)-5, |
197 | 11, 11, 0, 360*64); |
198 | } |
199 | |
200 | /* Create a new backing pixmap of the appropriate size */ |
201 | static gint pp_configure_event(GtkWidget *widget, GdkEventConfigure *event, |
202 | gpointer data) { |
203 | pp_context_t *context = (pp_context_t*)data; |
204 | |
205 | if(context->pixmap) |
206 | gdk_pixmap_unref(context->pixmap); |
207 | |
208 | context->pixmap = gdk_pixmap_new(widget->window, |
209 | widget->allocation.width, |
210 | widget->allocation.height, |
211 | -1); |
212 | context_update(context); |
213 | pp_draw(widget, context); |
214 | |
215 | return TRUE; |
216 | } |
217 | |
218 | /* Redraw the screen from the backing pixmap */ |
219 | static gint pp_expose_event(GtkWidget *widget, GdkEventExpose *event, |
220 | gpointer data) { |
221 | pp_context_t *context = (pp_context_t*)data; |
222 | |
223 | gdk_draw_pixmap(widget->window, |
224 | widget->style->fg_gc[GTK_WIDGET_STATE(widget)], |
225 | context->pixmap, |
226 | event->area.x, event->area.y, |
227 | event->area.x, event->area.y, |
228 | event->area.width, event->area.height); |
229 | |
230 | return FALSE; |
231 | } |
232 | |
233 | gint pp_destroy_event(GtkWidget *widget, gpointer data ) { |
234 | pp_context_t *context = (pp_context_t*)data; |
235 | |
236 | printf("destroying precise position view\n"); |
237 | |
238 | /* stop timer */ |
239 | if(context->handler_id) |
240 | gtk_timeout_remove(context->handler_id); |
241 | |
242 | pos_list_t *pos_list = context->pos_list; |
243 | while(pos_list) { |
244 | pos_list_t *next = pos_list->next; |
245 | free(pos_list); |
246 | pos_list = next; |
247 | } |
248 | |
249 | /* destroy context itself */ |
250 | g_free(context); |
251 | |
252 | return FALSE; |
253 | } |
254 | |
255 | /* called once a second */ |
256 | static gboolean update(gpointer data) { |
257 | pp_context_t *context = (pp_context_t*)data; |
258 | |
259 | pos_list_t **pos_list = &context->pos_list; |
260 | while(*pos_list && ((*pos_list)->len == MAX_POS)) |
261 | pos_list = &(*pos_list)->next; |
262 | |
263 | if(!*pos_list) { |
264 | printf("alloc new list\n"); |
265 | *pos_list = g_new0(pos_list_t, 1); |
266 | } |
267 | |
268 | /* get one position */ |
269 | pos_t *p = gps_get_pos(context->appdata); |
270 | if(p) (*pos_list)->pos[(*pos_list)->len++] = *p; |
271 | |
272 | context_update(context); |
273 | |
274 | if(context->pixmap) { |
275 | /* draw sat view */ |
276 | pp_draw(context->area, context); |
277 | gtk_widget_queue_draw_area(context->area, 0,0, |
278 | context->area->allocation.width, |
279 | context->area->allocation.height); |
280 | } |
281 | |
282 | /* and whatever else needs to be done ... */ |
283 | char str[32]; |
284 | snprintf(str, sizeof(str), _("Total: %d"), context->total); |
285 | gtk_label_set_text(GTK_LABEL(context->total_label), str); |
286 | |
287 | /* calculate range */ |
288 | pos_t pos1 = { context->mid.lat, context->min.lon }; |
289 | pos_t pos2 = { context->mid.lat, context->max.lon }; |
290 | |
291 | // printf("Total = %d\n", context->total); |
292 | |
293 | if(p) { |
294 | snprintf(str, sizeof(str), _("Diameter: ")); |
295 | if(context->total > 1) { |
296 | float dist = gpx_pos_get_distance(pos1, pos2, |
297 | context->appdata->imperial); |
298 | // printf("dist = %f\n", dist); |
299 | distance_str(str+strlen(str), sizeof(str)-strlen(str), |
300 | dist, context->appdata->imperial); |
301 | } else |
302 | strcat(str+strlen(str), "---"); |
303 | } else |
304 | strcpy(str, _("No fix")); |
305 | |
306 | #ifndef USE_MAEMO |
307 | gtk_label_set_text(GTK_LABEL(context->range_label), str); |
308 | #else |
309 | char *mup = g_markup_printf_escaped("<span size='x-small'>%s</span>", str); |
310 | gtk_label_set_markup(GTK_LABEL(context->range_label), mup); |
311 | g_free(mup); |
312 | #endif |
313 | |
314 | pos_lat_str(str, sizeof(str), context->avg.lat); |
315 | gtk_label_set_text(GTK_LABEL(context->lat_label), str); |
316 | pos_lon_str(str, sizeof(str), context->avg.lon); |
317 | gtk_label_set_text(GTK_LABEL(context->lon_label), str); |
318 | |
319 | return TRUE; // fire again |
320 | } |
321 | |
322 | #ifdef USE_MAEMO |
323 | static void on_mm_export_clicked(GtkButton *button, gpointer data) { |
324 | pp_context_t *context = (pp_context_t*)data; |
325 | |
326 | if(!context->avg.lat || !context->avg.lon) |
327 | return; |
328 | |
329 | pos_t pos = { context->avg.lat, context->avg.lon } ; |
330 | dbus_mm_set_position(context->appdata, &pos); |
331 | } |
332 | #endif |
333 | |
334 | #ifndef NO_COPY_N_PASTE |
335 | static void on_copy_clicked(GtkButton *button, gpointer data) { |
336 | pp_context_t *context = (pp_context_t*)data; |
337 | char str[64]; |
338 | |
339 | /* make a textual representation of the coordinate */ |
340 | pos_lat_str(str, sizeof(str), context->avg.lat); |
341 | strcat(str, " "); |
342 | pos_lon_str(str+strlen(str), sizeof(str)-strlen(str), context->avg.lon); |
343 | |
344 | printf("set clipboard to \"%s\"\n", str); |
345 | gtk_clipboard_set_text(context->appdata->clipboard, str, -1); |
346 | } |
347 | #endif |
348 | |
349 | void precise_position(appdata_t *appdata) { |
350 | pp_context_t *context = g_new0(pp_context_t, 1); |
351 | |
352 | context->appdata = appdata; |
353 | |
354 | if(!appdata->use_gps) { |
355 | errorf(_("GPS is disabled. Please enable it to use this feature.")); |
356 | g_free(context); |
357 | return; |
358 | } |
359 | |
360 | GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Precise Position"), |
361 | GTK_WINDOW(appdata->window), GTK_DIALOG_MODAL, |
362 | GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); |
363 | |
364 | #if defined(USE_MAEMO) && defined(HILDON_HELP) |
365 | hildon_help_dialog_help_enable(GTK_DIALOG(dialog), |
366 | HELP_ID_PRECPOS, appdata->osso_context); |
367 | #endif |
368 | |
369 | GtkWidget *hbox = gtk_hbox_new(FALSE,20); |
370 | |
371 | /* --------------- left part ------------------------ */ |
372 | GtkWidget *vbox = gtk_vbox_new(FALSE,2); |
373 | context->area = gtk_drawing_area_new(); |
374 | gtk_drawing_area_size(GTK_DRAWING_AREA(context->area), |
375 | PP_WIDTH, PP_HEIGHT); |
376 | |
377 | gtk_signal_connect(GTK_OBJECT(context->area), "expose_event", |
378 | G_CALLBACK(pp_expose_event), context); |
379 | gtk_signal_connect(GTK_OBJECT(context->area),"configure_event", |
380 | G_CALLBACK(pp_configure_event), context); |
381 | g_signal_connect(G_OBJECT(dialog), "destroy", |
382 | G_CALLBACK(pp_destroy_event), context); |
383 | |
384 | gtk_box_pack_start_defaults(GTK_BOX(vbox), context->area); |
385 | gtk_box_pack_start_defaults(GTK_BOX(vbox), |
386 | context->range_label = gtk_label_new("")); |
387 | |
388 | gtk_box_pack_start_defaults(GTK_BOX(hbox), vbox); |
389 | |
390 | /* --------------- right part ------------------------ */ |
391 | vbox = gtk_vbox_new(FALSE,2); |
392 | |
393 | gtk_box_pack_start_defaults(GTK_BOX(vbox), |
394 | context->total_label = gtk_label_new("")); |
395 | |
396 | gtk_box_pack_start_defaults(GTK_BOX(vbox), gtk_hseparator_new()); |
397 | |
398 | gtk_box_pack_start_defaults(GTK_BOX(vbox), gtk_label_new(_("Latitude:"))); |
399 | gtk_box_pack_start_defaults(GTK_BOX(vbox), |
400 | context->lat_label = gtk_label_new("")); |
401 | gtk_box_pack_start_defaults(GTK_BOX(vbox), gtk_label_new(_("Longitude:"))); |
402 | gtk_box_pack_start_defaults(GTK_BOX(vbox), |
403 | context->lon_label = gtk_label_new("")); |
404 | #ifndef NO_COPY_N_PASTE |
405 | GtkWidget *copy_but = gtk_button_new_with_label(_("Copy")); |
406 | gtk_signal_connect(GTK_OBJECT(copy_but), "clicked", |
407 | (GtkSignalFunc)on_copy_clicked, context); |
408 | gtk_box_pack_start_defaults(GTK_BOX(vbox), copy_but); |
409 | #endif |
410 | |
411 | gtk_box_pack_start_defaults(GTK_BOX(hbox), vbox); |
412 | |
413 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox); |
414 | |
415 | context->handler_id = gtk_timeout_add(1000, update, context); |
416 | |
417 | #ifdef USE_MAEMO |
418 | /* ------------- maemo mapper button ---------------- */ |
419 | GtkWidget *button = gtk_button_new(); |
420 | gtk_button_set_image(GTK_BUTTON(button), icon_get_widget(ICON_MISC, 0)); |
421 | gtk_signal_connect(GTK_OBJECT(button), "clicked", |
422 | (GtkSignalFunc)on_mm_export_clicked, context); |
423 | gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), button); |
424 | #endif |
425 | |
426 | update(context); |
427 | gtk_widget_show_all(dialog); |
428 | gtk_dialog_run(GTK_DIALOG(dialog)); |
429 | gtk_widget_destroy(dialog); |
430 | } |