/*
This file is part of darktable,
Copyright (C) 2011-2021 darktable developers.
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
darktable is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with darktable. If not, see .
*/
#include "gui/accelerators.h"
#include "common/darktable.h"
#include "common/debug.h"
#include "common/utility.h"
#include "control/control.h"
#include "develop/blend.h"
#include "bauhaus/bauhaus.h"
#include
#include
typedef struct _accel_iop_t
{
dt_accel_t *accel;
GClosure *closure;
} _accel_iop_t;
void dt_accel_path_global(char *s, size_t n, const char *path)
{
snprintf(s, n, "/%s/%s", "global", path);
}
void dt_accel_path_view(char *s, size_t n, char *module, const char *path)
{
snprintf(s, n, "/%s/%s/%s", "views", module, path);
}
void dt_accel_path_iop(char *s, size_t n, char *module, const char *path)
{
if(path)
{
gchar **split_paths = g_strsplit(path, "`", 4);
gchar **used_paths = split_paths;
// transitionally keep "preset" translated in keyboardrc to avoid breakage for now
// this also needs to be amended in preferences
if(!strcmp(split_paths[0], "preset"))
{
g_free(split_paths[0]);
split_paths[0] = g_strdup(_("preset"));
}
else if(!strcmp(split_paths[0], "blend"))
{
module = "blending";
used_paths++;
}
for(gchar **cur_path = used_paths; *cur_path; cur_path++)
{
gchar *after_context = strchr(*cur_path,'|');
if(after_context) memmove(*cur_path, after_context + 1, strlen(after_context));
}
gchar *joined_paths = g_strjoinv("/", used_paths);
snprintf(s, n, "/%s/%s/%s", "image operations", module, joined_paths);
g_free(joined_paths);
g_strfreev(split_paths);
}
else
snprintf(s, n, "/%s/%s", "image operations", module);
}
void dt_accel_path_lib(char *s, size_t n, char *module, const char *path)
{
snprintf(s, n, "/%s/%s/%s", "modules", module, path);
}
void dt_accel_path_lua(char *s, size_t n, const char *path)
{
snprintf(s, n, "/%s/%s", "lua", path);
}
void dt_accel_path_manual(char *s, size_t n, const char *full_path)
{
snprintf(s, n, "/%s", full_path);
}
static void dt_accel_path_global_translated(char *s, size_t n, const char *path)
{
snprintf(s, n, "/%s/%s", C_("accel", "global"), g_dpgettext2(NULL, "accel", path));
}
static void dt_accel_path_view_translated(char *s, size_t n, dt_view_t *module, const char *path)
{
snprintf(s, n, "/%s/%s/%s", C_("accel", "views"), module->name(module),
g_dpgettext2(NULL, "accel", path));
}
static void dt_accel_path_iop_translated(char *s, size_t n, dt_iop_module_so_t *module, const char *path)
{
gchar *module_clean = g_strdelimit(g_strdup(module->name()), "/", '-');
if(path)
{
gchar **split_paths = g_strsplit(path, "`", 4);
gchar **used_paths = split_paths;
if(!strcmp(split_paths[0], "blend"))
{
g_free(module_clean);
module_clean = g_strconcat(_("blending"), " ", NULL);
used_paths++;
}
for(gchar **cur_path = used_paths; *cur_path; cur_path++)
{
gchar *saved_path = *cur_path;
*cur_path = g_strdelimit(g_strconcat(Q_(*cur_path), (strcmp(*cur_path, "preset") ? NULL : " "), NULL), "/", '`');
g_free(saved_path);
}
gchar *joined_paths = g_strjoinv("/", used_paths);
snprintf(s, n, "/%s/%s/%s", C_("accel", "processing modules"), module_clean, joined_paths);
g_free(joined_paths);
g_strfreev(split_paths);
}
else
snprintf(s, n, "/%s/%s", C_("accel", "processing modules"), module_clean);
g_free(module_clean);
}
static void dt_accel_path_lib_translated(char *s, size_t n, dt_lib_module_t *module, const char *path)
{
snprintf(s, n, "/%s/%s/%s", C_("accel", "utility modules"), module->name(module),
g_dpgettext2(NULL, "accel", path));
}
static void dt_accel_path_lua_translated(char *s, size_t n, const char *path)
{
snprintf(s, n, "/%s/%s", C_("accel", "lua"), g_dpgettext2(NULL, "accel", path));
}
static void dt_accel_path_manual_translated(char *s, size_t n, const char *full_path)
{
snprintf(s, n, "/%s", g_dpgettext2(NULL, "accel", full_path));
}
void dt_accel_register_global(const gchar *path, guint accel_key, GdkModifierType mods)
{
gchar accel_path[256];
dt_accel_t *accel = (dt_accel_t *)g_malloc0(sizeof(dt_accel_t));
dt_accel_path_global(accel_path, sizeof(accel_path), path);
gtk_accel_map_add_entry(accel_path, accel_key, mods);
g_strlcpy(accel->path, accel_path, sizeof(accel->path));
dt_accel_path_global_translated(accel_path, sizeof(accel_path), path);
g_strlcpy(accel->translated_path, accel_path, sizeof(accel->translated_path));
*(accel->module) = '\0';
accel->local = FALSE;
accel->views = DT_VIEW_DARKROOM | DT_VIEW_LIGHTTABLE | DT_VIEW_TETHERING | DT_VIEW_MAP | DT_VIEW_PRINT | DT_VIEW_SLIDESHOW;
darktable.control->accelerator_list = g_list_prepend(darktable.control->accelerator_list, accel);
}
void dt_accel_register_view(dt_view_t *self, const gchar *path, guint accel_key, GdkModifierType mods)
{
gchar accel_path[256];
dt_accel_t *accel = (dt_accel_t *)g_malloc0(sizeof(dt_accel_t));
dt_accel_path_view(accel_path, sizeof(accel_path), self->module_name, path);
gtk_accel_map_add_entry(accel_path, accel_key, mods);
g_strlcpy(accel->path, accel_path, sizeof(accel->path));
dt_accel_path_view_translated(accel_path, sizeof(accel_path), self, path);
g_strlcpy(accel->translated_path, accel_path, sizeof(accel->translated_path));
g_strlcpy(accel->module, self->module_name, sizeof(accel->module));
accel->local = FALSE;
accel->views = self->view(self);
darktable.control->accelerator_list = g_list_prepend(darktable.control->accelerator_list, accel);
}
void dt_accel_register_iop(dt_iop_module_so_t *so, gboolean local, const gchar *path, guint accel_key,
GdkModifierType mods)
{
dt_accel_t *accel = (dt_accel_t *)g_malloc0(sizeof(dt_accel_t));
dt_accel_path_iop(accel->path, sizeof(accel->path), so->op, path);
gtk_accel_map_add_entry(accel->path, accel_key, mods);
dt_accel_path_iop_translated(accel->translated_path, sizeof(accel->translated_path), so, path);
g_strlcpy(accel->module, so->op, sizeof(accel->module));
accel->local = local;
accel->views = DT_VIEW_DARKROOM;
darktable.control->accelerator_list = g_list_prepend(darktable.control->accelerator_list, accel);
}
void dt_accel_register_lib_as_view(gchar *view_name, const gchar *path, guint accel_key, GdkModifierType mods)
{
//register a lib shortcut but place it in the path of a view
gchar accel_path[256];
dt_accel_path_view(accel_path, sizeof(accel_path), view_name, path);
if (dt_accel_find_by_path(accel_path)) return; // return if nothing to add, to avoid multiple entries
dt_accel_t *accel = (dt_accel_t *)g_malloc0(sizeof(dt_accel_t));
gtk_accel_map_add_entry(accel_path, accel_key, mods);
g_strlcpy(accel->path, accel_path, sizeof(accel->path));
snprintf(accel_path, sizeof(accel_path), "/%s/%s/%s", C_("accel", "views"),
g_dgettext(NULL, view_name),
g_dpgettext2(NULL, "accel", path));
g_strlcpy(accel->translated_path, accel_path, sizeof(accel->translated_path));
g_strlcpy(accel->module, view_name, sizeof(accel->module));
accel->local = FALSE;
if(strcmp(view_name, "lighttable") == 0)
accel->views = DT_VIEW_LIGHTTABLE;
else if(strcmp(view_name, "darkroom") == 0)
accel->views = DT_VIEW_DARKROOM;
else if(strcmp(view_name, "print") == 0)
accel->views = DT_VIEW_PRINT;
else if(strcmp(view_name, "slideshow") == 0)
accel->views = DT_VIEW_SLIDESHOW;
else if(strcmp(view_name, "map") == 0)
accel->views = DT_VIEW_MAP;
else if(strcmp(view_name, "tethering") == 0)
accel->views = DT_VIEW_TETHERING;
darktable.control->accelerator_list = g_list_prepend(darktable.control->accelerator_list, accel);
}
void dt_accel_register_lib_for_views(dt_lib_module_t *self, dt_view_type_flags_t views, const gchar *path,
guint accel_key, GdkModifierType mods)
{
gchar accel_path[256];
dt_accel_path_lib(accel_path, sizeof(accel_path), self->plugin_name, path);
if (dt_accel_find_by_path(accel_path)) return; // return if nothing to add, to avoid multiple entries
dt_accel_t *accel = (dt_accel_t *)g_malloc0(sizeof(dt_accel_t));
gtk_accel_map_add_entry(accel_path, accel_key, mods);
g_strlcpy(accel->path, accel_path, sizeof(accel->path));
dt_accel_path_lib_translated(accel_path, sizeof(accel_path), self, path);
g_strlcpy(accel->translated_path, accel_path, sizeof(accel->translated_path));
g_strlcpy(accel->module, self->plugin_name, sizeof(accel->module));
accel->local = FALSE;
// we get the views in which the lib will be displayed
accel->views = views;
darktable.control->accelerator_list = g_list_prepend(darktable.control->accelerator_list, accel);
}
void dt_accel_register_lib(dt_lib_module_t *self, const gchar *path, guint accel_key, GdkModifierType mods)
{
dt_view_type_flags_t v = 0;
int i=0;
const gchar **views = self->views(self);
while (views[i])
{
if(strcmp(views[i], "lighttable") == 0)
v |= DT_VIEW_LIGHTTABLE;
else if(strcmp(views[i], "darkroom") == 0)
v |= DT_VIEW_DARKROOM;
else if(strcmp(views[i], "print") == 0)
v |= DT_VIEW_PRINT;
else if(strcmp(views[i], "slideshow") == 0)
v |= DT_VIEW_SLIDESHOW;
else if(strcmp(views[i], "map") == 0)
v |= DT_VIEW_MAP;
else if(strcmp(views[i], "tethering") == 0)
v |= DT_VIEW_TETHERING;
else if(strcmp(views[i], "*") == 0)
v |= DT_VIEW_DARKROOM | DT_VIEW_LIGHTTABLE | DT_VIEW_TETHERING | DT_VIEW_MAP | DT_VIEW_PRINT
| DT_VIEW_SLIDESHOW;
i++;
}
dt_accel_register_lib_for_views(self, v, path, accel_key, mods);
}
const gchar *_common_actions[]
= { NC_("accel", "show module"),
NC_("accel", "enable module"),
NC_("accel", "focus module"),
NC_("accel", "reset module parameters"),
NC_("accel", "show preset menu"),
NULL };
const gchar *_slider_actions[]
= { NC_("accel", "increase"),
NC_("accel", "decrease"),
NC_("accel", "reset"),
NC_("accel", "edit"),
NC_("accel", "dynamic"),
NULL };
const gchar *_combobox_actions[]
= { NC_("accel", "next"),
NC_("accel", "previous"),
NC_("accel", "dynamic"),
NULL };
void _accel_register_actions_iop(dt_iop_module_so_t *so, gboolean local, const gchar *path, const char **actions)
{
gchar accel_path[256];
gchar accel_path_trans[256];
dt_accel_path_iop(accel_path, sizeof(accel_path), so->op, path);
dt_accel_path_iop_translated(accel_path_trans, sizeof(accel_path_trans), so, path);
for(const char **action = actions; *action; action++)
{
dt_accel_t *accel = (dt_accel_t *)g_malloc0(sizeof(dt_accel_t));
snprintf(accel->path, sizeof(accel->path), "%s/%s", accel_path, *action);
gtk_accel_map_add_entry(accel->path, 0, 0);
snprintf(accel->translated_path, sizeof(accel->translated_path), "%s/%s ", accel_path_trans,
g_dpgettext2(NULL, "accel", *action));
g_strlcpy(accel->module, so->op, sizeof(accel->module));
accel->local = local;
accel->views = DT_VIEW_DARKROOM;
darktable.control->accelerator_list = g_list_prepend(darktable.control->accelerator_list, accel);
}
}
void dt_accel_register_common_iop(dt_iop_module_so_t *so)
{
_accel_register_actions_iop(so, FALSE, NULL, _common_actions);
}
void dt_accel_register_combobox_iop(dt_iop_module_so_t *so, gboolean local, const gchar *path)
{
_accel_register_actions_iop(so, local, path, _combobox_actions);
}
void dt_accel_register_slider_iop(dt_iop_module_so_t *so, gboolean local, const gchar *path)
{
_accel_register_actions_iop(so, local, path, _slider_actions);
}
void dt_accel_register_lua(const gchar *path, guint accel_key, GdkModifierType mods)
{
gchar accel_path[256];
dt_accel_t *accel = (dt_accel_t *)g_malloc0(sizeof(dt_accel_t));
dt_accel_path_lua(accel_path, sizeof(accel_path), path);
gtk_accel_map_add_entry(accel_path, accel_key, mods);
g_strlcpy(accel->path, accel_path, sizeof(accel->path));
dt_accel_path_lua_translated(accel_path, sizeof(accel_path), path);
g_strlcpy(accel->translated_path, accel_path, sizeof(accel->translated_path));
*(accel->module) = '\0';
accel->local = FALSE;
accel->views = DT_VIEW_DARKROOM | DT_VIEW_LIGHTTABLE | DT_VIEW_TETHERING | DT_VIEW_MAP | DT_VIEW_PRINT | DT_VIEW_SLIDESHOW;
darktable.control->accelerator_list = g_list_prepend(darktable.control->accelerator_list, accel);
}
void dt_accel_register_manual(const gchar *full_path, dt_view_type_flags_t views, guint accel_key,
GdkModifierType mods)
{
gchar accel_path[256];
dt_accel_t *accel = (dt_accel_t *)g_malloc0(sizeof(dt_accel_t));
dt_accel_path_manual(accel_path, sizeof(accel_path), full_path);
gtk_accel_map_add_entry(accel_path, accel_key, mods);
g_strlcpy(accel->path, accel_path, sizeof(accel->path));
dt_accel_path_manual_translated(accel_path, sizeof(accel_path), full_path);
g_strlcpy(accel->translated_path, accel_path, sizeof(accel->translated_path));
*(accel->module) = '\0';
accel->local = FALSE;
accel->views = views;
darktable.control->accelerator_list = g_list_prepend(darktable.control->accelerator_list, accel);
}
static dt_accel_t *_lookup_accel(const gchar *path)
{
for(const GList *l = darktable.control->accelerator_list; l; l = g_list_next(l))
{
dt_accel_t *accel = (dt_accel_t *)l->data;
if(accel && !strcmp(accel->path, path)) return accel;
}
return NULL;
}
void dt_accel_connect_global(const gchar *path, GClosure *closure)
{
gchar accel_path[256];
dt_accel_path_global(accel_path, sizeof(accel_path), path);
dt_accel_t *laccel = _lookup_accel(accel_path);
laccel->closure = closure;
gtk_accel_group_connect_by_path(darktable.control->accelerators, accel_path, closure);
}
void dt_accel_connect_view(dt_view_t *self, const gchar *path, GClosure *closure)
{
gchar accel_path[256];
dt_accel_path_view(accel_path, sizeof(accel_path), self->module_name, path);
gtk_accel_group_connect_by_path(darktable.control->accelerators, accel_path, closure);
dt_accel_t *laccel = _lookup_accel(accel_path);
laccel->closure = closure;
self->accel_closures = g_slist_prepend(self->accel_closures, laccel);
}
dt_accel_t *dt_accel_connect_lib_as_view(dt_lib_module_t *module, gchar *view_name, const gchar *path, GClosure *closure)
{
gchar accel_path[256];
dt_accel_path_view(accel_path, sizeof(accel_path), view_name, path);
gtk_accel_group_connect_by_path(darktable.control->accelerators, accel_path, closure);
dt_accel_t *accel = _lookup_accel(accel_path);
if(!accel) return NULL; // this happens when the path doesn't match any accel (typos, ...)
accel->closure = closure;
module->accel_closures = g_slist_prepend(module->accel_closures, accel);
return accel;
}
dt_accel_t *dt_accel_connect_lib_as_global(dt_lib_module_t *module, const gchar *path, GClosure *closure)
{
gchar accel_path[256];
dt_accel_path_global(accel_path, sizeof(accel_path), path);
dt_accel_t *accel = _lookup_accel(accel_path);
if(!accel) return NULL; // this happens when the path doesn't match any accel (typos, ...)
gtk_accel_group_connect_by_path(darktable.control->accelerators, accel_path, closure);
accel->closure = closure;
module->accel_closures = g_slist_prepend(module->accel_closures, accel);
return accel;
}
static dt_accel_t *_store_iop_accel_closure(dt_iop_module_t *module, gchar *accel_path, GClosure *closure)
{
// Looking up the entry in the global accelerators list
dt_accel_t *accel = _lookup_accel(accel_path);
if(!accel) return NULL; // this happens when the path doesn't match any accel (typos, ...)
GSList **save_list = accel->local ? &module->accel_closures_local : &module->accel_closures;
_accel_iop_t *stored_accel = g_malloc(sizeof(_accel_iop_t));
stored_accel->accel = accel;
stored_accel->closure = closure;
g_closure_ref(closure);
g_closure_sink(closure);
*save_list = g_slist_prepend(*save_list, stored_accel);
return accel;
}
dt_accel_t *dt_accel_connect_iop(dt_iop_module_t *module, const gchar *path, GClosure *closure)
{
gchar accel_path[256];
dt_accel_path_iop(accel_path, sizeof(accel_path), module->op, path);
return _store_iop_accel_closure(module, accel_path, closure);
}
dt_accel_t *dt_accel_connect_lib(dt_lib_module_t *module, const gchar *path, GClosure *closure)
{
gchar accel_path[256];
dt_accel_path_lib(accel_path, sizeof(accel_path), module->plugin_name, path);
gtk_accel_group_connect_by_path(darktable.control->accelerators, accel_path, closure);
dt_accel_t *accel = _lookup_accel(accel_path);
if(!accel) return NULL; // this happens when the path doesn't match any accel (typos, ...)
accel->closure = closure;
module->accel_closures = g_slist_prepend(module->accel_closures, accel);
return accel;
}
void dt_accel_connect_lua(const gchar *path, GClosure *closure)
{
gchar accel_path[256];
dt_accel_path_lua(accel_path, sizeof(accel_path), path);
dt_accel_t *laccel = _lookup_accel(accel_path);
laccel->closure = closure;
gtk_accel_group_connect_by_path(darktable.control->accelerators, accel_path, closure);
}
void dt_accel_connect_manual(GSList **list_ptr, const gchar *full_path, GClosure *closure)
{
gchar accel_path[256];
dt_accel_path_manual(accel_path, sizeof(accel_path), full_path);
dt_accel_t *accel = _lookup_accel(accel_path);
accel->closure = closure;
gtk_accel_group_connect_by_path(darktable.control->accelerators, accel_path, closure);
*list_ptr = g_slist_prepend(*list_ptr, accel);
}
static gboolean _press_button_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
GdkModifierType modifier, gpointer data)
{
if(!(GTK_IS_BUTTON(data))) return FALSE;
gtk_button_clicked(GTK_BUTTON(data));
return TRUE;
}
static gboolean _tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
GtkTooltip *tooltip, gpointer user_data)
{
char *text = gtk_widget_get_tooltip_text(widget);
GtkAccelKey key;
dt_accel_t *accel = g_object_get_data(G_OBJECT(widget), "dt-accel");
if(accel && gtk_accel_map_lookup_entry(accel->path, &key))
{
gchar *key_name = gtk_accelerator_get_label(key.accel_key, key.accel_mods);
if(key_name && *key_name)
{
char *tmp = g_strdup_printf(_("%s\n(shortcut: %s)"), text, key_name);
g_free(text);
text = tmp;
}
g_free(key_name);
}
gtk_tooltip_set_text(tooltip, text);
g_free(text);
return TRUE;
}
void dt_accel_connect_button_iop(dt_iop_module_t *module, const gchar *path, GtkWidget *button)
{
GClosure *closure = g_cclosure_new(G_CALLBACK(_press_button_callback), button, NULL);
dt_accel_t *accel = dt_accel_connect_iop(module, path, closure);
g_object_set_data(G_OBJECT(button), "dt-accel", accel);
if(gtk_widget_get_has_tooltip(button))
g_signal_connect(G_OBJECT(button), "query-tooltip", G_CALLBACK(_tooltip_callback), NULL);
}
void dt_accel_connect_button_lib(dt_lib_module_t *module, const gchar *path, GtkWidget *button)
{
GClosure *closure = g_cclosure_new(G_CALLBACK(_press_button_callback), button, NULL);
dt_accel_t *accel = dt_accel_connect_lib(module, path, closure);
g_object_set_data(G_OBJECT(button), "dt-accel", accel);
if(gtk_widget_get_has_tooltip(button))
g_signal_connect(G_OBJECT(button), "query-tooltip", G_CALLBACK(_tooltip_callback), NULL);
}
void dt_accel_connect_button_lib_as_global(dt_lib_module_t *module, const gchar *path, GtkWidget *button)
{
GClosure *closure = g_cclosure_new(G_CALLBACK(_press_button_callback), button, NULL);
dt_accel_t *accel = dt_accel_connect_lib_as_global(module, path, closure);
g_object_set_data(G_OBJECT(button), "dt-accel", accel);
if(gtk_widget_get_has_tooltip(button))
g_signal_connect(G_OBJECT(button), "query-tooltip", G_CALLBACK(_tooltip_callback), NULL);
}
static gboolean bauhaus_slider_edit_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
GdkModifierType modifier, gpointer data)
{
GtkWidget *slider = GTK_WIDGET(data);
dt_bauhaus_show_popup(DT_BAUHAUS_WIDGET(slider));
return TRUE;
}
void dt_accel_widget_toast(GtkWidget *widget)
{
dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)DT_BAUHAUS_WIDGET(widget);
if(!darktable.gui->reset)
{
char *text = NULL;
switch(w->type){
case DT_BAUHAUS_SLIDER:
{
text = dt_bauhaus_slider_get_text(widget);
break;
}
case DT_BAUHAUS_COMBOBOX:
text = g_strdup(dt_bauhaus_combobox_get_text(widget));
break;
default: //literally impossible but hey
return;
break;
}
if(w->label[0] != '\0')
{ // label is not empty
if(w->module && w->module->multi_name[0] != '\0')
dt_toast_log(_("%s %s / %s: %s"), w->module->name(), w->module->multi_name, w->label, text);
else if(w->module && !strstr(w->module->name(), w->label))
dt_toast_log(_("%s / %s: %s"), w->module->name(), w->label, text);
else
dt_toast_log(_("%s: %s"), w->label, text);
}
else
{ //label is empty
if(w->module && w->module->multi_name[0] != '\0')
dt_toast_log(_("%s %s / %s"), w->module->name(), w->module->multi_name, text);
else if(w->module)
dt_toast_log(_("%s / %s"), w->module->name(), text);
else
dt_toast_log("%s", text);
}
g_free(text);
}
}
float dt_accel_get_slider_scale_multiplier()
{
const int slider_precision = dt_conf_get_int("accel/slider_precision");
if(slider_precision == DT_IOP_PRECISION_COARSE)
{
return dt_conf_get_float("darkroom/ui/scale_rough_step_multiplier");
}
else if(slider_precision == DT_IOP_PRECISION_FINE)
{
return dt_conf_get_float("darkroom/ui/scale_precise_step_multiplier");
}
return dt_conf_get_float("darkroom/ui/scale_step_multiplier");
}
static gboolean _widget_invisible(GtkWidget *w)
{
return (!gtk_widget_get_visible(w) ||
!gtk_widget_get_visible(gtk_widget_get_parent(w)));
}
static gboolean bauhaus_slider_increase_callback(GtkAccelGroup *accel_group, GObject *acceleratable,
guint keyval, GdkModifierType modifier, gpointer data)
{
GtkWidget *slider = GTK_WIDGET(data);
if(_widget_invisible(slider)) return TRUE;
float value = dt_bauhaus_slider_get(slider);
float step = dt_bauhaus_slider_get_step(slider);
float multiplier = dt_accel_get_slider_scale_multiplier();
const float min_visible = powf(10.0f, -dt_bauhaus_slider_get_digits(slider));
if(fabsf(step*multiplier) < min_visible)
multiplier = min_visible / fabsf(step);
dt_bauhaus_slider_set(slider, value + step * multiplier);
g_signal_emit_by_name(G_OBJECT(slider), "value-changed");
dt_accel_widget_toast(slider);
return TRUE;
}
static gboolean bauhaus_slider_decrease_callback(GtkAccelGroup *accel_group, GObject *acceleratable,
guint keyval, GdkModifierType modifier, gpointer data)
{
GtkWidget *slider = GTK_WIDGET(data);
if(_widget_invisible(slider)) return TRUE;
float value = dt_bauhaus_slider_get(slider);
float step = dt_bauhaus_slider_get_step(slider);
float multiplier = dt_accel_get_slider_scale_multiplier();
const float min_visible = powf(10.0f, -dt_bauhaus_slider_get_digits(slider));
if(fabsf(step*multiplier) < min_visible)
multiplier = min_visible / fabsf(step);
dt_bauhaus_slider_set(slider, value - step * multiplier);
g_signal_emit_by_name(G_OBJECT(slider), "value-changed");
dt_accel_widget_toast(slider);
return TRUE;
}
static gboolean bauhaus_slider_reset_callback(GtkAccelGroup *accel_group, GObject *acceleratable,
guint keyval, GdkModifierType modifier, gpointer data)
{
GtkWidget *slider = GTK_WIDGET(data);
if(_widget_invisible(slider)) return TRUE;
dt_bauhaus_slider_reset(slider);
g_signal_emit_by_name(G_OBJECT(slider), "value-changed");
dt_accel_widget_toast(slider);
return TRUE;
}
static gboolean bauhaus_dynamic_callback(GtkAccelGroup *accel_group, GObject *acceleratable,
guint keyval, GdkModifierType modifier, gpointer data)
{
if(DT_IS_BAUHAUS_WIDGET(data))
{
dt_bauhaus_widget_t *widget = DT_BAUHAUS_WIDGET(data);
if(_widget_invisible(GTK_WIDGET(widget))) return TRUE;
darktable.view_manager->current_view->dynamic_accel_current = GTK_WIDGET(widget);
gchar *txt = g_strdup_printf (_("scroll to change %s of module %s %s"),
dt_bauhaus_widget_get_label(GTK_WIDGET(widget)),
widget->module->name(), widget->module->multi_name);
dt_control_hinter_message(darktable.control, txt);
g_free(txt);
}
else
dt_control_hinter_message(darktable.control, "");
return TRUE;
}
static gboolean bauhaus_combobox_next_callback(GtkAccelGroup *accel_group, GObject *acceleratable,
guint keyval, GdkModifierType modifier, gpointer data)
{
GtkWidget *combobox = GTK_WIDGET(data);
if(_widget_invisible(combobox)) return TRUE;
const int currentval = dt_bauhaus_combobox_get(combobox);
const int nextval = currentval + 1 >= dt_bauhaus_combobox_length(combobox) ? 0 : currentval + 1;
dt_bauhaus_combobox_set(combobox, nextval);
dt_accel_widget_toast(combobox);
return TRUE;
}
static gboolean bauhaus_combobox_prev_callback(GtkAccelGroup *accel_group, GObject *acceleratable,
guint keyval, GdkModifierType modifier, gpointer data)
{
GtkWidget *combobox = GTK_WIDGET(data);
if(_widget_invisible(combobox)) return TRUE;
const int currentval = dt_bauhaus_combobox_get(combobox);
const int prevval = currentval - 1 < 0 ? dt_bauhaus_combobox_length(combobox) : currentval - 1;
dt_bauhaus_combobox_set(combobox, prevval);
dt_accel_widget_toast(combobox);
return TRUE;
}
void _accel_connect_actions_iop(dt_iop_module_t *module, const gchar *path,
GtkWidget *w, const gchar *actions[], void *callbacks[])
{
gchar accel_path[256];
dt_accel_path_iop(accel_path, sizeof(accel_path) - 1, module->op, path);
size_t path_len = strlen(accel_path);
accel_path[path_len++] = '/';
for(const char **action = actions; *action; action++, callbacks++)
{
strncpy(accel_path + path_len, *action, sizeof(accel_path) - path_len);
GClosure *closure = g_cclosure_new(G_CALLBACK(*callbacks), (gpointer)w, NULL);
_store_iop_accel_closure(module, accel_path, closure);
}
}
void dt_accel_connect_combobox_iop(dt_iop_module_t *module, const gchar *path, GtkWidget *combobox)
{
assert(DT_IS_BAUHAUS_WIDGET(combobox));
void *combobox_callbacks[]
= { bauhaus_combobox_next_callback,
bauhaus_combobox_prev_callback,
bauhaus_dynamic_callback };
_accel_connect_actions_iop(module, path, combobox, _combobox_actions, combobox_callbacks);
}
void dt_accel_connect_slider_iop(dt_iop_module_t *module, const gchar *path, GtkWidget *slider)
{
assert(DT_IS_BAUHAUS_WIDGET(slider));
void *slider_callbacks[]
= { bauhaus_slider_increase_callback,
bauhaus_slider_decrease_callback,
bauhaus_slider_reset_callback,
bauhaus_slider_edit_callback,
bauhaus_dynamic_callback };
_accel_connect_actions_iop(module, path, slider, _slider_actions, slider_callbacks);
}
void dt_accel_connect_instance_iop(dt_iop_module_t *module)
{
for(GSList *l = module->accel_closures; l; l = g_slist_next(l))
{
_accel_iop_t *stored_accel = (_accel_iop_t *)l->data;
if(stored_accel && stored_accel->accel && stored_accel->closure)
{
if(stored_accel->accel->closure)
gtk_accel_group_disconnect(darktable.control->accelerators, stored_accel->accel->closure);
stored_accel->accel->closure = stored_accel->closure;
gtk_accel_group_connect_by_path(darktable.control->accelerators,
stored_accel->accel->path, stored_accel->closure);
}
}
}
void dt_accel_connect_locals_iop(dt_iop_module_t *module)
{
for(GSList *l = module->accel_closures_local; l; l = g_slist_next(l))
{
_accel_iop_t *accel = (_accel_iop_t *)l->data;
if(accel)
{
gtk_accel_group_connect_by_path(darktable.control->accelerators, accel->accel->path, accel->closure);
}
}
module->local_closures_connected = TRUE;
}
void dt_accel_disconnect_list(GSList **list_ptr)
{
GSList *list = *list_ptr;
while(list)
{
dt_accel_t *accel = (dt_accel_t *)list->data;
if(accel) gtk_accel_group_disconnect(darktable.control->accelerators, accel->closure);
list = g_slist_delete_link(list, list);
}
*list_ptr = NULL;
}
void dt_accel_disconnect_locals_iop(dt_iop_module_t *module)
{
if(!module->local_closures_connected) return;
for(GSList *l = module->accel_closures_local; l; l = g_slist_next(l))
{
_accel_iop_t *accel = (_accel_iop_t *)l->data;
if(accel)
{
gtk_accel_group_disconnect(darktable.control->accelerators, accel->closure);
}
}
module->local_closures_connected = FALSE;
}
void _free_iop_accel(gpointer data)
{
_accel_iop_t *accel = (_accel_iop_t *) data;
if(accel->accel->closure == accel->closure)
{
gtk_accel_group_disconnect(darktable.control->accelerators, accel->closure);
accel->accel->closure = NULL;
}
if(accel->closure->ref_count != 1)
fprintf(stderr, "iop accel refcount %d %s\n", accel->closure->ref_count, accel->accel->path);
g_closure_unref(accel->closure);
g_free(accel);
}
void dt_accel_cleanup_closures_iop(dt_iop_module_t *module)
{
dt_accel_disconnect_locals_iop(module);
g_slist_free_full(module->accel_closures, _free_iop_accel);
g_slist_free_full(module->accel_closures_local, _free_iop_accel);
module->accel_closures = NULL;
module->accel_closures_local = NULL;
}
typedef struct
{
dt_iop_module_t *module;
char *name;
} preset_iop_module_callback_description;
static void preset_iop_module_callback_destroyer(gpointer data, GClosure *closure)
{
preset_iop_module_callback_description *callback_description
= (preset_iop_module_callback_description *)data;
g_free(callback_description->name);
g_free(data);
}
static gboolean preset_iop_module_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
GdkModifierType modifier, gpointer data)
{
preset_iop_module_callback_description *callback_description
= (preset_iop_module_callback_description *)data;
dt_iop_module_t *module = callback_description->module;
const char *name = callback_description->name;
sqlite3_stmt *stmt;
DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT op_params, enabled, blendop_params, "
"blendop_version FROM data.presets "
"WHERE operation = ?1 AND name = ?2",
-1, &stmt, NULL);
DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, module->op, -1, SQLITE_TRANSIENT);
DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, name, -1, SQLITE_TRANSIENT);
if(sqlite3_step(stmt) == SQLITE_ROW)
{
const void *op_params = sqlite3_column_blob(stmt, 0);
int op_length = sqlite3_column_bytes(stmt, 0);
int enabled = sqlite3_column_int(stmt, 1);
const void *blendop_params = sqlite3_column_blob(stmt, 2);
int bl_length = sqlite3_column_bytes(stmt, 2);
int blendop_version = sqlite3_column_int(stmt, 3);
if(op_params && (op_length == module->params_size))
{
memcpy(module->params, op_params, op_length);
module->enabled = enabled;
}
if(blendop_params && (blendop_version == dt_develop_blend_version())
&& (bl_length == sizeof(dt_develop_blend_params_t)))
{
memcpy(module->blend_params, blendop_params, sizeof(dt_develop_blend_params_t));
}
else if(blendop_params
&& dt_develop_blend_legacy_params(module, blendop_params, blendop_version, module->blend_params,
dt_develop_blend_version(), bl_length) == 0)
{
// do nothing
}
else
{
memcpy(module->blend_params, module->default_blendop_params, sizeof(dt_develop_blend_params_t));
}
}
sqlite3_finalize(stmt);
dt_iop_gui_update(module);
dt_dev_add_history_item(darktable.develop, module, FALSE);
gtk_widget_queue_draw(module->widget);
return TRUE;
}
void dt_accel_connect_preset_iop(dt_iop_module_t *module, const gchar *path)
{
char build_path[1024];
gchar *name = g_strdup(path);
snprintf(build_path, sizeof(build_path), "%s`%s", N_("preset"), name);
preset_iop_module_callback_description *callback_description
= g_malloc(sizeof(preset_iop_module_callback_description));
callback_description->module = module;
callback_description->name = name;
GClosure *closure = g_cclosure_new(G_CALLBACK(preset_iop_module_callback), callback_description,
preset_iop_module_callback_destroyer);
dt_accel_connect_iop(module, build_path, closure);
}
typedef struct
{
dt_lib_module_t *module;
char *name;
} preset_lib_module_callback_description;
static void preset_lib_module_callback_destroyer(gpointer data, GClosure *closure)
{
preset_lib_module_callback_description *callback_description
= (preset_lib_module_callback_description *)data;
g_free(callback_description->name);
g_free(data);
}
static gboolean preset_lib_module_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
GdkModifierType modifier, gpointer data)
{
preset_lib_module_callback_description *callback_description
= (preset_lib_module_callback_description *)data;
dt_lib_module_t *module = callback_description->module;
const char *pn = callback_description->name;
sqlite3_stmt *stmt;
DT_DEBUG_SQLITE3_PREPARE_V2(
dt_database_get(darktable.db),
"SELECT op_params FROM data.presets WHERE operation = ?1 AND op_version = ?2 AND name = ?3", -1, &stmt,
NULL);
DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, module->plugin_name, -1, SQLITE_TRANSIENT);
DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, module->version());
DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 3, pn, -1, SQLITE_TRANSIENT);
int res = 0;
if(sqlite3_step(stmt) == SQLITE_ROW)
{
const void *blob = sqlite3_column_blob(stmt, 0);
int length = sqlite3_column_bytes(stmt, 0);
if(blob)
{
for(const GList *it = darktable.lib->plugins; it; it = g_list_next(it))
{
dt_lib_module_t *search_module = (dt_lib_module_t *)it->data;
if(!strncmp(search_module->plugin_name, module->plugin_name, 128))
{
res = module->set_params(module, blob, length);
break;
}
}
}
}
sqlite3_finalize(stmt);
if(res)
{
dt_control_log(_("deleting preset for obsolete module"));
DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
"DELETE FROM data.presets WHERE operation = ?1 AND op_version = ?2 AND name = ?3",
-1, &stmt, NULL);
DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, module->plugin_name, -1, SQLITE_TRANSIENT);
DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, module->version());
DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 3, pn, -1, SQLITE_TRANSIENT);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
return TRUE;
}
void dt_accel_connect_preset_lib(dt_lib_module_t *module, const gchar *path)
{
char build_path[1024];
gchar *name = g_strdup(path);
snprintf(build_path, sizeof(build_path), "%s/%s", _("preset"), name);
preset_lib_module_callback_description *callback_description
= g_malloc(sizeof(preset_lib_module_callback_description));
callback_description->module = module;
callback_description->name = name;
GClosure *closure = g_cclosure_new(G_CALLBACK(preset_lib_module_callback), callback_description,
preset_lib_module_callback_destroyer);
dt_accel_connect_lib(module, build_path, closure);
}
void dt_accel_deregister_iop(dt_iop_module_t *module, const gchar *path)
{
char build_path[1024];
dt_accel_path_iop(build_path, sizeof(build_path), module->op, path);
dt_accel_t *accel = NULL;
for(const GList *modules = darktable.develop->iop; modules; modules = g_list_next(modules))
{
dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
if(mod->so == module->so)
{
GSList **current_list = &mod->accel_closures;
GSList *l = *current_list;
while(l)
{
_accel_iop_t *iop_accel = (_accel_iop_t *)l->data;
if(iop_accel && iop_accel->accel && !strncmp(iop_accel->accel->path, build_path, 1024))
{
accel = iop_accel->accel;
if(iop_accel->closure == accel->closure || (accel->local && module->local_closures_connected))
gtk_accel_group_disconnect(darktable.control->accelerators, iop_accel->closure);
*current_list = g_slist_delete_link(*current_list, l);
g_closure_unref(iop_accel->closure);
g_free(iop_accel);
break;
}
l = g_slist_next(l);
// if we've run out of global accelerators, switch to processing the local accelerators
if(!l && current_list == &mod->accel_closures) l = *(current_list = &module->accel_closures_local);
}
}
}
if(accel)
{
darktable.control->accelerator_list = g_list_remove(darktable.control->accelerator_list, accel);
g_free(accel);
}
}
void dt_accel_deregister_lib(dt_lib_module_t *module, const gchar *path)
{
char build_path[1024];
dt_accel_path_lib(build_path, sizeof(build_path), module->plugin_name, path);
for(GSList *l = module->accel_closures; l; l = g_slist_next(l))
{
dt_accel_t *accel = (dt_accel_t *)l->data;
if(accel && !strncmp(accel->path, build_path, 1024))
{
module->accel_closures = g_slist_delete_link(module->accel_closures, l);
gtk_accel_group_disconnect(darktable.control->accelerators, accel->closure);
break;
}
}
for(GList *ll = darktable.control->accelerator_list; ll; ll = g_list_next(ll))
{
dt_accel_t *accel = (dt_accel_t *)ll->data;
if(accel && !strncmp(accel->path, build_path, 1024))
{
darktable.control->accelerator_list = g_list_delete_link(darktable.control->accelerator_list, ll);
g_free(accel);
break;
}
}
}
void dt_accel_deregister_global(const gchar *path)
{
char build_path[1024];
dt_accel_path_global(build_path, sizeof(build_path), path);
for(GList *l = darktable.control->accelerator_list; l; l = g_list_next(l))
{
dt_accel_t *accel = (dt_accel_t *)l->data;
if(accel && !strncmp(accel->path, build_path, 1024))
{
darktable.control->accelerator_list = g_list_delete_link(darktable.control->accelerator_list, l);
gtk_accel_group_disconnect(darktable.control->accelerators, accel->closure);
g_free(accel);
break;
}
}
}
void dt_accel_deregister_lua(const gchar *path)
{
char build_path[1024];
dt_accel_path_lua(build_path, sizeof(build_path), path);
for(GList *l = darktable.control->accelerator_list; l; l = g_list_next(l))
{
dt_accel_t *accel = (dt_accel_t *)l->data;
if(accel && !strncmp(accel->path, build_path, 1024))
{
darktable.control->accelerator_list = g_list_delete_link(darktable.control->accelerator_list, l);
gtk_accel_group_disconnect(darktable.control->accelerators, accel->closure);
g_free(accel);
break;
}
}
}
void dt_accel_deregister_manual(GSList *list, const gchar *full_path)
{
char build_path[1024];
dt_accel_path_manual(build_path, sizeof(build_path), full_path);
for(GSList *l = list; l; l = g_slist_next(l))
{
dt_accel_t *accel = (dt_accel_t *)l->data;
if(accel && !strncmp(accel->path, build_path, 1024))
{
list = g_slist_delete_link(list, l);
gtk_accel_group_disconnect(darktable.control->accelerators, accel->closure);
break;
}
}
for(GList *ll = darktable.control->accelerator_list; ll; ll = g_list_next(ll))
{
dt_accel_t *accel = (dt_accel_t *)ll->data;
if(accel && !strncmp(accel->path, build_path, 1024))
{
darktable.control->accelerator_list = g_list_delete_link(darktable.control->accelerator_list, ll);
g_free(accel);
break;
}
}
}
gboolean find_accel_internal(GtkAccelKey *key, GClosure *closure, gpointer data)
{
return (closure == data);
}
void dt_accel_rename_preset_iop(dt_iop_module_t *module, const gchar *path, const gchar *new_path)
{
char *path_preset = g_strdup_printf("%s`%s", N_("preset"), path);
char build_path[1024];
dt_accel_path_iop(build_path, sizeof(build_path), module->op, path_preset);
for(GSList *l = module->accel_closures; l; l = g_slist_next(l))
{
_accel_iop_t *iop_accel = (_accel_iop_t *)l->data;
if(iop_accel && iop_accel->accel && !strncmp(iop_accel->accel->path, build_path, 1024))
{
GtkAccelKey tmp_key
= *(gtk_accel_group_find(darktable.control->accelerators, find_accel_internal, iop_accel->closure));
gboolean local = iop_accel->accel->local;
dt_accel_deregister_iop(module, path_preset);
snprintf(build_path, sizeof(build_path), "%s`%s", N_("preset"), new_path);
dt_accel_register_iop(module->so, local, build_path, tmp_key.accel_key, tmp_key.accel_mods);
for(const GList *modules = darktable.develop->iop; modules; modules = g_list_next(modules))
{
dt_iop_module_t *mod = (dt_iop_module_t *)modules->data;
if(mod->so == module->so)
dt_accel_connect_preset_iop(mod, new_path);
}
break;
}
}
g_free(path_preset);
dt_accel_connect_instance_iop(module);
}
void dt_accel_rename_preset_lib(dt_lib_module_t *module, const gchar *path, const gchar *new_path)
{
char build_path[1024];
dt_accel_path_lib(build_path, sizeof(build_path), module->plugin_name, path);
for(GSList *l = module->accel_closures; l; l = g_slist_next(l))
{
dt_accel_t *accel = (dt_accel_t *)l->data;
if(accel && !strncmp(accel->path, build_path, 1024))
{
GtkAccelKey tmp_key
= *(gtk_accel_group_find(darktable.control->accelerators, find_accel_internal, accel->closure));
dt_accel_deregister_lib(module, path);
snprintf(build_path, sizeof(build_path), "%s/%s", _("preset"), new_path);
dt_accel_register_lib(module, build_path, tmp_key.accel_key, tmp_key.accel_mods);
dt_accel_connect_preset_lib(module, new_path);
break;
}
}
}
void dt_accel_rename_global(const gchar *path, const gchar *new_path)
{
char build_path[1024];
dt_accel_path_global(build_path, sizeof(build_path), path);
for(GList *l = darktable.control->accelerator_list; l; l = g_list_next(l))
{
dt_accel_t *accel = (dt_accel_t *)l->data;
if(accel && !strncmp(accel->path, build_path, 1024))
{
GtkAccelKey tmp_key
= *(gtk_accel_group_find(darktable.control->accelerators, find_accel_internal, accel->closure));
GClosure* closure = g_closure_ref(accel->closure);
dt_accel_deregister_global(path);
dt_accel_register_global(new_path, tmp_key.accel_key, tmp_key.accel_mods);
dt_accel_connect_global(new_path, closure);
g_closure_unref(closure);
break;
}
}
}
void dt_accel_rename_lua(const gchar *path, const gchar *new_path)
{
char build_path[1024];
dt_accel_path_lua(build_path, sizeof(build_path), path);
for(GList *l = darktable.control->accelerator_list; l; l = g_list_next(l))
{
dt_accel_t *accel = (dt_accel_t *)l->data;
if(accel && !strncmp(accel->path, build_path, 1024))
{
GtkAccelKey tmp_key
= *(gtk_accel_group_find(darktable.control->accelerators, find_accel_internal, accel->closure));
GClosure* closure = g_closure_ref(accel->closure);
dt_accel_deregister_lua(path);
dt_accel_register_lua(new_path, tmp_key.accel_key, tmp_key.accel_mods);
dt_accel_connect_lua(new_path, closure);
g_closure_unref(closure);
break;
}
}
}
dt_accel_t *dt_accel_find_by_path(const gchar *path)
{
return _lookup_accel(path);
}
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;