152#include <glib/gi18n.h>
153#include <glib/gstdio.h>
158#include <gio/gunixmounts.h>
160#ifdef GDK_WINDOWING_QUARTZ
167#define PARAM_STRING_SIZE 256
282static void tag_changed(gpointer instance, gpointer self);
329static const char *
const OP_TOKENS[] = {
"",
"<",
"<=",
">",
">=",
"<>" };
330static const char *
const OP_LABELS[] = {
"=",
"<",
"≤",
">",
"≥",
"≠" };
331#define COLLECT_N_OPS ((int)G_N_ELEMENTS(OP_TOKENS))
337 *
value = text ? text :
"";
339 if(g_str_has_prefix(text,
"<="))
344 else if(g_str_has_prefix(text,
">="))
349 else if(g_str_has_prefix(text,
"<>"))
354 else if(g_str_has_prefix(text,
"<"))
359 else if(g_str_has_prefix(text,
">"))
364 else if(g_str_has_prefix(text,
"="))
369 while(**
value ==
' ') (*value)++;
383 static const char *
v[] = {
"lighttable",
"map",
"print", NULL };
398 const int old_version,
int *new_version,
size_t *new_size)
400 if(old_version == 1 || old_version == 2)
413#define CLEAR_PARAMS(r) \
415 memset(¶ms, 0, sizeof(params)); \
417 params.rule[0].mode = 0; \
418 params.rule[0].item = r; \
421 GDateTime *now = g_date_time_new_now_local();
422 char *datetime_today = g_date_time_format(now,
"%Y:%m:%d");
423 GDateTime *gdt = g_date_time_add_days(now, -1);
424 char *datetime_24hrs = g_date_time_format(gdt,
"> %Y:%m:%d %H:%M");
425 g_date_time_unref(gdt);
426 gdt = g_date_time_add_days(now, -30);
427 char *datetime_30d = g_date_time_format(gdt,
"> %Y:%m:%d");
428 g_date_time_unref(gdt);
429 g_date_time_unref(now);
479 snprintf(
k,
sizeof(
k),
"plugins/lighttable/collect/item%1d",
n);
486 snprintf(
k,
sizeof(
k),
"plugins/lighttable/collect/item%1d",
n);
493 snprintf(
k,
sizeof(
k),
"plugins/lighttable/collect/mode%1d",
n);
500 snprintf(
k,
sizeof(
k),
"plugins/lighttable/collect/mode%1d",
n);
507 snprintf(
k,
sizeof(
k),
"plugins/lighttable/collect/recursive%1d",
n);
514 snprintf(
k,
sizeof(
k),
"plugins/lighttable/collect/recursive%1d",
n);
521 snprintf(
k,
sizeof(
k),
"plugins/lighttable/collect/string%1d",
n);
528 s[strlen(s) - 1] =
'\0';
538 snprintf(
k,
sizeof(
k),
"plugins/lighttable/collect/string%1d",
n);
547 const char *val = gtk_entry_get_text(GTK_ENTRY(dr->
text));
552 s = g_strconcat(
OP_TOKENS[idx], val, NULL);
564 const gboolean folders_visible =
d->folders_controls && gtk_widget_get_visible(
d->folders_controls);
577 g_signal_handlers_block_matched(dr->
text, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
entry_changed, NULL);
578 g_signal_handlers_block_matched(dr->
op_combo, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
_op_changed, NULL);
585 gtk_combo_box_set_active(GTK_COMBO_BOX(dr->
op_combo), idx);
586 gtk_entry_set_text(GTK_ENTRY(dr->
text), val);
590 gtk_entry_set_text(GTK_ENTRY(dr->
text), text);
593 gtk_editable_set_position(GTK_EDITABLE(dr->
text), -1);
596 g_signal_handlers_unblock_matched(dr->
op_combo, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
_op_changed, NULL);
597 g_signal_handlers_unblock_matched(dr->
text, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
entry_changed, NULL);
617 d->has_last_folders =
TRUE;
620 d->has_last_folders =
FALSE;
642 return d->rule +
d->active_rule;
648 d->active_rule = CLAMP(
d->active_rule, 0,
d->nb_rules - 1);
657 for(
int i = 0;
i <
n;
i++)
665 && !g_str_has_suffix(s,
"*") && !g_str_has_suffix(s,
"%"))
667 gchar *s2 = g_strconcat(s,
"*", NULL);
681 void *
p = malloc(*
size);
689 for(uint32_t
i = 0;
i <
p->rules;
i++)
736#define ADD_COLLECT_ENTRY(value) \
737 dt_bauhaus_combobox_add_full(w, dt_collection_name(value), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, \
738 GUINT_TO_POINTER(value + 1), NULL, TRUE)
749 gchar *setting = g_strdup_printf(
"plugins/lighttable/metadata/%s_flag", name_);
778#undef ADD_COLLECT_ENTRY
788 for(; *list; list++) length++;
798 char **tokens = g_strsplit(path, G_DIR_SEPARATOR_S, -1);
801 if(!(g_ascii_isalpha(tokens[0][0]) && tokens[0][strlen(tokens[0]) - 1] ==
':'))
808 const unsigned int size = g_strv_length(tokens);
809 result = malloc(
sizeof(
char *) *
size);
810 for(
unsigned int i = 0;
i <
size;
i++) result[
i] = tokens[
i + 1];
843 gchar *folded = g_utf8_casefold(tag, -1);
844 const size_t len = strlen(folded);
845 char *result = g_malloc(len + 2);
846 if(!g_strcmp0(tag, _(
"not tagged")))
848 else if(g_str_has_prefix(tag,
"darktable|"))
852 memcpy(result + 1, folded, len + 1);
853 for(
char *iter = result + 1; *iter; iter++)
854 if(*iter ==
'|') *iter =
'\1';
860 GtkTreeIter *iter, gpointer data)
866 g_object_set(renderer,
"text",
name, NULL);
869 gchar *coltext = g_strdup_printf(
"%s (%d)",
name, count);
870 g_object_set(renderer,
"text", coltext, NULL);
881 gboolean visible =
FALSE;
882 gboolean was_visible;
885 gchar *haystack = g_utf8_strdown(str, -1);
893 gchar *
operator, * number, *number2;
897 const float nb1 = g_strtod(number, NULL);
898 const float nb2 = g_strtod(haystack, NULL);
899 if(
operator&& strcmp(
operator,
">") == 0)
900 visible = (nb2 > nb1);
901 else if(
operator&& strcmp(
operator,
">=") == 0)
902 visible = (nb2 >= nb1);
903 else if(
operator&& strcmp(
operator,
"<") == 0)
904 visible = (nb2 < nb1);
905 else if(
operator&& strcmp(
operator,
"<=") == 0)
906 visible = (nb2 <= nb1);
907 else if(
operator&& strcmp(
operator,
"<>") == 0)
908 visible = (nb1 != nb2);
909 else if(
operator&& number2 && strcmp(
operator,
"[]") == 0)
911 const float nb3 = g_strtod(number2, NULL);
912 visible = (nb2 >= nb1 && nb2 <= nb3);
915 visible = (nb1 == nb2);
924 for(
const GList *l = list; l; l = g_list_next(l))
926 const char *
name = (
char *)l->data;
927 if((visible = (g_strrstr(haystack,
name + (
name[0] ==
'%')) != NULL)))
break;
933 if(needle[0] ==
'%') needle++;
937 visible = (strchr(haystack, needle[0]) != NULL);
939 visible = (g_strrstr(haystack, needle) != NULL);
944 if(visible != was_visible)
955 if(g_str_has_suffix(needle,
"%")) needle[strlen(needle) - 1] =
'\0';
956 if(g_str_has_suffix(haystack,
"%")) haystack[strlen(haystack) - 1] =
'\0';
959 if(g_str_has_suffix(needle,
"*")) needle[strlen(needle) - 1] =
'\0';
960 if(g_str_has_suffix(needle,
"|")) needle[strlen(needle) - 1] =
'\0';
961 if(g_str_has_suffix(haystack,
"|")) haystack[strlen(haystack) - 1] =
'\0';
965 if(g_str_has_suffix(needle,
"*")) needle[strlen(needle) - 1] =
'\0';
966 if(g_str_has_suffix(needle,
"/")) needle[strlen(needle) - 1] =
'\0';
967 if(g_str_has_suffix(haystack,
"/")) haystack[strlen(haystack) - 1] =
'\0';
971 if(g_str_has_suffix(needle,
":")) needle[strlen(needle) - 1] =
'\0';
972 if(g_str_has_suffix(haystack,
":")) haystack[strlen(haystack) - 1] =
'\0';
981 gboolean cur_state, visible;
988 gchar *haystack = g_utf8_strdown(str, -1),
989 *needle = g_utf8_strdown(gtk_entry_get_text(GTK_ENTRY(dr->
text)), -1);
991 visible = (g_strrstr(haystack, needle) != NULL);
1003 GtkTreeIter parent, child = *iter;
1006 while(gtk_tree_model_iter_parent(
model, &parent, &child))
1028 for(
const char *
p = s;
p && *
p &&
n < 14;
p++)
1029 if(g_ascii_isdigit(*
p)) digits[
n++] = *
p;
1030 while(
n < 14) digits[
n++] = pad;
1032 return g_ascii_strtoull(digits, NULL, 10);
1048 const guint64 node_lo =
_date_key(str,
'0');
1049 const guint64 node_hi =
_date_key(str,
'9');
1051 const gboolean visible = (node_hi >=
r->lo) && (node_lo <= r->hi);
1056static gboolean
list_select(GtkTreeModel *
model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1063 gchar *haystack = g_utf8_strdown(str, -1);
1064 gchar *needle = g_utf8_strdown(gtk_entry_get_text(GTK_ENTRY(dr->
text)), -1);
1065 if(strcmp(haystack, needle) == 0)
1067 gtk_tree_selection_select_path(gtk_tree_view_get_selection(
d->view), path);
1068 gtk_tree_view_scroll_to_cell(
d->view, path, NULL,
FALSE, 0.2, 0);
1076static gboolean
range_select(GtkTreeModel *
model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1082 gchar *haystack = g_utf8_strdown(str, -1);
1083 gchar *needle = range->
path1 ? g_utf8_strdown(range->
stop, -1) : g_utf8_strdown(range->
start, -1);
1084 if(strcmp(haystack, needle) == 0)
1088 range->
path2 = gtk_tree_path_copy(path);
1095 range->
path1 = gtk_tree_path_copy(path);
1103static gboolean
tree_expand(GtkTreeModel *
model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
1107 gchar *str = NULL, *txt = NULL;
1108 gboolean startwildcard =
FALSE, expanded =
FALSE;
1111 gchar *haystack = g_utf8_strdown(str, -1);
1112 gchar *needle = g_utf8_strdown(gtk_entry_get_text(GTK_ENTRY(dr->
text)), -1);
1113 gchar *txt2 = g_utf8_strdown(txt, -1);
1120 if(g_str_has_prefix(needle,
"%")) startwildcard =
TRUE;
1123 if(reveal && g_strrstr(txt2, needle) != NULL)
1125 gtk_tree_view_expand_to_path(
d->view, path);
1129 if(strlen(needle) == 0)
1133 else if(strcmp(haystack, needle) == 0)
1135 gtk_tree_view_expand_to_path(
d->view, path);
1136 gtk_tree_selection_select_path(gtk_tree_view_get_selection(
d->view), path);
1137 gtk_tree_view_scroll_to_cell(
d->view, path, NULL,
FALSE, 0.2, 0);
1140 else if(startwildcard && g_strrstr(haystack, needle + 1) != NULL)
1142 gtk_tree_view_expand_to_path(
d->view, path);
1147 gtk_tree_view_expand_to_path(
d->view, path);
1164 GtkTreeIter child, iter;
1166 while(gtk_tree_model_iter_n_children(
model, level > 0 ? &iter : NULL) > 0)
1176 if(!gtk_tree_model_iter_parent(
model, &child, &iter)) level = 0;
1181 if(gtk_tree_model_iter_n_children(
model, level > 0 ? &iter : NULL) != 1)
break;
1182 gtk_tree_model_iter_children(
model, &child, level > 0 ? &iter : NULL);
1186 if(level <= 0)
return NULL;
1188 if(gtk_tree_model_iter_n_children(
model, &iter) == 0 && gtk_tree_model_iter_parent(
model, &child, &iter))
1189 return gtk_tree_model_get_path(
model, &child);
1190 return gtk_tree_model_get_path(
model, &iter);
1199 GtkTreeModel *filter = gtk_tree_model_filter_new(
model, path);
1200 gtk_tree_path_free(path);
1210 GHashTable *set = (GHashTable *)data;
1211 GtkTreeModel *
model = gtk_tree_view_get_model(
view);
1213 if(gtk_tree_model_get_iter(
model, &it, path))
1217 if(
p) g_hash_table_add(set,
p);
1232 if(
p && g_hash_table_contains(c->set,
p)) gtk_tree_view_expand_to_path(c->d->view, path);
1243 return g_strsplit(
name,
"|", -1);
1251 GtkTreeIter parent, child = *leaf;
1252 while(gtk_tree_model_iter_parent(
model, &parent, &child))
1264 GtkTreeIter *uncategorized, guint *index,
int count)
1266 if(strchr(
name,
'|') != NULL)
return FALSE;
1268 char *next_name = g_strdup(next_name_raw ? next_name_raw :
"");
1269 if(strlen(next_name) >= strlen(
name) + 1 && next_name[strlen(
name)] ==
'|') next_name[strlen(
name)] =
'\0';
1270 const gboolean leaf_toplevel = g_strcmp0(next_name,
name) && g_strcmp0(
name, _(
"not tagged"));
1272 if(!leaf_toplevel)
return FALSE;
1274 if(!uncategorized->stamp)
1296 GList *sorted_names = NULL;
1298 for(GList *
v = values;
v;
v = g_list_next(
v))
1305 char *name_folded = g_utf8_casefold(
name, -1);
1306 char *name_folded_slash = g_strconcat(name_folded, G_DIR_SEPARATOR_S, NULL);
1307 collate_key = g_utf8_collate_key_for_filename(name_folded_slash, -1);
1319 sorted_names = g_list_prepend(sorted_names, tuple);
1324 if(!
dt_conf_get_bool(
"plugins/collect/descending")) sorted_names = g_list_reverse(sorted_names);
1325 return sorted_names;
1332 gboolean no_uncategorized,
const char *format_separator)
1335 GtkTreeIter uncategorized = { 0 };
1336 char **last_tokens = NULL;
1337 int last_tokens_length = 0;
1338 GtkTreeIter last_parent = { 0 };
1345 const int count = tuple->
count;
1356 GtkTreeIter parent = last_parent;
1358 int common_length = 0;
1361 while(tokens[common_length] && last_tokens[common_length]
1362 && !g_strcmp0(tokens[common_length], last_tokens[common_length]))
1364 for(
int i = common_length;
i < last_tokens_length;
i++)
1366 gtk_tree_model_iter_parent(
model, &parent, &last_parent);
1367 last_parent = parent;
1375 for(
int i = 0;
i < common_length;
i++) pth =
dt_util_dstrcat(pth, format_separator, tokens[
i]);
1377 for(
char **token = &tokens[common_length]; *token; token++)
1383 gchar *pth2 = g_strdup(pth);
1384 pth2[strlen(pth2) - 1] =
'\0';
1385 const gboolean leaf = !*(token + 1);
1386 gtk_tree_store_insert_with_values(
store, &iter, common_length > 0 ? &parent : NULL, 0,
1402 if(last_tokens) g_strfreev(last_tokens);
1403 last_tokens = tokens;
1404 last_parent = parent;
1405 last_tokens_length = tokens_length;
1407 g_strfreev(last_tokens);
1415 char *format_separator =
"";
1420 format_separator =
"%s" G_DIR_SEPARATOR_S;
1424 format_separator =
"%s|";
1432 format_separator =
"%s:";
1438 GtkTreeModel *
model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(
d->treefilter));
1439 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(
model), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
1440 GTK_SORT_ASCENDING);
1442 GHashTable *saved_expanded = NULL;
1444 if(
d->view_rule != property)
1447 saved_expanded = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1450 g_object_ref(
model);
1451 g_object_unref(
d->treefilter);
1452 gtk_tree_view_set_model(GTK_TREE_VIEW(
d->view), NULL);
1453 gtk_tree_store_clear(GTK_TREE_STORE(
model));
1454 gtk_widget_hide(GTK_WIDGET(
d->view));
1466 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(
d->view));
1467 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
1469 gtk_tree_view_set_model(GTK_TREE_VIEW(
d->view),
d->treefilter);
1470 gtk_widget_set_no_show_all(GTK_WIDGET(
d->view),
FALSE);
1471 gtk_widget_show_all(GTK_WIDGET(
d->view));
1473 g_object_unref(
model);
1474 d->view_rule = property;
1483 GRegex *regex = g_regex_new(
"^\\s*\\[\\s*(.*)\\s*;\\s*(.*)\\s*\\]\\s*$", 0, 0, NULL);
1484 GMatchInfo *match_info;
1485 g_regex_match_full(regex, gtk_entry_get_text(GTK_ENTRY(dr->
text)), -1, 0, 0, &match_info, NULL);
1486 if(g_match_info_get_match_count(match_info) == 3)
1489 range->
start = g_match_info_fetch(match_info, 2);
1490 range->
stop = g_match_info_fetch(match_info, 1);
1492 g_match_info_free(match_info);
1493 g_regex_unref(regex);
1508 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(
d->view));
1513 gtk_tree_view_expand_all(
d->view);
1515 gtk_tree_view_collapse_all(
d->view);
1516 else if(saved_expanded)
1521 if(saved_expanded) g_hash_table_destroy(saved_expanded);
1526 gtk_tree_model_foreach(
d->treefilter, (GtkTreeModelForeachFunc)
range_select, range);
1528 gtk_tree_selection_select_range(gtk_tree_view_get_selection(
d->view), range->
path1, range->
path2);
1531 gtk_tree_path_free(range->
path1);
1532 gtk_tree_path_free(range->
path2);
1536 gtk_tree_model_foreach(
d->treefilter, (GtkTreeModelForeachFunc)
tree_expand, dr);
1547 GtkTreeModel *
model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(
d->listfilter));
1548 if(
d->view_rule != property)
1551 g_object_unref(
d->listfilter);
1552 g_object_ref(
model);
1553 gtk_tree_view_set_model(GTK_TREE_VIEW(
d->view), NULL);
1554 gtk_list_store_clear(GTK_LIST_STORE(
model));
1555 gtk_widget_hide(GTK_WIDGET(
d->view));
1558 for(GList *
v = values;
v;
v = g_list_next(
v))
1567 gchar *text = g_strdup(
value);
1569 while(!g_utf8_validate(ptr, -1, (
const gchar **)&ptr)) ptr[0] =
'?';
1570 gchar *escaped_text = g_markup_escape_text(text, -1);
1572 gtk_list_store_append(GTK_LIST_STORE(
model), &iter);
1586 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(
d->view));
1588 gtk_tree_selection_set_mode(selection, multi ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE);
1590 gtk_tree_view_set_model(GTK_TREE_VIEW(
d->view),
d->listfilter);
1591 gtk_widget_set_no_show_all(GTK_WIDGET(
d->view),
FALSE);
1592 gtk_widget_show_all(GTK_WIDGET(
d->view));
1593 g_object_unref(
model);
1594 d->view_rule = property;
1607 gchar *needle = g_utf8_strdown(gtk_entry_get_text(GTK_ENTRY(dr->
text)), -1);
1608 if(g_str_has_suffix(needle,
"%")) needle[strlen(needle) - 1] =
'\0';
1614 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(
d->view));
1618 GRegex *regex = g_regex_new(
"^\\s*\\[\\s*(.*)\\s*;\\s*(.*)\\s*\\]\\s*$", 0, 0, NULL);
1619 GMatchInfo *match_info;
1620 g_regex_match_full(regex, gtk_entry_get_text(GTK_ENTRY(dr->
text)), -1, 0, 0, &match_info, NULL);
1621 const int match_count = g_match_info_get_match_count(match_info);
1622 if(match_count == 3)
1625 range->
start = g_match_info_fetch(match_info, 1);
1626 range->
stop = g_match_info_fetch(match_info, 2);
1627 gtk_tree_model_foreach(
d->listfilter, (GtkTreeModelForeachFunc)
range_select, range);
1629 gtk_tree_selection_select_range(gtk_tree_view_get_selection(
d->view), range->
path1, range->
path2);
1632 gtk_tree_path_free(range->
path1);
1633 gtk_tree_path_free(range->
path2);
1637 gtk_tree_model_foreach(
d->listfilter, (GtkTreeModelForeachFunc)
list_select, dr);
1638 g_match_info_free(match_info);
1639 g_regex_unref(regex);
1642 gtk_tree_model_foreach(
d->listfilter, (GtkTreeModelForeachFunc)
list_select, dr);
1666 gchar *
n = g_strconcat(text,
"|%", NULL);
1672 gchar *
n = g_strconcat(text,
"*", NULL);
1690 gboolean descending =
FALSE;
1705 GtkTreeModel *
model = NULL;
1706 GtkTreeSelection *selection = gtk_tree_view_get_selection(
view);
1707 const int n_selected = gtk_tree_selection_count_selected_rows(selection);
1708 if(n_selected < 1)
return;
1710 GList *sels = gtk_tree_selection_get_selected_rows(selection, &
model);
1711 GtkTreePath *path1 = (GtkTreePath *)sels->data;
1712 if(!gtk_tree_model_get_iter(
model, &iter, path1))
1714 g_list_free_full(sels, (GDestroyNotify)gtk_tree_path_free);
1724 gboolean order_request =
FALSE;
1728 if(text && strlen(text) > 0)
1734 GtkTreePath *path2 = (GtkTreePath *)g_list_last(sels)->data;
1735 if(gtk_tree_model_get_iter(
model, &iter2, path2))
1740 ? g_strdup_printf(
"[%s;%s]", text2, text)
1741 : g_strdup_printf(
"[%s;%s]", text, text2);
1754 if(gtk_tree_model_iter_has_child(
model, &iter))
1762 g_list_free_full(sels, (GDestroyNotify)gtk_tree_path_free);
1764 g_signal_handlers_block_matched(active_rule->
text, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
entry_changed, NULL);
1765 gtk_entry_set_text(GTK_ENTRY(active_rule->
text), text);
1766 gtk_editable_set_position(GTK_EDITABLE(active_rule->
text), -1);
1767 g_signal_handlers_unblock_matched(active_rule->
text, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
entry_changed, NULL);
1817 GtkTreeModel *
model = NULL;
1818 GList *paths = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(
d->view), &
model);
1820 for(GList *l = paths; l; l = g_list_next(l))
1823 if(gtk_tree_model_get_iter(
model, &it, (GtkTreePath *)l->data))
1830 g_list_free_full(paths, (GDestroyNotify)gtk_tree_path_free);
1831 return g_list_reverse(
out);
1838 GHashTable *seen = g_hash_table_new(g_direct_hash, g_direct_equal);
1840 for(GList *l = rows; l; l = g_list_next(l))
1846 for(GList *
i = ids;
i;
i = g_list_next(
i))
1847 if(!g_hash_table_contains(seen,
i->data))
1849 g_hash_table_add(seen,
i->data);
1850 out = g_list_prepend(
out,
i->data);
1854 g_hash_table_destroy(seen);
1855 return g_list_reverse(
out);
1859static gboolean
_confirm(
const char *title,
const char *message)
1862 GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION,
1863 GTK_BUTTONS_YES_NO,
"%s", message);
1864 gtk_window_set_title(GTK_WINDOW(dialog), title);
1865#ifdef GDK_WINDOWING_QUARTZ
1868 const gint
res = gtk_dialog_run(GTK_DIALOG(dialog));
1869 gtk_widget_destroy(dialog);
1870 return res == GTK_RESPONSE_YES;
1873static gchar *
_ask_text(
const char *title,
const char *initial)
1877 = gtk_dialog_new_with_buttons(title, GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, _(
"_cancel"),
1878 GTK_RESPONSE_CANCEL, _(
"_ok"), GTK_RESPONSE_ACCEPT, NULL);
1879 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1881 gtk_entry_set_activates_default(GTK_ENTRY(entry),
TRUE);
1882 if(initial) gtk_entry_set_text(GTK_ENTRY(entry), initial);
1883 gtk_box_pack_start(GTK_BOX(area), entry,
TRUE,
TRUE, 0);
1884 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
1886 gtk_widget_show_all(dialog);
1887#ifdef GDK_WINDOWING_QUARTZ
1890 gchar *result = NULL;
1891 if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
1893 const gchar *
t = gtk_entry_get_text(GTK_ENTRY(entry));
1894 if(
t && *
t) result = g_strdup(
t);
1896 gtk_widget_destroy(dialog);
1912 g_list_free(imgids);
1919 const int n = g_list_length(rows);
1920 const gboolean single = (
n == 1);
1923 GtkFileChooserNative *
fc = gtk_file_chooser_native_new(
1924 single ? _(
"select the new location of this folder") : _(
"select the new parent folder"), GTK_WINDOW(win),
1925 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, _(
"_open"), _(
"_cancel"));
1926 if(single && first->
path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(
fc), first->
path);
1928 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(
fc)) == GTK_RESPONSE_ACCEPT)
1930 gchar *uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(
fc));
1931 gchar *chosen = g_filename_from_uri(uri, NULL, NULL);
1935 for(GList *l = rows; l; l = g_list_next(l))
1943 gchar *base = g_path_get_basename(
r->path);
1944 gchar *dest = g_build_filename(chosen, base, NULL);
1964 const int n = g_list_length(rows);
1965 gchar *msg = g_strdup_printf(ngettext(
"Delete %d tag and detach it from all images?",
1966 "Delete %d tags and detach them from all images?",
n),
1968 const gboolean ok =
_confirm(_(
"delete tags"), msg);
1972 for(GList *l = rows; l; l = g_list_next(l))
1990 gchar *newname =
_ask_text(_(
"rename tag"),
r->path);
2021 const int n = g_list_length(
p->imgids);
2022 const float total = (
n > 0) ? (
float)(
n * (
max + 1)) : 1.0f;
2027 const int32_t imgid = GPOINTER_TO_INT(l->data);
2106 const collect_action_t *act = g_object_get_data(G_OBJECT(mi),
"collect-action");
2108 if(rows) act->
run(
d, rows);
2114 const int property =
d->view_rule;
2115 const int n = gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(
d->view));
2120 for(
size_t i = 0;
i < G_N_ELEMENTS(
ACTIONS);
i++)
2123 if(!act->
enabled(property,
n))
continue;
2124 if(!act->
multi &&
n > 1)
continue;
2126 g_object_set_data(G_OBJECT(mi),
"collect-action", (gpointer)act);
2128 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
2133 gtk_widget_show_all(menu);
2134 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
2137 gtk_widget_destroy(menu);
2150 const int n = g_list_length(imgs);
2151 gchar *msg = g_strdup_printf(ngettext(
"Physically move %d image to\n%s ?\n\nFiles are moved on disk.",
2152 "Physically move %d images to\n%s ?\n\nFiles are moved on disk.",
n),
2154 const gboolean ok =
_confirm(_(
"move images"), msg);
2156 if(!ok)
return FALSE;
2161 const int32_t filmid = film.
id;
2170 for(GList *l = imgs; l; l = g_list_next(l))
2171 if(
dt_image_move(GPOINTER_TO_INT(l->data), filmid) != -1) moved++;
2188 if(!tagid)
return FALSE;
2213 const int imgs_nb = gtk_selection_data_get_length(sel) / (int)
sizeof(uint32_t);
2214 if(imgs_nb <= 0)
return FALSE;
2216 GtkTreePath *path = NULL;
2217 if(!gtk_tree_view_get_path_at_pos(tree,
x, y, &path, NULL, NULL, NULL))
return FALSE;
2219 GtkTreeModel *
model = gtk_tree_view_get_model(tree);
2221 gboolean ok =
FALSE;
2222 if(gtk_tree_model_get_iter(
model, &iter, path))
2224 const uint32_t *imgt = (
const uint32_t *)gtk_selection_data_get_data(sel);
2226 for(
int i = 0;
i < imgs_nb;
i++) imgs = g_list_prepend(imgs, GINT_TO_POINTER((
int)imgt[
i]));
2228 gchar *rowpath = NULL;
2234 gtk_tree_path_free(path);
2251 GtkTreeView *tree = GTK_TREE_VIEW(widget);
2252 GtkTreePath *path = NULL;
2254 && gtk_tree_view_get_path_at_pos(tree,
x, y, &path, NULL, NULL, NULL))
2256 gtk_tree_view_set_drag_dest_row(tree, path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
2257 gtk_tree_path_free(path);
2258 gdk_drag_status(context, GDK_ACTION_MOVE, time);
2262 gtk_tree_view_set_drag_dest_row(tree, NULL, 0);
2263 gdk_drag_status(context, 0, time);
2270 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(widget), NULL, 0);
2274 GtkSelectionData *selection_data, guint target_type, guint time,
2277 GtkTreeView *tree = GTK_TREE_VIEW(widget);
2278 g_signal_stop_emission_by_name(tree,
"drag-data-received");
2279 gtk_tree_view_set_drag_dest_row(tree, NULL, 0);
2283 d->suppress_row_activated =
TRUE;
2287 gtk_drag_finish(context, success,
FALSE, time);
2294 if(event->button != 3 || event->type != GDK_BUTTON_PRESS)
return FALSE;
2297 GtkTreeView *
view = GTK_TREE_VIEW(treeview);
2298 GtkTreeSelection *sel = gtk_tree_view_get_selection(
view);
2302 GtkTreePath *path = NULL;
2303 if(gtk_tree_view_get_path_at_pos(
view, (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL) && path)
2305 if(!gtk_tree_selection_path_is_selected(sel, path))
2307 gtk_tree_selection_unselect_all(sel);
2308 gtk_tree_selection_select_path(sel, path);
2310 gtk_tree_path_free(path);
2314 if(gtk_tree_selection_count_selected_rows(sel) < 1)
return FALSE;
2328 if(
d->suppress_row_activated)
return;
2330 GdkEvent *ev = gtk_get_current_event();
2334 = (ev && (ev->type == GDK_BUTTON_PRESS || ev->type == GDK_2BUTTON_PRESS || ev->type == GDK_BUTTON_RELEASE))
2335 ? (GdkEventButton *)ev
2338 if(ev) gdk_event_free(ev);
2344 gtk_tree_view_scroll_to_cell(
view, path, NULL,
TRUE, 0.0, 0.0);
2355 gtk_widget_set_tooltip_text(dr->
text, _(
"use <, <=, >, >=, <>, =, [;] as operators"));
2357 gtk_widget_set_tooltip_text(dr->
text, _(
"use <, <=, >, >=, <>, =, [;] as operators\n"
2358 "star rating: 0-5\n"
2359 "rejected images: -1"));
2361 gtk_widget_set_tooltip_text(dr->
text,
2362 _(
"use <, <=, >, >=, <>, =, [;] as operators\n"
2363 "type dates in the form: YYYY:MM:DD hh:mm:ss.sss (only the year is mandatory)"));
2366 gtk_widget_set_tooltip_text(dr->
text, _(
"use `%' as wildcard and `,' to separate values"));
2369 gtk_widget_set_tooltip_text(dr->
text, _(
"use `%' as wildcard\n"
2370 "click to include hierarchy + sub-hierarchies (suffix `*')\n"
2371 "shift+click to include only the current hierarchy (no suffix)\n"
2372 "ctrl+click to include only sub-hierarchies (suffix `|%')"));
2375 gtk_widget_set_tooltip_text(dr->
text, _(
"use `%' as wildcard\n"
2376 "click to include location + sub-locations (suffix `*')\n"
2377 "shift+click to include only the current location (no suffix)\n"
2378 "ctrl+click to include only sub-locations (suffix `|%')"));
2381 gtk_widget_set_tooltip_text(dr->
text,
2382 _(
"use `%' as wildcard and append `*' to match sub-folders"));
2385 gtk_widget_set_tooltip_text(dr->
text, _(
"use `%' as wildcard"));
2387 gchar *tip = gtk_widget_get_tooltip_text(dr->
text);
2388 gtk_widget_set_tooltip_text(GTK_WIDGET(dr->
combo), tip);
2402 c->active_rule = dr->
num;
2415 c->active_rule = dr->
num;
2420 const gboolean transferable
2424 g_signal_handlers_block_matched(dr->
text, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
entry_changed, NULL);
2425 gtk_entry_set_text(GTK_ENTRY(dr->
text),
"");
2426 g_signal_handlers_unblock_matched(dr->
text, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
entry_changed, NULL);
2430 g_signal_handlers_block_matched(dr->
op_combo, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
_op_changed, NULL);
2431 gtk_combo_box_set_active(GTK_COMBO_BOX(dr->
op_combo), 0);
2432 g_signal_handlers_unblock_matched(dr->
op_combo, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
_op_changed, NULL);
2438 if(c->folders_controls && gtk_widget_get_visible(c->folders_controls))
2451 dr->
reveal = transferable;
2478 GtkTreeModel *
model = gtk_tree_view_get_model(GTK_TREE_VIEW(c->view));
2479 if(gtk_tree_model_iter_n_children(
model, NULL) == 1)
2482 if(gtk_tree_model_get_iter_first(
model, &iter))
2486 g_signal_handlers_block_matched(dr->
text, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
entry_changed, NULL);
2487 gtk_entry_set_text(GTK_ENTRY(dr->
text), text);
2488 gtk_editable_set_position(GTK_EDITABLE(dr->
text), -1);
2489 g_signal_handlers_unblock_matched(dr->
text, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
entry_changed, NULL);
2503 c->active_rule = dr->
num;
2524 const gboolean desc = gtk_toggle_button_get_active(b);
2527 gtk_widget_queue_draw(GTK_WIDGET(b));
2544 dt_conf_set_int(
"show_folder_levels", (
int)gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin)));
2551 dt_conf_set_bool(
"plugins/lighttable/tagging/no_uncategorized", gtk_toggle_button_get_active(b));
2573 const dt_lib_collect_mode_t mode = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem),
"menuitem_mode"));
2580 c->active_rule = active;
2588 const int num = dr->
num + 1;
2589 if(num < MAX_RULES && num > 0)
2590 _rule_set_mode(num, GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem),
"menuitem_mode")));
2602 const int new_count = active - 1;
2604 if(c->active_rule > dr->
num) c->active_rule--;
2605 if(c->active_rule >= new_count) c->active_rule = new_count - 1;
2633 if(event->button != 1)
return FALSE;
2639 mi = gtk_menu_item_new_with_label(_(
"clear this rule"));
2640 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
2641 g_signal_connect(G_OBJECT(mi),
"activate", G_CALLBACK(
menuitem_clear), dr);
2643 if(dr->
num == active - 1)
2645 mi = gtk_menu_item_new_with_label(_(
"narrow down search"));
2647 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
2648 g_signal_connect(G_OBJECT(mi),
"activate", G_CALLBACK(
menuitem_mode), dr);
2650 mi = gtk_menu_item_new_with_label(_(
"add more images"));
2652 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
2653 g_signal_connect(G_OBJECT(mi),
"activate", G_CALLBACK(
menuitem_mode), dr);
2655 mi = gtk_menu_item_new_with_label(_(
"exclude images"));
2657 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
2658 g_signal_connect(G_OBJECT(mi),
"activate", G_CALLBACK(
menuitem_mode), dr);
2660 else if(dr->
num < active - 1)
2662 mi = gtk_menu_item_new_with_label(_(
"change to: and"));
2664 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
2667 mi = gtk_menu_item_new_with_label(_(
"change to: or"));
2669 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
2672 mi = gtk_menu_item_new_with_label(_(
"change to: except"));
2674 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
2678 gtk_widget_show_all(GTK_WIDGET(menu));
2679 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
2721 gtk_button_set_label(GTK_BUTTON(dr->
button),
"-");
2722 gtk_widget_set_tooltip_text(GTK_WIDGET(dr->
button), _(
"clear this rule"));
2726 gtk_button_set_label(GTK_BUTTON(dr->
button),
"+");
2727 gtk_widget_set_tooltip_text(GTK_WIDGET(dr->
button), _(
"clear this rule or add new rules"));
2735 gtk_widget_set_tooltip_text(GTK_WIDGET(dr->
button), _(
"clear this rule"));
2743 gtk_widget_set_no_show_all(
d->rule[
i].hbox,
TRUE);
2744 gtk_widget_hide(
d->rule[
i].hbox);
2746 gtk_widget_set_no_show_all(
d->folders_controls,
TRUE);
2747 gtk_widget_hide(
d->folders_controls);
2748 gtk_widget_set_no_show_all(
d->collections_controls,
TRUE);
2749 gtk_widget_hide(
d->collections_controls);
2750 gtk_widget_set_no_show_all(
d->raw_box,
TRUE);
2751 gtk_widget_hide(
d->raw_box);
2752 gtk_widget_set_no_show_all(GTK_WIDGET(
d->view),
FALSE);
2753 gtk_widget_show(GTK_WIDGET(
d->view));
2770 if(
d->has_last_folders)
2772 item =
d->last_folders_item;
2787 gtk_widget_set_no_show_all(
d->rule[0].hbox,
FALSE);
2788 gtk_widget_show_all(
d->rule[0].hbox);
2789 gtk_widget_show(
d->rule[0].combo);
2790 gtk_widget_hide(
d->rule[0].button);
2791 gtk_entry_set_placeholder_text(GTK_ENTRY(
d->rule[0].text), _(
"Search a folder..."));
2795 gtk_widget_set_no_show_all(
d->folders_controls,
FALSE);
2796 gtk_widget_show_all(
d->folders_controls);
2799 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
d->sort_dir), desc);
2807 gtk_spin_button_set_value(GTK_SPIN_BUTTON(
d->folder_levels),
2825 gtk_widget_set_no_show_all(
d->rule[0].hbox,
FALSE);
2826 gtk_widget_show_all(
d->rule[0].hbox);
2827 gtk_widget_hide(
d->rule[0].combo);
2828 gtk_widget_hide(
d->rule[0].button);
2829 gtk_entry_set_placeholder_text(GTK_ENTRY(
d->rule[0].text), _(
"Search a collection..."));
2833 gtk_widget_set_no_show_all(
d->collections_controls,
FALSE);
2834 gtk_widget_show_all(
d->collections_controls);
2835 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
d->no_uncategorized),
2843 gtk_widget_set_no_show_all(
d->raw_box,
FALSE);
2844 gtk_widget_show_all(
d->raw_box);
2845 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
d->raw_check), raw);
2850 gtk_entry_set_text(GTK_ENTRY(
d->raw_entry), s ? s :
"");
2852 gtk_widget_show(
d->raw_entry);
2853 gtk_widget_set_no_show_all(GTK_WIDGET(
d->view),
TRUE);
2854 gtk_widget_hide(GTK_WIDGET(
d->view));
2859 gtk_widget_hide(
d->raw_entry);
2860 for(
int i = 0;
i <
d->nb_rules;
i++)
2864 gtk_widget_set_no_show_all(
d->rule[
i].hbox,
FALSE);
2865 gtk_widget_show_all(
d->rule[
i].hbox);
2866 gtk_widget_show(
d->rule[
i].combo);
2867 gtk_widget_show(
d->rule[
i].button);
2869 gtk_entry_set_placeholder_text(GTK_ENTRY(
d->rule[
i].text), _(
"Search..."));
2881 if(gtk_toggle_button_get_active(b))
2924 if(
d->view_rule != -1)
return;
2938 g_signal_handlers_block_matched(
d->notebook, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
_on_tab_switch, NULL);
2939 gtk_notebook_set_current_page(GTK_NOTEBOOK(
d->notebook), tab);
2940 g_signal_handlers_unblock_matched(
d->notebook, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
_on_tab_switch, NULL);
2960 gboolean refresh =
TRUE;
2964 for(
int i = 0;
i <
d->nb_rules;
i++)
2997 gboolean uses_tag =
FALSE;
2998 for(
int i = 0;
i <
d->nb_rules;
i++)
3018static void geotag_changed(gpointer instance, GList *imgs,
const int locid, gpointer self)
3077 self->
data = (
void *)
d;
3088 dt_ui_notebook_page(GTK_NOTEBOOK(
d->notebook), _(
"Folders"), _(
"Browse and manage the folders known to Ansel"));
3090 dt_ui_notebook_page(GTK_NOTEBOOK(
d->notebook), _(
"Queries"), _(
"Build arbitrary collections"));
3091 gtk_widget_show_all(
d->notebook);
3092 gtk_box_pack_start(GTK_BOX(self->
widget),
d->notebook,
TRUE,
TRUE, 0);
3093 gtk_notebook_set_scrollable(GTK_NOTEBOOK(
d->notebook),
TRUE);
3094 g_signal_connect(G_OBJECT(
d->notebook),
"switch_page", G_CALLBACK(
_on_tab_switch), self);
3101 d->rule[
i].lib_collect = (
void *)
d;
3104 d->rule[
i].hbox = GTK_WIDGET(box);
3105 gtk_box_pack_start(GTK_BOX(self->
widget), GTK_WIDGET(box),
TRUE,
TRUE, 0);
3106 gtk_widget_set_name(GTK_WIDGET(box),
"lib-dtbutton");
3111 g_signal_connect(G_OBJECT(
d->rule[
i].combo),
"value-changed", G_CALLBACK(
combo_changed),
d->rule +
i);
3112 gtk_box_pack_start(box,
d->rule[
i].combo,
FALSE,
FALSE, 0);
3115 gtk_box_pack_start(box, GTK_WIDGET(hbox),
FALSE,
FALSE, 0);
3118 d->rule[
i].op_combo = gtk_combo_box_text_new();
3120 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(
d->rule[
i].op_combo),
OP_LABELS[o]);
3121 gtk_combo_box_set_active(GTK_COMBO_BOX(
d->rule[
i].op_combo), 0);
3122 gtk_widget_set_no_show_all(
d->rule[
i].op_combo,
TRUE);
3123 gtk_widget_set_tooltip_text(
d->rule[
i].op_combo, _(
"comparison operator"));
3124 g_signal_connect(G_OBJECT(
d->rule[
i].op_combo),
"changed", G_CALLBACK(
_op_changed),
d->rule +
i);
3125 gtk_box_pack_start(hbox,
d->rule[
i].op_combo,
FALSE,
FALSE, 0);
3129 d->rule[
i].text = w;
3130 gtk_widget_add_events(w, GDK_FOCUS_CHANGE_MASK | GDK_KEY_PRESS_MASK);
3131 gtk_entry_set_placeholder_text(GTK_ENTRY(w), _(
"Search..."));
3132 g_signal_connect(G_OBJECT(w),
"focus-in-event", G_CALLBACK(
_entry_focus_in),
d->rule +
i);
3133 g_signal_connect(G_OBJECT(w),
"changed", G_CALLBACK(
entry_changed),
d->rule +
i);
3134 g_signal_connect(G_OBJECT(w),
"activate", G_CALLBACK(
entry_activated),
d->rule +
i);
3135 gtk_widget_set_name(GTK_WIDGET(w),
"lib-collect-entry");
3136 gtk_box_pack_start(hbox, w,
TRUE,
TRUE, 0);
3137 gtk_entry_set_width_chars(GTK_ENTRY(w), 5);
3139 d->rule[
i].button = gtk_button_new();
3140 gtk_widget_set_events(
d->rule[
i].button, GDK_BUTTON_PRESS_MASK);
3143 gtk_box_pack_start(hbox,
d->rule[
i].button,
FALSE,
FALSE, 0);
3148 d->recursive_check = gtk_check_button_new_with_label(_(
"include sub-folders"));
3151 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
d->recursive_check),
FALSE);
3153 gtk_box_pack_start(GTK_BOX(
d->folders_controls),
d->recursive_check,
FALSE,
FALSE, 0);
3161 gtk_widget_set_valign(
d->sort_by, GTK_ALIGN_CENTER);
3162 g_signal_connect(G_OBJECT(
d->sort_by),
"value-changed", G_CALLBACK(
_sort_by_changed),
d);
3163 gtk_box_pack_start(GTK_BOX(
d->folders_controls),
d->sort_by,
TRUE,
TRUE, 0);
3167 gtk_widget_set_tooltip_text(
d->sort_dir, _(
"toggle ascending / descending order"));
3169 gtk_box_pack_start(GTK_BOX(
d->folders_controls),
d->sort_dir,
FALSE,
FALSE, 0);
3171 d->folder_levels = gtk_spin_button_new_with_range(1, 5, 1);
3172 gtk_widget_set_tooltip_text(
d->folder_levels,
3173 _(
"number of folder levels to show in film-roll names, from the right"));
3175 gtk_box_pack_start(GTK_BOX(
d->folders_controls),
d->folder_levels,
FALSE,
FALSE, 0);
3176 gtk_box_pack_start(GTK_BOX(self->
widget),
d->folders_controls,
FALSE,
FALSE, 0);
3180 d->no_uncategorized = gtk_check_button_new_with_label(_(
"no 'uncategorized' group"));
3181 gtk_widget_set_tooltip_text(
d->no_uncategorized,
3182 _(
"do not group childless tags under an 'uncategorized' entry"));
3184 gtk_box_pack_start(GTK_BOX(
d->collections_controls),
d->no_uncategorized,
FALSE,
FALSE, 0);
3185 gtk_box_pack_start(GTK_BOX(self->
widget),
d->collections_controls,
FALSE,
FALSE, 0);
3189 d->raw_check = gtk_check_button_new_with_label(_(
"edit as raw SQL"));
3190 g_signal_connect(G_OBJECT(
d->raw_check),
"toggled", G_CALLBACK(
_raw_toggled),
d);
3191 gtk_box_pack_start(GTK_BOX(
d->raw_box),
d->raw_check,
FALSE,
FALSE, 0);
3192 d->raw_entry = gtk_entry_new();
3193 gtk_entry_set_placeholder_text(GTK_ENTRY(
d->raw_entry),
3194 _(
"SQL WHERE expression, e.g. iso > 800 AND lens LIKE '%50mm%'"));
3197 gtk_box_pack_start(GTK_BOX(
d->raw_box),
d->raw_entry,
FALSE,
FALSE, 0);
3201 GtkTreeView *
view = GTK_TREE_VIEW(gtk_tree_view_new());
3203 gtk_tree_view_set_headers_visible(
view,
FALSE);
3204 gtk_tree_view_set_activate_on_single_click(
view,
TRUE);
3205 gtk_widget_set_can_focus(GTK_WIDGET(
view),
TRUE);
3221 GtkTreeViewColumn *col = gtk_tree_view_column_new();
3222 gtk_tree_view_append_column(
view, col);
3223 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
3224 gtk_tree_view_column_pack_start(col, renderer,
TRUE);
3225 gtk_tree_view_column_set_cell_data_func(col, renderer,
tree_count_show, NULL, NULL);
3227 g_object_set(renderer,
"strikethrough",
TRUE,
"ellipsize", PANGO_ELLIPSIZE_MIDDLE, (gchar *)0);
3230 GtkTreeModel *listmodel = GTK_TREE_MODEL(
3232 G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INT));
3233 d->listfilter = gtk_tree_model_filter_new(listmodel, NULL);
3236 GtkTreeModel *treemodel = GTK_TREE_MODEL(
3238 G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INT));
3239 d->treefilter = gtk_tree_model_filter_new(treemodel, NULL);
3241 g_object_unref(treemodel);
3245 gtk_box_pack_start(GTK_BOX(self->
widget),
3257 d->vmonitor = g_volume_monitor_get();
3258 g_signal_connect(G_OBJECT(
d->vmonitor),
"mount-changed", G_CALLBACK(
_mount_changed), self);
3259 g_signal_connect(G_OBJECT(
d->vmonitor),
"mount-added", G_CALLBACK(
_mount_changed), self);
3261 d->vmonitor = g_unix_mount_monitor_get();
3262 g_signal_connect(G_OBJECT(
d->vmonitor),
"mounts-changed", G_CALLBACK(
_mount_changed), self);
3295 g_object_unref(
d->treefilter);
3296 g_object_unref(
d->listfilter);
3297 g_object_unref(
d->vmonitor);
void dt_accels_disconnect_on_text_input(GtkWidget *widget)
Disconnect accels while a text or search entry has the focus, and reconnect them when it loses it....
Handle default and user-set shortcuts (accelerators)
GtkWidget * dt_gui_main_window(void)
void dt_gui_refocus_center()
void dt_bauhaus_combobox_clear(GtkWidget *widget)
void dt_bauhaus_combobox_set_selected_text_align(GtkWidget *widget, const dt_bauhaus_combobox_alignment_t text_align)
gboolean dt_bauhaus_combobox_set_from_value(GtkWidget *widget, int value)
int dt_bauhaus_combobox_get(GtkWidget *widget)
void dt_bauhaus_combobox_add_full(GtkWidget *widget, const char *text, dt_bauhaus_combobox_alignment_t align, gpointer data, void(free_func)(void *data), gboolean sensitive)
gpointer dt_bauhaus_combobox_get_data(GtkWidget *widget)
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
@ DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
static void get_properties(dt_lib_collect_rule_t *dr)
static gboolean _confirm(const char *title, const char *message)
void gui_reset(dt_lib_module_t *self)
int set_params(dt_lib_module_t *self, const void *params, int size)
static void _set_tooltip(dt_lib_collect_rule_t *dr)
static void _hide_all_widgets(dt_lib_collect_t *d)
static gboolean tree_expand(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
static void metadata_changed(gpointer instance, int type, gpointer self)
static void _folder_levels_changed(GtkWidget *spin, dt_lib_collect_t *d)
void * get_params(dt_lib_module_t *self, int *size)
static void _collect_expanded_cb(GtkTreeView *view, GtkTreePath *path, gpointer data)
static int _rule_get_mode(int n)
@ DT_LIB_COLLECT_COL_TOOLTIP
@ DT_LIB_COLLECT_COL_COUNT
@ DT_LIB_COLLECT_COL_UNREACHABLE
@ DT_LIB_COLLECT_NUM_COLS
@ DT_LIB_COLLECT_COL_FONT
@ DT_LIB_COLLECT_COL_INDEX
@ DT_LIB_COLLECT_COL_PATH
@ DT_LIB_COLLECT_COL_TEXT
@ DT_LIB_COLLECT_COL_VISIBLE
static void _view_drag_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint target_type, guint time, dt_lib_collect_t *d)
static void _mount_changed(GVolumeMonitor *volume_monitor, GMount *mount, dt_lib_module_t *self)
void * legacy_params(struct dt_lib_module_t *self, const void *const old_params, const size_t old_params_size, const int old_version, int *new_version, size_t *new_size)
static void _populate_collect_combo(GtkWidget *w)
static void geotag_changed(gpointer instance, GList *imgs, const int locid, gpointer self)
#define PARAM_STRING_SIZE
static const char * UNCATEGORIZED_TAG
static void _free_row(gpointer p)
static int _rules_count()
static GList * _collect_sorted_tree_names(int property, int rule)
static void menuitem_clear(GtkMenuItem *menuitem, dt_lib_collect_rule_t *dr)
static void get_number_of_rules(dt_lib_collect_t *d)
static void _view_drag_leave(GtkWidget *widget, GdkDragContext *context, guint time, dt_lib_collect_t *d)
static void _action_activate(GtkMenuItem *mi, dt_lib_collect_t *d)
static void _sort_dir_toggled(GtkToggleButton *b, dt_lib_collect_t *d)
static void _on_tab_switch(GtkNotebook *nb, GtkWidget *page, guint page_num, dt_lib_module_t *self)
static void _raw_entry_activated(GtkWidget *entry, dt_lib_collect_t *d)
static void _raw_toggled(GtkToggleButton *b, dt_lib_collect_t *d)
static char ** _split_tree_name(int property, const char *name)
static void _configure_tab(dt_lib_collect_t *d, dt_collect_tab_t tab)
static void _view_row_activated(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col, dt_lib_collect_t *d)
static void _rule_set_mode(int n, int mode)
static int32_t _prerender_job(dt_job_t *job)
static void _split_operator(const char *text, int *op_idx, const char **value)
static gboolean item_is_tree(int item)
static gboolean _drop_move_to_folder(dt_lib_collect_t *d, const char *folder, GList *imgs)
static void tag_changed(gpointer instance, gpointer self)
static gboolean _view_button_pressed(GtkWidget *treeview, GdkEventButton *event, dt_lib_collect_t *d)
static gboolean _en_tags(int property, int n)
static void _build_tree_store(GtkTreeStore *store, int property, GList *sorted_names, gboolean no_uncategorized, const char *format_separator)
static gboolean list_match_string(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
static int string_array_length(char **list)
static gboolean item_is_tag(int item)
static gboolean _view_drag_motion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, dt_lib_collect_t *d)
static void _lib_collect_gui_update(dt_lib_module_t *self)
static gboolean _rule_get_recursive(int n)
static void _force_refresh(dt_lib_collect_t *d)
static void _op_changed(GtkWidget *w, dt_lib_collect_rule_t *dr)
static gboolean _view_popup_menu(GtkWidget *treeview, dt_lib_collect_t *d)
static void _combo_as_view_toggle(GtkWidget *combo)
static gboolean tree_match_string(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
void gui_cleanup(dt_lib_module_t *self)
static void _populate_list(dt_lib_collect_rule_t *dr)
static void row_activated(GtkTreeView *view, GtkTreePath *path, GdkEventButton *event, dt_lib_collect_t *d)
static void tree_count_show(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
static void _show_context_menu(dt_lib_collect_t *d, GdkEventButton *event)
static gboolean _drop_attach_tag(dt_lib_collect_t *d, const char *tagpath, GList *imgs)
static GList * _selected_rows(dt_lib_collect_t *d)
static char * tag_collate_key(char *tag)
static const char *const OP_LABELS[]
static gboolean _do_drop(dt_lib_collect_t *d, GtkTreeView *tree, gint x, gint y, GtkSelectionData *sel)
static void _act_tags_remove(dt_lib_collect_t *d, GList *rows)
static void _no_uncategorized_toggled(GtkToggleButton *b, dt_lib_collect_t *d)
static gboolean _clear_suppress_row_activated(gpointer d)
static gboolean _combo_set_active_collection(GtkWidget *combo, const int property)
static void _lib_collect_update_params(dt_lib_collect_t *d)
static gint sort_folder_tag(gconstpointer a, gconstpointer b)
static void _rule_set_recursive(int n, gboolean recursive)
static void menuitem_mode_change(GtkMenuItem *menuitem, dt_lib_collect_rule_t *dr)
static void entry_activated(GtkWidget *entry, dt_lib_collect_rule_t *dr)
static gboolean item_is_folder(int item)
void init_presets(dt_lib_module_t *self)
static void _rules_set_count(int n)
static GList * _rows_to_imgids(int property, GList *rows, gboolean recursive)
static void _act_folders_remove(dt_lib_collect_t *d, GList *rows)
static void _combo_as_collections(GtkWidget *combo)
static void _view_row_expanded(GtkTreeView *view, GtkTreeIter *iter, GtkTreePath *path, dt_lib_collect_t *d)
static void _prerender_free(void *p)
static dt_lib_collect_t * get_collect(dt_lib_collect_rule_t *r)
#define ADD_COLLECT_ENTRY(value)
uint32_t container(dt_lib_module_t *self)
static gboolean _entry_focus_in(GtkWidget *w, GdkEventFocus *event, dt_lib_collect_rule_t *dr)
static int is_time_property(int property)
static dt_lib_module_t * _self()
static gboolean _restore_expanded_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
static void set_properties(dt_lib_collect_rule_t *dr)
static void entry_changed(GtkEntry *entry, dt_lib_collect_rule_t *dr)
static void _act_tag_rename(dt_lib_collect_t *d, GList *rows)
static void filmrolls_removed(gpointer instance, gpointer self)
static const collect_action_t ACTIONS[]
static gboolean range_select(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
static void menuitem_mode(GtkMenuItem *menuitem, dt_lib_collect_rule_t *dr)
static int _rule_get_item(int n)
static void collection_updated(gpointer instance, dt_collection_change_t query_change, dt_collection_properties_t changed_property, gpointer imgs, int next, gpointer self)
static void preferences_changed(gpointer instance, gpointer self)
static void _act_folders_relocate(dt_lib_collect_t *d, GList *rows)
static void _rule_set_item(int n, int item)
static gboolean popup_button_callback(GtkWidget *widget, GdkEventButton *event, dt_lib_collect_rule_t *dr)
void gui_init(dt_lib_module_t *self)
static GtkTreeModel * _create_filtered_model(GtkTreeModel *model, dt_lib_collect_rule_t *dr)
static void _rule_set_string(int n, const char *s)
static void _commit_colllection()
const char ** views(dt_lib_module_t *self)
static gboolean _maybe_file_uncategorized(GtkTreeStore *store, const char *name, const char *next_name_raw, GtkTreeIter *uncategorized, guint *index, int count)
static gboolean list_select(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
static gboolean _en_folders(int property, int n)
static void combo_changed(GtkWidget *combo, dt_lib_collect_rule_t *dr)
static void tree_set_visibility(GtkTreeModel *model, gpointer data)
static void _set_rule_button(dt_lib_collect_rule_t *dr, gboolean last, gboolean active)
static gchar * _rule_get_string(int n)
static gchar * _ask_text(const char *title, const char *initial)
static dt_lib_collect_rule_t * get_active_rule(dt_lib_collect_t *d)
static void filmrolls_updated(gpointer instance, gpointer self)
static void _strip_recursion_marker(gchar *needle, gchar *haystack, int property)
static void _act_prerender(dt_lib_collect_t *d, GList *rows)
static gboolean _en_any(int property, int n)
static void _commit_quiet()
static void _update_op_combo(dt_lib_collect_rule_t *dr)
static void _recursive_toggled(GtkToggleButton *b, dt_lib_collect_t *d)
static const char *const OP_TOKENS[]
static gboolean item_is_numeric(int item)
static GtkTreePath * _folders_root_collapse_path(GtkTreeModel *model)
static gboolean tree_range_visible(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
static gboolean tree_reveal_func(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
static gboolean _en_tag_single(int property, int n)
static void update_view(dt_lib_collect_rule_t *dr)
static gboolean _en_folders_lighttable(int property, int n)
static void free_tuple(gpointer data)
static void _propagate_count_to_ancestors(GtkTreeStore *store, GtkTreeIter *leaf, int count)
static gboolean _adopt_tag_order(const char *text, int *order)
static void _sort_by_changed(GtkWidget *combo, dt_lib_collect_t *d)
static void _combo_as_full(GtkWidget *combo)
static char ** split_path(const char *path)
static guint64 _date_key(const char *s, char pad)
static int _combo_get_active_collection(GtkWidget *combo)
static gchar * _decorate_hierarchy(gchar *text, GdkEventButton *event)
static void _populate_tree(dt_lib_collect_rule_t *dr)
@ DT_LIB_COLLECT_MODE_AND_NOT
@ DT_LIB_COLLECT_MODE_AND
void dt_collection_update_query(const dt_collection_t *collection, dt_collection_change_t query_change, dt_collection_properties_t changed_property, GList *list)
void dt_collection_set_query_flags(const dt_collection_t *collection, dt_collection_query_flags_t flags)
dt_collection_t * dt_collection_get_global(void)
void dt_collection_memory_update()
void dt_collection_split_operator_number(const gchar *input, char **number1, char **number2, char **operator)
GList * dt_collection_get_images_for_rule(const dt_collection_properties_t property, const char *text, gboolean recursive)
void dt_collection_set_tag_id(dt_collection_t *collection, const uint32_t tagid)
void dt_collection_name_value_free(gpointer value)
GList * dt_collection_get_property_values(const dt_collection_properties_t property, const int rule)
dt_collection_properties_t
@ DT_COLLECTION_PROP_EXPOSURE
@ DT_COLLECTION_PROP_MODULE
@ DT_COLLECTION_PROP_RATING
@ DT_COLLECTION_PROP_QUERY
@ DT_COLLECTION_PROP_TIME
@ DT_COLLECTION_PROP_METADATA
@ DT_COLLECTION_PROP_GROUPING
@ DT_COLLECTION_PROP_FILMROLL
@ DT_COLLECTION_PROP_LENS
@ DT_COLLECTION_PROP_CAMERA
@ DT_COLLECTION_PROP_LOCAL_COPY
@ DT_COLLECTION_PROP_GEOTAGGING
@ DT_COLLECTION_PROP_FILENAME
@ DT_COLLECTION_PROP_COLORLABEL
@ DT_COLLECTION_PROP_UNDEF
@ DT_COLLECTION_PROP_ORDER
@ DT_COLLECTION_PROP_FOLDERS
@ DT_COLLECTION_PROP_APERTURE
@ DT_COLLECTION_PROP_IMPORT_TIMESTAMP
@ DT_COLLECTION_PROP_FOCAL_LENGTH
@ DT_COLLECTION_PROP_EXPORT_TIMESTAMP
@ DT_COLLECTION_PROP_CHANGE_TIMESTAMP
@ DT_COLLECTION_PROP_HISTORY
@ DT_COLLECTION_PROP_PRINT_TIMESTAMP
#define DT_COLLECTION_ORDER_FLAG
#define COLLECTION_QUERY_FULL
@ DT_COLLECTION_CHANGE_RELOAD
@ DT_COLLECTION_CHANGE_NEW_QUERY
@ DT_COLLECTION_SORT_FILENAME
@ DT_COLLECTION_SORT_NONE
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_conf_set_bool(const char *name, int val)
int dt_conf_get_bool(const char *name)
gchar * dt_conf_get_string(const char *name)
Read the stored string for name as a private copy.
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_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
Borrow the stored string for name without copying it.
int32_t dt_image_move(const int32_t imgid, const int32_t filmid)
const char * dt_image_film_roll_name(const char *path)
void dt_image_synch_xmp(const int selected)
void dt_control_log(const char *msg,...)
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
struct dt_control_t * dt_control_get_global(void)
gboolean dt_control_remove_images()
struct dt_view_manager_t * dt_view_manager_get_global(void)
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
GHashTable * names
GtkWidget* -> the name the application knows it by.
GtkTreeStore * store
its model, owned by the view
GtkWidget * folder
destination folder chooser
GtkWidget * status
result of the last capture
GtkWidget * view
the tree view
static const GtkTargetEntry target_list_internal[]
static const guint n_targets_internal
void dt_film_init(dt_film_t *film)
void dt_film_relocate(const char *old_path, const char *new_path)
int dt_film_new(dt_film_t *film, const char *directory)
int32_t dt_film_get_id(const char *folder)
void dt_film_set_folder_status()
void dt_film_cleanup(dt_film_t *film)
dt_job_state_t dt_control_job_get_state(_dt_job_t *job)
dt_job_t * dt_control_job_create(dt_job_execute_callback execute, const char *msg,...)
int dt_control_add_job(dt_control_t *control, dt_job_queue_t queue_id, _dt_job_t *job)
void * dt_control_job_get_params(const _dt_job_t *job)
void dt_control_job_set_progress(dt_job_t *job, double value)
void dt_control_job_add_progress(dt_job_t *job, const char *message, gboolean cancellable)
void dt_control_job_set_params(_dt_job_t *job, void *params, dt_job_destroy_callback callback)
gboolean dt_handle_dialog_enter(GtkWidget *widget, GdkEventKey *event, gpointer data)
void dt_lib_presets_add(const char *name, const char *plugin_name, const int32_t version, const void *params, const int32_t params_size, gboolean readonly)
_lib_location_type_t type
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
void dt_mimap_cache_evict(const int32_t imgid)
void dt_mipmap_get_cache_filename(char path[DT_PATH_MAX], dt_mipmap_size_t mip, const int32_t imgid)
#define dt_mipmap_cache_get(B, C, D, E, F)
#define dt_mipmap_cache_release(B)
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
GtkNotebook * dt_ui_notebook_new()
void dt_osx_disallow_fullscreen(GtkWidget *widget)
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
struct dt_selection_t * dt_selection_get_global(void)
void dt_selection_select_list(struct dt_selection_t *selection, const GList *const l)
void dt_selection_clear(dt_selection_t *selection)
void dt_control_signal_unblock_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data)
void dt_control_signal_block_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data)
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
struct dt_control_signal_t * dt_control_signal_get_global(void)
@ DT_SIGNAL_METADATA_CHANGED
This signal is raised when metadata status (shown/hidden) or value has changed.
@ DT_SIGNAL_IMAGES_ORDER_CHANGE
This signal is raised to request image order change.
@ DT_SIGNAL_FILMROLLS_CHANGED
This signal is raised when a filmroll is deleted/changed but not imported.
@ DT_SIGNAL_GEOTAG_CHANGED
This signal is raised when a geotag is added/deleted/changed
@ DT_SIGNAL_PREFERENCES_CHANGE
This signal is raised after preferences have been changed no parameters no return.
@ DT_SIGNAL_FILMROLLS_REMOVED
This signal is raised only when a filmroll is removed.
@ DT_SIGNAL_TAG_CHANGED
This signal is raised when a tag is added/deleted/changed
@ DT_SIGNAL_COLLECTION_CHANGED
This signal is raised when collection changed. To avoid leaking the list, dt_collection_t is connecte...
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
static const dt_aligned_pixel_simd_t value
const float uint32_t state[4]
gboolean(* enabled)(int property, int n)
void(* run)(dt_lib_collect_t *d, GList *rows)
dt_mipmap_size_t max_size
dt_lib_collect_params_rule_t rule[10]
GtkWidget * recursive_check
struct dt_lib_collect_params_t * params
GtkWidget * collections_controls
GVolumeMonitor * vmonitor
GtkTreeModel * treefilter
dt_lib_collect_rule_t rule[10]
GtkTreeModel * listfilter
GtkWidget * no_uncategorized
GtkWidget * folder_levels
gchar * last_folders_string
gboolean has_last_folders
gboolean last_folders_recursive
GtkWidget * folders_controls
gboolean suppress_row_activated
struct dt_lib_module_t *void(* update)(struct dt_lib_module_t *)
struct dt_view_manager_t::@60 proxy
struct dt_view_manager_t::@60::@62 module_collect
GList * dt_util_str_to_glist(const gchar *separator, const gchar *text)
gboolean dt_util_test_image_file(const char *filename)
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER