1 /*
2 This file is part of darktable,
3 Copyright (C) 2011-2021 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "common/collection.h"
20 #include "common/darktable.h"
21 #include "common/debug.h"
22 #include "common/gpx.h"
23 #include "common/geo.h"
24 #include "common/image_cache.h"
25 #include "common/mipmap_cache.h"
26 #include "common/undo.h"
27 #include "control/conf.h"
28 #include "control/control.h"
29 #include "dtgtk/thumbtable.h"
30 #include "gui/accelerators.h"
31 #include "gui/drag_and_drop.h"
32 #include "gui/draw.h"
33 #include "libs/lib.h"
34 #include "views/view.h"
35 #include "views/view_api.h"
36 #include <gdk/gdkkeysyms.h>
37
38 #include <osm-gps-map.h>
39
40 DT_MODULE(1)
41
42 typedef struct dt_geo_position_t
43 {
44 double x, y;
45 int cluster_id;
46 int imgid;
47 } dt_geo_position_t;
48
49 typedef struct dt_map_image_t
50 {
51 gint imgid;
52 double latitude;
53 double longitude;
54 int group;
55 int group_count;
56 gboolean group_same_loc;
57 gboolean selected_in_group;
58 OsmGpsMapImage *image;
59 gint width, height;
60 int thumbnail;
61 } dt_map_image_t;
62
63 typedef struct dt_map_t
64 {
65 gboolean entering;
66 OsmGpsMap *map;
67 OsmGpsMapSource_t map_source;
68 OsmGpsMapLayer *osd;
69 GSList *images;
70 dt_geo_position_t *points;
71 int nb_points;
72 GdkPixbuf *image_pin, *place_pin;
73 GList *selected_images;
74 gboolean start_drag;
75 int start_drag_x, start_drag_y;
76 int start_drag_offset_x, start_drag_offset_y;
77 float thumb_lat_angle, thumb_lon_angle;
78 sqlite3_stmt *main_query;
79 gboolean drop_filmstrip_activated;
80 gboolean filter_images_drawn;
81 int max_images_drawn;
82 dt_map_box_t bbox;
83 int time_out;
84 int timeout_event_source;
85 int thumbnail;
86 dt_map_image_t *last_hovered_entry;
87 struct
88 {
89 dt_location_draw_t main;
90 gboolean drag;
91 int time_out;
92 GList *others;
93 } loc;
94 } dt_map_t;
95
96 #define UNCLASSIFIED -1
97 #define NOISE -2
98
99 #define CORE_POINT 1
100 #define NOT_CORE_POINT 0
101
102 static const int thumb_size = 128, thumb_border = 2, image_pin_size = 13, place_pin_size = 72;
103 static const int cross_size = 16, max_size = 1024;
104 static const uint32_t thumb_frame_color = 0x000000aa;
105 static const uint32_t thumb_frame_sel_color = 0xffffffee;
106 static const uint32_t thumb_frame_gpx_color = 0xff000099;
107 static const uint32_t pin_outer_color = 0x0000aaaa;
108 static const uint32_t pin_inner_color = 0xffffffee;
109 static const uint32_t pin_line_color = 0x000000ff;
110
111 typedef enum dt_map_thumb_t
112 {
113 DT_MAP_THUMB_THUMB = 0,
114 DT_MAP_THUMB_COUNT,
115 DT_MAP_THUMB_NONE
116 } dt_map_thumb_t;
117
118 /* proxy function to center map view on location at a zoom level */
119 static void _view_map_center_on_location(const dt_view_t *view, gdouble lon, gdouble lat, gdouble zoom);
120 /* proxy function to center map view on a bounding box */
121 static void _view_map_center_on_bbox(const dt_view_t *view, gdouble lon1, gdouble lat1, gdouble lon2, gdouble lat2);
122 /* proxy function to show or hide the osd */
123 static void _view_map_show_osd(const dt_view_t *view);
124 /* proxy function to set the map source */
125 static void _view_map_set_map_source(const dt_view_t *view, OsmGpsMapSource_t map_source);
126 /* wrapper for setting the map source in the GObject */
127 static void _view_map_set_map_source_g_object(const dt_view_t *view, OsmGpsMapSource_t map_source);
128 /* proxy function to check if preferences have changed */
129 static void _view_map_check_preference_changed(gpointer instance, gpointer user_data);
130 /* proxy function to add a marker to the map */
131 static GObject *_view_map_add_marker(const dt_view_t *view, dt_geo_map_display_t type, GList *points);
132 /* proxy function to remove a marker from the map */
133 static gboolean _view_map_remove_marker(const dt_view_t *view, dt_geo_map_display_t type, GObject *marker);
134 /* proxy function to add a location to the map */
135 static void _view_map_add_location(const dt_view_t *view, dt_map_location_data_t *g, const guint locid);
136 /* proxy function to remove a location from the map */
137 static void _view_map_location_action(const dt_view_t *view, const int action);
138 /* proxy function to provide a drag context icon */
139 static void _view_map_drag_set_icon(const dt_view_t *self, GdkDragContext *context,
140 const int imgid, const int count);
141
142 /* callback when the collection changes */
143 static void _view_map_collection_changed(gpointer instance, dt_collection_change_t query_change,
144 dt_collection_properties_t changed_property, gpointer imgs, int next,
145 gpointer user_data);
146 /* callback when the selection changes */
147 static void _view_map_selection_changed(gpointer instance, gpointer user_data);
148 /* callback when images geotags change */
149 static void _view_map_geotag_changed(gpointer instance, GList *imgs, const int locid, gpointer user_data);
150 /* update the geotag information on location tag */
151 static void _view_map_update_location_geotag(dt_view_t *self);
152 /* callback when an image is selected in filmstrip, centers map */
153 static void _view_map_filmstrip_activate_callback(gpointer instance, int imgid, gpointer user_data);
154 /* callback when an image is dropped from filmstrip */
155 static void _drag_and_drop_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
156 GtkSelectionData *selection_data, guint target_type, guint time,
157 gpointer data);
158 /* callback when the user drags images FROM the map */
159 static void _view_map_dnd_get_callback(GtkWidget *widget, GdkDragContext *context,
160 GtkSelectionData *selection_data, guint target_type, guint time,
161 dt_view_t *self);
162 /* callback that readds the images to the map */
163 static void _view_map_changed_callback(OsmGpsMap *map, dt_view_t *self);
164 /* callback that handles mouse scroll */
165 static gboolean _view_map_scroll_event(GtkWidget *w, GdkEventScroll *event, dt_view_t *self);
166 /* callback that handles clicks on the map */
167 static gboolean _view_map_button_press_callback(GtkWidget *w, GdkEventButton *e, dt_view_t *self);
168 static gboolean _view_map_button_release_callback(GtkWidget *w, GdkEventButton *e, dt_view_t *self);
169 /* callback when the mouse is moved */
170 static gboolean _view_map_motion_notify_callback(GtkWidget *w, GdkEventMotion *e, dt_view_t *self);
171 static gboolean _view_map_drag_motion_callback(GtkWidget *widget, GdkDragContext *dc,
172 gint x, gint y, guint time, dt_view_t *self);
173 static gboolean _view_map_dnd_failed_callback(GtkWidget *widget, GdkDragContext *drag_context,
174 GtkDragResult result, dt_view_t *self);
175 static void _view_map_dnd_remove_callback(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
176 GtkSelectionData *selection_data, guint target_type, guint time,
177 gpointer data);
178 // find the images clusters on the map
179 static void _dbscan(dt_geo_position_t *points, unsigned int num_points, double epsilon,
180 unsigned int minpts);
181 static gboolean _view_map_prefs_changed(dt_map_t *lib);
182 static void _view_map_build_main_query(dt_map_t *lib);
183
184 /* center map to on the baricenter of the image list */
185 static gboolean _view_map_center_on_image_list(dt_view_t *self, const char *table);
186 /* center map on the given image */
187 static void _view_map_center_on_image(dt_view_t *self, const int32_t imgid);
188
189 static OsmGpsMapPolygon *_view_map_add_polygon_location(dt_map_t *lib, dt_location_draw_t *ld);
190 static gboolean _view_map_remove_polygon(const dt_view_t *view, OsmGpsMapPolygon *polygon);
191 static OsmGpsMapImage *_view_map_draw_location(dt_map_t *lib, dt_location_draw_t *ld,
192 const gboolean main);
193 static void _view_map_remove_location(dt_map_t *lib, dt_location_draw_t *ld);
194
name(const dt_view_t * self)195 const char *name(const dt_view_t *self)
196 {
197 return _("map");
198 }
199
view(const dt_view_t * self)200 uint32_t view(const dt_view_t *self)
201 {
202 return DT_VIEW_MAP;
203 }
204
205 #ifdef USE_LUA
206
latitude_member(lua_State * L)207 static int latitude_member(lua_State *L)
208 {
209 dt_view_t *module = *(dt_view_t **)lua_touserdata(L, 1);
210 dt_map_t *lib = (dt_map_t *)module->data;
211 if(lua_gettop(L) != 3)
212 {
213 if(dt_view_manager_get_current_view(darktable.view_manager) != module)
214 {
215 lua_pushnumber(L, dt_conf_get_float("plugins/map/latitude"));
216 }
217 else
218 {
219 float value;
220 g_object_get(G_OBJECT(lib->map), "latitude", &value, NULL);
221 lua_pushnumber(L, value);
222 }
223 return 1;
224 }
225 else
226 {
227 luaL_checktype(L, 3, LUA_TNUMBER);
228 float lat = lua_tonumber(L, 3);
229 lat = CLAMP(lat, -90, 90);
230 if(dt_view_manager_get_current_view(darktable.view_manager) != module)
231 {
232 dt_conf_set_float("plugins/map/latitude", lat);
233 }
234 else
235 {
236 float value;
237 g_object_get(G_OBJECT(lib->map), "longitude", &value, NULL);
238 osm_gps_map_set_center(lib->map, lat, value);
239 }
240 return 0;
241 }
242 }
243
longitude_member(lua_State * L)244 static int longitude_member(lua_State *L)
245 {
246 dt_view_t *module = *(dt_view_t **)lua_touserdata(L, 1);
247 dt_map_t *lib = (dt_map_t *)module->data;
248 if(lua_gettop(L) != 3)
249 {
250 if(dt_view_manager_get_current_view(darktable.view_manager) != module)
251 {
252 lua_pushnumber(L, dt_conf_get_float("plugins/map/longitude"));
253 }
254 else
255 {
256 float value;
257 g_object_get(G_OBJECT(lib->map), "longitude", &value, NULL);
258 lua_pushnumber(L, value);
259 }
260 return 1;
261 }
262 else
263 {
264 luaL_checktype(L, 3, LUA_TNUMBER);
265 float longi = lua_tonumber(L, 3);
266 longi = CLAMP(longi, -180, 180);
267 if(dt_view_manager_get_current_view(darktable.view_manager) != module)
268 {
269 dt_conf_set_float("plugins/map/longitude", longi);
270 }
271 else
272 {
273 float value;
274 g_object_get(G_OBJECT(lib->map), "latitude", &value, NULL);
275 osm_gps_map_set_center(lib->map, value, longi);
276 }
277 return 0;
278 }
279 }
280
zoom_member(lua_State * L)281 static int zoom_member(lua_State *L)
282 {
283 dt_view_t *module = *(dt_view_t **)lua_touserdata(L, 1);
284 dt_map_t *lib = (dt_map_t *)module->data;
285 if(lua_gettop(L) != 3)
286 {
287 if(dt_view_manager_get_current_view(darktable.view_manager) != module)
288 {
289 lua_pushnumber(L, dt_conf_get_float("plugins/map/zoom"));
290 }
291 else
292 {
293 int value;
294 g_object_get(G_OBJECT(lib->map), "zoom", &value, NULL);
295 lua_pushnumber(L, value);
296 }
297 return 1;
298 }
299 else
300 {
301 // we rely on osm to correctly clamp zoom (checked in osm source
302 // lua can have temporarily false values but it will fix itself when entering map
303 // unfortunately we can't get the min max when lib->map doesn't exist
304 luaL_checktype(L, 3, LUA_TNUMBER);
305 const int zoom = luaL_checkinteger(L, 3);
306 if(dt_view_manager_get_current_view(darktable.view_manager) != module)
307 {
308 dt_conf_set_int("plugins/map/zoom", zoom);
309 }
310 else
311 {
312 osm_gps_map_set_zoom(lib->map, zoom);
313 }
314 return 0;
315 }
316 }
317 #endif // USE_LUA
318
319 #ifndef HAVE_OSMGPSMAP_110_OR_NEWER
320 // the following functions were taken from libosmgpsmap
321 // Copyright (C) Marcus Bauer 2008 <marcus.bauer@gmail.com>
322 // Copyright (C) 2013 John Stowers <john.stowers@gmail.com>
323 // Copyright (C) 2014 Martijn Goedhart <goedhart.martijn@gmail.com>
324
325 #if FLT_RADIX == 2
326 #define LOG2(x) (ilogb(x))
327 #else
328 #define LOG2(x) ((int)floor(log2(abs(x))))
329 #endif
330
331 #define TILESIZE 256
332
deg2rad(float deg)333 static float deg2rad(float deg)
334 {
335 return (deg * M_PI / 180.0);
336 }
337
latlon2zoom(int pix_height,int pix_width,float lat1,float lat2,float lon1,float lon2)338 static int latlon2zoom(int pix_height, int pix_width, float lat1, float lat2, float lon1, float lon2)
339 {
340 const float lat1_m = atanh(sinf(lat1));
341 const float lat2_m = atanh(sinf(lat2));
342 const int zoom_lon = LOG2((double)(2 * pix_width * M_PI) / (TILESIZE * (lon2 - lon1)));
343 const int zoom_lat = LOG2((double)(2 * pix_height * M_PI) / (TILESIZE * (lat2_m - lat1_m)));
344 return MIN(zoom_lon, zoom_lat);
345 }
346
347 #undef LOG2
348 #undef TILESIZE
349
350 // Copyright (C) 2013 John Stowers <john.stowers@gmail.com>
351 // Copyright (C) Marcus Bauer 2008 <marcus.bauer@gmail.com>
352 // Copyright (C) John Stowers 2009 <john.stowers@gmail.com>
353 // Copyright (C) Till Harbaum 2009 <till@harbaum.org>
354 //
355 // Contributions by
356 // Everaldo Canuto 2009 <everaldo.canuto@gmail.com>
osm_gps_map_zoom_fit_bbox(OsmGpsMap * map,float latitude1,float latitude2,float longitude1,float longitude2)357 static void osm_gps_map_zoom_fit_bbox(OsmGpsMap *map, float latitude1, float latitude2, float longitude1, float longitude2)
358 {
359 GtkAllocation allocation;
360 gtk_widget_get_allocation(GTK_WIDGET (map), &allocation);
361 const int zoom = latlon2zoom(allocation.height, allocation.width,
362 deg2rad(latitude1), deg2rad(latitude2),
363 deg2rad(longitude1), deg2rad(longitude2));
364 osm_gps_map_set_center(map, (latitude1 + latitude2) / 2, (longitude1 + longitude2) / 2);
365 osm_gps_map_set_zoom(map, zoom);
366 }
367 #endif // HAVE_OSMGPSMAP_110_OR_NEWER
368
_toast_log_lat_lon(const float lat,const float lon)369 static void _toast_log_lat_lon(const float lat, const float lon)
370 {
371 gchar *latitude = dt_util_latitude_str(lat);
372 gchar *longitude = dt_util_longitude_str(lon);
373 dt_toast_log("%s %s",latitude, longitude);
374 g_free(latitude);
375 g_free(longitude);
376 }
377
_view_map_images_count(const int nb_images,const gboolean same_loc,double * count_width,double * count_height)378 static GdkPixbuf *_view_map_images_count(const int nb_images, const gboolean same_loc,
379 double *count_width, double *count_height)
380 {
381 char text[8] = {0};
382 snprintf(text, sizeof(text), "%d", nb_images > 99999 ? 99999 : nb_images);
383
384 const int w = DT_PIXEL_APPLY_DPI(thumb_size + 2 * thumb_border);
385 const int h = DT_PIXEL_APPLY_DPI(image_pin_size);
386
387 cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
388 cairo_t *cr = cairo_create(cst);
389 /* fill background */
390 dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_MAP_COUNT_BG);
391 cairo_paint(cr);
392
393 dt_gui_gtk_set_source_rgb(cr, same_loc ? DT_GUI_COLOR_MAP_COUNT_SAME_LOC
394 : DT_GUI_COLOR_MAP_COUNT_DIFF_LOC);
395 cairo_set_font_size(cr, 12 * (1 + (darktable.gui->dpi_factor - 1) / 2));
396 cairo_text_extents_t te;
397 cairo_text_extents(cr, text, &te);
398 *count_width = te.width + 4 * te.x_bearing;
399 *count_height = te.height + 2;
400 cairo_move_to(cr, te.x_bearing, te.height + 1);
401
402 cairo_show_text(cr, text);
403 cairo_destroy(cr);
404 uint8_t *data = cairo_image_surface_get_data(cst);
405 dt_draw_cairo_to_gdk_pixbuf(data, w, h);
406 const size_t size = (size_t)w * h * 4;
407 uint8_t *buf = (uint8_t *)malloc(size);
408 memcpy(buf, data, size);
409 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(buf, GDK_COLORSPACE_RGB, TRUE, 8, w, h, w * 4,
410 (GdkPixbufDestroyNotify)free, NULL);
411 cairo_surface_destroy(cst);
412 return pixbuf;
413 }
414
_init_image_pin()415 static GdkPixbuf *_init_image_pin()
416 {
417 const size_t w = DT_PIXEL_APPLY_DPI(thumb_size + 2 * thumb_border);
418 const size_t h = DT_PIXEL_APPLY_DPI(image_pin_size);
419 g_return_val_if_fail(w > 0 && h > 0, NULL);
420
421 float r, g, b, a;
422 r = ((thumb_frame_color & 0xff000000) >> 24) / 255.0;
423 g = ((thumb_frame_color & 0x00ff0000) >> 16) / 255.0;
424 b = ((thumb_frame_color & 0x0000ff00) >> 8) / 255.0;
425 a = ((thumb_frame_color & 0x000000ff) >> 0) / 255.0;
426
427 cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
428 cairo_t *cr = cairo_create(cst);
429 cairo_set_source_rgba(cr, r, g, b, a);
430 dtgtk_cairo_paint_map_pin(cr, (h-w)/2, 0, w, h, 0, NULL); // keep the pin on left
431 cairo_destroy(cr);
432 uint8_t *data = cairo_image_surface_get_data(cst);
433 dt_draw_cairo_to_gdk_pixbuf(data, w, h);
434 const size_t size = (size_t)w * h * 4;
435 uint8_t *buf = (uint8_t *)malloc(size);
436 memcpy(buf, data, size);
437 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(buf, GDK_COLORSPACE_RGB, TRUE, 8, w, h, w * 4,
438 (GdkPixbufDestroyNotify)free, NULL);
439 cairo_surface_destroy(cst);
440 return pixbuf;
441 }
442
_init_place_pin()443 static GdkPixbuf *_init_place_pin()
444 {
445 const size_t w = DT_PIXEL_APPLY_DPI(place_pin_size);
446 const size_t h = DT_PIXEL_APPLY_DPI(place_pin_size);
447 g_return_val_if_fail(w > 0 && h > 0, NULL);
448
449 float r, g, b, a;
450
451 cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
452 cairo_t *cr = cairo_create(cst);
453
454 // outer shape
455 r = ((pin_outer_color & 0xff000000) >> 24) / 255.0;
456 g = ((pin_outer_color & 0x00ff0000) >> 16) / 255.0;
457 b = ((pin_outer_color & 0x0000ff00) >> 8) / 255.0;
458 a = ((pin_outer_color & 0x000000ff) >> 0) / 255.0;
459 cairo_set_source_rgba(cr, r, g, b, a);
460 cairo_arc(cr, 0.5 * w, 0.333 * h, 0.333 * h - 2, 150.0 * (M_PI / 180.0), 30.0 * (M_PI / 180.0));
461 cairo_line_to(cr, 0.5 * w, h - 2);
462 cairo_close_path(cr);
463 cairo_fill_preserve(cr);
464
465 r = ((pin_line_color & 0xff000000) >> 24) / 255.0;
466 g = ((pin_line_color & 0x00ff0000) >> 16) / 255.0;
467 b = ((pin_line_color & 0x0000ff00) >> 8) / 255.0;
468 a = ((pin_line_color & 0x000000ff) >> 0) / 255.0;
469 cairo_set_source_rgba(cr, r, g, b, a);
470 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1));
471 cairo_stroke(cr);
472
473 // inner circle
474 r = ((pin_inner_color & 0xff000000) >> 24) / 255.0;
475 g = ((pin_inner_color & 0x00ff0000) >> 16) / 255.0;
476 b = ((pin_inner_color & 0x0000ff00) >> 8) / 255.0;
477 a = ((pin_inner_color & 0x000000ff) >> 0) / 255.0;
478 cairo_set_source_rgba(cr, r, g, b, a);
479 cairo_arc(cr, 0.5 * w, 0.333 * h, 0.17 * h, 0, 2.0 * M_PI);
480 cairo_fill(cr);
481
482 cairo_destroy(cr);
483 uint8_t *data = cairo_image_surface_get_data(cst);
484 dt_draw_cairo_to_gdk_pixbuf(data, w, h);
485 size_t size = (size_t)w * h * 4;
486 uint8_t *buf = (uint8_t *)malloc(size);
487 memcpy(buf, data, size);
488 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(buf, GDK_COLORSPACE_RGB, TRUE, 8, w, h, w * 4,
489 (GdkPixbufDestroyNotify)free, NULL);
490 cairo_surface_destroy(cst);
491 return pixbuf;
492 }
493
_draw_ellipse(const float dlongitude,const float dlatitude,const gboolean main)494 static GdkPixbuf *_draw_ellipse(const float dlongitude, const float dlatitude,
495 const gboolean main)
496 {
497 const int dlon = dlongitude > max_size ? max_size :
498 dlongitude < cross_size ? cross_size : dlongitude;
499 const int dlat = dlatitude > max_size ? max_size :
500 dlatitude < cross_size ? cross_size : dlatitude;
501 const gboolean landscape = dlon > dlat ? TRUE : FALSE;
502 const float ratio = dlon > dlat ? (float)dlat / (float)dlon : (float)dlon / (float)dlat;
503 const int w = DT_PIXEL_APPLY_DPI(2.0 * (landscape ? dlon : dlat));
504 const int h = w;
505 const int d = DT_PIXEL_APPLY_DPI(main ? 2 : 1);
506 const int cross = DT_PIXEL_APPLY_DPI(cross_size);
507 cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
508 cairo_t *cr = cairo_create(cst);
509
510 cairo_set_line_width(cr, d);
511 const int color_hi = dlon == max_size || dlon == cross_size
512 ? main ? DT_GUI_COLOR_MAP_LOC_SHAPE_DEF
513 : DT_GUI_COLOR_MAP_LOC_SHAPE_HIGH
514 : DT_GUI_COLOR_MAP_LOC_SHAPE_HIGH;
515
516 cairo_matrix_t save_matrix;
517 cairo_get_matrix(cr, &save_matrix);
518 cairo_translate(cr, 0.5 * w, 0.5 * h);
519 cairo_scale(cr, landscape ? 1 : ratio, landscape ? ratio : 1);
520 cairo_translate(cr, -0.5 * w, -0.5 * h);
521
522 dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_MAP_LOC_SHAPE_LOW);
523 cairo_arc(cr, 0.5 * w, 0.5 * h, 0.5 * h - d - d, 0, 2.0 * M_PI);
524
525 cairo_set_matrix(cr, &save_matrix);
526 cairo_stroke(cr);
527 cairo_move_to(cr, 0.5 * w + d, 0.5 * h - cross);
528 cairo_line_to(cr, 0.5 * w + d, 0.5 * h + cross);
529 cairo_move_to(cr, 0.5 * w - cross, 0.5 * h - d);
530 cairo_line_to(cr, 0.5 * w + cross, 0.5 * h - d);
531 cairo_stroke(cr);
532
533 cairo_get_matrix(cr, &save_matrix);
534 cairo_translate(cr, 0.5 * w, 0.5 * h);
535 cairo_scale(cr, landscape ? 1 : ratio, landscape ? ratio : 1);
536 cairo_translate(cr, -0.5 * w, -0.5 * h);
537
538 dt_gui_gtk_set_source_rgb(cr, color_hi);
539 cairo_arc(cr, 0.5 * w, 0.5 * h, 0.5 * h - d, 0, 2.0 * M_PI);
540
541 cairo_set_matrix(cr, &save_matrix);
542
543 cairo_stroke(cr);
544 cairo_move_to(cr, 0.5 * w, 0.5 * h - cross);
545 cairo_line_to(cr, 0.5 * w, 0.5 * h + cross);
546 cairo_move_to(cr, 0.5 * w - cross, 0.5 * h );
547 cairo_line_to(cr, 0.5 * w + cross, 0.5 * h );
548 cairo_stroke(cr);
549
550 cairo_destroy(cr);
551 uint8_t *data = cairo_image_surface_get_data(cst);
552 dt_draw_cairo_to_gdk_pixbuf(data, w, h);
553 size_t size = (size_t)w * h * 4;
554 uint8_t *buf = (uint8_t *)malloc(size);
555 memcpy(buf, data, size);
556 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(buf, GDK_COLORSPACE_RGB, TRUE, 8, w, h, w * 4,
557 (GdkPixbufDestroyNotify)free, NULL);
558 cairo_surface_destroy(cst);
559 return pixbuf;
560 }
561
_draw_rectangle(const float dlongitude,const float dlatitude,const gboolean main)562 static GdkPixbuf *_draw_rectangle(const float dlongitude, const float dlatitude,
563 const gboolean main)
564 {
565 const int dlon = dlongitude > max_size ? max_size :
566 dlongitude < cross_size ? cross_size : dlongitude;
567 const int dlat = dlatitude > max_size ? max_size :
568 dlatitude < cross_size ? cross_size : dlatitude;
569 const int w = DT_PIXEL_APPLY_DPI(2.0 * dlon);
570 const int h = DT_PIXEL_APPLY_DPI(2.0 * dlat);
571 const int d = DT_PIXEL_APPLY_DPI(main ? 2 : 1);
572 const int cross = DT_PIXEL_APPLY_DPI(cross_size);
573 cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
574 cairo_t *cr = cairo_create(cst);
575
576 cairo_set_line_width(cr,d);
577 dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_MAP_LOC_SHAPE_LOW);
578 cairo_move_to(cr, d + d, d + d);
579 cairo_line_to(cr, w - d - d, d + d);
580 cairo_line_to(cr, w - d - d, h - d - d);
581 cairo_line_to(cr, d + d, h - d - d);
582 cairo_line_to(cr, d + d, d + d);
583 cairo_move_to(cr, 0.5 * w + d, 0.5 * h - cross);
584 cairo_line_to(cr, 0.5 * w + d, 0.5 * h + cross);
585 cairo_move_to(cr, 0.5 * w - cross, 0.5 * h - d);
586 cairo_line_to(cr, 0.5 * w + cross, 0.5 * h - d);
587 cairo_stroke(cr);
588
589 dt_gui_gtk_set_source_rgb(cr, dlon == max_size || dlon == cross_size ||
590 dlat == max_size || dlat == cross_size
591 ? main ? DT_GUI_COLOR_MAP_LOC_SHAPE_DEF
592 : DT_GUI_COLOR_MAP_LOC_SHAPE_HIGH
593 : DT_GUI_COLOR_MAP_LOC_SHAPE_HIGH);
594 cairo_move_to(cr, d, d);
595 cairo_line_to(cr, w - d, d);
596 cairo_line_to(cr, w - d, h - d);
597 cairo_line_to(cr, d, h - d);
598 cairo_line_to(cr, d, d);
599 cairo_move_to(cr, 0.5 * w, 0.5 * h - cross);
600 cairo_line_to(cr, 0.5 * w, 0.5 * h + cross);
601 cairo_move_to(cr, 0.5 * w - cross, 0.5 * h );
602 cairo_line_to(cr, 0.5 * w + cross, 0.5 * h );
603 cairo_stroke(cr);
604
605 cairo_destroy(cr);
606 uint8_t *data = cairo_image_surface_get_data(cst);
607 dt_draw_cairo_to_gdk_pixbuf(data, w, h);
608 size_t size = (size_t)w * h * 4;
609 uint8_t *buf = (uint8_t *)malloc(size);
610 memcpy(buf, data, size);
611 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(buf, GDK_COLORSPACE_RGB, TRUE, 8, w, h, w * 4,
612 (GdkPixbufDestroyNotify)free, NULL);
613 cairo_surface_destroy(cst);
614 return pixbuf;
615 }
616
expose(dt_view_t * self,cairo_t * cri,int32_t width,int32_t height,int32_t pointerx,int32_t pointery)617 void expose(dt_view_t *self, cairo_t *cri, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
618 {
619 dt_map_t *lib = (dt_map_t *)self->data;
620 if(lib->entering)
621 {
622 // we need to ensure there's no remaining things on canvas.
623 // otherwise they can appear on map move
624 lib->entering = FALSE;
625 cairo_set_source_rgb(cri, 0, 0, 0);
626 cairo_paint(cri);
627 }
628 }
629
_view_changed(gpointer instance,dt_view_t * old_view,dt_view_t * new_view,dt_view_t * self)630 static void _view_changed(gpointer instance, dt_view_t *old_view,
631 dt_view_t *new_view, dt_view_t *self)
632 {
633 if(old_view == self)
634 {
635 _view_map_location_action(self, MAP_LOCATION_ACTION_REMOVE);
636 }
637 }
638
init(dt_view_t * self)639 void init(dt_view_t *self)
640 {
641 self->data = calloc(1, sizeof(dt_map_t));
642
643 dt_map_t *lib = (dt_map_t *)self->data;
644
645 darktable.view_manager->proxy.map.view = self;
646
647 if(darktable.gui)
648 {
649 lib->image_pin = _init_image_pin();
650 lib->place_pin = _init_place_pin();
651 lib->drop_filmstrip_activated = FALSE;
652 lib->thumb_lat_angle = 0.01, lib->thumb_lon_angle = 0.01;
653 lib->time_out = 0, lib->timeout_event_source = 0;
654 lib->loc.main.id = 0, lib->loc.main.location = NULL, lib->loc.time_out = 0;
655 lib->loc.others = NULL;
656 lib->last_hovered_entry = NULL;
657
658 OsmGpsMapSource_t map_source
659 = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; // open street map should be a nice default ...
660 const char *old_map_source = dt_conf_get_string_const("plugins/map/map_source");
661 if(old_map_source && old_map_source[0] != '\0')
662 {
663 // find the number of the stored map_source
664 for(int i = 0; i <= OSM_GPS_MAP_SOURCE_LAST; i++)
665 {
666 const gchar *new_map_source = osm_gps_map_source_get_friendly_name(i);
667 if(!g_strcmp0(old_map_source, new_map_source))
668 {
669 if(osm_gps_map_source_is_valid(i)) map_source = i;
670 break;
671 }
672 }
673 }
674 else
675 dt_conf_set_string("plugins/map/map_source", osm_gps_map_source_get_friendly_name(map_source));
676
677 lib->map_source = map_source;
678
679 lib->map = g_object_new(OSM_TYPE_GPS_MAP, "map-source", OSM_GPS_MAP_SOURCE_NULL, "proxy-uri",
680 g_getenv("http_proxy"), NULL);
681
682 g_object_ref(lib->map); // we want to keep map alive until explicit destroy
683
684 lib->osd = g_object_new(OSM_TYPE_GPS_MAP_OSD, "show-scale", TRUE, "show-coordinates", TRUE, "show-dpad",
685 TRUE, "show-zoom", TRUE,
686 #ifdef HAVE_OSMGPSMAP_NEWER_THAN_110
687 "show-copyright", TRUE,
688 #endif
689 NULL);
690
691 if(dt_conf_get_bool("plugins/map/show_map_osd"))
692 {
693 osm_gps_map_layer_add(OSM_GPS_MAP(lib->map), lib->osd);
694 }
695
696 gtk_drag_dest_set(GTK_WIDGET(lib->map), GTK_DEST_DEFAULT_ALL,
697 target_list_internal, n_targets_internal, GDK_ACTION_MOVE);
698 g_signal_connect(GTK_WIDGET(lib->map), "scroll-event", G_CALLBACK(_view_map_scroll_event), self);
699 g_signal_connect(GTK_WIDGET(lib->map), "drag-data-received", G_CALLBACK(_drag_and_drop_received), self);
700 g_signal_connect(GTK_WIDGET(lib->map), "drag-data-get", G_CALLBACK(_view_map_dnd_get_callback), self);
701 g_signal_connect(GTK_WIDGET(lib->map), "drag-failed", G_CALLBACK(_view_map_dnd_failed_callback), self);
702
703 g_signal_connect(GTK_WIDGET(lib->map), "changed", G_CALLBACK(_view_map_changed_callback), self);
704 g_signal_connect_after(G_OBJECT(lib->map), "button-press-event",
705 G_CALLBACK(_view_map_button_press_callback), self);
706 g_signal_connect_after(G_OBJECT(lib->map), "button-release-event",
707 G_CALLBACK(_view_map_button_release_callback), self);
708 g_signal_connect(G_OBJECT(lib->map), "motion-notify-event", G_CALLBACK(_view_map_motion_notify_callback),
709 self);
710 g_signal_connect(G_OBJECT(lib->map), "drag-motion", G_CALLBACK(_view_map_drag_motion_callback),
711 self);
712 }
713
714 /* build the query string */
715 lib->main_query = NULL;
716 _view_map_build_main_query(lib);
717
718 #ifdef USE_LUA
719 lua_State *L = darktable.lua_state.state;
720 luaA_Type my_type = dt_lua_module_entry_get_type(L, "view", self->module_name);
721 lua_pushcfunction(L, latitude_member);
722 dt_lua_gtk_wrap(L);
723 dt_lua_type_register_type(L, my_type, "latitude");
724 lua_pushcfunction(L, longitude_member);
725 dt_lua_gtk_wrap(L);
726 dt_lua_type_register_type(L, my_type, "longitude");
727 lua_pushcfunction(L, zoom_member);
728 dt_lua_gtk_wrap(L);
729 dt_lua_type_register_type(L, my_type, "zoom");
730
731 #endif // USE_LUA
732 /* connect collection changed signal */
733 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_COLLECTION_CHANGED,
734 G_CALLBACK(_view_map_collection_changed), (gpointer)self);
735 /* connect selection changed signal */
736 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_SELECTION_CHANGED,
737 G_CALLBACK(_view_map_selection_changed), (gpointer)self);
738 /* connect preference changed signal */
739 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_PREFERENCES_CHANGE,
740 G_CALLBACK(_view_map_check_preference_changed), (gpointer)self);
741
742 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_VIEWMANAGER_VIEW_CHANGED,
743 G_CALLBACK(_view_changed), (gpointer)self);
744
745 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_GEOTAG_CHANGED,
746 G_CALLBACK(_view_map_geotag_changed), (gpointer)self);
747 }
748
cleanup(dt_view_t * self)749 void cleanup(dt_view_t *self)
750 {
751 dt_map_t *lib = (dt_map_t *)self->data;
752
753 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_view_map_collection_changed), self);
754 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_view_map_selection_changed), self);
755 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_view_map_check_preference_changed), self);
756 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_view_changed), self);
757 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_view_map_geotag_changed), self);
758
759 if(darktable.gui)
760 {
761 g_object_unref(G_OBJECT(lib->image_pin));
762 g_object_unref(G_OBJECT(lib->place_pin));
763 g_object_unref(G_OBJECT(lib->osd));
764 osm_gps_map_image_remove_all(lib->map);
765 if(lib->points)
766 {
767 g_free(lib->points);
768 lib->points = NULL;
769 }
770 if(lib->images)
771 {
772 g_slist_free_full(lib->images, g_free);
773 lib->images = NULL;
774 }
775 if(lib->loc.main.id)
776 {
777 _view_map_remove_location(lib, &lib->loc.main);
778 lib->loc.main.id = 0;
779 }
780 if(lib->loc.others)
781 {
782 for(GList *other = lib->loc.others; other; other = g_list_next(other))
783 {
784 dt_location_draw_t *d = (dt_location_draw_t *)other->data;
785 _view_map_remove_location(lib, d);
786 // polygons are freed only from others list
787 dt_map_location_free_polygons(d);
788 }
789 g_list_free_full(lib->loc.others, g_free);
790 lib->loc.others = NULL;
791 }
792 // FIXME: it would be nice to cleanly destroy the object, but we are doing this inside expose() so
793 // removing the widget can cause segfaults.
794 // g_object_unref(G_OBJECT(lib->map));
795 }
796 if(lib->main_query) sqlite3_finalize(lib->main_query);
797 free(self->data);
798 }
799
configure(dt_view_t * self,int wd,int ht)800 void configure(dt_view_t *self, int wd, int ht)
801 {
802 // dt_capture_t *lib=(dt_capture_t*)self->data;
803 }
804
try_enter(dt_view_t * self)805 int try_enter(dt_view_t *self)
806 {
807 return 0;
808 }
809
_view_map_signal_change_raise(gpointer user_data)810 static void _view_map_signal_change_raise(gpointer user_data)
811 {
812 dt_view_t *self = (dt_view_t *)user_data;
813 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_view_map_geotag_changed), self);
814 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_view_map_collection_changed), self);
815 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_GEOTAG_CHANGED, NULL, 0);
816 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_view_map_collection_changed), self);
817 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_view_map_geotag_changed), self);
818 }
819
820 // updating collection when mouse scrolls to resize the location is too demanding
821 // so wait for scrolling stop
_view_map_signal_change_delayed(gpointer user_data)822 static gboolean _view_map_signal_change_delayed(gpointer user_data)
823 {
824 dt_view_t *self = (dt_view_t *)user_data;
825 dt_map_t *lib = (dt_map_t *)self->data;
826 if(lib->loc.time_out)
827 {
828 lib->loc.time_out--;
829 if(!lib->loc.time_out)
830 {
831 _view_map_signal_change_raise(self);
832 return FALSE;
833 }
834 }
835 return TRUE;
836 }
837
_view_map_signal_change_wait(dt_view_t * self,const int time_out)838 static void _view_map_signal_change_wait(dt_view_t *self, const int time_out)
839 {
840 dt_map_t *lib = (dt_map_t *)self->data;
841 if(time_out)
842 {
843 if(lib->loc.time_out)
844 {
845 lib->loc.time_out = time_out;
846 }
847 else
848 {
849 lib->loc.time_out = time_out;
850 g_timeout_add(100, _view_map_signal_change_delayed, self);
851 }
852 }
853 else _view_map_signal_change_raise(self);
854 }
855
_view_map_redraw(gpointer user_data)856 static gboolean _view_map_redraw(gpointer user_data)
857 {
858 dt_view_t *self = (dt_view_t *)user_data;
859 dt_map_t *lib = (dt_map_t *)self->data;
860 g_signal_emit_by_name(lib->map, "changed");
861 return FALSE; // remove the function again
862 }
863
_view_map_angles(dt_map_t * lib,const gint pixels,const float lat,const float lon,float * dlat_min,float * dlon_min)864 static void _view_map_angles(dt_map_t *lib, const gint pixels, const float lat,
865 const float lon, float *dlat_min, float *dlon_min)
866 {
867 OsmGpsMapPoint *pt0 = osm_gps_map_point_new_degrees(lat, lon);
868 OsmGpsMapPoint *pt1 = osm_gps_map_point_new_degrees(0.0, 0.0);
869 gint pixel_x = 0, pixel_y = 0;
870 osm_gps_map_convert_geographic_to_screen(lib->map, pt0, &pixel_x, &pixel_y);
871 osm_gps_map_convert_screen_to_geographic(lib->map, pixel_x + pixels,
872 pixel_y + pixels, pt1);
873 float lat1 = 0.0, lon1 = 0.0;
874 osm_gps_map_point_get_degrees(pt1, &lat1, &lon1);
875 osm_gps_map_point_free(pt0);
876 osm_gps_map_point_free(pt1);
877 *dlat_min = lat - lat1;
878 *dlon_min = lon1 - lon;
879 }
880
881 // when map is moving we often get incorrect (even negative) values.
882 // keep the last positive values here to limit wrong effects (still not perfect)
_view_map_thumb_angles(dt_map_t * lib,const float lat,const float lon,float * dlat_min,float * dlon_min)883 static void _view_map_thumb_angles(dt_map_t *lib, const float lat, const float lon,
884 float *dlat_min, float *dlon_min)
885 {
886 _view_map_angles(lib, thumb_size, lat, lon, dlat_min, dlon_min);
887 if(*dlat_min > 0.0 && *dlon_min > 0.0)
888 {
889 lib->thumb_lat_angle = *dlat_min;
890 lib->thumb_lon_angle = *dlon_min;
891 }
892 else // something got wrong, keep the last positive values
893 {
894 *dlat_min = lib->thumb_lat_angle ;
895 *dlon_min = lib->thumb_lon_angle;
896 }
897 }
898
_view_map_angles_to_pixels(const dt_map_t * lib,const float lat0,const float lon0,const float angle)899 static float _view_map_angles_to_pixels(const dt_map_t *lib, const float lat0,
900 const float lon0, const float angle)
901 {
902 OsmGpsMapPoint *pt0 = osm_gps_map_point_new_degrees(lat0, lon0);
903 OsmGpsMapPoint *pt1 = osm_gps_map_point_new_degrees(lat0 + angle, lon0 + angle);
904 gint px0 = 0, py0 = 0;
905 gint px1 = 0, py1 = 0;
906 osm_gps_map_convert_geographic_to_screen(lib->map, pt0, &px0, &py0);
907 osm_gps_map_convert_geographic_to_screen(lib->map, pt1, &px1, &py1);
908 osm_gps_map_point_free(pt0);
909 osm_gps_map_point_free(pt1);
910 return abs(px1 - px0);
911 }
912
_view_map_get_angles_ratio(const dt_map_t * lib,const float lat0,const float lon0)913 static double _view_map_get_angles_ratio(const dt_map_t *lib, const float lat0,
914 const float lon0)
915 {
916 OsmGpsMapPoint *pt0 = osm_gps_map_point_new_degrees(lat0, lon0);
917 OsmGpsMapPoint *pt1 = osm_gps_map_point_new_degrees(lat0 + 2.0, lon0 + 2.0);
918 gint px0 = 0, py0 = 0;
919 gint px1 = 0, py1 = 0;
920 osm_gps_map_convert_geographic_to_screen(lib->map, pt0, &px0, &py0);
921 osm_gps_map_convert_geographic_to_screen(lib->map, pt1, &px1, &py1);
922 osm_gps_map_point_free(pt0);
923 osm_gps_map_point_free(pt1);
924 double ratio = 1.;
925 if((px1 - px0) > 0)
926 ratio = (float)abs(py1 - py0) / (float)(px1 - px0);
927 return ratio;
928 }
929
_draw_location(dt_map_t * lib,int * width,int * height,dt_map_location_data_t * data,const gboolean main)930 static GdkPixbuf *_draw_location(dt_map_t *lib, int *width, int *height,
931 dt_map_location_data_t *data,
932 const gboolean main)
933 {
934 float pixel_lon = _view_map_angles_to_pixels(lib, data->lat, data->lon, data->delta1);
935 float pixel_lat = pixel_lon * data->delta2 * data->ratio / data->delta1;
936 GdkPixbuf *draw = NULL;
937 if(data->shape == MAP_LOCATION_SHAPE_ELLIPSE)
938 {
939 draw = _draw_ellipse(pixel_lon, pixel_lat, main);
940 if(pixel_lon > pixel_lat)
941 pixel_lat = pixel_lon;
942 else
943 pixel_lon = pixel_lat;
944 }
945 else if(data->shape == MAP_LOCATION_SHAPE_RECTANGLE)
946 draw = _draw_rectangle(pixel_lon, pixel_lat, main);
947
948 if(width) *width = (int)pixel_lon;
949 if(height) *height = (int)pixel_lat;
950 return draw;
951 }
952
_others_location_draw(dt_map_t * lib,const int locid)953 dt_location_draw_t *_others_location_draw(dt_map_t *lib, const int locid)
954 {
955 for(GList *other = lib->loc.others; other; other = g_list_next(other))
956 {
957 dt_location_draw_t *d = (dt_location_draw_t *)other->data;
958 if(d->id == locid)
959 return d;
960 }
961 return NULL;
962 }
963
_others_location(GList * others,const int locid)964 GList *_others_location(GList *others, const int locid)
965 {
966 for(GList *other = others; other; other = g_list_next(other))
967 {
968 dt_location_draw_t *d = (dt_location_draw_t *)other->data;
969 if(d->id == locid)
970 return other;
971 }
972 return NULL;
973 }
974
_view_map_draw_location(dt_map_t * lib,dt_location_draw_t * ld,const gboolean main)975 static OsmGpsMapImage *_view_map_draw_location(dt_map_t *lib, dt_location_draw_t *ld,
976 const gboolean main)
977 {
978 if(ld->data.shape != MAP_LOCATION_SHAPE_POLYGONS)
979 {
980 GdkPixbuf *draw = _draw_location(lib, NULL, NULL, &ld->data, main);
981 OsmGpsMapImage *location = NULL;
982 if(draw)
983 {
984 location = osm_gps_map_image_add_with_alignment(lib->map, ld->data.lat, ld->data.lon, draw, 0.5, 0.5);
985 g_object_unref(draw);
986 }
987 return location;
988 }
989 else
990 {
991 if(!ld->data.polygons && ld == &lib->loc.main)
992 {
993 dt_location_draw_t *d = _others_location_draw(lib, lib->loc.main.id);
994 if(d)
995 {
996 ld->data.polygons = d->data.polygons;
997 ld->data.plg_pts = d->data.plg_pts;
998 }
999 }
1000 if(!ld->data.polygons)
1001 dt_map_location_get_polygons(ld);
1002 OsmGpsMapPolygon *location = _view_map_add_polygon_location(lib, ld);
1003 return (OsmGpsMapImage *)location;
1004 }
1005 }
1006
_view_map_remove_location(dt_map_t * lib,dt_location_draw_t * ld)1007 static void _view_map_remove_location(dt_map_t *lib, dt_location_draw_t *ld)
1008 {
1009 if(ld->location)
1010 {
1011 if(ld->data.shape != MAP_LOCATION_SHAPE_POLYGONS)
1012 osm_gps_map_image_remove(lib->map, ld->location);
1013 else
1014 osm_gps_map_polygon_remove(lib->map, (OsmGpsMapPolygon *)ld->location);
1015 ld->location = NULL;
1016 }
1017 }
1018
_view_map_delete_other_location(dt_map_t * lib,GList * other)1019 static void _view_map_delete_other_location(dt_map_t *lib, GList *other)
1020 {
1021 dt_location_draw_t *d = (dt_location_draw_t *)other->data;
1022 _view_map_remove_location(lib, d);
1023 dt_map_location_free_polygons(d);
1024 lib->loc.others = g_list_remove_link(lib->loc.others, other);
1025 g_free(other->data);
1026 g_list_free(other);
1027 }
1028
_view_map_draw_main_location(dt_map_t * lib,dt_location_draw_t * ld)1029 static void _view_map_draw_main_location(dt_map_t *lib, dt_location_draw_t *ld)
1030 {
1031 if(lib->loc.main.id && (ld != &lib->loc.main))
1032 {
1033 // save the data to others locations (including polygons)
1034 dt_location_draw_t *d = _others_location_draw(lib, lib->loc.main.id);
1035 if(!d)
1036 {
1037 d = g_malloc0(sizeof(dt_location_draw_t));
1038 lib->loc.others = g_list_append(lib->loc.others, d);
1039 }
1040 if(d)
1041 {
1042 // copy everything except the drawn location
1043 memcpy(d, &lib->loc.main, sizeof(dt_location_draw_t));
1044 d->location = NULL;
1045 if(dt_conf_get_bool("plugins/map/showalllocations"))
1046 d->location = _view_map_draw_location(lib, d, FALSE);
1047 }
1048 }
1049 _view_map_remove_location(lib, &lib->loc.main);
1050
1051 if(ld && ld->id)
1052 {
1053 memcpy(&lib->loc.main, ld, sizeof(dt_location_draw_t));
1054 lib->loc.main.location = _view_map_draw_location(lib, &lib->loc.main, TRUE);
1055 dt_location_draw_t *d = _others_location_draw(lib, ld->id);
1056 if(d && d->location)
1057 _view_map_remove_location(lib, d);
1058 }
1059 }
1060
_view_map_draw_other_locations(dt_map_t * lib,dt_map_box_t * bbox)1061 static void _view_map_draw_other_locations(dt_map_t *lib, dt_map_box_t *bbox)
1062 {
1063 // clean up other locations
1064 {
1065 for(GList *other = lib->loc.others; other; other = g_list_next(other))
1066 {
1067 dt_location_draw_t *d = (dt_location_draw_t *)other->data;
1068 _view_map_remove_location(lib, d);
1069 }
1070 }
1071 if(dt_conf_get_bool("plugins/map/showalllocations"))
1072 {
1073 GList *others = dt_map_location_get_locations_on_map(bbox);
1074
1075 for(GList *other = others; other; other = g_list_next(other))
1076 {
1077 dt_location_draw_t *d = (dt_location_draw_t *)other->data;
1078 GList *other2 = _others_location(lib->loc.others, d->id);
1079 // add the new ones
1080 if(!other2)
1081 {
1082 d = (dt_location_draw_t *)other->data;
1083 lib->loc.others = g_list_append(lib->loc.others, other->data);
1084 other->data = NULL; // to avoid it gets freed
1085 if(d->data.shape == MAP_LOCATION_SHAPE_POLYGONS)
1086 {
1087 if(d->id == lib->loc.main.id)
1088 {
1089 d->data.polygons = lib->loc.main.data.polygons;
1090 d->data.plg_pts = lib->loc.main.data.plg_pts;
1091 }
1092 if(!d->data.polygons)
1093 dt_map_location_get_polygons(d);
1094 }
1095 }
1096 else
1097 d = (dt_location_draw_t *)other2->data;
1098 // display again location except polygons and the selected one
1099 if((!lib->loc.main.id || lib->loc.main.id != d->id) && !d->location)
1100 d->location = _view_map_draw_location(lib, d, FALSE);
1101 }
1102 // free the new list
1103 g_list_free_full(others, g_free);
1104 }
1105 }
1106
_view_map_update_location_geotag(dt_view_t * self)1107 static void _view_map_update_location_geotag(dt_view_t *self)
1108 {
1109 dt_map_t *lib = (dt_map_t *)self->data;
1110 if(lib->loc.main.id > 0)
1111 {
1112 // update coordinates
1113 dt_map_location_set_data(lib->loc.main.id, &lib->loc.main.data);
1114 if(dt_map_location_update_images(&lib->loc.main))
1115 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_TAG_CHANGED);
1116 }
1117 }
1118
_draw_image(const int imgid,int * width,int * height,const int group_count,const gboolean group_same_loc,const uint32_t frame,const gboolean blocking,const int thumbnail,dt_view_t * self)1119 static GdkPixbuf *_draw_image(const int imgid, int *width, int *height,
1120 const int group_count, const gboolean group_same_loc,
1121 const uint32_t frame, const gboolean blocking,
1122 const int thumbnail, dt_view_t *self)
1123 {
1124 dt_map_t *lib = (dt_map_t *)self->data;
1125
1126 GdkPixbuf *thumb = NULL, *source = NULL, *count = NULL;
1127 const int _thumb_size = DT_PIXEL_APPLY_DPI(thumb_size);
1128 const float _thumb_border = DT_PIXEL_APPLY_DPI(thumb_border);
1129 const float _pin_size = DT_PIXEL_APPLY_DPI(image_pin_size);
1130
1131 if(thumbnail == DT_MAP_THUMB_THUMB)
1132 {
1133 dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, _thumb_size, _thumb_size);
1134 dt_mipmap_buffer_t buf;
1135 dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, mip,
1136 blocking ? DT_MIPMAP_BLOCKING : DT_MIPMAP_BEST_EFFORT, 'r');
1137
1138 if(buf.buf && buf.width > 0)
1139 {
1140 for(int i = 3; i < (size_t)4 * buf.width * buf.height; i += 4) buf.buf[i] = UINT8_MAX;
1141
1142 int w = _thumb_size, h = _thumb_size;
1143 if(buf.width < buf.height)
1144 w = (buf.width * _thumb_size) / buf.height; // portrait
1145 else
1146 h = (buf.height * _thumb_size) / buf.width; // landscape
1147
1148 // next we get a pixbuf for the image
1149 source = gdk_pixbuf_new_from_data(buf.buf, GDK_COLORSPACE_RGB, TRUE,
1150 8, buf.width, buf.height,
1151 buf.width * 4, NULL, NULL);
1152 dt_mipmap_cache_release(darktable.mipmap_cache, &buf);
1153 if(!source) goto map_changed_failure;
1154 // now we want a slightly larger pixbuf that we can put the image on
1155 thumb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w + 2 * _thumb_border,
1156 h + 2 * _thumb_border + _pin_size);
1157 if(!thumb) goto map_changed_failure;
1158 gdk_pixbuf_fill(thumb, frame);
1159 // put the image onto the frame
1160 gdk_pixbuf_scale(source, thumb, _thumb_border, _thumb_border, w, h,
1161 _thumb_border, _thumb_border, (1.0 * w) / buf.width,
1162 (1.0 * h) / buf.height, GDK_INTERP_HYPER);
1163 // add the pin
1164 gdk_pixbuf_copy_area(lib->image_pin, 0, 0, w + 2 * _thumb_border,
1165 _pin_size, thumb, 0, h + 2 * _thumb_border);
1166 // add the count
1167 if(group_count)
1168 {
1169 double count_height, count_width;
1170 count = _view_map_images_count(group_count, group_same_loc,
1171 &count_width, &count_height);
1172 gdk_pixbuf_copy_area(count, 0, 0, count_width, count_height, thumb,
1173 _thumb_border, h - count_height + _thumb_border);
1174
1175 }
1176 if(width) *width = w;
1177 if(height) *height = h;
1178 }
1179 }
1180 else if(thumbnail == DT_MAP_THUMB_COUNT)
1181 {
1182 double count_height, count_width;
1183 count = _view_map_images_count(group_count, group_same_loc,
1184 &count_width, &count_height);
1185 if(!count) goto map_changed_failure;
1186 const int w = count_width + 2 * _thumb_border;
1187 const int h = count_height + 2 * _thumb_border + _pin_size;
1188 thumb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
1189 if(!thumb) goto map_changed_failure;
1190 gdk_pixbuf_fill(thumb, frame);
1191 gdk_pixbuf_copy_area(count, 0, 0, count_width, count_height, thumb,
1192 _thumb_border, _thumb_border);
1193 gdk_pixbuf_copy_area(lib->image_pin, 0, 0, w, _pin_size, thumb,
1194 0, count_height + 2 * _thumb_border);
1195 if(width) *width = count_width;
1196 if(height) *height = count_height;
1197 }
1198 map_changed_failure:
1199 if(source) g_object_unref(source);
1200 if(count) g_object_unref(count);
1201 return thumb;
1202 }
1203
_view_map_draw_image(dt_map_image_t * entry,const gboolean blocking,const int thumbnail,dt_view_t * self)1204 static gboolean _view_map_draw_image(dt_map_image_t *entry, const gboolean blocking,
1205 const int thumbnail, dt_view_t *self)
1206 {
1207 dt_map_t *lib = (dt_map_t *)self->data;
1208 gboolean needs_redraw = FALSE;
1209 if(entry->image && entry->thumbnail != thumbnail)
1210 {
1211 osm_gps_map_image_remove(lib->map, entry->image);
1212 entry->image = NULL;
1213 }
1214 if(!entry->image)
1215 {
1216 GdkPixbuf *thumb = _draw_image(entry->imgid, &entry->width, &entry->height,
1217 entry->group_count, entry->group_same_loc,
1218 entry->selected_in_group ? thumb_frame_sel_color : thumb_frame_color,
1219 blocking, thumbnail, self);
1220 if(thumb)
1221 {
1222 entry->thumbnail = thumbnail;
1223 entry->image = osm_gps_map_image_add_with_alignment(lib->map, entry->latitude,
1224 entry->longitude,
1225 thumb, 0, 1);
1226 g_object_unref(thumb);
1227 }
1228 else
1229 needs_redraw = TRUE;
1230 }
1231 return needs_redraw;
1232 }
1233
1234 // scan the images list and draw the missing ones
1235 // if launched to be executed repeatedly, return FALSE when it is done
_view_map_draw_images(gpointer user_data)1236 static gboolean _view_map_draw_images(gpointer user_data)
1237 {
1238 dt_view_t *self = (dt_view_t *)user_data;
1239 dt_map_t *lib = (dt_map_t *)self->data;
1240 gboolean needs_redraw = FALSE;
1241 int img_drawn = 0;
1242 for(GSList *iter = lib->images; iter; iter = g_slist_next(iter))
1243 {
1244 needs_redraw = _view_map_draw_image((dt_map_image_t *)iter->data, FALSE,
1245 lib->thumbnail, self);
1246 img_drawn++;
1247 // we limit the number of displayed images as required
1248 if(lib->thumbnail == DT_MAP_THUMB_THUMB && img_drawn >= lib->max_images_drawn)
1249 break;
1250 }
1251 if(!needs_redraw)
1252 lib->timeout_event_source = 0;
1253 return needs_redraw;
1254 }
1255
_view_map_get_bounding_box(dt_map_t * lib,dt_map_box_t * bbox)1256 static void _view_map_get_bounding_box(dt_map_t *lib, dt_map_box_t *bbox)
1257 {
1258 OsmGpsMapPoint bb[2];
1259 osm_gps_map_get_bbox(lib->map, &bb[0], &bb[1]);
1260 dt_map_box_t box = {0.0};
1261
1262 osm_gps_map_point_get_degrees(&bb[0], &box.lat1, &box.lon1);
1263 osm_gps_map_point_get_degrees(&bb[1], &box.lat2, &box.lon2);
1264 box.lat1 = CLAMP(box.lat1, -90.0, 90.0);
1265 box.lat2 = CLAMP(box.lat2, -90.0, 90.0);
1266 box.lon1 = CLAMP(box.lon1, -180.0, 180.0);
1267 box.lon2 = CLAMP(box.lon2, -180.0, 180.0);
1268 memcpy(bbox, &box, sizeof(dt_map_box_t));
1269 }
1270
_view_map_changed_callback_delayed(gpointer user_data)1271 static void _view_map_changed_callback_delayed(gpointer user_data)
1272 {
1273 dt_view_t *self = (dt_view_t *)user_data;
1274 dt_map_t *lib = (dt_map_t *)self->data;
1275 gboolean all_good = TRUE;
1276 gboolean needs_redraw = FALSE;
1277 gboolean prefs_changed = _view_map_prefs_changed(lib);
1278
1279 if(!lib->timeout_event_source)
1280 {
1281 /* remove the old images */
1282 if(lib->images)
1283 {
1284 // we can't use osm_gps_map_image_remove_all() because we want to keep the marker
1285 for(GSList *iter = lib->images; iter; iter = g_slist_next(iter))
1286 {
1287 dt_map_image_t *image = (dt_map_image_t *)iter->data;
1288 if(image->image)
1289 osm_gps_map_image_remove(lib->map, image->image);
1290 }
1291 g_slist_free_full(lib->images, g_free);
1292 lib->images = NULL;
1293 }
1294 }
1295
1296 if(!lib->timeout_event_source && lib->thumbnail != DT_MAP_THUMB_NONE)
1297 {
1298 // not a redraw
1299 // rebuild main_query if needed
1300 if(prefs_changed) _view_map_build_main_query(lib);
1301
1302 /* get bounding box coords */
1303 _view_map_get_bounding_box(lib, &lib->bbox);
1304
1305 /* get map view state and store */
1306 int zoom;
1307 float center_lat, center_lon;
1308 g_object_get(G_OBJECT(lib->map), "zoom", &zoom, "latitude", ¢er_lat, "longitude", ¢er_lon, NULL);
1309 dt_conf_set_float("plugins/map/longitude", center_lon);
1310 dt_conf_set_float("plugins/map/latitude", center_lat);
1311 dt_conf_set_int("plugins/map/zoom", zoom);
1312
1313 /* let's reset and reuse the main_query statement */
1314 DT_DEBUG_SQLITE3_CLEAR_BINDINGS(lib->main_query);
1315 DT_DEBUG_SQLITE3_RESET(lib->main_query);
1316
1317 /* bind bounding box coords for the main query */
1318 DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->main_query, 1, lib->bbox.lon1);
1319 DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->main_query, 2, lib->bbox.lon2);
1320 DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->main_query, 3, lib->bbox.lat1);
1321 DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->main_query, 4, lib->bbox.lat2);
1322
1323 // count the images
1324 int img_count = 0;
1325 while(sqlite3_step(lib->main_query) == SQLITE_ROW)
1326 {
1327 img_count++;
1328 }
1329
1330 if(lib->points)
1331 g_free(lib->points);
1332 lib->points = NULL;
1333 lib->nb_points = img_count;
1334 if(img_count > 0)
1335 lib->points = (dt_geo_position_t *)calloc(img_count, sizeof(dt_geo_position_t));
1336 dt_geo_position_t *p = lib->points;
1337 if(p)
1338 {
1339 DT_DEBUG_SQLITE3_RESET(lib->main_query);
1340 /* make the image list */
1341 int i = 0;
1342 while(sqlite3_step(lib->main_query) == SQLITE_ROW && all_good && i < img_count)
1343 {
1344 p[i].imgid = sqlite3_column_int(lib->main_query, 0);
1345 p[i].x = sqlite3_column_double(lib->main_query, 1) * M_PI / 180;
1346 p[i].y = sqlite3_column_double(lib->main_query, 2) * M_PI / 180;
1347 p[i].cluster_id = UNCLASSIFIED;
1348 i++;
1349 }
1350
1351 const float epsilon_factor = dt_conf_get_int("plugins/map/epsilon_factor");
1352 const int min_images = dt_conf_get_int("plugins/map/min_images_per_group");
1353 // zoom varies from 0 (156412 m/pixel) to 20 (0.149 m/pixel)
1354 // https://wiki.openstreetmap.org/wiki/Zoom_levels
1355 // each time zoom increases by 1 the size is divided by 2
1356 // epsilon factor = 100 => epsilon covers more or less a thumbnail surface
1357 #define R 6371 // earth radius (km)
1358 double epsilon = thumb_size * (((unsigned int)(156412000 >> zoom))
1359 * epsilon_factor * 0.01 * 0.000001 / R);
1360
1361 dt_times_t start;
1362 dt_get_times(&start);
1363 _dbscan(p, img_count, epsilon, min_images);
1364 dt_show_times(&start, "[map] dbscan calculation");
1365
1366 // set the clusters
1367 const GList *sel_imgs = dt_view_get_images_to_act_on(FALSE, FALSE, FALSE);
1368 int group = -1;
1369 for(i = 0; i< img_count; i++)
1370 {
1371 if(p[i].cluster_id == NOISE)
1372 {
1373 dt_map_image_t *entry = (dt_map_image_t *)calloc(1, sizeof(dt_map_image_t));
1374 entry->imgid = p[i].imgid;
1375 entry->group = p[i].cluster_id;
1376 entry->group_count = 1;
1377 entry->longitude = p[i].x * 180 / M_PI;
1378 entry->latitude = p[i].y * 180 / M_PI;
1379 entry->group_same_loc = TRUE;
1380 if(sel_imgs)
1381 entry->selected_in_group = g_list_find((GList *)sel_imgs,
1382 GINT_TO_POINTER(entry->imgid))
1383 ? TRUE : FALSE;
1384 lib->images = g_slist_prepend(lib->images, entry);
1385 }
1386 else if(p[i].cluster_id > group)
1387 {
1388 group = p[i].cluster_id;
1389 dt_map_image_t *entry = (dt_map_image_t *)calloc(1, sizeof(dt_map_image_t));
1390 entry->imgid = p[i].imgid;
1391 entry->group = p[i].cluster_id;
1392 entry->group_same_loc = TRUE;
1393 entry->selected_in_group = (sel_imgs && g_list_find((GList *)sel_imgs,
1394 GINT_TO_POINTER(p[i].imgid)))
1395 ? TRUE : FALSE;
1396 const double lon = p[i].x, lat = p[i].y;
1397 for(int j = 0; j < img_count; j++)
1398 {
1399 if(p[j].cluster_id == group)
1400 {
1401 entry->group_count++;
1402 entry->longitude += p[j].x;
1403 entry->latitude += p[j].y;
1404 if(entry->group_same_loc && (p[j].x != lon || p[j].y != lat))
1405 {
1406 entry->group_same_loc = FALSE;
1407 }
1408 if(sel_imgs && !entry->selected_in_group)
1409 {
1410 if(g_list_find((GList *)sel_imgs, GINT_TO_POINTER(p[j].imgid)))
1411 entry->selected_in_group = TRUE;
1412 }
1413 }
1414 }
1415 entry->latitude = entry->latitude * 180 / M_PI / entry->group_count;
1416 entry->longitude = entry->longitude * 180 / M_PI / entry->group_count;
1417 lib->images = g_slist_prepend(lib->images, entry);
1418 }
1419 }
1420 }
1421
1422 needs_redraw = _view_map_draw_images(self);
1423 _view_map_draw_main_location(lib, &lib->loc.main);
1424 _view_map_draw_other_locations(lib, &lib->bbox);
1425 }
1426
1427 // not exactly thread safe, but should be good enough for updating the display
1428 if(needs_redraw && lib->timeout_event_source == 0)
1429 {
1430 lib->timeout_event_source = g_timeout_add(100, _view_map_draw_images, self); // try again later on
1431 }
1432 }
1433
_view_map_changed_callback_wait(gpointer user_data)1434 static gboolean _view_map_changed_callback_wait(gpointer user_data)
1435 {
1436 dt_view_t *self = (dt_view_t *)user_data;
1437 dt_map_t *lib = (dt_map_t *)self->data;
1438 if(lib->time_out)
1439 {
1440 lib->time_out--;
1441 if(!lib->time_out)
1442 {
1443 _view_map_changed_callback_delayed(self);
1444 return FALSE;
1445 }
1446 }
1447 return TRUE;
1448 }
1449
1450 static int first_times = 3;
1451
_view_map_changed_callback(OsmGpsMap * map,dt_view_t * self)1452 static void _view_map_changed_callback(OsmGpsMap *map, dt_view_t *self)
1453 {
1454 dt_map_t *lib = (dt_map_t *)self->data;
1455 // ugly but it avoids to display not well controlled maps at init time
1456 if(first_times)
1457 {
1458 first_times--;;
1459 return;
1460 }
1461
1462 // "changed" event can be high frequency. As calculation is heavy we don't to repeat it.
1463 if(!lib->time_out)
1464 {
1465 g_timeout_add(100, _view_map_changed_callback_wait, self);
1466 }
1467 lib->time_out = 2;
1468
1469 // activate this callback late in the process as we need the filmstrip proxy to be setup. This is not the
1470 // case in the initialization phase.
1471 if(!lib->drop_filmstrip_activated)
1472 {
1473 g_signal_connect(dt_ui_thumbtable(darktable.gui->ui)->widget, "drag-data-received",
1474 G_CALLBACK(_view_map_dnd_remove_callback), self);
1475 lib->drop_filmstrip_activated = TRUE;
1476 }
1477 }
1478
_view_map_get_entry_at_pos(dt_view_t * self,const double x,const double y)1479 static dt_map_image_t *_view_map_get_entry_at_pos(dt_view_t *self, const double x,
1480 const double y)
1481 {
1482 dt_map_t *lib = (dt_map_t *)self->data;
1483
1484 for(const GSList *iter = lib->images; iter; iter = g_slist_next(iter))
1485 {
1486 dt_map_image_t *entry = (dt_map_image_t *)iter->data;
1487 OsmGpsMapImage *image = entry->image;
1488 if(image)
1489 {
1490 OsmGpsMapPoint *pt = (OsmGpsMapPoint *)osm_gps_map_image_get_point(image);
1491 gint img_x = 0, img_y = 0;
1492 osm_gps_map_convert_geographic_to_screen(lib->map, pt, &img_x, &img_y);
1493 img_y -= DT_PIXEL_APPLY_DPI(image_pin_size);
1494 if(x >= img_x && x <= img_x + entry->width && y <= img_y && y >= img_y - entry->height)
1495 {
1496 return entry;
1497 }
1498 }
1499 }
1500 return NULL;
1501 }
1502
_view_map_get_imgs_at_pos(dt_view_t * self,const float x,const float y,int * offset_x,int * offset_y,const gboolean first_on)1503 static GList *_view_map_get_imgs_at_pos(dt_view_t *self, const float x, const float y,
1504 int *offset_x, int *offset_y, const gboolean first_on)
1505 {
1506 dt_map_t *lib = (dt_map_t *)self->data;
1507 GList *imgs = NULL;
1508 int imgid = -1;
1509 dt_map_image_t *entry = NULL;
1510
1511 for(const GSList *iter = lib->images; iter; iter = g_slist_next(iter))
1512 {
1513 entry = (dt_map_image_t *)iter->data;
1514 OsmGpsMapImage *image = entry->image;
1515 if(image)
1516 {
1517 OsmGpsMapPoint *pt = (OsmGpsMapPoint *)osm_gps_map_image_get_point(image);
1518 gint img_x = 0, img_y = 0;
1519 osm_gps_map_convert_geographic_to_screen(lib->map, pt, &img_x, &img_y);
1520 img_y -= DT_PIXEL_APPLY_DPI(image_pin_size);
1521 if(x >= img_x && x <= img_x + entry->width && y <= img_y && y >= img_y - entry->height)
1522 {
1523 imgid = entry->imgid;
1524 *offset_x = (int)x - img_x;
1525 *offset_y = (int)y - img_y - DT_PIXEL_APPLY_DPI(image_pin_size);
1526 break;
1527 }
1528 }
1529 }
1530
1531 if(imgid != -1 && !first_on && entry->group_count > 1 && lib->points)
1532 {
1533 dt_geo_position_t *p = lib->points;
1534 int count = 1;
1535 for(int i = 0; i < lib->nb_points; i++)
1536 {
1537 if(p[i].cluster_id == entry->group && p[i].imgid != imgid)
1538 {
1539 imgs = g_list_prepend(imgs, GINT_TO_POINTER(p[i].imgid));
1540 count++;
1541 if(count >= entry->group_count)
1542 {
1543 break;
1544 }
1545 }
1546 }
1547 }
1548 if(imgid != -1)
1549 // it's necessary to have the visible image as the first one of the list
1550 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgid));
1551 return imgs;
1552 }
1553
_find_image_in_images(gconstpointer a,gconstpointer b)1554 gint _find_image_in_images(gconstpointer a, gconstpointer b)
1555 {
1556 dt_map_image_t *entry = (dt_map_image_t *)a;
1557 return entry->imgid == GPOINTER_TO_INT(b) ? 0 : 1;
1558 }
1559
_display_next_image(dt_view_t * self,dt_map_image_t * entry,const gboolean next)1560 static gboolean _display_next_image(dt_view_t *self, dt_map_image_t *entry, const gboolean next)
1561 {
1562 dt_map_t *lib = (dt_map_t *)self->data;
1563 if(!entry) return FALSE;
1564
1565 if(entry->group_count == 1)
1566 {
1567 if(entry->image)
1568 {
1569 osm_gps_map_image_remove(lib->map, entry->image);
1570 entry->image = NULL;
1571 }
1572 _view_map_draw_image(entry, TRUE, DT_MAP_THUMB_THUMB, self);
1573 dt_control_set_mouse_over_id(entry->imgid);
1574 return TRUE;
1575 }
1576
1577 dt_geo_position_t *p = lib->points;
1578 int index = -1;
1579 for(int i = 0; i < lib->nb_points; i++)
1580 {
1581 if(p[i].imgid == GPOINTER_TO_INT(entry->imgid))
1582 {
1583 if(next)
1584 {
1585 for(int j = i + 1; j < lib->nb_points; j++)
1586 {
1587 if(p[j].cluster_id == entry->group)
1588 {
1589 index = j;
1590 break;
1591 }
1592 }
1593 if(index == -1)
1594 {
1595 for(int j = 0; j < i; j++)
1596 {
1597 if(p[j].cluster_id == entry->group)
1598 {
1599 index = j;
1600 break;
1601 }
1602 }
1603 }
1604 }
1605 else
1606 {
1607 for(int j = i - 1; j >= 0; j--)
1608 {
1609 if(p[j].cluster_id == entry->group)
1610 {
1611 index = j;
1612 break;
1613 }
1614 }
1615 if(index == -1)
1616 {
1617 for(int j = lib->nb_points - 1; j > i; j--)
1618 {
1619 if(p[j].cluster_id == entry->group)
1620 {
1621 index = j;
1622 break;
1623 }
1624 }
1625 }
1626 }
1627 break;
1628 }
1629 }
1630 if(index == -1) return FALSE;
1631 entry->imgid = p[index].imgid;
1632 if(entry->image)
1633 {
1634 osm_gps_map_image_remove(lib->map, entry->image);
1635 entry->image = NULL;
1636 }
1637 _view_map_draw_image(entry, TRUE, DT_MAP_THUMB_THUMB, self);
1638 dt_control_set_mouse_over_id(entry->imgid);
1639 return TRUE;
1640 }
1641
_view_map_drag_set_icon(const dt_view_t * self,GdkDragContext * context,const int imgid,const int count)1642 static void _view_map_drag_set_icon(const dt_view_t *self, GdkDragContext *context,
1643 const int imgid, const int count)
1644 {
1645 dt_map_t *lib = (dt_map_t *)self->data;
1646 int height;
1647 GdkPixbuf *thumb = _draw_image(imgid, NULL, &height, count, TRUE, thumb_frame_sel_color,
1648 TRUE, lib->thumbnail, (dt_view_t *)self);
1649 if(thumb)
1650 {
1651 GtkWidget *image = gtk_image_new_from_pixbuf(thumb);
1652 gtk_widget_set_name((image), "map_drag_icon");
1653 gtk_widget_show(image);
1654 gtk_drag_set_icon_widget(context, image, lib->start_drag_offset_x,
1655 DT_PIXEL_APPLY_DPI(height + image_pin_size + 2 * thumb_border) + lib->start_drag_offset_y);
1656 g_object_unref(thumb);
1657 }
1658 }
1659
_view_map_drag_motion_callback(GtkWidget * widget,GdkDragContext * dc,gint x,gint y,guint time,dt_view_t * self)1660 static gboolean _view_map_drag_motion_callback(GtkWidget *widget, GdkDragContext *dc,
1661 gint x, gint y, guint time, dt_view_t *self)
1662 {
1663 dt_map_t *lib = (dt_map_t *)self->data;
1664 OsmGpsMapPoint *p = osm_gps_map_point_new_degrees(0.0, 0.0);
1665 osm_gps_map_convert_screen_to_geographic(lib->map, x, y, p);
1666 float lat, lon;
1667 osm_gps_map_point_get_degrees(p, &lat, &lon);
1668 osm_gps_map_point_free(p);
1669 _toast_log_lat_lon(lat, lon);
1670 return FALSE;
1671 }
1672
_view_map_motion_notify_callback(GtkWidget * widget,GdkEventMotion * e,dt_view_t * self)1673 static gboolean _view_map_motion_notify_callback(GtkWidget *widget, GdkEventMotion *e, dt_view_t *self)
1674 {
1675 dt_map_t *lib = (dt_map_t *)self->data;
1676 OsmGpsMapPoint *p = osm_gps_map_get_event_location(lib->map, (GdkEventButton *)e);
1677 float lat, lon;
1678 osm_gps_map_point_get_degrees(p, &lat, &lon);
1679 _toast_log_lat_lon(lat, lon);
1680
1681 if(lib->loc.drag && lib->loc.main.id > 0 &&
1682 (abs(lib->start_drag_x - (int)ceil(e->x_root)) +
1683 abs(lib->start_drag_y - (int)ceil(e->y_root))) > DT_PIXEL_APPLY_DPI(8))
1684 {
1685 lib->loc.drag = FALSE;
1686 osm_gps_map_image_remove(lib->map, lib->loc.main.location);
1687 GtkTargetList *targets = gtk_target_list_new(target_list_internal, n_targets_internal);
1688
1689 GdkDragContext *context =
1690 gtk_drag_begin_with_coordinates(GTK_WIDGET(lib->map), targets,
1691 GDK_ACTION_MOVE, 1,
1692 (GdkEvent *)e, -1, -1);
1693
1694 int width;
1695 int height;
1696 GdkPixbuf *location = _draw_location(lib, &width, &height, &lib->loc.main.data, TRUE);
1697 if(location)
1698 {
1699 GtkWidget *image = gtk_image_new_from_pixbuf(location);
1700 gtk_widget_set_name(image, "map_drag_icon");
1701 gtk_widget_show(image);
1702 gtk_drag_set_icon_widget(context, image,
1703 DT_PIXEL_APPLY_DPI(width),
1704 DT_PIXEL_APPLY_DPI(height));
1705 g_object_unref(location);
1706 }
1707 gtk_target_list_unref(targets);
1708 return TRUE;
1709 }
1710
1711 if(lib->start_drag && lib->selected_images &&
1712 (abs(lib->start_drag_x - (int)ceil(e->x_root)) +
1713 abs(lib->start_drag_y - (int)ceil(e->y_root))) > DT_PIXEL_APPLY_DPI(8))
1714 {
1715 const int nb = g_list_length(lib->selected_images);
1716 for(const GSList *iter = lib->images; iter; iter = g_slist_next(iter))
1717 {
1718 dt_map_image_t *entry = (dt_map_image_t *)iter->data;
1719 if(entry->image)
1720 {
1721 GList *sel_img = lib->selected_images;
1722 if(entry->imgid == GPOINTER_TO_INT(sel_img->data))
1723 {
1724 if(entry->group_count == nb)
1725 {
1726 osm_gps_map_image_remove(lib->map, entry->image);
1727 entry->image = NULL;
1728 }
1729 else
1730 _display_next_image(self, entry, TRUE);
1731 break;
1732 }
1733 }
1734 }
1735
1736 const int group_count = g_list_length(lib->selected_images);
1737
1738 lib->start_drag = FALSE;
1739 GtkTargetList *targets = gtk_target_list_new(target_list_all, n_targets_all);
1740 GdkDragContext *context = gtk_drag_begin_with_coordinates(GTK_WIDGET(lib->map), targets,
1741 GDK_ACTION_MOVE, 1,
1742 (GdkEvent *)e, -1, -1);
1743 _view_map_drag_set_icon(self, context, GPOINTER_TO_INT(lib->selected_images->data),
1744 group_count);
1745 gtk_target_list_unref(targets);
1746 return TRUE;
1747 }
1748
1749
1750 dt_map_image_t *entry = _view_map_get_entry_at_pos(self, e->x, e->y);
1751 if(entry)
1752 {
1753 // show image information if image is hovered
1754 dt_control_set_mouse_over_id(entry->imgid);
1755 // if count is displayed shows the thumbnail
1756 if(lib->thumbnail == DT_MAP_THUMB_COUNT)
1757 {
1758 _view_map_draw_image(entry, TRUE, DT_MAP_THUMB_THUMB, self);
1759 lib->last_hovered_entry = entry;
1760 return TRUE;
1761 }
1762 }
1763 else
1764 {
1765 dt_control_set_mouse_over_id(-1);
1766 if(lib->last_hovered_entry)
1767 {
1768 // _view_map_draw_image(lib->last_hovered_entry) would be faster but is not safe
1769 _view_map_draw_images(self);
1770 lib->last_hovered_entry = NULL;
1771 }
1772 }
1773 return FALSE;
1774 }
1775
_zoom_and_center(const gint x,const gint y,const int direction,dt_view_t * self)1776 static gboolean _zoom_and_center(const gint x, const gint y, const int direction, dt_view_t *self)
1777 {
1778 // try to keep the center of zoom at the mouse position
1779 dt_map_t *lib = (dt_map_t *)self->data;
1780 int zoom, max_zoom;
1781 g_object_get(G_OBJECT(lib->map), "zoom", &zoom, "max-zoom", &max_zoom, NULL);
1782
1783 OsmGpsMapPoint bb[2];
1784 osm_gps_map_get_bbox(lib->map, &bb[0], &bb[1]);
1785 gint x0, x1, y0, y1;
1786 osm_gps_map_convert_geographic_to_screen(lib->map, &bb[0], &x0, &y0);
1787 osm_gps_map_convert_geographic_to_screen(lib->map, &bb[1], &x1, &y1);
1788
1789 gint nx, ny;
1790 if(direction == GDK_SCROLL_UP && zoom < max_zoom)
1791 {
1792 zoom++;
1793 nx = (x0 + x1 + 2 * x) / 4;
1794 ny = (y0 + y1 + 2 * y) / 4;
1795 }
1796 else if(direction == GDK_SCROLL_DOWN && zoom > 0)
1797 {
1798 zoom--;
1799 nx = x0 + x1 - x;
1800 ny = y0 + y1 - y;
1801 }
1802 else return FALSE;
1803
1804 OsmGpsMapPoint *pt = osm_gps_map_point_new_degrees(0.0, 0.0);
1805 osm_gps_map_convert_screen_to_geographic(lib->map, nx, ny, pt);
1806 float nlat, nlon;
1807 osm_gps_map_point_get_degrees(pt, &nlat, &nlon);
1808 osm_gps_map_point_free(pt);
1809 osm_gps_map_set_center_and_zoom(lib->map, nlat, nlon, zoom);
1810
1811 return TRUE;
1812 }
1813
_view_map_scroll_event(GtkWidget * w,GdkEventScroll * event,dt_view_t * self)1814 static gboolean _view_map_scroll_event(GtkWidget *w, GdkEventScroll *event, dt_view_t *self)
1815 {
1816 dt_map_t *lib = (dt_map_t *)self->data;
1817 // check if the click was on image(s) or just some random position
1818 dt_map_image_t *entry = _view_map_get_entry_at_pos(self, event->x, event->y);
1819 if(entry)
1820 {
1821 if(_display_next_image(self, entry, event->direction == GDK_SCROLL_DOWN))
1822 return TRUE;
1823 }
1824
1825 OsmGpsMapPoint *p = osm_gps_map_get_event_location(lib->map, (GdkEventButton *)event);
1826 float lat, lon;
1827 osm_gps_map_point_get_degrees(p, &lat, &lon);
1828 if(lib->loc.main.id > 0)
1829 {
1830 if(dt_map_location_included(lon, lat, &lib->loc.main.data))
1831 {
1832 if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
1833 {
1834 if(event->direction == GDK_SCROLL_DOWN)
1835 lib->loc.main.data.delta1 *= 1.1;
1836 else
1837 lib->loc.main.data.delta1 /= 1.1;
1838 }
1839 else if(dt_modifier_is(event->state, GDK_CONTROL_MASK))
1840 {
1841 if(event->direction == GDK_SCROLL_DOWN)
1842 lib->loc.main.data.delta2 *= 1.1;
1843 else
1844 lib->loc.main.data.delta2 /= 1.1;
1845 }
1846 else
1847 {
1848 if(event->direction == GDK_SCROLL_DOWN)
1849 {
1850 lib->loc.main.data.delta1 *= 1.1;
1851 lib->loc.main.data.delta2 *= 1.1;
1852 }
1853 else
1854 {
1855 lib->loc.main.data.delta1 /= 1.1;
1856 lib->loc.main.data.delta2 /= 1.1;
1857 }
1858 }
1859 _view_map_draw_main_location(lib, &lib->loc.main);
1860 _view_map_update_location_geotag(self);
1861 _view_map_signal_change_wait(self, 5); // wait 5/10 sec after last scroll
1862 return TRUE;
1863 }
1864 else // scroll on the map. try to keep the map where it is
1865 {
1866 _toast_log_lat_lon(lat, lon);
1867 return _zoom_and_center(event->x, event->y, event->direction, self);
1868 }
1869 }
1870 else
1871 {
1872 _toast_log_lat_lon(lat, lon);
1873 return _zoom_and_center(event->x, event->y, event->direction, self);
1874 }
1875 return FALSE;
1876 }
1877
_view_map_button_press_callback(GtkWidget * w,GdkEventButton * e,dt_view_t * self)1878 static gboolean _view_map_button_press_callback(GtkWidget *w, GdkEventButton *e, dt_view_t *self)
1879 {
1880 dt_map_t *lib = (dt_map_t *)self->data;
1881 if(lib->selected_images)
1882 {
1883 g_list_free(lib->selected_images);
1884 lib->selected_images = NULL;
1885 }
1886 if(e->button == 1)
1887 {
1888 // check if the click was in a location form - crtl gives priority to images
1889 if(lib->loc.main.id > 0 && (lib->loc.main.data.shape != MAP_LOCATION_SHAPE_POLYGONS)
1890 && !dt_modifier_is(e->state, GDK_CONTROL_MASK))
1891 {
1892
1893 OsmGpsMapPoint *p = osm_gps_map_get_event_location(lib->map, e);
1894 float lat, lon;
1895 osm_gps_map_point_get_degrees(p, &lat, &lon);
1896 if(dt_map_location_included(lon, lat, &lib->loc.main.data))
1897 {
1898 if(!dt_modifier_is(e->state, GDK_SHIFT_MASK))
1899 {
1900 lib->start_drag_x = ceil(e->x_root);
1901 lib->start_drag_y = ceil(e->y_root);
1902 lib->loc.drag = TRUE;
1903 return TRUE;
1904 }
1905 }
1906 }
1907 // check if another location is clicked - ctrl gives priority to images
1908 if (!dt_modifier_is(e->state, GDK_CONTROL_MASK) &&
1909 dt_conf_get_bool("plugins/map/showalllocations"))
1910 {
1911 OsmGpsMapPoint *p = osm_gps_map_get_event_location(lib->map, e);
1912 float lat, lon;
1913 osm_gps_map_point_get_degrees(p, &lat, &lon);
1914 for(GList *other = lib->loc.others; other; other = g_list_next(other))
1915 {
1916 dt_location_draw_t *d = (dt_location_draw_t *)other->data;
1917 if(dt_map_location_included(lon, lat, &d->data))
1918 {
1919 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_view_map_geotag_changed), self);
1920 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_view_map_collection_changed), self);
1921 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_GEOTAG_CHANGED, NULL, d->id);
1922 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_view_map_collection_changed), self);
1923 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_view_map_geotag_changed), self);
1924 return TRUE;
1925 }
1926 }
1927 }
1928 // check if the click was on image(s) or just some random position
1929 lib->selected_images = _view_map_get_imgs_at_pos(self, e->x, e->y,
1930 &lib->start_drag_offset_x, &lib->start_drag_offset_y,
1931 !dt_modifier_is(e->state, GDK_SHIFT_MASK));
1932 if(e->type == GDK_BUTTON_PRESS)
1933 {
1934 if(lib->selected_images)
1935 {
1936 lib->start_drag_x = ceil(e->x_root);
1937 lib->start_drag_y = ceil(e->y_root);
1938 lib->start_drag = TRUE;
1939 return TRUE;
1940 }
1941 else
1942 {
1943 return FALSE;
1944 }
1945 }
1946 if(e->type == GDK_2BUTTON_PRESS)
1947 {
1948 if(lib->selected_images)
1949 {
1950 // open the image in darkroom
1951 dt_control_set_mouse_over_id(GPOINTER_TO_INT(lib->selected_images->data));
1952 dt_ctl_switch_mode_to("darkroom");
1953 return TRUE;
1954 }
1955 else
1956 {
1957 // zoom into that position
1958 float longitude, latitude;
1959 OsmGpsMapPoint *pt = osm_gps_map_point_new_degrees(0.0, 0.0);
1960 osm_gps_map_convert_screen_to_geographic(lib->map, e->x, e->y, pt);
1961 osm_gps_map_point_get_degrees(pt, &latitude, &longitude);
1962 osm_gps_map_point_free(pt);
1963 int zoom, max_zoom;
1964 g_object_get(G_OBJECT(lib->map), "zoom", &zoom, "max-zoom", &max_zoom, NULL);
1965 zoom = MIN(zoom + 1, max_zoom);
1966 _view_map_center_on_location(self, longitude, latitude, zoom);
1967 }
1968 return TRUE;
1969 }
1970 }
1971 return FALSE;
1972 }
1973
_view_map_button_release_callback(GtkWidget * w,GdkEventButton * e,dt_view_t * self)1974 static gboolean _view_map_button_release_callback(GtkWidget *w, GdkEventButton *e, dt_view_t *self)
1975 {
1976 dt_map_t *lib = (dt_map_t *)self->data;
1977 lib->start_drag = FALSE;
1978 lib->start_drag_offset_x = 0;
1979 lib->start_drag_offset_y = 0;
1980 lib->loc.drag = FALSE;
1981 return FALSE;
1982 }
1983
_view_map_display_selected(gpointer user_data)1984 static gboolean _view_map_display_selected(gpointer user_data)
1985 {
1986 dt_view_t *self = (dt_view_t *)user_data;
1987 dt_map_t *lib = (dt_map_t *)self->data;
1988 gboolean done = FALSE;
1989 // selected images ?
1990 done = _view_map_center_on_image_list(self, "main.selected_images");
1991
1992 // collection ?
1993 if(!done)
1994 {
1995 done = _view_map_center_on_image_list(self, "memory.collected_images");
1996 }
1997
1998 // last map view
1999 if(!done)
2000 {
2001 /* if nothing to show restore last zoom,location in map */
2002 float lon = dt_conf_get_float("plugins/map/longitude");
2003 lon = CLAMP(lon, -180, 180);
2004 float lat = dt_conf_get_float("plugins/map/latitude");
2005 lat = CLAMP(lat, -90, 90);
2006 const int zoom = dt_conf_get_int("plugins/map/zoom");
2007 osm_gps_map_set_center_and_zoom(lib->map, lat, lon, zoom);
2008 }
2009 return FALSE; // don't call again
2010 }
2011
enter(dt_view_t * self)2012 void enter(dt_view_t *self)
2013 {
2014 dt_map_t *lib = (dt_map_t *)self->data;
2015
2016 lib->selected_images = NULL;
2017 lib->start_drag = FALSE;
2018 lib->start_drag_offset_x = 0;
2019 lib->start_drag_offset_y = 0;
2020 lib->loc.drag = FALSE;
2021 lib->entering = TRUE;
2022
2023 /* set the correct map source */
2024 _view_map_set_map_source_g_object(self, lib->map_source);
2025
2026 /* add map to center widget */
2027 gtk_overlay_add_overlay(GTK_OVERLAY(dt_ui_center_base(darktable.gui->ui)), GTK_WIDGET(lib->map));
2028
2029 // ensure the log msg widget stay on top
2030 gtk_overlay_reorder_overlay(GTK_OVERLAY(dt_ui_center_base(darktable.gui->ui)),
2031 gtk_widget_get_parent(dt_ui_log_msg(darktable.gui->ui)), -1);
2032 gtk_overlay_reorder_overlay(GTK_OVERLAY(dt_ui_center_base(darktable.gui->ui)),
2033 gtk_widget_get_parent(dt_ui_toast_msg(darktable.gui->ui)), -1);
2034
2035 gtk_widget_show_all(GTK_WIDGET(lib->map));
2036
2037 /* setup proxy functions */
2038 darktable.view_manager->proxy.map.center_on_location = _view_map_center_on_location;
2039 darktable.view_manager->proxy.map.center_on_bbox = _view_map_center_on_bbox;
2040 darktable.view_manager->proxy.map.show_osd = _view_map_show_osd;
2041 darktable.view_manager->proxy.map.set_map_source = _view_map_set_map_source;
2042 darktable.view_manager->proxy.map.add_marker = _view_map_add_marker;
2043 darktable.view_manager->proxy.map.remove_marker = _view_map_remove_marker;
2044 darktable.view_manager->proxy.map.add_location = _view_map_add_location;
2045 darktable.view_manager->proxy.map.location_action = _view_map_location_action;
2046 darktable.view_manager->proxy.map.drag_set_icon = _view_map_drag_set_icon;
2047 darktable.view_manager->proxy.map.redraw = _view_map_redraw;
2048 darktable.view_manager->proxy.map.display_selected = _view_map_display_selected;
2049
2050 darktable.view_manager->proxy.map.view = self;
2051
2052 /* connect signal for filmstrip image activate */
2053 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE,
2054 G_CALLBACK(_view_map_filmstrip_activate_callback), self);
2055
2056 g_timeout_add(250, _view_map_display_selected, self);
2057 }
2058
leave(dt_view_t * self)2059 void leave(dt_view_t *self)
2060 {
2061 /* disable the map source again. no need to risk network traffic while we are not in map mode. */
2062 _view_map_set_map_source_g_object(self, OSM_GPS_MAP_SOURCE_NULL);
2063
2064 /* disconnect from filmstrip image activate */
2065 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_view_map_filmstrip_activate_callback),
2066 (gpointer)self);
2067 g_signal_handlers_disconnect_by_func(dt_ui_thumbtable(darktable.gui->ui)->widget,
2068 G_CALLBACK(_view_map_dnd_remove_callback), self);
2069 dt_map_t *lib = (dt_map_t *)self->data;
2070 lib->drop_filmstrip_activated = FALSE;
2071
2072 if(lib->selected_images)
2073 {
2074 g_list_free(lib->selected_images);
2075 lib->selected_images = NULL;
2076 }
2077 gtk_widget_hide(GTK_WIDGET(lib->map));
2078 gtk_container_remove(GTK_CONTAINER(dt_ui_center_base(darktable.gui->ui)), GTK_WIDGET(lib->map));
2079
2080 /* reset proxy */
2081 darktable.view_manager->proxy.map.view = NULL;
2082 }
2083
init_key_accels(dt_view_t * self)2084 void init_key_accels(dt_view_t *self)
2085 {
2086 dt_accel_register_view(self, NC_("accel", "undo"), GDK_KEY_z, GDK_CONTROL_MASK);
2087 dt_accel_register_view(self, NC_("accel", "redo"), GDK_KEY_y, GDK_CONTROL_MASK);
2088 }
2089
_view_map_undo_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)2090 static gboolean _view_map_undo_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2091 GdkModifierType modifier, gpointer data)
2092 {
2093 dt_view_t *self = (dt_view_t *)data;
2094 dt_map_t *lib = (dt_map_t *)self->data;
2095
2096 // let current map view unchanged (avoid to center the map on collection)
2097 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_view_map_geotag_changed), data);
2098 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_view_map_collection_changed), data);
2099 dt_undo_do_undo(darktable.undo, DT_UNDO_MAP);
2100 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_view_map_collection_changed), data);
2101 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_view_map_geotag_changed), data);
2102 g_signal_emit_by_name(lib->map, "changed");
2103
2104 return TRUE;
2105 }
2106
_view_map_redo_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)2107 static gboolean _view_map_redo_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
2108 GdkModifierType modifier, gpointer data)
2109 {
2110 dt_view_t *self = (dt_view_t *)data;
2111 dt_map_t *lib = (dt_map_t *)self->data;
2112
2113 // let current map view unchanged (avoid to center the map on collection)
2114 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_view_map_geotag_changed), data);
2115 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_view_map_collection_changed), data);
2116 dt_undo_do_redo(darktable.undo, DT_UNDO_MAP);
2117 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_view_map_collection_changed), data);
2118 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_view_map_geotag_changed), data);
2119 g_signal_emit_by_name(lib->map, "changed");
2120
2121 return TRUE;
2122 }
2123
connect_key_accels(dt_view_t * self)2124 void connect_key_accels(dt_view_t *self)
2125 {
2126 // undo/redo
2127 GClosure *closure = g_cclosure_new(G_CALLBACK(_view_map_undo_callback), (gpointer)self, NULL);
2128 dt_accel_connect_view(self, "undo", closure);
2129 closure = g_cclosure_new(G_CALLBACK(_view_map_redo_callback), (gpointer)self, NULL);
2130 dt_accel_connect_view(self, "redo", closure);
2131 }
2132
2133
_view_map_center_on_location(const dt_view_t * view,gdouble lon,gdouble lat,gdouble zoom)2134 static void _view_map_center_on_location(const dt_view_t *view, gdouble lon, gdouble lat, gdouble zoom)
2135 {
2136 dt_map_t *lib = (dt_map_t *)view->data;
2137 osm_gps_map_set_center_and_zoom(lib->map, lat, lon, zoom);
2138 }
2139
_view_map_center_on_bbox(const dt_view_t * view,gdouble lon1,gdouble lat1,gdouble lon2,gdouble lat2)2140 static void _view_map_center_on_bbox(const dt_view_t *view, gdouble lon1, gdouble lat1, gdouble lon2, gdouble lat2)
2141 {
2142 dt_map_t *lib = (dt_map_t *)view->data;
2143 osm_gps_map_zoom_fit_bbox(lib->map, lat1, lat2, lon1, lon2);
2144 }
2145
_view_map_show_osd(const dt_view_t * view)2146 static void _view_map_show_osd(const dt_view_t *view)
2147 {
2148 dt_map_t *lib = (dt_map_t *)view->data;
2149 const gboolean enabled = dt_conf_get_bool("plugins/map/show_map_osd");
2150 if(enabled)
2151 osm_gps_map_layer_add(OSM_GPS_MAP(lib->map), lib->osd);
2152 else
2153 osm_gps_map_layer_remove(OSM_GPS_MAP(lib->map), lib->osd);
2154
2155 g_signal_emit_by_name(lib->map, "changed");
2156 }
2157
_view_map_set_map_source_g_object(const dt_view_t * view,OsmGpsMapSource_t map_source)2158 static void _view_map_set_map_source_g_object(const dt_view_t *view, OsmGpsMapSource_t map_source)
2159 {
2160 dt_map_t *lib = (dt_map_t *)view->data;
2161
2162 GValue value = {
2163 0,
2164 };
2165 g_value_init(&value, G_TYPE_INT);
2166 g_value_set_int(&value, map_source);
2167 g_object_set_property(G_OBJECT(lib->map), "map-source", &value);
2168 g_value_unset(&value);
2169 }
2170
_view_map_set_map_source(const dt_view_t * view,OsmGpsMapSource_t map_source)2171 static void _view_map_set_map_source(const dt_view_t *view, OsmGpsMapSource_t map_source)
2172 {
2173 dt_map_t *lib = (dt_map_t *)view->data;
2174
2175 if(map_source == lib->map_source) return;
2176
2177 lib->map_source = map_source;
2178 dt_conf_set_string("plugins/map/map_source", osm_gps_map_source_get_friendly_name(map_source));
2179 _view_map_set_map_source_g_object(view, map_source);
2180 }
2181
_view_map_add_pin(const dt_view_t * view,GList * points)2182 static OsmGpsMapImage *_view_map_add_pin(const dt_view_t *view, GList *points)
2183 {
2184 dt_map_t *lib = (dt_map_t *)view->data;
2185 dt_geo_map_display_point_t *p = (dt_geo_map_display_point_t *)points->data;
2186 return osm_gps_map_image_add_with_alignment(lib->map, p->lat, p->lon, lib->place_pin, 0.5, 1);
2187 }
2188
_view_map_remove_pin(const dt_view_t * view,OsmGpsMapImage * pin)2189 static gboolean _view_map_remove_pin(const dt_view_t *view, OsmGpsMapImage *pin)
2190 {
2191 dt_map_t *lib = (dt_map_t *)view->data;
2192 return osm_gps_map_image_remove(lib->map, pin);
2193 }
2194
_track_add_point(OsmGpsMapTrack * track,OsmGpsMapPoint * point,OsmGpsMapPoint * prev_point)2195 static void _track_add_point(OsmGpsMapTrack *track, OsmGpsMapPoint *point, OsmGpsMapPoint *prev_point)
2196 {
2197 float lat, lon, prev_lat, prev_lon;
2198 osm_gps_map_point_get_degrees(point, &lat, &lon);
2199 osm_gps_map_point_get_degrees(prev_point, &prev_lat, &prev_lon);
2200 double d, delta;
2201 gboolean short_distance = TRUE;
2202 if(fabs(lat - prev_lat) > DT_MINIMUM_ANGULAR_DELTA_FOR_GEODESIC
2203 || fabs(lon - prev_lon) > DT_MINIMUM_ANGULAR_DELTA_FOR_GEODESIC)
2204 {
2205 short_distance = FALSE;
2206 dt_gpx_geodesic_distance(lat, lon,
2207 prev_lat, prev_lon,
2208 &d, &delta);
2209 }
2210
2211 if(short_distance || d < DT_MINIMUM_DISTANCE_FOR_GEODESIC)
2212 {
2213 osm_gps_map_track_add_point(track, point);
2214 }
2215 else
2216 {
2217 /* the line must be splitted in order to seek the geodesic line */
2218 OsmGpsMapPoint *ith_point;
2219 double f, ith_lat, ith_lon;
2220 const int n_segments = ceil(d / DT_MINIMUM_DISTANCE_FOR_GEODESIC);
2221 gboolean first_time = TRUE;
2222 for(int i = 1; i <= n_segments; i ++)
2223 {
2224 f = (double)i / n_segments;
2225 dt_gpx_geodesic_intermediate_point(prev_lat, prev_lon,
2226 lat, lon,
2227 delta,
2228 first_time,
2229 f,
2230 &ith_lat, &ith_lon);
2231
2232 ith_point = osm_gps_map_point_new_degrees (ith_lat, ith_lon);
2233 osm_gps_map_track_add_point(track, ith_point);
2234 osm_gps_map_point_free(ith_point);
2235 first_time = FALSE;
2236 }
2237 }
2238 }
2239
2240 #ifdef HAVE_OSMGPSMAP_110_OR_NEWER
_view_map_add_polygon(const dt_view_t * view,GList * points)2241 static OsmGpsMapPolygon *_view_map_add_polygon(const dt_view_t *view, GList *points)
2242 {
2243 dt_map_t *lib = (dt_map_t *)view->data;
2244 OsmGpsMapPolygon *poly = osm_gps_map_polygon_new();
2245 OsmGpsMapTrack* track = osm_gps_map_track_new();
2246
2247 // angles for 1 pixel;
2248 float dlat, dlon;
2249 _view_map_angles(lib, 1, (lib->bbox.lat1 + lib->bbox.lat2) * 0.5 ,
2250 (lib->bbox.lon1 + lib->bbox.lon2) * 0.5 , &dlat, &dlon);
2251
2252 float prev_lat = 0.0;
2253 float prev_lon = 0.0;
2254 for(GList *iter = points; iter; iter = g_list_next(iter))
2255 {
2256 dt_geo_map_display_point_t *p = (dt_geo_map_display_point_t *)iter->data;
2257 if((fabs(p->lat - prev_lat) > dlat) || (fabs(p->lon - prev_lon) > dlon))
2258 {
2259 OsmGpsMapPoint* point = osm_gps_map_point_new_degrees(p->lat, p->lon);
2260 osm_gps_map_track_add_point(track, point);
2261 prev_lat = p->lat;
2262 prev_lon = p->lon;
2263 }
2264 }
2265
2266 g_object_set(poly, "track", track, (gchar *)0);
2267 g_object_set(poly, "editable", FALSE, (gchar *)0);
2268 g_object_set(poly, "shaded", FALSE, (gchar *)0);
2269
2270 osm_gps_map_polygon_add(lib->map, poly);
2271
2272 return poly;
2273 }
2274
_view_map_add_polygon_location(dt_map_t * lib,dt_location_draw_t * ld)2275 static OsmGpsMapPolygon *_view_map_add_polygon_location(dt_map_t *lib, dt_location_draw_t *ld)
2276 {
2277 OsmGpsMapPolygon *poly = osm_gps_map_polygon_new();
2278 OsmGpsMapTrack* track = osm_gps_map_track_new();
2279 g_object_set(track, "line-width" , 2.0, "alpha", 0.9, (gchar *)0);
2280
2281 // angles for 1 pixel;
2282 float dlat, dlon;
2283 _view_map_angles(lib, 1, ld->data.lat, ld->data.lon, &dlat, &dlon);
2284 int zoom;
2285 g_object_get(G_OBJECT(lib->map), "zoom", &zoom, NULL);
2286 // zoom = 20 => mod = 21 ; zoom = 0 => mod = 1
2287 const int mod2 = zoom + 1;
2288
2289 // keep a bit of points around the bounding box
2290 dt_map_box_t bbox;
2291 _view_map_get_bounding_box(lib, &bbox);
2292 const float mlon = (bbox.lon2 - bbox.lon1) * 0.5;
2293 const float mlat = (bbox.lat1 - bbox.lat2) * 0.5;
2294 bbox.lon1 = CLAMP(bbox.lon1 - mlon, -180.0, 180);
2295 bbox.lon2 = CLAMP(bbox.lon2 + mlon, -180.0, 180);
2296 bbox.lat1 = CLAMP(bbox.lat1 + mlat, -90.0, 90);
2297 bbox.lat2 = CLAMP(bbox.lat2 - mlat, -90.0, 90);
2298
2299 int i = 0;
2300 int j = 0;
2301 float prev_lat = 0.0;
2302 float prev_lon = 0.0;
2303 for(GList *iter = ld->data.polygons; iter; iter = g_list_next(iter), i++)
2304 {
2305 dt_geo_map_display_point_t *p = (dt_geo_map_display_point_t *)iter->data;
2306 if(p->lat <= bbox.lat1 && p->lat >= bbox.lat2 &&
2307 p->lon >= bbox.lon1 && p->lon <= bbox.lon2)
2308 {
2309 if((fabs(p->lat - prev_lat) > dlat) || (fabs(p->lon - prev_lon) > dlon))
2310 {
2311 OsmGpsMapPoint* point = osm_gps_map_point_new_degrees(p->lat, p->lon);
2312 osm_gps_map_track_add_point(track, point);
2313 prev_lat = p->lat;
2314 prev_lon = p->lon;
2315 j++;
2316 }
2317 }
2318 else if(!(i % mod2))
2319 {
2320 OsmGpsMapPoint* point = osm_gps_map_point_new_degrees(p->lat, p->lon);
2321 osm_gps_map_track_add_point(track, point);
2322 j++;
2323 }
2324 }
2325
2326 g_object_set(poly, "track", track, (gchar *)0);
2327 g_object_set(poly, "editable", FALSE, (gchar *)0);
2328 g_object_set(poly, "shaded", FALSE, (gchar *)0);
2329
2330 osm_gps_map_polygon_add(lib->map, poly);
2331
2332 return poly;
2333 }
2334
_view_map_remove_polygon(const dt_view_t * view,OsmGpsMapPolygon * polygon)2335 static gboolean _view_map_remove_polygon(const dt_view_t *view, OsmGpsMapPolygon *polygon)
2336 {
2337 dt_map_t *lib = (dt_map_t *)view->data;
2338 return osm_gps_map_polygon_remove(lib->map, polygon);
2339 }
2340 #endif
2341
_view_map_add_track(const dt_view_t * view,GList * points)2342 static OsmGpsMapTrack *_view_map_add_track(const dt_view_t *view, GList *points)
2343 {
2344 dt_map_t *lib = (dt_map_t *)view->data;
2345
2346 OsmGpsMapTrack* track = osm_gps_map_track_new();
2347
2348 OsmGpsMapPoint* prev_point;
2349 gboolean first_point = TRUE;
2350 for(GList *iter = points; iter; iter = g_list_next(iter))
2351 {
2352 dt_geo_map_display_point_t *p = (dt_geo_map_display_point_t *)iter->data;
2353 OsmGpsMapPoint* point = osm_gps_map_point_new_degrees(p->lat, p->lon);
2354 if(first_point)
2355 {
2356 osm_gps_map_track_add_point(track, point);
2357 }
2358 else
2359 {
2360 _track_add_point(track, point, prev_point);
2361 osm_gps_map_point_free(prev_point);
2362 }
2363 prev_point = &(*point);
2364 if(!g_list_next(iter))
2365 osm_gps_map_point_free(prev_point);
2366 first_point = FALSE;
2367 }
2368
2369 g_object_set(track, "editable", FALSE, (gchar *)0);
2370
2371 osm_gps_map_track_add(lib->map, track);
2372
2373 return track;
2374 }
2375
_view_map_remove_track(const dt_view_t * view,OsmGpsMapTrack * track)2376 static gboolean _view_map_remove_track(const dt_view_t *view, OsmGpsMapTrack *track)
2377 {
2378 dt_map_t *lib = (dt_map_t *)view->data;
2379 return osm_gps_map_track_remove(lib->map, track);
2380 }
2381
_view_map_draw_single_image(const dt_view_t * view,GList * points)2382 static OsmGpsMapImage *_view_map_draw_single_image(const dt_view_t *view, GList *points)
2383 {
2384 dt_map_t *lib = (dt_map_t *)view->data;
2385 struct {uint32_t imgid; float latitude; float longitude; int count;} *p;
2386 p = points->data;
2387 GdkPixbuf *thumb = _draw_image(p->imgid, NULL, NULL, p->count, TRUE,
2388 thumb_frame_gpx_color, TRUE, DT_MAP_THUMB_THUMB, (dt_view_t *)view);
2389 OsmGpsMapImage *image = NULL;
2390 if(thumb)
2391 {
2392 image = osm_gps_map_image_add_with_alignment(lib->map, p->latitude, p->longitude, thumb, 0, 1);
2393 g_object_unref(thumb);
2394 }
2395 return image;
2396 }
2397
_view_map_add_marker(const dt_view_t * view,dt_geo_map_display_t type,GList * points)2398 static GObject *_view_map_add_marker(const dt_view_t *view, dt_geo_map_display_t type, GList *points)
2399 {
2400 switch(type)
2401 {
2402 case MAP_DISPLAY_POINT: return G_OBJECT(_view_map_add_pin(view, points));
2403 case MAP_DISPLAY_TRACK: return G_OBJECT(_view_map_add_track(view, points));
2404 #ifdef HAVE_OSMGPSMAP_110_OR_NEWER
2405 case MAP_DISPLAY_POLYGON: return G_OBJECT(_view_map_add_polygon(view, points));
2406 #endif
2407 case MAP_DISPLAY_THUMB: return G_OBJECT(_view_map_draw_single_image(view, points));
2408 default: return NULL;
2409 }
2410 }
2411
_view_map_remove_marker(const dt_view_t * view,dt_geo_map_display_t type,GObject * marker)2412 static gboolean _view_map_remove_marker(const dt_view_t *view, dt_geo_map_display_t type, GObject *marker)
2413 {
2414 dt_map_t *lib = (dt_map_t *)view->data;
2415 if(type == MAP_DISPLAY_NONE) return FALSE;
2416
2417 switch(type)
2418 {
2419 case MAP_DISPLAY_POINT: return _view_map_remove_pin(view, OSM_GPS_MAP_IMAGE(marker));
2420 case MAP_DISPLAY_TRACK: return _view_map_remove_track(view, OSM_GPS_MAP_TRACK(marker));
2421 #ifdef HAVE_OSMGPSMAP_110_OR_NEWER
2422 case MAP_DISPLAY_POLYGON: return _view_map_remove_polygon(view, OSM_GPS_MAP_POLYGON(marker));
2423 #endif
2424 case MAP_DISPLAY_THUMB: return osm_gps_map_image_remove(lib->map, OSM_GPS_MAP_IMAGE(marker));
2425 default: return FALSE;
2426 }
2427 }
2428
_view_map_add_location(const dt_view_t * view,dt_map_location_data_t * g,const guint locid)2429 static void _view_map_add_location(const dt_view_t *view, dt_map_location_data_t *g, const guint locid)
2430 {
2431 dt_map_t *lib = (dt_map_t *)view->data;
2432 dt_location_draw_t loc_main;
2433 loc_main.id = locid;
2434 if(g)
2435 {
2436 if(g->delta1 != 0.0 && g->delta2 != 0.0)
2437 {
2438 // existing location
2439 memcpy(&loc_main.data, g, sizeof(dt_map_location_data_t));
2440 const double max_lon = CLAMP(g->lon + g->delta1, -180, 180);
2441 const double min_lon = CLAMP(g->lon - g->delta1, -180, 180);
2442 const double max_lat = CLAMP(g->lat + g->delta2, -90, 90);
2443 const double min_lat = CLAMP(g->lat - g->delta2, -90, 90);
2444 if(max_lon > min_lon && max_lat > min_lat)
2445 {
2446 // only if new box not imcluded in the current map box
2447 if(g->lon < lib->bbox.lon1 || g->lon > lib->bbox.lon2 ||
2448 g->lat > lib->bbox.lat1 || g->lat < lib->bbox.lat2)
2449 _view_map_center_on_bbox(view, min_lon, max_lat, max_lon, min_lat);
2450 _view_map_draw_main_location(lib, &loc_main);
2451 }
2452 }
2453 else
2454 {
2455 // this is a new location
2456 loc_main.data.shape = g->shape;
2457 if(g->shape == MAP_LOCATION_SHAPE_POLYGONS)
2458 {
2459 dt_map_box_t bbox;
2460 loc_main.data.polygons = dt_map_location_convert_polygons(g->polygons, &bbox, &loc_main.data.plg_pts);
2461 _view_map_center_on_bbox(view, bbox.lon1, bbox.lat2, bbox.lon2, bbox.lat1);
2462 loc_main.data.lon = (bbox.lon1 + bbox.lon2) * 0.5;
2463 loc_main.data.lat = (bbox.lat1 + bbox.lat2) * 0.5;
2464 loc_main.data.ratio = 1;
2465 loc_main.data.delta1 = (bbox.lon2 - bbox.lon1) * 0.5;
2466 loc_main.data.delta2 = (bbox.lat1 - bbox.lat2) * 0.5;
2467 }
2468 else
2469 {
2470 // create the location on the center of the map
2471 float lon, lat;
2472 g_object_get(G_OBJECT(lib->map), "latitude", &lat, "longitude", &lon, NULL);
2473 loc_main.data.lon = lon, loc_main.data.lat = lat;
2474 // get a radius angle equivalent to thumb dimension to start with for delta1
2475 float dlat, dlon;
2476 _view_map_thumb_angles(lib, loc_main.data.lat, loc_main.data.lon, &dlat, &dlon);
2477 loc_main.data.ratio = _view_map_get_angles_ratio(lib, loc_main.data.lat, loc_main.data.lon);
2478 loc_main.data.delta1 = dlon;
2479 loc_main.data.delta2 = dlon / loc_main.data.ratio;
2480 }
2481 _view_map_draw_main_location(lib, &loc_main);
2482 _view_map_update_location_geotag((dt_view_t *)view);
2483 }
2484 }
2485 }
2486
_view_map_location_action(const dt_view_t * view,const int action)2487 static void _view_map_location_action(const dt_view_t *view, const int action)
2488 {
2489 dt_map_t *lib = (dt_map_t *)view->data;
2490 if(action == MAP_LOCATION_ACTION_REMOVE && lib->loc.main.id)
2491 {
2492 GList *other = _others_location(lib->loc.others, lib->loc.main.id);
2493 if(other)
2494 _view_map_delete_other_location(lib, other);
2495 // remove the main location
2496 _view_map_remove_location(lib, &lib->loc.main);
2497 lib->loc.main.id = 0;
2498 }
2499 _view_map_draw_other_locations(lib, &lib->bbox);
2500 }
2501
2502
_view_map_check_preference_changed(gpointer instance,gpointer user_data)2503 static void _view_map_check_preference_changed(gpointer instance, gpointer user_data)
2504 {
2505 dt_view_t *view = (dt_view_t *)user_data;
2506 dt_map_t *lib = (dt_map_t *)view->data;
2507
2508 if(_view_map_prefs_changed(lib)) g_signal_emit_by_name(lib->map, "changed");
2509 }
2510
_view_map_collection_changed(gpointer instance,dt_collection_change_t query_change,dt_collection_properties_t changed_property,gpointer imgs,int next,gpointer user_data)2511 static void _view_map_collection_changed(gpointer instance, dt_collection_change_t query_change,
2512 dt_collection_properties_t changed_property, gpointer imgs, int next,
2513 gpointer user_data)
2514 {
2515 dt_view_t *self = (dt_view_t *)user_data;
2516 dt_map_t *lib = (dt_map_t *)self->data;
2517 // avoid to centre the map on collection while a location is active
2518 if(darktable.view_manager->proxy.map.view && !lib->loc.main.id)
2519 {
2520 _view_map_center_on_image_list(self, "memory.collected_images");
2521 }
2522
2523 if(dt_conf_get_bool("plugins/map/filter_images_drawn"))
2524 {
2525 // only redraw when map mode is currently active, otherwise enter() does the magic
2526 if(darktable.view_manager->proxy.map.view) g_signal_emit_by_name(lib->map, "changed");
2527 }
2528 }
2529
_view_map_selection_changed(gpointer instance,gpointer user_data)2530 static void _view_map_selection_changed(gpointer instance, gpointer user_data)
2531 {
2532 dt_view_t *self = (dt_view_t *)user_data;
2533 dt_map_t *lib = (dt_map_t *)self->data;
2534
2535 /* only redraw when map mode is currently active, otherwise enter() does the magic */
2536 if(darktable.view_manager->proxy.map.view) g_signal_emit_by_name(lib->map, "changed");
2537 }
2538
_view_map_geotag_changed(gpointer instance,GList * imgs,const int locid,gpointer user_data)2539 static void _view_map_geotag_changed(gpointer instance, GList *imgs, const int locid, gpointer user_data)
2540 {
2541 // if locid <> NULL this event doesn't concern geotag but location
2542 if(!locid)
2543 {
2544 dt_view_t *self = (dt_view_t *)user_data;
2545 dt_map_t *lib = (dt_map_t *)self->data;
2546 if(darktable.view_manager->proxy.map.view) g_signal_emit_by_name(lib->map, "changed");
2547 }
2548 }
2549
_view_map_center_on_image(dt_view_t * self,const int32_t imgid)2550 static void _view_map_center_on_image(dt_view_t *self, const int32_t imgid)
2551 {
2552 if(imgid)
2553 {
2554 const dt_map_t *lib = (dt_map_t *)self->data;
2555 dt_image_geoloc_t geoloc;
2556 dt_image_get_location(imgid, &geoloc);
2557
2558 if(!isnan(geoloc.longitude) && !isnan(geoloc.latitude))
2559 {
2560 int zoom;
2561 g_object_get(G_OBJECT(lib->map), "zoom", &zoom, NULL);
2562 _view_map_center_on_location(self, geoloc.longitude, geoloc.latitude, zoom);
2563 }
2564 }
2565 }
2566
_view_map_center_on_image_list(dt_view_t * self,const char * table)2567 static gboolean _view_map_center_on_image_list(dt_view_t *self, const char* table)
2568 {
2569 const dt_map_t *lib = (dt_map_t *)self->data;
2570 double max_longitude = -INFINITY;
2571 double max_latitude = -INFINITY;
2572 double min_longitude = INFINITY;
2573 double min_latitude = INFINITY;
2574 int count = 0;
2575
2576 gchar *query = g_strdup_printf("SELECT MIN(latitude), MAX(latitude),"
2577 " MIN(longitude), MAX(longitude), COUNT(*)"
2578 " FROM main.images AS i "
2579 " JOIN %s AS l ON l.imgid = i.id "
2580 " WHERE latitude NOT NULL AND longitude NOT NULL",
2581 table);
2582 sqlite3_stmt *stmt;
2583 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
2584 if(sqlite3_step(stmt) == SQLITE_ROW)
2585 {
2586 min_latitude = sqlite3_column_double(stmt, 0);
2587 max_latitude = sqlite3_column_double(stmt, 1);
2588 min_longitude = sqlite3_column_double(stmt, 2);
2589 max_longitude = sqlite3_column_double(stmt, 3);
2590 count = sqlite3_column_int(stmt, 4);
2591 }
2592 sqlite3_finalize(stmt);
2593 g_free(query);
2594
2595 if(count>0)
2596 {
2597 max_longitude = CLAMP(max_longitude, -180, 180);
2598 min_longitude = CLAMP(min_longitude, -180, 180);
2599 max_latitude = CLAMP(max_latitude, -90, 90);
2600 min_latitude = CLAMP(min_latitude, -90, 90);
2601
2602 _view_map_center_on_bbox(self, min_longitude, min_latitude, max_longitude, max_latitude);
2603
2604 // Now the zoom is set we can use the thumb angle to give some room
2605 max_longitude = CLAMP(max_longitude + 1.0 * lib->thumb_lon_angle, -180, 180);
2606 min_longitude = CLAMP(min_longitude - 0.2 * lib->thumb_lon_angle, -180, 180);
2607 max_latitude = CLAMP(max_latitude + 1.0 * lib->thumb_lat_angle, -90, 90);
2608 min_latitude = CLAMP(min_latitude - 0.2 * lib->thumb_lat_angle, -90, 90);
2609
2610 _view_map_center_on_bbox(self, min_longitude, min_latitude, max_longitude, max_latitude);
2611
2612 return TRUE;
2613 }
2614 else
2615 return FALSE;
2616 }
2617
_view_map_filmstrip_activate_callback(gpointer instance,int imgid,gpointer user_data)2618 static void _view_map_filmstrip_activate_callback(gpointer instance, int imgid, gpointer user_data)
2619 {
2620 dt_view_t *self = (dt_view_t *)user_data;
2621 _view_map_center_on_image(self, imgid);
2622 }
2623
_drag_and_drop_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint target_type,guint time,gpointer data)2624 static void _drag_and_drop_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
2625 GtkSelectionData *selection_data, guint target_type, guint time,
2626 gpointer data)
2627 {
2628 dt_view_t *self = (dt_view_t *)data;
2629 dt_map_t *lib = (dt_map_t *)self->data;
2630 gboolean success = FALSE;
2631 if(selection_data != NULL && target_type == DND_TARGET_IMGID)
2632 {
2633 const int imgs_nb = gtk_selection_data_get_length(selection_data) / sizeof(uint32_t);
2634 if(imgs_nb)
2635 {
2636 uint32_t *imgt = (uint32_t *)gtk_selection_data_get_data(selection_data);
2637 if(imgs_nb == 1 && imgt[0] == -1)
2638 {
2639 // move of location
2640 OsmGpsMapPoint *pt = osm_gps_map_point_new_degrees(0.0, 0.0);
2641 osm_gps_map_convert_screen_to_geographic(lib->map, x, y, pt);
2642 float lat, lon;
2643 osm_gps_map_point_get_degrees(pt, &lat, &lon);
2644 lib->loc.main.data.lat = lat, lib->loc.main.data.lon = lon;
2645 const float prev_ratio = lib->loc.main.data.ratio;
2646 lib->loc.main.data.ratio = _view_map_get_angles_ratio(lib, lib->loc.main.data.lat,
2647 lib->loc.main.data.lon);
2648 lib->loc.main.data.delta2 = lib->loc.main.data.delta2 * prev_ratio / lib->loc.main.data.ratio;
2649 osm_gps_map_point_free(pt);
2650 _view_map_update_location_geotag(self);
2651 _view_map_draw_main_location(lib, &lib->loc.main);
2652 _view_map_signal_change_wait(self, 1);
2653 success = TRUE;
2654 }
2655 else
2656 {
2657 GList *imgs = NULL;
2658 for(int i = 0; i < imgs_nb; i++)
2659 {
2660 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgt[i]));
2661 }
2662 float longitude, latitude;
2663 OsmGpsMapPoint *pt = osm_gps_map_point_new_degrees(0.0, 0.0);
2664 osm_gps_map_convert_screen_to_geographic(lib->map, x - lib->start_drag_offset_x,
2665 y - lib->start_drag_offset_y, pt);
2666 osm_gps_map_point_get_degrees(pt, &latitude, &longitude);
2667 osm_gps_map_point_free(pt);
2668 // TODO redraw the image group
2669 // it seems that at this time osm_gps_map doesn't answer before dt_image_set_locations(). Locked in some way ?
2670 const dt_image_geoloc_t geoloc = { longitude, latitude, NAN };
2671 dt_control_signal_block_by_func(darktable.signals, G_CALLBACK(_view_map_collection_changed), self);
2672 dt_image_set_locations(imgs, &geoloc, TRUE);
2673 dt_control_signal_unblock_by_func(darktable.signals, G_CALLBACK(_view_map_collection_changed), self);
2674 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_GEOTAG_CHANGED, imgs, 0);
2675 g_signal_emit_by_name(lib->map, "changed");
2676 success = TRUE;
2677 }
2678 }
2679 }
2680 gtk_drag_finish(context, success, FALSE, time);
2681 }
2682
_view_map_dnd_get_callback(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint target_type,guint time,dt_view_t * self)2683 static void _view_map_dnd_get_callback(GtkWidget *widget, GdkDragContext *context,
2684 GtkSelectionData *selection_data, guint target_type, guint time,
2685 dt_view_t *self)
2686 {
2687 dt_map_t *lib = (dt_map_t *)self->data;
2688 g_assert(selection_data != NULL);
2689 switch(target_type)
2690 {
2691 case DND_TARGET_IMGID:
2692 {
2693 if(lib->selected_images)
2694 {
2695 // drag & drop of images
2696 const guint imgs_nb = g_list_length(lib->selected_images);
2697 if(imgs_nb)
2698 {
2699 uint32_t *imgs = malloc(sizeof(uint32_t) * imgs_nb);
2700 int i = 0;
2701 for(GList *l = lib->selected_images; l; l = g_list_next(l))
2702 {
2703 imgs[i++] = GPOINTER_TO_INT(l->data);
2704 }
2705 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
2706 _DWORD, (guchar *)imgs, imgs_nb * sizeof(uint32_t));
2707 free(imgs);
2708 }
2709 }
2710 else if(lib->loc.main.id > 0)
2711 {
2712 // move of location
2713 uint32_t *imgs = malloc(sizeof(uint32_t));
2714 imgs[0] = -1;
2715 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
2716 _DWORD, (guchar *)imgs, sizeof(uint32_t));
2717 free(imgs);
2718 }
2719 }
2720 break;
2721 default: // return the location of the file as a last resort
2722 case DND_TARGET_URI:
2723 {
2724 if(lib->selected_images)
2725 {
2726 const int imgid = GPOINTER_TO_INT(lib->selected_images->data);
2727 gchar pathname[PATH_MAX] = { 0 };
2728 gboolean from_cache = TRUE;
2729 dt_image_full_path(imgid, pathname, sizeof(pathname), &from_cache);
2730 gchar *uri = g_strdup_printf("file://%s", pathname); // TODO: should we add the host?
2731 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data), _BYTE,
2732 (guchar *)uri, strlen(uri));
2733 g_free(uri);
2734 }
2735 break;
2736 }
2737 }
2738 }
2739
_view_map_dnd_remove_callback(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint target_type,guint time,gpointer data)2740 static void _view_map_dnd_remove_callback(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
2741 GtkSelectionData *selection_data, guint target_type, guint time,
2742 gpointer data)
2743 {
2744 dt_view_t *self = (dt_view_t *)data;
2745 dt_map_t *lib = (dt_map_t *)self->data;
2746
2747 gboolean success = FALSE;
2748
2749 if(selection_data != NULL && target_type == DND_TARGET_IMGID)
2750 {
2751 const int imgs_nb = gtk_selection_data_get_length(selection_data) / sizeof(uint32_t);
2752 if(imgs_nb)
2753 {
2754 uint32_t *imgt = (uint32_t *)gtk_selection_data_get_data(selection_data);
2755 GList *imgs = NULL;
2756 for(int i = 0; i < imgs_nb; i++)
2757 {
2758 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgt[i]));
2759 }
2760 // image(s) dropped into the filmstrip, let's remove it (them) in this case
2761 const dt_image_geoloc_t geoloc = { NAN, NAN, NAN };
2762 dt_image_set_locations(imgs, &geoloc, TRUE);
2763 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_GEOTAG_CHANGED, imgs, 0);
2764 success = TRUE;
2765 }
2766 }
2767 gtk_drag_finish(context, success, FALSE, time);
2768 if(success) g_signal_emit_by_name(lib->map, "changed");
2769 }
2770
_view_map_dnd_failed_callback(GtkWidget * widget,GdkDragContext * drag_context,GtkDragResult result,dt_view_t * self)2771 static gboolean _view_map_dnd_failed_callback(GtkWidget *widget, GdkDragContext *drag_context,
2772 GtkDragResult result, dt_view_t *self)
2773 {
2774 dt_map_t *lib = (dt_map_t *)self->data;
2775 g_signal_emit_by_name(lib->map, "changed");
2776
2777 return TRUE;
2778 }
2779
_view_map_prefs_changed(dt_map_t * lib)2780 static gboolean _view_map_prefs_changed(dt_map_t *lib)
2781 {
2782 gboolean prefs_changed = FALSE;
2783
2784 lib->max_images_drawn = dt_conf_get_int("plugins/map/max_images_drawn");
2785 if(lib->max_images_drawn == 0) lib->max_images_drawn = 100;
2786
2787 gboolean filter_images_drawn = dt_conf_get_bool("plugins/map/filter_images_drawn");
2788 if(lib->filter_images_drawn != filter_images_drawn) prefs_changed = TRUE;
2789
2790 const char *thumbnail = dt_conf_get_string_const("plugins/map/images_thumbnail");
2791 lib->thumbnail = !g_strcmp0(thumbnail, "thumbnail") ? DT_MAP_THUMB_THUMB :
2792 !g_strcmp0(thumbnail, "count") ? DT_MAP_THUMB_COUNT : DT_MAP_THUMB_NONE;
2793
2794 return prefs_changed;
2795 }
2796
_view_map_build_main_query(dt_map_t * lib)2797 static void _view_map_build_main_query(dt_map_t *lib)
2798 {
2799 char *geo_query;
2800
2801 if(lib->main_query) sqlite3_finalize(lib->main_query);
2802
2803 lib->filter_images_drawn = dt_conf_get_bool("plugins/map/filter_images_drawn");
2804 geo_query = g_strdup_printf("SELECT * FROM"
2805 " (SELECT id, longitude, latitude "
2806 " FROM %s WHERE longitude >= ?1 AND longitude <= ?2"
2807 " AND latitude <= ?3 AND latitude >= ?4 "
2808 " AND longitude NOT NULL AND latitude NOT NULL)"
2809 " ORDER BY longitude ASC", // critical to make dbscan work
2810 lib->filter_images_drawn
2811 ? "main.images i INNER JOIN memory.collected_images c ON i.id = c.imgid"
2812 : "main.images");
2813
2814 /* prepare the main query statement */
2815 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), geo_query, -1, &lib->main_query, NULL);
2816
2817 g_free(geo_query);
2818 }
2819
mouse_actions(const dt_view_t * self)2820 GSList *mouse_actions(const dt_view_t *self)
2821 {
2822 GSList *lm = NULL;
2823 lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_DOUBLE_LEFT, 0, _("[on image] open in darkroom"));
2824 lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_DOUBLE_LEFT, 0, _("[on map] zoom map"));
2825 lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_DRAG_DROP, 0, _("move image location"));
2826 return lm;
2827 }
2828
2829 // starting point taken from https://github.com/gyaikhom/dbscan
2830 // Copyright 2015 Gagarine Yaikhom (MIT License)
2831
2832 typedef struct epsilon_neighbours_t
2833 {
2834 unsigned int num_members;
2835 unsigned int index[];
2836 } epsilon_neighbours_t;
2837
2838 typedef struct dt_dbscan_t
2839 {
2840 dt_geo_position_t *points;
2841 unsigned int num_points;
2842 double epsilon;
2843 unsigned int minpts;
2844 epsilon_neighbours_t *seeds;
2845 epsilon_neighbours_t *spreads;
2846 unsigned int index;
2847 unsigned int cluster_id;
2848 } dt_dbscan_t;
2849
2850 static dt_dbscan_t db;
2851
_get_epsilon_neighbours(epsilon_neighbours_t * en,unsigned int index)2852 static void _get_epsilon_neighbours(epsilon_neighbours_t *en, unsigned int index)
2853 {
2854 // points are ordered by longitude
2855 // limit the exploration to epsilon east and west
2856 // west
2857 for(int i = index; i < db.num_points; ++i)
2858 {
2859 if(i == index || db.points[i].cluster_id >= 0)
2860 continue;
2861 if((db.points[i].x - db.points[index].x) > db.epsilon)
2862 break;
2863 if(fabs(db.points[i].y - db.points[index].y) > db.epsilon)
2864 continue;
2865 else
2866 {
2867 en->index[en->num_members] = i;
2868 en->num_members++;
2869 }
2870 }
2871 // east
2872 for(int i = index; i >= 0; --i)
2873 {
2874 if(i == (int)index || db.points[i].cluster_id >= 0)
2875 continue;
2876 if((db.points[index].x - db.points[i].x) > db.epsilon)
2877 break;
2878 if(fabs(db.points[index].y - db.points[i].y) > db.epsilon)
2879 continue;
2880 else
2881 {
2882 en->index[en->num_members] = i;
2883 en->num_members++;
2884 }
2885 }
2886 }
2887
_dbscan_spread(unsigned int index)2888 static void _dbscan_spread(unsigned int index)
2889 {
2890 db.spreads->num_members = 0;
2891 _get_epsilon_neighbours(db.spreads, index);
2892
2893 for(unsigned int i = 0; i < db.spreads->num_members; i++)
2894 {
2895 dt_geo_position_t *d = &db.points[db.spreads->index[i]];
2896 if(d->cluster_id == NOISE || d->cluster_id == UNCLASSIFIED)
2897 {
2898 db.seeds->index[db.seeds->num_members] = db.spreads->index[i];
2899 db.seeds->num_members++;
2900 d->cluster_id = db.cluster_id;
2901 }
2902 }
2903 }
2904
_dbscan_expand(unsigned int index)2905 static int _dbscan_expand(unsigned int index)
2906 {
2907 int return_value = NOT_CORE_POINT;
2908 db.seeds->num_members = 0;
2909 _get_epsilon_neighbours(db.seeds, index);
2910
2911 if (db.seeds->num_members < db.minpts)
2912 db.points[index].cluster_id = NOISE;
2913 else
2914 {
2915 db.points[index].cluster_id = db.cluster_id;
2916 for(int i = 0; i < db.seeds->num_members; i++)
2917 {
2918 db.points[db.seeds->index[i]].cluster_id = db.cluster_id;
2919 }
2920
2921 for(int i = 0; i < db.seeds->num_members; i++)
2922 {
2923 _dbscan_spread(db.seeds->index[i]);
2924 }
2925 return_value = CORE_POINT;
2926 }
2927 return return_value;
2928 }
2929
_dbscan(dt_geo_position_t * points,unsigned int num_points,double epsilon,unsigned int minpts)2930 static void _dbscan(dt_geo_position_t *points, unsigned int num_points,
2931 double epsilon, unsigned int minpts)
2932 {
2933 db.points = points;
2934 db.num_points = num_points;
2935 db.epsilon = epsilon;
2936 // remove the pivot from target
2937 db.minpts = minpts > 1 ? minpts - 1 : minpts;
2938 db.cluster_id = 0;
2939 db.seeds = (epsilon_neighbours_t *)malloc(sizeof(db.seeds->num_members)
2940 + num_points * sizeof(db.seeds->index[0]));
2941 db.spreads = (epsilon_neighbours_t *)malloc(sizeof(db.spreads->num_members)
2942 + num_points * sizeof(db.spreads->index[0]));
2943
2944 if(db.seeds && db.spreads)
2945 {
2946 for(unsigned int i = 0; i < db.num_points; ++i)
2947 {
2948 if(db.points[i].cluster_id == UNCLASSIFIED)
2949 {
2950 if(_dbscan_expand(i) == CORE_POINT)
2951 {
2952 ++db.cluster_id;
2953 }
2954 }
2955 }
2956 g_free(db.seeds);
2957 g_free(db.spreads);
2958 }
2959 }
2960
2961 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
2962 // vim: shiftwidth=2 expandtab tabstop=2 cindent
2963 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2964