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 "widgets/gtkentry.h"
42#include "system/macros.h"
43#include "system/mem_alloc.h"
44
45#include <glib/gi18n.h>
46
57static gboolean on_match_select(GtkEntryCompletion *widget, GtkTreeModel *model, GtkTreeIter *iter,
58 gpointer user_data)
59{
60 const char *trigger_char = (const char *)user_data;
61 const size_t trigger_char_len = strlen(trigger_char);
62
63 const gchar *varname;
64 GtkEditable *e = (GtkEditable *)gtk_entry_completion_get_entry(widget);
65 gchar *s = gtk_editable_get_chars(e, 0, -1);
66 gint cur_pos = gtk_editable_get_position(e);
67 gint p = cur_pos;
68
69 GValue value = {
70 0,
71 };
72 gtk_tree_model_get_value(model, iter, COMPL_VARNAME, &value);
73 varname = g_value_get_string(&value);
74
75 for(p = cur_pos; p - trigger_char_len > 0; p--)
76 {
77 if(strncmp(s + p - trigger_char_len, trigger_char, trigger_char_len) == 0)
78 {
79 break;
80 }
81 }
82
83 size_t text_len = strlen(varname) + trigger_char_len;
84 gchar *addtext = (gchar *)g_malloc(text_len);
85 snprintf(addtext, text_len, "%s)", varname);
86
87 gtk_editable_delete_text(e, p, cur_pos);
88 gtk_editable_insert_text(e, addtext, -1, &p);
89 gtk_editable_set_position(e, p);
90 g_value_unset(&value);
91 dt_free(addtext);
92 dt_free(s);
93 return TRUE;
94}
95
109static gboolean on_match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter,
110 gpointer user_data)
111{
112 gboolean ret = FALSE;
113 const char *trigger_char = (const char *)user_data;
114 const size_t trigger_char_len = strlen(trigger_char);
115
116 GtkEditable *e = (GtkEditable *)gtk_entry_completion_get_entry(completion);
117 gint cur_pos = gtk_editable_get_position(e); /* returns 1..* */
118 gint var_start = 0;
119 gboolean var_present = FALSE;
120
121 for(gint p = cur_pos; p >= 0; p--)
122 {
123 gchar *ss = gtk_editable_get_chars(e, p, cur_pos);
124 if(strncmp(ss, trigger_char, trigger_char_len) == 0)
125 {
126 var_start = p + trigger_char_len;
127 var_present = TRUE;
128 dt_free(ss);
129 break;
130 }
131 dt_free(ss);
132 }
133
134 if(var_present)
135 {
136 gchar *varname = gtk_editable_get_chars(e, var_start, cur_pos);
137
138 GtkTreeModel *model = gtk_entry_completion_get_model(completion);
139 gchar *item = NULL;
140 gtk_tree_model_get(model, iter, COMPL_VARNAME, &item, -1);
141
142 if(!IS_NULL_PTR(item))
143 {
144 // Do utf8-safe case insensitive string compare.
145 // Shamelessly stolen from GtkEntryCompletion.
146 gchar *normalized_string = g_utf8_normalize(item, -1, G_NORMALIZE_ALL);
147
148 if(!IS_NULL_PTR(normalized_string))
149 {
150 gchar *case_normalized_string = g_utf8_casefold(normalized_string, -1);
151
152 if(!g_ascii_strncasecmp(varname, case_normalized_string, strlen(varname))) ret = TRUE;
153
154 dt_free(case_normalized_string);
155 }
156 dt_free(normalized_string);
157 }
158 dt_free(varname);
159 dt_free(item);
160 }
161
162 return ret;
163}
164
176void dt_gtkentry_setup_completion(GtkEntry *entry, const dt_gtkentry_completion_spec *compl_list, const char *trigger_char)
177{
178 GtkEntryCompletion *completion = gtk_entry_completion_new();
179 GtkListStore *model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
180 GtkTreeIter iter;
181
182 gtk_entry_completion_set_text_column(completion, COMPL_DESCRIPTION);
183 gtk_entry_set_completion(entry, completion);
184 g_signal_connect(G_OBJECT(completion), "match-selected", G_CALLBACK(on_match_select), (gpointer)trigger_char);
185
186 /* Populate the completion database. */
187 for(const dt_gtkentry_completion_spec *l = compl_list; l && l->varname; l++)
188 {
189 gtk_list_store_append(model, &iter);
190 gtk_list_store_set(model, &iter, COMPL_VARNAME, l->varname, COMPL_DESCRIPTION, _(l->description), -1);
191 }
192 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(model));
193 gtk_entry_completion_set_match_func(completion, on_match_func, (gpointer)trigger_char, NULL);
194 g_object_unref(model);
195}
196
201{
202 static const dt_gtkentry_completion_spec default_path_compl_list[]
203 = { { "FOLDER.NAME", N_("$(FOLDER.NAME) - name of the folder containing the input image") },
204 { "FILE.FOLDER", N_("$(FILE.FOLDER) - directory structure containing the input image") },
205 { "FILE.NAME", N_("$(FILE.NAME) - basename of the input image") },
206 { "FILE.EXTENSION", N_("$(FILE.EXTENSION) - extension of the input image") },
207 { "FILE.YEAR", N_("$(FILE.YEAR) - year of modification of the input image") },
208 { "FILE.MONTH", N_("$(FILE.MONTH) - month of modification of the input image") },
209 { "FILE.DAY", N_("$(FILE.DAY) - day of modification of the input image") },
210 { "FILE.HOUR", N_("$(FILE.HOUR) - hour of modification of the input image") },
211 { "FILE.MINUTE", N_("$(FILE.MINUTE) - minute of modification of the input image") },
212 { "FILE.SECOND", N_("$(FILE.SECOND) - second of modification of the input image") },
213 { "VERSION", N_("$(VERSION) - duplicate version") },
214 { "VERSION.IF_MULTI", N_("$(VERSION.IF_MULTI) - same as $(VERSION) but null string if only one version exists") },
215 { "VERSION.NAME", N_("$(VERSION.NAME) - version name from metadata") },
216 { "JOBCODE", N_("$(JOBCODE) - job code for import") },
217 { "SEQUENCE", N_("$(SEQUENCE) - sequence number") },
218 { "WIDTH.MAX", N_("$(WIDTH.MAX) - maximum image export width") },
219 { "WIDTH.SENSOR", N_("$(WIDTH.SENSOR) - image sensor width") },
220 { "WIDTH.RAW", N_("$(WIDTH.RAW) - RAW image width") },
221 { "WIDTH.CROP", N_("$(WIDTH.CROP) - image width after crop") },
222 { "WIDTH.EXPORT", N_("$(WIDTH.EXPORT) - exported image width") },
223 { "HEIGHT.MAX", N_("$(HEIGHT.MAX) - maximum image export height") },
224 { "HEIGHT.SENSOR", N_("$(HEIGHT.SENSOR) - image sensor height") },
225 { "HEIGHT.RAW", N_("$(HEIGHT.RAW) - RAW image height") },
226 { "HEIGHT.CROP", N_("$(HEIGHT.CROP) - image height after crop") },
227 { "HEIGHT.EXPORT", N_("$(HEIGHT.EXPORT) - exported image height") },
228 { "YEAR", N_("$(YEAR) - year") },
229 { "YEAR.SHORT", N_("$(YEAR.SHORT) - year without century") },
230 { "MONTH", N_("$(MONTH) - month") },
231 { "MONTH.SHORT", N_("$(MONTH.SHORT) - abbreviated month name according to the current locale") },
232 { "MONTH.LONG", N_("$(MONTH.LONG) - full month name according to the current locale") },
233 { "DAY", N_("$(DAY) - day") },
234 { "HOUR", N_("$(HOUR) - hour") },
235 { "HOUR.AMPM", N_("$(HOUR.AMPM) - hour, 12-hour clock") },
236 { "MINUTE", N_("$(MINUTE) - minute") },
237 { "SECOND", N_("$(SECOND) - second") },
238 { "MSEC", N_("$(MSEC) - millisecond") },
239 { "IMPORT.DATE", N_("$(IMPORT.DATE) - import date (YYYY-MM-DD HH:MM:SS)") },
240 { "CHANGE.DATE", N_("$(CHANGE.DATE) - last change date (YYYY-MM-DD HH:MM:SS)") },
241 { "EXPORT.DATE", N_("$(EXPORT.DATE) - last export date (YYYY-MM-DD HH:MM:SS)") },
242 { "PRINT.DATE", N_("$(PRINT.DATE) - last print date (YYYY-MM-DD HH:MM:SS)") },
243 { "EXIF.YEAR", N_("$(EXIF.YEAR) - EXIF year") },
244 { "EXIF.YEAR.SHORT", N_("$(EXIF.YEAR.SHORT) - EXIF year without century") },
245 { "EXIF.MONTH", N_("$(EXIF.MONTH) - EXIF month") },
246 { "EXIF.MONTH.SHORT", N_("$(EXIF.MONTH.SHORT) - abbreviated EXIF month name according to the current locale") },
247 { "EXIF.MONTH.LONG", N_("$(EXIF.MONTH.LONG) - full EXIF month name according to the current locale") },
248 { "EXIF.DAY", N_("$(EXIF.DAY) - EXIF day") },
249 { "EXIF.HOUR", N_("$(EXIF.HOUR) - EXIF hour") },
250 { "EXIF.HOUR.AMPM", N_("$(EXIF.HOUR.AMPM) - EXIF hour, 12-hour clock") },
251 { "EXIF.MINUTE", N_("$(EXIF.MINUTE) - EXIF minute") },
252 { "EXIF.SECOND", N_("$(EXIF.SECOND) - EXIF second") },
253 { "EXIF.MSEC", N_("$(EXIF.MSEC) - EXIF millisecond") },
254 { "EXIF.ISO", N_("$(EXIF.ISO) - ISO value") },
255 { "EXIF.EXPOSURE", N_("$(EXIF.EXPOSURE) - EXIF exposure") },
256 { "EXIF.EXPOSURE.BIAS", N_("$(EXIF.EXPOSURE.BIAS) - EXIF exposure bias") },
257 { "EXIF.APERTURE", N_("$(EXIF.APERTURE) - EXIF aperture") },
258 { "EXIF.FOCAL.LENGTH", N_("$(EXIF.FOCAL.LENGTH) - EXIF focal length") },
259 { "EXIF.FOCUS.DISTANCE", N_("$(EXIF.FOCUS.DISTANCE) - EXIF focal distance") },
260 { "EXIF.MAKER", N_("$(EXIF.MAKER) - camera maker") },
261 { "EXIF.MODEL", N_("$(EXIF.MODEL) - camera model") },
262 { "EXIF.LENS", N_("$(EXIF.LENS) - lens") },
263 { "LONGITUDE", N_("$(LONGITUDE) - longitude") },
264 { "LATITUDE", N_("$(LATITUDE) - latitude") },
265 { "ELEVATION", N_("$(ELEVATION) - elevation") },
266 { "STARS", N_("$(STARS) - star rating as number (-1 for rejected)") },
267 { "RATING.ICONS", N_("$(RATING.ICONS) - star/reject rating in icon form") },
268 { "LABELS", N_("$(LABELS) - color labels as text") },
269 { "LABELS.ICONS", N_("$(LABELS.ICONS) - color labels as icons") },
270 { "ID", N_("$(ID) - image ID") },
271 { "TITLE", N_("$(TITLE) - title from metadata") },
272 { "DESCRIPTION", N_("$(DESCRIPTION) - description from metadata") },
273 { "CREATOR", N_("$(CREATOR) - creator from metadata") },
274 { "PUBLISHER", N_("$(PUBLISHER) - publisher from metadata") },
275 { "RIGHTS", N_("$(RIGHTS) - rights from metadata") },
276 { "USERNAME", N_("$(USERNAME) - login name") },
277 { "FOLDER.PICTURES", N_("$(FOLDER.PICTURES) - pictures folder") },
278 { "FOLDER.HOME", N_("$(FOLDER.HOME) - home folder") },
279 { "FOLDER.DESKTOP", N_("$(FOLDER.DESKTOP) - desktop folder") },
280 { "OPENCL.ACTIVATED", N_("$(OPENCL.ACTIVATED) - whether OpenCL is activated") },
281 { "CATEGORY", N_("$(CATEGORY0(category)) - subtag of level 0 in hierarchical tags") },
282 { "TAGS", N_("$(TAGS) - tags as set in metadata settings") },
283 { "ANSEL.NAME", N_("$(ANSEL.NAME) - Ansel name") },
284 { "ANSEL.VERSION", N_("$(ANSEL.VERSION) - current Ansel version") },
285 { NULL, NULL } };
286
287 return default_path_compl_list;
288}
289
297 const dt_gtkentry_completion_spec *compl_list)
298{
299 size_t array_len = 0;
300 for(dt_gtkentry_completion_spec const *p = compl_list; !IS_NULL_PTR(p->description); p++) array_len++;
301 const gchar **lines = malloc(sizeof(gchar *) * (array_len + 2));
302 const gchar **l = lines;
303 *l++ = header;
304
305 for(dt_gtkentry_completion_spec const *p = compl_list; !IS_NULL_PTR(p->description); p++, l++)
306 *l = _(p->description);
307
308 *l = NULL;
309
310 gchar *ret = g_strjoinv("\n", (gchar **)lines);
311
312 dt_free(lines);
313
314 return ret;
315}
316
317
318gboolean dt_gui_search_start(GtkWidget *widget, GdkEventKey *event, GtkSearchEntry *entry)
319{
320 if(gtk_search_entry_handle_event(entry, (GdkEvent *)event))
321 {
322 gtk_entry_grab_focus_without_selecting(GTK_ENTRY(entry));
323 return TRUE;
324 }
325
326 return FALSE;
327}
328
329void dt_gui_search_stop(GtkSearchEntry *entry, GtkWidget *widget)
330{
331 gtk_widget_grab_focus(widget);
332
333 gtk_entry_set_text(GTK_ENTRY(entry), "");
334
335 if(GTK_IS_TREE_VIEW(widget))
336 {
337 GtkTreePath *path = NULL;
338 gtk_tree_view_get_cursor(GTK_TREE_VIEW(widget), &path, NULL);
339 gtk_tree_selection_select_path(gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)), path);
340 gtk_tree_path_free(path);
341 }
342}
343
344// clang-format off
345// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
346// vim: shiftwidth=2 expandtab tabstop=2 cindent
347// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
348// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
void dt_gui_search_stop(GtkSearchEntry *entry, GtkWidget *widget)
Definition gtkentry.c:329
gchar * dt_gtkentry_build_completion_tooltip_text(const gchar *header, const dt_gtkentry_completion_spec *compl_list)
Definition gtkentry.c:296
void dt_gtkentry_setup_completion(GtkEntry *entry, const dt_gtkentry_completion_spec *compl_list, const char *trigger_char)
Definition gtkentry.c:176
gboolean dt_gui_search_start(GtkWidget *widget, GdkEventKey *event, GtkSearchEntry *entry)
Definition gtkentry.c:318
static gboolean on_match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer user_data)
Definition gtkentry.c:109
static gboolean on_match_select(GtkEntryCompletion *widget, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
Definition gtkentry.c:57
const dt_gtkentry_completion_spec * dt_gtkentry_get_default_path_compl_list()
Definition gtkentry.c:200
G_BEGIN_DECLS struct completion_spec dt_gtkentry_completion_spec
@ COMPL_DESCRIPTION
Definition gtkentry.h:46
@ COMPL_VARNAME
Definition gtkentry.h:45
const char * model
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
char * key
static const dt_aligned_pixel_simd_t value
Definition simd.h:144