1 /*
2 This file is part of darktable,
3 copyright (c) 2020 Aldric Renaudin.
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 /** a class to manage a collection of zoomable thumbnails for culling or full preview. */
19 #include "dtgtk/culling.h"
20 #include "common/collection.h"
21 #include "common/debug.h"
22 #include "common/selection.h"
23 #include "control/control.h"
24 #include "gui/gtk.h"
25 #include "views/view.h"
26
27 #define FULL_PREVIEW_IN_MEMORY_LIMIT 9
28 #define ZOOM_MAX 100000.0f
29
_absmul(float a,float b)30 static inline float _absmul(float a, float b)
31 {
32 return a > b ? a / b : b / a;
33 }
_get_max_in_memory_images()34 static inline int _get_max_in_memory_images()
35 {
36 const int max_in_memory_images = dt_conf_get_int("plugins/lighttable/preview/max_in_memory_images");
37 return MIN(max_in_memory_images, FULL_PREVIEW_IN_MEMORY_LIMIT);
38 }
39 // specials functions for GList globals actions
_list_compare_by_imgid(gconstpointer a,gconstpointer b)40 static gint _list_compare_by_imgid(gconstpointer a, gconstpointer b)
41 {
42 dt_thumbnail_t *th = (dt_thumbnail_t *)a;
43 const int imgid = GPOINTER_TO_INT(b);
44 if(th->imgid < 0 || b < 0) return 1;
45 return (th->imgid != imgid);
46 }
_list_remove_thumb(gpointer user_data)47 static void _list_remove_thumb(gpointer user_data)
48 {
49 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
50 gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(thumb->w_main)), thumb->w_main);
51 dt_thumbnail_destroy(thumb);
52 }
53
_get_selection_count()54 static int _get_selection_count()
55 {
56 int nb = 0;
57 gchar *query = dt_util_dstrcat(
58 NULL,
59 "SELECT count(*) FROM main.selected_images AS s, memory.collected_images as m WHERE s.imgid = m.imgid");
60 sqlite3_stmt *stmt;
61 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
62 if(stmt != NULL)
63 {
64 if(sqlite3_step(stmt) == SQLITE_ROW)
65 {
66 nb = sqlite3_column_int(stmt, 0);
67 }
68 sqlite3_finalize(stmt);
69 }
70 g_free(query);
71
72 return nb;
73 }
74
75 // get imgid from rowid
_thumb_get_imgid(int rowid)76 static int _thumb_get_imgid(int rowid)
77 {
78 int id = -1;
79 sqlite3_stmt *stmt;
80 gchar *query = dt_util_dstrcat(NULL, "SELECT imgid FROM memory.collected_images WHERE rowid=%d", rowid);
81 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
82 if(sqlite3_step(stmt) == SQLITE_ROW)
83 {
84 id = sqlite3_column_int(stmt, 0);
85 }
86 g_free(query);
87 sqlite3_finalize(stmt);
88 return id;
89 }
90 // get rowid from imgid
_thumb_get_rowid(int imgid)91 static int _thumb_get_rowid(int imgid)
92 {
93 int id = -1;
94 sqlite3_stmt *stmt;
95 gchar *query = dt_util_dstrcat(NULL, "SELECT rowid FROM memory.collected_images WHERE imgid=%d", imgid);
96 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
97 if(sqlite3_step(stmt) == SQLITE_ROW)
98 {
99 id = sqlite3_column_int(stmt, 0);
100 }
101 g_free(query);
102 sqlite3_finalize(stmt);
103 return id;
104 }
105
106 // compute thumb_size, thumbs_per_row and rows for the current widget size
107 // return TRUE if something as changed (or forced) FALSE otherwise
_compute_sizes(dt_culling_t * table,gboolean force)108 static gboolean _compute_sizes(dt_culling_t *table, gboolean force)
109 {
110 gboolean ret = FALSE; // return value to show if something as changed
111 GtkAllocation allocation;
112 gtk_widget_get_allocation(table->widget, &allocation);
113
114 if(allocation.width <= 20 || allocation.height <= 20)
115 {
116 table->view_width = allocation.width;
117 table->view_height = allocation.height;
118 return FALSE;
119 }
120
121 // check the offset
122 if(table->list)
123 {
124 dt_thumbnail_t *th = (dt_thumbnail_t *)table->list->data;
125 if(th->imgid != table->offset_imgid || th->display_focus != table->focus) ret = TRUE;
126 }
127 else if(table->offset_imgid > 0)
128 ret = TRUE;
129
130 if(table->mode == DT_CULLING_MODE_CULLING)
131 {
132 const int npr = dt_view_lighttable_get_zoom(darktable.view_manager);
133
134 if(force || allocation.width != table->view_width || allocation.height != table->view_height
135 || npr != table->thumbs_count)
136 {
137 table->thumbs_count = npr;
138 table->view_width = allocation.width;
139 table->view_height = allocation.height;
140 ret = TRUE;
141 }
142 }
143 else if(table->mode == DT_CULLING_MODE_PREVIEW)
144 {
145 if(force || allocation.width != table->view_width || allocation.height != table->view_height)
146 {
147 table->thumbs_count = 1;
148 table->view_width = allocation.width;
149 table->view_height = allocation.height;
150 ret = TRUE;
151 }
152 }
153 return ret;
154 }
155
156 // set mouse_over_id to thumb under mouse or to first thumb
_thumbs_refocus(dt_culling_t * table)157 static void _thumbs_refocus(dt_culling_t *table)
158 {
159 int overid = -1;
160
161 if(table->mouse_inside)
162 {
163 // the exact position of the mouse
164 int x = -1;
165 int y = -1;
166 gdk_window_get_origin(gtk_widget_get_window(table->widget), &x, &y);
167 x = table->pan_x - x;
168 y = table->pan_y - y;
169
170 // which thumb is under the mouse ?
171 for(GList *l = table->list; l; l = g_list_next(l))
172 {
173 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
174 if(th->x <= x && th->x + th->width > x && th->y <= y && th->y + th->height > y)
175 {
176 overid = th->imgid;
177 break;
178 }
179 }
180 }
181
182 // if overid not valid, we use the offset image
183 if(overid <= 0)
184 {
185 overid = table->offset_imgid;
186 }
187
188 // and we set the overid
189 dt_control_set_mouse_over_id(overid);
190 }
191
_thumbs_move(dt_culling_t * table,int move)192 static void _thumbs_move(dt_culling_t *table, int move)
193 {
194 if(move == 0) return;
195 int new_offset = table->offset;
196 // we sanintize the values to be sure to stay in the allowed collection
197 if(move < 0)
198 {
199 if(table->navigate_inside_selection)
200 {
201 sqlite3_stmt *stmt;
202 gchar *query = dt_util_dstrcat(NULL,
203 "SELECT m.rowid FROM memory.collected_images as m, main.selected_images as s "
204 "WHERE m.imgid=s.imgid AND m.rowid<=%d "
205 "ORDER BY m.rowid DESC LIMIT 1 OFFSET %d",
206 table->offset, -1 * move);
207 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
208 if(sqlite3_step(stmt) == SQLITE_ROW)
209 {
210 new_offset = sqlite3_column_int(stmt, 0);
211 }
212 else
213 {
214 // if we are here, that means we don't have enough space to move as wanted. So we move to first position
215 g_free(query);
216 sqlite3_finalize(stmt);
217 query
218 = dt_util_dstrcat(NULL, "SELECT m.rowid FROM memory.collected_images as m, main.selected_images as s "
219 "WHERE m.imgid=s.imgid "
220 "ORDER BY m.rowid LIMIT 1");
221 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
222 if(sqlite3_step(stmt) == SQLITE_ROW)
223 {
224 new_offset = sqlite3_column_int(stmt, 0);
225 }
226 }
227 g_free(query);
228 sqlite3_finalize(stmt);
229 if(new_offset == table->offset)
230 {
231 dt_control_log(_("you have reached the start of your selection"));
232 return;
233 }
234 }
235 else
236 {
237 new_offset = MAX(1, table->offset + move);
238 if(new_offset == table->offset)
239 {
240 dt_control_log(_("you have reached the start of your collection"));
241 return;
242 }
243 }
244 }
245 else
246 {
247 if(table->navigate_inside_selection)
248 {
249 sqlite3_stmt *stmt;
250 gchar *query
251 = dt_util_dstrcat(NULL,
252 "SELECT COUNT(m.rowid) FROM memory.collected_images as m, main.selected_images as s "
253 "WHERE m.imgid=s.imgid AND m.rowid>%d",
254 table->offset);
255 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
256 int nb_after = 0;
257 if(sqlite3_step(stmt) == SQLITE_ROW)
258 {
259 nb_after = sqlite3_column_int(stmt, 0);
260 }
261 g_free(query);
262 sqlite3_finalize(stmt);
263
264 if(nb_after >= table->thumbs_count)
265 {
266 const int delta = MIN(nb_after + 1 - table->thumbs_count, move);
267 query = dt_util_dstrcat(NULL,
268 "SELECT m.rowid FROM memory.collected_images as m, main.selected_images as s "
269 "WHERE m.imgid=s.imgid AND m.rowid>=%d "
270 "ORDER BY m.rowid LIMIT 1 OFFSET %d",
271 table->offset, delta);
272 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
273 if(sqlite3_step(stmt) == SQLITE_ROW)
274 {
275 new_offset = sqlite3_column_int(stmt, 0);
276 }
277 g_free(query);
278 sqlite3_finalize(stmt);
279 }
280
281 if(new_offset == table->offset)
282 {
283 dt_control_log(_("you have reached the end of your selection"));
284 return;
285 }
286 }
287 else
288 {
289 sqlite3_stmt *stmt;
290 gchar *query = dt_util_dstrcat(NULL,
291 "SELECT COUNT(m.rowid) FROM memory.collected_images as m "
292 "WHERE m.rowid>%d",
293 table->offset);
294 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
295 if(sqlite3_step(stmt) == SQLITE_ROW)
296 {
297 const int nb = sqlite3_column_int(stmt, 0);
298 if(nb >= table->thumbs_count)
299 {
300 new_offset = table->offset + MIN(nb + 1 - table->thumbs_count, move);
301 }
302 }
303 g_free(query);
304 sqlite3_finalize(stmt);
305 if(new_offset == table->offset)
306 {
307 dt_control_log(_("you have reached the end of your collection"));
308 return;
309 }
310 }
311 }
312
313 if(new_offset != table->offset)
314 {
315 table->offset = new_offset;
316 dt_culling_full_redraw(table, TRUE);
317 _thumbs_refocus(table);
318 }
319 }
320
_set_table_zoom_ratio(dt_culling_t * table,dt_thumbnail_t * th)321 static void _set_table_zoom_ratio(dt_culling_t *table, dt_thumbnail_t *th)
322 {
323 table->zoom_ratio = dt_thumbnail_get_zoom_ratio(th);
324 }
325
_get_root_offset(GtkWidget * w_image_box,float x_root,float y_root,int * x_offset,int * y_offset)326 static void _get_root_offset(GtkWidget *w_image_box, float x_root, float y_root, int *x_offset, int *y_offset)
327 {
328 gdk_window_get_origin(gtk_widget_get_window(w_image_box), x_offset, y_offset);
329 *x_offset = x_root - *x_offset;
330 *y_offset = y_root - *y_offset;
331 }
332
_zoom_and_shift(dt_thumbnail_t * th,const int x_offset,const int y_offset,const float zoom_delta)333 static gboolean _zoom_and_shift(dt_thumbnail_t *th, const int x_offset, const int y_offset, const float zoom_delta)
334 {
335 float zd = CLAMP(th->zoom + zoom_delta, 1.0f, th->zoom_100);
336 if(zd == th->zoom)
337 return FALSE; // delta_zoom did not change this thumbnail's zoom factor
338
339 const float z_ratio = zd / th->zoom;
340 th->zoom = zd;
341
342 int posx = x_offset;
343 int posy = y_offset;
344
345 const int iw = gtk_widget_get_allocated_width(th->w_image);
346 const int ih = gtk_widget_get_allocated_height(th->w_image);
347
348 // we center the zoom around cursor position
349 if(posx >= 0 && posy >= 0)
350 {
351 // we take in account that the image may be smaller that the imagebox
352 posx -= (gtk_widget_get_allocated_width(th->w_image_box) - iw) / 2;
353 posy -= (gtk_widget_get_allocated_height(th->w_image_box) - ih) / 2;
354 }
355
356 // we change the value. Values will be sanitized in the drawing event
357 th->zoomx = posx - (posx - th->zoomx) * z_ratio;
358 th->zoomy = posy - (posy - th->zoomy) * z_ratio;
359
360 dt_thumbnail_image_refresh(th);
361
362 return TRUE;
363 }
364
_zoom_to_x_root(dt_thumbnail_t * th,const float x_root,const float y_root,const float zoom_delta)365 static gboolean _zoom_to_x_root(dt_thumbnail_t *th, const float x_root, const float y_root, const float zoom_delta)
366 {
367 int x_offset = 0;
368 int y_offset = 0;
369
370 _get_root_offset(th->w_image_box, x_root, y_root, &x_offset, &y_offset);
371
372 return _zoom_and_shift(th, x_offset, y_offset, zoom_delta);
373 }
374
_zoom_to_center(dt_thumbnail_t * th,const float zoom_delta)375 static gboolean _zoom_to_center(dt_thumbnail_t *th, const float zoom_delta)
376 {
377 float zd = CLAMP(th->zoom + zoom_delta, 1.0f, th->zoom_100);
378 if(zd == th->zoom)
379 return FALSE; // delta_zoom did not change this thumbnail's zoom factor
380
381 const float z_ratio = zd / th->zoom;
382 th->zoom = zd;
383 // we center the zoom around center of the shown image
384 int iw = 0;
385 int ih = 0;
386 gtk_widget_get_size_request(th->w_image_box, &iw, &ih);
387 th->zoomx = fmaxf(iw - th->img_width * z_ratio, fminf(0.0f, iw / 2.0 - (iw / 2.0 - th->zoomx) * z_ratio));
388 th->zoomy = fmaxf(ih - th->img_height * z_ratio, fminf(0.0f, ih / 2.0 - (ih / 2.0 - th->zoomy) * z_ratio));
389
390 dt_thumbnail_image_refresh(th);
391
392 return TRUE;
393 }
394
_thumbs_zoom_add(dt_culling_t * table,const float zoom_delta,const float x_root,const float y_root,int state)395 static gboolean _thumbs_zoom_add(dt_culling_t *table, const float zoom_delta, const float x_root,
396 const float y_root, int state)
397 {
398 const int max_in_memory_images = _get_max_in_memory_images();
399 if(table->mode == DT_CULLING_MODE_CULLING && table->thumbs_count > max_in_memory_images)
400 {
401 dt_control_log(_("zooming is limited to %d images"), max_in_memory_images);
402 return TRUE;
403 }
404
405 // we ensure the zoom 100 is computed for all images
406 for(GList *l = table->list; l; l = g_list_next(l))
407 {
408 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
409 dt_thumbnail_get_zoom100(th);
410 }
411
412 if(!g_list_shorter_than(table->list, 2)) // at least two images?
413 {
414 // CULLING with multiple images
415 // if shift+ctrl, we only change the current image
416 if(dt_modifier_is(state, GDK_SHIFT_MASK))
417 {
418 const int mouseid = dt_control_get_mouse_over_id();
419 for(GList *l = table->list; l; l = g_list_next(l))
420 {
421 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
422 if(th->imgid == mouseid)
423 {
424 if(_zoom_to_x_root(th, x_root, y_root, zoom_delta))
425 _set_table_zoom_ratio(table, th);
426 break;
427 }
428 }
429 }
430 else
431 {
432 const int mouseid = dt_control_get_mouse_over_id();
433 int x_offset = 0;
434 int y_offset = 0;
435 gboolean to_pointer = FALSE;
436
437 // get the offset for the image under the cursor
438 for(GList *l = table->list; l; l = g_list_next(l))
439 {
440 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
441 if(th->imgid == mouseid)
442 {
443 _get_root_offset(th->w_image_box, x_root, y_root, &x_offset, &y_offset);
444 to_pointer = TRUE;
445 break;
446 }
447 }
448
449 // apply the offset to all images
450 for(GList *l = table->list; l; l = g_list_next(l))
451 {
452 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
453 if(to_pointer == TRUE ? _zoom_and_shift(th, x_offset, y_offset, zoom_delta)
454 : _zoom_to_center(th, zoom_delta))
455 _set_table_zoom_ratio(table, th);
456 }
457 }
458 }
459 else if(table->list)
460 {
461 // FULL PREVIEW or CULLING with 1 image
462 dt_thumbnail_t *th = (dt_thumbnail_t *)table->list->data;
463 if(_zoom_to_x_root(th, x_root, y_root, zoom_delta))
464 _set_table_zoom_ratio(table, th);
465 }
466
467 return TRUE;
468 }
469
_zoom_thumb_fit(dt_thumbnail_t * th)470 static void _zoom_thumb_fit(dt_thumbnail_t *th)
471 {
472 th->zoom = 1.0;
473 th->zoomx = 0;
474 th->zoomy = 0;
475 dt_thumbnail_image_refresh(th);
476 }
477
_zoom_thumb_max(dt_thumbnail_t * th,float x_root,float y_root)478 static gboolean _zoom_thumb_max(dt_thumbnail_t *th, float x_root, float y_root)
479 {
480 dt_thumbnail_get_zoom100(th);
481 return _zoom_to_x_root(th, x_root, y_root, ZOOM_MAX);
482 }
483
484 // toggle zoom max / zoom fit of image currently having mouse over id
_toggle_zoom_current(dt_culling_t * table,float x_root,float y_root)485 static void _toggle_zoom_current(dt_culling_t *table, float x_root, float y_root)
486 {
487 const int id = dt_control_get_mouse_over_id();
488 for(GList *l = table->list; l; l = g_list_next(l))
489 {
490 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
491 if(th->imgid == id)
492 {
493 if(th->zoom_100 < 1.0 || th->zoom < th->zoom_100)
494 _zoom_thumb_max(th, x_root, y_root);
495 else
496 _zoom_thumb_fit(th);
497 break;
498 }
499 }
500 }
501
502 // toggle zoom max / zoom fit of all images in culling table
_toggle_zoom_all(dt_culling_t * table,float x_root,float y_root)503 static void _toggle_zoom_all(dt_culling_t *table, float x_root, float y_root)
504 {
505 gboolean zmax = TRUE;
506 for(GList *l = table->list; l; l = g_list_next(l))
507 {
508 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
509 if(th->zoom_100 < 1.0 || th->zoom < th->zoom_100)
510 {
511 zmax = FALSE;
512 break;
513 }
514 }
515
516 if(zmax)
517 dt_culling_zoom_fit(table);
518 else
519 _thumbs_zoom_add(table, ZOOM_MAX, x_root, y_root, 0);
520 }
521
_event_scroll(GtkWidget * widget,GdkEvent * event,gpointer user_data)522 static gboolean _event_scroll(GtkWidget *widget, GdkEvent *event, gpointer user_data)
523 {
524 GdkEventScroll *e = (GdkEventScroll *)event;
525 dt_culling_t *table = (dt_culling_t *)user_data;
526 int delta;
527
528 if(dt_gui_get_scroll_unit_delta(e, &delta))
529 {
530 if(dt_modifier_is(e->state, GDK_CONTROL_MASK))
531 {
532 // zooming
533 const float zoom_delta = delta < 0 ? 0.5f : -0.5f;
534 _thumbs_zoom_add(table, zoom_delta, e->x_root, e->y_root, e->state);
535 }
536 else
537 {
538 const int move = delta < 0 ? -1 : 1;
539 _thumbs_move(table, move);
540 }
541 }
542 return TRUE;
543 }
544
_event_draw(GtkWidget * widget,cairo_t * cr,gpointer user_data)545 static gboolean _event_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
546 {
547 if(!GTK_IS_CONTAINER(gtk_widget_get_parent(widget))) return TRUE;
548
549 // we render the background (can be visible if before first image / after last image)
550 GtkStyleContext *context = gtk_widget_get_style_context(widget);
551 gtk_render_background(context, cr, 0, 0, gtk_widget_get_allocated_width(widget),
552 gtk_widget_get_allocated_height(widget));
553
554 // but we don't really want to draw something, this is just to know when the widget is really ready
555 dt_culling_t *table = (dt_culling_t *)user_data;
556 dt_culling_full_redraw(table, FALSE);
557 return FALSE; // let's propagate this event
558 }
559
_event_leave_notify(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)560 static gboolean _event_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
561 {
562 dt_culling_t *table = (dt_culling_t *)user_data;
563 // if the leaving cause is the hide of the widget, no mouseover change
564 if(!gtk_widget_is_visible(widget))
565 {
566 table->mouse_inside = FALSE;
567 return FALSE;
568 }
569
570 // if we leave thumbtable in favour of an inferior (a thumbnail) it's not a real leave !
571 if(event->detail == GDK_NOTIFY_INFERIOR) return FALSE;
572
573 table->mouse_inside = FALSE;
574 dt_control_set_mouse_over_id(-1);
575 return TRUE;
576 }
577
_event_enter_notify(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)578 static gboolean _event_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
579 {
580 // we only handle the case where we enter thumbtable from an inferior (a thumbnail)
581 // this is when the mouse enter an "empty" area of thumbtable
582 if(event->detail != GDK_NOTIFY_INFERIOR) return FALSE;
583
584 dt_control_set_mouse_over_id(-1);
585 return TRUE;
586 }
587
_event_button_press(GtkWidget * widget,GdkEventButton * event,gpointer user_data)588 static gboolean _event_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
589 {
590 dt_culling_t *table = (dt_culling_t *)user_data;
591
592 if(event->button == 2)
593 {
594 // if shift is pressed, we work only with image hovered
595 if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
596 _toggle_zoom_current(table, event->x_root, event->y_root);
597 else
598 _toggle_zoom_all(table, event->x_root, event->y_root);
599 return TRUE;
600 }
601
602 const int id = dt_control_get_mouse_over_id();
603
604 if(id > 0 && event->button == 1 && event->type == GDK_2BUTTON_PRESS)
605 {
606 dt_view_manager_switch(darktable.view_manager, "darkroom");
607 return TRUE;
608 }
609
610 table->pan_x = event->x_root;
611 table->pan_y = event->y_root;
612 table->panning = TRUE;
613 return TRUE;
614 }
615
_event_motion_notify(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)616 static gboolean _event_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
617 {
618 dt_culling_t *table = (dt_culling_t *)user_data;
619 table->mouse_inside = TRUE;
620 if(!table->panning)
621 {
622 table->pan_x = event->x_root;
623 table->pan_y = event->y_root;
624 return FALSE;
625 }
626
627 // get the max zoom of all images
628 const int max_in_memory_images = _get_max_in_memory_images();
629 if(table->mode == DT_CULLING_MODE_CULLING && table->thumbs_count > max_in_memory_images) return FALSE;
630
631 float fz = 1.0f;
632 for (GList *l = table->list; l; l = g_list_next(l))
633 {
634 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
635 fz = fmaxf(fz, th->zoom);
636 }
637
638 if(table->panning && fz > 1.0f)
639 {
640 const double x = event->x_root;
641 const double y = event->y_root;
642 // we want the images to stay in the screen
643 const float scale = darktable.gui->ppd_thb / darktable.gui->ppd;
644 const float valx = (x - table->pan_x) * scale;
645 const float valy = (y - table->pan_y) * scale;
646
647 if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
648 {
649 int mouseid = dt_control_get_mouse_over_id();
650 for(GList *l = table->list; l; l = g_list_next(l))
651 {
652 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
653 if(th->imgid == mouseid)
654 {
655 th->zoomx += valx;
656 th->zoomy += valy;
657 break;
658 }
659 }
660 }
661 else
662 {
663 for(GList *l = table->list; l; l = g_list_next(l))
664 {
665 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
666 th->zoomx += valx;
667 th->zoomy += valy;
668 }
669 }
670 // sanitize specific positions of individual images
671 for(GList *l = table->list; l; l = g_list_next(l))
672 {
673 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
674 int iw = 0;
675 int ih = 0;
676 gtk_widget_get_size_request(th->w_image, &iw, &ih);
677 const int mindx = iw * darktable.gui->ppd_thb - th->img_width;
678 const int mindy = ih * darktable.gui->ppd_thb - th->img_height;
679 if(th->zoomx > 0) th->zoomx = 0;
680 if(th->zoomx < mindx) th->zoomx = mindx;
681 if(th->zoomy > 0) th->zoomy = 0;
682 if(th->zoomy < mindy) th->zoomy = mindy;
683 }
684
685 table->pan_x = x;
686 table->pan_y = y;
687 }
688
689 for(GList *l = table->list; l; l = g_list_next(l))
690 {
691 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
692 dt_thumbnail_image_refresh_position(th);
693 }
694 return TRUE;
695 }
696
_event_button_release(GtkWidget * widget,GdkEventButton * event,gpointer user_data)697 static gboolean _event_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
698 {
699 dt_culling_t *table = (dt_culling_t *)user_data;
700 table->panning = FALSE;
701 return TRUE;
702 }
703
704 // called each time the preference change, to update specific parts
_dt_pref_change_callback(gpointer instance,gpointer user_data)705 static void _dt_pref_change_callback(gpointer instance, gpointer user_data)
706 {
707 if(!user_data) return;
708 dt_culling_t *table = (dt_culling_t *)user_data;
709
710 dt_culling_full_redraw(table, TRUE);
711
712 for(GList *l = table->list; l; l = g_list_next(l))
713 {
714 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
715 th->overlay_timeout_duration = dt_conf_get_int("plugins/lighttable/overlay_timeout");
716 dt_thumbnail_reload_infos(th);
717 const float zoom_ratio = th->zoom_100 > 1 ? th->zoom / th->zoom_100 : table->zoom_ratio;
718 dt_thumbnail_resize(th, th->width, th->height, TRUE, zoom_ratio);
719 }
720 }
721
_dt_selection_changed_callback(gpointer instance,gpointer user_data)722 static void _dt_selection_changed_callback(gpointer instance, gpointer user_data)
723 {
724 if(!user_data) return;
725 dt_culling_t *table = (dt_culling_t *)user_data;
726 if(!gtk_widget_get_visible(table->widget)) return;
727
728 // if we are in selection synchronisation mode, we exit this mode
729 if(table->selection_sync) table->selection_sync = FALSE;
730
731 // if we are in dynamic mode, zoom = selection count
732 if(table->mode == DT_CULLING_MODE_CULLING
733 && dt_view_lighttable_get_layout(darktable.view_manager) == DT_LIGHTTABLE_LAYOUT_CULLING_DYNAMIC)
734 {
735 sqlite3_stmt *stmt;
736 int sel_count = 0;
737 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
738 "SELECT count(*) "
739 "FROM memory.collected_images AS col, main.selected_images as sel "
740 "WHERE col.imgid=sel.imgid",
741 -1, &stmt, NULL);
742 if(sqlite3_step(stmt) == SQLITE_ROW) sel_count = sqlite3_column_int(stmt, 0);
743 sqlite3_finalize(stmt);
744 const int nz = (sel_count <= 1) ? dt_conf_get_int("plugins/lighttable/culling_num_images") : sel_count;
745 dt_view_lighttable_set_zoom(darktable.view_manager, nz);
746 }
747 // if we navigate only in the selection we just redraw to ensure no unselected image is present
748 if(table->navigate_inside_selection)
749 {
750 dt_culling_full_redraw(table, TRUE);
751 _thumbs_refocus(table);
752 }
753 }
754
_dt_profile_change_callback(gpointer instance,int type,gpointer user_data)755 static void _dt_profile_change_callback(gpointer instance, int type, gpointer user_data)
756 {
757 if(!user_data) return;
758 dt_culling_t *table = (dt_culling_t *)user_data;
759 if(!gtk_widget_get_visible(table->widget)) return;
760
761 for(GList *l = table->list; l; l = g_list_next(l))
762 {
763 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
764 dt_thumbnail_image_refresh(th);
765 }
766 }
767
768 // this is called each time mouse_over id change
_dt_mouse_over_image_callback(gpointer instance,gpointer user_data)769 static void _dt_mouse_over_image_callback(gpointer instance, gpointer user_data)
770 {
771 if(!user_data) return;
772 dt_culling_t *table = (dt_culling_t *)user_data;
773 if(!gtk_widget_get_visible(table->widget)) return;
774
775 const int imgid = dt_control_get_mouse_over_id();
776
777 if(imgid > 0)
778 {
779 // let's be absolutely sure that the right widget has the focus
780 // otherwise accels don't work...
781 gtk_widget_grab_focus(dt_ui_center(darktable.gui->ui));
782 }
783
784 // we crawl over all images to find the right one
785 for(GList *l = table->list; l; l = g_list_next(l))
786 {
787 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
788 // if needed, the change mouseover value of the thumb
789 if(th->mouse_over != (th->imgid == imgid)) dt_thumbnail_set_mouseover(th, (th->imgid == imgid));
790 }
791 }
792
_dt_filmstrip_change(gpointer instance,int imgid,gpointer user_data)793 static void _dt_filmstrip_change(gpointer instance, int imgid, gpointer user_data)
794 {
795 if(!user_data || imgid <= 0) return;
796 dt_culling_t *table = (dt_culling_t *)user_data;
797 if(!gtk_widget_get_visible(table->widget)) return;
798
799 table->offset = _thumb_get_rowid(imgid);
800 dt_culling_full_redraw(table, TRUE);
801 _thumbs_refocus(table);
802 }
803
804 // get the class name associated with the overlays mode
_thumbs_get_overlays_class(dt_thumbnail_overlay_t over)805 static gchar *_thumbs_get_overlays_class(dt_thumbnail_overlay_t over)
806 {
807 switch(over)
808 {
809 case DT_THUMBNAIL_OVERLAYS_NONE:
810 return dt_util_dstrcat(NULL, "dt_overlays_none");
811 case DT_THUMBNAIL_OVERLAYS_HOVER_EXTENDED:
812 return dt_util_dstrcat(NULL, "dt_overlays_hover_extended");
813 case DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL:
814 return dt_util_dstrcat(NULL, "dt_overlays_always");
815 case DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED:
816 return dt_util_dstrcat(NULL, "dt_overlays_always_extended");
817 case DT_THUMBNAIL_OVERLAYS_MIXED:
818 return dt_util_dstrcat(NULL, "dt_overlays_mixed");
819 case DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK:
820 return dt_util_dstrcat(NULL, "dt_overlays_hover_block");
821 default:
822 return dt_util_dstrcat(NULL, "dt_overlays_hover");
823 }
824 }
825
dt_culling_new(dt_culling_mode_t mode)826 dt_culling_t *dt_culling_new(dt_culling_mode_t mode)
827 {
828 dt_culling_t *table = (dt_culling_t *)calloc(1, sizeof(dt_culling_t));
829 table->mode = mode;
830 table->zoom_ratio = IMG_TO_FIT;
831 table->widget = gtk_layout_new(NULL, NULL);
832 // TODO dt_gui_add_help_link(table->widget, dt_get_help_url("lighttable_filemanager"));
833
834 // set css name and class
835 if(mode == DT_CULLING_MODE_PREVIEW)
836 gtk_widget_set_name(table->widget, "preview");
837 else
838 gtk_widget_set_name(table->widget, "culling");
839 GtkStyleContext *context = gtk_widget_get_style_context(table->widget);
840 if(mode == DT_CULLING_MODE_PREVIEW)
841 gtk_style_context_add_class(context, "dt_preview");
842 else
843 gtk_style_context_add_class(context, "dt_culling");
844
845 // overlays
846 gchar *otxt = dt_util_dstrcat(NULL, "plugins/lighttable/overlays/culling/%d", table->mode);
847 table->overlays = dt_conf_get_int(otxt);
848 g_free(otxt);
849
850 gchar *cl0 = _thumbs_get_overlays_class(table->overlays);
851 gtk_style_context_add_class(context, cl0);
852 free(cl0);
853
854 otxt = dt_util_dstrcat(NULL, "plugins/lighttable/overlays/culling_block_timeout/%d", table->mode);
855 table->overlays_block_timeout = 2;
856 if(!dt_conf_key_exists(otxt))
857 table->overlays_block_timeout = dt_conf_get_int("plugins/lighttable/overlay_timeout");
858 else
859 table->overlays_block_timeout = dt_conf_get_int(otxt);
860 g_free(otxt);
861
862 otxt = dt_util_dstrcat(NULL, "plugins/lighttable/tooltips/culling/%d", table->mode);
863 table->show_tooltips = dt_conf_get_bool(otxt);
864 g_free(otxt);
865
866 // set widget signals
867 gtk_widget_set_events(table->widget, GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK
868 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK
869 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
870 gtk_widget_set_app_paintable(table->widget, TRUE);
871 gtk_widget_set_can_focus(table->widget, TRUE);
872
873 g_signal_connect(G_OBJECT(table->widget), "scroll-event", G_CALLBACK(_event_scroll), table);
874 g_signal_connect(G_OBJECT(table->widget), "draw", G_CALLBACK(_event_draw), table);
875 g_signal_connect(G_OBJECT(table->widget), "leave-notify-event", G_CALLBACK(_event_leave_notify), table);
876 g_signal_connect(G_OBJECT(table->widget), "enter-notify-event", G_CALLBACK(_event_enter_notify), table);
877 g_signal_connect(G_OBJECT(table->widget), "button-press-event", G_CALLBACK(_event_button_press), table);
878 g_signal_connect(G_OBJECT(table->widget), "motion-notify-event", G_CALLBACK(_event_motion_notify), table);
879 g_signal_connect(G_OBJECT(table->widget), "button-release-event", G_CALLBACK(_event_button_release), table);
880
881 // we register globals signals
882 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_MOUSE_OVER_IMAGE_CHANGE,
883 G_CALLBACK(_dt_mouse_over_image_callback), table);
884 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_CONTROL_PROFILE_USER_CHANGED,
885 G_CALLBACK(_dt_profile_change_callback), table);
886 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_PREFERENCES_CHANGE, G_CALLBACK(_dt_pref_change_callback),
887 table);
888 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE,
889 G_CALLBACK(_dt_filmstrip_change), table);
890 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_SELECTION_CHANGED,
891 G_CALLBACK(_dt_selection_changed_callback), table);
892
893 g_object_ref(table->widget);
894
895 return table;
896 }
897
898 // initialize offset, ... values
899 // to be used when reentering culling
dt_culling_init(dt_culling_t * table,int offset)900 void dt_culling_init(dt_culling_t *table, int offset)
901 {
902 /** HOW it works :
903 *
904 * For the first image :
905 * image_over OR first selected OR first OR -1
906 *
907 * For the navigation in selection :
908 * culling dynamic mode => OFF
909 * first image in selection AND selection > 1 => ON
910 * otherwise => OFF
911 *
912 * For the selection following :
913 * culling dynamic mode => OFF
914 * first image(s) == selection && selection is continuous => ON
915 */
916
917 // init values
918 table->navigate_inside_selection = FALSE;
919 table->selection_sync = FALSE;
920 table->zoom_ratio = IMG_TO_FIT;
921
922 // reset remaining zooming values if any
923 for(GList *l = table->list; l; l = g_list_next(l))
924 {
925 dt_thumbnail_t *thumb = (dt_thumbnail_t *)l->data;
926 thumb->zoom = 1.0f;
927 thumb->zoomx = 0.0;
928 thumb->zoomy = 0.0;
929 thumb->img_surf_dirty = TRUE;
930 }
931
932 const gboolean culling_dynamic
933 = (table->mode == DT_CULLING_MODE_CULLING
934 && dt_view_lighttable_get_layout(darktable.view_manager) == DT_LIGHTTABLE_LAYOUT_CULLING_DYNAMIC);
935
936 // get first id
937 sqlite3_stmt *stmt;
938 gchar *query = NULL;
939 int first_id = -1;
940
941 if(offset > 0)
942 first_id = _thumb_get_imgid(offset);
943 else
944 first_id = dt_control_get_mouse_over_id();
945
946 if(first_id < 1 || culling_dynamic)
947 {
948 // search the first selected image
949 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
950 "SELECT col.imgid "
951 "FROM memory.collected_images AS col, main.selected_images as sel "
952 "WHERE col.imgid=sel.imgid "
953 "ORDER BY col.rowid "
954 "LIMIT 1",
955 -1, &stmt, NULL);
956 if(sqlite3_step(stmt) == SQLITE_ROW) first_id = sqlite3_column_int(stmt, 0);
957 sqlite3_finalize(stmt);
958 }
959 if(first_id < 1)
960 {
961 // search the first image shown in view (this is the offset of thumbtable)
962 first_id = _thumb_get_imgid(1);
963 }
964 if(first_id < 1)
965 {
966 // Arrrghh
967 return;
968 }
969
970 // selection count
971 int sel_count = 0;
972 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
973 "SELECT count(*) "
974 "FROM memory.collected_images AS col, main.selected_images as sel "
975 "WHERE col.imgid=sel.imgid",
976 -1, &stmt, NULL);
977 if(sqlite3_step(stmt) == SQLITE_ROW) sel_count = sqlite3_column_int(stmt, 0);
978 sqlite3_finalize(stmt);
979
980 // special culling dynamic mode
981 if(culling_dynamic)
982 {
983 if(sel_count == 0)
984 {
985 dt_control_log(_("no image selected !"));
986 first_id = -1;
987 }
988 table->navigate_inside_selection = TRUE;
989 table->offset = _thumb_get_rowid(first_id);
990 table->offset_imgid = first_id;
991 return;
992 }
993
994 // is first_id inside selection ?
995 gboolean inside = FALSE;
996 query = dt_util_dstrcat(NULL,
997 "SELECT col.imgid "
998 "FROM memory.collected_images AS col, main.selected_images AS sel "
999 "WHERE col.imgid=sel.imgid AND col.imgid=%d",
1000 first_id);
1001 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
1002 if(sqlite3_step(stmt) == SQLITE_ROW) inside = TRUE;
1003 sqlite3_finalize(stmt);
1004 g_free(query);
1005
1006 if(table->mode == DT_CULLING_MODE_PREVIEW)
1007 {
1008 table->navigate_inside_selection = (sel_count > 1 && inside);
1009 table->selection_sync = (sel_count == 1 && inside);
1010 }
1011 else if(table->mode == DT_CULLING_MODE_CULLING)
1012 {
1013 const int zoom = dt_view_lighttable_get_zoom(darktable.view_manager);
1014 // we first determine if we synchronize the selection with culling images
1015 table->selection_sync = FALSE;
1016 if(sel_count == 1 && inside)
1017 table->selection_sync = TRUE;
1018 else if(sel_count == zoom && inside)
1019 {
1020 // we ensure that the selection is continuous
1021 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1022 "SELECT MIN(rowid), MAX(rowid) "
1023 "FROM memory.collected_images AS col, main.selected_images as sel "
1024 "WHERE col.imgid=sel.imgid ",
1025 -1, &stmt, NULL);
1026 if(sqlite3_step(stmt) == SQLITE_ROW)
1027 {
1028 if(sqlite3_column_int(stmt, 0) + sel_count - 1 == sqlite3_column_int(stmt, 1))
1029 {
1030 table->selection_sync = TRUE;
1031 }
1032 }
1033 sqlite3_finalize(stmt);
1034 }
1035
1036 // we now determine if we limit culling images to the selection
1037 table->navigate_inside_selection = (!table->selection_sync && inside);
1038 }
1039
1040 table->offset = _thumb_get_rowid(first_id);
1041 table->offset_imgid = first_id;
1042 }
1043
_thumbs_prefetch(dt_culling_t * table)1044 static void _thumbs_prefetch(dt_culling_t *table)
1045 {
1046 if(!table->list) return;
1047
1048 // get the mip level by using the max image size actually shown
1049 int maxw = 0;
1050 int maxh = 0;
1051 for(GList *l = table->list; l; l = g_list_next(l))
1052 {
1053 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
1054 maxw = MAX(maxw, th->width);
1055 maxh = MAX(maxh, th->height);
1056 }
1057 dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, maxw, maxh);
1058
1059 // prefetch next image
1060 gchar *query;
1061 sqlite3_stmt *stmt;
1062 dt_thumbnail_t *last = (dt_thumbnail_t *)g_list_last(table->list)->data;
1063 if(table->navigate_inside_selection)
1064 {
1065 query
1066 = dt_util_dstrcat(NULL,
1067 "SELECT m.imgid "
1068 "FROM memory.collected_images AS m, main.selected_images AS s "
1069 "WHERE m.imgid = s.imgid"
1070 " AND m.rowid > (SELECT mm.rowid FROM memory.collected_images AS mm WHERE mm.imgid=%d) "
1071 "ORDER BY m.rowid "
1072 "LIMIT 1",
1073 last->imgid);
1074 }
1075 else
1076 {
1077 query
1078 = dt_util_dstrcat(NULL,
1079 "SELECT m.imgid "
1080 "FROM memory.collected_images AS m "
1081 "WHERE m.rowid > (SELECT mm.rowid FROM memory.collected_images AS mm WHERE mm.imgid=%d) "
1082 "ORDER BY m.rowid "
1083 "LIMIT 1",
1084 last->imgid);
1085 }
1086 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
1087 if(sqlite3_step(stmt) == SQLITE_ROW)
1088 {
1089 const int id = sqlite3_column_int(stmt, 0);
1090 if(id > 0) dt_mipmap_cache_get(darktable.mipmap_cache, NULL, id, mip, DT_MIPMAP_PREFETCH, 'r');
1091 }
1092 sqlite3_finalize(stmt);
1093 g_free(query);
1094
1095 // prefetch previous image
1096 dt_thumbnail_t *prev = (dt_thumbnail_t *)(table->list)->data;
1097 if(table->navigate_inside_selection)
1098 {
1099 query
1100 = dt_util_dstrcat(NULL,
1101 "SELECT m.imgid "
1102 "FROM memory.collected_images AS m, main.selected_images AS s "
1103 "WHERE m.imgid = s.imgid"
1104 " AND m.rowid < (SELECT mm.rowid FROM memory.collected_images AS mm WHERE mm.imgid=%d) "
1105 "ORDER BY m.rowid DESC "
1106 "LIMIT 1",
1107 prev->imgid);
1108 }
1109 else
1110 {
1111 query
1112 = dt_util_dstrcat(NULL,
1113 "SELECT m.imgid "
1114 "FROM memory.collected_images AS m "
1115 "WHERE m.rowid < (SELECT mm.rowid FROM memory.collected_images AS mm WHERE mm.imgid=%d) "
1116 "ORDER BY m.rowid DESC "
1117 "LIMIT 1",
1118 prev->imgid);
1119 }
1120 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
1121 if(sqlite3_step(stmt) == SQLITE_ROW)
1122 {
1123 const int id = sqlite3_column_int(stmt, 0);
1124 if(id > 0) dt_mipmap_cache_get(darktable.mipmap_cache, NULL, id, mip, DT_MIPMAP_PREFETCH, 'r');
1125 }
1126 sqlite3_finalize(stmt);
1127 }
1128
_thumbs_recreate_list_at(dt_culling_t * table,const int offset)1129 static gboolean _thumbs_recreate_list_at(dt_culling_t *table, const int offset)
1130 {
1131 gchar *query = NULL;
1132 sqlite3_stmt *stmt;
1133
1134 if(table->navigate_inside_selection)
1135 {
1136 query = dt_util_dstrcat(NULL,
1137 "SELECT m.rowid, m.imgid, b.aspect_ratio "
1138 "FROM memory.collected_images AS m, main.selected_images AS s, images AS b "
1139 "WHERE m.imgid = b.id AND m.imgid = s.imgid AND m.rowid >= %d "
1140 "ORDER BY m.rowid "
1141 "LIMIT %d",
1142 offset, table->thumbs_count);
1143 }
1144 else
1145 {
1146 query = dt_util_dstrcat(NULL,
1147 "SELECT m.rowid, m.imgid, b.aspect_ratio "
1148 "FROM (SELECT rowid, imgid "
1149 "FROM memory.collected_images "
1150 "WHERE rowid < %d + %d "
1151 "ORDER BY rowid DESC "
1152 "LIMIT %d) AS m, "
1153 "images AS b "
1154 "WHERE m.imgid = b.id "
1155 "ORDER BY m.rowid",
1156 offset, table->thumbs_count, table->thumbs_count);
1157 }
1158
1159 GList *newlist = NULL;
1160 int nbnew = 0;
1161 int pos = 0;
1162 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
1163 while(sqlite3_step(stmt) == SQLITE_ROW && g_list_shorter_than(newlist, table->thumbs_count+1))
1164 {
1165 const int nrow = sqlite3_column_int(stmt, 0);
1166 const int nid = sqlite3_column_int(stmt, 1);
1167 // first, we search if the thumb is already here
1168 GList *tl = g_list_find_custom(table->list, GINT_TO_POINTER(nid), _list_compare_by_imgid);
1169 if(tl)
1170 {
1171 dt_thumbnail_t *thumb = (dt_thumbnail_t *)tl->data;
1172 thumb->rowid = nrow; // this may have changed
1173 thumb->display_focus = table->focus;
1174 newlist = g_list_prepend(newlist, thumb);
1175 // and we remove the thumb from the old list
1176 table->list = g_list_remove(table->list, thumb);
1177 }
1178 else
1179 {
1180 // we create a completely new thumb
1181 // we set its size to the thumb it replace in the list if any otherwise we set it to something > 0 to trigger
1182 // draw events
1183 int nw = 40;
1184 int nh = 40;
1185 if(table->mode == DT_CULLING_MODE_PREVIEW)
1186 {
1187 nw = table->view_width;
1188 nh = table->view_height;
1189 }
1190 else if(table->list)
1191 {
1192 dt_thumbnail_t *th_model
1193 = (dt_thumbnail_t *)g_list_nth_data(table->list, MIN(pos, g_list_length(table->list) - 1));
1194 nw = th_model->width;
1195 nh = th_model->height;
1196 }
1197 else if(newlist)
1198 {
1199 dt_thumbnail_t *th_model = (dt_thumbnail_t *)newlist->data; // get most recently added
1200 nw = th_model->width;
1201 nh = th_model->height;
1202 }
1203 dt_thumbnail_t *thumb;
1204 if(table->mode == DT_CULLING_MODE_PREVIEW)
1205 thumb = dt_thumbnail_new(nw, nh, table->zoom_ratio, nid, nrow, table->overlays,
1206 DT_THUMBNAIL_CONTAINER_PREVIEW, table->show_tooltips);
1207 else
1208 thumb = dt_thumbnail_new(nw, nh, table->zoom_ratio, nid, nrow, table->overlays,
1209 DT_THUMBNAIL_CONTAINER_CULLING, table->show_tooltips);
1210
1211 thumb->display_focus = table->focus;
1212 thumb->sel_mode = DT_THUMBNAIL_SEL_MODE_DISABLED;
1213 float aspect_ratio = sqlite3_column_double(stmt, 2);
1214 if(!aspect_ratio || aspect_ratio < 0.0001f)
1215 {
1216 aspect_ratio = dt_image_set_aspect_ratio(nid, FALSE);
1217 // if an error occurs, let's use 1:1 value
1218 if(aspect_ratio < 0.0001f) aspect_ratio = 1.0f;
1219 }
1220 thumb->aspect_ratio = aspect_ratio;
1221 newlist = g_list_prepend(newlist, thumb);
1222 nbnew++;
1223 }
1224 // if it's the offset, we record the imgid
1225 if(nrow == table->offset) table->offset_imgid = nid;
1226 pos++;
1227 }
1228 newlist = g_list_reverse(newlist); // list was built in reverse order, so un-reverse it
1229
1230 // in rare cases, we can have less images than wanted
1231 // although there's images before (this shouldn't happen in preview)
1232 if(table->navigate_inside_selection && g_list_shorter_than(newlist, table->thumbs_count)
1233 && g_list_shorter_than(newlist, _get_selection_count()))
1234 {
1235 const int nb = table->thumbs_count - g_list_length(newlist);
1236 query = dt_util_dstrcat(NULL,
1237 "SELECT m.rowid, m.imgid, b.aspect_ratio "
1238 "FROM memory.collected_images AS m, main.selected_images AS s, images AS b "
1239 "WHERE m.imgid = b.id AND m.imgid = s.imgid AND m.rowid < %d "
1240 "ORDER BY m.rowid DESC "
1241 "LIMIT %d",
1242 offset, nb);
1243 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
1244 if(stmt != NULL)
1245 {
1246 pos = 0;
1247 while(sqlite3_step(stmt) == SQLITE_ROW && g_list_shorter_than(newlist, table->thumbs_count+1))
1248 {
1249 const int nrow = sqlite3_column_int(stmt, 0);
1250 const int nid = sqlite3_column_int(stmt, 1);
1251 // first, we search if the thumb is already here
1252 GList *tl = g_list_find_custom(table->list, GINT_TO_POINTER(nid), _list_compare_by_imgid);
1253 if(tl)
1254 {
1255 dt_thumbnail_t *thumb = (dt_thumbnail_t *)tl->data;
1256 thumb->rowid = nrow; // this may have changed
1257 newlist = g_list_prepend(newlist, thumb);
1258 // and we remove the thumb from the old list
1259 table->list = g_list_remove(table->list, thumb);
1260 }
1261 else
1262 {
1263 // we create a completely new thumb
1264 // we set its size to the thumb it replace in the list if any otherwise we set it to something > 0 to
1265 // trigger draw events
1266 int nw = 40;
1267 int nh = 40;
1268 if(table->list)
1269 {
1270 dt_thumbnail_t *th_model = (dt_thumbnail_t *)(table->list)->data;
1271 nw = th_model->width;
1272 nh = th_model->height;
1273 }
1274 else if(newlist)
1275 {
1276 dt_thumbnail_t *th_model = (dt_thumbnail_t *)newlist->data;
1277 nw = th_model->width;
1278 nh = th_model->height;
1279 }
1280 dt_thumbnail_t *thumb;
1281 if(table->mode == DT_CULLING_MODE_PREVIEW)
1282 thumb = dt_thumbnail_new(nw, nh, table->zoom_ratio, nid, nrow, table->overlays,
1283 DT_THUMBNAIL_CONTAINER_PREVIEW, table->show_tooltips);
1284 else
1285 thumb = dt_thumbnail_new(nw, nh, table->zoom_ratio, nid, nrow, table->overlays,
1286 DT_THUMBNAIL_CONTAINER_CULLING, table->show_tooltips);
1287
1288 thumb->display_focus = table->focus;
1289 thumb->sel_mode = DT_THUMBNAIL_SEL_MODE_DISABLED;
1290 float aspect_ratio = sqlite3_column_double(stmt, 2);
1291 if(!aspect_ratio || aspect_ratio < 0.0001f)
1292 {
1293 aspect_ratio = dt_image_set_aspect_ratio(nid, FALSE);
1294 // if an error occurs, let's use 1:1 value
1295 if(aspect_ratio < 0.0001f) aspect_ratio = 1.0f;
1296 }
1297 thumb->aspect_ratio = aspect_ratio;
1298 newlist = g_list_prepend(newlist, thumb);
1299 nbnew++;
1300 }
1301 // if it's the offset, we record the imgid
1302 if(nrow == table->offset) table->offset_imgid = nid;
1303 pos++;
1304 }
1305 sqlite3_finalize(stmt);
1306 }
1307 g_free(query);
1308 }
1309
1310 // now we cleanup all remaining thumbs from old table->list and set it again
1311 g_list_free_full(table->list, _list_remove_thumb);
1312 table->list = newlist;
1313
1314 // and we ensure that we have the right offset
1315 if(table->list)
1316 {
1317 dt_thumbnail_t *thumb = (dt_thumbnail_t *)table->list->data;
1318 table->offset_imgid = thumb->imgid;
1319 table->offset = _thumb_get_rowid(thumb->imgid);
1320 }
1321 return TRUE;
1322 }
1323
_thumbs_compute_positions(dt_culling_t * table)1324 static gboolean _thumbs_compute_positions(dt_culling_t *table)
1325 {
1326 if(!gtk_widget_get_visible(table->widget)) return FALSE;
1327 if(!table->list) return FALSE;
1328
1329 // if we have only 1 image, it should take the entire screen
1330 if(g_list_is_singleton(table->list))
1331 {
1332 dt_thumbnail_t *thumb = (dt_thumbnail_t *)table->list->data;
1333 thumb->width = table->view_width;
1334 thumb->height = table->view_height;
1335 thumb->x = 0;
1336 thumb->y = 0;
1337 return TRUE;
1338 }
1339
1340 int sum_w = 0, max_h = 0, max_w = 0;
1341
1342 unsigned int total_width = 0, total_height = 0;
1343 int distance = 1;
1344 float avg_ratio = 0;
1345
1346 // reinit size and positions and get max values
1347 int count = 0;
1348 for(GList *l = table->list; l; l = g_list_next(l))
1349 {
1350 dt_thumbnail_t *thumb = (dt_thumbnail_t *)l->data;
1351 const float aspect_ratio = thumb->aspect_ratio;
1352 thumb->width = (gint)(sqrt(aspect_ratio) * 100);
1353 thumb->height = (gint)(1 / sqrt(aspect_ratio) * 100);
1354 thumb->x = thumb->y = 0;
1355
1356 sum_w += thumb->width;
1357 max_w = MAX(max_w, thumb->width);
1358 max_h = MAX(max_h, thumb->height);
1359 avg_ratio += thumb->width / (float)thumb->height;
1360 count++;
1361 }
1362
1363 avg_ratio /= count;
1364
1365 int per_row, tmp_per_row, per_col, tmp_per_col;
1366 per_row = tmp_per_row = ceil(sqrt(count));
1367 per_col = tmp_per_col = (count + per_row - 1) / per_row;
1368
1369 float tmp_slot_ratio, slot_ratio;
1370 tmp_slot_ratio = slot_ratio = (table->view_width / (float)per_row) / (table->view_height / (float)per_col);
1371
1372 do
1373 {
1374 per_row = tmp_per_row;
1375 per_col = tmp_per_col;
1376 slot_ratio = tmp_slot_ratio;
1377
1378 if(avg_ratio > slot_ratio)
1379 {
1380 tmp_per_row = per_row - 1;
1381 }
1382 else
1383 {
1384 tmp_per_row = per_row + 1;
1385 }
1386
1387 if(tmp_per_row == 0) break;
1388
1389 tmp_per_col = (count + tmp_per_row - 1) / tmp_per_row;
1390
1391 tmp_slot_ratio = (table->view_width / (float)tmp_per_row) / (table->view_height / (float)tmp_per_col);
1392
1393 } while(per_row > 0 && per_row <= count && _absmul(tmp_slot_ratio, avg_ratio) < _absmul(slot_ratio, avg_ratio));
1394
1395 GList *slots = NULL;
1396
1397 // Vertical layout
1398 for(GList *l = table->list; l; l = g_list_next(l))
1399 {
1400 dt_thumbnail_t *thumb = (dt_thumbnail_t *)l->data;
1401 GList *slot_iter = slots;
1402 for(; slot_iter; slot_iter = slot_iter->next)
1403 {
1404 GList *slot = (GList *)slot_iter->data;
1405 // Calculate current total height of slot
1406 int slot_h = distance;
1407 for(GList *slot_cw_iter = slot; slot_cw_iter; slot_cw_iter = g_list_next(slot_cw_iter))
1408 {
1409 dt_thumbnail_t *slot_cw = (dt_thumbnail_t *)slot_cw_iter->data;
1410 slot_h = slot_h + slot_cw->height + distance;
1411 }
1412 // Add window to slot if the slot height after adding the window
1413 // doesn't exceed max window height
1414 if(slot_h + distance + thumb->height < max_h)
1415 {
1416 slot_iter->data = g_list_append(slot, thumb);
1417 break;
1418 }
1419 }
1420 // Otherwise, create a new slot with only this window
1421 if(!slot_iter) slots = g_list_prepend(slots, g_list_prepend(NULL, thumb));
1422 }
1423 slots = g_list_reverse(slots); // list was built in reverse order, so un-reverse it
1424
1425 GList *rows = g_list_append(NULL, NULL);
1426 {
1427 int row_y = 0, x = 0, row_h = 0;
1428 int max_row_w = sum_w / per_col;
1429 for(GList *slot_iter = slots; slot_iter; slot_iter = g_list_next(slot_iter))
1430 {
1431 GList *slot = (GList *)slot_iter->data;
1432
1433 // Max width of windows in the slot
1434 int slot_max_w = 0;
1435 for(GList *slot_cw_iter = slot; slot_cw_iter; slot_cw_iter = g_list_next(slot_cw_iter))
1436 {
1437 dt_thumbnail_t *cw = (dt_thumbnail_t *)slot_cw_iter->data;
1438 slot_max_w = MAX(slot_max_w, cw->width);
1439 }
1440
1441 int y = row_y;
1442 for(GList *slot_cw_iter = slot; slot_cw_iter; slot_cw_iter = g_list_next(slot_cw_iter))
1443 {
1444 dt_thumbnail_t *cw = (dt_thumbnail_t *)slot_cw_iter->data;
1445 cw->x = x + (slot_max_w - cw->width) / 2;
1446 cw->y = y;
1447 y += cw->height + distance;
1448 rows->data = g_list_append(rows->data, cw);
1449 }
1450
1451 row_h = MAX(row_h, y - row_y);
1452 total_height = MAX(total_height, y);
1453 x += slot_max_w + distance;
1454 total_width = MAX(total_width, x);
1455
1456 if(x > max_row_w)
1457 {
1458 x = 0;
1459 row_y += row_h;
1460 row_h = 0;
1461 rows = g_list_append(rows, 0);
1462 rows = rows->next; // keep rows pointing at last element to avoid quadratic runtime
1463 }
1464 g_list_free(slot);
1465 }
1466 g_list_free(slots);
1467 slots = NULL;
1468 }
1469
1470 rows = g_list_first(rows); // rows points at the last element of the constructed list, so move it back to the start
1471
1472 total_width -= distance;
1473 total_height -= distance;
1474
1475 for(const GList *iter = rows; iter; iter = g_list_next(iter))
1476 {
1477 GList *row = (GList *)iter->data;
1478 int row_w = 0, xoff;
1479 int max_rh = 0;
1480
1481 for(GList *slot_cw_iter = row; slot_cw_iter; slot_cw_iter = g_list_next(slot_cw_iter))
1482 {
1483 dt_thumbnail_t *cw = (dt_thumbnail_t *)slot_cw_iter->data;
1484 row_w = MAX(row_w, cw->x + cw->width);
1485 max_rh = MAX(max_rh, cw->height);
1486 }
1487
1488 xoff = (total_width - row_w) / 2;
1489
1490 for(GList *cw_iter = row; cw_iter; cw_iter = g_list_next(cw_iter))
1491 {
1492 dt_thumbnail_t *cw = (dt_thumbnail_t *)cw_iter->data;
1493 cw->x += xoff;
1494 cw->height = max_rh;
1495 }
1496 g_list_free(row);
1497 }
1498
1499 g_list_free(rows);
1500
1501 float factor;
1502 factor = (float)(table->view_width - 1) / total_width;
1503 if(factor * total_height > table->view_height - 1) factor = (float)(table->view_height - 1) / total_height;
1504
1505 int xoff = (table->view_width - (float)total_width * factor) / 2;
1506 int yoff = (table->view_height - (float)total_height * factor) / 2;
1507
1508 for(GList *l = table->list; l; l = g_list_next(l))
1509 {
1510 dt_thumbnail_t *thumb = (dt_thumbnail_t *)l->data;
1511 thumb->width = thumb->width * factor;
1512 thumb->height = thumb->height * factor;
1513 thumb->x = thumb->x * factor + xoff;
1514 thumb->y = thumb->y * factor + yoff;
1515 }
1516
1517 // we save the current first id
1518 dt_conf_set_int("plugins/lighttable/culling_last_id", table->offset_imgid);
1519
1520 return TRUE;
1521 }
1522
dt_culling_update_active_images_list(dt_culling_t * table)1523 void dt_culling_update_active_images_list(dt_culling_t *table)
1524 {
1525 // we erase the list of active images
1526 g_slist_free(darktable.view_manager->active_images);
1527 darktable.view_manager->active_images = NULL;
1528
1529 // and we effectively move and resize thumbs
1530 for(GList *l = table->list; l; l = g_list_next(l))
1531 {
1532 dt_thumbnail_t *thumb = (dt_thumbnail_t *)l->data;
1533 // we update the active images list
1534 darktable.view_manager->active_images
1535 = g_slist_append(darktable.view_manager->active_images, GINT_TO_POINTER(thumb->imgid));
1536 }
1537
1538 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_ACTIVE_IMAGES_CHANGE);
1539 }
1540
1541 // recreate the list of thumb if needed and recomputes sizes and positions if needed
dt_culling_full_redraw(dt_culling_t * table,gboolean force)1542 void dt_culling_full_redraw(dt_culling_t *table, gboolean force)
1543 {
1544 if(!gtk_widget_get_visible(table->widget)) return;
1545 const double start = dt_get_wtime();
1546 // first, we see if we need to do something
1547 if(!_compute_sizes(table, force)) return;
1548
1549 // we store first image zoom and pos for new ones
1550 float old_zx = 0.0;
1551 float old_zy = 0.0;
1552 int old_margin_x = 0;
1553 int old_margin_y = 0;
1554 if(table->list)
1555 {
1556 dt_thumbnail_t *thumb = (dt_thumbnail_t *)table->list->data;
1557 old_zx = thumb->zoomx;
1558 old_zy = thumb->zoomy;
1559 old_margin_x = gtk_widget_get_margin_start(thumb->w_image_box);
1560 old_margin_y = gtk_widget_get_margin_top(thumb->w_image_box);
1561 }
1562 // we recreate the list of images
1563 _thumbs_recreate_list_at(table, table->offset);
1564
1565 // we compute the sizes and positions of thumbs
1566 _thumbs_compute_positions(table);
1567
1568 // we erase the list of active images
1569 g_slist_free(darktable.view_manager->active_images);
1570 darktable.view_manager->active_images = NULL;
1571
1572 // and we effectively move and resize thumbs
1573 for(GList *l = table->list; l; l = g_list_next(l))
1574 {
1575 dt_thumbnail_t *thumb = (dt_thumbnail_t *)l->data;
1576 // we set the overlays timeout
1577 thumb->overlay_timeout_duration = table->overlays_block_timeout;
1578 // we add or move the thumb at the right position
1579 if(!gtk_widget_get_parent(thumb->w_main))
1580 {
1581 gtk_widget_set_margin_start(thumb->w_image_box, old_margin_x);
1582 gtk_widget_set_margin_top(thumb->w_image_box, old_margin_y);
1583 // and we resize the thumb
1584 dt_thumbnail_resize(thumb, thumb->width, thumb->height, FALSE, table->zoom_ratio);
1585 gtk_layout_put(GTK_LAYOUT(table->widget), thumb->w_main, thumb->x, thumb->y);
1586 thumb->zoomx = old_zx;
1587 thumb->zoomy = old_zy;
1588 }
1589 else
1590 {
1591 gtk_layout_move(GTK_LAYOUT(table->widget), thumb->w_main, thumb->x, thumb->y);
1592 // and we resize the thumb
1593 const float zoom_ratio = thumb->zoom_100 > 1 ? thumb->zoom / thumb->zoom_100 : IMG_TO_FIT;
1594 dt_thumbnail_resize(thumb, thumb->width, thumb->height, FALSE, zoom_ratio);
1595 }
1596
1597 // we update the active images list
1598 darktable.view_manager->active_images
1599 = g_slist_append(darktable.view_manager->active_images, GINT_TO_POINTER(thumb->imgid));
1600 }
1601
1602 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_ACTIVE_IMAGES_CHANGE);
1603
1604 // if the selection should follow active images
1605 if(table->selection_sync)
1606 {
1607 // deactivate selection_change event
1608 table->select_desactivate = TRUE;
1609 // deselect all
1610 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM main.selected_images", NULL, NULL, NULL);
1611 // select all active images
1612 GList *ls = NULL;
1613 for (GList *l = table->list; l; l = g_list_next(l))
1614 {
1615 dt_thumbnail_t *thumb = (dt_thumbnail_t *)l->data;
1616 ls = g_list_prepend(ls, GINT_TO_POINTER(thumb->imgid));
1617 }
1618 ls = g_list_reverse(ls); // list was built in reverse order, so un-reverse it
1619 dt_selection_select_list(darktable.selection, ls);
1620 g_list_free(ls);
1621 // reactivate selection_change event
1622 table->select_desactivate = FALSE;
1623 }
1624
1625 // we prefetch next/previous images
1626 _thumbs_prefetch(table);
1627
1628 // ensure one of the shown image as the focus (to avoid to keep focus to hidden image)
1629 const int selid = dt_control_get_mouse_over_id();
1630 if(selid >= 0)
1631 {
1632 gboolean in_list = FALSE;
1633 for(GList *l = table->list; l; l = g_list_next(l))
1634 {
1635 dt_thumbnail_t *thumb = (dt_thumbnail_t *)l->data;
1636 if(thumb->imgid == selid)
1637 {
1638 in_list = TRUE;
1639 break;
1640 }
1641 }
1642 if(!in_list && table->list)
1643 {
1644 dt_thumbnail_t *thumb = (dt_thumbnail_t *)table->list->data;
1645 dt_control_set_mouse_over_id(thumb->imgid);
1646 }
1647 }
1648
1649 // be sure the focus is in the right widget (needed for accels)
1650 gtk_widget_grab_focus(dt_ui_center(darktable.gui->ui));
1651
1652 dt_print(DT_DEBUG_LIGHTTABLE, "done in %0.04f sec\n", dt_get_wtime() - start);
1653
1654 if(darktable.unmuted & DT_DEBUG_CACHE) dt_mipmap_cache_print(darktable.mipmap_cache);
1655 }
1656
dt_culling_key_move(dt_culling_t * table,dt_culling_move_t move)1657 gboolean dt_culling_key_move(dt_culling_t *table, dt_culling_move_t move)
1658 {
1659 int val = 0;
1660 switch(move)
1661 {
1662 case DT_CULLING_MOVE_LEFT:
1663 case DT_CULLING_MOVE_UP:
1664 val = -1;
1665 break;
1666 case DT_CULLING_MOVE_RIGHT:
1667 case DT_CULLING_MOVE_DOWN:
1668 val = 1;
1669 break;
1670 case DT_CULLING_MOVE_PAGEUP:
1671 val = -1 * table->thumbs_count;
1672 break;
1673 case DT_CULLING_MOVE_PAGEDOWN:
1674 val = table->thumbs_count;
1675 break;
1676 case DT_CULLING_MOVE_START:
1677 val = -1 * INT_MAX;
1678 break;
1679 case DT_CULLING_MOVE_END:
1680 val = INT_MAX;
1681 break;
1682 default:
1683 val = 0;
1684 break;
1685 }
1686 _thumbs_move(table, val);
1687 return TRUE;
1688 }
1689
dt_culling_change_offset_image(dt_culling_t * table,int imgid)1690 void dt_culling_change_offset_image(dt_culling_t *table, int imgid)
1691 {
1692 table->offset = _thumb_get_rowid(imgid);
1693 dt_culling_full_redraw(table, TRUE);
1694 _thumbs_refocus(table);
1695 }
1696
dt_culling_zoom_max(dt_culling_t * table)1697 void dt_culling_zoom_max(dt_culling_t *table)
1698 {
1699 float x = 0;
1700 float y = 0;
1701 if(table->mode == DT_CULLING_MODE_PREVIEW && table->list)
1702 {
1703 dt_thumbnail_t *th = (dt_thumbnail_t *)table->list->data;
1704 x = gtk_widget_get_allocated_width(th->w_image_box) / 2.0;
1705 y = gtk_widget_get_allocated_height(th->w_image_box) / 2.0;
1706 }
1707 _thumbs_zoom_add(table, ZOOM_MAX, x, y, 0);
1708 }
1709
dt_culling_zoom_fit(dt_culling_t * table)1710 void dt_culling_zoom_fit(dt_culling_t *table)
1711 {
1712 table->zoom_ratio = IMG_TO_FIT;
1713 for(GList *l = table->list; l; l = g_list_next(l))
1714 {
1715 _zoom_thumb_fit((dt_thumbnail_t *)l->data);
1716 }
1717 }
1718
1719 // change the type of overlays that should be shown
dt_culling_set_overlays_mode(dt_culling_t * table,dt_thumbnail_overlay_t over)1720 void dt_culling_set_overlays_mode(dt_culling_t *table, dt_thumbnail_overlay_t over)
1721 {
1722 if(!table) return;
1723 gchar *txt = dt_util_dstrcat(NULL, "plugins/lighttable/overlays/culling/%d", table->mode);
1724 dt_conf_set_int(txt, over);
1725 g_free(txt);
1726 gchar *cl0 = _thumbs_get_overlays_class(table->overlays);
1727 gchar *cl1 = _thumbs_get_overlays_class(over);
1728
1729 GtkStyleContext *context = gtk_widget_get_style_context(table->widget);
1730 gtk_style_context_remove_class(context, cl0);
1731 gtk_style_context_add_class(context, cl1);
1732
1733 txt = dt_util_dstrcat(NULL, "plugins/lighttable/overlays/culling_block_timeout/%d", table->mode);
1734 int timeout = 2;
1735 if(!dt_conf_key_exists(txt))
1736 timeout = dt_conf_get_int("plugins/lighttable/overlay_timeout");
1737 else
1738 timeout = dt_conf_get_int(txt);
1739 g_free(txt);
1740
1741 txt = dt_util_dstrcat(NULL, "plugins/lighttable/tooltips/culling/%d", table->mode);
1742 table->show_tooltips = dt_conf_get_bool(txt);
1743 g_free(txt);
1744
1745 // we need to change the overlay content if we pass from normal to extended overlays
1746 // this is not done on the fly with css to avoid computing extended msg for nothing and to reserve space if needed
1747 for(GList *l = table->list; l; l = g_list_next(l))
1748 {
1749 dt_thumbnail_t *th = (dt_thumbnail_t *)l->data;
1750 dt_thumbnail_set_overlay(th, over, timeout);
1751 th->tooltip = table->show_tooltips;
1752 // and we resize the bottom area
1753 const float zoom_ratio = th->zoom_100 > 1 ? th->zoom / th->zoom_100 : table->zoom_ratio;
1754 dt_thumbnail_resize(th, th->width, th->height, TRUE, zoom_ratio);
1755 }
1756
1757 table->overlays = over;
1758 g_free(cl0);
1759 g_free(cl1);
1760 }
1761
1762 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1763 // vim: shiftwidth=2 expandtab tabstop=2 cindent
1764 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1765