Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
gtkentry.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2012 Christian Tellefsen.
4 Copyright (C) 2012 Jean-Sébastien Pédron.
5 Copyright (C) 2012 johannes hanika.
6 Copyright (C) 2012-2016 Tobias Ellinghaus.
7 Copyright (C) 2013-2014, 2016 Roman Lebedev.
8 Copyright (C) 2013 Thomas Pryds.
9 Copyright (C) 2014 Kevin.
10 Copyright (C) 2015 Šarūnas Burdulis.
11 Copyright (C) 2016 Kael Shipman.
12 Copyright (C) 2019 Denis Dyakov.
13 Copyright (C) 2019-2020, 2022 Pascal Obry.
14 Copyright (C) 2019-2022 Philippe Weyland.
15 Copyright (C) 2020 Chris Elston.
16 Copyright (C) 2020 EdgarLux.
17 Copyright (C) 2020 Heiko Bauke.
18 Copyright (C) 2020 Hubert Kowalski.
19 Copyright (C) 2021 Marco Carrarini.
20 Copyright (C) 2021 Mark-64.
21 Copyright (C) 2021-2022 Ralf Brown.
22 Copyright (C) 2022 Martin Bařinka.
23 Copyright (C) 2022 Victor Forsiuk.
24 Copyright (C) 2023, 2025 Guillaume Stutin.
25 Copyright (C) 2023 Maurizio Paglia.
26 Copyright (C) 2024-2026 Aurélien PIERRE.
27
28 darktable is free software: you can redistribute it and/or modify
29 it under the terms of the GNU General Public License as published by
30 the Free Software Foundation, either version 3 of the License, or
31 (at your option) any later version.
32
33 darktable is distributed in the hope that it will be useful,
34 but WITHOUT ANY WARRANTY; without even the implied warranty of
35 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 GNU General Public License for more details.
37
38 You should have received a copy of the GNU General Public License
39 along with darktable. If not, see <http://www.gnu.org/licenses/>.
40*/
41#include "gtkentry.h"
42#include "common/darktable.h"
43
54static gboolean on_match_select(GtkEntryCompletion *widget, GtkTreeModel *model, GtkTreeIter *iter,
55 gpointer user_data)
56{
57 const char *trigger_char = (const char *)user_data;
58 const size_t trigger_char_len = strnlen(trigger_char, -1);
59
60 const gchar *varname;
61 GtkEditable *e = (GtkEditable *)gtk_entry_completion_get_entry(widget);
62 gchar *s = gtk_editable_get_chars(e, 0, -1);
63 gint cur_pos = gtk_editable_get_position(e);
64 gint p = cur_pos;
65
66 GValue value = {
67 0,
68 };
69 gtk_tree_model_get_value(model, iter, COMPL_VARNAME, &value);
70 varname = g_value_get_string(&value);
71
72 for(p = cur_pos; p - trigger_char_len > 0; p--)
73 {
74 if(strncmp(s + p - trigger_char_len, trigger_char, trigger_char_len) == 0)
75 {
76 break;
77 }
78 }
79
80 size_t text_len = strlen(varname) + trigger_char_len;
81 gchar *addtext = (gchar *)g_malloc(text_len);
82 snprintf(addtext, text_len, "%s)", varname);
83
84 gtk_editable_delete_text(e, p, cur_pos);
85 gtk_editable_insert_text(e, addtext, -1, &p);
86 gtk_editable_set_position(e, p);
87 g_value_unset(&value);
88 dt_free(addtext);
89 dt_free(s);
90 return TRUE;
91}
92
106static gboolean on_match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter,
107 gpointer user_data)
108{
109 gboolean ret = FALSE;
110 const char *trigger_char = (const char *)user_data;
111 const size_t trigger_char_len = strnlen(trigger_char, -1);
112
113 GtkEditable *e = (GtkEditable *)gtk_entry_completion_get_entry(completion);
114 gint cur_pos = gtk_editable_get_position(e); /* returns 1..* */
115 gint var_start = 0;
116 gboolean var_present = FALSE;
117
118 for(gint p = cur_pos; p >= 0; p--)
119 {
120 gchar *ss = gtk_editable_get_chars(e, p, cur_pos);
121 if(strncmp(ss, trigger_char, trigger_char_len) == 0)
122 {
123 var_start = p + trigger_char_len;
124 var_present = TRUE;
125 dt_free(ss);
126 break;
127 }
128 dt_free(ss);
129 }
130
131 if(var_present)
132 {
133 gchar *varname = gtk_editable_get_chars(e, var_start, cur_pos);
134
135 GtkTreeModel *model = gtk_entry_completion_get_model(completion);
136 gchar *item = NULL;
137 gtk_tree_model_get(model, iter, COMPL_VARNAME, &item, -1);
138
139 if(!IS_NULL_PTR(item))
140 {
141 // Do utf8-safe case insensitive string compare.
142 // Shamelessly stolen from GtkEntryCompletion.
143 gchar *normalized_string = g_utf8_normalize(item, -1, G_NORMALIZE_ALL);
144
145 if(!IS_NULL_PTR(normalized_string))
146 {
147 gchar *case_normalized_string = g_utf8_casefold(normalized_string, -1);
148
149 if(!g_ascii_strncasecmp(varname, case_normalized_string, strlen(varname))) ret = TRUE;
150
151 dt_free(case_normalized_string);
152 }
153 dt_free(normalized_string);
154 }
155 dt_free(varname);
156 dt_free(item);
157 }
158
159 return ret;
160}
161
173void dt_gtkentry_setup_completion(GtkEntry *entry, const dt_gtkentry_completion_spec *compl_list, const char *trigger_char)
174{
175 GtkEntryCompletion *completion = gtk_entry_completion_new();
176 GtkListStore *model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
177 GtkTreeIter iter;
178
179 gtk_entry_completion_set_text_column(completion, COMPL_DESCRIPTION);
180 gtk_entry_set_completion(entry, completion);
181 g_signal_connect(G_OBJECT(completion), "match-selected", G_CALLBACK(on_match_select), (gpointer)trigger_char);
182
183 /* Populate the completion database. */
184 for(const dt_gtkentry_completion_spec *l = compl_list; l && l->varname; l++)
185 {
186 gtk_list_store_append(model, &iter);
187 gtk_list_store_set(model, &iter, COMPL_VARNAME, l->varname, COMPL_DESCRIPTION, _(l->description), -1);
188 }
189 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(model));
190 gtk_entry_completion_set_match_func(completion, on_match_func, (gpointer)trigger_char, NULL);
191 g_object_unref(model);
192}
193
198{
199 static dt_gtkentry_completion_spec default_path_compl_list[]
200 = { { "FOLDER.NAME", N_("$(FOLDER.NAME) - name of the folder containing the input image") },
201 { "FILE.FOLDER", N_("$(FILE.FOLDER) - directory structure containing the input image") },
202 { "FILE.NAME", N_("$(FILE.NAME) - basename of the input image") },
203 { "FILE.EXTENSION", N_("$(FILE.EXTENSION) - extension of the input image") },
204 { "FILE.YEAR", N_("$(FILE.YEAR) - year of modification of the input image") },
205 { "FILE.MONTH", N_("$(FILE.MONTH) - month of modification of the input image") },
206 { "FILE.DAY", N_("$(FILE.DAY) - day of modification of the input image") },
207 { "FILE.HOUR", N_("$(FILE.HOUR) - hour of modification of the input image") },
208 { "FILE.MINUTE", N_("$(FILE.MINUTE) - minute of modification of the input image") },
209 { "FILE.SECOND", N_("$(FILE.SECOND) - second of modification of the input image") },
210 { "VERSION", N_("$(VERSION) - duplicate version") },
211 { "VERSION.IF_MULTI", N_("$(VERSION.IF_MULTI) - same as $(VERSION) but null string if only one version exists") },
212 { "VERSION.NAME", N_("$(VERSION.NAME) - version name from metadata") },
213 { "JOBCODE", N_("$(JOBCODE) - job code for import") },
214 { "SEQUENCE", N_("$(SEQUENCE) - sequence number") },
215 { "WIDTH.MAX", N_("$(WIDTH.MAX) - maximum image export width") },
216 { "WIDTH.SENSOR", N_("$(WIDTH.SENSOR) - image sensor width") },
217 { "WIDTH.RAW", N_("$(WIDTH.RAW) - RAW image width") },
218 { "WIDTH.CROP", N_("$(WIDTH.CROP) - image width after crop") },
219 { "WIDTH.EXPORT", N_("$(WIDTH.EXPORT) - exported image width") },
220 { "HEIGHT.MAX", N_("$(HEIGHT.MAX) - maximum image export height") },
221 { "HEIGHT.SENSOR", N_("$(HEIGHT.SENSOR) - image sensor height") },
222 { "HEIGHT.RAW", N_("$(HEIGHT.RAW) - RAW image height") },
223 { "HEIGHT.CROP", N_("$(HEIGHT.CROP) - image height after crop") },
224 { "HEIGHT.EXPORT", N_("$(HEIGHT.EXPORT) - exported image height") },
225 { "YEAR", N_("$(YEAR) - year") },
226 { "YEAR.SHORT", N_("$(YEAR.SHORT) - year without century") },
227 { "MONTH", N_("$(MONTH) - month") },
228 { "MONTH.SHORT", N_("$(MONTH.SHORT) - abbreviated month name according to the current locale") },
229 { "MONTH.LONG", N_("$(MONTH.LONG) - full month name according to the current locale") },
230 { "DAY", N_("$(DAY) - day") },
231 { "HOUR", N_("$(HOUR) - hour") },
232 { "HOUR.AMPM", N_("$(HOUR.AMPM) - hour, 12-hour clock") },
233 { "MINUTE", N_("$(MINUTE) - minute") },
234 { "SECOND", N_("$(SECOND) - second") },
235 { "MSEC", N_("$(MSEC) - millisecond") },
236 { "IMPORT.DATE", N_("$(IMPORT.DATE) - import date (YYYY-MM-DD HH:MM:SS)") },
237 { "CHANGE.DATE", N_("$(CHANGE.DATE) - last change date (YYYY-MM-DD HH:MM:SS)") },
238 { "EXPORT.DATE", N_("$(EXPORT.DATE) - last export date (YYYY-MM-DD HH:MM:SS)") },
239 { "PRINT.DATE", N_("$(PRINT.DATE) - last print date (YYYY-MM-DD HH:MM:SS)") },
240 { "EXIF.YEAR", N_("$(EXIF.YEAR) - EXIF year") },
241 { "EXIF.YEAR.SHORT", N_("$(EXIF.YEAR.SHORT) - EXIF year without century") },
242 { "EXIF.MONTH", N_("$(EXIF.MONTH) - EXIF month") },
243 { "EXIF.MONTH.SHORT", N_("$(EXIF.MONTH.SHORT) - abbreviated EXIF month name according to the current locale") },
244 { "EXIF.MONTH.LONG", N_("$(EXIF.MONTH.LONG) - full EXIF month name according to the current locale") },
245 { "EXIF.DAY", N_("$(EXIF.DAY) - EXIF day") },
246 { "EXIF.HOUR", N_("$(EXIF.HOUR) - EXIF hour") },
247 { "EXIF.HOUR.AMPM", N_("$(EXIF.HOUR.AMPM) - EXIF hour, 12-hour clock") },
248 { "EXIF.MINUTE", N_("$(EXIF.MINUTE) - EXIF minute") },
249 { "EXIF.SECOND", N_("$(EXIF.SECOND) - EXIF second") },
250 { "EXIF.MSEC", N_("$(EXIF.MSEC) - EXIF millisecond") },
251 { "EXIF.ISO", N_("$(EXIF.ISO) - ISO value") },
252 { "EXIF.EXPOSURE", N_("$(EXIF.EXPOSURE) - EXIF exposure") },
253 { "EXIF.EXPOSURE.BIAS", N_("$(EXIF.EXPOSURE.BIAS) - EXIF exposure bias") },
254 { "EXIF.APERTURE", N_("$(EXIF.APERTURE) - EXIF aperture") },
255 { "EXIF.FOCAL.LENGTH", N_("$(EXIF.FOCAL.LENGTH) - EXIF focal length") },
256 { "EXIF.FOCUS.DISTANCE", N_("$(EXIF.FOCUS.DISTANCE) - EXIF focal distance") },
257 { "EXIF.MAKER", N_("$(EXIF.MAKER) - camera maker") },
258 { "EXIF.MODEL", N_("$(EXIF.MODEL) - camera model") },
259 { "EXIF.LENS", N_("$(EXIF.LENS) - lens") },
260 { "LONGITUDE", N_("$(LONGITUDE) - longitude") },
261 { "LATITUDE", N_("$(LATITUDE) - latitude") },
262 { "ELEVATION", N_("$(ELEVATION) - elevation") },
263 { "STARS", N_("$(STARS) - star rating as number (-1 for rejected)") },
264 { "RATING.ICONS", N_("$(RATING.ICONS) - star/reject rating in icon form") },
265 { "LABELS", N_("$(LABELS) - color labels as text") },
266 { "LABELS.ICONS", N_("$(LABELS.ICONS) - color labels as icons") },
267 { "ID", N_("$(ID) - image ID") },
268 { "TITLE", N_("$(TITLE) - title from metadata") },
269 { "DESCRIPTION", N_("$(DESCRIPTION) - description from metadata") },
270 { "CREATOR", N_("$(CREATOR) - creator from metadata") },
271 { "PUBLISHER", N_("$(PUBLISHER) - publisher from metadata") },
272 { "RIGHTS", N_("$(RIGHTS) - rights from metadata") },
273 { "USERNAME", N_("$(USERNAME) - login name") },
274 { "FOLDER.PICTURES", N_("$(FOLDER.PICTURES) - pictures folder") },
275 { "FOLDER.HOME", N_("$(FOLDER.HOME) - home folder") },
276 { "FOLDER.DESKTOP", N_("$(FOLDER.DESKTOP) - desktop folder") },
277 { "OPENCL.ACTIVATED", N_("$(OPENCL.ACTIVATED) - whether OpenCL is activated") },
278 { "CATEGORY", N_("$(CATEGORY0(category)) - subtag of level 0 in hierarchical tags") },
279 { "TAGS", N_("$(TAGS) - tags as set in metadata settings") },
280 { "ANSEL.NAME", N_("$(ANSEL.NAME) - Ansel name") },
281 { "ANSEL.VERSION", N_("$(ANSEL.VERSION) - current Ansel version") },
282 { NULL, NULL } };
283
284 return default_path_compl_list;
285}
286
294 const dt_gtkentry_completion_spec *compl_list)
295{
296 size_t array_len = 0;
297 for(dt_gtkentry_completion_spec const *p = compl_list; !IS_NULL_PTR(p->description); p++) array_len++;
298 const gchar **lines = malloc(sizeof(gchar *) * (array_len + 2));
299 const gchar **l = lines;
300 *l++ = header;
301
302 for(dt_gtkentry_completion_spec const *p = compl_list; !IS_NULL_PTR(p->description); p++, l++)
303 *l = _(p->description);
304
305 *l = NULL;
306
307 gchar *ret = g_strjoinv("\n", (gchar **)lines);
308
309 dt_free(lines);
310
311 return ret;
312}
313
314
315// clang-format off
316// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
317// vim: shiftwidth=2 expandtab tabstop=2 cindent
318// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
319// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
char * key
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#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
gchar * dt_gtkentry_build_completion_tooltip_text(const gchar *header, const dt_gtkentry_completion_spec *compl_list)
Definition gtkentry.c:293
void dt_gtkentry_setup_completion(GtkEntry *entry, const dt_gtkentry_completion_spec *compl_list, const char *trigger_char)
Definition gtkentry.c:173
static gboolean on_match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer user_data)
Definition gtkentry.c:106
static gboolean on_match_select(GtkEntryCompletion *widget, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
Definition gtkentry.c:54
const dt_gtkentry_completion_spec * dt_gtkentry_get_default_path_compl_list()
Definition gtkentry.c:197
@ COMPL_DESCRIPTION
Definition gtkentry.h:43
@ COMPL_VARNAME
Definition gtkentry.h:42
const char * model
gchar * varname
Definition gtkentry.h:36