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