1 /*
2 This file is part of darktable,
3 Copyright (C) 2019-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 /** this is the thumbnail class for the lighttable module. */
19 #include "dtgtk/thumbnail.h"
20
21 #include "bauhaus/bauhaus.h"
22 #include "common/collection.h"
23 #include "common/debug.h"
24 #include "common/focus.h"
25 #include "common/focus_peaking.h"
26 #include "common/grouping.h"
27 #include "common/image_cache.h"
28 #include "common/ratings.h"
29 #include "common/selection.h"
30 #include "common/variables.h"
31 #include "control/control.h"
32 #include "dtgtk/button.h"
33 #include "dtgtk/icon.h"
34 #include "dtgtk/thumbnail_btn.h"
35 #include "gui/drag_and_drop.h"
36 #include "gui/accelerators.h"
37 #include "views/view.h"
38
39 static void _thumb_resize_overlays(dt_thumbnail_t *thumb);
40
_set_flag(GtkWidget * w,GtkStateFlags flag,gboolean over)41 static void _set_flag(GtkWidget *w, GtkStateFlags flag, gboolean over)
42 {
43 int flags = gtk_widget_get_state_flags(w);
44 if(over)
45 flags |= flag;
46 else
47 flags &= ~flag;
48
49 gtk_widget_set_state_flags(w, flags, TRUE);
50 }
51
52 // create a new extended infos line from strach
_thumb_update_extended_infos_line(dt_thumbnail_t * thumb)53 static void _thumb_update_extended_infos_line(dt_thumbnail_t *thumb)
54 {
55 gchar *pattern = dt_conf_get_string("plugins/lighttable/extended_pattern");
56 // we compute the info line (we reuse the function used in export to disk)
57 char input_dir[1024] = { 0 };
58 gboolean from_cache = TRUE;
59 dt_image_full_path(thumb->imgid, input_dir, sizeof(input_dir), &from_cache);
60
61 dt_variables_params_t *vp;
62 dt_variables_params_init(&vp);
63
64 vp->filename = input_dir;
65 vp->jobcode = "infos";
66 vp->imgid = thumb->imgid;
67 vp->sequence = 0;
68 vp->escape_markup = TRUE;
69
70 if(thumb->info_line) g_free(thumb->info_line);
71 thumb->info_line = dt_variables_expand(vp, pattern, TRUE);
72
73 dt_variables_params_destroy(vp);
74
75 g_free(pattern);
76 }
77
_image_update_group_tooltip(dt_thumbnail_t * thumb)78 static void _image_update_group_tooltip(dt_thumbnail_t *thumb)
79 {
80 if(!thumb->w_group) return;
81 if(!thumb->is_grouped)
82 {
83 gtk_widget_set_has_tooltip(thumb->w_group, FALSE);
84 return;
85 }
86
87 gchar *tt = NULL;
88 int nb = 0;
89
90 // the group leader
91 if(thumb->imgid == thumb->groupid)
92 tt = g_strdup_printf("\n<b>%s (%s)</b>", _("current"), _("leader"));
93 else
94 {
95 const dt_image_t *img = dt_image_cache_get(darktable.image_cache, thumb->groupid, 'r');
96 if(img)
97 {
98 tt = g_strdup_printf("\n<b>%s (%s)</b>", img->filename, _("leader"));
99 dt_image_cache_read_release(darktable.image_cache, img);
100 }
101 }
102
103 // and the other images
104 sqlite3_stmt *stmt;
105 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
106 "SELECT id, version, filename FROM main.images WHERE group_id = ?1", -1, &stmt,
107 NULL);
108 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, thumb->groupid);
109 while(sqlite3_step(stmt) == SQLITE_ROW)
110 {
111 nb++;
112 const int id = sqlite3_column_int(stmt, 0);
113 const int v = sqlite3_column_int(stmt, 1);
114
115 if(id != thumb->groupid)
116 {
117 if(id == thumb->imgid)
118 tt = dt_util_dstrcat(tt, "\n%s", _("current"));
119 else
120 {
121 tt = dt_util_dstrcat(tt, "\n%s", sqlite3_column_text(stmt, 2));
122 if(v > 0) tt = dt_util_dstrcat(tt, " v%d", v);
123 }
124 }
125 }
126 sqlite3_finalize(stmt);
127
128 // and the number of grouped images
129 gchar *ttf = g_strdup_printf("%d %s\n%s", nb, _("grouped images"), tt);
130 g_free(tt);
131
132 // let's apply the tooltip
133 gtk_widget_set_tooltip_markup(thumb->w_group, ttf);
134 g_free(ttf);
135 }
136
_thumb_update_rating_class(dt_thumbnail_t * thumb)137 static void _thumb_update_rating_class(dt_thumbnail_t *thumb)
138 {
139 if(!thumb->w_main) return;
140
141 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_main);
142 for(int i = DT_VIEW_DESERT; i <= DT_VIEW_REJECT; i++)
143 {
144 gchar *cn = g_strdup_printf("dt_thumbnail_rating_%d", i);
145 if(thumb->rating == i)
146 gtk_style_context_add_class(context, cn);
147 else
148 gtk_style_context_remove_class(context, cn);
149 g_free(cn);
150 }
151 }
152
_image_get_infos(dt_thumbnail_t * thumb)153 static void _image_get_infos(dt_thumbnail_t *thumb)
154 {
155 if(thumb->imgid <= 0) return;
156 if(thumb->over == DT_THUMBNAIL_OVERLAYS_NONE) return;
157
158 // we only get here infos that might change, others(exif, ...) are cached on widget creation
159
160 const int old_rating = thumb->rating;
161 thumb->rating = 0;
162 const dt_image_t *img = dt_image_cache_get(darktable.image_cache, thumb->imgid, 'r');
163 if(img)
164 {
165 thumb->has_localcopy = (img->flags & DT_IMAGE_LOCAL_COPY);
166 thumb->rating = img->flags & DT_IMAGE_REJECTED ? DT_VIEW_REJECT : (img->flags & DT_VIEW_RATINGS_MASK);
167 thumb->is_bw = dt_image_monochrome_flags(img);
168 thumb->is_bw_flow = dt_image_use_monochrome_workflow(img);
169 thumb->is_hdr = dt_image_is_hdr(img);
170
171 thumb->groupid = img->group_id;
172
173 dt_image_cache_read_release(darktable.image_cache, img);
174 }
175 // if the rating as changed, update the rejected
176 if(old_rating != thumb->rating)
177 {
178 _thumb_update_rating_class(thumb);
179 }
180
181 // colorlabels
182 thumb->colorlabels = 0;
183 DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.get_color);
184 DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.get_color);
185 DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.get_color, 1, thumb->imgid);
186 while(sqlite3_step(darktable.view_manager->statements.get_color) == SQLITE_ROW)
187 {
188 const int col = sqlite3_column_int(darktable.view_manager->statements.get_color, 0);
189 // we reuse CPF_* flags, as we'll pass them to the paint fct after
190 if(col == 0)
191 thumb->colorlabels |= CPF_DIRECTION_UP;
192 else if(col == 1)
193 thumb->colorlabels |= CPF_DIRECTION_DOWN;
194 else if(col == 2)
195 thumb->colorlabels |= CPF_DIRECTION_LEFT;
196 else if(col == 3)
197 thumb->colorlabels |= CPF_DIRECTION_RIGHT;
198 else if(col == 4)
199 thumb->colorlabels |= CPF_BG_TRANSPARENT;
200 }
201 if(thumb->w_color)
202 {
203 GtkDarktableThumbnailBtn *btn = (GtkDarktableThumbnailBtn *)thumb->w_color;
204 btn->icon_flags = thumb->colorlabels;
205 }
206
207 // altered
208 thumb->is_altered = dt_image_altered(thumb->imgid);
209
210 // grouping
211 DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.get_grouped);
212 DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.get_grouped);
213 DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.get_grouped, 1, thumb->imgid);
214 DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.get_grouped, 2, thumb->imgid);
215 thumb->is_grouped = (sqlite3_step(darktable.view_manager->statements.get_grouped) == SQLITE_ROW);
216
217 // grouping tooltip
218 _image_update_group_tooltip(thumb);
219 }
220
_thumb_expose_again(gpointer user_data)221 static gboolean _thumb_expose_again(gpointer user_data)
222 {
223 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
224 if(!thumb) return FALSE;
225 gpointer w_image = thumb->w_image;
226 if(!w_image || !GTK_IS_WIDGET(w_image)) return FALSE;
227
228 thumb->expose_again_timeout_id = 0;
229 gtk_widget_queue_draw((GtkWidget *)w_image);
230 return FALSE;
231 }
232
_thumb_set_image_size(dt_thumbnail_t * thumb,int image_w,int image_h)233 static void _thumb_set_image_size(dt_thumbnail_t *thumb, int image_w, int image_h)
234 {
235 int imgbox_w = 0;
236 int imgbox_h = 0;
237 gtk_widget_get_size_request(thumb->w_image_box, &imgbox_w, &imgbox_h);
238
239 gtk_widget_set_size_request(thumb->w_image, MIN(image_w, imgbox_w), MIN(image_h, imgbox_h));
240 }
241
_thumb_draw_image(dt_thumbnail_t * thumb,cairo_t * cr)242 static void _thumb_draw_image(dt_thumbnail_t *thumb, cairo_t *cr)
243 {
244 if(!thumb->w_image) return;
245
246 // we draw the image
247 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_image);
248 int w = 0;
249 int h = 0;
250 gtk_widget_get_size_request(thumb->w_image, &w, &h);
251
252 // Safety check to avoid possible error
253 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) >= 1)
254 {
255 cairo_save(cr);
256 const float scaler = 1.0f / darktable.gui->ppd_thb;
257 cairo_scale(cr, scaler, scaler);
258
259 cairo_set_source_surface(cr, thumb->img_surf, thumb->zoomx * darktable.gui->ppd,
260 thumb->zoomy * darktable.gui->ppd);
261
262 // get the transparency value
263 GdkRGBA im_color;
264 gtk_style_context_get_color(context, gtk_widget_get_state_flags(thumb->w_image), &im_color);
265 cairo_paint_with_alpha(cr, im_color.alpha);
266
267 // and eventually the image border
268 gtk_render_frame(context, cr, 0, 0, w * darktable.gui->ppd_thb, h * darktable.gui->ppd_thb);
269 cairo_restore(cr);
270 }
271
272 // if needed we draw the working msg too
273 if(thumb->busy)
274 {
275 dt_control_draw_busy_msg(cr, w, h);
276 }
277 }
278
_thumb_retrieve_margins(dt_thumbnail_t * thumb)279 static void _thumb_retrieve_margins(dt_thumbnail_t *thumb)
280 {
281 if(thumb->img_margin) gtk_border_free(thumb->img_margin);
282 // we retrieve image margins from css
283 GtkStateFlags state = gtk_widget_get_state_flags(thumb->w_image);
284 thumb->img_margin = gtk_border_new();
285 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_image);
286 gtk_style_context_get_margin(context, state, thumb->img_margin);
287
288 // and we apply it to the thumb size
289 int width, height;
290 gtk_widget_get_size_request(thumb->w_main, &width, &height);
291 thumb->img_margin->left = MAX(0, thumb->img_margin->left * width / 1000);
292 thumb->img_margin->top = MAX(0, thumb->img_margin->top * height / 1000);
293 thumb->img_margin->right = MAX(0, thumb->img_margin->right * width / 1000);
294 thumb->img_margin->bottom = MAX(0, thumb->img_margin->bottom * height / 1000);
295 }
296
_thumb_write_extension(dt_thumbnail_t * thumb)297 static void _thumb_write_extension(dt_thumbnail_t *thumb)
298 {
299 // fill the file extension label
300 const char *ext = thumb->filename + strlen(thumb->filename);
301 while(ext > thumb->filename && *ext != '.') ext--;
302 ext++;
303 gchar *uext = dt_view_extend_modes_str(ext, thumb->is_hdr, thumb->is_bw, thumb->is_bw_flow);
304 gtk_label_set_text(GTK_LABEL(thumb->w_ext), uext);
305 g_free(uext);
306 }
307
_event_cursor_draw(GtkWidget * widget,cairo_t * cr,gpointer user_data)308 static gboolean _event_cursor_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
309 {
310 if(!user_data || !widget) return TRUE;
311 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
312
313 GtkStateFlags state = gtk_widget_get_state_flags(thumb->w_cursor);
314 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_cursor);
315 GdkRGBA col;
316 gtk_style_context_get_color(context, state, &col);
317
318 cairo_set_source_rgba(cr, col.red, col.green, col.blue, col.alpha);
319 cairo_line_to(cr, gtk_widget_get_allocated_width(widget), 0);
320 cairo_line_to(cr, gtk_widget_get_allocated_width(widget) / 2, gtk_widget_get_allocated_height(widget));
321 cairo_line_to(cr, 0, 0);
322 cairo_close_path(cr);
323 cairo_fill(cr);
324
325 return TRUE;
326 }
327
328 // zoom_ratio is 0-1 based, where 0 is "img to fit" and 1 "zoom to 100%". returns a thumb->zoom value
_zoom_ratio_to_thumb_zoom(float zoom_ratio,float zoom_100)329 static float _zoom_ratio_to_thumb_zoom(float zoom_ratio, float zoom_100)
330 {
331 return (zoom_100 - 1) * zoom_ratio + 1;
332 }
333
334 // converts a thumb->zoom value based on it's zoom_100 (max value) to a 0-1 based zoom_ratio.
_thumb_zoom_to_zoom_ratio(float zoom,float zoom_100)335 static float _thumb_zoom_to_zoom_ratio(float zoom, float zoom_100)
336 {
337 return (zoom - 1) / (zoom_100 - 1);
338 }
339
340 // given max_width & max_height, the width and height is calculated to fit an image in a "img to fit" mode
341 // (everything is visible)
_get_dimensions_for_img_to_fit(dt_thumbnail_t * thumb,int max_width,int max_height,float * width,float * height)342 static void _get_dimensions_for_img_to_fit(dt_thumbnail_t *thumb, int max_width, int max_height, float *width,
343 float *height)
344 {
345 float iw = max_width;
346 float ih = max_height;
347
348 // we can't rely on img->aspect_ratio as the value is round to 1 decimal, so not enough accurate
349 // so we compute it from the larger available mipmap
350 float ar = 0.0f;
351 for(int k = DT_MIPMAP_7; k >= DT_MIPMAP_0; k--)
352 {
353 dt_mipmap_buffer_t tmp;
354 dt_mipmap_cache_get(darktable.mipmap_cache, &tmp, thumb->imgid, k, DT_MIPMAP_TESTLOCK, 'r');
355 if(tmp.buf)
356 {
357 const int mipw = tmp.width;
358 const int miph = tmp.height;
359 dt_mipmap_cache_release(darktable.mipmap_cache, &tmp);
360 if(mipw > 0 && miph > 0)
361 {
362 ar = (float)mipw / miph;
363 break;
364 }
365 }
366 }
367
368 if(ar < 0.001)
369 {
370 // let's try with the aspect_ratio store in image structure, even if it's less accurate
371 const dt_image_t *img = dt_image_cache_get(darktable.image_cache, thumb->imgid, 'r');
372 if(img)
373 {
374 ar = img->aspect_ratio;
375 dt_image_cache_read_release(darktable.image_cache, img);
376 }
377 }
378
379 if(ar > 0.001)
380 {
381 // we have a valid ratio, let's apply it
382 if(ar < 1.0)
383 iw = ih * ar;
384 else
385 ih = iw / ar;
386 // rescale to ensure it stay in thumbnails bounds
387 const float scale = fminf(1.0, fminf((float)max_width / iw, (float)max_height / ih));
388 iw *= scale;
389 ih *= scale;
390 }
391
392 *width = iw;
393 *height = ih;
394 }
395
396 // retrieves image zoom100 and final_width/final_height to calculate the dimensions of the zoomed image.
_get_dimensions_for_zoomed_img(dt_thumbnail_t * thumb,int max_width,int max_height,float zoom_ratio,float * width,float * height)397 static void _get_dimensions_for_zoomed_img(dt_thumbnail_t *thumb, int max_width, int max_height, float zoom_ratio,
398 float *width, float *height)
399 {
400 float iw = max_width;
401 float ih = max_height;
402 // we need to get proper dimensions for the image to determine the image_w size.
403 // calling dt_thumbnail_get_zoom100 is used to get the max zoom, but also to ensure that final_width and
404 // height are available.
405 const float zoom_100 = dt_thumbnail_get_zoom100(thumb);
406 const dt_image_t *img = dt_image_cache_get(darktable.image_cache, thumb->imgid, 'r');
407 if(img)
408 {
409 if(img->final_width > 0 && img->final_height > 0)
410 {
411 iw = img->final_width;
412 ih = img->final_height;
413 }
414 dt_image_cache_read_release(darktable.image_cache, img);
415 }
416
417 // scale first to "img to fit", then apply the zoom ratio to get the resulting final (zoomed) image
418 // dimensions, while making sure to still fit in the imagebox.
419 const float scale_to_fit = fminf((float)max_width / iw, (float)max_height / ih);
420 thumb->zoom = _zoom_ratio_to_thumb_zoom(zoom_ratio, zoom_100);
421 *width = MIN(iw * scale_to_fit * thumb->zoom, max_width);
422 *height = MIN(ih * scale_to_fit * thumb->zoom, max_height);
423 }
424
_thumb_set_image_area(dt_thumbnail_t * thumb,float zoom_ratio)425 static void _thumb_set_image_area(dt_thumbnail_t *thumb, float zoom_ratio)
426 {
427 // let's ensure we have the right margins
428 _thumb_retrieve_margins(thumb);
429
430 int image_w, image_h;
431 int posy = 0;
432 if(thumb->over == DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL || thumb->over == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED)
433 {
434 image_w = thumb->width - thumb->img_margin->left - thumb->img_margin->right;
435 int w = 0;
436 int h = 0;
437 gtk_widget_get_size_request(thumb->w_bottom_eb, &w, &h);
438 image_h = thumb->height - MAX(0, h);
439 gtk_widget_get_size_request(thumb->w_altered, &w, &h);
440 if(!thumb->zoomable)
441 {
442 posy = h + gtk_widget_get_margin_top(thumb->w_altered);
443 image_h -= posy;
444 }
445 else
446 image_h -= thumb->img_margin->bottom;
447 image_h -= thumb->img_margin->top;
448 posy += thumb->img_margin->top;
449 }
450 else if(thumb->over == DT_THUMBNAIL_OVERLAYS_MIXED)
451 {
452 image_w = thumb->width - thumb->img_margin->left - thumb->img_margin->right;
453 int w = 0;
454 int h = 0;
455 gtk_widget_get_size_request(thumb->w_reject, &w, &h);
456 image_h = thumb->height - (h + gtk_widget_get_margin_bottom(thumb->w_reject));
457 gtk_widget_get_size_request(thumb->w_altered, &w, &h);
458 posy = h + gtk_widget_get_margin_top(thumb->w_altered);
459 image_h -= posy;
460 image_h -= thumb->img_margin->top + thumb->img_margin->bottom;
461 posy += thumb->img_margin->top;
462 }
463 else
464 {
465 image_w = thumb->width - thumb->img_margin->left - thumb->img_margin->right;
466 image_h = thumb->height - thumb->img_margin->top - thumb->img_margin->bottom;
467 posy = thumb->img_margin->top;
468 }
469
470 // we check that the image drawing area is not greater than the box
471 int wi = 0;
472 int hi = 0;
473 gtk_widget_get_size_request(thumb->w_image, &wi, &hi);
474 if(wi <= 0 || hi <= 0)
475 {
476 // we arrive here if we are inside the creation process
477 float iw = image_w;
478 float ih = image_h;
479
480 if(zoom_ratio == IMG_TO_FIT)
481 _get_dimensions_for_img_to_fit(thumb, image_w, image_h, &iw, &ih);
482 else
483 _get_dimensions_for_zoomed_img(thumb, image_w, image_h, zoom_ratio, &iw, &ih);
484
485 gtk_widget_set_size_request(thumb->w_image, iw, ih);
486 }
487 else
488 {
489 const float scale = fminf((float)image_w / wi, (float)image_h / hi);
490 if(scale < 1.0f) gtk_widget_set_size_request(thumb->w_image, wi * scale, hi * scale);
491 }
492
493 // and we set the size and margins of the imagebox
494 gtk_widget_set_size_request(thumb->w_image_box, image_w, image_h);
495 gtk_widget_set_margin_start(thumb->w_image_box, thumb->img_margin->left);
496 gtk_widget_set_margin_top(thumb->w_image_box, posy);
497 }
498
_event_image_draw(GtkWidget * widget,cairo_t * cr,gpointer user_data)499 static gboolean _event_image_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
500 {
501 if(!user_data) return TRUE;
502 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
503 if(thumb->imgid <= 0)
504 {
505 dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_LIGHTTABLE_BG);
506 cairo_paint(cr);
507 return TRUE;
508 }
509
510 // if we have a rgbbuf but the thumb is not anymore the darkroom main one
511 dt_develop_t *dev = darktable.develop;
512 const dt_view_t *v = dt_view_manager_get_current_view(darktable.view_manager);
513 if(thumb->img_surf_preview
514 && (v->view(v) != DT_VIEW_DARKROOM || !dev->preview_pipe->output_backbuf
515 || dev->preview_pipe->output_imgid != thumb->imgid))
516 {
517 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0)
518 cairo_surface_destroy(thumb->img_surf);
519 thumb->img_surf = NULL;
520 thumb->img_surf_dirty = TRUE;
521 }
522
523 // if image surface has no more ref. let's samitize it's value to NULL
524 if(thumb->img_surf
525 && cairo_surface_get_reference_count(thumb->img_surf) < 1)
526 thumb->img_surf = NULL;
527
528 // if we don't have it in memory, we want the image surface
529 dt_view_surface_value_t res = DT_VIEW_SURFACE_OK;
530 if(!thumb->img_surf || thumb->img_surf_dirty)
531 {
532 int image_w = 0;
533 int image_h = 0;
534 _thumb_set_image_area(thumb, IMG_TO_FIT);
535 gtk_widget_get_size_request(thumb->w_image_box, &image_w, &image_h);
536
537 if(v->view(v) == DT_VIEW_DARKROOM
538 && dev->preview_pipe->output_imgid == thumb->imgid
539 && dev->preview_pipe->output_backbuf)
540 {
541 // the current thumb is the one currently developed in darkroom
542 // better use the preview buffer for surface, in order to stay in sync
543 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0)
544 cairo_surface_destroy(thumb->img_surf);
545 thumb->img_surf = NULL;
546
547 // get new surface with preview image
548 const int buf_width = dev->preview_pipe->output_backbuf_width;
549 const int buf_height = dev->preview_pipe->output_backbuf_height;
550 uint8_t *rgbbuf = g_malloc0(sizeof(unsigned char) * 4 * buf_width * buf_height);
551
552 dt_pthread_mutex_t *mutex = &dev->preview_pipe->backbuf_mutex;
553 dt_pthread_mutex_lock(mutex);
554 memcpy(rgbbuf, dev->preview_pipe->output_backbuf, sizeof(unsigned char) * 4 * buf_width * buf_height);
555 dt_pthread_mutex_unlock(mutex);
556
557 const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, buf_width);
558 cairo_surface_t *tmp_surface
559 = cairo_image_surface_create_for_data(rgbbuf, CAIRO_FORMAT_RGB24, buf_width, buf_height, stride);
560
561 // copy preview image into final surface
562 if(tmp_surface)
563 {
564 float scale = fminf(image_w / (float)buf_width, image_h / (float)buf_height) * darktable.gui->ppd_thb;
565 const int img_width = roundf(buf_width * scale);
566 const int img_height = roundf(buf_height * scale);
567 scale = fmaxf(img_width / (float)buf_width, img_height / (float)buf_height);
568 thumb->img_surf = cairo_image_surface_create(CAIRO_FORMAT_RGB24, img_width, img_height);
569 cairo_t *cr2 = cairo_create(thumb->img_surf);
570 cairo_scale(cr2, scale, scale);
571
572 cairo_set_source_surface(cr2, tmp_surface, 0, 0);
573 // set filter no nearest:
574 // in skull mode, we want to see big pixels.
575 // in 1 iir mode for the right mip, we want to see exactly what the pipe gave us, 1:1 pixel for pixel.
576 // in between, filtering just makes stuff go unsharp.
577 if((buf_width <= 8 && buf_height <= 8) || fabsf(scale - 1.0f) < 0.01f)
578 cairo_pattern_set_filter(cairo_get_source(cr2), CAIRO_FILTER_NEAREST);
579 else
580 cairo_pattern_set_filter(cairo_get_source(cr2), darktable.gui->filter_image);
581
582 cairo_paint(cr2);
583
584 if(darktable.gui->show_focus_peaking)
585 {
586 cairo_save(cr2);
587 cairo_scale(cr2, 1.0f/scale, 1.0f/scale);
588 dt_focuspeaking(cr2, img_width, img_height, cairo_image_surface_get_data(thumb->img_surf),
589 cairo_image_surface_get_width(thumb->img_surf),
590 cairo_image_surface_get_height(thumb->img_surf));
591 cairo_restore(cr2);
592 }
593
594 cairo_surface_destroy(tmp_surface);
595 cairo_destroy(cr2);
596 }
597 if(rgbbuf) g_free(rgbbuf);
598 }
599 else
600 {
601 cairo_surface_t *img_surf = NULL;
602 if(thumb->zoomable)
603 {
604 if(thumb->zoom > 1.0f)
605 thumb->zoom = MIN(thumb->zoom, dt_thumbnail_get_zoom100(thumb));
606 res = dt_view_image_get_surface(thumb->imgid, image_w * thumb->zoom, image_h * thumb->zoom, &img_surf, FALSE);
607 }
608 else
609 {
610 res = dt_view_image_get_surface(thumb->imgid, image_w, image_h, &img_surf, FALSE);
611 }
612
613 if(res == DT_VIEW_SURFACE_OK || res == DT_VIEW_SURFACE_SMALLER)
614 {
615 // if we succeed to get an image (even a smaller one)
616 cairo_surface_t *tmp_surf = thumb->img_surf;
617 thumb->img_surf = img_surf;
618 if(tmp_surf && cairo_surface_get_reference_count(tmp_surf) > 0)
619 cairo_surface_destroy(tmp_surf);
620 }
621 }
622
623 if(thumb->img_surf)
624 {
625 thumb->img_width = cairo_image_surface_get_width(thumb->img_surf);
626 thumb->img_height = cairo_image_surface_get_height(thumb->img_surf);
627 // and we want to resize the imagebox to fit in the imagearea
628 const int imgbox_w = MIN(image_w, thumb->img_width / darktable.gui->ppd_thb);
629 const int imgbox_h = MIN(image_h, thumb->img_height / darktable.gui->ppd_thb);
630 // we record the imagebox size before the change
631 int hh = 0;
632 int ww = 0;
633 gtk_widget_get_size_request(thumb->w_image, &ww, &hh);
634 // and we set the new size of the imagebox
635 _thumb_set_image_size(thumb, imgbox_w, imgbox_h);
636 // the imagebox size may have been slightly sanitized, so we get it again
637 int nhi = 0;
638 int nwi = 0;
639 gtk_widget_get_size_request(thumb->w_image, &nwi, &nhi);
640
641 // panning value need to be adjusted if the imagebox size as changed
642 thumb->zoomx = thumb->zoomx + (nwi - ww) / 2.0;
643 thumb->zoomy = thumb->zoomy + (nhi - hh) / 2.0;
644 // let's sanitize and apply panning values as we are sure the zoomed image is loaded now
645 // here we have to make sure to properly align according to ppd
646 thumb->zoomx
647 = CLAMP(thumb->zoomx, (nwi * darktable.gui->ppd_thb - thumb->img_width) / darktable.gui->ppd_thb, 0);
648 thumb->zoomy
649 = CLAMP(thumb->zoomy, (nhi * darktable.gui->ppd_thb - thumb->img_height) / darktable.gui->ppd_thb, 0);
650
651 // for overlay block, we need to resize it
652 if(thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
653 _thumb_resize_overlays(thumb);
654 }
655
656 // if we don't have the right size of the image now, we reload it again
657 if(res != DT_VIEW_SURFACE_OK)
658 {
659 thumb->busy = TRUE;
660 if(!thumb->expose_again_timeout_id)
661 thumb->expose_again_timeout_id = g_timeout_add(250, _thumb_expose_again, thumb);
662 }
663
664 // if needed we compute and draw here the big rectangle to show focused areas
665 if(res == DT_VIEW_SURFACE_OK && thumb->display_focus)
666 {
667 uint8_t *full_res_thumb = NULL;
668 int32_t full_res_thumb_wd, full_res_thumb_ht;
669 dt_colorspaces_color_profile_type_t color_space;
670 char path[PATH_MAX] = { 0 };
671 gboolean from_cache = TRUE;
672 dt_image_full_path(thumb->imgid, path, sizeof(path), &from_cache);
673 if(!dt_imageio_large_thumbnail(path, &full_res_thumb, &full_res_thumb_wd, &full_res_thumb_ht, &color_space))
674 {
675 // we look for focus areas
676 dt_focus_cluster_t full_res_focus[49];
677 const int frows = 5, fcols = 5;
678 dt_focus_create_clusters(full_res_focus, frows, fcols, full_res_thumb, full_res_thumb_wd,
679 full_res_thumb_ht);
680 // and we draw them on the image
681 cairo_t *cri = cairo_create(thumb->img_surf);
682 dt_focus_draw_clusters(cri, cairo_image_surface_get_width(thumb->img_surf),
683 cairo_image_surface_get_height(thumb->img_surf), thumb->imgid, full_res_thumb_wd,
684 full_res_thumb_ht, full_res_focus, frows, fcols, 1.0, 0, 0);
685 cairo_destroy(cri);
686 }
687 dt_free_align(full_res_thumb);
688 }
689
690
691 // here we are sure to have the right imagesurface
692 if(res == DT_VIEW_SURFACE_OK)
693 {
694 thumb->img_surf_dirty = FALSE;
695 thumb->busy = FALSE;
696 }
697
698 // and we can also set the zooming level if needed
699 if(res == DT_VIEW_SURFACE_OK
700 && thumb->zoomable
701 && thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
702 {
703 if(thumb->zoom_100 < 1.0 || thumb->zoom <= 1.0f)
704 {
705 gtk_label_set_text(GTK_LABEL(thumb->w_zoom), _("fit"));
706 }
707 else
708 {
709 gchar *z = g_strdup_printf("%.0f%%", thumb->zoom * 100.0 / thumb->zoom_100);
710 gtk_label_set_text(GTK_LABEL(thumb->w_zoom), z);
711 g_free(z);
712 }
713 }
714 }
715
716 _thumb_draw_image(thumb, cr);
717
718 return TRUE;
719 }
720
_thumb_update_icons(dt_thumbnail_t * thumb)721 static void _thumb_update_icons(dt_thumbnail_t *thumb)
722 {
723 gtk_widget_set_visible(thumb->w_local_copy, thumb->has_localcopy);
724 gtk_widget_set_visible(thumb->w_altered, thumb->is_altered);
725 gtk_widget_set_visible(thumb->w_group, thumb->is_grouped);
726 gtk_widget_set_visible(thumb->w_audio, thumb->has_audio);
727 gtk_widget_set_visible(thumb->w_color, thumb->colorlabels != 0);
728 gtk_widget_set_visible(thumb->w_zoom_eb, (thumb->zoomable && thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK));
729 gtk_widget_show(thumb->w_bottom_eb);
730 gtk_widget_show(thumb->w_reject);
731 gtk_widget_show(thumb->w_ext);
732 gtk_widget_show(thumb->w_cursor);
733 for(int i = 0; i < MAX_STARS; i++) gtk_widget_show(thumb->w_stars[i]);
734
735 _set_flag(thumb->w_main, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
736 _set_flag(thumb->w_main, GTK_STATE_FLAG_ACTIVE, thumb->active);
737
738 _set_flag(thumb->w_reject, GTK_STATE_FLAG_ACTIVE, (thumb->rating == DT_VIEW_REJECT));
739 for(int i = 0; i < MAX_STARS; i++)
740 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_ACTIVE, (thumb->rating > i && thumb->rating < DT_VIEW_REJECT));
741 _set_flag(thumb->w_group, GTK_STATE_FLAG_ACTIVE, (thumb->imgid == thumb->groupid));
742
743 _set_flag(thumb->w_main, GTK_STATE_FLAG_SELECTED, thumb->selected);
744
745 // and the tooltip
746 gchar *pattern = dt_conf_get_string("plugins/lighttable/thumbnail_tooltip_pattern");
747 if(!thumb->tooltip || strcmp(pattern, "") == 0)
748 {
749 gtk_widget_set_has_tooltip(thumb->w_main, FALSE);
750 }
751 else
752 {
753 // we compute the tooltip (we reuse the function used in export to disk)
754 char input_dir[1024] = { 0 };
755 gboolean from_cache = TRUE;
756 dt_image_full_path(thumb->imgid, input_dir, sizeof(input_dir), &from_cache);
757
758 dt_variables_params_t *vp;
759 dt_variables_params_init(&vp);
760
761 vp->filename = input_dir;
762 vp->jobcode = "infos";
763 vp->imgid = thumb->imgid;
764 vp->sequence = 0;
765 vp->escape_markup = TRUE;
766
767 gchar *msg = dt_variables_expand(vp, pattern, TRUE);
768
769 dt_variables_params_destroy(vp);
770
771 // we change the label
772 gtk_widget_set_tooltip_markup(thumb->w_main, msg);
773
774 g_free(msg);
775 }
776 g_free(pattern);
777
778 // we recompte the history tooltip if needed
779 thumb->is_altered = dt_image_altered(thumb->imgid);
780 gtk_widget_set_visible(thumb->w_altered, thumb->is_altered);
781 if(thumb->is_altered)
782 {
783 char *tooltip = dt_history_get_items_as_string(thumb->imgid);
784 if(tooltip)
785 {
786 gtk_widget_set_tooltip_text(thumb->w_altered, tooltip);
787 g_free(tooltip);
788 }
789 }
790 }
791
_thumbs_hide_overlays(gpointer user_data)792 static gboolean _thumbs_hide_overlays(gpointer user_data)
793 {
794 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
795 thumb->overlay_timeout_id = 0;
796 // if the mouse is inside the infos block, we don't hide them
797 if(gtk_widget_get_state_flags(thumb->w_bottom_eb) & GTK_STATE_FLAG_PRELIGHT) return FALSE;
798
799 gtk_widget_hide(thumb->w_bottom_eb);
800 gtk_widget_hide(thumb->w_reject);
801 for(int i = 0; i < MAX_STARS; i++) gtk_widget_hide(thumb->w_stars[i]);
802 gtk_widget_hide(thumb->w_color);
803 gtk_widget_hide(thumb->w_local_copy);
804 gtk_widget_hide(thumb->w_altered);
805 gtk_widget_hide(thumb->w_group);
806 gtk_widget_hide(thumb->w_audio);
807 gtk_widget_hide(thumb->w_zoom_eb);
808 gtk_widget_hide(thumb->w_ext);
809 return G_SOURCE_REMOVE;
810 }
_thumbs_show_overlays(gpointer user_data)811 static gboolean _thumbs_show_overlays(gpointer user_data)
812 {
813 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
814 _thumb_update_icons(thumb);
815 return G_SOURCE_REMOVE;
816 }
817
_event_main_motion(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)818 static gboolean _event_main_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
819 {
820 if(!user_data) return TRUE;
821 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
822
823 // first, we hide the block overlays after a delay if the mouse hasn't move
824 if(thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
825 {
826 if(thumb->overlay_timeout_id > 0)
827 {
828 g_source_remove(thumb->overlay_timeout_id);
829 thumb->overlay_timeout_id = 0;
830 }
831 _thumbs_show_overlays(thumb);
832 if(thumb->overlay_timeout_duration >= 0)
833 {
834 thumb->overlay_timeout_id
835 = g_timeout_add_seconds(thumb->overlay_timeout_duration, _thumbs_hide_overlays, thumb);
836 }
837 }
838
839 if(!thumb->mouse_over && !thumb->disable_mouseover) dt_control_set_mouse_over_id(thumb->imgid);
840 return FALSE;
841 }
842
_event_main_press(GtkWidget * widget,GdkEventButton * event,gpointer user_data)843 static gboolean _event_main_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
844 {
845 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
846 if(event->button == 1
847 && ((event->type == GDK_2BUTTON_PRESS && !thumb->single_click)
848 || (event->type == GDK_BUTTON_PRESS && dt_modifier_is(event->state, 0) && thumb->single_click)))
849 {
850 dt_control_set_mouse_over_id(thumb->imgid); // to ensure we haven't lost imgid during double-click
851 }
852 return FALSE;
853 }
_event_main_release(GtkWidget * widget,GdkEventButton * event,gpointer user_data)854 static gboolean _event_main_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
855 {
856 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
857
858 if(event->button == 1 && !thumb->moved && thumb->sel_mode != DT_THUMBNAIL_SEL_MODE_DISABLED)
859 {
860 if(dt_modifier_is(event->state, 0) && thumb->sel_mode != DT_THUMBNAIL_SEL_MODE_MOD_ONLY)
861 dt_selection_select_single(darktable.selection, thumb->imgid);
862 else if(dt_modifier_is(event->state, GDK_MOD1_MASK))
863 dt_selection_select_single(darktable.selection, thumb->imgid);
864 else if(dt_modifier_is(event->state, GDK_CONTROL_MASK))
865 dt_selection_toggle(darktable.selection, thumb->imgid);
866 else if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
867 dt_selection_select_range(darktable.selection, thumb->imgid);
868 }
869 return FALSE;
870 }
871
_event_rating_press(GtkWidget * widget,GdkEventButton * event,gpointer user_data)872 static gboolean _event_rating_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
873 {
874 return TRUE;
875 }
_event_rating_release(GtkWidget * widget,GdkEventButton * event,gpointer user_data)876 static gboolean _event_rating_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
877 {
878 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
879 if(thumb->disable_actions) return FALSE;
880 if(dtgtk_thumbnail_btn_is_hidden(widget)) return FALSE;
881
882 if(event->button == 1 && !thumb->moved)
883 {
884 dt_view_image_over_t rating = DT_VIEW_DESERT;
885 if(widget == thumb->w_reject)
886 rating = DT_VIEW_REJECT;
887 else if(widget == thumb->w_stars[0])
888 rating = DT_VIEW_STAR_1;
889 else if(widget == thumb->w_stars[1])
890 rating = DT_VIEW_STAR_2;
891 else if(widget == thumb->w_stars[2])
892 rating = DT_VIEW_STAR_3;
893 else if(widget == thumb->w_stars[3])
894 rating = DT_VIEW_STAR_4;
895 else if(widget == thumb->w_stars[4])
896 rating = DT_VIEW_STAR_5;
897
898 if(rating != DT_VIEW_DESERT)
899 {
900 dt_ratings_apply_on_image(thumb->imgid, rating, TRUE, TRUE, TRUE);
901 dt_collection_update_query(darktable.collection, DT_COLLECTION_CHANGE_RELOAD, DT_COLLECTION_PROP_RATING,
902 g_list_prepend(NULL, GINT_TO_POINTER(thumb->imgid)));
903 }
904 }
905 return TRUE;
906 }
907
_event_grouping_release(GtkWidget * widget,GdkEventButton * event,gpointer user_data)908 static gboolean _event_grouping_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
909 {
910 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
911 if(thumb->disable_actions) return FALSE;
912 if(dtgtk_thumbnail_btn_is_hidden(widget)) return FALSE;
913
914 if(event->button == 1 && !thumb->moved)
915 {
916 //TODO: will succeed if either or *both* of Shift and Control are pressed. Do we want this?
917 if(dt_modifier_is(event->state, GDK_SHIFT_MASK) | dt_modifier_is(event->state, GDK_CONTROL_MASK))
918 {
919 // just add the whole group to the selection. TODO: make this also work for collapsed groups.
920 sqlite3_stmt *stmt;
921 DT_DEBUG_SQLITE3_PREPARE_V2(
922 dt_database_get(darktable.db),
923 "INSERT OR IGNORE INTO main.selected_images SELECT id FROM main.images WHERE group_id = ?1", -1, &stmt,
924 NULL);
925 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, thumb->groupid);
926 sqlite3_step(stmt);
927 sqlite3_finalize(stmt);
928 }
929 else if(!darktable.gui->grouping
930 || thumb->groupid == darktable.gui->expanded_group_id) // the group is already expanded, so ...
931 {
932 if(thumb->imgid == darktable.gui->expanded_group_id && darktable.gui->grouping) // ... collapse it
933 darktable.gui->expanded_group_id = -1;
934 else // ... make the image the new representative of the group
935 darktable.gui->expanded_group_id = dt_grouping_change_representative(thumb->imgid);
936 }
937 else // expand the group
938 darktable.gui->expanded_group_id = thumb->groupid;
939 dt_collection_update_query(darktable.collection, DT_COLLECTION_CHANGE_RELOAD, DT_COLLECTION_PROP_GROUPING,
940 NULL);
941 }
942 return FALSE;
943 }
944
_event_audio_release(GtkWidget * widget,GdkEventButton * event,gpointer user_data)945 static gboolean _event_audio_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
946 {
947 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
948 if(thumb->disable_actions) return FALSE;
949 if(dtgtk_thumbnail_btn_is_hidden(widget)) return FALSE;
950
951 if(event->button == 1 && !thumb->moved)
952 {
953 gboolean start_audio = TRUE;
954 if(darktable.view_manager->audio.audio_player_id != -1)
955 {
956 // don't start the audio for the image we just killed it for
957 if(darktable.view_manager->audio.audio_player_id == thumb->imgid) start_audio = FALSE;
958 dt_view_audio_stop(darktable.view_manager);
959 }
960
961 if(start_audio)
962 {
963 dt_view_audio_start(darktable.view_manager, thumb->imgid);
964 }
965 }
966 return FALSE;
967 }
968
969 // this is called each time the images info change
_dt_image_info_changed_callback(gpointer instance,gpointer imgs,gpointer user_data)970 static void _dt_image_info_changed_callback(gpointer instance, gpointer imgs, gpointer user_data)
971 {
972 if(!user_data || !imgs) return;
973 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
974 for(const GList *i = imgs; i; i = g_list_next(i))
975 {
976 if(GPOINTER_TO_INT(i->data) == thumb->imgid)
977 {
978 dt_thumbnail_update_infos(thumb);
979 break;
980 }
981 }
982 }
983
984 // this is called each time collected images change
985 // we only use this because the image infos may have changed
_dt_collection_changed_callback(gpointer instance,dt_collection_change_t query_change,dt_collection_properties_t changed_property,gpointer imgs,const int next,gpointer user_data)986 static void _dt_collection_changed_callback(gpointer instance, dt_collection_change_t query_change,
987 dt_collection_properties_t changed_property, gpointer imgs,
988 const int next, gpointer user_data)
989 {
990 if(!user_data || !imgs) return;
991 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
992 for(const GList *i = imgs; i; i = g_list_next(i))
993 {
994 if(GPOINTER_TO_INT(i->data) == thumb->imgid)
995 {
996 dt_thumbnail_update_infos(thumb);
997 break;
998 }
999 }
1000 }
1001
dt_thumbnail_update_selection(dt_thumbnail_t * thumb)1002 void dt_thumbnail_update_selection(dt_thumbnail_t *thumb)
1003 {
1004 if(!thumb) return;
1005 if(!gtk_widget_is_visible(thumb->w_main)) return;
1006
1007 gboolean selected = FALSE;
1008 /* clear and reset statements */
1009 DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.is_selected);
1010 DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.is_selected);
1011 /* bind imgid to prepared statements */
1012 DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.is_selected, 1, thumb->imgid);
1013 /* lets check if imgid is selected */
1014 if(sqlite3_step(darktable.view_manager->statements.is_selected) == SQLITE_ROW) selected = TRUE;
1015
1016 // if there's a change, update the thumb
1017 if(selected != thumb->selected)
1018 {
1019 thumb->selected = selected;
1020 _thumb_update_icons(thumb);
1021 gtk_widget_queue_draw(thumb->w_main);
1022 }
1023 }
1024
_dt_selection_changed_callback(gpointer instance,gpointer user_data)1025 static void _dt_selection_changed_callback(gpointer instance, gpointer user_data)
1026 {
1027 if(!user_data) return;
1028 dt_thumbnail_update_selection((dt_thumbnail_t *)user_data);
1029 }
1030
_dt_active_images_callback(gpointer instance,gpointer user_data)1031 static void _dt_active_images_callback(gpointer instance, gpointer user_data)
1032 {
1033 if(!user_data) return;
1034 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1035 if(!thumb) return;
1036
1037 gboolean active = FALSE;
1038 for(GSList *l = darktable.view_manager->active_images; l; l = g_slist_next(l))
1039 {
1040 int id = GPOINTER_TO_INT(l->data);
1041 if(id == thumb->imgid)
1042 {
1043 active = TRUE;
1044 break;
1045 }
1046 }
1047
1048 // if there's a change, update the thumb
1049 if(active != thumb->active)
1050 {
1051 thumb->active = active;
1052 if(gtk_widget_is_visible(thumb->w_main))
1053 {
1054 _thumb_update_icons(thumb);
1055 gtk_widget_queue_draw(thumb->w_main);
1056 }
1057 }
1058 }
1059
_dt_preview_updated_callback(gpointer instance,gpointer user_data)1060 static void _dt_preview_updated_callback(gpointer instance, gpointer user_data)
1061 {
1062 if(!user_data) return;
1063 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1064 if(!thumb) return;
1065 if(!gtk_widget_is_visible(thumb->w_main)) return;
1066
1067 const dt_view_t *v = dt_view_manager_get_current_view(darktable.view_manager);
1068 if(v->view(v) == DT_VIEW_DARKROOM && darktable.develop->preview_pipe->output_imgid == thumb->imgid
1069 && darktable.develop->preview_pipe->output_backbuf)
1070 {
1071 // reset surface
1072 thumb->img_surf_dirty = TRUE;
1073 gtk_widget_queue_draw(thumb->w_main);
1074 }
1075 }
1076
_dt_mipmaps_updated_callback(gpointer instance,int imgid,gpointer user_data)1077 static void _dt_mipmaps_updated_callback(gpointer instance, int imgid, gpointer user_data)
1078 {
1079 if(!user_data) return;
1080 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1081 if(!thumb || (imgid > 0 && thumb->imgid != imgid)) return;
1082
1083 // we recompte the history tooltip if needed
1084 thumb->is_altered = dt_image_altered(thumb->imgid);
1085 gtk_widget_set_visible(thumb->w_altered, thumb->is_altered);
1086 if(thumb->is_altered)
1087 {
1088 char *tooltip = dt_history_get_items_as_string(thumb->imgid);
1089 if(tooltip)
1090 {
1091 gtk_widget_set_tooltip_text(thumb->w_altered, tooltip);
1092 g_free(tooltip);
1093 }
1094 }
1095
1096 // reset surface
1097 thumb->img_surf_dirty = TRUE;
1098 gtk_widget_queue_draw(thumb->w_main);
1099 }
1100
_event_box_enter_leave(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)1101 static gboolean _event_box_enter_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1102 {
1103 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1104 // if we leave for ancestor, that means we leave for blank thumbtable area
1105 if(event->type == GDK_LEAVE_NOTIFY && event->detail == GDK_NOTIFY_ANCESTOR) dt_control_set_mouse_over_id(-1);
1106
1107 if(!thumb->mouse_over && event->type == GDK_ENTER_NOTIFY && !thumb->disable_mouseover)
1108 dt_control_set_mouse_over_id(thumb->imgid);
1109 _set_flag(widget, GTK_STATE_FLAG_PRELIGHT, (event->type == GDK_ENTER_NOTIFY));
1110 _set_flag(thumb->w_image_box, GTK_STATE_FLAG_PRELIGHT, (event->type == GDK_ENTER_NOTIFY));
1111 return FALSE;
1112 }
1113
_event_image_enter_leave(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)1114 static gboolean _event_image_enter_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1115 {
1116 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1117 _set_flag(thumb->w_image_box, GTK_STATE_FLAG_PRELIGHT, (event->type == GDK_ENTER_NOTIFY));
1118 return FALSE;
1119 }
1120
_event_btn_enter_leave(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)1121 static gboolean _event_btn_enter_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1122 {
1123 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1124
1125 if(event->type == GDK_ENTER_NOTIFY && widget == thumb->w_reject) darktable.control->element = DT_VIEW_REJECT;
1126
1127 // if we leave for ancestor, that means we leave for blank thumbtable area
1128 if(event->type == GDK_LEAVE_NOTIFY && event->detail == GDK_NOTIFY_ANCESTOR) dt_control_set_mouse_over_id(-1);
1129
1130 if(thumb->disable_actions) return TRUE;
1131 if(event->type == GDK_ENTER_NOTIFY) _set_flag(thumb->w_image_box, GTK_STATE_FLAG_PRELIGHT, TRUE);
1132 return FALSE;
1133 }
1134
_event_star_enter(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)1135 static gboolean _event_star_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1136 {
1137 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1138 if(thumb->disable_actions) return TRUE;
1139 if(!thumb->mouse_over && !thumb->disable_mouseover) dt_control_set_mouse_over_id(thumb->imgid);
1140 _set_flag(thumb->w_bottom_eb, GTK_STATE_FLAG_PRELIGHT, TRUE);
1141 _set_flag(thumb->w_image_box, GTK_STATE_FLAG_PRELIGHT, TRUE);
1142
1143 // we prelight all stars before the current one
1144 gboolean pre = TRUE;
1145 for(int i = 0; i < MAX_STARS; i++)
1146 {
1147 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_PRELIGHT, pre);
1148 gtk_widget_queue_draw(thumb->w_stars[i]);
1149 if(thumb->w_stars[i] == widget)
1150 {
1151 darktable.control->element = i + 1;
1152 pre = FALSE;
1153 }
1154 }
1155 return TRUE;
1156 }
_event_star_leave(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)1157 static gboolean _event_star_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1158 {
1159 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1160 // if we leave for ancestor, that means we leave for blank thumbtable area
1161 if(event->type == GDK_LEAVE_NOTIFY && event->detail == GDK_NOTIFY_ANCESTOR) dt_control_set_mouse_over_id(-1);
1162
1163 if(thumb->disable_actions) return TRUE;
1164 for(int i = 0; i < MAX_STARS; i++)
1165 {
1166 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_PRELIGHT, FALSE);
1167 gtk_widget_queue_draw(thumb->w_stars[i]);
1168 }
1169 return TRUE;
1170 }
1171
_event_main_leave(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)1172 static gboolean _event_main_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1173 {
1174 // if we leave for ancestor, that means we leave for blank thumbtable area
1175 if(event->detail == GDK_NOTIFY_ANCESTOR) dt_control_set_mouse_over_id(-1);
1176 return FALSE;
1177 }
1178
1179 // we only want to specify that the mouse is hovereing the thumbnail
_event_main_drag_motion(GtkWidget * widget,GdkDragContext * dc,gint x,gint y,guint time,gpointer user_data)1180 static gboolean _event_main_drag_motion(GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint time,
1181 gpointer user_data)
1182 {
1183 _event_main_motion(widget, NULL, user_data);
1184 return TRUE;
1185 }
1186
dt_thumbnail_create_widget(dt_thumbnail_t * thumb,float zoom_ratio)1187 GtkWidget *dt_thumbnail_create_widget(dt_thumbnail_t *thumb, float zoom_ratio)
1188 {
1189 // main widget (overlay)
1190 thumb->w_main = gtk_overlay_new();
1191 gtk_widget_set_name(thumb->w_main, "thumb_main");
1192 _thumb_update_rating_class(thumb);
1193 gtk_widget_set_size_request(thumb->w_main, thumb->width, thumb->height);
1194
1195 if(thumb->imgid > 0)
1196 {
1197 // this is only here to ensure that mouse-over value is updated correctly
1198 // all dragging actions take place inside thumbatble.c
1199 gtk_drag_dest_set(thumb->w_main, GTK_DEST_DEFAULT_MOTION, target_list_all, n_targets_all, GDK_ACTION_MOVE);
1200 g_signal_connect(G_OBJECT(thumb->w_main), "drag-motion", G_CALLBACK(_event_main_drag_motion), thumb);
1201
1202 g_signal_connect(G_OBJECT(thumb->w_main), "button-press-event", G_CALLBACK(_event_main_press), thumb);
1203 g_signal_connect(G_OBJECT(thumb->w_main), "button-release-event", G_CALLBACK(_event_main_release), thumb);
1204
1205 g_object_set_data(G_OBJECT(thumb->w_main), "thumb", thumb);
1206 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_ACTIVE_IMAGES_CHANGE,
1207 G_CALLBACK(_dt_active_images_callback), thumb);
1208 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_SELECTION_CHANGED,
1209 G_CALLBACK(_dt_selection_changed_callback), thumb);
1210 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_MIPMAP_UPDATED,
1211 G_CALLBACK(_dt_mipmaps_updated_callback), thumb);
1212 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED,
1213 G_CALLBACK(_dt_preview_updated_callback), thumb);
1214 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_IMAGE_INFO_CHANGED,
1215 G_CALLBACK(_dt_image_info_changed_callback), thumb);
1216 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_COLLECTION_CHANGED,
1217 G_CALLBACK(_dt_collection_changed_callback), thumb);
1218
1219 // the background
1220 thumb->w_back = gtk_event_box_new();
1221 gtk_widget_set_events(thumb->w_back, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK
1222 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
1223 | GDK_POINTER_MOTION_MASK);
1224 gtk_widget_set_name(thumb->w_back, "thumb_back");
1225 g_signal_connect(G_OBJECT(thumb->w_back), "motion-notify-event", G_CALLBACK(_event_main_motion), thumb);
1226 g_signal_connect(G_OBJECT(thumb->w_back), "leave-notify-event", G_CALLBACK(_event_main_leave), thumb);
1227 gtk_widget_show(thumb->w_back);
1228 gtk_container_add(GTK_CONTAINER(thumb->w_main), thumb->w_back);
1229
1230 // the file extension label
1231 thumb->w_ext = gtk_label_new("");
1232 gtk_widget_set_name(thumb->w_ext, "thumb_ext");
1233 gtk_widget_set_valign(thumb->w_ext, GTK_ALIGN_START);
1234 gtk_widget_set_halign(thumb->w_ext, GTK_ALIGN_START);
1235 gtk_label_set_justify(GTK_LABEL(thumb->w_ext), GTK_JUSTIFY_CENTER);
1236 gtk_widget_show(thumb->w_ext);
1237 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_ext);
1238 gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(thumb->w_main), thumb->w_ext, TRUE);
1239
1240 // the image drawing area
1241 thumb->w_image_box = gtk_overlay_new();
1242 gtk_widget_set_name(thumb->w_image_box, "thumb_image");
1243 gtk_widget_set_size_request(thumb->w_image_box, thumb->width, thumb->height);
1244 gtk_widget_set_valign(thumb->w_image_box, GTK_ALIGN_START);
1245 gtk_widget_set_halign(thumb->w_image_box, GTK_ALIGN_START);
1246 gtk_widget_show(thumb->w_image_box);
1247 // we add a eventbox which cover all the w_image_box otherwise event don't work in areas not covered by w_image
1248 // itself
1249 GtkWidget *evt_image = gtk_event_box_new();
1250 gtk_widget_set_valign(evt_image, GTK_ALIGN_FILL);
1251 gtk_widget_set_halign(evt_image, GTK_ALIGN_FILL);
1252 gtk_widget_set_events(evt_image, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK
1253 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
1254 | GDK_POINTER_MOTION_MASK);
1255 g_signal_connect(G_OBJECT(evt_image), "motion-notify-event", G_CALLBACK(_event_main_motion), thumb);
1256 g_signal_connect(G_OBJECT(evt_image), "enter-notify-event", G_CALLBACK(_event_image_enter_leave), thumb);
1257 g_signal_connect(G_OBJECT(evt_image), "leave-notify-event", G_CALLBACK(_event_image_enter_leave), thumb);
1258 gtk_widget_show(evt_image);
1259 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_image_box), evt_image);
1260 thumb->w_image = gtk_drawing_area_new();
1261 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_image);
1262 if(thumb->container == DT_THUMBNAIL_CONTAINER_PREVIEW)
1263 gtk_style_context_add_class(context, "dt_preview_thumb_image");
1264 else if(thumb->container == DT_THUMBNAIL_CONTAINER_CULLING)
1265 gtk_style_context_add_class(context, "dt_culling_thumb_image");
1266 gtk_widget_set_name(thumb->w_image, "thumb_image");
1267 gtk_widget_set_valign(thumb->w_image, GTK_ALIGN_CENTER);
1268 gtk_widget_set_halign(thumb->w_image, GTK_ALIGN_CENTER);
1269 // the size will be defined at the end, inside dt_thumbnail_resize
1270 gtk_widget_set_events(thumb->w_image, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK
1271 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
1272 | GDK_POINTER_MOTION_MASK);
1273 g_signal_connect(G_OBJECT(thumb->w_image), "draw", G_CALLBACK(_event_image_draw), thumb);
1274 g_signal_connect(G_OBJECT(thumb->w_image), "motion-notify-event", G_CALLBACK(_event_main_motion), thumb);
1275 g_signal_connect(G_OBJECT(thumb->w_image), "enter-notify-event", G_CALLBACK(_event_image_enter_leave), thumb);
1276 g_signal_connect(G_OBJECT(thumb->w_image), "leave-notify-event", G_CALLBACK(_event_image_enter_leave), thumb);
1277 gtk_widget_show(thumb->w_image);
1278 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_image_box), thumb->w_image);
1279 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_image_box);
1280
1281 // triangle to indicate current image(s) in filmstrip
1282 thumb->w_cursor = gtk_drawing_area_new();
1283 gtk_widget_set_name(thumb->w_cursor, "thumb_cursor");
1284 gtk_widget_set_valign(thumb->w_cursor, GTK_ALIGN_START);
1285 gtk_widget_set_halign(thumb->w_cursor, GTK_ALIGN_CENTER);
1286 g_signal_connect(G_OBJECT(thumb->w_cursor), "draw", G_CALLBACK(_event_cursor_draw), thumb);
1287 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_cursor);
1288
1289 // determine the overlays parents
1290 GtkWidget *overlays_parent = thumb->w_main;
1291 if(thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK) overlays_parent = thumb->w_image_box;
1292
1293 // the infos background
1294 thumb->w_bottom_eb = gtk_event_box_new();
1295 gtk_widget_set_name(thumb->w_bottom_eb, "thumb_bottom");
1296 g_signal_connect(G_OBJECT(thumb->w_bottom_eb), "enter-notify-event", G_CALLBACK(_event_box_enter_leave),
1297 thumb);
1298 g_signal_connect(G_OBJECT(thumb->w_bottom_eb), "leave-notify-event", G_CALLBACK(_event_box_enter_leave),
1299 thumb);
1300 gtk_widget_set_valign(thumb->w_bottom_eb, GTK_ALIGN_END);
1301 gtk_widget_set_halign(thumb->w_bottom_eb, GTK_ALIGN_CENTER);
1302 gtk_widget_show(thumb->w_bottom_eb);
1303 if(thumb->over == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED || thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_EXTENDED
1304 || thumb->over == DT_THUMBNAIL_OVERLAYS_MIXED || thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
1305 {
1306 gchar *lb = g_strdup(thumb->info_line);
1307 thumb->w_bottom = gtk_label_new(NULL);
1308 gtk_label_set_markup(GTK_LABEL(thumb->w_bottom), lb);
1309 g_free(lb);
1310 }
1311 else
1312 {
1313 thumb->w_bottom = gtk_label_new(NULL);
1314 gtk_label_set_markup(GTK_LABEL(thumb->w_bottom), "");
1315 }
1316 gtk_widget_set_name(thumb->w_bottom, "thumb_bottom_label");
1317 gtk_widget_show(thumb->w_bottom);
1318 gtk_label_set_yalign(GTK_LABEL(thumb->w_bottom), 0.05);
1319 gtk_label_set_ellipsize(GTK_LABEL(thumb->w_bottom), PANGO_ELLIPSIZE_MIDDLE);
1320 gtk_container_add(GTK_CONTAINER(thumb->w_bottom_eb), thumb->w_bottom);
1321 gtk_overlay_add_overlay(GTK_OVERLAY(overlays_parent), thumb->w_bottom_eb);
1322
1323 // the reject icon
1324 thumb->w_reject = dtgtk_thumbnail_btn_new(dtgtk_cairo_paint_reject, 0, NULL);
1325 gtk_widget_set_name(thumb->w_reject, "thumb_reject");
1326 dt_action_define(&darktable.control->actions_thumb, NULL, "rating", thumb->w_reject, &dt_action_def_rating);
1327 gtk_widget_set_valign(thumb->w_reject, GTK_ALIGN_END);
1328 gtk_widget_set_halign(thumb->w_reject, GTK_ALIGN_START);
1329 gtk_widget_show(thumb->w_reject);
1330 g_signal_connect(G_OBJECT(thumb->w_reject), "button-press-event", G_CALLBACK(_event_rating_press), thumb);
1331 g_signal_connect(G_OBJECT(thumb->w_reject), "button-release-event", G_CALLBACK(_event_rating_release), thumb);
1332 g_signal_connect(G_OBJECT(thumb->w_reject), "enter-notify-event", G_CALLBACK(_event_btn_enter_leave), thumb);
1333 g_signal_connect(G_OBJECT(thumb->w_reject), "leave-notify-event", G_CALLBACK(_event_btn_enter_leave), thumb);
1334 gtk_overlay_add_overlay(GTK_OVERLAY(overlays_parent), thumb->w_reject);
1335
1336 // the stars
1337 for(int i = 0; i < MAX_STARS; i++)
1338 {
1339 thumb->w_stars[i] = dtgtk_thumbnail_btn_new(dtgtk_cairo_paint_star, 0, NULL);
1340 g_signal_connect(G_OBJECT(thumb->w_stars[i]), "enter-notify-event", G_CALLBACK(_event_star_enter), thumb);
1341 g_signal_connect(G_OBJECT(thumb->w_stars[i]), "leave-notify-event", G_CALLBACK(_event_star_leave), thumb);
1342 g_signal_connect(G_OBJECT(thumb->w_stars[i]), "button-press-event", G_CALLBACK(_event_rating_press), thumb);
1343 g_signal_connect(G_OBJECT(thumb->w_stars[i]), "button-release-event", G_CALLBACK(_event_rating_release),
1344 thumb);
1345 gtk_widget_set_name(thumb->w_stars[i], "thumb_star");
1346 dt_action_define(&darktable.control->actions_thumb, NULL, "rating", thumb->w_stars[i], &dt_action_def_rating);
1347 gtk_widget_set_valign(thumb->w_stars[i], GTK_ALIGN_END);
1348 gtk_widget_set_halign(thumb->w_stars[i], GTK_ALIGN_START);
1349 gtk_widget_show(thumb->w_stars[i]);
1350 gtk_overlay_add_overlay(GTK_OVERLAY(overlays_parent), thumb->w_stars[i]);
1351 }
1352
1353 // the color labels
1354 thumb->w_color = dtgtk_thumbnail_btn_new(dtgtk_cairo_paint_label_flower, thumb->colorlabels, &darktable.bauhaus->colorlabels);
1355 gtk_widget_set_name(thumb->w_color, "thumb_colorlabels");
1356 gtk_widget_set_valign(thumb->w_color, GTK_ALIGN_END);
1357 gtk_widget_set_halign(thumb->w_color, GTK_ALIGN_END);
1358 gtk_widget_set_no_show_all(thumb->w_color, TRUE);
1359 g_signal_connect(G_OBJECT(thumb->w_color), "enter-notify-event", G_CALLBACK(_event_btn_enter_leave), thumb);
1360 g_signal_connect(G_OBJECT(thumb->w_color), "leave-notify-event", G_CALLBACK(_event_btn_enter_leave), thumb);
1361 gtk_overlay_add_overlay(GTK_OVERLAY(overlays_parent), thumb->w_color);
1362
1363 // the local copy indicator
1364 thumb->w_local_copy = dtgtk_thumbnail_btn_new(dtgtk_cairo_paint_local_copy, CPF_DO_NOT_USE_BORDER, NULL);
1365 gtk_widget_set_name(thumb->w_local_copy, "thumb_localcopy");
1366 gtk_widget_set_valign(thumb->w_local_copy, GTK_ALIGN_START);
1367 gtk_widget_set_halign(thumb->w_local_copy, GTK_ALIGN_END);
1368 gtk_widget_set_no_show_all(thumb->w_local_copy, TRUE);
1369 g_signal_connect(G_OBJECT(thumb->w_local_copy), "enter-notify-event", G_CALLBACK(_event_btn_enter_leave),
1370 thumb);
1371 g_signal_connect(G_OBJECT(thumb->w_local_copy), "leave-notify-event", G_CALLBACK(_event_btn_enter_leave),
1372 thumb);
1373 gtk_overlay_add_overlay(GTK_OVERLAY(overlays_parent), thumb->w_local_copy);
1374
1375 // the altered icon
1376 thumb->w_altered = dtgtk_thumbnail_btn_new(dtgtk_cairo_paint_altered, CPF_DO_NOT_USE_BORDER, NULL);
1377 gtk_widget_set_name(thumb->w_altered, "thumb_altered");
1378 gtk_widget_set_valign(thumb->w_altered, GTK_ALIGN_START);
1379 gtk_widget_set_halign(thumb->w_altered, GTK_ALIGN_END);
1380 gtk_widget_set_no_show_all(thumb->w_altered, TRUE);
1381 g_signal_connect(G_OBJECT(thumb->w_altered), "enter-notify-event", G_CALLBACK(_event_btn_enter_leave), thumb);
1382 g_signal_connect(G_OBJECT(thumb->w_altered), "leave-notify-event", G_CALLBACK(_event_btn_enter_leave), thumb);
1383 gtk_overlay_add_overlay(GTK_OVERLAY(overlays_parent), thumb->w_altered);
1384
1385 // the group bouton
1386 thumb->w_group = dtgtk_thumbnail_btn_new(dtgtk_cairo_paint_grouping, CPF_DO_NOT_USE_BORDER, NULL);
1387 gtk_widget_set_name(thumb->w_group, "thumb_group");
1388 g_signal_connect(G_OBJECT(thumb->w_group), "button-release-event", G_CALLBACK(_event_grouping_release), thumb);
1389 g_signal_connect(G_OBJECT(thumb->w_group), "enter-notify-event", G_CALLBACK(_event_btn_enter_leave), thumb);
1390 g_signal_connect(G_OBJECT(thumb->w_group), "leave-notify-event", G_CALLBACK(_event_btn_enter_leave), thumb);
1391 gtk_widget_set_valign(thumb->w_group, GTK_ALIGN_START);
1392 gtk_widget_set_halign(thumb->w_group, GTK_ALIGN_END);
1393 gtk_widget_set_no_show_all(thumb->w_group, TRUE);
1394 gtk_overlay_add_overlay(GTK_OVERLAY(overlays_parent), thumb->w_group);
1395
1396 // the sound icon
1397 thumb->w_audio = dtgtk_thumbnail_btn_new(dtgtk_cairo_paint_audio, CPF_DO_NOT_USE_BORDER, NULL);
1398 gtk_widget_set_name(thumb->w_audio, "thumb_audio");
1399 g_signal_connect(G_OBJECT(thumb->w_audio), "button-release-event", G_CALLBACK(_event_audio_release), thumb);
1400 g_signal_connect(G_OBJECT(thumb->w_audio), "enter-notify-event", G_CALLBACK(_event_btn_enter_leave), thumb);
1401 g_signal_connect(G_OBJECT(thumb->w_audio), "leave-notify-event", G_CALLBACK(_event_btn_enter_leave), thumb);
1402 gtk_widget_set_valign(thumb->w_audio, GTK_ALIGN_START);
1403 gtk_widget_set_halign(thumb->w_audio, GTK_ALIGN_END);
1404 gtk_widget_set_no_show_all(thumb->w_audio, TRUE);
1405 gtk_overlay_add_overlay(GTK_OVERLAY(overlays_parent), thumb->w_audio);
1406
1407 // the zoom indicator
1408 thumb->w_zoom_eb = gtk_event_box_new();
1409 g_signal_connect(G_OBJECT(thumb->w_zoom_eb), "enter-notify-event", G_CALLBACK(_event_btn_enter_leave), thumb);
1410 gtk_widget_set_name(thumb->w_zoom_eb, "thumb_zoom");
1411 gtk_widget_set_valign(thumb->w_zoom_eb, GTK_ALIGN_START);
1412 gtk_widget_set_halign(thumb->w_zoom_eb, GTK_ALIGN_START);
1413 if(zoom_ratio == IMG_TO_FIT)
1414 thumb->w_zoom = gtk_label_new(_("fit"));
1415 else
1416 thumb->w_zoom = gtk_label_new("mini");
1417 gtk_widget_set_name(thumb->w_zoom, "thumb_zoom_label");
1418 gtk_widget_show(thumb->w_zoom);
1419 gtk_container_add(GTK_CONTAINER(thumb->w_zoom_eb), thumb->w_zoom);
1420 gtk_overlay_add_overlay(GTK_OVERLAY(overlays_parent), thumb->w_zoom_eb);
1421
1422 dt_thumbnail_resize(thumb, thumb->width, thumb->height, TRUE, zoom_ratio);
1423 }
1424 gtk_widget_show(thumb->w_main);
1425 g_object_ref(G_OBJECT(thumb->w_main));
1426 return thumb->w_main;
1427 }
1428
dt_thumbnail_new(int width,int height,float zoom_ratio,int imgid,int rowid,dt_thumbnail_overlay_t over,dt_thumbnail_container_t container,gboolean tooltip)1429 dt_thumbnail_t *dt_thumbnail_new(int width, int height, float zoom_ratio, int imgid, int rowid,
1430 dt_thumbnail_overlay_t over, dt_thumbnail_container_t container, gboolean tooltip)
1431 {
1432 dt_thumbnail_t *thumb = calloc(1, sizeof(dt_thumbnail_t));
1433 thumb->width = width;
1434 thumb->height = height;
1435 thumb->imgid = imgid;
1436 thumb->rowid = rowid;
1437 thumb->over = over;
1438 thumb->container = container;
1439 thumb->zoomable = (container == DT_THUMBNAIL_CONTAINER_CULLING || container == DT_THUMBNAIL_CONTAINER_PREVIEW);
1440 thumb->zoom = 1.0f;
1441 thumb->overlay_timeout_duration = dt_conf_get_int("plugins/lighttable/overlay_timeout");
1442 thumb->tooltip = tooltip;
1443 thumb->expose_again_timeout_id = 0;
1444
1445 // we read and cache all the infos from dt_image_t that we need
1446 const dt_image_t *img = dt_image_cache_get(darktable.image_cache, thumb->imgid, 'r');
1447 if(img)
1448 {
1449 thumb->filename = g_strdup(img->filename);
1450 if(thumb->over != DT_THUMBNAIL_OVERLAYS_NONE)
1451 {
1452 thumb->has_audio = (img->flags & DT_IMAGE_HAS_WAV);
1453 thumb->has_localcopy = (img->flags & DT_IMAGE_LOCAL_COPY);
1454 }
1455 dt_image_cache_read_release(darktable.image_cache, img);
1456 }
1457 if(thumb->over == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED || thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_EXTENDED
1458 || over == DT_THUMBNAIL_OVERLAYS_MIXED || thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
1459 _thumb_update_extended_infos_line(thumb);
1460
1461 // we read all other infos
1462 _image_get_infos(thumb);
1463
1464 // we create the widget
1465 dt_thumbnail_create_widget(thumb, zoom_ratio);
1466
1467 // let's see if the images are selected or active or mouse_overed
1468 _dt_active_images_callback(NULL, thumb);
1469 _dt_selection_changed_callback(NULL, thumb);
1470 if(dt_control_get_mouse_over_id() == thumb->imgid) dt_thumbnail_set_mouseover(thumb, TRUE);
1471
1472 // set tooltip for altered icon if needed
1473 if(thumb->is_altered)
1474 {
1475 char *tooltip_txt = dt_history_get_items_as_string(thumb->imgid);
1476 if(tooltip_txt)
1477 {
1478 gtk_widget_set_tooltip_text(thumb->w_altered, tooltip_txt);
1479 g_free(tooltip_txt);
1480 }
1481 }
1482
1483 // grouping tooltip
1484 _image_update_group_tooltip(thumb);
1485
1486 // get the file extension
1487 _thumb_write_extension(thumb);
1488
1489 // ensure all icons are up to date
1490 _thumb_update_icons(thumb);
1491
1492 return thumb;
1493 }
1494
dt_thumbnail_destroy(dt_thumbnail_t * thumb)1495 void dt_thumbnail_destroy(dt_thumbnail_t *thumb)
1496 {
1497 if(thumb->overlay_timeout_id > 0) g_source_remove(thumb->overlay_timeout_id);
1498 if(thumb->expose_again_timeout_id != 0) g_source_remove(thumb->expose_again_timeout_id);
1499 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_dt_selection_changed_callback), thumb);
1500 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_dt_active_images_callback), thumb);
1501 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_dt_mipmaps_updated_callback), thumb);
1502 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_dt_preview_updated_callback), thumb);
1503 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_dt_image_info_changed_callback), thumb);
1504 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_dt_collection_changed_callback), thumb);
1505 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0)
1506 cairo_surface_destroy(thumb->img_surf);
1507 thumb->img_surf = NULL;
1508 if(thumb->w_main) gtk_widget_destroy(thumb->w_main);
1509 if(thumb->filename) g_free(thumb->filename);
1510 if(thumb->info_line) g_free(thumb->info_line);
1511 if(thumb->img_margin) gtk_border_free(thumb->img_margin);
1512 free(thumb);
1513 }
1514
dt_thumbnail_update_infos(dt_thumbnail_t * thumb)1515 void dt_thumbnail_update_infos(dt_thumbnail_t *thumb)
1516 {
1517 if(!thumb) return;
1518 _image_get_infos(thumb);
1519 _thumb_write_extension(thumb);
1520 _thumb_update_icons(thumb);
1521 gtk_widget_queue_draw(thumb->w_main);
1522 }
1523
_thumb_resize_overlays(dt_thumbnail_t * thumb)1524 static void _thumb_resize_overlays(dt_thumbnail_t *thumb)
1525 {
1526 PangoAttrList *attrlist;
1527 PangoAttribute *attr;
1528 int width = 0;
1529 int height = 0;
1530
1531 int max_size = darktable.gui->icon_size;
1532 if(max_size < 2) max_size = round(1.2f * darktable.bauhaus->line_height); // fallback if toolbar icons are not realized
1533
1534 if(thumb->over != DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
1535 {
1536 gtk_widget_get_size_request(thumb->w_main, &width, &height);
1537 // we need to squeeze 5 stars + 1 reject + 1 colorlabels symbols on a thumbnail width
1538 // stars + reject having a width of 2 * r1 and spaced by r1 => 18 * r1
1539 // colorlabels => 3 * r1 + space r1
1540 // inner margins are defined in css (margin_* values)
1541
1542 // retrieves the size of the main icons in the top panel, thumbtable overlays shall not exceed that
1543 const float r1 = fminf(max_size / 2.0f, (width - thumb->img_margin->left - thumb->img_margin->right) / 22.0f);
1544 const float icon_size = 2.5 * r1;
1545
1546 // file extension
1547 gtk_widget_set_margin_top(thumb->w_ext, thumb->img_margin->top);
1548 gtk_widget_set_margin_start(thumb->w_ext, thumb->img_margin->left);
1549
1550 // bottom background
1551 gtk_widget_set_margin_start(thumb->w_bottom, thumb->img_margin->left);
1552 gtk_widget_set_margin_end(thumb->w_bottom, thumb->img_margin->right);
1553 if(thumb->over == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED || thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_EXTENDED
1554 || thumb->over == DT_THUMBNAIL_OVERLAYS_MIXED)
1555 {
1556 attrlist = pango_attr_list_new();
1557 attr = pango_attr_size_new_absolute(1.5 * r1 * PANGO_SCALE);
1558 pango_attr_list_insert(attrlist, attr);
1559 gtk_label_set_attributes(GTK_LABEL(thumb->w_bottom), attrlist);
1560 pango_attr_list_unref(attrlist);
1561 int w = 0;
1562 int h = 0;
1563 pango_layout_get_pixel_size(gtk_label_get_layout(GTK_LABEL(thumb->w_bottom)), &w, &h);
1564 gtk_widget_set_size_request(thumb->w_bottom_eb, width, icon_size * 0.75 + h + 3 * thumb->img_margin->bottom);
1565 }
1566 else
1567 gtk_widget_set_size_request(thumb->w_bottom_eb, width, icon_size * 0.75 + 2 * thumb->img_margin->bottom);
1568
1569 gtk_label_set_xalign(GTK_LABEL(thumb->w_bottom), 0.5);
1570 gtk_label_set_yalign(GTK_LABEL(thumb->w_bottom), 0);
1571 gtk_widget_set_margin_top(thumb->w_bottom, thumb->img_margin->bottom);
1572 gtk_widget_set_valign(thumb->w_bottom_eb, GTK_ALIGN_END);
1573 gtk_widget_set_halign(thumb->w_bottom_eb, GTK_ALIGN_CENTER);
1574
1575 // reject icon
1576 const int margin_b_icons = MAX(0, thumb->img_margin->bottom - icon_size * 0.125 - 1);
1577 gtk_widget_set_size_request(thumb->w_reject, icon_size, icon_size);
1578 gtk_widget_set_valign(thumb->w_reject, GTK_ALIGN_END);
1579 int pos = MAX(0, MAX(thumb->img_margin->left - icon_size * 0.125, (width - 15.0 * r1) * 0.5 - 4 * 3.0 * r1));
1580 gtk_widget_set_margin_start(thumb->w_reject, pos);
1581 gtk_widget_set_margin_bottom(thumb->w_reject, margin_b_icons);
1582
1583 // stars
1584 for(int i = 0; i < MAX_STARS; i++)
1585 {
1586 gtk_widget_set_size_request(thumb->w_stars[i], icon_size, icon_size);
1587 gtk_widget_set_valign(thumb->w_stars[i], GTK_ALIGN_END);
1588 gtk_widget_set_margin_bottom(thumb->w_stars[i], margin_b_icons);
1589 gtk_widget_set_margin_start(
1590 thumb->w_stars[i], thumb->img_margin->left
1591 + (width - thumb->img_margin->left - thumb->img_margin->right - 13.0 * r1) * 0.5
1592 + i * 2.5 * r1);
1593 }
1594
1595 // the color labels
1596 gtk_widget_set_size_request(thumb->w_color, icon_size, icon_size);
1597 gtk_widget_set_valign(thumb->w_color, GTK_ALIGN_END);
1598 gtk_widget_set_halign(thumb->w_color, GTK_ALIGN_START);
1599 gtk_widget_set_margin_bottom(thumb->w_color, margin_b_icons);
1600 pos = MIN(width - (thumb->img_margin->right - icon_size * 0.125 + icon_size),
1601 (width - 15.0 * r1) * 0.5 + 8.25 * 3.0 * r1);
1602 gtk_widget_set_margin_start(thumb->w_color, pos);
1603
1604 // the local copy indicator
1605 gtk_widget_set_size_request(thumb->w_local_copy, 1.618 * r1, 1.618 * r1);
1606 gtk_widget_set_halign(thumb->w_local_copy, GTK_ALIGN_END);
1607
1608 // the altered icon
1609 gtk_widget_set_size_request(thumb->w_altered, 2.0 * r1, 2.0 * r1);
1610 gtk_widget_set_halign(thumb->w_altered, GTK_ALIGN_END);
1611 gtk_widget_set_margin_top(thumb->w_altered, thumb->img_margin->top);
1612 gtk_widget_set_margin_end(thumb->w_altered, thumb->img_margin->right);
1613
1614 // the group bouton
1615 gtk_widget_set_size_request(thumb->w_group, 2.0 * r1, 2.0 * r1);
1616 gtk_widget_set_halign(thumb->w_group, GTK_ALIGN_END);
1617 gtk_widget_set_margin_top(thumb->w_group, thumb->img_margin->top);
1618 gtk_widget_set_margin_end(thumb->w_group, thumb->img_margin->right + 2.5 * r1);
1619
1620 // the sound icon
1621 gtk_widget_set_size_request(thumb->w_audio, 2.0 * r1, 2.0 * r1);
1622 gtk_widget_set_halign(thumb->w_audio, GTK_ALIGN_END);
1623 gtk_widget_set_margin_top(thumb->w_audio, thumb->img_margin->top);
1624 gtk_widget_set_margin_end(thumb->w_audio, thumb->img_margin->right + 5.0 * r1);
1625
1626 // the filmstrip cursor
1627 gtk_widget_set_size_request(thumb->w_cursor, 6.0 * r1, 1.5 * r1);
1628 }
1629 else
1630 {
1631 gtk_widget_get_size_request(thumb->w_image, &width, &height);
1632 int w = 0;
1633 int h = 0;
1634 gtk_widget_get_size_request(thumb->w_image_box, &w, &h);
1635 const int px = (w - width) / 2;
1636 const int py = (h - height) / 2;
1637
1638 // we need to squeeze 5 stars + 1 reject + 1 colorlabels symbols on a thumbnail width
1639 // all icons having a width of 3.0 * r1 => 21 * r1
1640 // we want r1 spaces at extremities, after reject, before colorlables => 4 * r1
1641 const float r1 = fminf(max_size / 2.0f, width / 25.0f);
1642
1643 // file extension
1644 gtk_widget_set_margin_top(thumb->w_ext, 0.03 * width + py);
1645 gtk_widget_set_margin_start(thumb->w_ext, 0.03 * width + px);
1646
1647 // bottom background
1648 attrlist = pango_attr_list_new();
1649 attr = pango_attr_size_new_absolute(1.5 * r1 * PANGO_SCALE);
1650 pango_attr_list_insert(attrlist, attr);
1651 gtk_label_set_attributes(GTK_LABEL(thumb->w_bottom), attrlist);
1652 gtk_label_set_attributes(GTK_LABEL(thumb->w_zoom), attrlist);
1653 pango_attr_list_unref(attrlist);
1654 w = 0;
1655 h = 0;
1656 pango_layout_get_pixel_size(gtk_label_get_layout(GTK_LABEL(thumb->w_bottom)), &w, &h);
1657 // for the position, we use css margin and use it as per thousand (and not pixels)
1658 GtkBorder *margins = gtk_border_new();
1659 GtkBorder *borders = gtk_border_new();
1660 GtkStateFlags state = gtk_widget_get_state_flags(thumb->w_bottom_eb);
1661 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_bottom_eb);
1662 GtkStateFlags statei = gtk_widget_get_state_flags(thumb->w_image);
1663 GtkStyleContext *contexti = gtk_widget_get_style_context(thumb->w_image);
1664 gtk_style_context_get_margin(context, state, margins);
1665 gtk_style_context_get_border(contexti, statei, borders);
1666 const int padding = r1;
1667 const int padding_t = 0.8 * r1; // reduced to compensate label top margin applied by gtk
1668 const int margin_t = height * margins->top / 1000;
1669 const int margin_l = width * margins->left / 1000;
1670 const int border_t = borders->top;
1671 const int border_l = borders->left;
1672 const float icon_size = 3.0 * r1;
1673 const float icon_size2 = 2.0 * r1;
1674 const int line2 = padding_t + h + padding - icon_size / 8.0 + margin_t + border_t;
1675 const int line3 = line2 + icon_size - icon_size / 8.0 + padding - icon_size / 8.0;
1676 gtk_border_free(margins);
1677 gtk_border_free(borders);
1678
1679 const int min_width = 2.0 * padding - icon_size / 4.0 + 2 * r1 + 7 * icon_size;
1680 gtk_widget_set_size_request(thumb->w_bottom_eb, CLAMP(w + padding_t * 2.0, min_width, width),
1681 line3 - margin_t - border_t + icon_size2 + padding);
1682
1683 gtk_label_set_xalign(GTK_LABEL(thumb->w_bottom), 0);
1684 gtk_label_set_yalign(GTK_LABEL(thumb->w_bottom), 0);
1685 gtk_widget_set_valign(thumb->w_bottom_eb, GTK_ALIGN_START);
1686 gtk_widget_set_halign(thumb->w_bottom_eb, GTK_ALIGN_START);
1687
1688 gtk_widget_set_margin_top(thumb->w_bottom_eb, margin_t + border_t + py);
1689 gtk_widget_set_margin_start(thumb->w_bottom_eb, margin_l + border_l + px);
1690 gtk_widget_set_margin_top(thumb->w_bottom, padding_t);
1691 gtk_widget_set_margin_start(thumb->w_bottom, padding_t);
1692 gtk_widget_set_margin_end(thumb->w_bottom, padding_t);
1693
1694 // reject icon
1695 gtk_widget_set_size_request(thumb->w_reject, icon_size, icon_size);
1696 gtk_widget_set_valign(thumb->w_reject, GTK_ALIGN_START);
1697 gtk_widget_set_margin_start(thumb->w_reject, padding - icon_size / 8.0 + border_l + px);
1698 gtk_widget_set_margin_top(thumb->w_reject, line2 + py);
1699 // stars
1700 for(int i = 0; i < MAX_STARS; i++)
1701 {
1702 gtk_widget_set_size_request(thumb->w_stars[i], icon_size, icon_size);
1703 gtk_widget_set_valign(thumb->w_stars[i], GTK_ALIGN_START);
1704 gtk_widget_set_margin_top(thumb->w_stars[i], line2 + py);
1705 gtk_widget_set_margin_start(thumb->w_stars[i],
1706 padding - icon_size / 8.0 + border_l + r1 + (i + 1) * 3.0 * r1 + px);
1707 }
1708 // the color labels
1709 gtk_widget_set_size_request(thumb->w_color, icon_size, icon_size);
1710 gtk_widget_set_valign(thumb->w_color, GTK_ALIGN_START);
1711 gtk_widget_set_halign(thumb->w_color, GTK_ALIGN_START);
1712 gtk_widget_set_margin_top(thumb->w_color, line2 + py);
1713 gtk_widget_set_margin_start(thumb->w_color,
1714 padding - icon_size / 8.0 + border_l + 2.0 * r1 + (MAX_STARS + 1) * 3.0 * r1 + px);
1715 // the local copy indicator
1716 gtk_widget_set_size_request(thumb->w_local_copy, icon_size2, icon_size2);
1717 gtk_widget_set_halign(thumb->w_local_copy, GTK_ALIGN_START);
1718 gtk_widget_set_margin_top(thumb->w_altered, line3 + py);
1719 gtk_widget_set_margin_start(thumb->w_altered, 10.0 * r1 + px);
1720 // the altered icon
1721 gtk_widget_set_size_request(thumb->w_altered, icon_size2, icon_size2);
1722 gtk_widget_set_halign(thumb->w_altered, GTK_ALIGN_START);
1723 gtk_widget_set_margin_top(thumb->w_altered, line3 + py);
1724 gtk_widget_set_margin_start(thumb->w_altered, 7.0 * r1 + px);
1725 // the group bouton
1726 gtk_widget_set_size_request(thumb->w_group, icon_size2, icon_size2);
1727 gtk_widget_set_halign(thumb->w_group, GTK_ALIGN_START);
1728 gtk_widget_set_margin_top(thumb->w_group, line3 + py);
1729 gtk_widget_set_margin_start(thumb->w_group, 4.0 * r1 + px);
1730 // the sound icon
1731 gtk_widget_set_size_request(thumb->w_audio, icon_size2, icon_size2);
1732 gtk_widget_set_halign(thumb->w_audio, GTK_ALIGN_START);
1733 gtk_widget_set_margin_top(thumb->w_audio, line3 + py);
1734 gtk_widget_set_margin_start(thumb->w_audio, r1 + px);
1735 // the zoomming indicator
1736 gtk_widget_set_margin_top(thumb->w_zoom_eb, line3 + py);
1737 gtk_widget_set_margin_start(thumb->w_zoom_eb, 18.0 * r1 + px);
1738 }
1739 }
1740
dt_thumbnail_resize(dt_thumbnail_t * thumb,int width,int height,gboolean force,float zoom_ratio)1741 void dt_thumbnail_resize(dt_thumbnail_t *thumb, int width, int height, gboolean force, float zoom_ratio)
1742 {
1743 int w = 0;
1744 int h = 0;
1745 gtk_widget_get_size_request(thumb->w_main, &w, &h);
1746
1747 // first, we verify that there's something to change
1748 if(!force && w == width && h == height) return;
1749
1750 // widget resizing
1751 thumb->width = width;
1752 thumb->height = height;
1753 gtk_widget_set_size_request(thumb->w_main, width, height);
1754
1755 // for thumbtable, we need to set the size class to the image widget
1756 if(thumb->container == DT_THUMBNAIL_CONTAINER_LIGHTTABLE)
1757 {
1758 // we get the corresponding size
1759 const char *txt = dt_conf_get_string_const("plugins/lighttable/thumbnail_sizes");
1760 gchar **ts = g_strsplit(txt, "|", -1);
1761 int i = 0;
1762 while(ts[i])
1763 {
1764 const int s = g_ascii_strtoll(ts[i], NULL, 10);
1765 if(thumb->width < s) break;
1766 i++;
1767 }
1768 g_strfreev(ts);
1769
1770 gchar *cl = g_strdup_printf("dt_thumbnails_%d", i);
1771 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_image);
1772 if(!gtk_style_context_has_class(context, cl))
1773 {
1774 // we remove all previous size class if any
1775 GList *l = gtk_style_context_list_classes(context);
1776 for(GList *l_iter = l; l_iter; l_iter = g_list_next(l_iter))
1777 {
1778 gchar *ll = (gchar *)l_iter->data;
1779 if(g_str_has_prefix(ll, "dt_thumbnails_"))
1780 {
1781 gtk_style_context_remove_class(context, ll);
1782 }
1783 }
1784 g_list_free(l);
1785
1786 // we set the new class
1787 gtk_style_context_add_class(context, cl);
1788 }
1789 g_free(cl);
1790 }
1791
1792 // file extension
1793 _thumb_retrieve_margins(thumb);
1794 gtk_widget_set_margin_start(thumb->w_ext, thumb->img_margin->left);
1795 gtk_widget_set_margin_top(thumb->w_ext, thumb->img_margin->top);
1796
1797 // retrieves the size of the main icons in the top panel, thumbtable overlays shall not exceed that
1798 int max_size = darktable.gui->icon_size;
1799 if(max_size < 2) max_size = round(1.2f * darktable.bauhaus->line_height); // fallback if toolbar icons are not realized
1800
1801 const int fsize = fminf(max_size, (height - thumb->img_margin->top - thumb->img_margin->bottom) / 11.0f);
1802
1803 PangoAttrList *attrlist = pango_attr_list_new();
1804 PangoAttribute *attr = pango_attr_size_new_absolute(fsize * PANGO_SCALE);
1805 pango_attr_list_insert(attrlist, attr);
1806 // the idea is to reduce line-height, but it doesn't work for whatever reason...
1807 // PangoAttribute *attr2 = pango_attr_rise_new(-fsize * PANGO_SCALE);
1808 // pango_attr_list_insert(attrlist, attr2);
1809 gtk_label_set_attributes(GTK_LABEL(thumb->w_ext), attrlist);
1810 pango_attr_list_unref(attrlist);
1811
1812 // for overlays different than block, we compute their size here, so we have valid value for th image area compute
1813 if(thumb->over != DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK) _thumb_resize_overlays(thumb);
1814 // we change the size and margins according to the size change. This will be refined after
1815 _thumb_set_image_area(thumb, zoom_ratio);
1816
1817 // and the overlays for the block only (the others have been done before)
1818 if(thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK) _thumb_resize_overlays(thumb);
1819
1820 // reset surface
1821 dt_thumbnail_image_refresh(thumb);
1822 }
1823
dt_thumbnail_set_group_border(dt_thumbnail_t * thumb,dt_thumbnail_border_t border)1824 void dt_thumbnail_set_group_border(dt_thumbnail_t *thumb, dt_thumbnail_border_t border)
1825 {
1826 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_main);
1827 if(border == DT_THUMBNAIL_BORDER_NONE)
1828 {
1829 gtk_style_context_remove_class(context, "dt_group_left");
1830 gtk_style_context_remove_class(context, "dt_group_top");
1831 gtk_style_context_remove_class(context, "dt_group_right");
1832 gtk_style_context_remove_class(context, "dt_group_bottom");
1833 thumb->group_borders = DT_THUMBNAIL_BORDER_NONE;
1834 return;
1835 }
1836 else if(border & DT_THUMBNAIL_BORDER_LEFT)
1837 gtk_style_context_add_class(context, "dt_group_left");
1838 else if(border & DT_THUMBNAIL_BORDER_TOP)
1839 gtk_style_context_add_class(context, "dt_group_top");
1840 else if(border & DT_THUMBNAIL_BORDER_RIGHT)
1841 gtk_style_context_add_class(context, "dt_group_right");
1842 else if(border & DT_THUMBNAIL_BORDER_BOTTOM)
1843 gtk_style_context_add_class(context, "dt_group_bottom");
1844
1845 thumb->group_borders |= border;
1846 }
1847
dt_thumbnail_set_mouseover(dt_thumbnail_t * thumb,gboolean over)1848 void dt_thumbnail_set_mouseover(dt_thumbnail_t *thumb, gboolean over)
1849 {
1850 if(thumb->mouse_over == over) return;
1851 thumb->mouse_over = over;
1852 _thumb_update_icons(thumb);
1853
1854 if(!thumb->mouse_over)
1855 {
1856 _set_flag(thumb->w_bottom_eb, GTK_STATE_FLAG_PRELIGHT, FALSE);
1857 }
1858 gtk_widget_queue_draw(thumb->w_main);
1859 }
1860
1861 // set if the thumbnail should react (mouse_over) to drag and drop
1862 // note that it's just cosmetic as dropping occurs in thumbtable in any case
dt_thumbnail_set_drop(dt_thumbnail_t * thumb,gboolean accept_drop)1863 void dt_thumbnail_set_drop(dt_thumbnail_t *thumb, gboolean accept_drop)
1864 {
1865 if(accept_drop)
1866 {
1867 gtk_drag_dest_set(thumb->w_main, GTK_DEST_DEFAULT_MOTION, target_list_all, n_targets_all, GDK_ACTION_MOVE);
1868 }
1869 else
1870 {
1871 gtk_drag_dest_unset(thumb->w_main);
1872 }
1873 }
1874
1875 // force the image to be reloaded from cache if any
dt_thumbnail_image_refresh(dt_thumbnail_t * thumb)1876 void dt_thumbnail_image_refresh(dt_thumbnail_t *thumb)
1877 {
1878 thumb->img_surf_dirty = TRUE;
1879
1880 // we ensure that the image is not completely outside the thumbnail, otherwise the image_draw is not triggered
1881 if(gtk_widget_get_margin_start(thumb->w_image_box) >= thumb->width
1882 || gtk_widget_get_margin_top(thumb->w_image_box) >= thumb->height)
1883 {
1884 gtk_widget_set_margin_start(thumb->w_image_box, 0);
1885 gtk_widget_set_margin_top(thumb->w_image_box, 0);
1886 }
1887 gtk_widget_queue_draw(thumb->w_main);
1888 }
1889
_widget_change_parent_overlay(GtkWidget * w,GtkOverlay * new_parent)1890 static void _widget_change_parent_overlay(GtkWidget *w, GtkOverlay *new_parent)
1891 {
1892 g_object_ref(w);
1893 gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(w)), w);
1894 gtk_overlay_add_overlay(new_parent, w);
1895 gtk_widget_show(w);
1896 g_object_unref(w);
1897 }
dt_thumbnail_set_overlay(dt_thumbnail_t * thumb,dt_thumbnail_overlay_t over,int timeout)1898 void dt_thumbnail_set_overlay(dt_thumbnail_t *thumb, dt_thumbnail_overlay_t over, int timeout)
1899 {
1900 thumb->overlay_timeout_duration = timeout;
1901 // if no change, do nothing...
1902 if(thumb->over == over) return;
1903 dt_thumbnail_overlay_t old_over = thumb->over;
1904 thumb->over = over;
1905
1906 // first, if we change from/to hover/block, we need to change some parent widgets
1907 if(old_over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK || over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
1908 {
1909 GtkOverlay *overlays_parent = GTK_OVERLAY(thumb->w_main);
1910 if(thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK) overlays_parent = GTK_OVERLAY(thumb->w_image_box);
1911
1912 _widget_change_parent_overlay(thumb->w_bottom_eb, overlays_parent);
1913 _widget_change_parent_overlay(thumb->w_reject, overlays_parent);
1914 for(int i = 0; i < MAX_STARS; i++)
1915 {
1916 _widget_change_parent_overlay(thumb->w_stars[i], overlays_parent);
1917 }
1918 _widget_change_parent_overlay(thumb->w_color, overlays_parent);
1919 _widget_change_parent_overlay(thumb->w_local_copy, overlays_parent);
1920 _widget_change_parent_overlay(thumb->w_altered, overlays_parent);
1921 _widget_change_parent_overlay(thumb->w_group, overlays_parent);
1922 _widget_change_parent_overlay(thumb->w_audio, overlays_parent);
1923 _widget_change_parent_overlay(thumb->w_zoom_eb, overlays_parent);
1924 }
1925
1926 // we read and cache all the infos from dt_image_t that we need, depending on the overlay level
1927 // note that when "downgrading" overlay level, we don't bother to remove the infos
1928 dt_thumbnail_reload_infos(thumb);
1929
1930 // and we resize the overlays
1931 _thumb_resize_overlays(thumb);
1932 }
1933
1934 // force the image to be redraw at the right position
dt_thumbnail_image_refresh_position(dt_thumbnail_t * thumb)1935 void dt_thumbnail_image_refresh_position(dt_thumbnail_t *thumb)
1936 {
1937 // let's sanitize and apply panning values
1938 // here we have to make sure to properly align according to ppd
1939 int iw = 0;
1940 int ih = 0;
1941 gtk_widget_get_size_request(thumb->w_image, &iw, &ih);
1942 thumb->zoomx = CLAMP(thumb->zoomx, (iw * darktable.gui->ppd_thb - thumb->img_width) / darktable.gui->ppd_thb, 0);
1943 thumb->zoomy = CLAMP(thumb->zoomy, (ih * darktable.gui->ppd_thb - thumb->img_height) / darktable.gui->ppd_thb, 0);
1944 gtk_widget_queue_draw(thumb->w_main);
1945 }
1946
1947 // get the max zoom value of the thumb
dt_thumbnail_get_zoom100(dt_thumbnail_t * thumb)1948 float dt_thumbnail_get_zoom100(dt_thumbnail_t *thumb)
1949 {
1950 if(thumb->zoom_100 < 1.0f) // we only compute the sizes if needed
1951 {
1952 int w = 0;
1953 int h = 0;
1954 dt_image_get_final_size(thumb->imgid, &w, &h);
1955 if(!thumb->img_margin) _thumb_retrieve_margins(thumb);
1956
1957 const float used_h = (float)(thumb->height - thumb->img_margin->top - thumb->img_margin->bottom);
1958 const float used_w = (float)(thumb->width - thumb->img_margin->left - thumb->img_margin->right);
1959 thumb->zoom_100 = fmaxf((float)w / used_w, (float)h / used_h);
1960 if(thumb->zoom_100 < 1.0f) thumb->zoom_100 = 1.0f;
1961 }
1962
1963 return thumb->zoom_100;
1964 }
1965
dt_thumbnail_get_zoom_ratio(dt_thumbnail_t * thumb)1966 float dt_thumbnail_get_zoom_ratio(dt_thumbnail_t *thumb)
1967 {
1968 if(thumb->zoom_100 < 1.0f) // we only compute the sizes if needed
1969 dt_thumbnail_get_zoom100(thumb);
1970
1971 return _thumb_zoom_to_zoom_ratio(thumb->zoom, thumb->zoom_100);
1972 }
1973
1974 // force the reload of image infos
dt_thumbnail_reload_infos(dt_thumbnail_t * thumb)1975 void dt_thumbnail_reload_infos(dt_thumbnail_t *thumb)
1976 {
1977 const dt_image_t *img = dt_image_cache_get(darktable.image_cache, thumb->imgid, 'r');
1978 if(img)
1979 {
1980 if(thumb->over != DT_THUMBNAIL_OVERLAYS_NONE)
1981 {
1982 thumb->filename = g_strdup(img->filename);
1983 thumb->has_audio = (img->flags & DT_IMAGE_HAS_WAV);
1984 thumb->has_localcopy = (img->flags & DT_IMAGE_LOCAL_COPY);
1985 }
1986
1987 dt_image_cache_read_release(darktable.image_cache, img);
1988 }
1989 if(thumb->over == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED || thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_EXTENDED
1990 || thumb->over == DT_THUMBNAIL_OVERLAYS_MIXED || thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
1991 _thumb_update_extended_infos_line(thumb);
1992
1993 // we read all other infos
1994 if(thumb->over != DT_THUMBNAIL_OVERLAYS_NONE)
1995 {
1996 _image_get_infos(thumb);
1997 _thumb_update_icons(thumb);
1998 }
1999
2000 _thumb_write_extension(thumb);
2001
2002 // extended overlay text
2003 gchar *lb = NULL;
2004 if(thumb->over == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED || thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_EXTENDED
2005 || thumb->over == DT_THUMBNAIL_OVERLAYS_MIXED || thumb->over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
2006 lb = g_strdup(thumb->info_line);
2007
2008 // we set the text
2009 gtk_label_set_markup(GTK_LABEL(thumb->w_bottom), lb);
2010 g_free(lb);
2011 }
2012 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
2013 // vim: shiftwidth=2 expandtab tabstop=2 cindent
2014 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2015