/* This file is part of darktable, copyright (c) 2009--2011 johannes hanika. copyright (c) 2011--2012 henrik andersson. 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 . */ /** this is the view for the darkroom module. */ #include "common/darktable.h" #include "common/collection.h" #include "views/view.h" #include "develop/develop.h" #include "control/jobs.h" #include "control/control.h" #include "control/conf.h" #include "dtgtk/button.h" #include "dtgtk/tristatebutton.h" #include "develop/imageop.h" #include "develop/blend.h" #include "develop/masks.h" #include "common/image_cache.h" #include "common/imageio.h" #include "common/imageio_module.h" #include "common/debug.h" #include "common/tags.h" #include "common/styles.h" #include "gui/accelerators.h" #include "gui/gtk.h" #include "gui/presets.h" #include "libs/colorpicker.h" #include "bauhaus/bauhaus.h" #include #include #include #include DT_MODULE(1) static gboolean film_strip_key_accel(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data); static gboolean zoom_key_accel(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data); static gboolean export_key_accel_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data); static gboolean skip_f_key_accel_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data); static gboolean skip_b_key_accel_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data); static gboolean _overexposed_toggle_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data); static gboolean _brush_size_up_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data); static gboolean _brush_size_down_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data); /* signal handler for filmstrip image switching */ static void _view_darkroom_filmstrip_activate_callback(gpointer instance, gpointer user_data); const char *name(dt_view_t *self) { return _("darkroom"); } void init(dt_view_t *self) { self->data = malloc(sizeof(dt_develop_t)); dt_dev_init((dt_develop_t *)self->data, 1); } uint32_t view(dt_view_t *self) { return DT_VIEW_DARKROOM; } void cleanup(dt_view_t *self) { dt_develop_t *dev = (dt_develop_t *)self->data; dt_dev_cleanup(dev); free(dev); } void expose(dt_view_t *self, cairo_t *cri, int32_t width_i, int32_t height_i, int32_t pointerx, int32_t pointery) { // startup-time conf parameter: const int32_t capwd = darktable.thumbnail_width; const int32_t capht = darktable.thumbnail_height; // if width or height > max pipeline pixels: center the view and clamp. int32_t width = MIN(width_i, capwd); int32_t height = MIN(height_i, capht); cairo_set_source_rgb(cri, .2, .2, .2); cairo_save(cri); cairo_set_fill_rule(cri, CAIRO_FILL_RULE_EVEN_ODD); cairo_rectangle(cri, 0, 0, width_i, height_i); cairo_rectangle(cri, MAX(1.0, width_i - capwd) * .5f, MAX(1.0, height_i - capht) * .5f, MIN(width, width_i - 1), MIN(height, height_i - 1)); cairo_fill(cri); cairo_restore(cri); if(width_i > capwd) cairo_translate(cri, -(capwd - width_i) * .5f, 0.0f); if(height_i > capht) cairo_translate(cri, 0.0f, -(capht - height_i) * .5f); cairo_save(cri); dt_develop_t *dev = (dt_develop_t *)self->data; if(dev->gui_synch && !dev->image_loading) { // synch module guis from gtk thread: darktable.gui->reset = 1; GList *modules = dev->iop; while(modules) { dt_iop_module_t *module = (dt_iop_module_t *)(modules->data); dt_iop_gui_update(module); modules = g_list_next(modules); } darktable.gui->reset = 0; dev->gui_synch = 0; } if(dev->image_status == DT_DEV_PIXELPIPE_DIRTY || dev->image_status == DT_DEV_PIXELPIPE_INVALID || dev->pipe->input_timestamp < dev->preview_pipe->input_timestamp) dt_dev_process_image(dev); if(dev->preview_status == DT_DEV_PIXELPIPE_DIRTY || dev->preview_status == DT_DEV_PIXELPIPE_INVALID || dev->pipe->input_timestamp > dev->preview_pipe->input_timestamp) dt_dev_process_preview(dev); dt_pthread_mutex_t *mutex = NULL; int wd, ht, stride, closeup; int32_t zoom; float zoom_x, zoom_y; zoom_y = dt_control_get_dev_zoom_y(); zoom_x = dt_control_get_dev_zoom_x(); zoom = dt_control_get_dev_zoom(); closeup = dt_control_get_dev_closeup(); static cairo_surface_t *image_surface = NULL; static int image_surface_width = 0, image_surface_height = 0, image_surface_imgid = -1; static float roi_hash_old = -1.0f; // compute patented dreggn hash so we don't need to check all values: const float roi_hash = width + 7.0f * height + 23.0f * zoom + 42.0f * zoom_x + 91.0f * zoom_y + 666.0f * zoom; if(image_surface_width != width || image_surface_height != height || image_surface == NULL) { // create double-buffered image to draw on, to make modules draw more fluently. image_surface_width = width; image_surface_height = height; if(image_surface) cairo_surface_destroy(image_surface); image_surface = dt_cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); image_surface_imgid = -1; // invalidate old stuff } cairo_surface_t *surface; cairo_t *cr = cairo_create(image_surface); // adjust scroll bars { float zx = zoom_x, zy = zoom_y, boxw = 1., boxh = 1.; dt_dev_check_zoom_bounds(dev, &zx, &zy, zoom, closeup, &boxw, &boxh); dt_view_set_scrollbar(self, zx + .5 - boxw * .5, 1.0, boxw, zy + .5 - boxh * .5, 1.0, boxh); } if((dev->image_status == DT_DEV_PIXELPIPE_VALID) && dev->pipe->input_timestamp >= dev->preview_pipe->input_timestamp) { // draw image roi_hash_old = roi_hash; mutex = &dev->pipe->backbuf_mutex; dt_pthread_mutex_lock(mutex); wd = dev->pipe->backbuf_width; ht = dev->pipe->backbuf_height; stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, wd); surface = dt_cairo_image_surface_create_for_data(dev->pipe->backbuf, CAIRO_FORMAT_RGB24, wd, ht, stride); wd /= darktable.gui->ppd; ht /= darktable.gui->ppd; cairo_set_source_rgb(cr, .2, .2, .2); cairo_paint(cr); cairo_translate(cr, .5f * (width - wd), .5f * (height - ht)); if(closeup) { cairo_scale(cr, 2.0, 2.0); cairo_translate(cr, -.25f * wd, -.25f * ht); } cairo_rectangle(cr, 0, 0, wd, ht); cairo_set_source_surface(cr, surface, 0, 0); cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_FAST); cairo_fill_preserve(cr); cairo_set_line_width(cr, 1.0); cairo_set_source_rgb(cr, .3, .3, .3); cairo_stroke(cr); cairo_surface_destroy(surface); dt_pthread_mutex_unlock(mutex); image_surface_imgid = dev->image_storage.id; } else if((dev->preview_status == DT_DEV_PIXELPIPE_VALID) && (roi_hash != roi_hash_old)) { // draw preview roi_hash_old = roi_hash; mutex = &dev->preview_pipe->backbuf_mutex; dt_pthread_mutex_lock(mutex); wd = dev->preview_pipe->backbuf_width; ht = dev->preview_pipe->backbuf_height; float zoom_scale = dt_dev_get_zoom_scale(dev, zoom, closeup ? 2 : 1, 1); cairo_set_source_rgb(cr, .2, .2, .2); cairo_paint(cr); cairo_rectangle(cr, 0, 0, width, height); cairo_clip(cr); stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, wd); surface = cairo_image_surface_create_for_data(dev->preview_pipe->backbuf, CAIRO_FORMAT_RGB24, wd, ht, stride); cairo_translate(cr, width / 2.0, height / 2.0f); cairo_scale(cr, zoom_scale, zoom_scale); cairo_translate(cr, -.5f * wd - zoom_x * wd, -.5f * ht - zoom_y * ht); // avoid to draw the 1px garbage that sometimes shows up in the preview :( cairo_rectangle(cr, 0, 0, wd - 1, ht - 1); cairo_set_source_surface(cr, surface, 0, 0); cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_FAST); cairo_fill(cr); cairo_surface_destroy(surface); dt_pthread_mutex_unlock(mutex); image_surface_imgid = dev->image_storage.id; } cairo_restore(cri); if(image_surface_imgid == dev->image_storage.id) { cairo_destroy(cr); cairo_set_source_surface(cri, image_surface, 0, 0); cairo_paint(cri); } /* check if we should create a snapshot of view */ if(darktable.develop->proxy.snapshot.request && !darktable.develop->image_loading) { /* reset the request */ darktable.develop->proxy.snapshot.request = FALSE; /* validation of snapshot filename */ g_assert(darktable.develop->proxy.snapshot.filename != NULL); /* Store current image surface to snapshot file. FIXME: add checks so that we dont make snapshots of preview pipe image surface. */ cairo_surface_write_to_png(image_surface, darktable.develop->proxy.snapshot.filename); } // Displaying sample areas if enabled if(darktable.lib->proxy.colorpicker.live_samples && darktable.lib->proxy.colorpicker.display_samples) { GSList *samples = darktable.lib->proxy.colorpicker.live_samples; dt_colorpicker_sample_t *sample = NULL; cairo_save(cri); float wd = dev->preview_pipe->backbuf_width; float ht = dev->preview_pipe->backbuf_height; float zoom_y = dt_control_get_dev_zoom_y(); float zoom_x = dt_control_get_dev_zoom_x(); dt_dev_zoom_t zoom = dt_control_get_dev_zoom(); int closeup = dt_control_get_dev_closeup(); float zoom_scale = dt_dev_get_zoom_scale(dev, zoom, closeup ? 2 : 1, 1); cairo_translate(cri, width / 2.0, height / 2.0f); cairo_scale(cri, zoom_scale, zoom_scale); cairo_translate(cri, -.5f * wd - zoom_x * wd, -.5f * ht - zoom_y * ht); while(samples) { sample = samples->data; cairo_set_line_width(cri, 1.0 / zoom_scale); if(sample == darktable.lib->proxy.colorpicker.selected_sample) cairo_set_source_rgb(cri, .2, 0, 0); else cairo_set_source_rgb(cri, 0, 0, .2); float *box = sample->box; float *point = sample->point; if(sample->size == DT_COLORPICKER_SIZE_BOX) { cairo_rectangle(cri, box[0] * wd, box[1] * ht, (box[2] - box[0]) * wd, (box[3] - box[1]) * ht); cairo_stroke(cri); cairo_translate(cri, 1.0 / zoom_scale, 1.0 / zoom_scale); if(sample == darktable.lib->proxy.colorpicker.selected_sample) cairo_set_source_rgb(cri, .8, 0, 0); else cairo_set_source_rgb(cri, 0, 0, .8); cairo_rectangle(cri, box[0] * wd + 1.0 / zoom_scale, box[1] * ht, (box[2] - box[0]) * wd - 3. / zoom_scale, (box[3] - box[1]) * ht - 2. / zoom_scale); cairo_stroke(cri); } else { cairo_rectangle(cri, point[0] * wd - .01 * wd, point[1] * ht - .01 * wd, .02 * wd, .02 * wd); cairo_stroke(cri); if(sample == darktable.lib->proxy.colorpicker.selected_sample) cairo_set_source_rgb(cri, .8, 0, 0); else cairo_set_source_rgb(cri, 0, 0, .8); cairo_rectangle(cri, (point[0] - 0.01) * wd + 1.0 / zoom_scale, point[1] * ht - 0.01 * wd + 1.0 / zoom_scale, .02 * wd - 2. / zoom_scale, .02 * wd - 2. / zoom_scale); cairo_move_to(cri, point[0] * wd, point[1] * ht - .01 * wd + 1. / zoom_scale); cairo_line_to(cri, point[0] * wd, point[1] * ht + .01 * wd - 1. / zoom_scale); cairo_move_to(cri, point[0] * wd - .01 * wd + 1. / zoom_scale, point[1] * ht); cairo_line_to(cri, point[0] * wd + .01 * wd - 1. / zoom_scale, point[1] * ht); cairo_stroke(cri); } samples = g_slist_next(samples); } cairo_restore(cri); } // execute module callback hook. if(dev->gui_module && dev->gui_module->request_color_pick != DT_REQUEST_COLORPICK_OFF) { float wd = dev->preview_pipe->backbuf_width; float ht = dev->preview_pipe->backbuf_height; float zoom_y = dt_control_get_dev_zoom_y(); float zoom_x = dt_control_get_dev_zoom_x(); dt_dev_zoom_t zoom = dt_control_get_dev_zoom(); int closeup = dt_control_get_dev_closeup(); float zoom_scale = dt_dev_get_zoom_scale(dev, zoom, closeup ? 2 : 1, 1); cairo_translate(cri, width / 2.0, height / 2.0f); cairo_scale(cri, zoom_scale, zoom_scale); cairo_translate(cri, -.5f * wd - zoom_x * wd, -.5f * ht - zoom_y * ht); // cairo_set_operator(cri, CAIRO_OPERATOR_XOR); cairo_set_line_width(cri, 1.0 / zoom_scale); cairo_set_source_rgb(cri, .2, .2, .2); float *box = dev->gui_module->color_picker_box; float *point = dev->gui_module->color_picker_point; if(darktable.lib->proxy.colorpicker.size) { cairo_rectangle(cri, box[0] * wd, box[1] * ht, (box[2] - box[0]) * wd, (box[3] - box[1]) * ht); cairo_stroke(cri); cairo_translate(cri, 1.0 / zoom_scale, 1.0 / zoom_scale); cairo_set_source_rgb(cri, .8, .8, .8); cairo_rectangle(cri, box[0] * wd + 1.0 / zoom_scale, box[1] * ht, (box[2] - box[0]) * wd - 3. / zoom_scale, (box[3] - box[1]) * ht - 2. / zoom_scale); cairo_stroke(cri); } else if(point[0] >= 0.0f && point[0] <= 1.0f && point[1] >= 0.0f && point[1] <= 1.0f) { cairo_rectangle(cri, point[0] * wd - .01 * wd, point[1] * ht - .01 * wd, .02 * wd, .02 * wd); cairo_stroke(cri); cairo_set_source_rgb(cri, .8, .8, .8); cairo_rectangle(cri, (point[0] - 0.01) * wd + 1.0 / zoom_scale, point[1] * ht - 0.01 * wd + 1.0 / zoom_scale, .02 * wd - 2. / zoom_scale, .02 * wd - 2. / zoom_scale); cairo_move_to(cri, point[0] * wd, point[1] * ht - .01 * wd + 1. / zoom_scale); cairo_line_to(cri, point[0] * wd, point[1] * ht + .01 * wd - 1. / zoom_scale); cairo_move_to(cri, point[0] * wd - .01 * wd + 1. / zoom_scale, point[1] * ht); cairo_line_to(cri, point[0] * wd + .01 * wd - 1. / zoom_scale, point[1] * ht); cairo_stroke(cri); } } else { // masks if(dev->form_visible) { if(width_i > capwd) pointerx += (capwd - width_i) * .5f; if(height_i > capht) pointery += (capht - height_i) * .5f; dt_masks_events_post_expose(dev->gui_module, cri, width, height, pointerx, pointery); } // module if(dev->gui_module && dev->gui_module->gui_post_expose) { if(width_i > capwd) pointerx += (capwd - width_i) * .5f; if(height_i > capht) pointery += (capht - height_i) * .5f; dev->gui_module->gui_post_expose(dev->gui_module, cri, width, height, pointerx, pointery); } } } void reset(dt_view_t *self) { dt_control_set_dev_zoom(DT_ZOOM_FIT); dt_control_set_dev_zoom_x(0); dt_control_set_dev_zoom_y(0); dt_control_set_dev_closeup(0); } int try_enter(dt_view_t *self) { int selected = dt_control_get_mouse_over_id(); if(selected < 0) { // try last selected sqlite3_stmt *stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select * from selected_images", -1, &stmt, NULL); if(sqlite3_step(stmt) == SQLITE_ROW) selected = sqlite3_column_int(stmt, 0); sqlite3_finalize(stmt); // Leave as selected only the image being edited DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "delete from selected_images", NULL, NULL, NULL); DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "insert or ignore into selected_images values (?1)", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, selected); sqlite3_step(stmt); sqlite3_finalize(stmt); } if(selected < 0) { // fail :( dt_control_log(_("no image selected!")); return 1; } // this loads the image from db if needed: const dt_image_t *img = dt_image_cache_read_get(darktable.image_cache, selected); // get image and check if it has been deleted from disk first! char imgfilename[PATH_MAX] = { 0 }; gboolean from_cache = TRUE; dt_image_full_path(img->id, imgfilename, sizeof(imgfilename), &from_cache); if(!g_file_test(imgfilename, G_FILE_TEST_IS_REGULAR)) { dt_control_log(_("image `%s' is currently unavailable"), img->filename); // dt_image_remove(selected); dt_image_cache_read_release(darktable.image_cache, img); return 1; } // and drop the lock again. dt_image_cache_read_release(darktable.image_cache, img); darktable.develop->image_storage.id = selected; return 0; } static void select_this_image(const int imgid) { // select this image, if no multiple selection: if(dt_collection_get_selected_count(NULL) < 2) { sqlite3_stmt *stmt; DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "delete from selected_images", NULL, NULL, NULL); DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "insert or ignore into selected_images values (?1)", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid); sqlite3_step(stmt); sqlite3_finalize(stmt); } } static void dt_dev_cleanup_module_accels(dt_iop_module_t *module) { dt_accel_disconnect_list(module->accel_closures); dt_accel_cleanup_locals_iop(module); } static void dt_dev_change_image(dt_develop_t *dev, const uint32_t imgid) { // stop crazy users from sleeping on key-repeat spacebar: if(dev->image_loading) return; // make sure we can destroy and re-setup the pixel pipes. // we acquire the pipe locks, which will block the processing threads // in darkroom mode before they touch the pipes (init buffers etc). // we don't block here, since we hold the gdk lock, which will // result in circular locking when background threads emit signals // which in turn try to acquire the gdk lock. // // worst case, it'll drop some change image events. sorry. if(dt_pthread_mutex_trylock(&dev->preview_pipe_mutex)) return; if(dt_pthread_mutex_trylock(&dev->pipe_mutex)) { dt_pthread_mutex_unlock(&dev->preview_pipe_mutex); return; } // get last active plugin, make sure focus out is called: gchar *active_plugin = dt_conf_get_string("plugins/darkroom/active"); dt_iop_request_focus(NULL); // store last active group dt_conf_set_int("plugins/darkroom/groups", dt_dev_modulegroups_get(dev)); // store last active plugin: if(darktable.develop->gui_module) dt_conf_set_string("plugins/darkroom/active", darktable.develop->gui_module->op); else dt_conf_set_string("plugins/darkroom/active", ""); g_assert(dev->gui_attached); // commit image ops to db dt_dev_write_history(dev); // be sure light table will update the thumbnail // TODO: only if image changed! // if() { dt_mipmap_cache_remove(darktable.mipmap_cache, dev->image_storage.id); dt_image_synch_xmp(dev->image_storage.id); } // cleanup visible masks if(!dev->form_gui) { dev->form_gui = (dt_masks_form_gui_t *)calloc(1, sizeof(dt_masks_form_gui_t)); } dt_masks_init_form_gui(dev); dev->form_visible = NULL; dev->form_gui->pipe_hash = 0; dev->form_gui->formid = 0; select_this_image(imgid); while(dev->history) { // clear history of old image free(((dt_dev_history_item_t *)dev->history->data)->params); free((dt_dev_history_item_t *)dev->history->data); dev->history = g_list_delete_link(dev->history, dev->history); } // get new image: dt_dev_reload_image(dev, imgid); // make sure no signals propagate here: darktable.gui->reset = 1; guint nb_iop = g_list_length(dev->iop); dt_dev_pixelpipe_cleanup_nodes(dev->pipe); dt_dev_pixelpipe_cleanup_nodes(dev->preview_pipe); for(int i = nb_iop - 1; i > 0; i--) { dt_iop_module_t *module = (dt_iop_module_t *)(g_list_nth_data(dev->iop, i)); // the base module is the one with the highest multi_priority const guint clen = g_list_length(dev->iop); int mp_base = 0; for(int k = 0; k < clen; k++) { dt_iop_module_t *mod = (dt_iop_module_t *)(g_list_nth_data(dev->iop, k)); if(strcmp(module->op, mod->op) == 0) mp_base = MAX(mp_base, mod->multi_priority); } if(module->multi_priority == mp_base) // if the module is the "base" instance, we keep it { module->multi_priority = 0; dt_iop_reload_defaults(module); dt_iop_gui_update(module); } else // else we delete it and remove it from the panel { if(!dt_iop_is_hidden(module)) { gtk_container_remove( GTK_CONTAINER(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER)), module->expander); dt_iop_gui_cleanup_module(module); } // we remove the module from the list dev->iop = g_list_remove_link(dev->iop, g_list_nth(dev->iop, i)); // we cleanup the module dt_accel_disconnect_list(module->accel_closures); dt_accel_cleanup_locals_iop(module); module->accel_closures = NULL; dt_iop_cleanup_module(module); free(module); } } dt_dev_pixelpipe_create_nodes(dev->pipe, dev); dt_dev_pixelpipe_create_nodes(dev->preview_pipe, dev); dt_masks_read_forms(dev); dt_dev_read_history(dev); // we have to init all module instances other than "base" instance GList *modules = dev->iop; while(modules) { dt_iop_module_t *module = (dt_iop_module_t *)(modules->data); if(module->multi_priority > 0) { if(!dt_iop_is_hidden(module)) { module->gui_init(module); dt_iop_reload_defaults(module); // we search the base iop corresponding GList *mods = g_list_first(dev->iop); dt_iop_module_t *base = NULL; int pos_module = 0; int pos_base = 0; int pos = 0; while(mods) { dt_iop_module_t *mod = (dt_iop_module_t *)(mods->data); if(mod->multi_priority == 0 && mod->instance == module->instance) { base = mod; pos_base = pos; } else if(mod == module) pos_module = pos; mods = g_list_next(mods); pos++; } if(!base) continue; /* add module to right panel */ GtkWidget *expander = dt_iop_gui_get_expander(module); dt_ui_container_add_widget(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER, expander); GValue gv = { 0, { { 0 } } }; g_value_init(&gv, G_TYPE_INT); gtk_container_child_get_property( GTK_CONTAINER(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER)), base->expander, "position", &gv); gtk_box_reorder_child(dt_ui_get_container(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER), expander, g_value_get_int(&gv) + pos_base - pos_module); dt_iop_gui_set_expanded(module, TRUE, FALSE); dt_iop_gui_update_blending(module); } /* setup key accelerators */ module->accel_closures = NULL; if(module->connect_key_accels) module->connect_key_accels(module); dt_iop_connect_common_accels(module); // we update show params for multi-instances for each other instances dt_dev_modules_update_multishow(module->dev); } else { // update the module header to ensure proper multi-name display if(!dt_iop_is_hidden(module)) dt_iop_gui_update_header(module); } modules = g_list_next(modules); } dt_dev_pop_history_items(dev, dev->history_end); if(active_plugin) { modules = dev->iop; while(modules) { dt_iop_module_t *module = (dt_iop_module_t *)(modules->data); if(!strcmp(module->op, active_plugin)) dt_iop_request_focus(module); modules = g_list_next(modules); } g_free(active_plugin); } dt_dev_masks_list_change(dev); /* last set the group to update visibility of iop modules for new pipe */ dt_dev_modulegroups_set(dev, dt_conf_get_int("plugins/darkroom/groups")); /* cleanup histograms */ g_list_foreach(dev->iop, (GFunc)dt_iop_cleanup_histogram, (gpointer)NULL); // make signals work again, but only after focus event, // to avoid crop/rotate for example to add another history item. darktable.gui->reset = 0; // Signal develop initialize dt_control_signal_raise(darktable.signals, DT_SIGNAL_DEVELOP_IMAGE_CHANGED); // prefetch next few from first selected image on. dt_view_filmstrip_prefetch(); // release pixel pipe mutices dt_pthread_mutex_unlock(&dev->preview_pipe_mutex); dt_pthread_mutex_unlock(&dev->pipe_mutex); } static void film_strip_activated(const int imgid, void *data) { // switch images in darkroom mode: dt_view_t *self = (dt_view_t *)data; dt_develop_t *dev = (dt_develop_t *)self->data; dt_dev_change_image(dev, imgid); dt_view_filmstrip_scroll_to_image(darktable.view_manager, imgid, FALSE); // record the imgid to display when going back to lighttable dt_view_lighttable_set_position(darktable.view_manager, dt_collection_image_offset(imgid)); // force redraw dt_control_queue_redraw(); } static void _view_darkroom_filmstrip_activate_callback(gpointer instance, gpointer user_data) { int32_t imgid = 0; if((imgid = dt_view_filmstrip_get_activated_imgid(darktable.view_manager)) > 0) film_strip_activated(imgid, user_data); } static void dt_dev_jump_image(dt_develop_t *dev, int diff) { const gchar *qin = dt_collection_get_query(darktable.collection); int offset = 0; if(qin) { int orig_imgid = -1, imgid = -1; sqlite3_stmt *stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select imgid from selected_images", -1, &stmt, NULL); if(sqlite3_step(stmt) == SQLITE_ROW) orig_imgid = sqlite3_column_int(stmt, 0); sqlite3_finalize(stmt); offset = dt_collection_image_offset(orig_imgid); 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) { imgid = sqlite3_column_int(stmt, 0); if(orig_imgid == imgid) { // nothing to do sqlite3_finalize(stmt); return; } if(!dev->image_loading) { dt_view_filmstrip_scroll_to_image(darktable.view_manager, imgid, FALSE); // record the imgid to display when going back to lighttable dt_view_lighttable_set_position(darktable.view_manager, dt_collection_image_offset(imgid)); dt_dev_change_image(dev, imgid); } } sqlite3_finalize(stmt); } } static gboolean zoom_key_accel(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data) { dt_develop_t *dev = darktable.develop; int zoom, closeup; float zoom_x, zoom_y; switch(GPOINTER_TO_INT(data)) { case 1: zoom = dt_control_get_dev_zoom(); zoom_x = dt_control_get_dev_zoom_x(); zoom_y = dt_control_get_dev_zoom_y(); closeup = dt_control_get_dev_closeup(); if(zoom == DT_ZOOM_1) closeup ^= 1; dt_dev_check_zoom_bounds(dev, &zoom_x, &zoom_y, DT_ZOOM_1, closeup, NULL, NULL); dt_control_set_dev_zoom(DT_ZOOM_1); dt_control_set_dev_zoom_x(zoom_x); dt_control_set_dev_zoom_y(zoom_y); dt_control_set_dev_closeup(closeup); dt_dev_invalidate(dev); break; case 2: dt_control_set_dev_zoom(DT_ZOOM_FILL); dt_dev_check_zoom_bounds(dev, &zoom_x, &zoom_y, DT_ZOOM_FILL, 0, NULL, NULL); dt_control_set_dev_zoom_x(zoom_x); dt_control_set_dev_zoom_y(zoom_y); dt_control_set_dev_closeup(0); dt_dev_invalidate(dev); break; case 3: dt_control_set_dev_zoom(DT_ZOOM_FIT); dt_control_set_dev_zoom_x(0); dt_control_set_dev_zoom_y(0); dt_control_set_dev_closeup(0); dt_dev_invalidate(dev); break; default: break; } dt_control_queue_redraw_center(); return TRUE; } static gboolean film_strip_key_accel(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data) { dt_lib_module_t *m = darktable.view_manager->proxy.filmstrip.module; gboolean vs = dt_lib_is_visible(m); dt_lib_set_visible(m, !vs); return TRUE; } static gboolean export_key_accel_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data) { dt_develop_t *dev = (dt_develop_t *)data; /* write history before exporting */ dt_dev_write_history(dev); /* export current image */ int max_width = dt_conf_get_int("plugins/lighttable/export/width"); int max_height = dt_conf_get_int("plugins/lighttable/export/height"); char *format_name = dt_conf_get_string("plugins/lighttable/export/format_name"); char *storage_name = dt_conf_get_string("plugins/lighttable/export/storage_name"); int format_index = dt_imageio_get_index_of_format(dt_imageio_get_format_by_name(format_name)); int storage_index = dt_imageio_get_index_of_storage(dt_imageio_get_storage_by_name(storage_name)); gboolean high_quality = dt_conf_get_bool("plugins/lighttable/export/high_quality_processing"); char *style = dt_conf_get_string("plugins/lighttable/export/style"); // darkroom is for single images, so only export the one the user is working on GList *l = g_list_append(NULL, GINT_TO_POINTER(dev->image_storage.id)); dt_control_export(l, max_width, max_height, format_index, storage_index, high_quality, style); g_free(format_name); g_free(storage_name); g_free(style); return TRUE; } static gboolean skip_f_key_accel_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data) { dt_dev_jump_image((dt_develop_t *)data, 1); return TRUE; } static gboolean skip_b_key_accel_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data) { dt_dev_jump_image((dt_develop_t *)data, -1); return TRUE; } static void _darkroom_ui_pipe_finish_signal_callback(gpointer instance, gpointer data) { dt_control_queue_redraw(); } static void _darkroom_ui_favorite_presets_popupmenu(GtkWidget *w, gpointer user_data) { /* create favorites menu and popup */ dt_gui_favorite_presets_menu_show(); /* if we got any styles, lets popup menu for selection */ if(darktable.gui->presets_popup_menu) { gtk_widget_show_all(GTK_WIDGET(darktable.gui->presets_popup_menu)); gtk_menu_popup(darktable.gui->presets_popup_menu, NULL, NULL, NULL, NULL, 0, 0); } else dt_control_log(_("no userdefined presets for favorite modules were found")); } static void _darkroom_ui_apply_style_activate_callback(gchar *name) { dt_control_log(_("applied style `%s' on current image"), name); /* write current history changes so nothing gets lost */ dt_dev_write_history(darktable.develop); /* apply style on image and reload*/ dt_styles_apply_to_image(name, FALSE, darktable.develop->image_storage.id); dt_dev_reload_image(darktable.develop, darktable.develop->image_storage.id); } static void _darkroom_ui_apply_style_popupmenu(GtkWidget *w, gpointer user_data) { /* show styles popup menu */ GList *styles = dt_styles_get_list(""); GtkMenuShell *menu = NULL; if(styles) { menu = GTK_MENU_SHELL(gtk_menu_new()); do { dt_style_t *style = (dt_style_t *)styles->data; GtkWidget *mi = gtk_menu_item_new_with_label(style->name); char *items_string = dt_styles_get_item_list_as_string(style->name); gchar *tooltip = NULL; if(style->description && *style->description) { tooltip = g_strconcat("", style->description, "\n", items_string, NULL); } else { tooltip = g_strdup(items_string); } gtk_widget_set_tooltip_markup(mi, tooltip); gtk_menu_shell_append(menu, mi); g_signal_connect_swapped(GTK_OBJECT(mi), "activate", G_CALLBACK(_darkroom_ui_apply_style_activate_callback), (gpointer)g_strdup(style->name)); gtk_widget_show(mi); g_free(items_string); g_free(tooltip); } while((styles = g_list_next(styles)) != NULL); g_list_free_full(styles, dt_style_free); } /* if we got any styles, lets popup menu for selection */ if(menu) { gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, 0); } else dt_control_log(_("no styles have been created yet")); } static void _overexposed_quickbutton_clicked(GtkWidget *w, gpointer user_data) { dt_develop_t *d = (dt_develop_t *)user_data; d->overexposed.enabled = !d->overexposed.enabled; // dt_dev_reprocess_center(d); dt_dev_reprocess_all(d); } static gboolean _overexposed_close_popup(GtkWidget *widget, GdkEvent *event, gpointer user_data) { dt_develop_t *d = (dt_develop_t *)user_data; g_signal_handler_disconnect(widget, d->overexposed.destroy_signal_handler); d->overexposed.destroy_signal_handler = 0; gtk_widget_hide(d->overexposed.floating_window); return FALSE; } static gboolean _overexposed_show_popup(gpointer user_data) { dt_develop_t *d = (dt_develop_t *)user_data; /** finally move the window next to the button */ gint x, y, wx, wy; gint px, py, window_w, window_h; GtkWidget *window = dt_ui_main_window(darktable.gui->ui); gtk_widget_show_all(d->overexposed.floating_window); gdk_window_get_origin(gtk_widget_get_window(d->overexposed.button), &px, &py); window_w = gdk_window_get_width(gtk_widget_get_window(d->overexposed.floating_window)); window_h = gdk_window_get_height(gtk_widget_get_window(d->overexposed.floating_window)); gtk_widget_translate_coordinates(d->overexposed.button, window, 0, 0, &wx, &wy); x = px + wx - window_w + 5; y = py + wy - window_h - 5; gtk_window_move(GTK_WINDOW(d->overexposed.floating_window), x, y); gtk_window_present(GTK_WINDOW(d->overexposed.floating_window)); // when the mouse moves back over the main window we close the popup. d->overexposed.destroy_signal_handler = g_signal_connect(window, "focus-in-event", G_CALLBACK(_overexposed_close_popup), user_data); return FALSE; } static gboolean _overexposed_quickbutton_pressed(GtkWidget *widget, GdkEvent *event, gpointer user_data) { dt_develop_t *d = (dt_develop_t *)user_data; GdkEventButton *e = (GdkEventButton *)event; if(e->button == 3) { _overexposed_show_popup(user_data); return TRUE; } else { d->overexposed.timeout = g_timeout_add_seconds(1, _overexposed_show_popup, user_data); return FALSE; } } static gboolean _overexposed_quickbutton_released(GtkWidget *widget, GdkEvent *event, gpointer user_data) { dt_develop_t *d = (dt_develop_t *)user_data; if(d->overexposed.timeout > 0) g_source_remove(d->overexposed.timeout); d->overexposed.timeout = 0; return FALSE; } static void colorscheme_callback(GtkWidget *combo, gpointer user_data) { dt_develop_t *d = (dt_develop_t *)user_data; d->overexposed.colorscheme = dt_bauhaus_combobox_get(combo); if(d->overexposed.enabled == FALSE) gtk_button_clicked(GTK_BUTTON(d->overexposed.button)); else // dt_dev_reprocess_center(d); dt_dev_reprocess_all(d); } static void lower_callback(GtkWidget *slider, gpointer user_data) { dt_develop_t *d = (dt_develop_t *)user_data; d->overexposed.lower = dt_bauhaus_slider_get(slider); if(d->overexposed.enabled == FALSE) gtk_button_clicked(GTK_BUTTON(d->overexposed.button)); else // dt_dev_reprocess_center(d); dt_dev_reprocess_all(d); } static void upper_callback(GtkWidget *slider, gpointer user_data) { dt_develop_t *d = (dt_develop_t *)user_data; d->overexposed.upper = dt_bauhaus_slider_get(slider); if(d->overexposed.enabled == FALSE) gtk_button_clicked(GTK_BUTTON(d->overexposed.button)); else // dt_dev_reprocess_center(d); dt_dev_reprocess_all(d); } static gboolean _overexposed_toggle_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data) { gtk_button_clicked(GTK_BUTTON(((dt_develop_t *)data)->overexposed.button)); return TRUE; } static gboolean _brush_size_up_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data) { dt_develop_t *dev = (dt_develop_t *)data; dt_masks_events_mouse_scrolled(dev->gui_module, 0, 0, 0, 0); return TRUE; } static gboolean _brush_size_down_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval, GdkModifierType modifier, gpointer data) { dt_develop_t *dev = (dt_develop_t *)data; dt_masks_events_mouse_scrolled(dev->gui_module, 0, 0, 1, 0); return TRUE; } void enter(dt_view_t *self) { /* connect to ui pipe finished signal for redraw */ dt_control_signal_connect(darktable.signals, DT_SIGNAL_DEVELOP_UI_PIPE_FINISHED, G_CALLBACK(_darkroom_ui_pipe_finish_signal_callback), (gpointer)self); dt_print(DT_DEBUG_CONTROL, "[run_job+] 11 %f in darkroom mode\n", dt_get_wtime()); dt_develop_t *dev = (dt_develop_t *)self->data; if(!dev->form_gui) { dev->form_gui = (dt_masks_form_gui_t *)calloc(1, sizeof(dt_masks_form_gui_t)); } dt_masks_init_form_gui(dev); dev->form_visible = NULL; dev->form_gui->pipe_hash = 0; dev->form_gui->formid = 0; dev->gui_leaving = 0; dev->gui_module = NULL; select_this_image(dev->image_storage.id); dt_control_set_dev_zoom(DT_ZOOM_FIT); dt_control_set_dev_zoom_x(0); dt_control_set_dev_zoom_y(0); dt_control_set_dev_closeup(0); // take a copy of the image struct for convenience. dt_dev_load_image(darktable.develop, dev->image_storage.id); /* * Add view specific tool buttons */ /* create favorite plugin preset popup tool */ GtkWidget *favorite_presets = dtgtk_button_new(dtgtk_cairo_paint_presets, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER); g_object_set(G_OBJECT(favorite_presets), "tooltip-text", _("quick access to presets of your favorites"), (char *)NULL); g_signal_connect(G_OBJECT(favorite_presets), "clicked", G_CALLBACK(_darkroom_ui_favorite_presets_popupmenu), NULL); dt_view_manager_view_toolbox_add(darktable.view_manager, favorite_presets); /* create quick styles popup menu tool */ GtkWidget *styles = dtgtk_button_new(dtgtk_cairo_paint_styles, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER); g_signal_connect(G_OBJECT(styles), "clicked", G_CALLBACK(_darkroom_ui_apply_style_popupmenu), NULL); g_object_set(G_OBJECT(styles), "tooltip-text", _("quick access for applying any of your styles"), (char *)NULL); dt_view_manager_view_toolbox_add(darktable.view_manager, styles); /* create overexposed popup tool */ { // the button dev->overexposed.button = dtgtk_togglebutton_new(dtgtk_cairo_paint_overexposed, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER); g_object_set(G_OBJECT(dev->overexposed.button), "tooltip-text", _("toggle over/under exposed indication\nright click for options"), (char *)NULL); g_signal_connect(G_OBJECT(dev->overexposed.button), "clicked", G_CALLBACK(_overexposed_quickbutton_clicked), dev); g_signal_connect(G_OBJECT(dev->overexposed.button), "button-press-event", G_CALLBACK(_overexposed_quickbutton_pressed), dev); g_signal_connect(G_OBJECT(dev->overexposed.button), "button-release-event", G_CALLBACK(_overexposed_quickbutton_released), dev); dt_view_manager_module_toolbox_add(darktable.view_manager, dev->overexposed.button); // and the popup window // int panel_width = dt_conf_get_int("panel_width"); GtkWidget *window = dt_ui_main_window(darktable.gui->ui); dev->overexposed.floating_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); GtkWidget *frame = gtk_frame_new(NULL); GtkWidget *event_box = gtk_event_box_new(); GtkWidget *alignment = gtk_alignment_new(0.5, 0.5, 1, 1); GtkWidget *vbox = gtk_vbox_new(TRUE, 5); gtk_widget_set_can_focus(dev->overexposed.floating_window, TRUE); gtk_window_set_decorated(GTK_WINDOW(dev->overexposed.floating_window), FALSE); gtk_window_set_type_hint(GTK_WINDOW(dev->overexposed.floating_window), GDK_WINDOW_TYPE_HINT_POPUP_MENU); gtk_window_set_transient_for(GTK_WINDOW(dev->overexposed.floating_window), GTK_WINDOW(window)); gtk_window_set_opacity(GTK_WINDOW(dev->overexposed.floating_window), 0.9); // gtk_widget_set_size_request(frame, panel_width, -1); gtk_widget_set_state(frame, GTK_STATE_SELECTED); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 8, 8, 8, 8); gtk_container_add(GTK_CONTAINER(dev->overexposed.floating_window), frame); gtk_container_add(GTK_CONTAINER(frame), event_box); gtk_container_add(GTK_CONTAINER(event_box), alignment); gtk_container_add(GTK_CONTAINER(alignment), vbox); /** let's fill the encapsulating widgets */ /* color scheme */ GtkWidget *colorscheme = dt_bauhaus_combobox_new(NULL); dt_bauhaus_widget_set_label(colorscheme, NULL, _("color scheme")); dt_bauhaus_combobox_add(colorscheme, _("black & white")); dt_bauhaus_combobox_add(colorscheme, _("red & blue")); dt_bauhaus_combobox_add(colorscheme, _("purple & green")); dt_bauhaus_combobox_set(colorscheme, dev->overexposed.colorscheme); g_object_set(G_OBJECT(colorscheme), "tooltip-text", _("select colors to indicate over/under exposure"), (char *)NULL); g_signal_connect(G_OBJECT(colorscheme), "value-changed", G_CALLBACK(colorscheme_callback), dev); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(colorscheme), TRUE, TRUE, 0); gtk_widget_set_state(colorscheme, GTK_STATE_SELECTED); /* lower */ GtkWidget *lower = dt_bauhaus_slider_new_with_range(NULL, 0.0, 100.0, 0.1, 2.0, 2); dt_bauhaus_slider_set(lower, dev->overexposed.lower); dt_bauhaus_slider_set_format(lower, "%.0f%%"); dt_bauhaus_widget_set_label(lower, NULL, _("lower threshold")); g_object_set(G_OBJECT(lower), "tooltip-text", _("threshold of what shall be considered underexposed"), (char *)NULL); g_signal_connect(G_OBJECT(lower), "value-changed", G_CALLBACK(lower_callback), dev); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(lower), TRUE, TRUE, 0); /* upper */ GtkWidget *upper = dt_bauhaus_slider_new_with_range(NULL, 0.0, 100.0, 0.1, 98.0, 2); dt_bauhaus_slider_set(upper, dev->overexposed.upper); dt_bauhaus_slider_set_format(upper, "%.0f%%"); dt_bauhaus_widget_set_label(upper, NULL, _("upper threshold")); g_object_set(G_OBJECT(upper), "tooltip-text", _("threshold of what shall be considered overexposed"), (char *)NULL); g_signal_connect(G_OBJECT(upper), "value-changed", G_CALLBACK(upper_callback), dev); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(upper), TRUE, TRUE, 0); } /* * add IOP modules to plugin list */ // avoid triggering of events before plugin is ready: darktable.gui->reset = 1; char option[1024]; GList *modules = g_list_last(dev->iop); while(modules) { dt_iop_module_t *module = (dt_iop_module_t *)(modules->data); /* initialize gui if iop have one defined */ if(!dt_iop_is_hidden(module)) { module->gui_init(module); dt_iop_reload_defaults(module); /* add module to right panel */ GtkWidget *expander = dt_iop_gui_get_expander(module); dt_ui_container_add_widget(darktable.gui->ui, DT_UI_CONTAINER_PANEL_RIGHT_CENTER, expander); snprintf(option, sizeof(option), "plugins/darkroom/%s/expanded", module->op); dt_iop_gui_set_expanded(module, dt_conf_get_bool(option), FALSE); } /* setup key accelerators */ module->accel_closures = NULL; if(module->connect_key_accels) module->connect_key_accels(module); dt_iop_connect_common_accels(module); modules = g_list_previous(modules); } darktable.gui->reset = 0; /* signal that darktable.develop is initialized and ready to be used */ dt_control_signal_raise(darktable.signals, DT_SIGNAL_DEVELOP_INITIALIZE); // synch gui and flag gegl pipe as dirty // this is done here and not in dt_read_history, as it would else be triggered before module->gui_init. dt_dev_pop_history_items(dev, dev->history_end); /* ensure that filmstrip shows current image */ dt_view_filmstrip_scroll_to_image(darktable.view_manager, dev->image_storage.id, FALSE); // switch on groups as they where last time: dt_dev_modulegroups_set(dev, dt_conf_get_int("plugins/darkroom/groups")); // make signals work again: darktable.gui->reset = 0; // get last active plugin: gchar *active_plugin = dt_conf_get_string("plugins/darkroom/active"); if(active_plugin) { GList *modules = dev->iop; while(modules) { dt_iop_module_t *module = (dt_iop_module_t *)(modules->data); if(!strcmp(module->op, active_plugin)) dt_iop_request_focus(module); modules = g_list_next(modules); } g_free(active_plugin); } dt_dev_masks_list_change(dev); // image should be there now. float zoom_x, zoom_y; dt_dev_check_zoom_bounds(dev, &zoom_x, &zoom_y, DT_ZOOM_FIT, 0, NULL, NULL); dt_control_set_dev_zoom_x(zoom_x); dt_control_set_dev_zoom_y(zoom_y); /* connect signal for filmstrip image activate */ dt_control_signal_connect(darktable.signals, DT_SIGNAL_VIEWMANAGER_FILMSTRIP_ACTIVATE, G_CALLBACK(_view_darkroom_filmstrip_activate_callback), self); // prefetch next few from first selected image on. dt_view_filmstrip_prefetch(); dt_collection_hint_message(darktable.collection); } void leave(dt_view_t *self) { /* disconnect from filmstrip image activate */ dt_control_signal_disconnect(darktable.signals, G_CALLBACK(_view_darkroom_filmstrip_activate_callback), (gpointer)self); /* disconnect from pipe finish signal */ dt_control_signal_disconnect(darktable.signals, G_CALLBACK(_darkroom_ui_pipe_finish_signal_callback), (gpointer)self); // store groups for next time: dt_conf_set_int("plugins/darkroom/groups", dt_dev_modulegroups_get(darktable.develop)); // store last active plugin: if(darktable.develop->gui_module) dt_conf_set_string("plugins/darkroom/active", darktable.develop->gui_module->op); else dt_conf_set_string("plugins/darkroom/active", ""); dt_develop_t *dev = (dt_develop_t *)self->data; // tag image as changed // TODO: only tag the image when there was a real change. guint tagid = 0; dt_tag_new_from_gui("darktable|changed", &tagid); dt_tag_attach(tagid, dev->image_storage.id); // commit image ops to db dt_dev_write_history(dev); // be sure light table will regenerate the thumbnail: // TODO: only if changed! // if() { dt_mipmap_cache_remove(darktable.mipmap_cache, dev->image_storage.id); // dump new xmp data dt_image_synch_xmp(dev->image_storage.id); } // clear gui. dev->gui_leaving = 1; dt_pthread_mutex_lock(&dev->history_mutex); dt_dev_pixelpipe_cleanup_nodes(dev->pipe); dt_dev_pixelpipe_cleanup_nodes(dev->preview_pipe); while(dev->history) { dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(dev->history->data); // printf("removing history item %d - %s, data %f %f\n", hist->module->instance, hist->module->op, *(float // *)hist->params, *((float *)hist->params+1)); free(hist->params); hist->params = NULL; free(hist); dev->history = g_list_delete_link(dev->history, dev->history); } while(dev->iop) { dt_iop_module_t *module = (dt_iop_module_t *)(dev->iop->data); if(!dt_iop_is_hidden(module)) dt_iop_gui_cleanup_module(module); dt_dev_cleanup_module_accels(module); module->accel_closures = NULL; dt_iop_cleanup_module(module); free(module); dev->iop = g_list_delete_link(dev->iop, dev->iop); } dt_pthread_mutex_unlock(&dev->history_mutex); // cleanup visible masks if(dev->form_gui) { dt_masks_clear_form_gui(dev); free(dev->form_gui); dev->form_gui = NULL; dev->form_visible = NULL; } // take care of the overexposed window if(dev->overexposed.timeout > 0) g_source_remove(dev->overexposed.timeout); if(dev->overexposed.destroy_signal_handler > 0) g_signal_handler_disconnect(dt_ui_main_window(darktable.gui->ui), dev->overexposed.destroy_signal_handler); gtk_widget_destroy(dev->overexposed.floating_window); dt_print(DT_DEBUG_CONTROL, "[run_job-] 11 %f in darkroom mode\n", dt_get_wtime()); } void mouse_leave(dt_view_t *self) { // if we are not hovering over a thumbnail in the filmstrip -> show metadata of opened image. dt_develop_t *dev = (dt_develop_t *)self->data; dt_control_set_mouse_over_id(dev->image_storage.id); // reset any changes the selected plugin might have made. dt_control_change_cursor(GDK_LEFT_PTR); } void mouse_moved(dt_view_t *self, double x, double y, double pressure, int which) { const int32_t capwd = darktable.thumbnail_width; const int32_t capht = darktable.thumbnail_height; dt_develop_t *dev = (dt_develop_t *)self->data; // if we are not hovering over a thumbnail in the filmstrip -> show metadata of opened image. int32_t mouse_over_id = dt_control_get_mouse_over_id(); if(mouse_over_id == -1) { mouse_over_id = dev->image_storage.id; dt_control_set_mouse_over_id(mouse_over_id); } dt_control_t *ctl = darktable.control; const int32_t width_i = self->width; const int32_t height_i = self->height; int32_t offx = 0.0f, offy = 0.0f; if(width_i > capwd) offx = (capwd - width_i) * .5f; if(height_i > capht) offy = (capht - height_i) * .5f; int handled = 0; x += offx; y += offy; if(dev->gui_module && dev->gui_module->request_color_pick != DT_REQUEST_COLORPICK_OFF && ctl->button_down && ctl->button_down_which == 1) { // module requested a color box float zoom_x, zoom_y, bzoom_x, bzoom_y; dt_dev_get_pointer_zoom_pos(dev, x, y, &zoom_x, &zoom_y); dt_dev_get_pointer_zoom_pos(dev, ctl->button_x + offx, ctl->button_y + offy, &bzoom_x, &bzoom_y); if(darktable.lib->proxy.colorpicker.size) { dev->gui_module->color_picker_box[0] = fmaxf(0.0, fminf(.5f + bzoom_x, .5f + zoom_x)); dev->gui_module->color_picker_box[1] = fmaxf(0.0, fminf(.5f + bzoom_y, .5f + zoom_y)); dev->gui_module->color_picker_box[2] = fminf(1.0, fmaxf(.5f + bzoom_x, .5f + zoom_x)); dev->gui_module->color_picker_box[3] = fminf(1.0, fmaxf(.5f + bzoom_y, .5f + zoom_y)); } else { dev->gui_module->color_picker_point[0] = .5f + zoom_x; dev->gui_module->color_picker_point[1] = .5f + zoom_y; } dev->preview_pipe->changed |= DT_DEV_PIPE_SYNCH; dt_dev_invalidate_all(dev); dt_control_queue_redraw(); return; } // masks if(dev->form_visible) handled = dt_masks_events_mouse_moved(dev->gui_module, x, y, pressure, which); if(handled) return; // module if(dev->gui_module && dev->gui_module->mouse_moved) handled = dev->gui_module->mouse_moved(dev->gui_module, x, y, pressure, which); if(handled) return; if(darktable.control->button_down && darktable.control->button_down_which == 1) { // depending on dev_zoom, adjust dev_zoom_x/y. dt_dev_zoom_t zoom = dt_control_get_dev_zoom(); int closeup = dt_control_get_dev_closeup(); int procw, proch; dt_dev_get_processed_size(dev, &procw, &proch); const float scale = dt_dev_get_zoom_scale(dev, zoom, closeup ? 2 : 1, 0); float old_zoom_x, old_zoom_y; old_zoom_x = dt_control_get_dev_zoom_x(); old_zoom_y = dt_control_get_dev_zoom_y(); float zx = old_zoom_x - (1.0 / scale) * (x - ctl->button_x - offx) / procw; float zy = old_zoom_y - (1.0 / scale) * (y - ctl->button_y - offy) / proch; dt_dev_check_zoom_bounds(dev, &zx, &zy, zoom, closeup, NULL, NULL); dt_control_set_dev_zoom_x(zx); dt_control_set_dev_zoom_y(zy); ctl->button_x = x - offx; ctl->button_y = y - offy; dt_dev_invalidate(dev); dt_control_queue_redraw(); } } int button_released(dt_view_t *self, double x, double y, int which, uint32_t state) { const int32_t capwd = darktable.thumbnail_width; const int32_t capht = darktable.thumbnail_height; dt_develop_t *dev = darktable.develop; const int32_t width_i = self->width; const int32_t height_i = self->height; if(width_i > capwd) x += (capwd - width_i) * .5f; if(height_i > capht) y += (capht - height_i) * .5f; int handled = 0; // masks if(dev->form_visible) handled = dt_masks_events_button_released(dev->gui_module, x, y, which, state); if(handled) return handled; // module if(dev->gui_module && dev->gui_module->button_released) handled = dev->gui_module->button_released(dev->gui_module, x, y, which, state); if(handled) return handled; if(which == 1) dt_control_change_cursor(GDK_LEFT_PTR); return 1; } int button_pressed(dt_view_t *self, double x, double y, double pressure, int which, int type, uint32_t state) { const int32_t capwd = darktable.thumbnail_width; const int32_t capht = darktable.thumbnail_height; dt_develop_t *dev = (dt_develop_t *)self->data; const int32_t width_i = self->width; const int32_t height_i = self->height; if(width_i > capwd) x += (capwd - width_i) * .5f; if(height_i > capht) y += (capht - height_i) * .5f; int handled = 0; if(dev->gui_module && dev->gui_module->request_color_pick != DT_REQUEST_COLORPICK_OFF && which == 1) { float zoom_x, zoom_y; dt_dev_get_pointer_zoom_pos(dev, x, y, &zoom_x, &zoom_y); if(darktable.lib->proxy.colorpicker.size) { dev->gui_module->color_picker_box[0] = .5f + zoom_x; dev->gui_module->color_picker_box[1] = .5f + zoom_y; dev->gui_module->color_picker_box[2] = .5f + zoom_x; dev->gui_module->color_picker_box[3] = .5f + zoom_y; } else { dev->gui_module->color_picker_point[0] = .5f + zoom_x; dev->gui_module->color_picker_point[1] = .5f + zoom_y; dev->preview_pipe->changed |= DT_DEV_PIPE_SYNCH; dt_dev_invalidate_all(dev); } dt_control_queue_redraw(); return 1; } // masks if(dev->form_visible) handled = dt_masks_events_button_pressed(dev->gui_module, x, y, pressure, which, type, state); if(handled) return handled; // module if(dev->gui_module && dev->gui_module->button_pressed) handled = dev->gui_module->button_pressed(dev->gui_module, x, y, pressure, which, type, state); if(handled) return handled; if(which == 1 && type == GDK_2BUTTON_PRESS) return 0; if(which == 1) { dt_control_change_cursor(GDK_HAND1); return 1; } if(which == 2) { // zoom to 1:1 2:1 and back dt_dev_zoom_t zoom; int closeup, procw, proch; float zoom_x, zoom_y; zoom = dt_control_get_dev_zoom(); closeup = dt_control_get_dev_closeup(); zoom_x = dt_control_get_dev_zoom_x(); zoom_y = dt_control_get_dev_zoom_y(); dt_dev_get_processed_size(dev, &procw, &proch); const float scale = dt_dev_get_zoom_scale(dev, zoom, closeup ? 2 : 1, 0); zoom_x += (1.0 / scale) * (x - .5f * dev->width) / procw; zoom_y += (1.0 / scale) * (y - .5f * dev->height) / proch; if(zoom == DT_ZOOM_1) { if(!closeup) closeup = 1; else { zoom = DT_ZOOM_FIT; zoom_x = zoom_y = 0.0f; closeup = 0; } } else zoom = DT_ZOOM_1; dt_dev_check_zoom_bounds(dev, &zoom_x, &zoom_y, zoom, closeup, NULL, NULL); dt_control_set_dev_zoom(zoom); dt_control_set_dev_closeup(closeup); dt_control_set_dev_zoom_x(zoom_x); dt_control_set_dev_zoom_y(zoom_y); dt_dev_invalidate(dev); return 1; } return 0; } void scrolled(dt_view_t *self, double x, double y, int up, int state) { const int32_t capwd = darktable.thumbnail_width; const int32_t capht = darktable.thumbnail_height; dt_develop_t *dev = (dt_develop_t *)self->data; const int32_t width_i = self->width; const int32_t height_i = self->height; if(width_i > capwd) x += (capwd - width_i) * .5f; if(height_i > capht) y += (capht - height_i) * .5f; int handled = 0; // masks if(dev->form_visible) handled = dt_masks_events_mouse_scrolled(dev->gui_module, x, y, up, state); if(handled) return; // module if(dev->gui_module && dev->gui_module->scrolled) handled = dev->gui_module->scrolled(dev->gui_module, x, y, up, state); if(handled) return; // free zoom dt_dev_zoom_t zoom; int closeup, procw, proch; float zoom_x, zoom_y; zoom = dt_control_get_dev_zoom(); closeup = dt_control_get_dev_closeup(); zoom_x = dt_control_get_dev_zoom_x(); zoom_y = dt_control_get_dev_zoom_y(); dt_dev_get_processed_size(dev, &procw, &proch); float scale = dt_dev_get_zoom_scale(dev, zoom, closeup ? 2.0 : 1.0, 0); const float fitscale = dt_dev_get_zoom_scale(dev, DT_ZOOM_FIT, 1.0, 0); float oldscale = scale; // offset from center now (current zoom_{x,y} points there) float mouse_off_x = x - .5 * dev->width, mouse_off_y = y - .5 * dev->height; zoom_x += mouse_off_x / (procw * scale); zoom_y += mouse_off_y / (proch * scale); zoom = DT_ZOOM_FREE; closeup = 0; if(up) { if(scale == 1.0f && !((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)) return; if(scale >= 2.0f) return; else if(scale < fitscale) scale += .05f * (1.0f - fitscale); else scale += .1f * (1.0f - fitscale); } else { if(scale == fitscale && !((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)) return; else if(scale < 0.5 * fitscale) return; else if(scale <= fitscale) scale -= .05f * (1.0f - fitscale); else scale -= .1f * (1.0f - fitscale); } // we want to be sure to stop at 1:1 and FIT levels if((scale - 1.0) * (oldscale - 1.0) < 0) scale = 1.0f; if((scale - fitscale) * (oldscale - fitscale) < 0) scale = fitscale; scale = fmaxf(fminf(scale, 2.0f), 0.5 * fitscale); // for 200% zoom we want pixel doubling instead of interpolation if(scale > 1.9999f) { scale = 1.0f; // don't interpolate closeup = 1; // enable closeup mode (pixel doubling) } dt_control_set_dev_zoom_scale(scale); if(fabsf(scale - 1.0f) < 0.001f) zoom = DT_ZOOM_1; if(fabsf(scale - fitscale) < 0.001f) zoom = DT_ZOOM_FIT; zoom_x -= mouse_off_x / (procw * scale); zoom_y -= mouse_off_y / (proch * scale); dt_dev_check_zoom_bounds(dev, &zoom_x, &zoom_y, zoom, closeup, NULL, NULL); dt_control_set_dev_zoom(zoom); dt_control_set_dev_closeup(closeup); dt_control_set_dev_zoom_x(zoom_x); dt_control_set_dev_zoom_y(zoom_y); dt_dev_invalidate(dev); dt_control_queue_redraw(); } void border_scrolled(dt_view_t *view, double x, double y, int which, int up) { dt_develop_t *dev = (dt_develop_t *)view->data; dt_dev_zoom_t zoom; int closeup; float zoom_x, zoom_y; zoom = dt_control_get_dev_zoom(); closeup = dt_control_get_dev_closeup(); zoom_x = dt_control_get_dev_zoom_x(); zoom_y = dt_control_get_dev_zoom_y(); if(which > 1) { if(up) zoom_x -= 0.02; else zoom_x += 0.02; } else { if(up) zoom_y -= 0.02; else zoom_y += 0.02; } dt_dev_check_zoom_bounds(dev, &zoom_x, &zoom_y, zoom, closeup, NULL, NULL); dt_control_set_dev_zoom_x(zoom_x); dt_control_set_dev_zoom_y(zoom_y); dt_dev_invalidate(dev); dt_control_queue_redraw(); } int key_pressed(dt_view_t *self, guint key, guint state) { return 1; } void configure(dt_view_t *self, int wd, int ht) { dt_develop_t *dev = (dt_develop_t *)self->data; dt_dev_configure(dev, wd, ht); } void init_key_accels(dt_view_t *self) { // Film strip shortcuts dt_accel_register_view(self, NC_("accel", "toggle film strip"), GDK_KEY_f, GDK_CONTROL_MASK); // Zoom shortcuts dt_accel_register_view(self, NC_("accel", "zoom close-up"), GDK_KEY_1, GDK_MOD1_MASK); dt_accel_register_view(self, NC_("accel", "zoom fill"), GDK_KEY_2, GDK_MOD1_MASK); dt_accel_register_view(self, NC_("accel", "zoom fit"), GDK_KEY_3, GDK_MOD1_MASK); // enable shortcut to export with current export settings: dt_accel_register_view(self, NC_("accel", "export"), GDK_KEY_e, GDK_CONTROL_MASK); // Shortcut to skip images dt_accel_register_view(self, NC_("accel", "image forward"), GDK_KEY_space, 0); dt_accel_register_view(self, NC_("accel", "image back"), GDK_KEY_BackSpace, 0); // toggle overexposure indication dt_accel_register_view(self, NC_("accel", "overexposed"), GDK_KEY_o, 0); // brush size +/- dt_accel_register_view(self, NC_("accel", "brush larger"), GDK_KEY_bracketright, 0); dt_accel_register_view(self, NC_("accel", "brush smaller"), GDK_KEY_bracketleft, 0); } void connect_key_accels(dt_view_t *self) { GClosure *closure; // Film strip shortcuts closure = g_cclosure_new(G_CALLBACK(film_strip_key_accel), (gpointer)self, NULL); dt_accel_connect_view(self, "toggle film strip", closure); // Zoom shortcuts closure = g_cclosure_new(G_CALLBACK(zoom_key_accel), GINT_TO_POINTER(1), NULL); dt_accel_connect_view(self, "zoom close-up", closure); closure = g_cclosure_new(G_CALLBACK(zoom_key_accel), GINT_TO_POINTER(2), NULL); dt_accel_connect_view(self, "zoom fill", closure); closure = g_cclosure_new(G_CALLBACK(zoom_key_accel), GINT_TO_POINTER(3), NULL); dt_accel_connect_view(self, "zoom fit", closure); // enable shortcut to export with current export settings: closure = g_cclosure_new(G_CALLBACK(export_key_accel_callback), (gpointer)self->data, NULL); dt_accel_connect_view(self, "export", closure); // Shortcut to skip images closure = g_cclosure_new(G_CALLBACK(skip_f_key_accel_callback), (gpointer)self->data, NULL); dt_accel_connect_view(self, "image forward", closure); closure = g_cclosure_new(G_CALLBACK(skip_b_key_accel_callback), (gpointer)self->data, NULL); dt_accel_connect_view(self, "image back", closure); // toggle overexposure indication closure = g_cclosure_new(G_CALLBACK(_overexposed_toggle_callback), (gpointer)self->data, NULL); dt_accel_connect_view(self, "overexposed", closure); // brush size +/- closure = g_cclosure_new(G_CALLBACK(_brush_size_up_callback), (gpointer)self->data, NULL); dt_accel_connect_view(self, "brush larger", closure); closure = g_cclosure_new(G_CALLBACK(_brush_size_down_callback), (gpointer)self->data, NULL); dt_accel_connect_view(self, "brush smaller", closure); } // 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-space on;