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 "common/darktable.h"
28#include "common/collection.h"
29#include "common/debug.h"
31#include "control/conf.h"
32#include "control/control.h"
33
34#include "libs/lib.h"
35
36// map position module uses the tag dictionary with dt_geo_tag_root as a prefix.
37// Synonym field is used to store positions coordinates in ascii format.
38
40static void _show_location(dt_lib_module_t *self);
41
42DT_MODULE(1)
43
44const char *name(struct dt_lib_module_t *self)
45{
46 return _("locations");
47}
48
49const char **views(dt_lib_module_t *self)
50{
51 static const char *v[] = {"map", NULL};
52 return v;
53}
54
59
74
75typedef struct dt_loc_op_t
76{
80
82{
83 return 995;
84}
85
94
99
101 dtgtk_cairo_paint_rect_landscape, // MAP_LOCATION_SHAPE_RECTANGLE
102 dtgtk_cairo_paint_polygon}; // MAP_LOCATION_SHAPE_POLYGONS
103
104static gboolean _mouse_scroll(GtkWidget *treeview, GdkEventScroll *event,
105 dt_lib_module_t *self)
106{
108 if (dt_modifier_is(event->state, GDK_CONTROL_MASK))
109 {
110 const gint increment = DT_PIXEL_APPLY_DPI(10.0);
111 const gint min_height = DT_PIXEL_APPLY_DPI(100.0);
112 const gint max_height = DT_PIXEL_APPLY_DPI(500.0);
113 gint width, height;
114 gtk_widget_get_size_request (GTK_WIDGET(d->window), &width, &height);
115 height = height + increment * event->delta_y;
116 height = (height < min_height) ? min_height
117 : (height > max_height) ? max_height : height;
118 gtk_widget_set_size_request(GTK_WIDGET(d->window), -1, (gint)height);
119 dt_conf_set_int("plugins/map/heightlocationwindow", (gint)height);
120 return TRUE;
121 }
122 return FALSE;
123}
124
125// find a tag on the tree
126static gboolean _find_tag_iter_id(GtkTreeModel *model, GtkTreeIter *iter,
127 const guint locid)
128{
129 gboolean found = FALSE;
130 if(locid <= 0) return found;
131 guint id;
132 do
133 {
134 gtk_tree_model_get(model, iter, DT_MAP_LOCATION_COL_ID, &id, -1);
135 found = id == locid;
136 if(found) return found;
137 GtkTreeIter child, parent = *iter;
138 if(gtk_tree_model_iter_children(model, &child, &parent))
139 {
140 found = _find_tag_iter_id(model, &child, locid);
141 if(found)
142 {
143 *iter = child;
144 return found;
145 }
146 }
147 } while(gtk_tree_model_iter_next(model, iter));
148 return found;
149}
150
151static void _locations_tree_update(dt_lib_module_t *self, const guint locid)
152{
155 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
156
157 g_object_ref(model);
158 gtk_tree_view_set_model(GTK_TREE_VIEW(d->view), NULL);
159 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
160 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
161 GTK_SORT_ASCENDING);
162 gtk_tree_store_clear(GTK_TREE_STORE(model));
163 if(tags)
164 {
165 char **last_tokens = NULL;
166 int last_tokens_length = 0;
167 GtkTreeIter last_parent = { 0 };
168 GList *sorted_tags = dt_sort_tag(tags, 0); // ordered by full tag name
169 tags = sorted_tags;
170 for(GList *stag = tags; stag; stag = g_list_next(stag))
171 {
172 GtkTreeIter iter;
173 const gchar *tag = ((dt_map_location_t *)stag->data)->tag;
174 if(IS_NULL_PTR(tag)) continue;
175 char **tokens;
176 tokens = g_strsplit(tag, "|", -1);
177 if(tokens)
178 {
179 // find the number of common parts at the beginning of tokens and last_tokens
180 GtkTreeIter parent = last_parent;
181 const int tokens_length = g_strv_length(tokens);
182 int common_length = 0;
183 if(last_tokens)
184 {
185 while(tokens[common_length] && last_tokens[common_length] &&
186 !g_strcmp0(tokens[common_length], last_tokens[common_length]))
187 {
188 common_length++;
189 }
190
191 // point parent iter to where the entries should be added
192 for(int i = common_length; i < last_tokens_length; i++)
193 {
194 gtk_tree_model_iter_parent(GTK_TREE_MODEL(model), &parent, &last_parent);
195 last_parent = parent;
196 }
197 }
198
199 // insert everything from tokens past the common part
200 char *pth = NULL;
201 for(int i = 0; i < common_length; i++)
202 pth = dt_util_dstrcat(pth, "%s|", tokens[i]);
203
204 for(char **token = &tokens[common_length]; *token; token++)
205 {
206 pth = dt_util_dstrcat(pth, "%s|", *token);
207 gchar *pth2 = g_strdup(pth);
208 pth2[strlen(pth2) - 1] = '\0';
209 gtk_tree_store_insert(GTK_TREE_STORE(model), &iter, common_length > 0 ? &parent : NULL, -1);
210 gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
212 DT_MAP_LOCATION_COL_ID, (token == &tokens[tokens_length-1]) ?
213 ((dt_map_location_t *)stag->data)->id : 0,
215 DT_MAP_LOCATION_COL_COUNT, (token == &tokens[tokens_length-1]) ?
216 ((dt_map_location_t *)stag->data)->count : 0,
217 -1);
218 common_length++;
219 parent = iter;
220 dt_free(pth2);
221 }
222 dt_free(pth);
223
224 // remember things for the next round
225 if(last_tokens) g_strfreev(last_tokens);
226 last_tokens = tokens;
227 last_parent = parent;
228 last_tokens_length = tokens_length;
229 }
230 }
231 g_strfreev(last_tokens);
233 }
234 gtk_tree_view_set_model(GTK_TREE_VIEW(d->view), model);
235 g_object_unref(model);
236 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
238 GTK_SORT_ASCENDING);
239 if(locid)
240 {
241 // try to select the right record
242 GtkTreeIter iter;
243 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
244 if(gtk_tree_model_get_iter_first(model, &iter))
245 {
246 if(_find_tag_iter_id(model, &iter, locid))
247 {
248 gtk_tree_selection_select_iter(selection, &iter);
249 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
250 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(d->view), path);
251 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(d->view), path, NULL, TRUE, 0.5, 0.5);
252 gtk_tree_view_set_cursor(GTK_TREE_VIEW(d->view), path, d->name_col, FALSE);
253 gtk_tree_path_free(path);
254 }
255 }
256 }
257}
258
260{
262 GtkTreeIter iter;
263 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
264 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
265 if(gtk_tree_selection_get_selected(selection, &model, &iter))
266 {
267 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->new_button))), _("new sub-location"));
268 }
269 else
270 {
271 gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->new_button))), _("new location"));
272 }
273}
274
275static void _tree_name_show(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
276 GtkTreeModel *model, GtkTreeIter *iter,
277 gpointer data)
278{
279 guint locid;
280 gchar *name;
281 gchar *path;
282 guint count;
283 gchar *coltext;
284 gtk_tree_model_get(model, iter,
288 DT_MAP_LOCATION_COL_PATH, &path, -1);
289 if (count < 1)
290 {
291 coltext = g_markup_printf_escaped(locid ? "%s" : "<i>%s</i>", name);
292 }
293 else
294 {
295 coltext = g_markup_printf_escaped(locid ? "%s (%d)" : "<i>%s</i> (%d)", name, count);
296 }
297 g_object_set(renderer, "markup", coltext, NULL);
298 dt_free(coltext);
299 dt_free(name);
300 dt_free(path);
301}
302
303static void _new_button_clicked(GtkButton *button, dt_lib_module_t *self)
304{
306 GtkTreeIter iter, parent;
307 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
308 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
309 char *name = NULL;
310 char *path = NULL;
311 if(gtk_tree_selection_get_selected(selection, &model, &iter))
312 {
313 gtk_tree_model_get(model, &iter, DT_MAP_LOCATION_COL_PATH, &path, -1);
314 parent = iter;
315 }
316 name = path ? g_strconcat(path, "|", NULL) : g_strdup("");
317 const int base_len = strlen(name);
318 int i = 1;
319 name = dt_util_dstrcat(name, "%s", _("new location"));
320 char *new_name = g_strdup(name);
321 while(dt_map_location_name_exists(new_name))
322 {
323 dt_free(new_name);
324 new_name = g_strdup_printf("%s %d", name, i);
325 i++;
326 }
327
328 // add the new record to the tree
329 gtk_tree_store_insert(GTK_TREE_STORE(model), &iter, path ? &parent : NULL, -1);
330 gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
331 DT_MAP_LOCATION_COL_TAG, &new_name[base_len],
333 DT_MAP_LOCATION_COL_PATH, new_name,
335 -1);
336 dt_free(new_name);
337 dt_free(name);
338 dt_free(path);
339
340 // set the new record editable
341 g_object_set(G_OBJECT(d->renderer), "editable", TRUE, NULL);
342 GtkTreePath *path2 = gtk_tree_model_get_path(model, &iter);
343 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(d->view), path2);
344 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(d->view), path2, NULL, TRUE, 0.5, 0.5);
345 gtk_tree_view_set_cursor(GTK_TREE_VIEW(d->view), path2, d->name_col, TRUE);
346 gtk_tree_path_free(path2);
347}
348
349static void _shape_button_clicked(GtkButton *button, dt_lib_module_t *self)
350{
352 int shape = dt_conf_get_int("plugins/map/locationshape");
353 shape++;
354 if((shape > G_N_ELEMENTS(location_shapes) - 1) ||
355 (IS_NULL_PTR(d->polygons) && shape == MAP_LOCATION_SHAPE_POLYGONS))
356 shape = 0;
357 dt_conf_set_int("plugins/map/locationshape", shape);
358
359 g_signal_handler_block (d->shape_button, d->shape_button_handler);
360 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->shape_button), FALSE);
362 g_signal_handler_unblock (d->shape_button, d->shape_button_handler);
363}
364
365static void _show_all_button_clicked(GtkButton *button, dt_lib_module_t *self)
366{
368 dt_conf_set_bool("plugins/map/showalllocations",
369 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->show_all_button)));
370 dt_view_map_location_action(darktable.view_manager, MAP_LOCATION_ACTION_UPDATE_OTHERS);
371}
372
373// delete a path of the tag tree
374static void _delete_tree_path(GtkTreeModel *model, GtkTreeIter *iter, gboolean root)
375{
376 GtkTreeIter child, parent = *iter;
377 gboolean valid = TRUE;
378 do
379 {
380 if(gtk_tree_model_iter_children(model, &child, &parent))
381 _delete_tree_path(model, &child, FALSE);
382 GtkTreeIter tobedel = parent;
383 valid = gtk_tree_model_iter_next(model, &parent);
384 char *path = NULL;
385 gtk_tree_model_get(model, &tobedel, DT_MAP_LOCATION_COL_PATH, &path, -1);
386 dt_free(path);
387 gtk_tree_store_remove(GTK_TREE_STORE(model), &tobedel);
388 } while (!root && valid);
389}
390
391static gboolean _update_tag_name_per_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, dt_loc_op_t *to)
392{
393 char *tagname;
394 char *newtagname = to->newtagname;
395 char *oldtagname = to->oldtagname;
396 gtk_tree_model_get(model, iter, DT_MAP_LOCATION_COL_PATH, &tagname, -1);
397 if (g_str_has_prefix(tagname, oldtagname))
398 {
399 if (strlen(tagname) == strlen(oldtagname))
400 {
401 // rename the tag itself
402 char *subtag = g_strrstr(to->newtagname, "|");
403 subtag = (!subtag) ? newtagname : subtag + 1;
404 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
405 DT_MAP_LOCATION_COL_PATH, newtagname,
406 DT_MAP_LOCATION_COL_TAG, subtag, -1);
407 }
408 else if (strlen(tagname) > strlen(oldtagname) && tagname[strlen(oldtagname)] == '|')
409 {
410 // rename similar path
411 char *newpath = g_strconcat(newtagname, &tagname[strlen(oldtagname)] , NULL);
412 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
413 DT_MAP_LOCATION_COL_PATH, newpath, -1);
414 dt_free(newpath);
415 }
416 }
417 dt_free(tagname);
418 return FALSE;
419}
420
421static void _view_map_geotag_changed(gpointer instance, GList *imgs, const int newlocid, dt_lib_module_t *self)
422{
424
425 // one of the other location has been clicked on the map
426 if(newlocid)
427 {
428 GtkTreeIter iter;
429 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
430 if(gtk_tree_model_get_iter_first(model, &iter))
431 {
432 if(_find_tag_iter_id(model, &iter, newlocid))
433 {
434 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
435 gtk_tree_selection_select_iter(selection, &iter);
436 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
437 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(d->view), path);
438 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(d->view), path, NULL, TRUE, 0.5, 0.5);
439 gtk_tree_view_set_cursor(GTK_TREE_VIEW(d->view), path, d->name_col, FALSE);
440 gtk_tree_path_free(path);
441 _show_location(self);
442 _display_buttons(self);
443 }
444 }
445 }
446 else
447 {
448 for(GList* img = imgs; img; img = g_list_next(img))
449 {
450 // find new locations for that image
451 GList *tags = dt_map_location_find_locations(GPOINTER_TO_INT(img->data));
452 // update locations for that image
453 dt_map_location_update_locations(GPOINTER_TO_INT(img->data), tags);
454 g_list_free(tags);
455 tags = NULL;
456 }
457 // update count on the treeview
459 GtkTreeIter iter;
460 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
461 if(gtk_tree_model_get_iter_first(model, &iter))
462 {
463 for(GList *loc = locs; loc; loc = g_list_next(loc))
464 {
465 const guint locid = ((dt_map_location_t *)loc->data)->id;
466 GtkTreeIter iter2 = iter;
467 if(_find_tag_iter_id(model, &iter2, locid))
468 {
469 gtk_tree_store_set(GTK_TREE_STORE(model), &iter2,
470 DT_MAP_LOCATION_COL_COUNT, ((dt_map_location_t *)loc->data)->count,
471 -1);
472 }
473 }
474 }
476 }
477}
478
479static void _view_map_location_changed(gpointer instance, GList *polygons, dt_lib_module_t *self)
480{
482 const int shape = dt_conf_get_int("plugins/map/locationshape");
483 if((shape == MAP_LOCATION_SHAPE_POLYGONS) && IS_NULL_PTR(polygons))
484 {
485 g_signal_handler_block (d->shape_button, d->shape_button_handler);
488 g_signal_handler_unblock (d->shape_button, d->shape_button_handler);
489 dt_conf_set_int("plugins/map/locationshape", MAP_LOCATION_SHAPE_ELLIPSE);
490 }
491 d->polygons = polygons;
492}
493
500
501static void _name_editing_done(GtkCellEditable *editable, dt_lib_module_t *self)
502{
504 gboolean canceled = TRUE;
505 g_object_get(editable, "editing-canceled", &canceled, NULL);
506 const gchar *name = gtk_entry_get_text(GTK_ENTRY(editable));
507 const gboolean reset = name[0] ? FALSE : TRUE;
508 GtkTreeIter iter;
509 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
510 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
511 if(gtk_tree_selection_get_selected(selection, &model, &iter))
512 {
513 char *path = NULL;
514 char *leave = NULL;
515 guint locid;
516 gtk_tree_model_get(model, &iter, DT_MAP_LOCATION_COL_ID, &locid,
519 if(reset && locid)
520 canceled = TRUE; // empty name for a location is not allowed
521 if(!canceled)
522 {
523 const gboolean isroot = !strcmp(path, leave);
524 const int path_len = strlen(path);
525 char *new_path;
526 if(!isroot)
527 {
528 const int leave_len = strlen(leave);
529 const char letter = path[path_len - leave_len];
530 path[path_len - leave_len] = '\0';
531 new_path = g_strconcat(path, name, NULL);
532 path[path_len - leave_len] = letter;
533 }
534 else
535 new_path = g_strdup((char *)name);
536
537 gboolean new_exists = FALSE;
538 GList *new_existing = NULL;
539 if(!reset) // in case of reset we rely on dt_tag_rename to be safe
540 new_existing = dt_map_location_get_locations_by_path(new_path, FALSE);
541 if(new_existing)
542 {
543 dt_map_location_free_result(&new_existing);
544 new_exists = TRUE;
545 }
546 if(!new_exists)
547 {
548 // new name is free, we can work
549 if(locid == -1)
550 {
551 // new location - create and show it
552 locid = dt_map_location_new(new_path);
553 if(locid != -1)
554 {
555 // add the location on the map
557 g.shape = dt_conf_get_int("plugins/map/locationshape");
558 g.lon = g.lat = NAN;
559 g.delta1 = g.delta2 = 0.0;
560 g.polygons = d->polygons;
561 dt_view_map_add_location(darktable.view_manager, &g, locid);
562 const int count = dt_map_location_get_images_count(locid);
563 if(g_strstr_len(name, -1, "|"))
564 {
565 // the user wants to insert some group(s). difficult to handle the tree => reset
566 _locations_tree_update(self, locid);
567 }
568 else
569 {
570 gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
572 DT_MAP_LOCATION_COL_PATH, new_path,
575 -1);
576 }
577 }
578 else canceled = TRUE;
579 }
580 else
581 {
582 // existing location - rename it
583 GList *children = dt_map_location_get_locations_by_path(path, FALSE);
584 for (GList *tag = children; tag; tag = g_list_next(tag))
585 {
586 // reset on leave is not possible. should be safe
587 const char *new_part = &((dt_map_location_t *)tag->data)->tag[path_len + (reset ? 1 :0)];
588 char *new_name = g_strconcat(new_path, new_part, NULL);
589 dt_map_location_rename(((dt_map_location_t *)tag->data)->id, new_name);
590 dt_free(new_name);
591 }
593
594 // update the store
595 if(reset || g_strstr_len(name, -1, "|"))
596 {
597 // the user wants to insert some group(s). difficult to handle the tree => reset
598 _locations_tree_update(self, locid);
599 }
600 else
601 {
602 dt_loc_op_t to;
603 to.oldtagname = path;
604 to.newtagname = new_path;
605 gint sort_column;
606 GtkSortType sort_order;
607 gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(model),
608 &sort_column, &sort_order);
609 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
610 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
611 GTK_SORT_ASCENDING);
612 gtk_tree_model_foreach(model, (GtkTreeModelForeachFunc)_update_tag_name_per_name, &to);
613 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
614 sort_column, sort_order);
615 }
617 }
618 }
619 else
620 {
621 dt_control_log(_("location name \'%s\' already exists"), new_path);
622 canceled = TRUE;
623 }
624 dt_free(new_path);
625 }
626 if(canceled)
627 {
628 if(locid == -1)
629 {
630 // if new we have to remove the new location from tree
632 gtk_tree_selection_unselect_all(selection);
633 }
634 }
635 dt_free(path);
636 dt_free(leave);
637 }
638 g_object_set(G_OBJECT(d->renderer), "editable", FALSE, NULL);
639 _display_buttons(self);
640}
641
642static void _name_start_editing(GtkCellRenderer *renderer, GtkCellEditable *editable,
643 char *path, dt_lib_module_t *self)
644{
646 if (GTK_IS_ENTRY(editable))
647 {
648 // set up the editable with name (without number)
649 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
650 GtkTreeIter iter;
651 GtkTreePath *new_path = gtk_tree_path_new_from_string(path);
652 if(gtk_tree_model_get_iter(model, &iter, new_path))
653 {
654 char *name = NULL;
655 gtk_tree_model_get(model, &iter, DT_MAP_LOCATION_COL_TAG, &name, -1);
656 gtk_entry_set_text(GTK_ENTRY(editable), name);
657 dt_free(name);
658 }
659 gtk_tree_path_free(new_path);
660
661 g_signal_connect(G_OBJECT(editable), "editing-done", G_CALLBACK(_name_editing_done), self);
662 }
663}
664
665static gint _sort_position_names_func(GtkTreeModel *model,
666 GtkTreeIter *a, GtkTreeIter *b,
667 dt_lib_module_t *self)
668{
669 char *tag_a = NULL;
670 char *tag_b = NULL;
671 gtk_tree_model_get(model, a, DT_MAP_LOCATION_COL_PATH, &tag_a, -1);
672 gtk_tree_model_get(model, b, DT_MAP_LOCATION_COL_PATH, &tag_b, -1);
673 if(IS_NULL_PTR(tag_a)) tag_a = g_strdup("");
674 if(IS_NULL_PTR(tag_b)) tag_b = g_strdup("");
675 const gboolean sort = g_ascii_strncasecmp(tag_a, tag_b, -1);
676 dt_free(tag_a);
677 dt_free(tag_b);
678 return sort;
679}
680
682{
684 GtkTreeIter iter;
685 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
686 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
687 if(gtk_tree_selection_get_selected(selection, &model, &iter))
688 {
689 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
690 g_object_set(G_OBJECT(d->renderer), "editable", TRUE, NULL);
691 gtk_tree_view_set_cursor(GTK_TREE_VIEW(d->view), path, d->name_col, TRUE);
692 gtk_tree_path_free(path);
693 _display_buttons(self);
694 }
695}
696
698{
700 GtkTreeIter iter;
701 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
702 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
703 if(gtk_tree_selection_get_selected(selection, &model, &iter))
704 {
705 guint locid = 0;
706 gtk_tree_model_get(model, &iter, DT_MAP_LOCATION_COL_ID, &locid, -1);
707 if(locid > 0)
708 {
709 dt_view_map_location_action(darktable.view_manager, MAP_LOCATION_ACTION_REMOVE);
712 }
713 // update the treeview
714 GtkTreeIter parent;
715 if(gtk_tree_model_iter_parent(model, &parent, &iter))
716 {
717 guint parentid;
718 gtk_tree_model_get(model, &parent, DT_MAP_LOCATION_COL_ID, &parentid, -1);
719 if(parentid > 0)
720 {
722 gtk_tree_selection_unselect_all(selection);
723 }
724 else
725 {
726 // parent is a node (not a location). reset treeview
727 _locations_tree_update(self, 0);
728 }
729 }
730 else
731 {
733 gtk_tree_selection_unselect_all(selection);
734 }
735 }
736 _display_buttons(self);
737}
738
740{
742 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
743 GtkTreeIter iter;
744 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
745 if(gtk_tree_selection_get_selected(selection, &model, &iter))
746 {
747 guint locid;
748 gtk_tree_model_get(model, &iter,
749 DT_MAP_LOCATION_COL_ID, &locid, -1);
750 if(locid)
751 {
753 dt_view_map_add_location(darktable.view_manager, p, locid);
754 dt_free(p);
755 }
756 else
757 {
758 // this is not a location (only a parent). remove location from map if any
759 dt_view_map_location_action(darktable.view_manager, MAP_LOCATION_ACTION_REMOVE);
760 }
761 }
762}
763
765{
767 GtkTreeIter iter;
768 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
769 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->view));
770 if(gtk_tree_selection_get_selected(selection, &model, &iter))
771 {
772 char *name;
773 gtk_tree_model_get(model, &iter, DT_MAP_LOCATION_COL_PATH, &name, -1);
774 char *collection = g_strdup_printf("1:0:%d:%s|%s$",
776 _("tagged"), name);
777 dt_collection_deserialize(collection);
778 dt_free(collection);
779 dt_free(name);
780 return TRUE;
781 }
782 return FALSE;
783}
784
786{
788}
789
791{
794}
795
796static void _pop_menu_view(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
797{
798 GtkWidget *menu, *menuitem;
799 menu = gtk_menu_new();
800
801 GtkTreeIter iter;
802 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
803 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
804
805 if(gtk_tree_selection_get_selected(selection, &model, &iter))
806 {
807 guint locid = 0;
808 gtk_tree_model_get(model, &iter, DT_MAP_LOCATION_COL_ID, &locid, -1);
809 GtkTreeIter child, parent = iter;
810 const gboolean children = gtk_tree_model_iter_children(model, &child, &parent);
811
812 menuitem = gtk_menu_item_new_with_label(_("edit location"));
813 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_edit_location, self);
814 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
815 menuitem = gtk_menu_item_new_with_label(_("delete location"));
816 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_delete_location, self);
817 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
818 if(children)
819 {
820 gtk_widget_set_sensitive(menuitem, FALSE);
821 }
822
823 menuitem = gtk_separator_menu_item_new();
824 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
825 menuitem = gtk_menu_item_new_with_label(_("update filmstrip"));
826 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
827 if(!locid)
828 {
829 gtk_widget_set_sensitive(menuitem, FALSE);
830 }
831 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_update_filmstrip, self);
832 menuitem = gtk_menu_item_new_with_label(_("go to collection (lighttable)"));
833 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
834 g_signal_connect(menuitem, "activate", (GCallback)_pop_menu_goto_collection, self);
835 if(!locid)
836 {
837 gtk_widget_set_sensitive(menuitem, FALSE);
838 }
839 }
840
841 gtk_widget_show_all(GTK_WIDGET(menu));
842
843 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
844}
845
846static gboolean _force_selection_changed(gpointer user_data)
847{
848 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
850 gtk_tree_selection_unselect_all(d->selection);
851 return FALSE;
852}
853
854static void _selection_changed(GtkTreeSelection *selection, dt_lib_module_t *self)
855{
857 GtkTreeIter iter;
858 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->view));
859 if(gtk_tree_selection_get_selected(selection, &model, &iter))
860 {
861 _show_location(self);
862 }
863 else
864 {
865 dt_view_map_location_action(darktable.view_manager, MAP_LOCATION_ACTION_REMOVE);
866 }
867 _display_buttons(self);
868}
869
870static gboolean _click_on_view(GtkWidget *view, GdkEventButton *event, dt_lib_module_t *self)
871{
873
874 gboolean editing;
875 g_object_get(G_OBJECT(d->renderer), "editing", &editing, NULL);
876 if(editing)
877 {
878 dt_control_log(_("terminate edit (press enter or escape) before selecting another location"));
879 return TRUE;
880 }
881
882 const int button_pressed = (event->type == GDK_BUTTON_PRESS) ? event->button : 0;
883 const gboolean ctrl_pressed = dt_modifier_is(event->state, GDK_CONTROL_MASK);
884 if((button_pressed == 3)
885 || (button_pressed == 1 && !ctrl_pressed)
886 || (button_pressed == 1 && ctrl_pressed)
887 )
888 {
889 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
890 GtkTreePath *path = NULL;
891 // Get tree path for row that was clicked
892 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), (gint)event->x,
893 (gint)event->y, &path, NULL, NULL, NULL))
894 {
895 if(button_pressed == 3)
896 {
897 gtk_tree_selection_select_path(selection, path);
898 _pop_menu_view(view, event, self);
899 gtk_tree_path_free(path);
900 _display_buttons(self);
901 return TRUE;
902 }
903 else if(button_pressed == 1 && !ctrl_pressed)
904 {
905 if(gtk_tree_selection_path_is_selected(selection, path))
906 g_timeout_add(100, _force_selection_changed, self);
907 gtk_tree_path_free(path);
908 return FALSE;
909 }
910 else if(button_pressed == 1 && ctrl_pressed)
911 {
912 gtk_tree_selection_select_path(selection, path);
913 g_object_set(G_OBJECT(d->renderer), "editable", TRUE, NULL);
914 gtk_tree_view_set_cursor(GTK_TREE_VIEW(d->view), path, d->name_col, TRUE);
915 gtk_tree_path_free(path);
916 _display_buttons(self);
917 return TRUE;
918 }
919 }
920 else
921 {
922 g_timeout_add(10, _force_selection_changed, self);
923 return FALSE;
924 }
925 }
926 return FALSE;
927}
928
930{
932 self->data = d;
933
934 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
935
936 GtkWidget *w = gtk_scrolled_window_new(NULL, NULL);
937 d->window = w;
938 int height = dt_conf_get_int("plugins/map/heightlocationwindow");
939 gtk_widget_set_size_request(w, -1, DT_PIXEL_APPLY_DPI(height ? height : 100));
940 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
941 dt_gui_add_class(w, "dt_recessed_scroll");
942 gtk_box_pack_start(GTK_BOX(self->widget), w, TRUE, TRUE, 0);
943 GtkTreeView *view = GTK_TREE_VIEW(gtk_tree_view_new());
944 d->view = GTK_WIDGET(view);
945 gtk_tree_view_set_headers_visible(view, FALSE);
946 GtkTreeStore *treestore = gtk_tree_store_new(DT_MAP_LOCATION_NUM_COLS, G_TYPE_UINT,
947 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);
948 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treestore), DT_MAP_POSITION_SORT_NAME_ID,
949 (GtkTreeIterCompareFunc)_sort_position_names_func, self, NULL);
950
951 GtkTreeViewColumn *col = gtk_tree_view_column_new();
952 gtk_tree_view_append_column(view, col);
953 gtk_tree_view_set_expander_column(view, col);
954 d->name_col = col;
955
956 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
957 gtk_tree_view_column_pack_start(col, renderer, TRUE);
958 gtk_tree_view_column_add_attribute(col, renderer, "text", DT_MAP_LOCATION_COL_TAG);
959 gtk_tree_view_column_set_cell_data_func(col, renderer, _tree_name_show, (gpointer)self, NULL);
960// g_object_set(renderer, "editable", TRUE, NULL);
961 g_signal_connect(G_OBJECT(renderer), "editing-started", G_CALLBACK(_name_start_editing), self);
962 d->renderer = renderer;
963
964 GtkTreeSelection *selection = gtk_tree_view_get_selection(view);
965 d->selection = selection;
966 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
967 gtk_tree_view_set_model(view, GTK_TREE_MODEL(treestore));
968 g_object_unref(treestore);
969 g_signal_connect(G_OBJECT(view), "button-press-event", G_CALLBACK(_click_on_view), self);
970 g_signal_connect(G_OBJECT(view), "scroll-event", G_CALLBACK(_mouse_scroll), self);
971 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(view));
972 gtk_widget_set_tooltip_text(GTK_WIDGET(view),
973 _("list of user locations,"
974 "\nclick to show or hide a location on the map:"
975 "\n - wheel scroll inside the shape to resize it"
976 "\n - <shift> or <ctrl> scroll to modify the width or the height"
977 "\n - click inside the shape and drag it to change its position"
978 "\n - ctrl-click to move an image from inside the location"
979 "\nctrl-click to edit a location name"
980 "\n - a pipe \'|\' symbol breaks the name into several levels"
981 "\n - to remove a group of locations clear its name"
982 "\n - press enter to validate the new name, escape to cancel the edit"
983 "\nright-click for other actions: delete location and go to collection,"
984 "\nctrl-wheel scroll to resize the window"));
985
986 // buttons
987 GtkBox *hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING));
988
989 int shape = dt_conf_get_int("plugins/map/locationshape");
990 if(shape == MAP_LOCATION_SHAPE_POLYGONS)
991 {
993 dt_conf_set_int("plugins/map/locationshape", shape);
994 }
995 d->shape_button = dtgtk_togglebutton_new(location_shapes[shape], 0, NULL);
996 gtk_box_pack_start(hbox, d->shape_button, FALSE, TRUE, 0);
997 d->shape_button_handler = g_signal_connect(G_OBJECT(d->shape_button), "clicked",
998 G_CALLBACK(_shape_button_clicked), self);
999 gtk_widget_set_tooltip_text(GTK_WIDGET(d->shape_button ),
1000 _("select the shape of the location\'s limits on the map, circle or rectangle"
1001 "\nor even polygon if available (select first a polygon place in 'find location' module)"));
1002
1003 d->new_button = dt_action_button_new(self, N_("new location"), _new_button_clicked, self,
1004 _("add a new location on the center of the visible map"), 0, 0);
1005 gtk_box_pack_start(hbox, d->new_button, TRUE, TRUE, 0);
1006
1007 dt_conf_set_bool("plugins/map/showalllocations", FALSE);
1008 d->show_all_button = gtk_check_button_new_with_label(_("show all"));
1009 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->show_all_button))), PANGO_ELLIPSIZE_END);
1010 gtk_widget_set_tooltip_text(d->show_all_button,
1011 _("show all locations which are on the visible map"));
1012 gtk_box_pack_end(hbox, d->show_all_button, FALSE, FALSE, 8);
1013 g_signal_connect(G_OBJECT(d->show_all_button), "clicked", G_CALLBACK(_show_all_button_clicked), self);
1014
1015 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox), FALSE, TRUE, 0);
1016
1017 _locations_tree_update(self,0);
1018 _display_buttons(self);
1019
1020 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(_selection_changed), self);
1021
1022 // connect geotag changed signal
1024 G_CALLBACK(_view_map_geotag_changed), (gpointer)self);
1025 // connect location changed signal
1027 G_CALLBACK(_view_map_location_changed), (gpointer)self);
1028}
1029
1038
1039// clang-format off
1040// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1041// vim: shiftwidth=2 expandtab tabstop=2 cindent
1042// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1043// clang-format on
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:4460
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
void dt_collection_deserialize(const char *buf)
@ DT_COLLECTION_PROP_GEOTAGGING
Definition collection.h:126
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)
char * name
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)
void dt_control_log(const char *msg,...)
Definition control.c:761
void leave(dt_view_t *self)
Definition darkroom.c:2644
void reset(dt_view_t *self)
Definition darkroom.c:1266
uint32_t view(const dt_view_t *self)
Definition darkroom.c:227
darktable_t darktable
Definition darktable.c:181
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:456
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#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 darktable.h:281
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)
Definition dtgtk/paint.h:75
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
const char * model
const float v
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:1563
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)
@ MAP_LOCATION_ACTION_UPDATE_OTHERS
@ MAP_LOCATION_ACTION_REMOVE
@ MAP_LOCATION_SHAPE_POLYGONS
@ MAP_LOCATION_SHAPE_ELLIPSE
void dt_control_signal_unblock_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data)
Definition signal.c:481
void dt_control_signal_block_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data)
Definition signal.c:476
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_GEOTAG_CHANGED
This signal is raised when a geotag is added/deleted/changed
Definition signal.h:136
@ DT_SIGNAL_LOCATION_CHANGED
Definition signal.h:299
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_view_manager_t * view_manager
Definition darktable.h:772
GtkCellRenderer * renderer
GtkTreeViewColumn * name_col
GtkTreeSelection * selection
GModule *void * data
Definition lib.h:80
GtkWidget * widget
Definition lib.h:84
GList * dt_sort_tag(GList *tags, gint sort_type)
Definition tags.c:823
void dtgtk_togglebutton_set_paint(GtkDarktableToggleButton *button, DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:95
int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
Definition view.c:235
@ DT_UI_CONTAINER_PANEL_RIGHT_CENTER