Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
1708 lines (1480 sloc) 59.4 KB
/*
This file is part of darktable,
copyright (c) 2009--2010 johannes hanika.
copyright (c) 2011-2014 henrik andersson.
copyright (c) 2012 tobias ellinghaus.
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 <http://www.gnu.org/licenses/>.
*/
#include "views/view.h"
#include "bauhaus/bauhaus.h"
#include "common/collection.h"
#include "common/darktable.h"
#include "common/debug.h"
#include "common/history.h"
#include "common/image_cache.h"
#include "common/mipmap_cache.h"
#include "common/module.h"
#include "common/undo.h"
#include "control/conf.h"
#include "control/control.h"
#include "develop/develop.h"
#include "dtgtk/expander.h"
#include "gui/accelerators.h"
#include "gui/gtk.h"
#include "libs/lib.h"
#ifdef GDK_WINDOWING_QUARTZ
#include "osx/osx.h"
#endif
#include <glib.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#define DECORATION_SIZE_LIMIT 40
static void dt_view_manager_load_modules(dt_view_manager_t *vm);
static int dt_view_load_module(void *v, const char *libname, const char *module_name);
static void dt_view_unload_module(dt_view_t *view);
void dt_view_manager_init(dt_view_manager_t *vm)
{
/* prepare statements */
DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT imgid FROM main.selected_images "
"WHERE imgid = ?1", -1, &vm->statements.is_selected, NULL);
DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "DELETE FROM main.selected_images WHERE imgid = ?1",
-1, &vm->statements.delete_from_selected, NULL);
DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
"INSERT OR IGNORE INTO main.selected_images VALUES (?1)", -1,
&vm->statements.make_selected, NULL);
DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT num FROM main.history WHERE imgid = ?1", -1,
&vm->statements.have_history, NULL);
DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT color FROM main.color_labels WHERE imgid=?1",
-1, &vm->statements.get_color, NULL);
DT_DEBUG_SQLITE3_PREPARE_V2(
dt_database_get(darktable.db),
"SELECT id FROM main.images WHERE group_id = (SELECT group_id FROM main.images WHERE id=?1) AND id != ?2",
-1, &vm->statements.get_grouped, NULL);
dt_view_manager_load_modules(vm);
// Modules loaded, let's handle specific cases
for(GList *iter = vm->views; iter; iter = g_list_next(iter))
{
dt_view_t *view = (dt_view_t *)iter->data;
if(!strcmp(view->module_name, "darkroom"))
{
darktable.develop = (dt_develop_t *)view->data;
break;
}
}
vm->current_view = NULL;
}
void dt_view_manager_gui_init(dt_view_manager_t *vm)
{
for(GList *iter = vm->views; iter; iter = g_list_next(iter))
{
dt_view_t *view = (dt_view_t *)iter->data;
if(view->gui_init) view->gui_init(view);
}
}
void dt_view_manager_cleanup(dt_view_manager_t *vm)
{
for(GList *iter = vm->views; iter; iter = g_list_next(iter)) dt_view_unload_module((dt_view_t *)iter->data);
}
const dt_view_t *dt_view_manager_get_current_view(dt_view_manager_t *vm)
{
return vm->current_view;
}
// we want a stable order of views, for example for viewswitcher.
// anything not hardcoded will be put alphabetically wrt. localised names.
static gint sort_views(gconstpointer a, gconstpointer b)
{
static const char *view_order[] = {"lighttable", "darkroom"};
static const int n_view_order = G_N_ELEMENTS(view_order);
dt_view_t *av = (dt_view_t *)a;
dt_view_t *bv = (dt_view_t *)b;
const char *aname = av->name(av);
const char *bname = bv->name(bv);
int apos = n_view_order;
int bpos = n_view_order;
for(int i = 0; i < n_view_order; i++)
{
if(!strcmp(av->module_name, view_order[i])) apos = i;
if(!strcmp(bv->module_name, view_order[i])) bpos = i;
}
// order will be zero iff apos == bpos which can only happen when both views are not in view_order
const int order = apos - bpos;
return order ? order : strcmp(aname, bname);
}
static void dt_view_manager_load_modules(dt_view_manager_t *vm)
{
vm->views = dt_module_load_modules("/views", sizeof(dt_view_t), dt_view_load_module, NULL, sort_views);
}
/* default flags for view which does not implement the flags() function */
static uint32_t default_flags()
{
return 0;
}
/** load a view module */
static int dt_view_load_module(void *v, const char *libname, const char *module_name)
{
dt_view_t *view = (dt_view_t *)v;
view->data = NULL;
view->vscroll_size = view->vscroll_viewport_size = 1.0;
view->hscroll_size = view->hscroll_viewport_size = 1.0;
view->vscroll_pos = view->hscroll_pos = 0.0;
view->height = view->width = 100; // set to non-insane defaults before first expose/configure.
g_strlcpy(view->module_name, module_name, sizeof(view->module_name));
dt_print(DT_DEBUG_CONTROL, "[view_load_module] loading view `%s' from %s\n", module_name, libname);
view->module = g_module_open(libname, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
if(!view->module)
{
fprintf(stderr, "[view_load_module] could not open %s (%s)!\n", libname, g_module_error());
goto error;
}
int (*version)();
if(!g_module_symbol(view->module, "dt_module_dt_version", (gpointer) & (version))) goto error;
if(version() != dt_version())
{
fprintf(stderr, "[view_load_module] `%s' is compiled for another version of dt (module %d != dt %d) !\n",
libname, version(), dt_version());
goto error;
}
if(!g_module_symbol(view->module, "name", (gpointer) & (view->name))) view->name = NULL;
if(!g_module_symbol(view->module, "view", (gpointer) & (view->view))) view->view = NULL;
if(!g_module_symbol(view->module, "flags", (gpointer) & (view->flags))) view->flags = default_flags;
if(!g_module_symbol(view->module, "init", (gpointer) & (view->init))) view->init = NULL;
if(!g_module_symbol(view->module, "gui_init", (gpointer) & (view->gui_init))) view->gui_init = NULL;
if(!g_module_symbol(view->module, "cleanup", (gpointer) & (view->cleanup))) view->cleanup = NULL;
if(!g_module_symbol(view->module, "expose", (gpointer) & (view->expose))) view->expose = NULL;
if(!g_module_symbol(view->module, "try_enter", (gpointer) & (view->try_enter))) view->try_enter = NULL;
if(!g_module_symbol(view->module, "enter", (gpointer) & (view->enter))) view->enter = NULL;
if(!g_module_symbol(view->module, "leave", (gpointer) & (view->leave))) view->leave = NULL;
if(!g_module_symbol(view->module, "reset", (gpointer) & (view->reset))) view->reset = NULL;
if(!g_module_symbol(view->module, "mouse_enter", (gpointer) & (view->mouse_enter)))
view->mouse_enter = NULL;
if(!g_module_symbol(view->module, "mouse_leave", (gpointer) & (view->mouse_leave)))
view->mouse_leave = NULL;
if(!g_module_symbol(view->module, "mouse_moved", (gpointer) & (view->mouse_moved)))
view->mouse_moved = NULL;
if(!g_module_symbol(view->module, "button_released", (gpointer) & (view->button_released)))
view->button_released = NULL;
if(!g_module_symbol(view->module, "button_pressed", (gpointer) & (view->button_pressed)))
view->button_pressed = NULL;
if(!g_module_symbol(view->module, "key_pressed", (gpointer) & (view->key_pressed)))
view->key_pressed = NULL;
if(!g_module_symbol(view->module, "key_released", (gpointer) & (view->key_released)))
view->key_released = NULL;
if(!g_module_symbol(view->module, "configure", (gpointer) & (view->configure))) view->configure = NULL;
if(!g_module_symbol(view->module, "scrolled", (gpointer) & (view->scrolled))) view->scrolled = NULL;
if(!g_module_symbol(view->module, "init_key_accels", (gpointer) & (view->init_key_accels)))
view->init_key_accels = NULL;
if(!g_module_symbol(view->module, "connect_key_accels", (gpointer) & (view->connect_key_accels)))
view->connect_key_accels = NULL;
view->accel_closures = NULL;
if(!strcmp(view->module_name, "darkroom")) darktable.develop = (dt_develop_t *)view->data;
#ifdef USE_LUA
dt_lua_register_view(darktable.lua_state.state, view);
#endif
if(view->init) view->init(view);
if(darktable.gui && view->init_key_accels) view->init_key_accels(view);
return 0;
error:
if(view->module) g_module_close(view->module);
return 1;
}
/** unload, cleanup */
static void dt_view_unload_module(dt_view_t *view)
{
if(view->cleanup) view->cleanup(view);
g_slist_free(view->accel_closures);
if(view->module) g_module_close(view->module);
}
void dt_vm_remove_child(GtkWidget *widget, gpointer data)
{
gtk_container_remove(GTK_CONTAINER(data), widget);
}
/*
When expanders get destoyed, they destroy the child
so remove the child before that
*/
static void _remove_child(GtkWidget *child,GtkContainer *container)
{
if(DTGTK_IS_EXPANDER(child))
{
GtkWidget * evb = dtgtk_expander_get_body_event_box(DTGTK_EXPANDER(child));
gtk_container_remove(GTK_CONTAINER(evb),dtgtk_expander_get_body(DTGTK_EXPANDER(child)));
gtk_widget_destroy(child);
}
else
{
gtk_container_remove(container,child);
}
}
static void bitness_nagging()
{
const int bits = (sizeof(void *) == 4) ? 32 : 64;
if((bits < 64) && !dt_conf_get_bool("please_let_me_suffer_by_using_32bit_darktable"))
{
fprintf(stderr, "warning: 32-bit build!\n");
GtkWidget *dialog, *content_area;
GtkDialogFlags flags;
// Create the widgets
flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
dialog = gtk_dialog_new_with_buttons(
_("you are making a mistake!"), GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)), flags,
_("_yes, i understood. please let me suffer by using 32-bit darktable."), GTK_RESPONSE_NONE,
NULL);
#ifdef GDK_WINDOWING_QUARTZ
dt_osx_disallow_fullscreen(dialog);
#endif
content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
const gchar *msg = _("warning!\nyou are using a 32-bit build of darktable.\nthe 32-bit build has "
"severely limited virtual address space.\nwe have had numerous reports that "
"darktable exhibits sporadic issues and crashes when using 32-bit builds.\nwe "
"strongly recommend you switch to a proper 64-bit build.\notherwise, you are "
"GUARANTEED to experience issues which cannot be fixed.\n");
gtk_container_add(GTK_CONTAINER(content_area), gtk_label_new(msg));
gtk_widget_show_all(dialog);
(void)gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
}
}
int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
{
gboolean switching_to_none = *view_name == '\0';
dt_view_t *new_view = NULL;
if(!switching_to_none)
{
for(GList *iter = vm->views; iter; iter = g_list_next(iter))
{
dt_view_t *v = (dt_view_t *)iter->data;
if(!strcmp(v->module_name, view_name))
{
new_view = v;
break;
}
}
if(!new_view) return 1; // the requested view doesn't exist
}
return dt_view_manager_switch_by_view(vm, new_view);
}
int dt_view_manager_switch_by_view(dt_view_manager_t *vm, const dt_view_t *nv)
{
dt_view_t *old_view = vm->current_view;
dt_view_t *new_view = (dt_view_t *)nv; // views belong to us, we can de-const them :-)
// Before switching views, restore accelerators if disabled
if(!darktable.control->key_accelerators_on) dt_control_key_accelerators_on(darktable.control);
// reset the cursor to the default one
dt_control_change_cursor(GDK_LEFT_PTR);
// also ignore what scrolling there was previously happening
memset(darktable.gui->scroll_to, 0, sizeof(darktable.gui->scroll_to));
// destroy old module list
/* clear the undo list, for now we do this inconditionally. At some point we will probably want to clear
only part
of the undo list. This should probably done with a view proxy routine returning the type of undo to
remove. */
dt_undo_clear(darktable.undo, DT_UNDO_ALL);
/* Special case when entering nothing (just before leaving dt) */
if(!new_view)
{
if(old_view)
{
/* leave the current view*/
if(old_view->leave) old_view->leave(old_view);
/* iterator plugins and cleanup plugins in current view */
for(GList *iter = darktable.lib->plugins; iter; iter = g_list_next(iter))
{
dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
/* does this module belong to current view ?*/
if(dt_lib_is_visible_in_view(plugin, old_view))
{
if(plugin->view_leave) plugin->view_leave(plugin, old_view, NULL);
plugin->gui_cleanup(plugin);
plugin->data = NULL;
dt_accel_disconnect_list(plugin->accel_closures);
plugin->accel_closures = NULL;
plugin->widget = NULL;
}
}
}
/* remove all widgets in all containers */
for(int l = 0; l < DT_UI_CONTAINER_SIZE; l++)
dt_ui_container_destroy_children(darktable.gui->ui, l);
vm->current_view = NULL;
return 0;
}
// invariant: new_view != NULL after this point
assert(new_view != NULL);
if(new_view->try_enter)
{
int error = new_view->try_enter(new_view);
if(error) return error;
}
// annoy the users that are still on 32 bit systems!
bitness_nagging();
/* cleanup current view before initialization of new */
if(old_view)
{
/* leave current view */
if(old_view->leave) old_view->leave(old_view);
dt_accel_disconnect_list(old_view->accel_closures);
old_view->accel_closures = NULL;
/* iterator plugins and cleanup plugins in current view */
for(GList *iter = darktable.lib->plugins; iter; iter = g_list_next(iter))
{
dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
/* does this module belong to current view ?*/
if(dt_lib_is_visible_in_view(plugin, old_view))
{
if(plugin->view_leave) plugin->view_leave(plugin, old_view, new_view);
dt_accel_disconnect_list(plugin->accel_closures);
plugin->accel_closures = NULL;
}
}
/* remove all widets in all containers */
for(int l = 0; l < DT_UI_CONTAINER_SIZE; l++)
dt_ui_container_foreach(darktable.gui->ui, l,(GtkCallback)_remove_child);
}
/* change current view to the new view */
vm->current_view = new_view;
/* restore visible stat of panels for the new view */
dt_ui_restore_panels(darktable.gui->ui);
/* lets add plugins related to new view into panels.
* this has to be done in reverse order to have the lowest position at the bottom! */
for(GList *iter = g_list_last(darktable.lib->plugins); iter; iter = g_list_previous(iter))
{
dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
if(dt_lib_is_visible_in_view(plugin, new_view))
{
/* try get the module expander */
GtkWidget *w = dt_lib_gui_get_expander(plugin);
if(plugin->connect_key_accels) plugin->connect_key_accels(plugin);
dt_lib_connect_common_accels(plugin);
/* if we didn't get an expander let's add the widget */
if(!w) w = plugin->widget;
/* add module to its container */
dt_ui_container_add_widget(darktable.gui->ui, plugin->container(plugin), w);
}
}
/* hide/show modules as last config */
for(GList *iter = darktable.lib->plugins; iter; iter = g_list_next(iter))
{
dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
if(dt_lib_is_visible_in_view(plugin, new_view))
{
/* set expanded if last mode was that */
char var[1024];
gboolean expanded = FALSE;
gboolean visible = dt_lib_is_visible(plugin);
if(plugin->expandable(plugin))
{
snprintf(var, sizeof(var), "plugins/%s/%s/expanded", new_view->module_name, plugin->plugin_name);
expanded = dt_conf_get_bool(var);
dt_lib_gui_set_expanded(plugin, expanded);
}
else
{
/* show/hide plugin widget depending on expanded flag or if plugin
not is expandeable() */
if(visible)
gtk_widget_show_all(plugin->widget);
else
gtk_widget_hide(plugin->widget);
}
if(plugin->view_enter) plugin->view_enter(plugin, old_view, new_view);
}
}
/* enter view. crucially, do this before initing the plugins below,
as e.g. modulegroups requires the dr stuff to be inited. */
if(new_view->enter) new_view->enter(new_view);
if(new_view->connect_key_accels) new_view->connect_key_accels(new_view);
/* raise view changed signal */
dt_control_signal_raise(darktable.signals, DT_SIGNAL_VIEWMANAGER_VIEW_CHANGED, old_view, new_view);
/* add endmarkers to left and right center containers */
GtkWidget *endmarker = gtk_drawing_area_new();
dt_ui_container_add_widget(darktable.gui->ui, DT_UI_CONTAINER_PANEL_LEFT_CENTER, endmarker);
g_signal_connect(G_OBJECT(endmarker), "draw", G_CALLBACK(dt_control_draw_endmarker), 0);
gtk_widget_set_size_request(endmarker, -1, DT_PIXEL_APPLY_DPI(50));
gtk_widget_show(endmarker);
endmarker = gtk_drawing_area_new();
dt_ui_container_add_widget(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER, endmarker);
g_signal_connect(G_OBJECT(endmarker), "draw", G_CALLBACK(dt_control_draw_endmarker), GINT_TO_POINTER(1));
gtk_widget_set_size_request(endmarker, -1, DT_PIXEL_APPLY_DPI(50));
gtk_widget_show(endmarker);
return 0;
}
const char *dt_view_manager_name(dt_view_manager_t *vm)
{
if(!vm->current_view) return "";
if(vm->current_view->name)
return vm->current_view->name(vm->current_view);
else
return vm->current_view->module_name;
}
void dt_view_manager_expose(dt_view_manager_t *vm, cairo_t *cr, int32_t width, int32_t height,
int32_t pointerx, int32_t pointery)
{
if(!vm->current_view)
{
dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_BG);
cairo_paint(cr);
return;
}
vm->current_view->width = width;
vm->current_view->height = height;
if(vm->current_view->expose)
{
/* expose the view */
cairo_rectangle(cr, 0, 0, vm->current_view->width, vm->current_view->height);
cairo_clip(cr);
cairo_new_path(cr);
cairo_save(cr);
float px = pointerx, py = pointery;
if(pointery > vm->current_view->height)
{
px = 10000.0;
py = -1.0;
}
vm->current_view->expose(vm->current_view, cr, vm->current_view->width, vm->current_view->height, px, py);
cairo_restore(cr);
/* expose plugins */
GList *plugins = g_list_last(darktable.lib->plugins);
while(plugins)
{
dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
/* does this module belong to current view ?*/
if(plugin->gui_post_expose && dt_lib_is_visible_in_view(plugin, vm->current_view))
plugin->gui_post_expose(plugin, cr, vm->current_view->width, vm->current_view->height, px, py);
/* get next plugin */
plugins = g_list_previous(plugins);
}
}
}
void dt_view_manager_reset(dt_view_manager_t *vm)
{
if(!vm->current_view) return;
if(vm->current_view->reset) vm->current_view->reset(vm->current_view);
}
void dt_view_manager_mouse_leave(dt_view_manager_t *vm)
{
if(!vm->current_view) return;
if(vm->current_view->mouse_leave) vm->current_view->mouse_leave(vm->current_view);
}
void dt_view_manager_mouse_enter(dt_view_manager_t *vm)
{
if(!vm->current_view) return;
if(vm->current_view->mouse_enter) vm->current_view->mouse_enter(vm->current_view);
}
void dt_view_manager_mouse_moved(dt_view_manager_t *vm, double x, double y, double pressure, int which)
{
if(!vm->current_view) return;
dt_view_t *v = vm->current_view;
/* lets check if any plugins want to handle mouse move */
gboolean handled = FALSE;
GList *plugins = g_list_last(darktable.lib->plugins);
while(plugins)
{
dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
/* does this module belong to current view ?*/
if(plugin->mouse_moved && dt_lib_is_visible_in_view(plugin, v))
if(plugin->mouse_moved(plugin, x, y, pressure, which)) handled = TRUE;
/* get next plugin */
plugins = g_list_previous(plugins);
}
/* if not handled by any plugin let pass to view handler*/
if(!handled && v->mouse_moved) v->mouse_moved(v, x, y, pressure, which);
}
int dt_view_manager_button_released(dt_view_manager_t *vm, double x, double y, int which, uint32_t state)
{
if(!vm->current_view) return 0;
dt_view_t *v = vm->current_view;
/* lets check if any plugins want to handle button press */
gboolean handled = FALSE;
GList *plugins = g_list_last(darktable.lib->plugins);
while(plugins)
{
dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
/* does this module belong to current view ?*/
if(plugin->button_released && dt_lib_is_visible_in_view(plugin, v))
if(plugin->button_released(plugin, x, y, which, state)) handled = TRUE;
/* get next plugin */
plugins = g_list_previous(plugins);
}
/* if not handled by any plugin let pass to view handler*/
if(!handled && v->button_released) v->button_released(v, x, y, which, state);
return 0;
}
int dt_view_manager_button_pressed(dt_view_manager_t *vm, double x, double y, double pressure, int which,
int type, uint32_t state)
{
if(!vm->current_view) return 0;
dt_view_t *v = vm->current_view;
/* lets check if any plugins want to handle button press */
gboolean handled = FALSE;
GList *plugins = g_list_last(darktable.lib->plugins);
while(plugins && !handled)
{
dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
/* does this module belong to current view ?*/
if(plugin->button_pressed && dt_lib_is_visible_in_view(plugin, v))
if(plugin->button_pressed(plugin, x, y, pressure, which, type, state)) handled = TRUE;
/* get next plugin */
plugins = g_list_previous(plugins);
}
/* if not handled by any plugin let pass to view handler*/
if(!handled && v->button_pressed) return v->button_pressed(v, x, y, pressure, which, type, state);
return 0;
}
int dt_view_manager_key_pressed(dt_view_manager_t *vm, guint key, guint state)
{
// ↑ ↑ ↓ ↓ ← → ← → b a
static int konami_state = 0;
static guint konami_sequence[] = {
GDK_KEY_Up,
GDK_KEY_Up,
GDK_KEY_Down,
GDK_KEY_Down,
GDK_KEY_Left,
GDK_KEY_Right,
GDK_KEY_Left,
GDK_KEY_Right,
GDK_KEY_b,
GDK_KEY_a
};
if(key == konami_sequence[konami_state])
{
konami_state++;
if(konami_state == G_N_ELEMENTS(konami_sequence))
{
dt_ctl_switch_mode_to("knight");
konami_state = 0;
}
}
else
konami_state = 0;
int film_strip_result = 0;
if(!vm->current_view) return 0;
if(vm->current_view->key_pressed)
return vm->current_view->key_pressed(vm->current_view, key, state) || film_strip_result;
return 0;
}
int dt_view_manager_key_released(dt_view_manager_t *vm, guint key, guint state)
{
int film_strip_result = 0;
if(!vm->current_view) return 0;
if(vm->current_view->key_released)
return vm->current_view->key_released(vm->current_view, key, state) || film_strip_result;
return 0;
}
void dt_view_manager_configure(dt_view_manager_t *vm, int width, int height)
{
for(GList *iter = vm->views; iter; iter = g_list_next(iter))
{
// this is necessary for all
dt_view_t *v = (dt_view_t *)iter->data;
v->width = width;
v->height = height;
if(v->configure) v->configure(v, width, height);
}
}
void dt_view_manager_scrolled(dt_view_manager_t *vm, double x, double y, int up, int state)
{
if(!vm->current_view) return;
if(vm->current_view->scrolled) vm->current_view->scrolled(vm->current_view, x, y, up, state);
}
void dt_view_set_scrollbar(dt_view_t *view, float hpos, float hsize, float hwinsize, float vpos, float vsize,
float vwinsize)
{
view->vscroll_pos = vpos;
view->vscroll_size = vsize;
view->vscroll_viewport_size = vwinsize;
view->hscroll_pos = hpos;
view->hscroll_size = hsize;
view->hscroll_viewport_size = hwinsize;
GtkWidget *widget;
widget = darktable.gui->widgets.left_border;
gtk_widget_queue_draw(widget);
widget = darktable.gui->widgets.right_border;
gtk_widget_queue_draw(widget);
widget = darktable.gui->widgets.bottom_border;
gtk_widget_queue_draw(widget);
widget = darktable.gui->widgets.top_border;
gtk_widget_queue_draw(widget);
}
static inline void dt_view_draw_altered(cairo_t *cr, const float x, const float y, const float r)
{
cairo_new_sub_path(cr);
cairo_arc(cr, x, y, r, 0, 2.0f * M_PI);
const float dx = r * cosf(M_PI / 8.0f), dy = r * sinf(M_PI / 8.0f);
cairo_move_to(cr, x - dx, y - dy);
cairo_curve_to(cr, x, y - 2 * dy, x, y + 2 * dy, x + dx, y + dy);
cairo_move_to(cr, x - .20 * dx, y + .8 * dy);
cairo_line_to(cr, x - .80 * dx, y + .8 * dy);
cairo_move_to(cr, x + .20 * dx, y - .8 * dy);
cairo_line_to(cr, x + .80 * dx, y - .8 * dy);
cairo_move_to(cr, x + .50 * dx, y - .8 * dy - 0.3 * dx);
cairo_line_to(cr, x + .50 * dx, y - .8 * dy + 0.3 * dx);
cairo_stroke(cr);
}
static inline void dt_view_draw_audio(cairo_t *cr, const float x, const float y, const float r)
{
const float d = 2.0 * r;
cairo_save(cr);
cairo_translate(cr, x - (d / 2.0), y - (d / 2.0));
cairo_scale(cr, d, d);
cairo_rectangle(cr, 0.05, 0.4, 0.2, 0.2);
cairo_move_to(cr, 0.25, 0.6);
cairo_line_to(cr, 0.45, 0.77);
cairo_line_to(cr, 0.45, 0.23);
cairo_line_to(cr, 0.25, 0.4);
cairo_new_sub_path(cr);
cairo_arc(cr, 0.2, 0.5, 0.45, -(35.0 / 180.0) * M_PI, (35.0 / 180.0) * M_PI);
cairo_new_sub_path(cr);
cairo_arc(cr, 0.2, 0.5, 0.6, -(35.0 / 180.0) * M_PI, (35.0 / 180.0) * M_PI);
cairo_new_sub_path(cr);
cairo_arc(cr, 0.2, 0.5, 0.75, -(35.0 / 180.0) * M_PI, (35.0 / 180.0) * M_PI);
cairo_restore(cr);
cairo_stroke(cr);
}
static inline void dt_view_star(cairo_t *cr, float x, float y, float r1, float r2)
{
const float d = 2.0 * M_PI * 0.1f;
const float dx[10] = { sinf(0.0), sinf(d), sinf(2 * d), sinf(3 * d), sinf(4 * d),
sinf(5 * d), sinf(6 * d), sinf(7 * d), sinf(8 * d), sinf(9 * d) };
const float dy[10] = { cosf(0.0), cosf(d), cosf(2 * d), cosf(3 * d), cosf(4 * d),
cosf(5 * d), cosf(6 * d), cosf(7 * d), cosf(8 * d), cosf(9 * d) };
cairo_move_to(cr, x + r1 * dx[0], y - r1 * dy[0]);
for(int k = 1; k < 10; k++)
if(k & 1)
cairo_line_to(cr, x + r2 * dx[k], y - r2 * dy[k]);
else
cairo_line_to(cr, x + r1 * dx[k], y - r1 * dy[k]);
cairo_close_path(cr);
}
int32_t dt_view_get_image_to_act_on()
{
// this works as follows:
// - if mouse hovers over an image, that's the one, except:
// - if images are selected and the mouse hovers over the selection,
// in which case it affects the whole selection.
// - if the mouse is outside the center view (or no image hovered over otherwise)
// it only affects the selection.
const int32_t mouse_over_id = dt_control_get_mouse_over_id();
const int zoom = darktable.view_manager->proxy.lighttable.get_images_in_row(
darktable.view_manager->proxy.lighttable.view);
const int full_preview_id = darktable.view_manager->proxy.lighttable.get_full_preview_id(
darktable.view_manager->proxy.lighttable.view);
if(zoom == 1 || full_preview_id > 1)
{
return mouse_over_id;
}
else
{
/* clear and reset statement */
DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.is_selected);
DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.is_selected);
/* setup statement and iterate over rows */
DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.is_selected, 1, mouse_over_id);
if(mouse_over_id <= 0 || sqlite3_step(darktable.view_manager->statements.is_selected) == SQLITE_ROW)
return -1;
else
return mouse_over_id;
}
}
int dt_view_image_expose(dt_view_image_over_t *image_over, uint32_t imgid, cairo_t *cr, int32_t width,
int32_t height, int32_t zoom, int32_t px, int32_t py, gboolean full_preview, gboolean image_only)
{
int missing = 0;
const double start = dt_get_wtime();
// some performance tuning stuff, for your pleasure.
// on my machine with 7 image per row it seems grouping has the largest
// impact from around 400ms -> 55ms per redraw.
// active if zoom>1 or in the proper area
const gboolean in_metadata_zone = (px < width && py < height / 2) || (zoom > 1);
const gboolean draw_thumb = TRUE;
const gboolean draw_colorlabels = !image_only && (darktable.gui->show_overlays || in_metadata_zone);
const gboolean draw_local_copy = !image_only && (darktable.gui->show_overlays || in_metadata_zone);
const gboolean draw_grouping = !image_only;
const gboolean draw_selected = !image_only;
const gboolean draw_history = !image_only;
const gboolean draw_metadata = !image_only && (darktable.gui->show_overlays || in_metadata_zone);
const gboolean draw_audio = !image_only;
cairo_save(cr);
float bgcol = 0.4, fontcol = 0.425, bordercol = 0.1, outlinecol = 0.2;
int selected = 0, is_grouped = 0;
// this is a gui thread only thing. no mutex required:
const int imgsel = dt_control_get_mouse_over_id(); // darktable.control->global_settings.lib_image_mouse_over_id;
if (draw_selected)
{
/* clear and reset statements */
DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.is_selected);
DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.is_selected);
/* bind imgid to prepared statements */
DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.is_selected, 1, imgid);
/* lets check if imgid is selected */
if(sqlite3_step(darktable.view_manager->statements.is_selected) == SQLITE_ROW) selected = 1;
}
dt_image_t buffered_image;
const dt_image_t *img;
// if darktable.gui->show_overlays is set or the user points at this image, we really want it:
if(darktable.gui->show_overlays || imgsel == imgid || zoom == 1)
img = dt_image_cache_get(darktable.image_cache, imgid, 'r');
else
img = dt_image_cache_testget(darktable.image_cache, imgid, 'r');
if(selected == 1 && zoom != 1) // If zoom == 1 there is no need to set colors here
{
outlinecol = 0.4;
bgcol = 0.6;
fontcol = 0.5;
}
if(imgsel == imgid || zoom == 1)
{
bgcol = 0.8; // mouse over
fontcol = 0.7;
outlinecol = 0.6;
}
// release image cache lock as early as possible, to avoid deadlocks (mipmap cache might need to lock it, too)
if(img)
{
buffered_image = *img;
dt_image_cache_read_release(darktable.image_cache, img);
img = &buffered_image;
}
float imgwd = 0.90f;
if (image_only)
{
imgwd = 1.0;
}
else if(zoom == 1)
{
imgwd = .97f;
// cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
}
else
{
double x0 = DT_PIXEL_APPLY_DPI(1), y0 = DT_PIXEL_APPLY_DPI(1), rect_width = width - DT_PIXEL_APPLY_DPI(2),
rect_height = height - DT_PIXEL_APPLY_DPI(2), radius = DT_PIXEL_APPLY_DPI(5);
double x1, y1, off, off1;
x1 = x0 + rect_width;
y1 = y0 + rect_height;
off = radius * 0.666;
off1 = radius - off;
cairo_move_to(cr, x0, y0 + radius);
cairo_curve_to(cr, x0, y0 + off1, x0 + off1, y0, x0 + radius, y0);
cairo_line_to(cr, x1 - radius, y0);
cairo_curve_to(cr, x1 - off1, y0, x1, y0 + off1, x1, y0 + radius);
cairo_line_to(cr, x1, y1 - radius);
cairo_curve_to(cr, x1, y1 - off1, x1 - off1, y1, x1 - radius, y1);
cairo_line_to(cr, x0 + radius, y1);
cairo_curve_to(cr, x0 + off1, y1, x0, y1 - off1, x0, y1 - radius);
cairo_close_path(cr);
cairo_set_source_rgb(cr, bgcol, bgcol, bgcol);
cairo_fill_preserve(cr);
cairo_set_line_width(cr, 0.005 * width);
cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol);
cairo_stroke(cr);
if(img)
{
PangoLayout *layout;
PangoRectangle ink;
PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
const int fontsize = 0.25 * width;
pango_font_description_set_absolute_size(desc, fontsize * PANGO_SCALE);
layout = pango_cairo_create_layout(cr);
pango_layout_set_font_description(layout, desc);
const char *ext = img->filename + strlen(img->filename);
while(ext > img->filename && *ext != '.') ext--;
ext++;
cairo_set_source_rgb(cr, fontcol, fontcol, fontcol);
pango_layout_set_text(layout, ext, -1);
pango_layout_get_pixel_extents(layout, &ink, NULL);
cairo_move_to(cr, .025 * width - ink.x, .24 * height - fontsize);
pango_cairo_show_layout(cr, layout);
pango_font_description_free(desc);
g_object_unref(layout);
}
}
dt_mipmap_buffer_t buf;
dt_mipmap_size_t mip
= dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, imgwd * width, imgwd * height);
dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, mip, DT_MIPMAP_BEST_EFFORT, 'r');
// if we got a different mip than requested, and it's not a skull (8x8 px), we count
// this thumbnail as missing (to trigger re-exposure)
if(buf.size != mip && buf.width != 8 && buf.height != 8) missing = 1;
if (draw_thumb)
{
float scale = 1.0;
cairo_surface_t *surface = NULL;
uint8_t *rgbbuf = NULL;
if(buf.buf)
{
rgbbuf = (uint8_t *)calloc(buf.width * buf.height * 4, sizeof(uint8_t));
if(rgbbuf)
{
gboolean have_lock = FALSE;
cmsHTRANSFORM transform = NULL;
if(dt_conf_get_bool("cache_color_managed"))
{
pthread_rwlock_rdlock(&darktable.color_profiles->xprofile_lock);
have_lock = TRUE;
// we only color manage when a thumbnail is sRGB or AdobeRGB. everything else just gets dumped to the screen
if(buf.color_space == DT_COLORSPACE_SRGB &&
darktable.color_profiles->transform_srgb_to_display)
{
transform = darktable.color_profiles->transform_srgb_to_display;
}
else if(buf.color_space == DT_COLORSPACE_ADOBERGB &&
darktable.color_profiles->transform_adobe_rgb_to_display)
{
transform = darktable.color_profiles->transform_adobe_rgb_to_display;
}
else
{
pthread_rwlock_unlock(&darktable.color_profiles->xprofile_lock);
have_lock = FALSE;
if(buf.color_space == DT_COLORSPACE_NONE)
{
fprintf(stderr, "oops, there seems to be a code path not setting the color space of thumbnails!\n");
}
else if(buf.color_space != DT_COLORSPACE_DISPLAY)
{
fprintf(stderr, "oops, there seems to be a code path setting an unhandled color space of thumbnails (%s)!\n",
dt_colorspaces_get_name(buf.color_space, "from file"));
}
}
}
#ifdef _OPENMP
#pragma omp parallel for schedule(static) default(none) shared(buf, rgbbuf, transform)
#endif
for(int i = 0; i < buf.height; i++)
{
const uint8_t *in = buf.buf + i * buf.width * 4;
uint8_t *out = rgbbuf + i * buf.width * 4;
if(transform)
{
cmsDoTransform(transform, in, out, buf.width);
}
else
{
for(int j = 0; j < buf.width; j++, in += 4, out += 4)
{
out[0] = in[2];
out[1] = in[1];
out[2] = in[0];
}
}
}
if(have_lock) pthread_rwlock_unlock(&darktable.color_profiles->xprofile_lock);
const int32_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, buf.width);
surface
= cairo_image_surface_create_for_data(rgbbuf, CAIRO_FORMAT_RGB24, buf.width, buf.height, stride);
}
if(zoom == 1 && !image_only)
{
const int32_t tb = DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size"));
scale = fminf((width - 2 * tb) / (float)buf.width, (height - 2 * tb) / (float)buf.height);
}
else
scale = fminf(width * imgwd / (float)buf.width, height * imgwd / (float)buf.height);
}
// draw centered and fitted:
cairo_save(cr);
if (image_only) // in this case we want to display the picture exactly at (px, py)
cairo_translate(cr, px, py);
else
cairo_translate(cr, width / 2.0, height / 2.0);
cairo_scale(cr, scale, scale);
if(buf.buf && surface)
{
if (!image_only) cairo_translate(cr, -0.5 * buf.width, -0.5 * buf.height);
cairo_set_source_surface(cr, surface, 0, 0);
// set filter no nearest:
// in skull mode, we want to see big pixels.
// in 1 iir mode for the right mip, we want to see exactly what the pipe gave us, 1:1 pixel for pixel.
// in between, filtering just makes stuff go unsharp.
if((buf.width <= 8 && buf.height <= 8) || fabsf(scale - 1.0f) < 0.01f)
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
cairo_rectangle(cr, 0, 0, buf.width, buf.height);
cairo_fill(cr);
cairo_surface_destroy(surface);
cairo_rectangle(cr, 0, 0, buf.width, buf.height);
}
free(rgbbuf);
if (image_only)
{
cairo_restore(cr);
cairo_save(cr);
cairo_new_path(cr);
}
else
{
// border around image
cairo_set_source_rgb(cr, bordercol, bordercol, bordercol);
if(buf.buf && (selected || zoom == 1))
{
const float border = zoom == 1 ? DT_PIXEL_APPLY_DPI(16 / scale) : DT_PIXEL_APPLY_DPI(2 / scale);
cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1. / scale));
if(zoom == 1)
{
// draw shadow around border
cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
cairo_stroke(cr);
// cairo_new_path(cr);
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
float alpha = 1.0f;
for(int k = 0; k < 16; k++)
{
cairo_rectangle(cr, 0, 0, buf.width, buf.height);
cairo_new_sub_path(cr);
cairo_rectangle(cr, -k / scale, -k / scale, buf.width + 2. * k / scale, buf.height + 2. * k / scale);
cairo_set_source_rgba(cr, 0, 0, 0, alpha);
alpha *= 0.6f;
cairo_fill(cr);
}
}
else
{
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
cairo_new_sub_path(cr);
cairo_rectangle(cr, -border, -border, buf.width + 2. * border, buf.height + 2. * border);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, 1.0 - bordercol, 1.0 - bordercol, 1.0 - bordercol);
cairo_fill(cr);
}
}
else if(buf.buf)
{
cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(0.5 / scale));
cairo_stroke(cr);
}
}
}
cairo_restore(cr);
dt_mipmap_cache_release(darktable.mipmap_cache, &buf);
cairo_save(cr);
const float fscale = DT_PIXEL_APPLY_DPI(fminf(width, height));
if(imgsel == imgid || full_preview || darktable.gui->show_overlays || zoom == 1)
{
if(draw_metadata && width > DECORATION_SIZE_LIMIT)
{
// draw mouseover hover effects, set event hook for mouse button down!
cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.5));
cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol);
cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
float r1, r2;
if(zoom != 1)
{
r1 = 0.05 * width;
r2 = 0.022 * width;
}
else
{
r1 = 0.015 * fscale;
r2 = 0.007 * fscale;
}
float x, y;
if(zoom != 1)
y = 0.90 * height;
else
y = .12 * fscale;
const gboolean image_is_rejected = (img && ((img->flags & 0x7) == 6));
if(img)
for(int k = 0; k < 5; k++)
{
if(zoom != 1)
x = (0.41 + k * 0.12) * width;
else
x = (.08 + k * 0.04) * fscale;
if(!image_is_rejected) // if rejected: draw no stars
{
dt_view_star(cr, x, y, r1, r2);
// Only draw hovering effects in stars for the hovered image
// printf ("Image selected: %d - Image processed: %d\n", imgsel, imgid);
if((imgsel == imgid || zoom == 1) && ((px - x) * (px - x) + (py - y) * (py - y) < r1 * r1))
{
*image_over = DT_VIEW_STAR_1 + k;
cairo_fill(cr);
}
else if((img->flags & 0x7) > k)
{
cairo_fill_preserve(cr);
cairo_set_source_rgb(cr, 1.0 - bordercol, 1.0 - bordercol, 1.0 - bordercol);
cairo_stroke(cr);
cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol);
}
else
cairo_stroke(cr);
}
}
// Image rejected?
if(zoom != 1)
x = 0.11 * width;
else
x = .04 * fscale;
if(image_is_rejected) cairo_set_source_rgb(cr, 1., 0., 0.);
// Only draw hovering effects in stars for the hovered image
if((imgsel == imgid || zoom == 1) && ((px - x) * (px - x) + (py - y) * (py - y) < r1 * r1))
{
*image_over = DT_VIEW_REJECT; // mouse sensitive
cairo_new_sub_path(cr);
cairo_arc(cr, x, y, (r1 + r2) * .5, 0, 2.0f * M_PI);
cairo_stroke(cr);
}
if(image_is_rejected) cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.5));
// reject cross:
cairo_move_to(cr, x - r2, y - r2);
cairo_line_to(cr, x + r2, y + r2);
cairo_move_to(cr, x + r2, y - r2);
cairo_line_to(cr, x - r2, y + r2);
cairo_close_path(cr);
cairo_stroke(cr);
cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol);
cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.5));
if(draw_audio)
{
if(img && (img->flags & DT_IMAGE_HAS_WAV))
{
// align to right
const float s = (r1 + r2) * .5;
if(zoom != 1)
{
x = width * 0.9 - s * 5;
y = height * 0.1;
}
else
x = (.04 + 8 * 0.04 - 1.9 * .04) * fscale;
dt_view_draw_audio(cr, x, y, s);
// mouse is over the audio icon
if(fabsf(px - x) <= 1.2 * s && fabsf(-y) <= 1.2 * s) *image_over = DT_VIEW_AUDIO;
}
}
if(draw_grouping)
{
DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.get_grouped);
DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.get_grouped);
DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.get_grouped, 1, imgid);
DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.get_grouped, 2, imgid);
/* lets check if imgid is in a group */
if(sqlite3_step(darktable.view_manager->statements.get_grouped) == SQLITE_ROW)
is_grouped = 1;
else if(img && darktable.gui->expanded_group_id == img->group_id)
darktable.gui->expanded_group_id = -1;
}
// image part of a group?
if(is_grouped && darktable.gui && darktable.gui->grouping)
{
// draw grouping icon and border if the current group is expanded
// align to the right, left of altered
const float s = (r1 + r2) * .6;
float _x, _y;
if(zoom != 1)
{
_x = width * 0.9 - s * 2.5;
_y = height * 0.1 - s * .4;
}
else
{
_x = (.04 + 8 * 0.04 - 1.1 * .04) * fscale;
_y = y - (.17 * .04) * fscale;
}
cairo_save(cr);
if(img && (imgid != img->group_id)) cairo_set_source_rgb(cr, fontcol, fontcol, fontcol);
dtgtk_cairo_paint_grouping(cr, _x, _y, s, s, 23);
cairo_restore(cr);
// mouse is over the grouping icon
if(img && fabs(px - _x - .5 * s) <= .8 * s && fabs(py - _y - .5 * s) <= .8 * s)
*image_over = DT_VIEW_GROUP;
}
// image altered?
if(draw_history && dt_image_altered(imgid))
{
// align to right
const float s = (r1 + r2) * .5;
if(zoom != 1)
{
x = width * 0.9;
y = height * 0.1;
}
else
x = (.04 + 8 * 0.04) * fscale;
dt_view_draw_altered(cr, x, y, s);
// g_print("px = %d, x = %.4f, py = %d, y = %.4f\n", px, x, py, y);
if(img && fabsf(px - x) <= 1.2 * s
&& fabsf(py - y) <= 1.2 * s) // mouse hovers over the altered-icon -> history tooltip!
{
darktable.gui->center_tooltip = 1;
}
}
}
}
cairo_restore(cr);
// kill all paths, in case img was not loaded yet, or is blocked:
cairo_new_path(cr);
if (draw_colorlabels)
{
// TODO: make mouse sensitive, just as stars!
// TODO: cache in image struct!
// TODO: there is a branch that sets the bg == colorlabel
// this might help if zoom > 15
if(width > DECORATION_SIZE_LIMIT)
{
// color labels:
const float x = zoom == 1 ? (0.07) * fscale : .21 * width;
const float y = zoom == 1 ? 0.17 * fscale : 0.1 * height;
const float r = zoom == 1 ? 0.01 * fscale : 0.03 * width;
/* clear and reset prepared statement */
DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.get_color);
DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.get_color);
/* setup statement and iterate rows */
DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.get_color, 1, imgid);
while(sqlite3_step(darktable.view_manager->statements.get_color) == SQLITE_ROW)
{
cairo_save(cr);
const int col = sqlite3_column_int(darktable.view_manager->statements.get_color, 0);
// see src/dtgtk/paint.c
dtgtk_cairo_paint_label(cr, x + (3 * r * col) - 5 * r, y - r, r * 2, r * 2, col);
cairo_restore(cr);
}
}
}
if (draw_local_copy)
{
if(img && width > DECORATION_SIZE_LIMIT)
{
// copy status:
const float x = zoom == 1 ? (0.07) * fscale : .21 * width;
const float y = zoom == 1 ? 0.17 * fscale : 0.1 * height;
const float r = zoom == 1 ? 0.01 * fscale : 0.03 * width;
const int xoffset = 6;
const gboolean has_local_copy = (img && (img->flags & DT_IMAGE_LOCAL_COPY));
cairo_save(cr);
dtgtk_cairo_paint_local_copy(cr, x + (3 * r * xoffset) - 5 * r, y - r, r * 2, r * 2, has_local_copy);
cairo_restore(cr);
}
}
if(draw_metadata && img && (zoom == 1))
{
// some exif data
PangoLayout *layout;
PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
layout = pango_cairo_create_layout(cr);
const int fontsize = 0.025 * fscale;
pango_font_description_set_absolute_size(desc, fontsize * PANGO_SCALE);
pango_layout_set_font_description(layout, desc);
cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.0));
cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
cairo_move_to(cr, .02 * fscale, .04 * fscale - fontsize);
pango_layout_set_text(layout, img->filename, -1);
pango_cairo_layout_path(cr, layout);
char exifline[50];
cairo_move_to(cr, .02 * fscale, .08 * fscale - fontsize);
dt_image_print_exif(img, exifline, sizeof(exifline));
pango_layout_set_text(layout, exifline, -1);
pango_cairo_layout_path(cr, layout);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, .7, .7, .7);
cairo_fill(cr);
pango_font_description_free(desc);
g_object_unref(layout);
}
// draw custom metadata from accompanying text file:
if(draw_metadata && img && (img->flags & DT_IMAGE_HAS_TXT) && dt_conf_get_bool("plugins/lighttable/draw_custom_metadata")
&& (zoom == 1))
{
char *path = dt_image_get_text_path(img->id);
if(path)
{
FILE *f = g_fopen(path, "rb");
if(f)
{
char line[2048];
PangoLayout *layout;
PangoFontDescription *desc = pango_font_description_from_string("monospace bold");
layout = pango_cairo_create_layout(cr);
const float fontsize = 0.015 * fscale;
pango_font_description_set_absolute_size(desc, fontsize * PANGO_SCALE);
pango_layout_set_font_description(layout, desc);
// cairo_set_operator(cr, CAIRO_OPERATOR_XOR);
cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.0));
cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
int k = 0;
while(!feof(f))
{
gchar *line_pattern = g_strdup_printf("%%%zu[^\n]", sizeof(line) - 1);
const int read = fscanf(f, line_pattern, line);
g_free(line_pattern);
if(read != 1) break;
fgetc(f); // munch \n
cairo_move_to(cr, .02 * fscale, .20 * fscale + .017 * fscale * k - fontsize);
cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
pango_layout_set_text(layout, line, -1);
pango_cairo_layout_path(cr, layout);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, .7, .7, .7);
cairo_fill(cr);
k++;
}
fclose(f);
pango_font_description_free(desc);
g_object_unref(layout);
}
g_free(path);
}
}
cairo_restore(cr);
// if(zoom == 1) cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
const double end = dt_get_wtime();
if(darktable.unmuted & DT_DEBUG_PERF)
dt_print(DT_DEBUG_LIGHTTABLE, "[lighttable] image expose took %0.04f sec\n", end - start);
return missing;
}
void
dt_view_image_only_expose(
uint32_t imgid,
cairo_t *cr,
int32_t width,
int32_t height,
int32_t offsetx,
int32_t offsety)
{
dt_view_image_over_t image_over;
dt_view_image_expose(&image_over, imgid, cr, width, height, 1, offsetx, offsety, TRUE, TRUE);
}
/**
* \brief Set the selection bit to a given value for the specified image
* \param[in] imgid The image id
* \param[in] value The boolean value for the bit
*/
void dt_view_set_selection(int imgid, int value)
{
/* clear and reset statement */
DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.is_selected);
DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.is_selected);
/* setup statement and iterate over rows */
DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.is_selected, 1, imgid);
if(sqlite3_step(darktable.view_manager->statements.is_selected) == SQLITE_ROW)
{
if(!value)
{
/* Value is set and should be unset; get rid of it */
/* clear and reset statement */
DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.delete_from_selected);
DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.delete_from_selected);
/* setup statement and execute */
DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.delete_from_selected, 1, imgid);
sqlite3_step(darktable.view_manager->statements.delete_from_selected);
}
}
else if(value)
{
/* Select bit is unset and should be set; add it */
/* clear and reset statement */
DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.make_selected);
DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.make_selected);
/* setup statement and execute */
DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.make_selected, 1, imgid);
sqlite3_step(darktable.view_manager->statements.make_selected);
}
}
/**
* \brief Toggle the selection bit in the database for the specified image
* \param[in] imgid The image id
*/
void dt_view_toggle_selection(int imgid)
{
/* clear and reset statement */
DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.is_selected);
DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.is_selected);
/* setup statement and iterate over rows */
DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.is_selected, 1, imgid);
if(sqlite3_step(darktable.view_manager->statements.is_selected) == SQLITE_ROW)
{
/* clear and reset statement */
DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.delete_from_selected);
DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.delete_from_selected);
/* setup statement and execute */
DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.delete_from_selected, 1, imgid);
sqlite3_step(darktable.view_manager->statements.delete_from_selected);
}
else
{
/* clear and reset statement */
DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.make_selected);
DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.make_selected);
/* setup statement and execute */
DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.make_selected, 1, imgid);
sqlite3_step(darktable.view_manager->statements.make_selected);
}
}
/**
* \brief Reset filter
*/
void dt_view_filter_reset(const dt_view_manager_t *vm, gboolean smart_filter)
{
if(vm->proxy.filter.module && vm->proxy.filter.reset_filter)
vm->proxy.filter.reset_filter(vm->proxy.filter.module, smart_filter);
}
void dt_view_filmstrip_scroll_relative(const int diff, int offset)
{
const gchar *qin = dt_collection_get_query(darktable.collection);
if(qin)
{
sqlite3_stmt *stmt;
DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), qin, -1, &stmt, NULL);
DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, offset + diff);
DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, 1);
if(sqlite3_step(stmt) == SQLITE_ROW)
{
const int imgid = sqlite3_column_int(stmt, 0);
if(!darktable.develop->image_loading)
{
dt_view_filmstrip_scroll_to_image(darktable.view_manager, imgid, TRUE);
}
}
sqlite3_finalize(stmt);
}
}
void dt_view_filmstrip_scroll_to_image(dt_view_manager_t *vm, const int imgid, gboolean activate)
{
// g_return_if_fail(vm->proxy.filmstrip.module!=NULL); // This can happen here for debugging
// g_return_if_fail(vm->proxy.filmstrip.scroll_to_image!=NULL);
if(vm->proxy.filmstrip.module && vm->proxy.filmstrip.scroll_to_image)
vm->proxy.filmstrip.scroll_to_image(vm->proxy.filmstrip.module, imgid, activate);
}
int32_t dt_view_filmstrip_get_activated_imgid(dt_view_manager_t *vm)
{
// g_return_val_if_fail(vm->proxy.filmstrip.module!=NULL, 0); // This can happen here for debugging
// g_return_val_if_fail(vm->proxy.filmstrip.activated_image!=NULL, 0);
if(vm->proxy.filmstrip.module && vm->proxy.filmstrip.activated_image)
return vm->proxy.filmstrip.activated_image(vm->proxy.filmstrip.module);
return 0;
}
void dt_view_filmstrip_set_active_image(dt_view_manager_t *vm, int iid)
{
/* First off clear all selected images... */
DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM main.selected_images", NULL, NULL, NULL);
/* clear and reset statement */
DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.make_selected);
DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.make_selected);
/* setup statement and execute */
DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.make_selected, 1, iid);
sqlite3_step(darktable.view_manager->statements.make_selected);
dt_view_filmstrip_scroll_to_image(vm, iid, TRUE);
}
void dt_view_filmstrip_prefetch()
{
const gchar *qin = dt_collection_get_query(darktable.collection);
if(!qin) return;
int offset = 0;
if(qin)
{
int imgid = -1;
sqlite3_stmt *stmt;
DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT imgid FROM main.selected_images", -1, &stmt,
NULL);
if(sqlite3_step(stmt) == SQLITE_ROW) imgid = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
offset = dt_collection_image_offset(imgid);
}
sqlite3_stmt *stmt;
DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), qin, -1, &stmt, NULL);
// only get one more image:
DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, offset + 1);
DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, offset + 2);
if(sqlite3_step(stmt) == SQLITE_ROW)
{
const uint32_t prefetchid = sqlite3_column_int(stmt, 0);
// dt_control_log("prefetching image %u", prefetchid);
dt_mipmap_cache_get(darktable.mipmap_cache, NULL, prefetchid, DT_MIPMAP_FULL, DT_MIPMAP_PREFETCH, 'r');
}
sqlite3_finalize(stmt);
}
void dt_view_manager_view_toolbox_add(dt_view_manager_t *vm, GtkWidget *tool, dt_view_type_flags_t views)
{
if(vm->proxy.view_toolbox.module) vm->proxy.view_toolbox.add(vm->proxy.view_toolbox.module, tool, views);
}
void dt_view_manager_module_toolbox_add(dt_view_manager_t *vm, GtkWidget *tool, dt_view_type_flags_t views)
{
if(vm->proxy.module_toolbox.module) vm->proxy.module_toolbox.add(vm->proxy.module_toolbox.module, tool, views);
}
void dt_view_lighttable_set_zoom(dt_view_manager_t *vm, gint zoom)
{
if(vm->proxy.lighttable.module) vm->proxy.lighttable.set_zoom(vm->proxy.lighttable.module, zoom);
}
void dt_view_lighttable_set_position(dt_view_manager_t *vm, uint32_t pos)
{
if(vm->proxy.lighttable.view) vm->proxy.lighttable.set_position(vm->proxy.lighttable.view, pos);
// ugh. but will go away once module guis are persistent between views:
dt_conf_set_int("plugins/lighttable/recentcollect/pos0", pos);
}
uint32_t dt_view_lighttable_get_position(dt_view_manager_t *vm)
{
if(vm->proxy.lighttable.view) return vm->proxy.lighttable.get_position(vm->proxy.lighttable.view);
return 0;
}
void dt_view_collection_update(const dt_view_manager_t *vm)
{
if(vm->proxy.module_collect.module) vm->proxy.module_collect.update(vm->proxy.module_collect.module);
}
int32_t dt_view_tethering_get_selected_imgid(const dt_view_manager_t *vm)
{
if(vm->proxy.tethering.view) return vm->proxy.tethering.get_selected_imgid(vm->proxy.tethering.view);
return -1;
}
void dt_view_tethering_set_job_code(const dt_view_manager_t *vm, const char *name)
{
if(vm->proxy.tethering.view) vm->proxy.tethering.set_job_code(vm->proxy.tethering.view, name);
}
const char *dt_view_tethering_get_job_code(const dt_view_manager_t *vm)
{
if(vm->proxy.tethering.view) return vm->proxy.tethering.get_job_code(vm->proxy.tethering.view);
return NULL;
}
#ifdef HAVE_MAP
void dt_view_map_center_on_location(const dt_view_manager_t *vm, gdouble lon, gdouble lat, gdouble zoom)
{
if(vm->proxy.map.view) vm->proxy.map.center_on_location(vm->proxy.map.view, lon, lat, zoom);
}
void dt_view_map_center_on_bbox(const dt_view_manager_t *vm, gdouble lon1, gdouble lat1, gdouble lon2, gdouble lat2)
{
if(vm->proxy.map.view) vm->proxy.map.center_on_bbox(vm->proxy.map.view, lon1, lat1, lon2, lat2);
}
void dt_view_map_show_osd(const dt_view_manager_t *vm, gboolean enabled)
{
if(vm->proxy.map.view) vm->proxy.map.show_osd(vm->proxy.map.view, enabled);
}
void dt_view_map_set_map_source(const dt_view_manager_t *vm, OsmGpsMapSource_t map_source)
{
if(vm->proxy.map.view) vm->proxy.map.set_map_source(vm->proxy.map.view, map_source);
}
GObject *dt_view_map_add_marker(const dt_view_manager_t *vm, dt_geo_map_display_t type, GList *points)
{
if(vm->proxy.map.view) return vm->proxy.map.add_marker(vm->proxy.map.view, type, points);
return NULL;
}
gboolean dt_view_map_remove_marker(const dt_view_manager_t *vm, dt_geo_map_display_t type, GObject *marker)
{
if(vm->proxy.map.view) return vm->proxy.map.remove_marker(vm->proxy.map.view, type, marker);
return FALSE;
}
#endif
#ifdef HAVE_PRINT
void dt_view_print_settings(const dt_view_manager_t *vm, dt_print_info_t *pinfo)
{
if (vm->proxy.print.view)
vm->proxy.print.print_settings(vm->proxy.print.view, pinfo);
}
#endif
// 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;