1 /*
2     This file is part of darktable,
3     Copyright (C) 2009-2021 darktable developers.
4 
5     darktable is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     darktable is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with darktable.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #pragma once
20 
21 #include "common/darktable.h"
22 #include "common/dtpthread.h"
23 
24 #include <gtk/gtk.h>
25 #include <stdint.h>
26 
27 #define DT_GUI_IOP_MODULE_CONTROL_SPACING 0
28 
29 #define DT_GUI_THUMBSIZE_REDUCE 0.7f
30 
31 /* helper macro that applies the DPI transformation to fixed pixel values. input should be defaulting to 96
32  * DPI */
33 #define DT_PIXEL_APPLY_DPI(value) ((value) * darktable.gui->dpi_factor)
34 
35 typedef struct dt_gui_widgets_t
36 {
37 
38   // Borders
39   GtkWidget *left_border;
40   GtkWidget *right_border;
41   GtkWidget *bottom_border;
42   GtkWidget *top_border;
43 
44   /* left panel */
45   GtkGrid *panel_left; // panel grid 3 rows, top,center,bottom and file on center
46   GtkGrid *panel_right;
47 
48   /* resize of left/right panels */
49   gboolean panel_handle_dragging;
50   int panel_handle_x, panel_handle_y;
51 } dt_gui_widgets_t;
52 
53 typedef struct dt_gui_scrollbars_t
54 {
55     GtkWidget *vscrollbar;
56     GtkWidget *hscrollbar;
57 
58     gboolean visible;
59     gboolean dragging;
60 } dt_gui_scrollbars_t;
61 
62 typedef enum dt_gui_color_t
63 {
64   DT_GUI_COLOR_BG = 0,
65   DT_GUI_COLOR_DARKROOM_BG,
66   DT_GUI_COLOR_DARKROOM_PREVIEW_BG,
67   DT_GUI_COLOR_LIGHTTABLE_BG,
68   DT_GUI_COLOR_LIGHTTABLE_PREVIEW_BG,
69   DT_GUI_COLOR_LIGHTTABLE_FONT,
70   DT_GUI_COLOR_PRINT_BG,
71   DT_GUI_COLOR_BRUSH_CURSOR,
72   DT_GUI_COLOR_BRUSH_TRACE,
73   DT_GUI_COLOR_THUMBNAIL_BG,
74   DT_GUI_COLOR_THUMBNAIL_SELECTED_BG,
75   DT_GUI_COLOR_THUMBNAIL_HOVER_BG,
76   DT_GUI_COLOR_THUMBNAIL_OUTLINE,
77   DT_GUI_COLOR_THUMBNAIL_SELECTED_OUTLINE,
78   DT_GUI_COLOR_THUMBNAIL_HOVER_OUTLINE,
79   DT_GUI_COLOR_THUMBNAIL_FONT,
80   DT_GUI_COLOR_THUMBNAIL_SELECTED_FONT,
81   DT_GUI_COLOR_THUMBNAIL_HOVER_FONT,
82   DT_GUI_COLOR_THUMBNAIL_BORDER,
83   DT_GUI_COLOR_THUMBNAIL_SELECTED_BORDER,
84   DT_GUI_COLOR_FILMSTRIP_BG,
85   DT_GUI_COLOR_CULLING_SELECTED_BORDER,
86   DT_GUI_COLOR_CULLING_FILMSTRIP_SELECTED_BORDER,
87   DT_GUI_COLOR_PREVIEW_HOVER_BORDER,
88   DT_GUI_COLOR_LOG_BG,
89   DT_GUI_COLOR_LOG_FG,
90   DT_GUI_COLOR_MAP_COUNT_SAME_LOC,
91   DT_GUI_COLOR_MAP_COUNT_DIFF_LOC,
92   DT_GUI_COLOR_MAP_COUNT_BG,
93   DT_GUI_COLOR_MAP_LOC_SHAPE_HIGH,
94   DT_GUI_COLOR_MAP_LOC_SHAPE_LOW,
95   DT_GUI_COLOR_MAP_LOC_SHAPE_DEF,
96   DT_GUI_COLOR_LAST
97 } dt_gui_color_t;
98 
99 typedef struct dt_gui_gtk_t
100 {
101 
102   struct dt_ui_t *ui;
103 
104   dt_gui_widgets_t widgets;
105 
106   dt_gui_scrollbars_t scrollbars;
107 
108   cairo_surface_t *surface;
109   GtkMenu *presets_popup_menu;
110   char *last_preset;
111 
112   int32_t reset;
113   GdkRGBA colors[DT_GUI_COLOR_LAST];
114 
115   int32_t center_tooltip; // 0 = no tooltip, 1 = new tooltip, 2 = old tooltip
116 
117   gboolean grouping;
118   int32_t expanded_group_id;
119 
120   gboolean show_overlays;
121   gboolean show_focus_peaking;
122   GtkWidget *focus_peaking_button;
123 
124   double dpi, dpi_factor, ppd, ppd_thb;
125 
126   int icon_size; // size of top panel icons
127 
128   // store which gtkrc we loaded:
129   char gtkrc[PATH_MAX];
130 
131   GtkWidget *scroll_to[2]; // one for left, one for right
132 
133   gint scroll_mask;
134   guint sidebar_scroll_mask;
135 
136   cairo_filter_t filter_image;    // filtering used for all modules expect darkroom
137   cairo_filter_t dr_filter_image; // filtering used in the darkroom
138 
139   dt_pthread_mutex_t mutex;
140 } dt_gui_gtk_t;
141 
dt_cairo_image_surface_create(cairo_format_t format,int width,int height)142 static inline cairo_surface_t *dt_cairo_image_surface_create(cairo_format_t format, int width, int height) {
143   cairo_surface_t *cst = cairo_image_surface_create(format, width * darktable.gui->ppd, height * darktable.gui->ppd);
144   cairo_surface_set_device_scale(cst, darktable.gui->ppd, darktable.gui->ppd);
145   return cst;
146 }
147 
dt_cairo_image_surface_create_for_data(unsigned char * data,cairo_format_t format,int width,int height,int stride)148 static inline cairo_surface_t *dt_cairo_image_surface_create_for_data(unsigned char *data, cairo_format_t format, int width, int height, int stride) {
149   cairo_surface_t *cst = cairo_image_surface_create_for_data(data, format, width, height, stride);
150   cairo_surface_set_device_scale(cst, darktable.gui->ppd, darktable.gui->ppd);
151   return cst;
152 }
153 
dt_cairo_image_surface_create_from_png(const char * filename)154 static inline cairo_surface_t *dt_cairo_image_surface_create_from_png(const char *filename) {
155   cairo_surface_t *cst = cairo_image_surface_create_from_png(filename);
156   cairo_surface_set_device_scale(cst, darktable.gui->ppd, darktable.gui->ppd);
157   return cst;
158 }
159 
dt_cairo_image_surface_get_width(cairo_surface_t * surface)160 static inline int dt_cairo_image_surface_get_width(cairo_surface_t *surface) {
161   return cairo_image_surface_get_width(surface) / darktable.gui->ppd;
162 }
163 
dt_cairo_image_surface_get_height(cairo_surface_t * surface)164 static inline int dt_cairo_image_surface_get_height(cairo_surface_t *surface) {
165   return cairo_image_surface_get_height(surface) / darktable.gui->ppd;
166 }
167 
dt_gdk_cairo_surface_create_from_pixbuf(const GdkPixbuf * pixbuf,int scale,GdkWindow * for_window)168 static inline cairo_surface_t *dt_gdk_cairo_surface_create_from_pixbuf(const GdkPixbuf *pixbuf, int scale, GdkWindow *for_window) {
169   cairo_surface_t *cst = gdk_cairo_surface_create_from_pixbuf(pixbuf, scale, for_window);
170   cairo_surface_set_device_scale(cst, darktable.gui->ppd, darktable.gui->ppd);
171   return cst;
172 }
173 
dt_gdk_pixbuf_new_from_file_at_size(const char * filename,int width,int height,GError ** error)174 static inline GdkPixbuf *dt_gdk_pixbuf_new_from_file_at_size(const char *filename, int width, int height, GError **error) {
175   return gdk_pixbuf_new_from_file_at_size(filename, width * darktable.gui->ppd, height * darktable.gui->ppd, error);
176 }
177 
178 int dt_gui_gtk_init(dt_gui_gtk_t *gui);
179 void dt_gui_gtk_run(dt_gui_gtk_t *gui);
180 void dt_gui_gtk_cleanup(dt_gui_gtk_t *gui);
181 void dt_gui_gtk_quit();
182 void dt_gui_store_last_preset(const char *name);
183 int dt_gui_gtk_load_config();
184 int dt_gui_gtk_write_config();
185 void dt_gui_gtk_set_source_rgb(cairo_t *cr, dt_gui_color_t);
186 void dt_gui_gtk_set_source_rgba(cairo_t *cr, dt_gui_color_t, float opacity_coef);
187 double dt_get_system_gui_ppd(GtkWidget *widget);
188 
189 /* Check sidebar_scroll_default and modifier keys to determine if scroll event
190  * should be processed by control or by panel. If default is panel scroll but
191  * modifiers are pressed to indicate the control should be scrolled, then remove
192  * the modifiers from the event before returning false */
193 gboolean dt_gui_ignore_scroll(GdkEventScroll *event);
194 /* Return requested scroll delta(s) from event. If delta_x or delta_y
195  * is NULL, do not return that delta. Return TRUE if requested deltas
196  * can be retrieved. Handles both GDK_SCROLL_UP/DOWN/LEFT/RIGHT and
197  * GDK_SCROLL_SMOOTH style scroll events. */
198 gboolean dt_gui_get_scroll_deltas(const GdkEventScroll *event, gdouble *delta_x, gdouble *delta_y);
199 /* Same as above, except accumulate smooth scrolls deltas of < 1 and
200  * only set deltas and return TRUE once scrolls accumulate to >= 1.
201  * Effectively makes smooth scroll events act like old-style unit
202  * scroll events. */
203 gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y);
204 
205 /* Note that on macOS Shift+vertical scroll can be reported as Shift+horizontal scroll.
206  * So if Shift changes scrolling effect, both scrolls should be handled the same.
207  * For this case (or if it's otherwise useful) use the following 2 functions. */
208 
209 /* Return sum of scroll deltas from event. Return TRUE if any deltas
210  * can be retrieved. Handles both GDK_SCROLL_UP/DOWN/LEFT/RIGHT and
211  * GDK_SCROLL_SMOOTH style scroll events. */
212 gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble *delta);
213 /* Same as above, except accumulate smooth scrolls deltas of < 1 and
214  * only set delta and return TRUE once scrolls accumulate to >= 1.
215  * Effectively makes smooth scroll events act like old-style unit
216  * scroll events. */
217 gboolean dt_gui_get_scroll_unit_delta(const GdkEventScroll *event, int *delta);
218 
219 /** block any keyaccelerators when widget have focus, block is released when widget lose focus. */
220 void dt_gui_key_accel_block_on_focus_connect(GtkWidget *w);
221 /** clean up connected signal handlers before destroying your widget: */
222 void dt_gui_key_accel_block_on_focus_disconnect(GtkWidget *w);
223 
224 /*
225  * new ui api
226  */
227 
228 
229 typedef enum dt_ui_container_t
230 {
231   /* the top container of left panel, the top container
232      disables the module expander and does not scroll with other modules
233   */
234   DT_UI_CONTAINER_PANEL_LEFT_TOP = 0,
235 
236   /* the center container of left panel, the center container
237      contains the scrollable area that all plugins are placed within and last
238      widget is the end marker.
239      This container will always expand|fill empty vertical space
240   */
241   DT_UI_CONTAINER_PANEL_LEFT_CENTER = 1,
242 
243   /* the bottom container of left panel, this container works just like
244      the top container but will be attached to bottom in the panel, such as
245      plugins like background jobs module in lighttable and the plugin selection
246      module in darkroom,
247   */
248   DT_UI_CONTAINER_PANEL_LEFT_BOTTOM = 2,
249 
250   DT_UI_CONTAINER_PANEL_RIGHT_TOP = 3,
251   DT_UI_CONTAINER_PANEL_RIGHT_CENTER = 4,
252   DT_UI_CONTAINER_PANEL_RIGHT_BOTTOM = 5,
253 
254 
255   /* the top header bar, left slot where darktable name is placed */
256   DT_UI_CONTAINER_PANEL_TOP_LEFT = 6,
257   /* center which is expanded as wide it can */
258   DT_UI_CONTAINER_PANEL_TOP_CENTER = 7,
259   /* right side were the different views are accessed */
260   DT_UI_CONTAINER_PANEL_TOP_RIGHT = 8,
261 
262   DT_UI_CONTAINER_PANEL_CENTER_TOP_LEFT = 9,
263   DT_UI_CONTAINER_PANEL_CENTER_TOP_CENTER = 10,
264   DT_UI_CONTAINER_PANEL_CENTER_TOP_RIGHT = 11,
265 
266   DT_UI_CONTAINER_PANEL_CENTER_BOTTOM_LEFT = 12,
267   DT_UI_CONTAINER_PANEL_CENTER_BOTTOM_CENTER = 13,
268   DT_UI_CONTAINER_PANEL_CENTER_BOTTOM_RIGHT = 14,
269 
270   /* this panel is placed at bottom of ui
271      only used by the filmstrip if shown */
272   DT_UI_CONTAINER_PANEL_BOTTOM = 15,
273 
274   /* Count of containers */
275   DT_UI_CONTAINER_SIZE
276 } dt_ui_container_t;
277 
278 typedef enum dt_ui_panel_t
279 {
280   /* the header panel */
281   DT_UI_PANEL_TOP,
282   /* center top toolbar panel */
283   DT_UI_PANEL_CENTER_TOP,
284   /* center bottom toolbar panel */
285   DT_UI_PANEL_CENTER_BOTTOM,
286   /* left panel */
287   DT_UI_PANEL_LEFT,
288   /* right panel */
289   DT_UI_PANEL_RIGHT,
290   /* bottom panel */
291   DT_UI_PANEL_BOTTOM,
292 
293   DT_UI_PANEL_SIZE
294 } dt_ui_panel_t;
295 
296 typedef enum dt_ui_border_t
297 {
298   DT_UI_BORDER_TOP,
299   DT_UI_BORDER_BOTTOM,
300   DT_UI_BORDER_LEFT,
301   DT_UI_BORDER_RIGHT,
302 
303   DT_UI_BORDER_SIZE
304 } dt_ui_border_t;
305 
306 /** \brief add's a widget to a defined container */
307 void dt_ui_container_add_widget(struct dt_ui_t *ui, const dt_ui_container_t c, GtkWidget *w);
308 /** \brief gives a widget focus in the container */
309 void dt_ui_container_focus_widget(struct dt_ui_t *ui, const dt_ui_container_t c, GtkWidget *w);
310 /** \brief calls a callback on all children widgets from container */
311 void dt_ui_container_foreach(struct dt_ui_t *ui, const dt_ui_container_t c, GtkCallback callback);
312 /** \brief destroy all child widgets from container */
313 void dt_ui_container_destroy_children(struct dt_ui_t *ui, const dt_ui_container_t c);
314 /** \brief shows/hide a panel */
315 void dt_ui_panel_show(struct dt_ui_t *ui, const dt_ui_panel_t, gboolean show, gboolean write);
316 /** \brief restore saved state of panel visibility for current view */
317 void dt_ui_restore_panels(struct dt_ui_t *ui);
318 /** \brief update scrollbars for current view */
319 void dt_ui_update_scrollbars(struct dt_ui_t *ui);
320 /** show or hide scrollbars */
321 void dt_ui_scrollbars_show(struct dt_ui_t *ui, gboolean show);
322 /** \brief toggle view of panels eg. collaps/expands to previous view state */
323 void dt_ui_toggle_panels_visibility(struct dt_ui_t *ui);
324 /** \brief draw user's attention */
325 void dt_ui_notify_user();
326 /** \brief get visible state of panel */
327 gboolean dt_ui_panel_visible(struct dt_ui_t *ui, const dt_ui_panel_t);
328 /**  \brief get width of right, left, or bottom panel */
329 int dt_ui_panel_get_size(struct dt_ui_t *ui, const dt_ui_panel_t p);
330 /**  \brief set width of right, left, or bottom panel */
331 void dt_ui_panel_set_size(struct dt_ui_t *ui, const dt_ui_panel_t p, int s);
332 /** \brief get the center drawable widget */
333 GtkWidget *dt_ui_center(struct dt_ui_t *ui);
334 GtkWidget *dt_ui_center_base(struct dt_ui_t *ui);
335 /** \brief get the main window widget */
336 GtkWidget *dt_ui_main_window(struct dt_ui_t *ui);
337 /** \brief get the thumb table */
338 struct dt_thumbtable_t *dt_ui_thumbtable(struct dt_ui_t *ui);
339 /** \brief get the log message widget */
340 GtkWidget *dt_ui_log_msg(struct dt_ui_t *ui);
341 /** \brief get the toast message widget */
342 GtkWidget *dt_ui_toast_msg(struct dt_ui_t *ui);
343 
344 GtkBox *dt_ui_get_container(struct dt_ui_t *ui, const dt_ui_container_t c);
345 
346 /*  activate ellipsization of the combox entries */
347 void dt_ellipsize_combo(GtkComboBox *cbox);
348 
dt_ui_section_label_set(GtkWidget * label)349 static inline void dt_ui_section_label_set(GtkWidget *label)
350 {
351   gtk_widget_set_halign(label, GTK_ALIGN_FILL); // make it span the whole available width
352   gtk_label_set_xalign (GTK_LABEL(label), 0.5f);
353   gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END); // ellipsize labels
354   gtk_widget_set_name(label, "section_label"); // make sure that we can style these easily
355 }
356 
dt_ui_section_label_new(const gchar * str)357 static inline GtkWidget *dt_ui_section_label_new(const gchar *str)
358 {
359   GtkWidget *label = gtk_label_new(str);
360   dt_ui_section_label_set(label);
361   return label;
362 };
363 
dt_ui_label_new(const gchar * str)364 static inline GtkWidget *dt_ui_label_new(const gchar *str)
365 {
366   GtkWidget *label = gtk_label_new(str);
367   gtk_widget_set_halign(label, GTK_ALIGN_START);
368   gtk_label_set_xalign (GTK_LABEL(label), 0.0f);
369   gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
370   return label;
371 };
372 
373 // clears all the pages of the notebook
374 void dt_ui_notebook_clear(GtkNotebook *notebook);
375 
376 GtkWidget *dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip);
377 
378 // show a dialog box with 2 buttons in case some user interaction is required BEFORE dt's gui is initialised.
379 // this expects gtk_init() to be called already which should be the case during most of dt's init phase.
380 gboolean dt_gui_show_standalone_yes_no_dialog(const char *title, const char *markup, const char *no_text,
381                                               const char *yes_text);
382 
383 // similar to the one above. this one asks the user for some string. the hint is shown in the empty entry box
384 char *dt_gui_show_standalone_string_dialog(const char *title, const char *markup, const char *placeholder,
385                                            const char *no_text, const char *yes_text);
386 
387 void *dt_gui_show_splashscreen();
388 void dt_gui_close_splashscreen(void *splashscreen);
389 
390 void dt_gui_add_help_link(GtkWidget *widget, const char *link);
391 
392 // load a CSS theme
393 void dt_gui_load_theme(const char *theme);
394 
395 // reload GUI scalings
396 void dt_configure_ppd_dpi(dt_gui_gtk_t *gui);
397 
398 // translate key press events to remove any modifiers used to produce the keyval
399 // for example when the shift key is used to create the asterisk character
400 guint dt_gui_translated_key_state(GdkEventKey *event);
401 
402 // return modifier keys currently pressed, independent of any key event
403 GdkModifierType dt_key_modifier_state();
404 
405 // create an ellipsized button with label, tooltip and help link
dt_ui_button_new(const gchar * label,const gchar * tooltip,const gchar * help)406 static inline GtkWidget *dt_ui_button_new(const gchar *label, const gchar *tooltip, const gchar *help)
407 {
408   GtkWidget *button = gtk_button_new_with_label(label);
409   gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(button))), PANGO_ELLIPSIZE_END);
410   if(tooltip) gtk_widget_set_tooltip_text(button, tooltip);
411   if(help) dt_gui_add_help_link(button, help);
412   return button;
413 };
414 
415 GtkWidget *dt_ui_scroll_wrap(GtkWidget *w, gint min_size, char *config_str);
416 
417 // check whether the given container has any user-added children
418 gboolean dt_gui_container_has_children(GtkContainer *container);
419 // return a count of the user-added children in the given container
420 int dt_gui_container_num_children(GtkContainer *container);
421 // return the first child of the given container
422 GtkWidget *dt_gui_container_first_child(GtkContainer *container);
423 // return the requested child of the given container, or NULL if it has fewer children
424 GtkWidget *dt_gui_container_nth_child(GtkContainer *container, int which);
425 
426 // remove all of the children we've added to the container.  Any which no longer have any references will
427 // be destroyed.
428 void dt_gui_container_remove_children(GtkContainer *container);
429 
430 // delete all of the children we've added to the container.  Use this function only if you are SURE
431 // there are no other references to any of the children (if in doubt, use dt_gui_container_remove_children
432 // instead; it's a bit slower but safer).
433 void dt_gui_container_destroy_children(GtkContainer *container);
434 
435 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
436 // vim: shiftwidth=2 expandtab tabstop=2 cindent
437 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
438