1 /*
2 This file is part of darktable,
3 Copyright (C) 2010-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 "common/collection.h"
19 #include "common/darktable.h"
20 #include "common/debug.h"
21 #include "common/selection.h"
22 #include "control/conf.h"
23 #include "control/control.h"
24 #include "dtgtk/button.h"
25 #include "gui/accelerators.h"
26 #include "gui/gtk.h"
27 #include "libs/lib.h"
28 #include <gdk/gdkkeysyms.h>
29 #ifdef USE_LUA
30 #include "lua/call.h"
31 #include "lua/image.h"
32 #endif
33 #include "libs/lib_api.h"
34
35 DT_MODULE(1)
36
name(dt_lib_module_t * self)37 const char *name(dt_lib_module_t *self)
38 {
39 return _("select");
40 }
41
views(dt_lib_module_t * self)42 const char **views(dt_lib_module_t *self)
43 {
44 static const char *v[] = {"lighttable", NULL};
45 return v;
46 }
47
container(dt_lib_module_t * self)48 uint32_t container(dt_lib_module_t *self)
49 {
50 return DT_UI_CONTAINER_PANEL_RIGHT_CENTER;
51 }
52
53 typedef struct dt_lib_select_t
54 {
55 GtkWidget *select_all_button, *select_none_button, *select_invert_button, *select_film_roll_button,
56 *select_untouched_button;
57 } dt_lib_select_t;
58
_update(dt_lib_module_t * self)59 static void _update(dt_lib_module_t *self)
60 {
61 dt_lib_select_t *d = (dt_lib_select_t *)self->data;
62
63 const uint32_t collection_cnt = dt_collection_get_count_no_group(darktable.collection);
64 const uint32_t selected_cnt = dt_collection_get_selected_count(darktable.collection);
65
66 gtk_widget_set_sensitive(GTK_WIDGET(d->select_all_button), selected_cnt < collection_cnt);
67 gtk_widget_set_sensitive(GTK_WIDGET(d->select_none_button), selected_cnt > 0);
68
69 gtk_widget_set_sensitive(GTK_WIDGET(d->select_invert_button), collection_cnt > 0);
70
71 //theoretically can count if there are unaltered in collection but no need to waste CPU cycles on that.
72 gtk_widget_set_sensitive(GTK_WIDGET(d->select_untouched_button), collection_cnt > 0);
73
74 gtk_widget_set_sensitive(GTK_WIDGET(d->select_film_roll_button), selected_cnt > 0);
75 }
76
_image_selection_changed_callback(gpointer instance,dt_lib_module_t * self)77 static void _image_selection_changed_callback(gpointer instance, dt_lib_module_t *self)
78 {
79 _update(self);
80 #ifdef USE_LUA
81 dt_lua_async_call_alien(dt_lua_event_trigger_wrapper,
82 0, NULL,NULL,
83 LUA_ASYNC_TYPENAME,"char*","selection-changed",
84 LUA_ASYNC_DONE);
85 #endif
86 }
87
_collection_updated_callback(gpointer instance,dt_collection_change_t query_change,dt_collection_properties_t changed_property,gpointer imgs,int next,dt_lib_module_t * self)88 static void _collection_updated_callback(gpointer instance, dt_collection_change_t query_change,
89 dt_collection_properties_t changed_property, gpointer imgs, int next,
90 dt_lib_module_t *self)
91 {
92 _update(self);
93 }
94
button_clicked(GtkWidget * widget,gpointer user_data)95 static void button_clicked(GtkWidget *widget, gpointer user_data)
96 {
97 switch(GPOINTER_TO_INT(user_data))
98 {
99 case 0: // all
100 dt_selection_select_all(darktable.selection);
101 break;
102 case 1: // none
103 dt_selection_clear(darktable.selection);
104 break;
105 case 2: // invert
106 dt_selection_invert(darktable.selection);
107 break;
108 case 4: // untouched
109 dt_selection_select_unaltered(darktable.selection);
110 break;
111 default: // case 3: same film roll
112 dt_selection_select_filmroll(darktable.selection);
113 }
114
115 dt_control_queue_redraw_center();
116 }
117
position()118 int position()
119 {
120 return 800;
121 }
122
gui_init(dt_lib_module_t * self)123 void gui_init(dt_lib_module_t *self)
124 {
125 dt_lib_select_t *d = (dt_lib_select_t *)malloc(sizeof(dt_lib_select_t));
126 self->data = d;
127 self->widget = gtk_grid_new();
128 dt_gui_add_help_link(self->widget, dt_get_help_url("select"));
129
130 GtkGrid *grid = GTK_GRID(self->widget);
131 gtk_grid_set_column_homogeneous(grid, TRUE);
132 int line = 0;
133
134 d->select_all_button = dt_ui_button_new(_("select all"), _("select all images in current collection"), NULL);
135 gtk_grid_attach(grid, d->select_all_button, 0, line, 1, 1);
136 g_signal_connect(G_OBJECT(d->select_all_button), "clicked", G_CALLBACK(button_clicked), GINT_TO_POINTER(0));
137
138 d->select_none_button = dt_ui_button_new(_("select none"), _("clear selection"), NULL);
139 gtk_grid_attach(grid, d->select_none_button, 1, line++, 1, 1);
140 g_signal_connect(G_OBJECT(d->select_none_button), "clicked", G_CALLBACK(button_clicked), GINT_TO_POINTER(1));
141
142 d->select_invert_button = dt_ui_button_new(_("invert selection"), _("select unselected images\nin current collection"), NULL);
143 gtk_grid_attach(grid, d->select_invert_button, 0, line, 1, 1);
144 g_signal_connect(G_OBJECT(d->select_invert_button), "clicked", G_CALLBACK(button_clicked), GINT_TO_POINTER(2));
145
146 d->select_film_roll_button = dt_ui_button_new(_("select film roll"), _("select all images which are in the same\nfilm roll as the selected images"), NULL);
147 gtk_grid_attach(grid, d->select_film_roll_button, 1, line++, 1, 1);
148 g_signal_connect(G_OBJECT(d->select_film_roll_button), "clicked", G_CALLBACK(button_clicked), GINT_TO_POINTER(3));
149
150 d->select_untouched_button = dt_ui_button_new(_("select untouched"), _("select untouched images in\ncurrent collection"), NULL);
151 gtk_grid_attach(grid, d->select_untouched_button, 0, line, 2, 1);
152 g_signal_connect(G_OBJECT(d->select_untouched_button), "clicked", G_CALLBACK(button_clicked), GINT_TO_POINTER(4));
153
154 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_SELECTION_CHANGED,
155 G_CALLBACK(_image_selection_changed_callback), self);
156 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_COLLECTION_CHANGED,
157 G_CALLBACK(_collection_updated_callback), self);
158
159 _update(self);
160 }
161
gui_cleanup(dt_lib_module_t * self)162 void gui_cleanup(dt_lib_module_t *self)
163 {
164 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_image_selection_changed_callback), self);
165 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_collection_updated_callback), self);
166 free(self->data);
167 self->data = NULL;
168 }
169
170 #ifdef USE_LUA
171 typedef struct
172 {
173 const char* key;
174 dt_lib_module_t * self;
175 } lua_callback_data;
176
177
lua_button_clicked_cb(lua_State * L)178 static int lua_button_clicked_cb(lua_State* L)
179 {
180 lua_callback_data * data = lua_touserdata(L, 1);
181 dt_lua_module_entry_push(L, "lib", data->self->plugin_name);
182 lua_getiuservalue(L, -1, 1);
183 lua_getfield(L, -1, "callbacks");
184 lua_getfield(L, -1, data->key);
185 lua_pushstring(L, data->key);
186
187 GList *image = dt_collection_get_all(darktable.collection, -1);
188 lua_newtable(L);
189 int table_index = 1;
190 while(image)
191 {
192 luaA_push(L, dt_lua_image_t, &image->data);
193 lua_seti(L, -2, table_index);
194 table_index++;
195 image = g_list_delete_link(image, image);
196 }
197
198 lua_call(L ,2, 1);
199
200 GList *new_selection = NULL;
201 luaL_checktype(L, -1, LUA_TTABLE);
202 lua_pushnil(L);
203 while(lua_next(L, -2) != 0)
204 {
205 /* uses 'key' (at index -2) and 'value' (at index -1) */
206 int imgid;
207 luaA_to(L, dt_lua_image_t, &imgid, -1);
208 new_selection = g_list_prepend(new_selection, GINT_TO_POINTER(imgid));
209 lua_pop(L, 1);
210 }
211 new_selection = g_list_reverse(new_selection);
212 dt_selection_clear(darktable.selection);
213 dt_selection_select_list(darktable.selection, new_selection);
214 g_list_free(new_selection);
215 return 0;
216
217 }
218
lua_button_clicked(GtkWidget * widget,gpointer user_data)219 static void lua_button_clicked(GtkWidget *widget, gpointer user_data)
220 {
221 dt_lua_async_call_alien(lua_button_clicked_cb,
222 0, NULL, NULL,
223 LUA_ASYNC_TYPENAME, "void*", user_data,
224 LUA_ASYNC_DONE);
225 }
226
lua_register_selection(lua_State * L)227 static int lua_register_selection(lua_State *L)
228 {
229 lua_settop(L, 3);
230 dt_lib_module_t *self = lua_touserdata(L, lua_upvalueindex(1));
231 dt_lua_module_entry_push(L, "lib", self->plugin_name);
232 lua_getiuservalue(L, -1, 1);
233 const char* name;
234 const char* key;
235 name = luaL_checkstring(L, 1);
236 key = luaL_checkstring(L, 2);
237 luaL_checktype(L, 3, LUA_TFUNCTION);
238
239 lua_getfield(L, -1, "callbacks");
240 lua_pushstring(L, name);
241 lua_pushvalue(L, 3);
242 lua_settable(L, -3);
243
244 GtkWidget* button = gtk_button_new_with_label(key);
245 const char * tooltip = lua_tostring(L, 3);
246 if(tooltip)
247 gtk_widget_set_tooltip_text(button, tooltip);
248
249 gtk_widget_set_name(button, name);
250 gtk_grid_attach_next_to(GTK_GRID(self->widget), button, NULL, GTK_POS_BOTTOM, 2, 1);
251
252
253 lua_callback_data * data = malloc(sizeof(lua_callback_data));
254 data->key = strdup(name);
255 data->self = self;
256 gulong s = g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(lua_button_clicked), data);
257
258 dt_lua_module_entry_push(L, "lib", self->plugin_name);
259 lua_getiuservalue(L, -1, 1);
260 lua_getfield(L, -1, "signal_handlers");
261 lua_pushstring(L, name);
262 lua_pushinteger(L, s);
263 lua_settable(L, -3);
264
265 gtk_widget_show_all(self->widget);
266
267 return 0;
268 }
269
lua_destroy_selection(lua_State * L)270 static int lua_destroy_selection(lua_State *L)
271 {
272 lua_settop(L, 3);
273 dt_lib_module_t *self = lua_touserdata(L, lua_upvalueindex(1));
274 const char* name = luaL_checkstring(L, 1);
275
276 // find the button named name
277
278 GtkWidget* widget = NULL;
279 int row;
280
281 for(row = 2; (widget = gtk_grid_get_child_at(GTK_GRID(self->widget), 0, row)) != NULL; row++)
282 {
283 if(GTK_IS_BUTTON(widget) && strcmp(gtk_widget_get_name(widget), name) == 0)
284 {
285 // set the callback to nil
286
287 dt_lua_module_entry_push(L, "lib", self->plugin_name);
288 lua_getiuservalue(L, -1, 1);
289 lua_getfield(L, -1, "callbacks");
290 lua_pushstring(L, name);
291 lua_pushnil(L);
292 lua_settable(L, -3);
293
294 // disconnect the signal
295
296 dt_lua_module_entry_push(L, "lib", self->plugin_name);
297 lua_getiuservalue(L, -1, 1);
298 lua_getfield(L, -1, "signal_handlers");
299 lua_pushstring(L, name);
300 lua_gettable(L, -2);
301 gulong handler_id = 0;
302 handler_id = luaL_checkinteger(L, -1);
303 g_signal_handler_disconnect(G_OBJECT(widget), handler_id);
304
305 // remove the widget
306
307 gtk_grid_remove_row(GTK_GRID(self->widget), row);
308 break;
309 }
310 }
311
312 return 0;
313 }
314
lua_set_selection_sensitive(lua_State * L)315 static int lua_set_selection_sensitive(lua_State *L)
316 {
317 lua_settop(L, 3);
318 dt_lib_module_t *self = lua_touserdata(L, lua_upvalueindex(1));
319 const char* name = luaL_checkstring(L, 1);
320 gboolean sensitive = lua_toboolean(L, 2);
321
322 // find the button named name
323
324 GtkWidget* widget = NULL;
325 int row;
326
327 for(row = 2; (widget = gtk_grid_get_child_at(GTK_GRID(self->widget), 0, row)) != NULL; row++)
328 {
329 if(GTK_IS_BUTTON(widget) && strcmp(gtk_widget_get_name(widget), name) == 0)
330 {
331 gtk_widget_set_sensitive(widget, sensitive);
332 break;
333 }
334 }
335 return 0;
336 }
337
init(struct dt_lib_module_t * self)338 void init(struct dt_lib_module_t *self)
339 {
340
341 lua_State *L = darktable.lua_state.state;
342 int my_type = dt_lua_module_entry_get_type(L, "lib", self->plugin_name);
343 lua_pushlightuserdata(L, self);
344 lua_pushcclosure(L, lua_register_selection , 1);
345 dt_lua_gtk_wrap(L);
346 lua_pushcclosure(L, dt_lua_type_member_common, 1);
347 dt_lua_type_register_const_type(L, my_type, "register_selection");
348
349 lua_pushlightuserdata(L, self);
350 lua_pushcclosure(L, lua_destroy_selection, 1);
351 dt_lua_gtk_wrap(L);
352 lua_pushcclosure(L, dt_lua_type_member_common, 1);
353 dt_lua_type_register_const_type(L, my_type, "destroy_selection");
354
355 lua_pushlightuserdata(L, self);
356 lua_pushcclosure(L, lua_set_selection_sensitive, 1);
357 dt_lua_gtk_wrap(L);
358 lua_pushcclosure(L, dt_lua_type_member_common, 1);
359 dt_lua_type_register_const_type(L, my_type, "set_sensitive");
360
361 dt_lua_module_entry_push(L, "lib", self->plugin_name);
362 lua_getiuservalue(L, -1, 1);
363 lua_newtable(L);
364 lua_setfield(L, -2, "callbacks");
365 lua_pop(L, 2);
366
367 dt_lua_module_entry_push(L, "lib", self->plugin_name);
368 lua_getiuservalue(L, -1, 1);
369 lua_newtable(L);
370 lua_setfield(L, -2, "signal_handlers");
371 lua_pop(L, 2);
372 }
373 #endif
374
init_key_accels(dt_lib_module_t * self)375 void init_key_accels(dt_lib_module_t *self)
376 {
377 dt_accel_register_lib(self, NC_("accel", "select all"), GDK_KEY_a, GDK_CONTROL_MASK);
378 dt_accel_register_lib(self, NC_("accel", "select none"), GDK_KEY_a, GDK_CONTROL_MASK | GDK_SHIFT_MASK);
379 dt_accel_register_lib(self, NC_("accel", "invert selection"), GDK_KEY_i, GDK_CONTROL_MASK);
380 dt_accel_register_lib(self, NC_("accel", "select film roll"), 0, 0);
381 dt_accel_register_lib(self, NC_("accel", "select untouched"), 0, 0);
382 }
383
connect_key_accels(dt_lib_module_t * self)384 void connect_key_accels(dt_lib_module_t *self)
385 {
386 dt_lib_select_t *d = (dt_lib_select_t *)self->data;
387
388 dt_accel_connect_button_lib(self, "select all", GTK_WIDGET(d->select_all_button));
389 dt_accel_connect_button_lib(self, "select none", GTK_WIDGET(d->select_none_button));
390 dt_accel_connect_button_lib(self, "invert selection", GTK_WIDGET(d->select_invert_button));
391 dt_accel_connect_button_lib(self, "select film roll", GTK_WIDGET(d->select_film_roll_button));
392 dt_accel_connect_button_lib(self, "select untouched", GTK_WIDGET(d->select_untouched_button));
393 }
394
395 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
396 // vim: shiftwidth=2 expandtab tabstop=2 cindent
397 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
398