Parent Directory | Revision Log
Map set widget
1 | harbaum | 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 | harbaum | 221 | lat_label_set(context->lat_label, context->avg.lat); |
315 | lon_label_set(context->lon_label, context->avg.lon); | ||
316 | harbaum | 1 | |
317 | return TRUE; // fire again | ||
318 | } | ||
319 | |||
320 | static void on_copy_clicked(GtkButton *button, gpointer data) { | ||
321 | pp_context_t *context = (pp_context_t*)data; | ||
322 | char str[64]; | ||
323 | |||
324 | /* make a textual representation of the coordinate */ | ||
325 | pos_lat_str(str, sizeof(str), context->avg.lat); | ||
326 | strcat(str, " "); | ||
327 | pos_lon_str(str+strlen(str), sizeof(str)-strlen(str), context->avg.lon); | ||
328 | |||
329 | printf("set clipboard to \"%s\"\n", str); | ||
330 | gtk_clipboard_set_text(context->appdata->clipboard, str, -1); | ||
331 | } | ||
332 | |||
333 | void precise_position(appdata_t *appdata) { | ||
334 | pp_context_t *context = g_new0(pp_context_t, 1); | ||
335 | |||
336 | context->appdata = appdata; | ||
337 | |||
338 | if(!appdata->use_gps) { | ||
339 | errorf(_("GPS is disabled. Please enable it to use this feature.")); | ||
340 | g_free(context); | ||
341 | return; | ||
342 | } | ||
343 | |||
344 | GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Precise Position"), | ||
345 | GTK_WINDOW(appdata->window), GTK_DIALOG_MODAL, | ||
346 | GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); | ||
347 | |||
348 | #if defined(USE_MAEMO) && defined(HILDON_HELP) | ||
349 | hildon_help_dialog_help_enable(GTK_DIALOG(dialog), | ||
350 | HELP_ID_PRECPOS, appdata->osso_context); | ||
351 | #endif | ||
352 | |||
353 | GtkWidget *hbox = gtk_hbox_new(FALSE,20); | ||
354 | |||
355 | /* --------------- left part ------------------------ */ | ||
356 | GtkWidget *vbox = gtk_vbox_new(FALSE,2); | ||
357 | context->area = gtk_drawing_area_new(); | ||
358 | gtk_drawing_area_size(GTK_DRAWING_AREA(context->area), | ||
359 | PP_WIDTH, PP_HEIGHT); | ||
360 | |||
361 | gtk_signal_connect(GTK_OBJECT(context->area), "expose_event", | ||
362 | G_CALLBACK(pp_expose_event), context); | ||
363 | gtk_signal_connect(GTK_OBJECT(context->area),"configure_event", | ||
364 | G_CALLBACK(pp_configure_event), context); | ||
365 | g_signal_connect(G_OBJECT(dialog), "destroy", | ||
366 | G_CALLBACK(pp_destroy_event), context); | ||
367 | |||
368 | gtk_box_pack_start_defaults(GTK_BOX(vbox), context->area); | ||
369 | gtk_box_pack_start_defaults(GTK_BOX(vbox), | ||
370 | context->range_label = gtk_label_new("")); | ||
371 | |||
372 | gtk_box_pack_start_defaults(GTK_BOX(hbox), vbox); | ||
373 | |||
374 | /* --------------- right part ------------------------ */ | ||
375 | vbox = gtk_vbox_new(FALSE,2); | ||
376 | |||
377 | gtk_box_pack_start_defaults(GTK_BOX(vbox), | ||
378 | context->total_label = gtk_label_new("")); | ||
379 | |||
380 | gtk_box_pack_start_defaults(GTK_BOX(vbox), gtk_hseparator_new()); | ||
381 | |||
382 | gtk_box_pack_start_defaults(GTK_BOX(vbox), gtk_label_new(_("Latitude:"))); | ||
383 | gtk_box_pack_start_defaults(GTK_BOX(vbox), | ||
384 | context->lat_label = gtk_label_new("")); | ||
385 | gtk_box_pack_start_defaults(GTK_BOX(vbox), gtk_label_new(_("Longitude:"))); | ||
386 | gtk_box_pack_start_defaults(GTK_BOX(vbox), | ||
387 | context->lon_label = gtk_label_new("")); | ||
388 | harbaum | 133 | |
389 | harbaum | 1 | GtkWidget *copy_but = gtk_button_new_with_label(_("Copy")); |
390 | harbaum | 133 | #if defined(USE_MAEMO) && (MAEMO_VERSION_MAJOR == 5) |
391 | hildon_gtk_widget_set_theme_size(copy_but, | ||
392 | (HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH)); | ||
393 | #endif | ||
394 | harbaum | 1 | gtk_signal_connect(GTK_OBJECT(copy_but), "clicked", |
395 | (GtkSignalFunc)on_copy_clicked, context); | ||
396 | gtk_box_pack_start_defaults(GTK_BOX(vbox), copy_but); | ||
397 | |||
398 | gtk_box_pack_start_defaults(GTK_BOX(hbox), vbox); | ||
399 | |||
400 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox); | ||
401 | |||
402 | context->handler_id = gtk_timeout_add(1000, update, context); | ||
403 | |||
404 | update(context); | ||
405 | gtk_widget_show_all(dialog); | ||
406 | gtk_dialog_run(GTK_DIALOG(dialog)); | ||
407 | gtk_widget_destroy(dialog); | ||
408 | } |