Contents of /trunk/src/area_edit.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 204 - (show annotations)
Fri Jul 10 07:36:19 2009 UTC (14 years, 10 months ago) by harbaum
File MIME type: text/plain
File size: 18946 byte(s)
Area edit map integration done
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 #include "appdata.h"
21 #include "osm-gps-map.h"
22
23 typedef struct {
24 GtkWidget *dialog, *notebook;
25 area_edit_t *area;
26 pos_t min, max; /* local copy to work on */
27 GtkWidget *minlat, *maxlat, *minlon, *maxlon;
28
29 struct {
30 GtkWidget *minlat, *maxlat, *minlon, *maxlon;
31 } direct;
32
33 struct {
34 GtkWidget *lat, *lon, *height, *width, *mil_km;
35 gboolean is_mil;
36 } extent;
37
38 #ifdef USE_HILDON
39 struct {
40 GtkWidget *fetch;
41 } mmapper;
42 #endif
43
44 struct {
45 GtkWidget *widget;
46 GtkWidget *zoomin, *zoomout;
47 gboolean needs_redraw;
48 } map;
49
50 } context_t;
51
52 static void parse_and_set_lat(GtkWidget *src, GtkWidget *dst, pos_float_t *store) {
53 pos_float_t i = pos_parse_lat((char*)gtk_entry_get_text(GTK_ENTRY(src)));
54 if(pos_lat_valid(i)) {
55 *store = i;
56 pos_lat_label_set(dst, i);
57 }
58 }
59
60 static void parse_and_set_lon(GtkWidget *src, GtkWidget *dst, pos_float_t *store) {
61 pos_float_t i = pos_parse_lon((char*)gtk_entry_get_text(GTK_ENTRY(src)));
62 if(pos_lon_valid(i)) {
63 *store = i;
64 pos_lon_label_set(dst, i);
65 }
66 }
67
68 #define LOG2(x) (log(x) / log(2))
69
70 /* the contents of the map tab have been changed */
71 static void map_update(context_t *context, gboolean forced) {
72
73 /* map is first tab (page 0) */
74 if(!forced && gtk_notebook_get_current_page(GTK_NOTEBOOK(context->notebook)) != 0) {
75 context->map.needs_redraw = TRUE;
76 return;
77 }
78
79 pos_float_t center_lat = (context->max.lat + context->min.lat)/2;
80 pos_float_t center_lon = (context->max.lon + context->min.lon)/2;
81
82 /* we know the widgets pixel size, we know the required real size, we want the zoom! */
83 double vzoom = LOG2((45.0 * context->map.widget->allocation.height)/
84 ((context->max.lat - context->min.lat)*32.0));
85
86 double hzoom = LOG2((45.0 * context->map.widget->allocation.width)/
87 ((context->max.lon - context->min.lon)*32.0));
88
89 osm_gps_map_set_center(OSM_GPS_MAP(context->map.widget),
90 center_lat, center_lon);
91
92 osm_gps_map_set_zoom(OSM_GPS_MAP(context->map.widget), (hzoom+vzoom+0.5)/2);
93
94 context->map.needs_redraw = FALSE;
95 }
96
97 static gboolean on_map_configure(GtkWidget *widget,
98 GdkEventConfigure *event,
99 context_t *context) {
100 map_update(context, FALSE);
101 return FALSE;
102 }
103
104 /* the contents of the direct tab have been changed */
105 static void direct_update(context_t *context) {
106 pos_lat_entry_set(context->direct.minlat, context->min.lat);
107 pos_lon_entry_set(context->direct.minlon, context->min.lon);
108 pos_lat_entry_set(context->direct.maxlat, context->max.lat);
109 pos_lon_entry_set(context->direct.maxlon, context->max.lon);
110 }
111
112 /* update the contents of the extent tab */
113 static void extent_update(context_t *context) {
114 pos_float_t center_lat = (context->max.lat + context->min.lat)/2;
115 pos_float_t center_lon = (context->max.lon + context->min.lon)/2;
116
117 pos_lat_entry_set(context->extent.lat, center_lat);
118 pos_lat_entry_set(context->extent.lon, center_lon);
119
120 double vscale = DEG2RAD(POS_EQ_RADIUS / 1000.0);
121 double hscale = DEG2RAD(cos(DEG2RAD(center_lat)) * POS_EQ_RADIUS / 1000.0);
122
123 double height = vscale * (context->max.lat - context->min.lat);
124 double width = hscale * (context->max.lon - context->min.lon);
125
126 pos_dist_entry_set(context->extent.width, width, context->extent.is_mil);
127 pos_dist_entry_set(context->extent.height, height, context->extent.is_mil);
128 }
129
130 static void callback_modified_direct(GtkWidget *widget, gpointer data) {
131 context_t *context = (context_t*)data;
132
133 /* direct is second tab (page 1) */
134 if(gtk_notebook_get_current_page(GTK_NOTEBOOK(context->notebook)) != 1)
135 return;
136
137 /* parse the fields from the direct entry pad */
138 parse_and_set_lat(context->direct.minlat, context->minlat, &context->min.lat);
139 parse_and_set_lon(context->direct.minlon, context->minlon, &context->min.lon);
140 parse_and_set_lat(context->direct.maxlat, context->maxlat, &context->max.lat);
141 parse_and_set_lon(context->direct.maxlon, context->maxlon, &context->max.lon);
142
143 /* also adjust other views */
144 extent_update(context);
145 }
146
147 static void callback_modified_extent(GtkWidget *widget, gpointer data) {
148 context_t *context = (context_t*)data;
149
150 /* extent is third tab (page 2) */
151 if(gtk_notebook_get_current_page(GTK_NOTEBOOK(context->notebook)) != 2)
152 return;
153
154 pos_float_t center_lat = pos_lat_get(context->extent.lat);
155 pos_float_t center_lon = pos_lon_get(context->extent.lon);
156
157 if(!pos_lat_valid(center_lat) || !pos_lon_valid(center_lon))
158 return;
159
160 double vscale = DEG2RAD(POS_EQ_RADIUS / 1000.0);
161 double hscale = DEG2RAD(cos(DEG2RAD(center_lat)) * POS_EQ_RADIUS / 1000.0);
162
163 double height = pos_dist_get(context->extent.height, context->extent.is_mil);
164 double width = pos_dist_get(context->extent.width, context->extent.is_mil);
165
166 height /= 2 * vscale;
167 context->min.lat = center_lat - height;
168 pos_lat_label_set(context->minlat, context->min.lat);
169 context->max.lat = center_lat + height;
170 pos_lat_label_set(context->maxlat, context->max.lat);
171
172 width /= 2 * hscale;
173 context->min.lon = center_lon - width;
174 pos_lon_label_set(context->minlon, context->min.lon);
175 context->max.lon = center_lon + width;
176 pos_lon_label_set(context->maxlon, context->max.lon);
177
178 /* also update other tabs */
179 direct_update(context);
180 map_update(context, FALSE);
181 }
182
183 static void callback_modified_unit(GtkWidget *widget, gpointer data) {
184 context_t *context = (context_t*)data;
185
186 /* get current values */
187 double height = pos_dist_get(context->extent.height, context->extent.is_mil);
188 double width = pos_dist_get(context->extent.width, context->extent.is_mil);
189
190 /* adjust unit flag */
191 context->extent.is_mil = gtk_combo_box_get_active(
192 GTK_COMBO_BOX(context->extent.mil_km)) == 0;
193
194 /* save values */
195 pos_dist_entry_set(context->extent.width, width, context->extent.is_mil);
196 pos_dist_entry_set(context->extent.height, height, context->extent.is_mil);
197 }
198
199 #ifdef USE_HILDON
200 static void callback_fetch_mm_clicked(GtkButton *button, gpointer data) {
201 context_t *context = (context_t*)data;
202
203 printf("clicked fetch mm!\n");
204
205 if(!dbus_mm_set_position(context->area->osso_context, NULL)) {
206 errorf(context->dialog,
207 _("Unable to communicate with Maemo Mapper. "
208 "You need to have Maemo Mapper installed "
209 "to use this feature."));
210 return;
211 }
212
213 if(!context->area->mmpos->valid) {
214 errorf(context->dialog,
215 _("No valid position received yet. You need "
216 "to scroll or zoom the Maemo Mapper view "
217 "in order to force it to send its current "
218 "view position to osm2go."));
219 return;
220 }
221
222 /* maemo mapper is fourth tab (page 3) */
223 if(gtk_notebook_get_current_page(GTK_NOTEBOOK(context->notebook)) != 3)
224 return;
225
226 /* maemo mapper pos data ... */
227 pos_float_t center_lat = context->area->mmpos->pos.lat;
228 pos_float_t center_lon = context->area->mmpos->pos.lon;
229 int zoom = context->area->mmpos->zoom;
230
231 if(!pos_lat_valid(center_lat) || !pos_lon_valid(center_lon))
232 return;
233
234 double vscale = DEG2RAD(POS_EQ_RADIUS);
235 double height = 8 * (1<<zoom) / vscale;
236 context->min.lat = center_lat - height;
237 pos_lat_label_set(context->minlat, context->min.lat);
238 context->max.lat = center_lat + height;
239 pos_lat_label_set(context->maxlat, context->max.lat);
240
241 double hscale = DEG2RAD(cos(DEG2RAD(center_lat)) * POS_EQ_RADIUS);
242 double width = 16 * (1<<zoom) / hscale;
243 context->min.lon = center_lon - width;
244 pos_lon_label_set(context->minlon, context->min.lon);
245 context->max.lon = center_lon + width;
246 pos_lon_label_set(context->maxlon, context->max.lon);
247
248 /* also update other tabs */
249 direct_update(context);
250 extent_update(context);
251 map_update(context, FALSE);
252 }
253 #endif
254
255 /* the user has changed the map view, update other views accordingly */
256 static void map_has_changed(context_t *context) {
257 coord_t pt1, pt2;
258
259 /* get maps bounding box */
260 osm_gps_map_get_bbox(OSM_GPS_MAP(context->map.widget), &pt1, &pt2);
261
262 context->min.lat = RAD2DEG(pt2.rlat);
263 pos_lat_label_set(context->minlat, context->min.lat);
264 context->max.lat = RAD2DEG(pt1.rlat);
265 pos_lat_label_set(context->maxlat, context->max.lat);
266
267 context->min.lon = RAD2DEG(pt1.rlon);
268 pos_lon_label_set(context->minlon, context->min.lon);
269 context->max.lon = RAD2DEG(pt2.rlon);
270 pos_lon_label_set(context->maxlon, context->max.lon);
271
272 direct_update(context);
273 extent_update(context);
274 }
275
276 static gboolean
277 on_map_button_release_event(GtkWidget *widget,
278 GdkEventButton *event, context_t *context) {
279 map_has_changed(context);
280 return FALSE;
281 }
282
283 static void map_zoom(context_t *context, int step) {
284 int zoom;
285 OsmGpsMap *map = OSM_GPS_MAP(context->map.widget);
286 g_object_get(map, "zoom", &zoom, NULL);
287 zoom = osm_gps_map_set_zoom(map, zoom+step);
288
289 /* enable/disable zoom buttons as required */
290 gtk_widget_set_sensitive(context->map.zoomin, zoom<17);
291 gtk_widget_set_sensitive(context->map.zoomout, zoom>1);
292
293 map_has_changed(context);
294 }
295
296 static gboolean
297 cb_map_zoomin(GtkButton *button, context_t *context) {
298 map_zoom(context, +1);
299 return FALSE;
300 }
301
302 static gboolean
303 cb_map_zoomout(GtkButton *button, context_t *context) {
304 map_zoom(context, -1);
305 return FALSE;
306 }
307
308 static void on_page_switch(GtkNotebook *notebook, GtkNotebookPage *page,
309 guint page_num, context_t *context) {
310
311 /* updating the map while the user manually changes some coordinates */
312 /* may confuse the map. so we delay those updates until the map tab */
313 /* is becoming visible */
314 if((page_num == 0) && context->map.needs_redraw)
315 map_update(context, TRUE);
316 }
317
318 gboolean area_edit(area_edit_t *area) {
319 gboolean ok = FALSE;
320
321 context_t context;
322 memset(&context, 0, sizeof(context_t));
323 context.area = area;
324 context.min.lat = area->min->lat;
325 context.min.lon = area->min->lon;
326 context.max.lat = area->max->lat;
327 context.max.lon = area->max->lon;
328
329 context.dialog =
330 misc_dialog_new(MISC_DIALOG_HIGH, _("Area editor"),
331 GTK_WINDOW(area->parent),
332 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
333 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
334 NULL);
335
336 GtkWidget *table = gtk_table_new(4, 2, FALSE); // x, y
337
338 GtkWidget *label = gtk_label_new(_("Latitude:"));
339 misc_table_attach(table, label, 0, 0);
340 context.minlat = pos_lat_label_new(area->min->lat);
341 misc_table_attach(table, context.minlat, 1, 0);
342 label = gtk_label_new(_("to"));
343 misc_table_attach(table, label, 2, 0);
344 context.maxlat = pos_lat_label_new(area->max->lat);
345 misc_table_attach(table, context.maxlat, 3, 0);
346
347 label = gtk_label_new(_("Longitude:"));
348 misc_table_attach(table, label, 0, 1);
349 context.minlon = pos_lon_label_new(area->min->lon);
350 misc_table_attach(table, context.minlon, 1, 1);
351 label = gtk_label_new(_("to"));
352 misc_table_attach(table, label, 2, 1);
353 context.maxlon = pos_lon_label_new(area->max->lon);
354 misc_table_attach(table, context.maxlon, 3, 1);
355
356 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(context.dialog)->vbox),
357 table, FALSE, FALSE, 0);
358
359 context.notebook = gtk_notebook_new();
360
361 /* ------------- fetch from map ------------------------ */
362
363 GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
364
365 context.map.needs_redraw = FALSE;
366 context.map.widget = g_object_new(OSM_TYPE_GPS_MAP,
367 "repo-uri", MAP_SOURCE_OPENSTREETMAP,
368 "proxy-uri", misc_get_proxy_uri(area->settings),
369 NULL);
370
371 g_signal_connect(G_OBJECT(context.map.widget), "configure-event",
372 G_CALLBACK(on_map_configure), &context);
373 g_signal_connect(G_OBJECT(context.map.widget), "button-release-event",
374 G_CALLBACK(on_map_button_release_event), &context);
375
376 gtk_box_pack_start_defaults(GTK_BOX(hbox), context.map.widget);
377
378 /* zoom button box */
379 GtkWidget *vbox = gtk_vbox_new(FALSE,0);
380
381 context.map.zoomin = gtk_button_new();
382 gtk_button_set_image(GTK_BUTTON(context.map.zoomin),
383 gtk_image_new_from_stock(GTK_STOCK_ZOOM_IN, GTK_ICON_SIZE_MENU));
384 g_signal_connect(context.map.zoomin, "clicked",
385 G_CALLBACK(cb_map_zoomin), &context);
386 gtk_box_pack_start(GTK_BOX(vbox), context.map.zoomin, FALSE, FALSE, 0);
387
388 context.map.zoomout = gtk_button_new();
389 gtk_button_set_image(GTK_BUTTON(context.map.zoomout),
390 gtk_image_new_from_stock(GTK_STOCK_ZOOM_OUT, GTK_ICON_SIZE_MENU));
391 g_signal_connect(context.map.zoomout, "clicked",
392 G_CALLBACK(cb_map_zoomout), &context);
393 gtk_box_pack_start(GTK_BOX(vbox), context.map.zoomout, FALSE, FALSE, 0);
394
395 gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
396
397 gtk_notebook_append_page(GTK_NOTEBOOK(context.notebook),
398 hbox, gtk_label_new(_("Map")));
399
400 /* ------------ direct min/max edit --------------- */
401
402 vbox = gtk_vbox_new(FALSE, 10);
403 table = gtk_table_new(3, 3, FALSE); // x, y
404 gtk_table_set_col_spacings(GTK_TABLE(table), 10);
405 gtk_table_set_row_spacings(GTK_TABLE(table), 5);
406
407 context.direct.minlat = pos_lat_entry_new(0.0);
408 misc_table_attach(table, context.direct.minlat, 0, 0);
409 label = gtk_label_new(_("to"));
410 misc_table_attach(table, label, 1, 0);
411 context.direct.maxlat = pos_lat_entry_new(0.0);
412 misc_table_attach(table, context.direct.maxlat, 2, 0);
413
414 context.direct.minlon = pos_lon_entry_new(area->min->lon);
415 misc_table_attach(table, context.direct.minlon, 0, 1);
416 label = gtk_label_new(_("to"));
417 misc_table_attach(table, label, 1, 1);
418 context.direct.maxlon = pos_lon_entry_new(0.0);
419 misc_table_attach(table, context.direct.maxlon, 2, 1);
420
421 /* setup this page */
422 direct_update(&context);
423
424 g_signal_connect(G_OBJECT(context.direct.minlat), "changed",
425 G_CALLBACK(callback_modified_direct), &context);
426 g_signal_connect(G_OBJECT(context.direct.minlon), "changed",
427 G_CALLBACK(callback_modified_direct), &context);
428 g_signal_connect(G_OBJECT(context.direct.maxlat), "changed",
429 G_CALLBACK(callback_modified_direct), &context);
430 g_signal_connect(G_OBJECT(context.direct.maxlon), "changed",
431 G_CALLBACK(callback_modified_direct), &context);
432
433
434 /* --- hint --- */
435 label = gtk_label_new(_("(recommended min/max diff <0.03 degrees)"));
436 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 3, 2, 3);
437
438 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
439 gtk_notebook_append_page(GTK_NOTEBOOK(context.notebook),
440 vbox, gtk_label_new(_("Direct")));
441
442 /* ------------- center/extent edit ------------------------ */
443
444 vbox = gtk_vbox_new(FALSE, 10);
445 table = gtk_table_new(3, 4, FALSE); // x, y
446 gtk_table_set_col_spacings(GTK_TABLE(table), 10);
447 gtk_table_set_row_spacings(GTK_TABLE(table), 5);
448
449 label = gtk_label_new(_("Center:"));
450 gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
451 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
452 context.extent.lat = pos_lat_entry_new(0.0);
453 gtk_table_attach_defaults(GTK_TABLE(table), context.extent.lat, 1, 2, 0, 1);
454 context.extent.lon = pos_lon_entry_new(0.0);
455 gtk_table_attach_defaults(GTK_TABLE(table), context.extent.lon, 2, 3, 0, 1);
456
457 gtk_table_set_row_spacing(GTK_TABLE(table), 0, 10);
458
459 label = gtk_label_new(_("Width:"));
460 gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
461 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
462 context.extent.width = gtk_entry_new();
463 gtk_table_attach_defaults(GTK_TABLE(table), context.extent.width, 1, 2, 1, 2);
464
465 label = gtk_label_new(_("Height:"));
466 gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
467 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
468 context.extent.height = gtk_entry_new();
469 gtk_table_attach_defaults(GTK_TABLE(table), context.extent.height, 1, 2, 2, 3);
470
471 context.extent.mil_km = gtk_combo_box_new_text();
472 gtk_combo_box_append_text(GTK_COMBO_BOX(context.extent.mil_km), _("mi"));
473 gtk_combo_box_append_text(GTK_COMBO_BOX(context.extent.mil_km), _("km"));
474 gtk_combo_box_set_active(GTK_COMBO_BOX(context.extent.mil_km), 1); // km
475
476 gtk_table_attach(GTK_TABLE(table), context.extent.mil_km, 2, 3, 1, 3,
477 0, 0, 0, 0);
478
479 /* setup this page */
480 extent_update(&context);
481
482 /* connect signals after inital update to avoid confusion */
483 g_signal_connect(G_OBJECT(context.extent.lat), "changed",
484 G_CALLBACK(callback_modified_extent), &context);
485 g_signal_connect(G_OBJECT(context.extent.lon), "changed",
486 G_CALLBACK(callback_modified_extent), &context);
487 g_signal_connect(G_OBJECT(context.extent.width), "changed",
488 G_CALLBACK(callback_modified_extent), &context);
489 g_signal_connect(G_OBJECT(context.extent.height), "changed",
490 G_CALLBACK(callback_modified_extent), &context);
491 g_signal_connect(G_OBJECT(context.extent.mil_km), "changed",
492 G_CALLBACK(callback_modified_unit), &context);
493
494 /* --- hint --- */
495 label = gtk_label_new(_("(recommended width/height < 2km/1.25mi)"));
496 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 3, 3, 4);
497
498 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
499 gtk_notebook_append_page(GTK_NOTEBOOK(context.notebook),
500 vbox, gtk_label_new(_("Extent")));
501
502 #ifdef USE_HILDON
503 /* ------------- fetch from maemo mapper ------------------------ */
504
505 vbox = gtk_vbox_new(FALSE, 8);
506 context.mmapper.fetch =
507 gtk_button_new_with_label(_("Get from Maemo Mapper"));
508 gtk_box_pack_start(GTK_BOX(vbox), context.mmapper.fetch, FALSE, FALSE, 0);
509
510 g_signal_connect(G_OBJECT(context.mmapper.fetch), "clicked",
511 G_CALLBACK(callback_fetch_mm_clicked), &context);
512
513 /* --- hint --- */
514 label = gtk_label_new(_("(recommended MM zoom level < 7)"));
515 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
516
517
518 gtk_notebook_append_page(GTK_NOTEBOOK(context.notebook),
519 vbox, gtk_label_new(_("Maemo Mapper")));
520 #endif
521
522 /* ------------------------------------------------------ */
523
524 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context.dialog)->vbox),
525 context.notebook);
526
527 g_signal_connect(G_OBJECT(context.notebook), "switch-page",
528 G_CALLBACK(on_page_switch), &context);
529
530 gtk_widget_show_all(context.dialog);
531
532 if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(context.dialog))) {
533 /* copy modified values back to given storage */
534 area->min->lat = context.min.lat;
535 area->min->lon = context.min.lon;
536 area->max->lat = context.max.lat;
537 area->max->lon = context.max.lon;
538 ok = TRUE;
539 }
540
541 gtk_widget_destroy(context.dialog);
542
543 return ok;
544 }