Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
libs/map_locations.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2020-2021 Pascal Obry.
4 Copyright (C) 2020-2021 Philippe Weyland.
5 Copyright (C) 2021-2022 Diederik Ter Rahe.
6 Copyright (C) 2021 EdgarLux.
7 Copyright (C) 2021 luzpaz.
8 Copyright (C) 2021 Ralf Brown.
9 Copyright (C) 2022 Aldric Renaudin.
10 Copyright (C) 2022, 2025 Aurélien PIERRE.
11 Copyright (C) 2022 Martin Bařinka.
12 Copyright (C) 2022 Miloš Komarčević.
13
14 darktable is free software: you can redistribute it and/or modify
15 it under the terms of the GNU General Public License as published by
16 the Free Software Foundation, either version 3 of the License, or
17 (at your option) any later version.
18
19 darktable is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with darktable. If not, see <http://www.gnu.org/licenses/>.
26*/
27#include "metadata/tags.h"
28#include "control/settings.h"
31#include "widgets/paint.h"
32#include "common/collection.h"
33#include "system/macros.h"
35#include "common/utility.h"
37#include "common/conf.h"
39
40#include "libs/lib.h"
41#include "views/view.h"
42#include "gui/window_manager.h"
45
46// map position module uses the tag dictionary with dt_geo_tag_root as a prefix.
47// Synonym field is used to store positions coordinates in ascii format.
48
50static void _show_location(dt_lib_module_t *self);
51
52DT_MODULE(1)
53
54const char *name(struct dt_lib_module_t *self)
55{
56 return _("locations");
57}
58
59const char **views(dt_lib_module_t *self)
60{
61 static const char *v[] = {"map", NULL};
62 return v;
63}
64
69
84
85typedef struct dt_loc_op_t
86{
90
92{
93 return 995;
94}
95
104
109
111 dtgtk_cairo_paint_rect_landscape, // MAP_LOCATION_SHAPE_RECTANGLE
112 dtgtk_cairo_paint_polygon}; // MAP_LOCATION_SHAPE_POLYGONS
113
114static gboolean _mouse_scroll(GtkWidget *treeview, GdkEventScroll *event,
115 dt_lib_module_t *self)
116{
118 if (dt_modifier_is(event->state, DT_PRIMARY_MASK))
119 {
120 const gint increment = DT_PIXEL_APPLY_DPI(10.0);
121 const gint min_height = DT_PIXEL_APPLY_DPI(100.0);
122 const gint max_height = DT_PIXEL_APPLY_DPI(500.0);
123 gint width, height;
124 gtk_widget_get_size_request (GTK_WIDGET(d->window), &width, &height);
125 height = height + increment * event->delta_y;
126 height = (height < min_height) ? min_height
127 : (height > max_height) ? max_height : height;
128 gtk_widget_set_size_request(GTK_WIDGET(d->window), -1, (gint)height);
129 dt_conf_set_int("plugins/map/heightlocationwindow", (gint)height);
130 return TRUE;
131 }
132 return FALSE;
133}
134
135// find a tag on the tree
136static gboolean _find_tag_iter_id(GtkTreeModel *model, GtkTreeIter *iter,
137 const guint locid)
138{
139 gboolean found = FALSE;
140 if(locid <= 0) return found;
141 guint id;
142 do
143 {
144 gtk_tree_model_get(model, iter, DT_MAP_LOCATION_COL_ID, &id, -1);
145 found = id == locid;
146 if(found) return found;
147 GtkTreeIter child, parent = *iter;
148 if(gtk_tree_model_iter_children(model, &child, &parent))
149 {
150 found = _find_tag_iter_id(model, &child, locid);
151 if(found)
152 {
153 *iter = child;
154 return found;
155 }
156 }
157 } while(gtk_tree_model_iter_next(model, iter));
158 return found;
159}
160
161static void _locations_tree_update(dt_lib_module_t *self, const guint locid)
162{
165 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
166
167 g_object_ref(model);
168 gtk_tree_view_set_model(GTK_TREE_VIEW(d->view), NULL);
169 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
170 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
171 GTK_SORT_ASCENDING);
172 gtk_tree_store_clear(GTK_TREE_STORE(model));
173 if(tags)
174 {
175 char **last_tokens = NULL;
176 int last_tokens_length = 0;
177 GtkTreeIter last_parent = { 0 };
178 GList *sorted_tags = dt_sort_tag(tags, 0); // ordered by full tag name
179 tags = sorted_tags;
180 for(GList *stag = tags; stag; stag = g_list_next(stag))
181 {
182 GtkTreeIter iter;
183 const gchar *tag = ((dt_map_location_t *)stag->data)->tag;
184 if(IS_NULL_PTR(tag)) continue;
185 char **tokens;
186 tokens = g_strsplit(tag, "|", -1);
187 if(tokens)
188 {
189 // find the number of common parts at the beginning of tokens and last_tokens
190 GtkTreeIter parent = last_parent;
191 const int tokens_length = g_strv_length(tokens);
192 int common_length = 0;
193 if(last_tokens)
194 {
195 while(tokens[common_length] && last_tokens[common_length] &&
196 !g_strcmp0(tokens[common_length], last_tokens[common_length]))
197 {
198 common_length++;
199 }
200
201 // point parent iter to where the entries should be added
202 for(int i = common_length; i < last_tokens_length; i++)
203 {
204 gtk_tree_model_iter_parent(GTK_TREE_MODEL(model), &parent, &last_parent);
205 last_parent = parent;
206 }
207 }
208
209 // insert everything from tokens past the common part
210 char *pth = NULL;
211 for(int i = 0; i < common_length; i++)
212 pth = dt_util_dstrcat(pth, "%s|", tokens[i]);
213
214 for(char **token = &tokens[common_length]; *token; token++)
215 {
216 pth = dt_util_dstrcat(pth, "%s|", *token);
217 gchar *pth2 = g_strdup(pth);
218 pth2[strlen(pth2) - 1] = '\0';
219 gtk_tree_store_insert(GTK_TREE_STORE(model), &iter, common_length > 0 ? &parent : NULL, -1);
220 gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
222 DT_MAP_LOCATION_COL_ID, (token == &tokens[tokens_length-1]) ?
223 ((dt_map_location_t *)stag->data)->id : 0,
225 DT_MAP_LOCATION_COL_COUNT, (token == &tokens[tokens_length-1]) ?
226 ((dt_map_location_t *)stag->data)->count : 0,
227 -1);
228 common_length++;
229 parent = iter;
230 dt_free(pth2);
231 }
232 dt_free(pth);
233
234 // remember things for the next round
235 if(last_tokens) g_strfreev(last_tokens);
236 last_tokens = tokens;
237 last_parent = parent;
238 last_tokens_length = tokens_length;
239 }
240 }
241 g_strfreev(last_tokens);
243 }
244 gtk_tree_view_set_model(GTK_TREE_VIEW(d->view), model);
245 g_object_unref(model);
246 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
248 GTK_SORT_ASCENDING);
249 if(locid)
250 {
251 // try to select the right record
252 GtkTreeIter iter;
253 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
254 if(gtk_tree_model_get_iter_first(model, &iter))
255 {
256 if(_find_tag_iter_id(model, &iter, locid))
257 {
258 gtk_tree_selection_select_iter(selection, &iter);
259 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
260 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(d->view), path);
261 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(d->view), path, NULL, TRUE, 0.5, 0.5);
262 gtk_tree_view_set_cursor(GTK_TREE_VIEW(d->view), path, d->name_col, FALSE);
263 gtk_tree_path_free(path);
264 }
265 }
266 }
267}
268
270{
272 GtkTreeIter iter;
273 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
274 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
275 if(gtk_tree_selection_get_selected(selection, &model, &iter))
276 {
277 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->new_button))), _("new sub-location"));
278 }
279 else
280 {
281 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->new_button))), _("new location"));
282 }
283}
284
285static void _tree_name_show(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
286 GtkTreeModel *model, GtkTreeIter *iter,
287 gpointer data)
288{
289 guint locid;
290 gchar *name;
291 gchar *path;
292 guint count;
293 gchar *coltext;
294 gtk_tree_model_get(model, iter,
298 DT_MAP_LOCATION_COL_PATH, &path, -1);
299 if (count < 1)
300 {
301 coltext = g_markup_printf_escaped(locid ? "%s" : "<i>%s</i>", name);
302 }
303 else
304 {
305 coltext = g_markup_printf_escaped(locid ? "%s (%d)" : "<i>%s</i> (%d)", name, count);
306 }
307 g_object_set(renderer, "markup", coltext, NULL);
308 dt_free(coltext);
309 dt_free(name);
310 dt_free(path);
311}
312
313static void _new_button_clicked(GtkButton *button, dt_lib_module_t *self)
314{
316 GtkTreeIter iter, parent;
317 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
318 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
319 char *name = NULL;
320 char *path = NULL;
321 if(gtk_tree_selection_get_selected(selection, &model, &iter))
322 {
323 gtk_tree_model_get(model, &iter, DT_MAP_LOCATION_COL_PATH, &path, -1);
324 parent = iter;
325 }
326 name = path ? g_strconcat(path, "|", NULL) : g_strdup("");
327 const int base_len = strlen(name);
328 int i = 1;
329 name = dt_util_dstrcat(name, "%s", _("new location"));
330 char *new_name = g_strdup(name);
331 while(dt_map_location_name_exists(new_name))
332 {
333 dt_free(new_name);
334 new_name = g_strdup_printf("%s %d", name, i);
335 i++;
336 }
337
338 // add the new record to the tree
339 gtk_tree_store_insert(GTK_TREE_STORE(model), &iter, path ? &parent : NULL, -1);
340 gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
341 DT_MAP_LOCATION_COL_TAG, &new_name[base_len],
343 DT_MAP_LOCATION_COL_PATH, new_name,
345 -1);
346 dt_free(new_name);
347 dt_free(name);
348 dt_free(path);
349
350 // set the new record editable
351 g_object_set(G_OBJECT(d->renderer), "editable", TRUE, NULL);
352 GtkTreePath *path2 = gtk_tree_model_get_path(model, &iter);
353 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(d->view), path2);
354 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(d->view), path2, NULL, TRUE, 0.5, 0.5);
355 gtk_tree_view_set_cursor(GTK_TREE_VIEW(d->view), path2, d->name_col, TRUE);
356 gtk_tree_path_free(path2);
357}
358
359static void _shape_button_clicked(GtkButton *button, dt_lib_module_t *self)
360{
362 int shape = dt_conf_get_int("plugins/map/locationshape");
363 shape++;
364 if((shape > G_N_ELEMENTS(location_shapes) - 1) ||
365 (IS_NULL_PTR(d->polygons) && shape == MAP_LOCATION_SHAPE_POLYGONS))
366 shape = 0;
367 dt_conf_set_int("plugins/map/locationshape", shape);
368
369 g_signal_handler_block (d->shape_button, d->shape_button_handler);
370 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->shape_button), FALSE);
372 g_signal_handler_unblock (d->shape_button, d->shape_button_handler);
373}
374
375static void _show_all_button_clicked(GtkButton *button, dt_lib_module_t *self)
376{
378 dt_conf_set_bool("plugins/map/showalllocations",
379 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->show_all_button)));
381}
382
383// delete a path of the tag tree
384static void _delete_tree_path(GtkTreeModel *model, GtkTreeIter *iter, gboolean root)
385{
386 GtkTreeIter child, parent = *iter;
387 gboolean valid = TRUE;
388 do
389 {
390 if(gtk_tree_model_iter_children(model, &child, &parent))
391 _delete_tree_path(model, &child, FALSE);
392 GtkTreeIter tobedel = parent;
393 valid = gtk_tree_model_iter_next(model, &parent);
394 char *path = NULL;
395 gtk_tree_model_get(model, &tobedel, DT_MAP_LOCATION_COL_PATH, &path, -1);
396 dt_free(path);
397 gtk_tree_store_remove(GTK_TREE_STORE(model), &tobedel);
398 } while (!root && valid);
399}
400
401static gboolean _update_tag_name_per_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, dt_loc_op_t *to)
402{
403 char *tagname;
404 char *newtagname = to->newtagname;
405 char *oldtagname = to->oldtagname;
406 gtk_tree_model_get(model, iter, DT_MAP_LOCATION_COL_PATH, &tagname, -1);
407 if (g_str_has_prefix(tagname, oldtagname))
408 {
409 if (strlen(tagname) == strlen(oldtagname))
410 {
411 // rename the tag itself
412 char *subtag = g_strrstr(to->newtagname, "|");
413 subtag = (!subtag) ? newtagname : subtag + 1;
414 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
415 DT_MAP_LOCATION_COL_PATH, newtagname,
416 DT_MAP_LOCATION_COL_TAG, subtag, -1);
417 }
418 else if (strlen(tagname) > strlen(oldtagname) && tagname[strlen(oldtagname)] == '|')
419 {
420 // rename similar path
421 char *newpath = g_strconcat(newtagname, &tagname[strlen(oldtagname)] , NULL);
422 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
423 DT_MAP_LOCATION_COL_PATH, newpath, -1);
424 dt_free(newpath);
425 }
426 }
427 dt_free(tagname);
428 return FALSE;
429}
430
431static void _view_map_geotag_changed(gpointer instance, GList *imgs, const int newlocid, dt_lib_module_t *self)
432{
434
435 // one of the other location has been clicked on the map
436 if(newlocid)
437 {
438 GtkTreeIter iter;
439 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
440 if(gtk_tree_model_get_iter_first(model, &iter))
441 {
442 if(_find_tag_iter_id(model, &iter, newlocid))
443 {
444 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
445 gtk_tree_selection_select_iter(selection, &iter);
446 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
447 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(d->view), path);
448 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(d->view), path, NULL, TRUE, 0.5, 0.5);
449 gtk_tree_view_set_cursor(GTK_TREE_VIEW(d->view), path, d->name_col, FALSE);
450 gtk_tree_path_free(path);
451 _show_location(self);
452 _display_buttons(self);
453 }
454 }
455 }
456 else
457 {
458 for(GList* img = imgs; img; img = g_list_next(img))
459 {
460 // find new locations for that image
461 GList *tags = dt_map_location_find_locations(GPOINTER_TO_INT(img->data));
462 // update locations for that image
463 dt_map_location_update_locations(GPOINTER_TO_INT(img->data), tags);
464 g_list_free(tags);
465 tags = NULL;
466 }
467 // update count on the treeview
469 GtkTreeIter iter;
470 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
471 if(gtk_tree_model_get_iter_first(model, &iter))
472 {
473 for(GList *loc = locs; loc; loc = g_list_next(loc))
474 {
475 const guint locid = ((dt_map_location_t *)loc->data)->id;
476 GtkTreeIter iter2 = iter;
477 if(_find_tag_iter_id(model, &iter2, locid))
478 {
479 gtk_tree_store_set(GTK_TREE_STORE(model), &iter2,
480 DT_MAP_LOCATION_COL_COUNT, ((dt_map_location_t *)loc->data)->count,
481 -1);
482 }
483 }
484 }
486 }
487}
488
489static void _view_map_location_changed(gpointer instance, GList *polygons, dt_lib_module_t *self)
490{
492 const int shape = dt_conf_get_int("plugins/map/locationshape");
493 if((shape == MAP_LOCATION_SHAPE_POLYGONS) && IS_NULL_PTR(polygons))
494 {
495 g_signal_handler_block (d->shape_button, d->shape_button_handler);
498 g_signal_handler_unblock (d->shape_button, d->shape_button_handler);
499 dt_conf_set_int("plugins/map/locationshape", MAP_LOCATION_SHAPE_ELLIPSE);
500 }
501 d->polygons = polygons;
502}
503
510
511static void _name_editing_done(GtkCellEditable *editable, dt_lib_module_t *self)
512{
514 gboolean canceled = TRUE;
515 g_object_get(editable, "editing-canceled", &canceled, NULL);
516 const gchar *name = gtk_entry_get_text(GTK_ENTRY(editable));
517 const gboolean reset = name[0] ? FALSE : TRUE;
518 GtkTreeIter iter;
519 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
520 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
521 if(gtk_tree_selection_get_selected(selection, &model, &iter))
522 {
523 char *path = NULL;
524 char *leave = NULL;
525 guint locid;
526 gtk_tree_model_get(model, &iter, DT_MAP_LOCATION_COL_ID, &locid,
529 if(reset && locid)
530 canceled = TRUE; // empty name for a location is not allowed
531 if(!canceled)
532 {
533 const gboolean isroot = !strcmp(path, leave);
534 const int path_len = strlen(path);
535 char *new_path;
536 if(!isroot)
537 {
538 const int leave_len = strlen(leave);
539 const char letter = path[path_len - leave_len];
540 path[path_len - leave_len] = '\0';
541 new_path = g_strconcat(path, name, NULL);
542 path[path_len - leave_len] = letter;
543 }
544 else
545 new_path = g_strdup((char *)name);
546
547 gboolean new_exists = FALSE;
548 GList *new_existing = NULL;
549 if(!reset) // in case of reset we rely on dt_tag_rename to be safe
550 new_existing = dt_map_location_get_locations_by_path(new_path, FALSE);
551 if(new_existing)
552 {
553 dt_map_location_free_result(&new_existing);
554 new_exists = TRUE;
555 }
556 if(!new_exists)
557 {
558 // new name is free, we can work
559 if(locid == -1)
560 {
561 // new location - create and show it
562 locid = dt_map_location_new(new_path);
563 if(locid != -1)
564 {
565 // add the location on the map
567 g.shape = dt_conf_get_int("plugins/map/locationshape");
568 g.lon = g.lat = NAN;
569 g.delta1 = g.delta2 = 0.0;
570 g.polygons = d->polygons;
572 const int count = dt_map_location_get_images_count(locid);
573 if(g_strstr_len(name, -1, "|"))
574 {
575 // the user wants to insert some group(s). difficult to handle the tree => reset
576 _locations_tree_update(self, locid);
577 }
578 else
579 {
580 gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
582 DT_MAP_LOCATION_COL_PATH, new_path,
585 -1);
586 }
587 }
588 else canceled = TRUE;
589 }
590 else
591 {
592 // existing location - rename it
593 GList *children = dt_map_location_get_locations_by_path(path, FALSE);
594 for (GList *tag = children; tag; tag = g_list_next(tag))
595 {
596 // reset on leave is not possible. should be safe
597 const char *new_part = &((dt_map_location_t *)tag->data)->tag[path_len + (reset ? 1 :0)];
598 char *new_name = g_strconcat(new_path, new_part, NULL);
599 dt_map_location_rename(((dt_map_location_t *)tag->data)->id, new_name);
600 dt_free(new_name);
601 }
603
604 // update the store
605 if(reset || g_strstr_len(name, -1, "|"))
606 {
607 // the user wants to insert some group(s). difficult to handle the tree => reset
608 _locations_tree_update(self, locid);
609 }
610 else
611 {
612 dt_loc_op_t to;
613 to.oldtagname = path;
614 to.newtagname = new_path;
615 gint sort_column;
616 GtkSortType sort_order;
617 gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(model),
618 &sort_column, &sort_order);
619 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
620 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
621 GTK_SORT_ASCENDING);
622 gtk_tree_model_foreach(model, (GtkTreeModelForeachFunc)_update_tag_name_per_name, &to);
623 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
624 sort_column, sort_order);
625 }
627 }
628 }
629 else
630 {
631 dt_control_log(_("location name \'%s\' already exists"), new_path);
632 canceled = TRUE;
633 }
634 dt_free(new_path);
635 }
636 if(canceled)
637 {
638 if(locid == -1)
639 {
640 // if new we have to remove the new location from tree
642 gtk_tree_selection_unselect_all(selection);
643 }
644 }
645 dt_free(path);
646 dt_free(leave);
647 }
648 g_object_set(G_OBJECT(d->renderer), "editable", FALSE, NULL);
649 _display_buttons(self);
650}
651
652static void _name_start_editing(GtkCellRenderer *renderer, GtkCellEditable *editable,
653 char *path, dt_lib_module_t *self)
654{
656 if (GTK_IS_ENTRY(editable))
657 {
658 // set up the editable with name (without number)
659 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
660 GtkTreeIter iter;
661 GtkTreePath *new_path = gtk_tree_path_new_from_string(path);
662 if(gtk_tree_model_get_iter(model, &iter, new_path))
663 {
664 char *name = NULL;
665 gtk_tree_model_get(model, &iter, DT_MAP_LOCATION_COL_TAG, &name, -1);
666 gtk_entry_set_text(GTK_ENTRY(editable), name);
667 dt_free(name);
668 }
669 gtk_tree_path_free(new_path);
670
671 g_signal_connect(G_OBJECT(editable), "editing-done", G_CALLBACK(_name_editing_done), self);
672 }
673}
674
675static gint _sort_position_names_func(GtkTreeModel *model,
676 GtkTreeIter *a, GtkTreeIter *b,
677 dt_lib_module_t *self)
678{
679 char *tag_a = NULL;
680 char *tag_b = NULL;
681 gtk_tree_model_get(model, a, DT_MAP_LOCATION_COL_PATH, &tag_a, -1);
682 gtk_tree_model_get(model, b, DT_MAP_LOCATION_COL_PATH, &tag_b, -1);
683 if(IS_NULL_PTR(tag_a)) tag_a = g_strdup("");
684 if(IS_NULL_PTR(tag_b)) tag_b = g_strdup("");
685 const gboolean sort = g_ascii_strncasecmp(tag_a, tag_b, -1);
686 dt_free(tag_a);
687 dt_free(tag_b);
688 return sort;
689}
690
692{
694 GtkTreeIter iter;
695 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
696 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
697 if(gtk_tree_selection_get_selected(selection, &model, &iter))
698 {
699 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
700 g_object_set(G_OBJECT(d->renderer), "editable", TRUE, NULL);
701 gtk_tree_view_set_cursor(GTK_TREE_VIEW(d->view), path, d->name_col, TRUE);
702 gtk_tree_path_free(path);
703 _display_buttons(self);
704 }
705}
706
708{
710 GtkTreeIter iter;
711 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
712 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
713 if(gtk_tree_selection_get_selected(selection, &model, &iter))
714 {
715 guint locid = 0;
716 gtk_tree_model_get(model, &iter, DT_MAP_LOCATION_COL_ID, &locid, -1);
717 if(locid > 0)
718 {
722 }
723 // update the treeview
724 GtkTreeIter parent;
725 if(gtk_tree_model_iter_parent(model, &parent, &iter))
726 {
727 guint parentid;
728 gtk_tree_model_get(model, &parent, DT_MAP_LOCATION_COL_ID, &parentid, -1);
729 if(parentid > 0)
730 {
732 gtk_tree_selection_unselect_all(selection);
733 }
734 else
735 {
736 // parent is a node (not a location). reset treeview
737 _locations_tree_update(self, 0);
738 }
739 }
740 else
741 {
743 gtk_tree_selection_unselect_all(selection);
744 }
745 }
746 _display_buttons(self);
747}
748
750{
752 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
753 GtkTreeIter iter;
754 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
755 if(gtk_tree_selection_get_selected(selection, &model, &iter))
756 {
757 guint locid;
758 gtk_tree_model_get(model, &iter,
759 DT_MAP_LOCATION_COL_ID, &locid, -1);
760 if(locid)
761 {
764 dt_free(p);
765 }
766 else
767 {
768 // this is not a location (only a parent). remove location from map if any
770 }
771 }
772}
773
775{
777 GtkTreeIter iter;
778 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
779 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
780 if(gtk_tree_selection_get_selected(selection, &model, &iter))
781 {
782 char *name;
783 gtk_tree_model_get(model, &iter, DT_MAP_LOCATION_COL_PATH, &name, -1);
784 char *collection = g_strdup_printf("1:0:%d:%s|%s$",
786 _("tagged"), name);
787 dt_collection_deserialize(collection);
788 dt_free(collection);
789 dt_free(name);
790 return TRUE;
791 }
792 return FALSE;
793}
794
796{
798}
799
805
806static void _pop_menu_view(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
807{
808 GtkWidget *menu, *menuitem;
809 menu = gtk_menu_new();
810
811 GtkTreeIter iter;
812 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
813 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
814
815 if(gtk_tree_selection_get_selected(selection, &model, &iter))
816 {
817 guint locid = 0;
818 gtk_tree_model_get(model, &iter, DT_MAP_LOCATION_COL_ID, &locid, -1);
819 GtkTreeIter child, parent = iter;
820 const gboolean children = gtk_tree_model_iter_children(model, &child, &parent);
821
822 menuitem = gtk_menu_item_new_with_label(_("edit location"));
823 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_edit_location, self);
824 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
825 menuitem = gtk_menu_item_new_with_label(_("delete location"));
826 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_delete_location, self);
827 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
828 if(children)
829 {
830 gtk_widget_set_sensitive(menuitem, FALSE);
831 }
832
833 menuitem = gtk_separator_menu_item_new();
834 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
835 menuitem = gtk_menu_item_new_with_label(_("update filmstrip"));
836 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
837 if(!locid)
838 {
839 gtk_widget_set_sensitive(menuitem, FALSE);
840 }
841 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_update_filmstrip, self);
842 menuitem = gtk_menu_item_new_with_label(_("go to collection (lighttable)"));
843 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
844 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_goto_collection, self);
845 if(!locid)
846 {
847 gtk_widget_set_sensitive(menuitem, FALSE);
848 }
849 }
850
851 gtk_widget_show_all(GTK_WIDGET(menu));
852
853 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
854}
855
856static gboolean _force_selection_changed(gpointer user_data)
857{
858 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
860 gtk_tree_selection_unselect_all(d->selection);
861 return FALSE;
862}
863
864static void _selection_changed(GtkTreeSelection *selection, dt_lib_module_t *self)
865{
867 GtkTreeIter iter;
868 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
869 if(gtk_tree_selection_get_selected(selection, &model, &iter))
870 {
871 _show_location(self);
872 }
873 else
874 {
876 }
877 _display_buttons(self);
878}
879
880static gboolean _click_on_view(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
881{
883
884 gboolean editing;
885 g_object_get(G_OBJECT(d->renderer), "editing", &editing, NULL);
886 if(editing)
887 {
888 dt_control_log(_("terminate edit (press enter or escape) before selecting another location"));
889 return TRUE;
890 }
891
892 const int button_pressed = (event->type == GDK_BUTTON_PRESS) ? event->button : 0;
893 const gboolean ctrl_pressed = dt_modifier_is(event->state, DT_PRIMARY_MASK);
894 if((button_pressed == 3)
895 || (button_pressed == 1 && !ctrl_pressed)
896 || (button_pressed == 1 && ctrl_pressed)
897 )
898 {
899 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
900 GtkTreePath *path = NULL;
901 // Get tree path for row that was clicked
902 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), (gint)event->x,
903 (gint)event->y, &path, NULL, NULL, NULL))
904 {
905 if(button_pressed == 3)
906 {
907 gtk_tree_selection_select_path(selection, path);
908 _pop_menu_view(view, event, self);
909 gtk_tree_path_free(path);
910 _display_buttons(self);
911 return TRUE;
912 }
913 else if(button_pressed == 1 && !ctrl_pressed)
914 {
915 if(gtk_tree_selection_path_is_selected(selection, path))
916 g_timeout_add(100, _force_selection_changed, self);
917 gtk_tree_path_free(path);
918 return FALSE;
919 }
920 else if(button_pressed == 1 && ctrl_pressed)
921 {
922 gtk_tree_selection_select_path(selection, path);
923 g_object_set(G_OBJECT(d->renderer), "editable", TRUE, NULL);
924 gtk_tree_view_set_cursor(GTK_TREE_VIEW(d->view), path, d->name_col, TRUE);
925 gtk_tree_path_free(path);
926 _display_buttons(self);
927 return TRUE;
928 }
929 }
930 else
931 {
932 g_timeout_add(10, _force_selection_changed, self);
933 return FALSE;
934 }
935 }
936 return FALSE;
937}
938
940{
942 self->data = d;
943
944 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
945
946 GtkWidget *w = gtk_scrolled_window_new(NULL, NULL);
947 d->window = w;
948 int height = dt_conf_get_int("plugins/map/heightlocationwindow");
949 gtk_widget_set_size_request(w, -1, DT_PIXEL_APPLY_DPI(height ? height : 100));
950 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
951 dt_gui_add_class(w, "dt_recessed_scroll");
952 gtk_box_pack_start(GTK_BOX(self->widget), w, TRUE, TRUE, 0);
953 GtkTreeView *view = GTK_TREE_VIEW(gtk_tree_view_new());
954 d->view = GTK_WIDGET(view);
955 gtk_tree_view_set_headers_visible(view, FALSE);
956 GtkTreeStore *treestore = gtk_tree_store_new(DT_MAP_LOCATION_NUM_COLS, G_TYPE_UINT,
957 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);
958 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treestore), DT_MAP_POSITION_SORT_NAME_ID,
959 (GtkTreeIterCompareFunc)_sort_position_names_func, self, NULL);
960
961 GtkTreeViewColumn *col = gtk_tree_view_column_new();
962 gtk_tree_view_append_column(view, col);
963 gtk_tree_view_set_expander_column(view, col);
964 d->name_col = col;
965
966 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
967 gtk_tree_view_column_pack_start(col, renderer, TRUE);
968 gtk_tree_view_column_add_attribute(col, renderer, "text", DT_MAP_LOCATION_COL_TAG);
969 gtk_tree_view_column_set_cell_data_func(col, renderer, _tree_name_show, (gpointer)self, NULL);
970// g_object_set(renderer, "editable", TRUE, NULL);
971 g_signal_connect(G_OBJECT(renderer), "editing-started", G_CALLBACK(_name_start_editing), self);
972 d->renderer = renderer;
973
974 GtkTreeSelection *selection = gtk_tree_view_get_selection(view);
975 d->selection = selection;
976 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
977 gtk_tree_view_set_model(view, GTK_TREE_MODEL(treestore));
978 g_object_unref(treestore);
979 g_signal_connect(G_OBJECT(view), "button-press-event", G_CALLBACK(_click_on_view), self);
980 g_signal_connect(G_OBJECT(view), "scroll-event", G_CALLBACK(_mouse_scroll), self);
981 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(view));
982 gtk_widget_set_tooltip_text(GTK_WIDGET(view),
983 _("list of user locations,"
984 "\nclick to show or hide a location on the map:"
985 "\n - wheel scroll inside the shape to resize it"
986 "\n - <shift> or <ctrl> scroll to modify the width or the height"
987 "\n - click inside the shape and drag it to change its position"
988 "\n - ctrl-click to move an image from inside the location"
989 "\nctrl-click to edit a location name"
990 "\n - a pipe \'|\' symbol breaks the name into several levels"
991 "\n - to remove a group of locations clear its name"
992 "\n - press enter to validate the new name, escape to cancel the edit"
993 "\nright-click for other actions: delete location and go to collection,"
994 "\nctrl-wheel scroll to resize the window"));
995
996 // buttons
997 GtkBox *hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING));
998
999 int shape = dt_conf_get_int("plugins/map/locationshape");
1000 if(shape == MAP_LOCATION_SHAPE_POLYGONS)
1001 {
1003 dt_conf_set_int("plugins/map/locationshape", shape);
1004 }
1005 d->shape_button = dtgtk_togglebutton_new(location_shapes[shape], 0, NULL);
1006 gtk_box_pack_start(hbox, d->shape_button, FALSE, TRUE, 0);
1007 d->shape_button_handler = g_signal_connect(G_OBJECT(d->shape_button), "clicked",
1008 G_CALLBACK(_shape_button_clicked), self);
1009 gtk_widget_set_tooltip_text(GTK_WIDGET(d->shape_button ),
1010 _("select the shape of the location\'s limits on the map, circle or rectangle"
1011 "\nor even polygon if available (select first a polygon place in 'find location' module)"));
1012
1013 d->new_button = dt_action_button_new(self, N_("new location"), _new_button_clicked, self,
1014 _("add a new location on the center of the visible map"), 0, 0);
1015 gtk_box_pack_start(hbox, d->new_button, TRUE, TRUE, 0);
1016
1017 dt_conf_set_bool("plugins/map/showalllocations", FALSE);
1018 d->show_all_button = gtk_check_button_new_with_label(_("show all"));
1019 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->show_all_button))), PANGO_ELLIPSIZE_END);
1020 gtk_widget_set_tooltip_text(d->show_all_button,
1021 _("show all locations which are on the visible map"));
1022 gtk_box_pack_end(hbox, d->show_all_button, FALSE, FALSE, 8);
1023 g_signal_connect(G_OBJECT(d->show_all_button), "clicked", G_CALLBACK(_show_all_button_clicked), self);
1024
1025 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox), FALSE, TRUE, 0);
1026
1027 _locations_tree_update(self,0);
1028 _display_buttons(self);
1029
1030 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(_selection_changed), self);
1031
1032 // connect geotag changed signal
1034 G_CALLBACK(_view_map_geotag_changed), (gpointer)self);
1035 // connect location changed signal
1037 G_CALLBACK(_view_map_location_changed), (gpointer)self);
1038}
1039
1048
1049// clang-format off
1050// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1051// vim: shiftwidth=2 expandtab tabstop=2 cindent
1052// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1053// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type, uint32_t state)
Definition ashift.c:4508
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_collection_deserialize(const char *buf)
Definition collection.c:763
@ DT_COLLECTION_PROP_GEOTAGGING
Definition collection.h:139
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
void dt_conf_set_bool(const char *name, int val)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_control_log(const char *msg,...)
Definition control.c:824
void leave(dt_view_t *self)
Definition darkroom.c:2072
void reset(dt_view_t *self)
Definition darkroom.c:1222
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:599
GtkWidget * view
the tree view
const char * model
GtkWidget * dt_action_button_new(dt_lib_module_t *self, const gchar *label, gpointer callback, gpointer data, const gchar *tooltip, guint accel_key, GdkModifierType mods)
Definition lib.c:1336
static void _view_map_geotag_changed(gpointer instance, GList *imgs, const int newlocid, dt_lib_module_t *self)
static void _display_buttons(dt_lib_module_t *self)
static void _name_start_editing(GtkCellRenderer *renderer, GtkCellEditable *editable, char *path, dt_lib_module_t *self)
static void _tree_name_show(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
static void _pop_menu_goto_collection(GtkWidget *menuitem, dt_lib_module_t *self)
static void _show_location(dt_lib_module_t *self)
static gboolean _set_location_collection(dt_lib_module_t *self)
const DTGTKCairoPaintIconFunc location_shapes[]
static void _delete_tree_path(GtkTreeModel *model, GtkTreeIter *iter, gboolean root)
static void _pop_menu_edit_location(GtkWidget *menuitem, dt_lib_module_t *self)
static void _new_button_clicked(GtkButton *button, dt_lib_module_t *self)
static gboolean _find_tag_iter_id(GtkTreeModel *model, GtkTreeIter *iter, const guint locid)
static void _signal_location_change(dt_lib_module_t *self)
static gboolean _update_tag_name_per_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, dt_loc_op_t *to)
static void _pop_menu_view(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
static void _show_all_button_clicked(GtkButton *button, dt_lib_module_t *self)
void gui_cleanup(dt_lib_module_t *self)
static void _name_editing_done(GtkCellEditable *editable, dt_lib_module_t *self)
static void _pop_menu_update_filmstrip(GtkWidget *menuitem, dt_lib_module_t *self)
static gboolean _mouse_scroll(GtkWidget *treeview, GdkEventScroll *event, dt_lib_module_t *self)
dt_map_position_name_sort_id
@ DT_MAP_POSITION_SORT_NAME_ID
uint32_t container(dt_lib_module_t *self)
dt_map_positions_cols_t
@ DT_MAP_LOCATION_NUM_COLS
@ DT_MAP_LOCATION_COL_ID
@ DT_MAP_LOCATION_COL_TAG
@ DT_MAP_LOCATION_COL_COUNT
@ DT_MAP_LOCATION_COL_PATH
static gboolean _click_on_view(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
static void _shape_button_clicked(GtkButton *button, dt_lib_module_t *self)
void gui_init(dt_lib_module_t *self)
int position()
static gboolean _force_selection_changed(gpointer user_data)
static void _selection_changed(GtkTreeSelection *selection, dt_lib_module_t *self)
const char ** views(dt_lib_module_t *self)
static void _view_map_location_changed(gpointer instance, GList *polygons, dt_lib_module_t *self)
static void _pop_menu_delete_location(GtkWidget *menuitem, dt_lib_module_t *self)
static gint _sort_position_names_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, dt_lib_module_t *self)
static void _locations_tree_update(dt_lib_module_t *self, const guint locid)
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
@ MAP_LOCATION_ACTION_UPDATE_OTHERS
@ MAP_LOCATION_ACTION_REMOVE
@ MAP_LOCATION_SHAPE_POLYGONS
@ MAP_LOCATION_SHAPE_ELLIPSE
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
GList * dt_map_location_get_locations_by_path(const gchar *path, const gboolean remove_root)
void dt_map_location_update_locations(const int32_t imgid, const GList *tags)
dt_map_location_data_t * dt_map_location_get_data(const guint locid)
void dt_map_location_free_result(GList **result)
void dt_map_location_delete(const guint locid)
void dt_map_location_rename(const guint locid, const char *const name)
GList * dt_map_location_find_locations(const int32_t imgid)
gboolean dt_map_location_name_exists(const char *const name)
int dt_map_location_get_images_count(const guint locid)
guint dt_map_location_new(const char *const name)
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
const char * name
Definition pdf.h:90
void dt_control_signal_unblock_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data)
Definition signal.c:512
void dt_control_signal_block_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data)
Definition signal.c:506
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:386
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_GEOTAG_CHANGED
This signal is raised when a geotag is added/deleted/changed
Definition signal.h:139
@ DT_SIGNAL_LOCATION_CHANGED
Definition signal.h:311
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
GtkCellRenderer * renderer
GtkTreeViewColumn * name_col
GtkTreeSelection * selection
GModule *void * data
Definition lib.h:79
GtkWidget * widget
Definition lib.h:83
GList * dt_sort_tag(GList *tags, gint sort_type)
Definition tags.c:596
void dtgtk_togglebutton_set_paint(GtkDarktableToggleButton *button, DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Telling the user something happened.
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:99
void dt_view_map_location_action(const dt_view_manager_t *vm, const int action)
Definition view.c:1387
void dt_view_map_add_location(const dt_view_manager_t *vm, dt_map_location_data_t *p, const guint posid)
Definition view.c:1381
int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
Definition view.c:234
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
void dtgtk_cairo_paint_masks_circle(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_polygon(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_rect_landscape(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void(* DTGTKCairoPaintIconFunc)(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ DT_UI_CONTAINER_PANEL_RIGHT_CENTER