1 /*
2    This file is part of darktable,
3    Copyright (C) 2015-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 
19 #include "control/control.h"
20 #include "lua/call.h"
21 #include "lua/modules.h"
22 #include "lua/types.h"
23 #include "lua/widget/common.h"
24 #include "stdarg.h"
25 
26 /**
27   TODO
28   use name to save/restore states as pref like other widgets
29   have a way to save presets
30   luastorage can't save presets
31 dt_ui_section_label : make new lua widget - Done
32 widget names : implement for CSS ? - Done
33   */
34 
35 #pragma GCC diagnostic ignored "-Wshadow"
36 
37 dt_lua_widget_type_t widget_type = {
38   .name = "widget",
39   .gui_init = NULL,
40   .gui_cleanup = NULL,
41   .alloc_size = sizeof(dt_lua_widget_t),
42   .parent = NULL
43 };
44 
45 
46 static void cleanup_widget_sub(lua_State *L, dt_lua_widget_type_t *widget_type, lua_widget widget);
cleanup_widget_sub(lua_State * L,dt_lua_widget_type_t * widget_type,lua_widget widget)47 static void cleanup_widget_sub(lua_State *L, dt_lua_widget_type_t *widget_type, lua_widget widget) {
48   if(widget_type->parent)
49     cleanup_widget_sub(L, widget_type->parent, widget);
50   if(widget_type->gui_cleanup) {
51     widget_type->gui_cleanup(L, widget);
52   }
53 }
54 
55 static void init_widget_sub(lua_State *L, dt_lua_widget_type_t *widget_type);
init_widget_sub(lua_State * L,dt_lua_widget_type_t * widget_type)56 static void init_widget_sub(lua_State *L, dt_lua_widget_type_t *widget_type) {
57   if(widget_type->parent)
58     init_widget_sub(L, widget_type->parent);
59   if(widget_type->gui_init)
60     widget_type->gui_init(L);
61 }
62 
on_destroy(GtkWidget * widget,gpointer user_data)63 static void on_destroy(GtkWidget *widget, gpointer user_data)
64 {
65 }
66 
on_destroy_wrapper(gpointer user_data)67 static gboolean on_destroy_wrapper(gpointer user_data)
68 {
69   gtk_widget_destroy((GtkWidget*) user_data);
70   return false;
71 }
72 
widget_gc(lua_State * L)73 static int widget_gc(lua_State *L)
74 {
75   lua_widget lwidget;
76   luaA_to(L, lua_widget, &lwidget, 1);
77   if(!lwidget) return 0; // object has been destroyed
78   if(gtk_widget_get_parent(lwidget->widget)) {
79     luaL_error(L, "Destroying a widget which is still parented, this should never happen (%s at %p)\n", lwidget->type->name, lwidget);
80   }
81   cleanup_widget_sub(L, lwidget->type, lwidget);
82   dt_lua_widget_unbind(L, lwidget);
83   // no need to drop, the pointer table is weak and the widget is already being GC, so it's not in the table anymore
84   //dt_lua_type_gpointer_drop(L,lwidget);
85   //dt_lua_type_gpointer_drop(L,lwidget->widget);
86   g_idle_add(on_destroy_wrapper, lwidget->widget);
87   free(lwidget);
88   return 0;
89 }
90 
get_widget_params(lua_State * L)91 static int get_widget_params(lua_State *L)
92 {
93   struct dt_lua_widget_type_t *widget_type = lua_touserdata(L, lua_upvalueindex(1));
94   if(G_TYPE_IS_ABSTRACT(widget_type->gtk_type)){
95     luaL_error(L, "Trying to create a widget of an abstract type : %s\n", widget_type->name);
96   }
97   lua_widget widget= malloc(widget_type->alloc_size);
98   widget->widget = gtk_widget_new(widget_type->gtk_type, NULL);
99   gtk_widget_show(widget->widget);// widgets are invisible by default
100   g_object_ref_sink(widget->widget);
101   widget->type = widget_type;
102   luaA_push_type(L, widget_type->associated_type, &widget);
103   dt_lua_type_gpointer_alias_type(L, widget_type->associated_type, widget, widget->widget);
104   init_widget_sub(L, widget_type);
105 
106   luaL_getmetafield(L, -1, "__gtk_signals");
107   lua_pushnil(L); /* first key */
108   while(lua_next(L, -2) != 0)
109   {
110     g_signal_connect(widget->widget, lua_tostring(L,-2), G_CALLBACK(lua_touserdata(L,-1)), widget);
111     lua_pop(L,1);
112   }
113   lua_pop(L,1);
114   g_signal_connect(widget->widget, "destroy", G_CALLBACK(on_destroy), widget);
115   return 1;
116 }
117 
118 
119 
dt_lua_init_widget_type_type(lua_State * L,dt_lua_widget_type_t * widget_type,const char * lua_type,GType gtk_type)120 luaA_Type dt_lua_init_widget_type_type(lua_State *L, dt_lua_widget_type_t *widget_type,const char *lua_type, GType gtk_type)
121 {
122   luaA_Type type_id = dt_lua_init_gpointer_type_type(L, luaA_type_add(L, lua_type, sizeof(gpointer)));
123   widget_type->associated_type = type_id;
124   widget_type->gtk_type = gtk_type;
125   dt_lua_type_register_parent_type(L, type_id, widget_type->parent->associated_type);
126 
127   lua_newtable(L);
128   dt_lua_type_setmetafield_type(L, type_id, "__gtk_signals");
129   // add to the table
130   lua_pushlightuserdata(L, widget_type);
131   lua_pushcclosure(L, get_widget_params, 1);
132   dt_lua_gtk_wrap(L);
133   dt_lua_module_entry_new(L, -1, "widget", widget_type->name);
134   lua_pop(L, 1);
135   return type_id;
136 };
137 
138 
139 
new_widget(lua_State * L)140 static int new_widget(lua_State *L)
141 {
142   const char *entry_name = luaL_checkstring(L, 1);
143   dt_lua_module_entry_push(L, "widget", entry_name);
144   lua_insert(L, 2);
145   lua_call(L, lua_gettop(L) - 2, 1);
146   return 1;
147 }
148 
dt_lua_widget_set_callback(lua_State * L,int index,const char * name)149 void dt_lua_widget_set_callback(lua_State *L, int index, const char *name)
150 {
151   luaL_argcheck(L, dt_lua_isa(L, index, lua_widget), index, "lua_widget expected");
152   luaL_checktype(L, -1, LUA_TFUNCTION);
153   lua_getiuservalue(L, index, 1);
154   lua_pushvalue(L, -2);
155   lua_setfield(L, -2, name);
156   lua_pop(L, 2);
157 }
158 
dt_lua_widget_get_callback(lua_State * L,int index,const char * name)159 void dt_lua_widget_get_callback(lua_State *L, int index, const char *name)
160 {
161   luaL_argcheck(L, dt_lua_isa(L, index, lua_widget), index, "lua_widget expected");
162   lua_getiuservalue(L, index, 1);
163   lua_getfield(L, -1, name);
164   lua_remove(L, -2);
165 }
166 
dt_lua_widget_trigger_callback(lua_State * L)167 int dt_lua_widget_trigger_callback(lua_State *L)
168 {
169   int nargs = lua_gettop(L) - 2;
170   lua_widget widget;
171   luaA_to(L, lua_widget, &widget, 1);
172   const char* name = lua_tostring(L, 2);
173   lua_getiuservalue(L, 1, 1);
174   lua_getfield(L, -1, name);
175   if(!lua_isnil(L, -1)) {
176     lua_pushvalue(L, 1);
177     for(int i = 0 ; i < nargs ; i++) {
178       lua_pushvalue(L, i + 3);
179     }
180     dt_lua_treated_pcall(L, nargs + 1, 0);
181     dt_lua_redraw_screen();
182   }
183   return 0;
184 }
185 
reset_member(lua_State * L)186 static int reset_member(lua_State *L)
187 {
188   if(lua_gettop(L) > 2) {
189     dt_lua_widget_set_callback(L, 1, "reset");
190     return 0;
191   }
192   dt_lua_widget_get_callback(L, 1, "reset");
193   return 1;
194 }
195 
196 
tooltip_member(lua_State * L)197 static int tooltip_member(lua_State *L)
198 {
199   lua_widget widget;
200   luaA_to(L, lua_widget, &widget, 1);
201   if(lua_gettop(L) > 2) {
202     if(lua_isnil(L, 3)) {
203       gtk_widget_set_tooltip_text(widget->widget, NULL);
204     } else {
205       const char * text = luaL_checkstring(L, 3);
206       gtk_widget_set_tooltip_text(widget->widget, text);
207     }
208     return 0;
209   }
210   char* result = gtk_widget_get_tooltip_text(widget->widget);
211   lua_pushstring(L, result);
212   free(result);
213   return 1;
214 }
215 
name_member(lua_State * L)216 static int name_member(lua_State *L)
217 {
218   lua_widget widget;
219   luaA_to(L, lua_widget, &widget, 1);
220   if(lua_gettop(L) > 2) {
221     if(lua_isnil(L, 3)) {
222       gtk_widget_set_name(widget->widget, NULL);
223     } else {
224       const gchar * text = luaL_checkstring(L, 3);
225       gtk_widget_set_name(widget->widget, text);
226     }
227     return 0;
228   }
229   const gchar* result = gtk_widget_get_name(widget->widget);
230   lua_pushstring(L, result);
231   return 1;
232 }
233 
visible_member(lua_State * L)234 static int visible_member(lua_State *L)
235 {
236   lua_widget widget;
237   gboolean value;
238   luaA_to(L, lua_widget, &widget, 1);
239   if(lua_gettop(L) > 2) {
240     value = lua_toboolean(L, 3);
241     //gtk_widget_set_visible(widget->widget, value);
242     if(value)
243     {
244       gtk_widget_show(widget->widget);
245       // enable gtk_widget_show_all() in case it was disabled by
246       // setting a widget to hidden
247       gtk_widget_set_no_show_all(widget->widget, FALSE);
248     }
249     else
250     {
251       gtk_widget_hide(widget->widget);
252       // dont let gtk_widget_show_all() unhide the widget
253       gtk_widget_set_no_show_all(widget->widget, TRUE);
254     }
255   }
256   value = gtk_widget_get_visible(widget->widget);
257   lua_pushboolean(L, value);
258   return 1;
259 }
260 
sensitive_member(lua_State * L)261 static int sensitive_member(lua_State *L)
262 {
263   lua_widget widget;
264   luaA_to(L, lua_widget, &widget, 1);
265   if(lua_gettop(L) > 2) {
266     gboolean value = lua_toboolean(L, 3);
267     gtk_widget_set_sensitive(widget->widget, value);
268     return 0;
269   }
270   gboolean result = gtk_widget_get_sensitive(widget->widget);
271   lua_pushboolean(L, result);
272   return 1;
273 }
274 
dt_lua_widget_tostring_member(lua_State * L)275 int dt_lua_widget_tostring_member(lua_State *L)
276 {
277   lua_widget widget;
278   luaA_to(L, lua_widget, &widget, 1);
279   lua_pushstring(L, G_OBJECT_TYPE_NAME(widget->widget));
280   return 1;
281 }
282 
gtk_signal_member(lua_State * L)283 static int gtk_signal_member(lua_State *L)
284 {
285 
286   const char *signal = lua_tostring(L, lua_upvalueindex(1));
287   if(lua_gettop(L) > 2) {
288     dt_lua_widget_set_callback(L, 1, signal);
289     return 0;
290   }
291   dt_lua_widget_get_callback(L, 1, signal);
292   return 1;
293 }
294 
dt_lua_widget_register_gtk_callback_type(lua_State * L,luaA_Type type_id,const char * signal_name,const char * lua_name,GCallback callback)295 void dt_lua_widget_register_gtk_callback_type(lua_State *L, luaA_Type type_id, const char *signal_name, const char *lua_name, GCallback callback)
296 {
297   lua_pushstring(L, signal_name);
298   lua_pushcclosure(L, gtk_signal_member, 1);
299   dt_lua_type_register_type(L, type_id, lua_name);
300 
301   luaL_newmetatable(L, luaA_typename(L, type_id));
302   lua_getfield(L, -1, "__gtk_signals");
303   lua_pushlightuserdata(L, callback);
304   lua_setfield(L, -2, signal_name);
305   lua_pop(L, 2);
306 
307 }
308 
widget_call(lua_State * L)309 int widget_call(lua_State *L)
310 {
311   lua_pushnil(L); /* first key */
312   while(lua_next(L, 2) != 0)
313   {
314     lua_pushvalue(L, -2);
315     lua_pushvalue(L, -2);
316     lua_settable(L, 1);
317     lua_pop(L, 1);
318   }
319   lua_pop(L, 1);
320   return 1;
321 }
322 
dt_lua_widget_bind(lua_State * L,lua_widget widget)323 void dt_lua_widget_bind(lua_State *L, lua_widget widget)
324 {
325   /* check that widget isn't already parented */
326   if(gtk_widget_get_parent (widget->widget) != NULL) {
327     luaL_error(L, "Attempting to bind a widget which already has a parent\n");
328   }
329 
330   /* store it as a toplevel widget */
331   lua_getfield(L, LUA_REGISTRYINDEX, "dt_lua_widget_bind_table");
332   lua_pushlightuserdata(L, widget);
333   luaA_push(L, lua_widget, &widget);
334   lua_settable(L, -3);
335   lua_pop(L, 1);
336 }
337 
dt_lua_widget_unbind(lua_State * L,lua_widget widget)338 void dt_lua_widget_unbind(lua_State *L, lua_widget widget)
339 {
340   lua_getfield(L, LUA_REGISTRYINDEX, "dt_lua_widget_bind_table");
341   lua_pushlightuserdata(L, widget);
342   lua_pushnil(L);
343   lua_settable(L, -3);
344   lua_pop(L, 1);
345 }
346 
347 
dt_lua_init_widget(lua_State * L)348 int dt_lua_init_widget(lua_State* L)
349 {
350 
351   lua_newtable(L);
352   lua_setfield(L, LUA_REGISTRYINDEX, "dt_lua_widget_bind_table");
353 
354   dt_lua_module_new(L, "widget");
355 
356   widget_type.associated_type = dt_lua_init_gpointer_type(L, lua_widget);
357   lua_pushcfunction(L, tooltip_member);
358   dt_lua_gtk_wrap(L);
359   dt_lua_type_register(L, lua_widget, "tooltip");
360   lua_pushcfunction(L, name_member);
361   dt_lua_gtk_wrap(L);
362   dt_lua_type_register(L, lua_widget, "name");
363   lua_pushcfunction(L, visible_member);
364   dt_lua_gtk_wrap(L);
365   dt_lua_type_register(L, lua_widget, "visible");
366   lua_pushcfunction(L, widget_gc);
367   dt_lua_gtk_wrap(L);
368   dt_lua_type_setmetafield(L, lua_widget, "__gc");
369   lua_pushcfunction(L, reset_member);
370   dt_lua_type_register(L, lua_widget, "reset_callback");
371   lua_pushcfunction(L, widget_call);
372   dt_lua_type_setmetafield(L, lua_widget, "__call");
373   lua_pushcfunction(L, sensitive_member);
374   dt_lua_gtk_wrap(L);
375   dt_lua_type_register(L, lua_widget, "sensitive");
376   lua_pushcfunction(L, dt_lua_widget_tostring_member);
377   dt_lua_gtk_wrap(L);
378   dt_lua_type_setmetafield(L, lua_widget, "__tostring");
379 
380   dt_lua_init_widget_container(L);
381 
382   dt_lua_init_widget_box(L);
383   dt_lua_init_widget_button(L);
384   dt_lua_init_widget_check_button(L);
385   dt_lua_init_widget_combobox(L);
386   dt_lua_init_widget_label(L);
387   dt_lua_init_widget_section_label(L);
388   dt_lua_init_widget_entry(L);
389   dt_lua_init_widget_file_chooser_button(L);
390   dt_lua_init_widget_separator(L);
391   dt_lua_init_widget_slider(L);
392   dt_lua_init_widget_stack(L);
393   dt_lua_init_widget_text_view(L);
394 
395   dt_lua_push_darktable_lib(L);
396   lua_pushstring(L, "new_widget");
397   lua_pushcfunction(L, &new_widget);
398   lua_settable(L, -3);
399   lua_pop(L, 1);
400   return 0;
401 }
402 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
403 // vim: shiftwidth=2 expandtab tabstop=2 cindent
404 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
405