1 /*
2    This file is part of darktable,
3    Copyright (C) 2013-2021 darktable developers.
4 
5    darktable is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9 
10    darktable is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with darktable.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 #include "lua/preferences.h"
19 #include "control/conf.h"
20 #include "gui/gtk.h"
21 #include "lua/call.h"
22 #include "lua/widget/widget.h"
23 #include <glib.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 typedef enum
28 {
29   pref_enum,
30   pref_dir,
31   pref_file,
32   pref_string,
33   pref_bool,
34   pref_int,
35   pref_float,
36   pref_lua,
37 } lua_pref_type;
38 
39 
40 typedef struct enum_data_t
41 {
42   char *default_value;
43   luaA_Type enum_type;
44 } enum_data_t;
45 typedef struct dir_data_t
46 {
47   char *default_value;
48 } dir_data_t;
49 typedef struct file_data_t
50 {
51   char *default_value;
52 } file_data_t;
53 typedef struct string_data_t
54 {
55   char *default_value;
56 } string_data_t;
57 typedef struct bool_data_t
58 {
59   gboolean default_value;
60 } bool_data_t;
61 typedef struct int_data_t
62 {
63   int default_value;
64 } int_data_t;
65 
66 typedef struct float_data_t
67 {
68   float default_value;
69 } float_data_t;
70 typedef struct lua_data_t
71 {
72   char *default_value;
73 } lua_data_t;
74 
75 typedef union all_data_t
76 {
77   enum_data_t enum_data;
78   dir_data_t dir_data;
79   file_data_t file_data;
80   string_data_t string_data;
81   bool_data_t bool_data;
82   int_data_t int_data;
83   float_data_t float_data;
84   lua_data_t lua_data;
85 } all_data_t;
86 
87 struct pref_element;
88 typedef void (update_widget_function)(struct pref_element* , GtkWidget* , GtkWidget* );
89 static void update_widget_enum(struct pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev);
90 static void update_widget_dir(struct pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev);
91 static void update_widget_file(struct pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev);
92 static void update_widget_string(struct pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev);
93 static void update_widget_bool(struct pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev);
94 static void update_widget_int(struct pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev);
95 static void update_widget_float(struct pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev);
96 static void update_widget_lua(struct pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev);
97 
98 typedef struct pref_element
99 {
100   char *script;
101   char *name;
102   char *label;
103   char *tooltip;
104   char *tooltip_reset;
105   lua_pref_type type;
106   struct pref_element *next;
107   all_data_t type_data;
108   // The widget used for this preference
109   GtkWidget *widget;
110   update_widget_function* update_widget;
111 
112 
113 } pref_element;
114 
destroy_pref_element(pref_element * elt)115 static void destroy_pref_element(pref_element *elt)
116 {
117   free(elt->script);
118   free(elt->name);
119   free(elt->label);
120   free(elt->tooltip);
121   free(elt->tooltip_reset);
122   if(elt->widget) g_object_unref(elt->widget);
123   switch(elt->type)
124   {
125     case pref_enum:
126       free(elt->type_data.enum_data.default_value);
127       break;
128     case pref_dir:
129       free(elt->type_data.dir_data.default_value);
130       break;
131     case pref_file:
132       free(elt->type_data.file_data.default_value);
133       break;
134     case pref_string:
135       free(elt->type_data.string_data.default_value);
136       break;
137     case pref_lua:
138       free(elt->type_data.lua_data.default_value);
139       break;
140     case pref_bool:
141     case pref_int:
142     case pref_float:
143     default:
144       break;
145   }
146   free(elt);
147 }
148 
149 static pref_element *pref_list = NULL;
150 
151 // get all the darktablerc keys
get_keys(lua_State * L)152 static int get_keys(lua_State *L)
153 {
154   dt_pthread_mutex_lock(&darktable.conf->mutex);
155   GList* keys = g_hash_table_get_keys(darktable.conf->table);
156   dt_pthread_mutex_unlock(&darktable.conf->mutex);
157 
158   keys = g_list_sort(keys, (GCompareFunc) strcmp);
159   lua_newtable(L);
160   int table_index = 1;
161   for(const GList* key = keys; key; key = g_list_next(key))
162   {
163     lua_pushstring(L, key->data);
164     lua_seti(L, -2, table_index);
165     table_index++;
166   }
167   g_list_free(keys);
168   return 1;
169 }
170 
get_pref_name(char * tgt,size_t size,const char * script,const char * name)171 static void get_pref_name(char *tgt, size_t size, const char *script, const char *name)
172 {
173   snprintf(tgt, size, "lua/%s/%s", script, name);
174 }
175 
176 // read lua and darktable prefs
read_pref(lua_State * L)177 static int read_pref(lua_State *L)
178 {
179   const char *script = luaL_checkstring(L, 1);
180   const char *name = luaL_checkstring(L, 2);
181   lua_pref_type i;
182   luaA_to(L, lua_pref_type, &i, 3);
183 
184   char pref_name[1024];
185   if(strcmp(script, "darktable") != 0)
186     get_pref_name(pref_name, sizeof(pref_name), script, name);
187   else
188     snprintf(pref_name, sizeof(pref_name), "%s", name);
189   switch(i)
190   {
191     case pref_enum:
192     {
193       const char *str = dt_conf_get_string_const(pref_name);
194       lua_pushstring(L, str);
195       break;
196     }
197     case pref_dir:
198     {
199       const char *str = dt_conf_get_string_const(pref_name);
200       lua_pushstring(L, str);
201       break;
202     }
203     case pref_file:
204     {
205       const char *str = dt_conf_get_string_const(pref_name);
206       lua_pushstring(L, str);
207       break;
208     }
209     case pref_string:
210     {
211       const char *str = dt_conf_get_string_const(pref_name);
212       lua_pushstring(L, str);
213       break;
214     }
215     case pref_bool:
216       lua_pushboolean(L, dt_conf_get_bool(pref_name));
217       break;
218     case pref_int:
219       lua_pushinteger(L, dt_conf_get_int(pref_name));
220       break;
221     case pref_float:
222       lua_pushnumber(L, dt_conf_get_float(pref_name));
223       break;
224     case pref_lua:
225     {
226       const char *str = dt_conf_get_string_const(pref_name);
227       lua_pushstring(L, str);
228       break;
229     }
230   }
231   return 1;
232 }
233 
234 // write lua prefs
write_pref(lua_State * L)235 static int write_pref(lua_State *L)
236 {
237   const char *script = luaL_checkstring(L, 1);
238   const char *name = luaL_checkstring(L, 2);
239   int i, tmp;
240   luaA_to(L, lua_pref_type, &i, 3);
241 
242   char pref_name[1024];
243   get_pref_name(pref_name, sizeof(pref_name), script, name);
244   switch(i)
245   {
246     case pref_enum:
247       luaA_to_type(L, luaA_type_find(L,pref_name), &tmp, 4);
248       dt_conf_set_string(pref_name, lua_tostring(L, 4));
249       break;
250     case pref_dir:
251       dt_conf_set_string(pref_name, luaL_checkstring(L, 4));
252       break;
253     case pref_file:
254       dt_conf_set_string(pref_name, luaL_checkstring(L, 4));
255       break;
256     case pref_string:
257       dt_conf_set_string(pref_name, luaL_checkstring(L, 4));
258       break;
259     case pref_bool:
260       luaL_checktype(L, 4, LUA_TBOOLEAN);
261       dt_conf_set_bool(pref_name, lua_toboolean(L, 4));
262       break;
263     case pref_int:
264       dt_conf_set_int(pref_name, luaL_checkinteger(L, 4));
265       break;
266     case pref_float:
267       dt_conf_set_float(pref_name, luaL_checknumber(L, 4));
268       break;
269     case pref_lua:
270       dt_conf_set_string(pref_name, luaL_checkstring(L, 4));
271       break;
272   }
273   return 0;
274 }
275 
276 // destroy lua prefs
destroy_pref(lua_State * L)277 static int destroy_pref(lua_State *L)
278 {
279   gboolean result;
280   const char *script = luaL_checkstring(L, 1);
281   const char *name = luaL_checkstring(L, 2);
282 
283   char pref_name[1024];
284   get_pref_name(pref_name, sizeof(pref_name), script, name);
285   dt_pthread_mutex_lock(&darktable.conf->mutex);
286   result = g_hash_table_remove(darktable.conf->table, pref_name);
287   dt_pthread_mutex_unlock(&darktable.conf->mutex);
288   lua_pushboolean(L, result);
289   return 1;
290 }
291 
response_callback_enum(GtkDialog * dialog,gint response_id,pref_element * cur_elt)292 static void response_callback_enum(GtkDialog *dialog, gint response_id, pref_element *cur_elt)
293 {
294   if(response_id == GTK_RESPONSE_DELETE_EVENT)
295   {
296     char pref_name[1024];
297     get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
298     char *text = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cur_elt->widget));
299     dt_conf_set_string(pref_name, text);
300     g_free(text);
301   }
302 }
303 
304 
response_callback_dir(GtkDialog * dialog,gint response_id,pref_element * cur_elt)305 static void response_callback_dir(GtkDialog *dialog, gint response_id, pref_element *cur_elt)
306 {
307   if(response_id == GTK_RESPONSE_DELETE_EVENT)
308   {
309     char pref_name[1024];
310     get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
311     gchar *folder = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(cur_elt->widget));
312     dt_conf_set_string(pref_name, folder);
313     g_free(folder);
314   }
315 }
316 
317 
response_callback_file(GtkDialog * dialog,gint response_id,pref_element * cur_elt)318 static void response_callback_file(GtkDialog *dialog, gint response_id, pref_element *cur_elt)
319 {
320   if(response_id == GTK_RESPONSE_DELETE_EVENT)
321   {
322     char pref_name[1024];
323     get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
324     gchar *file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(cur_elt->widget));
325     dt_conf_set_string(pref_name, file);
326     g_free(file);
327   }
328 }
329 
330 
response_callback_string(GtkDialog * dialog,gint response_id,pref_element * cur_elt)331 static void response_callback_string(GtkDialog *dialog, gint response_id, pref_element *cur_elt)
332 {
333   if(response_id == GTK_RESPONSE_DELETE_EVENT)
334   {
335     char pref_name[1024];
336     get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
337     dt_conf_set_string(pref_name, gtk_entry_get_text(GTK_ENTRY(cur_elt->widget)));
338   }
339 }
340 
341 
response_callback_bool(GtkDialog * dialog,gint response_id,pref_element * cur_elt)342 static void response_callback_bool(GtkDialog *dialog, gint response_id, pref_element *cur_elt)
343 {
344   if(response_id == GTK_RESPONSE_DELETE_EVENT)
345   {
346     char pref_name[1024];
347     get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
348     dt_conf_set_bool(pref_name, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cur_elt->widget)));
349   }
350 }
351 
352 
response_callback_int(GtkDialog * dialog,gint response_id,pref_element * cur_elt)353 static void response_callback_int(GtkDialog *dialog, gint response_id, pref_element *cur_elt)
354 {
355   if(response_id == GTK_RESPONSE_DELETE_EVENT)
356   {
357     char pref_name[1024];
358     get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
359     dt_conf_set_int(pref_name, gtk_spin_button_get_value(GTK_SPIN_BUTTON(cur_elt->widget)));
360   }
361 }
362 
363 
response_callback_float(GtkDialog * dialog,gint response_id,pref_element * cur_elt)364 static void response_callback_float(GtkDialog *dialog, gint response_id, pref_element *cur_elt)
365 {
366   if(response_id == GTK_RESPONSE_DELETE_EVENT)
367   {
368     char pref_name[1024];
369     get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
370     dt_conf_set_float(pref_name, gtk_spin_button_get_value(GTK_SPIN_BUTTON(cur_elt->widget)));
371   }
372 }
373 
374 
response_callback_lua(GtkDialog * dialog,gint response_id,pref_element * cur_elt)375 static void response_callback_lua(GtkDialog *dialog, gint response_id, pref_element *cur_elt)
376 {
377   if(response_id == GTK_RESPONSE_DELETE_EVENT)
378   {
379     dt_lua_lock_silent();
380     lua_State * L = darktable.lua_state.state;
381     lua_pushcfunction(L, dt_lua_widget_trigger_callback);
382     luaA_push(L, lua_widget, &cur_elt->widget);
383     lua_pushstring(L, "set_pref");
384     lua_call(L, 2, 0);
385     dt_lua_unlock();
386   }
387 }
388 
389 
reset_widget_enum(GtkWidget * label,GdkEventButton * event,pref_element * cur_elt)390 static gboolean reset_widget_enum(GtkWidget *label, GdkEventButton *event, pref_element *cur_elt)
391 {
392   if(event->type == GDK_2BUTTON_PRESS)
393   {
394     gtk_combo_box_set_active(GTK_COMBO_BOX(cur_elt->widget), 0);
395     return TRUE;
396   }
397   return FALSE;
398 }
399 
400 
reset_widget_dir(GtkWidget * label,GdkEventButton * event,pref_element * cur_elt)401 static gboolean reset_widget_dir(GtkWidget *label, GdkEventButton *event, pref_element *cur_elt)
402 {
403   if(event->type == GDK_2BUTTON_PRESS)
404   {
405     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(cur_elt->widget),
406                                         cur_elt->type_data.dir_data.default_value);
407     return TRUE;
408   }
409   return FALSE;
410 }
411 
412 
reset_widget_file(GtkWidget * label,GdkEventButton * event,pref_element * cur_elt)413 static gboolean reset_widget_file(GtkWidget *label, GdkEventButton *event, pref_element *cur_elt)
414 {
415   if(event->type == GDK_2BUTTON_PRESS)
416   {
417     gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(cur_elt->widget),
418                                   cur_elt->type_data.file_data.default_value);
419     return TRUE;
420   }
421   return FALSE;
422 }
423 
424 
reset_widget_string(GtkWidget * label,GdkEventButton * event,pref_element * cur_elt)425 static gboolean reset_widget_string(GtkWidget *label, GdkEventButton *event, pref_element *cur_elt)
426 {
427   if(event->type == GDK_2BUTTON_PRESS)
428   {
429     gtk_entry_set_text(GTK_ENTRY(cur_elt->widget), cur_elt->type_data.string_data.default_value);
430     return TRUE;
431   }
432   return FALSE;
433 }
434 
435 
reset_widget_bool(GtkWidget * label,GdkEventButton * event,pref_element * cur_elt)436 static gboolean reset_widget_bool(GtkWidget *label, GdkEventButton *event, pref_element *cur_elt)
437 {
438   if(event->type == GDK_2BUTTON_PRESS)
439   {
440     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cur_elt->widget),
441                                  cur_elt->type_data.bool_data.default_value);
442     return TRUE;
443   }
444   return FALSE;
445 }
446 
447 
reset_widget_int(GtkWidget * label,GdkEventButton * event,pref_element * cur_elt)448 static gboolean reset_widget_int(GtkWidget *label, GdkEventButton *event, pref_element *cur_elt)
449 {
450   if(event->type == GDK_2BUTTON_PRESS)
451   {
452     gtk_spin_button_set_value(GTK_SPIN_BUTTON(cur_elt->widget), cur_elt->type_data.int_data.default_value);
453     return TRUE;
454   }
455   return FALSE;
456 }
457 
458 
reset_widget_float(GtkWidget * label,GdkEventButton * event,pref_element * cur_elt)459 static gboolean reset_widget_float(GtkWidget *label, GdkEventButton *event, pref_element *cur_elt)
460 {
461   if(event->type == GDK_2BUTTON_PRESS)
462   {
463     gtk_spin_button_set_value(GTK_SPIN_BUTTON(cur_elt->widget), cur_elt->type_data.float_data.default_value);
464     return TRUE;
465   }
466   return FALSE;
467 }
468 
469 
reset_widget_lua(GtkWidget * label,GdkEventButton * event,pref_element * cur_elt)470 static gboolean reset_widget_lua(GtkWidget *label, GdkEventButton *event, pref_element *cur_elt)
471 {
472   if(event->type == GDK_2BUTTON_PRESS)
473   {
474     char pref_name[1024];
475     get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
476     gchar *old_str = dt_conf_get_string(pref_name);
477     dt_conf_set_string(pref_name, cur_elt->type_data.lua_data.default_value);
478     dt_lua_lock_silent();
479     lua_State * L = darktable.lua_state.state;
480     lua_pushcfunction(L, dt_lua_widget_trigger_callback);
481     luaA_push(L, lua_widget, &cur_elt->widget);
482     luaA_push(L, lua_widget, &cur_elt->widget);
483     lua_pushstring(L, "set_pref");
484     lua_call(L, 3, 0);
485     dt_lua_unlock();
486     dt_conf_set_string(pref_name, old_str);
487     g_free(old_str);
488     return TRUE;
489   }
490   return FALSE;
491 }
492 
493 
update_widget_enum(pref_element * cur_elt,GtkWidget * dialog,GtkWidget * labelev)494 static void update_widget_enum(pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev)
495 {
496   char pref_name[1024];
497   get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
498   g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(reset_widget_enum), cur_elt);
499   g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(response_callback_enum), cur_elt);
500   gtk_combo_box_set_active(GTK_COMBO_BOX(cur_elt->widget), 0);
501   const char *value = dt_conf_get_string_const(pref_name);
502   do {
503     char * active_entry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cur_elt->widget));
504     if(!active_entry)
505     {
506       gtk_combo_box_set_active(GTK_COMBO_BOX(cur_elt->widget), -1);
507       g_free(active_entry);
508       break;
509     }
510     else if(!strcmp(active_entry, value))
511     {
512       g_free(active_entry);
513       break;
514     }
515     else
516     {
517       gtk_combo_box_set_active(GTK_COMBO_BOX(cur_elt->widget), gtk_combo_box_get_active(GTK_COMBO_BOX(cur_elt->widget)) + 1);
518       g_free(active_entry);
519     }
520   } while(true);
521 }
522 
523 
update_widget_dir(pref_element * cur_elt,GtkWidget * dialog,GtkWidget * labelev)524 static void update_widget_dir(pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev)
525 {
526   char pref_name[1024];
527   get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
528   const char *str = dt_conf_get_string_const(pref_name);
529   gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(cur_elt->widget), str);
530   g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(reset_widget_dir), cur_elt);
531   g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(response_callback_dir), cur_elt);
532 }
533 
534 
update_widget_file(pref_element * cur_elt,GtkWidget * dialog,GtkWidget * labelev)535 static void update_widget_file(pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev)
536 {
537   char pref_name[1024];
538   get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
539   const char *str = dt_conf_get_string_const(pref_name);
540   gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(cur_elt->widget), str);
541   g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(reset_widget_file), cur_elt);
542   g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(response_callback_file), cur_elt);
543 }
544 
545 
update_widget_string(pref_element * cur_elt,GtkWidget * dialog,GtkWidget * labelev)546 static void update_widget_string(pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev)
547 {
548   char pref_name[1024];
549   get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
550   g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(reset_widget_string), cur_elt);
551   g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(response_callback_string), cur_elt);
552   const char *str = dt_conf_get_string_const(pref_name);
553   gtk_entry_set_text(GTK_ENTRY(cur_elt->widget), str);
554 }
555 
556 
update_widget_bool(pref_element * cur_elt,GtkWidget * dialog,GtkWidget * labelev)557 static void update_widget_bool(pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev)
558 {
559   char pref_name[1024];
560   get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
561   g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(reset_widget_bool), cur_elt);
562   g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(response_callback_bool), cur_elt);
563   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cur_elt->widget), dt_conf_get_bool(pref_name));
564 }
565 
566 
update_widget_int(pref_element * cur_elt,GtkWidget * dialog,GtkWidget * labelev)567 static void update_widget_int(pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev)
568 {
569   char pref_name[1024];
570   get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
571   gtk_spin_button_set_value(GTK_SPIN_BUTTON(cur_elt->widget), dt_conf_get_int(pref_name));
572   g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(reset_widget_int), cur_elt);
573   g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(response_callback_int), cur_elt);
574 }
575 
576 
update_widget_float(pref_element * cur_elt,GtkWidget * dialog,GtkWidget * labelev)577 static void update_widget_float(pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev)
578 {
579   char pref_name[1024];
580   get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
581   gtk_spin_button_set_value(GTK_SPIN_BUTTON(cur_elt->widget), dt_conf_get_float(pref_name));
582   g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(reset_widget_float), cur_elt);
583   g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(response_callback_float), cur_elt);
584 }
585 
586 
update_widget_lua(pref_element * cur_elt,GtkWidget * dialog,GtkWidget * labelev)587 static void update_widget_lua(pref_element* cur_elt, GtkWidget* dialog, GtkWidget* labelev)
588 {
589   dt_lua_lock_silent();
590   lua_State * L = darktable.lua_state.state;
591   lua_pushcfunction(L, dt_lua_widget_trigger_callback);
592   luaA_push(L, lua_widget, &cur_elt->widget);
593   lua_pushstring(L, "reset");
594   lua_call(L, 2, 0);
595   dt_lua_unlock();
596   g_signal_connect(G_OBJECT(labelev), "button-press-event", G_CALLBACK(reset_widget_lua), cur_elt);
597   g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(response_callback_lua), cur_elt);
598 }
599 
600 
register_pref_sub(lua_State * L)601 static int register_pref_sub(lua_State *L)
602 {
603   // to avoid leaks, we need to first get all params (which could raise errors) then alloc and fill the struct
604   // this is complicated, but needed
605   pref_element **tmp = lua_touserdata(L, -1);
606   lua_pop(L, 1);
607   *tmp = calloc(1, sizeof(pref_element));
608   pref_element *built_elt = *tmp;
609   int cur_param = 1;
610 
611   built_elt->script = strdup(luaL_checkstring(L, cur_param));
612   cur_param++;
613 
614   built_elt->name = strdup(luaL_checkstring(L, cur_param));
615   cur_param++;
616 
617   luaA_to(L, lua_pref_type, &built_elt->type, cur_param);
618   cur_param++;
619 
620   built_elt->label = strdup(luaL_checkstring(L, cur_param));
621   cur_param++;
622 
623   built_elt->tooltip = strdup(luaL_checkstring(L, cur_param));
624   cur_param++;
625 
626   char pref_name[1024];
627   get_pref_name(pref_name, sizeof(pref_name), built_elt->script, built_elt->name);
628   switch(built_elt->type)
629   {
630     case pref_enum:
631     {
632       luaA_Type enum_type = luaA_type_add(L, pref_name, sizeof(int));
633       luaA_enum_type(L, enum_type, sizeof(int));
634       built_elt->type_data.enum_data.enum_type = enum_type;
635       built_elt->widget = gtk_combo_box_text_new();
636 
637       int value = 0;
638       built_elt->type_data.enum_data.default_value = strdup(luaL_checkstring(L, cur_param));
639       while(!lua_isnoneornil(L, cur_param))
640       {
641         luaA_enum_value_type(L, enum_type, &value, luaL_checkstring(L, cur_param));
642         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(built_elt->widget), luaL_checkstring(L, cur_param));
643         cur_param++;
644         value++;
645       }
646 
647       if(!dt_conf_key_exists(pref_name)) {
648         dt_conf_set_string(pref_name, built_elt->type_data.enum_data.default_value);
649       }
650 
651 
652       g_object_ref_sink(G_OBJECT(built_elt->widget));
653       built_elt->tooltip_reset = g_strdup_printf(  _("double click to reset to `%s'"),
654           built_elt->type_data.enum_data.default_value);
655       built_elt->update_widget = update_widget_enum;
656       break;
657     }
658     case pref_dir:
659       built_elt->type_data.dir_data.default_value = strdup(luaL_checkstring(L, cur_param));
660       cur_param++;
661 
662       if(!dt_conf_key_exists(pref_name)) {
663         dt_conf_set_string(pref_name, built_elt->type_data.dir_data.default_value);
664       }
665       built_elt->widget = gtk_file_chooser_button_new(_("select directory"), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
666       gtk_file_chooser_button_set_width_chars(GTK_FILE_CHOOSER_BUTTON(built_elt->widget), 20);
667       g_object_ref_sink(G_OBJECT(built_elt->widget));
668       built_elt->tooltip_reset = g_strdup_printf( _("double click to reset to `%s'"), built_elt->type_data.dir_data.default_value);
669       built_elt->update_widget = update_widget_dir;
670       break;
671     case pref_file:
672       built_elt->type_data.file_data.default_value = strdup(luaL_checkstring(L, cur_param));
673       cur_param++;
674 
675       if(!dt_conf_key_exists(pref_name))
676         dt_conf_set_string(pref_name, built_elt->type_data.file_data.default_value);
677 
678       built_elt->widget = gtk_file_chooser_button_new(_("select file"), GTK_FILE_CHOOSER_ACTION_OPEN);
679       gtk_file_chooser_button_set_width_chars(GTK_FILE_CHOOSER_BUTTON(built_elt->widget), 20);
680       built_elt->tooltip_reset= g_strdup_printf( _("double click to reset to `%s'"), built_elt->type_data.file_data.default_value);
681       g_object_ref_sink(G_OBJECT(built_elt->widget));
682       built_elt->update_widget = update_widget_file;
683       break;
684     case pref_string:
685       built_elt->type_data.string_data.default_value = strdup(luaL_checkstring(L, cur_param));
686       cur_param++;
687 
688       if(!dt_conf_key_exists(pref_name))
689         dt_conf_set_string(pref_name, built_elt->type_data.string_data.default_value);
690 
691       built_elt->widget = gtk_entry_new();
692       built_elt->tooltip_reset= g_strdup_printf( _("double click to reset to `%s'"),
693           built_elt->type_data.string_data.default_value);
694       g_object_ref_sink(G_OBJECT(built_elt->widget));
695       built_elt->update_widget = update_widget_string;
696       break;
697     case pref_bool:
698       luaL_checktype(L, cur_param, LUA_TBOOLEAN);
699       built_elt->type_data.bool_data.default_value = lua_toboolean(L, cur_param);
700       cur_param++;
701 
702       if(!dt_conf_key_exists(pref_name))
703         dt_conf_set_bool(pref_name, built_elt->type_data.bool_data.default_value);
704 
705       built_elt->widget = gtk_check_button_new();
706       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(built_elt->widget), dt_conf_get_bool(pref_name));
707       g_object_ref_sink(G_OBJECT(built_elt->widget));
708       built_elt->tooltip_reset = g_strdup_printf(  _("double click to reset to `%s'"),
709           built_elt->type_data.bool_data.default_value ? "true" : "false");
710       built_elt->update_widget = update_widget_bool;
711       break;
712     case pref_int:
713       {
714         luaL_checktype(L, cur_param, LUA_TNUMBER);
715         built_elt->type_data.int_data.default_value = lua_tointeger(L, cur_param);
716         cur_param++;
717 
718         luaL_checktype(L, cur_param, LUA_TNUMBER);
719         int min = lua_tointeger(L, cur_param);
720         cur_param++;
721 
722         luaL_checktype(L, cur_param, LUA_TNUMBER);
723         int max = lua_tointeger(L, cur_param);
724         cur_param++;
725 
726         if(!dt_conf_key_exists(pref_name))
727           dt_conf_set_int(pref_name, built_elt->type_data.int_data.default_value);
728         built_elt->widget = gtk_spin_button_new_with_range(min, max, 1);
729         gtk_spin_button_set_digits(GTK_SPIN_BUTTON(built_elt->widget), 0);
730         g_object_ref_sink(G_OBJECT(built_elt->widget));
731         built_elt->tooltip_reset = g_strdup_printf( _("double click to reset to `%d'"),
732             built_elt->type_data.int_data.default_value);
733         built_elt->update_widget = update_widget_int;
734         break;
735       }
736     case pref_float:
737       {
738         luaL_checktype(L, cur_param, LUA_TNUMBER);
739         built_elt->type_data.float_data.default_value = lua_tonumber(L, cur_param);
740         cur_param++;
741 
742         luaL_checktype(L, cur_param, LUA_TNUMBER);
743         float min = lua_tonumber(L, cur_param);
744         cur_param++;
745 
746         luaL_checktype(L, cur_param, LUA_TNUMBER);
747         float max = lua_tonumber(L, cur_param);
748         cur_param++;
749 
750         luaL_checktype(L, cur_param, LUA_TNUMBER);
751         float step = lua_tonumber(L, cur_param);
752         cur_param++;
753 
754         if(!dt_conf_key_exists(pref_name))
755           dt_conf_set_float(pref_name, built_elt->type_data.float_data.default_value);
756 
757         built_elt->widget = gtk_spin_button_new_with_range(min, max, step);
758         built_elt->tooltip_reset = g_strdup_printf( _("double click to reset to `%f'"),
759             built_elt->type_data.float_data.default_value);
760         g_object_ref_sink(G_OBJECT(built_elt->widget));
761         built_elt->update_widget = update_widget_float;
762         break;
763       }
764     case pref_lua:
765       {
766         built_elt->type_data.lua_data.default_value = strdup(luaL_checkstring(L, cur_param));
767         cur_param++;
768 
769         if(!dt_conf_key_exists(pref_name))
770           dt_conf_set_string(pref_name, built_elt->type_data.lua_data.default_value);
771 
772         built_elt->tooltip_reset= g_strdup_printf( _("double click to reset to `%s'"),
773             built_elt->type_data.lua_data.default_value);
774 
775         lua_widget widget;
776         luaA_to(L, lua_widget, &widget, cur_param);
777         cur_param++;
778         dt_lua_widget_bind(L, widget);
779         built_elt->widget = widget->widget;
780         built_elt->update_widget = update_widget_lua;
781 
782         luaL_checktype(L, cur_param, LUA_TFUNCTION);
783         luaA_push(L, lua_widget, widget);
784         lua_pushvalue(L, cur_param);
785         dt_lua_widget_set_callback(L, -2, "set_pref");
786         lua_pop(L,1);
787 
788         break;
789       }
790   }
791   return 0;
792 }
793 
794 
register_pref(lua_State * L)795 static int register_pref(lua_State *L)
796 {
797   // wrapper to catch lua errors in a clean way
798   pref_element *built_elt = NULL;
799   lua_pushcfunction(L, register_pref_sub);
800   dt_lua_gtk_wrap(L);
801   lua_insert(L, 1);
802   lua_pushlightuserdata(L, &built_elt);
803   int result = dt_lua_treated_pcall(L, lua_gettop(L) - 1, 0);
804   if(result == LUA_OK)
805   {
806     built_elt->next = pref_list;
807     pref_list = built_elt;
808     return 0;
809   }
810   else
811   {
812     destroy_pref_element(built_elt);
813     return lua_error(L);
814   }
815 }
816 
817 
init_tab_lua(GtkWidget * dialog,GtkWidget * stack)818 GtkGrid* init_tab_lua(GtkWidget *dialog, GtkWidget *stack)
819 {
820   if(!pref_list) return NULL; // no option registered => don't create the tab
821   GtkWidget *label, *labelev, *viewport;
822   GtkWidget *grid = gtk_grid_new();
823   int line = 0;
824   gtk_grid_set_row_spacing(GTK_GRID(grid), DT_PIXEL_APPLY_DPI(5));
825   gtk_grid_set_column_spacing(GTK_GRID(grid), DT_PIXEL_APPLY_DPI(5));
826   gtk_grid_set_row_homogeneous(GTK_GRID(grid), TRUE);
827   gtk_widget_set_valign(grid, GTK_ALIGN_START);
828   GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
829   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
830   viewport = gtk_viewport_new(NULL, NULL);
831   gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE); // doesn't seem to work from gtkrc
832   gtk_container_add(GTK_CONTAINER(scroll), viewport);
833   gtk_container_add(GTK_CONTAINER(viewport), grid);
834   gtk_stack_add_titled(GTK_STACK(stack), scroll, _("lua options"), _("lua options"));
835 
836   for(pref_element *cur_elt = pref_list; cur_elt; cur_elt = cur_elt->next)
837   {
838     char pref_name[1024];
839     get_pref_name(pref_name, sizeof(pref_name), cur_elt->script, cur_elt->name);
840     label = gtk_label_new(cur_elt->label);
841     gtk_widget_set_halign(GTK_WIDGET(label), GTK_ALIGN_START);
842     labelev = gtk_event_box_new();
843     gtk_widget_add_events(labelev, GDK_BUTTON_PRESS_MASK);
844     gtk_container_add(GTK_CONTAINER(labelev), label);
845     cur_elt->update_widget(cur_elt,dialog,labelev);
846     gtk_widget_set_tooltip_text(labelev, cur_elt->tooltip_reset);
847     gtk_event_box_set_visible_window(GTK_EVENT_BOX(labelev), FALSE);
848     gtk_widget_set_tooltip_text(cur_elt->widget, cur_elt->tooltip);
849     gtk_grid_attach(GTK_GRID(grid), labelev, 0, line, 1, 1);
850     gtk_grid_attach(GTK_GRID(grid), cur_elt->widget, 1, line, 1, 1);
851     line++;
852   }
853   return GTK_GRID(grid);
854 }
855 
856 
destroy_tab_lua(GtkGrid * grid)857 void destroy_tab_lua(GtkGrid *grid)
858 {
859   if(!grid) return;
860   gtk_grid_remove_column(grid, 1); // detach all special widgets to avoid having them destroyed
861 }
862 
863 
dt_lua_init_preferences(lua_State * L)864 int dt_lua_init_preferences(lua_State *L)
865 {
866   luaA_enum(L, lua_pref_type);
867   luaA_enum_value_name(L, lua_pref_type, pref_string, "string");
868   luaA_enum_value_name(L, lua_pref_type, pref_bool, "bool");
869   luaA_enum_value_name(L, lua_pref_type, pref_int, "integer");
870   luaA_enum_value_name(L, lua_pref_type, pref_float, "float");
871   luaA_enum_value_name(L, lua_pref_type, pref_file, "file");
872   luaA_enum_value_name(L, lua_pref_type, pref_dir, "directory");
873   luaA_enum_value_name(L, lua_pref_type, pref_enum, "enum");
874   luaA_enum_value_name(L, lua_pref_type, pref_lua, "lua");
875 
876   dt_lua_push_darktable_lib(L);
877   dt_lua_goto_subtable(L, "preferences");
878 
879   lua_pushcfunction(L, register_pref);
880   lua_setfield(L, -2, "register");
881 
882   lua_pushcfunction(L, read_pref);
883   lua_setfield(L, -2, "read");
884 
885   lua_pushcfunction(L, write_pref);
886   lua_setfield(L, -2, "write");
887 
888   lua_pushcfunction(L, destroy_pref);
889   lua_setfield(L, -2, "destroy");
890 
891   lua_pushcfunction(L, get_keys);
892   lua_setfield(L, -2, "get_keys");
893 
894   lua_pop(L, 1);
895   return 0;
896 }
897 
898 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
899 // vim: shiftwidth=2 expandtab tabstop=2 cindent
900 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
901