src/libs/print_settings.c
Rama master · réplica de lectura del repositorio original.
/*
This file is part of darktable,
Copyright (C) 2014-2026 darktable developers.
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
darktable is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with darktable. If not, see <http://www.gnu.org/licenses/>.
*/
#include "common/gdk_event_utils.h"
#include <glib.h>
#include "bauhaus/bauhaus.h"
#include "common/collection.h"
#include "common/colorspaces.h"
#include "common/cups_print.h"
#include "common/image_cache.h"
#include "common/metadata.h"
#include "common/pdf.h"
#include "common/printprof.h"
#include "common/printing.h"
#include "common/styles.h"
#include "common/tags.h"
#include "common/variables.h"
#include "control/jobs.h"
#include "dtgtk/resetlabel.h"
#include "dtgtk/stylemenu.h"
#include "gui/accelerators.h"
#include "gui/drag_and_drop.h"
#include "gui/gtk.h"
#include "libs/lib.h"
#include "libs/lib_api.h"
#ifdef _WIN32
#include "win/win32_print.h"
#include <windows.h>
#include <prntvpt.h> // PTOpenProvider, PTConvertDevModeToPrintTicket
#include <objidl.h> // IStream
#endif
#ifdef GDK_WINDOWING_WIN32
#include <gdk/gdkwin32.h>
#endif
DT_MODULE(4)
#define PRINT_CONFIG_PREFIX "plugins/print/print/"
const char *name(dt_lib_module_t *self)
{
return _("print settings");
}
dt_view_type_flags_t views(dt_lib_module_t *self)
{
return DT_VIEW_PRINT;
}
uint32_t container(dt_lib_module_t *self)
{
return DT_UI_CONTAINER_PANEL_RIGHT_CENTER;
}
typedef enum _set_controls
{
BOX_LEFT = 1 << 0,
BOX_RIGHT = 1 << 1,
BOX_TOP = 1 << 2,
BOX_BOTTOM = 1 << 3,
BOX_TOP_LEFT = BOX_LEFT | BOX_TOP,
BOX_TOP_RIGHT = BOX_RIGHT | BOX_TOP,
BOX_BOTTOM_LEFT = BOX_LEFT | BOX_BOTTOM,
BOX_BOTTOM_RIGHT = BOX_RIGHT | BOX_BOTTOM,
BOX_ALL = BOX_LEFT | BOX_RIGHT | BOX_TOP | BOX_BOTTOM
} dt_box_control_set;
typedef enum _unit_t
{
UNIT_MM = 0,
UNIT_CM,
UNIT_IN,
UNIT_N // needs to be the last one
} _unit_t;
static const float units[UNIT_N] = { 1.0f, 0.1f, 1.0f/25.4f };
static const gchar *_unit_names[] = { N_("mm"), N_("cm"), N_("inch"), NULL };
typedef struct dt_lib_print_settings_t
{
GtkWidget *profile, *intent, *style, *style_mode, *papers, *media;
GtkWidget *printers, *orientation, *pprofile, *pintent;
GtkWidget *width, *height, *black_point_compensation;
GtkWidget *info;
GtkWidget *b_x, *b_y;
GtkWidget *b_width, *b_height;
GtkWidget *del;
GtkWidget *grid, *grid_size, *snap_grid;
GtkWidget *borderless;
GList *profiles;
GtkButton *print_button;
GtkToggleButton *lock_button;
GtkWidget *b_top, *b_bottom, *b_left, *b_right;
GtkDarktableToggleButton *dtba[9]; // Alignment buttons
GList *paper_list, *media_list;
gboolean lock_activated;
dt_print_info_t prt;
dt_images_box imgs;
_unit_t unit;
int v_intent, v_pintent;
int v_icctype, v_picctype;
char *v_iccprofile, *v_piccprofile, *v_style;
gboolean v_style_append, v_black_point_compensation;
gboolean busy;
// for adding new area
gboolean creation;
gboolean dragging;
float x1, y1, x2, y2;
int selected; // selected area in imgs.box
int last_selected; // last selected area to edit
dt_box_control_set sel_controls; // which border/corner is selected
float click_pos_x, click_pos_y;
gboolean has_changed;
GList *printer_list;
dt_pthread_mutex_t printer_list_mutex;
// for windows side printing support
#ifdef _WIN32
char *waiting_for_printer;
GtkButton *print_settings_button; // button to open printer settings (windows only)
struct dt_win32_print_ctx_t *settings_ctx;
#endif
} dt_lib_print_settings_t;
typedef struct dt_lib_print_job_t
{
gchar *job_title;
dt_print_info_t prt;
gchar* style;
gboolean style_append, black_point_compensation;
dt_colorspaces_color_profile_type_t buf_icc_type, p_icc_type;
gchar *buf_icc_profile, *p_icc_profile;
dt_iop_color_intent_t buf_icc_intent, p_icc_intent;
dt_images_box imgs;
uint16_t *buf; // ??? should be removed
dt_pdf_page_t *pdf_page;
char pdf_filename[PATH_MAX];
#ifdef _WIN32
void *print_ticket_data; // Windows only: print ticket data for the job
size_t print_ticket_size; // Windows only: size of the print ticket data
gboolean is_color_device; // Windows only: TRUE if the printer is color-capable
#endif
} dt_lib_print_job_t;
typedef struct dt_lib_export_profile_t
{
dt_colorspaces_color_profile_type_t type; // filename is only used for type DT_COLORSPACE_FILE
char filename[512]; // icc file name
char name[512]; // product name
int pos, ppos; // position in combo boxen
} dt_lib_export_profile_t;
typedef struct _dialog_description
{
const char *name;
} dialog_description_t;
static void _update_slider(dt_lib_print_settings_t *ps);
static void _width_changed(GtkWidget *widget, gpointer user_data);
static void _height_changed(GtkWidget *widget, gpointer user_data);
static void _x_changed(GtkWidget *widget, gpointer user_data);
static void _y_changed(GtkWidget *widget, gpointer user_data);
int position(const dt_lib_module_t *self)
{
return 990;
}
/* get paper dimension for the orientation (in mm) */
static void _get_page_dimension(dt_print_info_t *prt,
float *width,
float *height)
{
if(prt->page.landscape)
{
*width = prt->paper.height;
*height = prt->paper.width;
}
else
{
*width = prt->paper.width;
*height = prt->paper.height;
}
}
static void _precision_by_unit(const _unit_t unit,
int *n_digits,
float *incr,
char **format)
{
// this gives us these precisions
// unit precision increment
// mm 1 1
// cm 0.1 0.1
// in 0.01 0.05
//
// This allows for >= 1mm precision display regardless of unit, and
// allows for entering common fractions (e.g. 1/4 as .25) for
// inches. Increment is kept to 1mm except in the cases of inches,
// where we round up from 0.03937 (1mm) to 0.05 to keep to a factor
// of a power of ten.
*n_digits = ceilf(log10f(1.0f / units[unit]));
if(incr)
{
*incr = roundf(units[unit] * 20.0f) / 20.0f;
}
if(format)
{
*format = g_strdup_printf("%%.%df", *n_digits);
}
}
// unit conversion
static float _to_mm(dt_lib_print_settings_t *ps,
const double value)
{
return value / units[ps->unit];
}
// horizontal mm to pixels
static float _mm_to_hscreen(dt_lib_print_settings_t *ps,
const float value,
const gboolean offset)
{
float width, height;
_get_page_dimension(&ps->prt, &width, &height);
return (offset ? ps->imgs.screen.page.x : 0)
+ (ps->imgs.screen.page.width * value / width);
}
// vertical mm to pixels
static float _mm_to_vscreen(dt_lib_print_settings_t *ps,
const float value,
const gboolean offset)
{
float width, height;
_get_page_dimension(&ps->prt, &width, &height);
return (offset ? ps->imgs.screen.page.y : 0)
+ (ps->imgs.screen.page.height * value / height);
}
static float _hscreen_to_mm(dt_lib_print_settings_t *ps,
const float value,
const gboolean offset)
{
float width, height;
_get_page_dimension(&ps->prt, &width, &height);
return width * (value - (offset ? ps->imgs.screen.page.x : 0.0f))
/ ps->imgs.screen.page.width;
}
static float _vscreen_to_mm(dt_lib_print_settings_t *ps,
const float value,
const gboolean offset)
{
float width, height;
_get_page_dimension(&ps->prt, &width, &height);
return height * (value - (offset ? ps->imgs.screen.page.y : 0.0f))
/ ps->imgs.screen.page.height;
}
static inline float _percent_unit_of(dt_lib_print_settings_t *ps,
const float ref,
const float value)
{
return value * ref * units[ps->unit];
}
// callbacks for in-memory export
typedef struct dt_print_format_t
{
dt_imageio_module_data_t head;
int bpp;
dt_lib_print_job_t *params;
} dt_print_format_t;
static int bpp(dt_imageio_module_data_t *data)
{
const dt_print_format_t *d = (dt_print_format_t *)data;
return d->bpp;
}
static int levels(dt_imageio_module_data_t *data)
{
const dt_print_format_t *d = (dt_print_format_t *)data;
return IMAGEIO_RGB | (d->bpp == 8 ? IMAGEIO_INT8 : IMAGEIO_INT16);
}
static const char *mime(dt_imageio_module_data_t *data)
{
return "memory";
}
static int write_image(dt_imageio_module_data_t *data,
const char *filename,
const void *in,
dt_colorspaces_color_profile_type_t over_type,
const char *over_filename,
void *exif, int exif_len,
const dt_imgid_t imgid,
const int num,
const int total,
dt_dev_pixelpipe_t *pipe,
const gboolean export_masks)
{
dt_print_format_t *d = (dt_print_format_t *)data;
d->params->buf =
(uint16_t *)g_try_malloc((size_t)3 * (d->bpp == 8?1:2) * d->head.width * d->head.height);
if(!d->params->buf)
{
dt_print(DT_DEBUG_ALWAYS, "[print] unable to allocate memory for image %s", filename);
return 1;
}
if(d->bpp == 8)
{
const uint8_t *in_ptr = (const uint8_t *)in;
uint8_t *out_ptr = (uint8_t *)d->params->buf;
for(int y = 0; y < d->head.height; y++)
{
for(int x = 0; x < d->head.width; x++, in_ptr += 4, out_ptr += 3)
memcpy(out_ptr, in_ptr, 3);
}
}
else
{
const uint16_t *in_ptr = (const uint16_t *)in;
uint16_t *out_ptr = (uint16_t *)d->params->buf;
for(int y = 0; y < d->head.height; y++)
{
for(int x = 0; x < d->head.width; x++, in_ptr += 4, out_ptr += 3)
memcpy(out_ptr, in_ptr, 6);
}
}
return 0;
}
// export image imgid with given max_width & max_height, set iwidth &
// iheight with the final image size as exported.
static int _export_image(dt_job_t *job, dt_image_box *img)
{
dt_lib_print_job_t *params = dt_control_job_get_params(job);
dt_imageio_module_format_t buf;
buf.mime = mime;
buf.levels = levels;
buf.bpp = bpp;
buf.write_image = write_image;
dt_print_format_t dat;
dat.head.max_width = img->max_width;
dat.head.max_height = img->max_height;
dat.head.style[0] = '\0';
dat.head.style_append = params->style_append;
dat.bpp = *params->p_icc_profile ? 16 : 8; // set to 16bit when a profile is to be applied
dat.params = params;
if(params->style) g_strlcpy(dat.head.style, params->style, sizeof(dat.head.style));
// let the user know something is happening
dt_control_job_set_progress(job, 0.05);
dt_control_log(_("processing `%s' for `%s'"),
params->job_title, params->prt.printer.name);
const gboolean high_quality = TRUE;
const gboolean upscale = TRUE;
const gboolean export_masks = FALSE;
const gboolean is_scaling = FALSE;
const double scale_factor = 1.0;
dt_imageio_export_with_flags
(img->imgid, "unused", &buf, (dt_imageio_module_data_t *)&dat, TRUE, FALSE,
high_quality, upscale, is_scaling, scale_factor, FALSE, NULL,
FALSE, export_masks, params->buf_icc_type,
params->buf_icc_profile, params->buf_icc_intent, NULL, NULL, 1, 1, NULL, -1);
img->exp_width = dat.head.width;
img->exp_height = dat.head.height;
// we have the exported buffer, let's apply the printer profile
const dt_colorspaces_color_profile_t *buf_profile =
dt_colorspaces_get_output_profile(img->imgid,
params->buf_icc_type,
params->buf_icc_profile);
if(*params->p_icc_profile)
{
const dt_colorspaces_color_profile_t *pprof =
dt_colorspaces_get_profile(params->p_icc_type, params->p_icc_profile,
DT_PROFILE_DIRECTION_OUT);
if(!pprof)
{
dt_control_log(_("cannot open printer profile `%s'"), params->p_icc_profile);
dt_print(DT_DEBUG_ALWAYS,
"cannot open printer profile `%s'",
params->p_icc_profile);
dt_control_queue_redraw();
return 1;
}
else
{
if(!buf_profile || !buf_profile->profile)
{
dt_control_log(_("error getting output profile for image %d"), img->imgid);
dt_print(DT_DEBUG_ALWAYS,
"error getting output profile for image %d",
img->imgid);
dt_control_queue_redraw();
return 1;
}
if(dt_apply_printer_profile
((void **)&(params->buf), dat.head.width, dat.head.height,
dat.bpp, buf_profile->profile,
pprof->profile, params->p_icc_intent, params->black_point_compensation))
{
dt_control_log(_("cannot apply printer profile `%s'"),
params->p_icc_profile);
dt_print(DT_DEBUG_ALWAYS,
"cannot apply printer profile `%s'",
params->p_icc_profile);
dt_control_queue_redraw();
return 1;
}
}
}
// In _export_image(): Print actual buffer address and size right before assigning to img->buf
img->buf = params->buf;
img->img_bpp = dat.bpp; // Store the image bitdepth with the image being sent to print job
params->buf = NULL;
return 0;
}
#ifndef _WIN32
static void _create_pdf(dt_job_t *job,
dt_images_box imgs,
const float width,
const float height)
{
dt_lib_print_job_t *params = dt_control_job_get_params(job);
const float page_width = dt_pdf_mm_to_point(width);
const float page_height = dt_pdf_mm_to_point(height);
int icc_id = 0;
dt_pdf_image_t *pdf_image[MAX_IMAGE_PER_PAGE];
// create the PDF page
dt_pdf_t *pdf = dt_pdf_start(params->pdf_filename, page_width, page_height,
params->prt.printer.resolution,
DT_PDF_STREAM_ENCODER_FLATE);
#ifdef __APPLE__
// On macOS, embed the printer ICC profile in the PDF so the
// already-converted pixel data is correctly tagged with its actual
// color space. Without this, cgpdftoraster assumes DeviceRGB = sRGB
// and misinterprets the data.
//
// On Linux this must NOT be done — Poppler (inside pdftoraster) would
// see the ICCBased color space and convert the data back to sRGB,
// undoing the LittleCMS conversion. Linux uses cm-calibration instead.
if(params->p_icc_profile && *params->p_icc_profile)
icc_id = dt_pdf_add_icc(pdf, params->p_icc_profile);
#endif
int32_t count = 0;
for(int k=0; k<imgs.count; k++)
{
const int resolution = params->prt.printer.resolution;
dt_image_box *box = &imgs.box[k];
//If image is 16bit, it needs to be converted to big endian before sending for PDF creation
if(box->img_bpp == 16)
{
const size_t total_samples = (size_t)3 * box->exp_width * box->exp_height;
uint16_t *tmp = g_malloc(total_samples * sizeof(uint16_t));
const uint16_t *src = box->buf;
for(size_t i = 0; i < total_samples; i++)
{
tmp[i] = GUINT16_TO_BE(src[i]);
}
g_free(box->buf);
box->buf = tmp;
}
if(dt_is_valid_imgid(box->imgid))
{
pdf_image[count] =
dt_pdf_add_image(pdf,
box->buf,
box->exp_width, box->exp_height,
box->img_bpp, icc_id, 0.0);
// PDF bounding-box has origin on bottom-left
pdf_image[count]->bb_x = dt_pdf_pixel_to_point(box->print.x, resolution);
pdf_image[count]->bb_y = dt_pdf_pixel_to_point(box->print.y, resolution);
pdf_image[count]->bb_width = dt_pdf_pixel_to_point(box->print.width, resolution);
pdf_image[count]->bb_height = dt_pdf_pixel_to_point(box->print.height, resolution);
count++;
}
}
params->pdf_page = dt_pdf_add_page(pdf, pdf_image, count);
dt_pdf_finish(pdf, ¶ms->pdf_page, 1);
// now releases all the buf
for(int k=0; k<imgs.count; k++)
{
dt_image_box *box = &imgs.box[k];
g_free(box->buf);
box->buf = NULL;
}
}
#endif
static void _fill_box_values(dt_lib_print_settings_t *ps)
{
float x = 0.0f, y = 0.0f, swidth = 0.0f, sheight = 0.0f;
if(ps->last_selected != -1)
{
dt_image_box *box = &ps->imgs.box[ps->last_selected];
float width, height;
_get_page_dimension(&ps->prt, &width, &height);
x = _percent_unit_of(ps, width, box->pos.x);
y = _percent_unit_of(ps, height, box->pos.y);
swidth = _percent_unit_of(ps, width, box->pos.width);
sheight = _percent_unit_of(ps, height, box->pos.height);
for(int i=0; i<9; i++)
{
DT_ENTER_GUI_UPDATE();
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ps->dtba[i]), (i == box->alignment));
DT_LEAVE_GUI_UPDATE();
}
}
DT_ENTER_GUI_UPDATE();
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_x), x);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_y), y);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_width), swidth);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_height), sheight);
DT_LEAVE_GUI_UPDATE();
}
static int _export_and_setup_pos(dt_job_t *job,
dt_image_box *img,
const int32_t idx)
{
dt_lib_print_job_t *params = dt_control_job_get_params(job);
float width, height;
_get_page_dimension(¶ms->prt, &width, &height);
dt_printing_setup_page(¶ms->imgs, width, height, params->prt.printer.resolution);
dt_print(DT_DEBUG_PRINT, "[print] max image size %d x %d (at resolution %d)",
img->max_width, img->max_height, params->prt.printer.resolution);
if(_export_image(job, img))
return 1;
dt_printing_setup_image(¶ms->imgs, idx,
img->imgid, img->exp_width, img->exp_height, img->alignment);
return 0;
}
static int _print_job_run(dt_job_t *job)
{
dt_lib_print_job_t *params = dt_control_job_get_params(job);
// get first image on a box, needed as print leader
dt_imgid_t imgid = NO_IMGID;
// compute the needed size for picture for the given printer resolution
for(int k=0; k<params->imgs.count; k++)
{
if(dt_is_valid_imgid(params->imgs.box[k].imgid))
{
if(!dt_is_valid_imgid(imgid)) imgid = params->imgs.box[k].imgid;
if(_export_and_setup_pos(job, ¶ms->imgs.box[k], k))
return 1;
}
}
if(dt_control_job_get_state(job) == DT_JOB_STATE_CANCELLED)
return 0;
dt_control_job_set_progress(job, 0.9);
#ifdef _WIN32
// on Windows, leverage the XPS print api
float width, height;
_get_page_dimension(¶ms->prt, &width, &height);
// pull icc profile info to pass to win32_print.c to include in the job sent to the printer
void *icc_data = NULL;
size_t icc_size = 0;
{
dt_colorspaces_color_profile_type_t out_type =
*params->prt.printer.profile ? params->p_icc_type : params->buf_icc_type;
const char *out_filename =
*params->prt.printer.profile ? params->p_icc_profile : params->buf_icc_profile;
const dt_colorspaces_color_profile_t *cprof =
dt_colorspaces_get_output_profile(imgid, out_type, out_filename);
if(cprof && cprof->profile)
{
cmsUInt32Number len = 0;
cmsSaveProfileToMem(cprof->profile, NULL, &len);
if(len > 0)
{
icc_data = malloc(len);
if(icc_data) cmsSaveProfileToMem(cprof->profile, icc_data, &len);
icc_size = (size_t)len;
}
}
}
// send the print job to win32_print.c
if(!dt_win_print_file(¶ms->imgs, params->job_title, ¶ms->prt,
params->print_ticket_data, params->print_ticket_size,
icc_data, icc_size, params->is_color_device, width, height))
{
dt_control_log(_("Windows printing failed"));
}
free(icc_data);
#else
// on Linux/OSX, print directly as a PDF
dt_loc_get_tmp_dir(params->pdf_filename, sizeof(params->pdf_filename));
g_strlcat(params->pdf_filename, "/pf.XXXXXX.pdf", sizeof(params->pdf_filename));
const gint fd = g_mkstemp(params->pdf_filename);
if(fd == -1)
{
dt_control_log(_("failed to create temporary PDF for printing"));
dt_print(DT_DEBUG_ALWAYS, "failed to create temporary PDF for printing");
return 1;
}
close(fd);
float width, height;
_get_page_dimension(¶ms->prt, &width, &height);
_create_pdf(job, params->imgs, width, height);
if(dt_control_job_get_state(job) == DT_JOB_STATE_CANCELLED) return 0;
dt_control_job_set_progress(job, 0.95);
// send to CUPS
dt_print_file(imgid, params->pdf_filename, params->job_title, ¶ms->prt);
#endif
dt_control_job_set_progress(job, 1.0);
// add tag for this image
char tag[256] = { 0 };
guint tagid = 0;
snprintf (tag, sizeof(tag), "darktable|printed|%s", params->prt.printer.name);
dt_tag_new(tag, &tagid);
for(int k=0; k < params->imgs.count; k++)
{
if(dt_is_valid_imgid(params->imgs.box[k].imgid))
{
if(dt_tag_attach(tagid, params->imgs.box[k].imgid, FALSE, FALSE))
DT_CONTROL_SIGNAL_RAISE(DT_SIGNAL_TAG_CHANGED);
/* register print timestamp in cache */
dt_image_cache_set_print_timestamp(params->imgs.box[k].imgid);
}
}
return 0;
}
static void _page_new_area_clicked(GtkWidget *widget, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
if(ps->imgs.count == MAX_IMAGE_PER_PAGE)
{
dt_control_log(_("maximum image per page reached"));
return;
}
dt_control_change_cursor("cell");
ps->creation = TRUE;
ps->has_changed = TRUE;
}
static void _page_clear_area_clicked(GtkWidget *widget, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
ps->has_changed = TRUE;
dt_printing_clear_boxes(&ps->imgs);
gtk_widget_set_sensitive(ps->del, FALSE);
dt_control_queue_redraw_center();
}
static void _page_delete_area(const dt_lib_module_t *self,
const int box_index)
{
dt_lib_print_settings_t *ps = self->data;
if(box_index == -1) return;
for(int k=box_index; k<MAX_IMAGE_PER_PAGE-1; k++)
{
memcpy(&ps->imgs.box[k], &ps->imgs.box[k+1], sizeof(dt_image_box));
}
ps->last_selected = -1;
ps->selected = -1;
dt_printing_clear_box(&ps->imgs.box[MAX_IMAGE_PER_PAGE-1]);
ps->imgs.count--;
if(ps->imgs.count > 0)
ps->selected = 0;
else
gtk_widget_set_sensitive(ps->del, FALSE);
_fill_box_values(ps);
ps->has_changed = TRUE;
dt_control_queue_redraw_center();
}
static void _page_delete_area_clicked(GtkWidget *widget, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
_page_delete_area(self, ps->last_selected);
}
static void _print_job_cleanup(void *p)
{
dt_lib_print_job_t *params = p;
if(params->pdf_filename[0]) g_unlink(params->pdf_filename);
free(params->pdf_page);
free(params->buf);
g_free(params->style);
g_free(params->buf_icc_profile);
g_free(params->p_icc_profile);
g_free(params->job_title);
#ifdef _WIN32
free(params->print_ticket_data);
#endif
free(params);
}
#ifdef _WIN32
// idle callback to force a redraw once GTK is back in control
static gboolean _redraw_later(gpointer user_data)
{
GtkWidget *widget = GTK_WIDGET(user_data);
if(GTK_IS_WIDGET(widget))
gtk_widget_queue_draw(widget);
return G_SOURCE_REMOVE; // run once, then remove
}
static const dt_paper_info_t *_find_paper_by_dm(GList *paper_list, const dt_paper_info_t *from_dm)
{
// prefer matching by driver ID — unambiguous, resolves same-dimension
// variants (e.g. bordered/borderless) correctly
if(from_dm->dm_paper_id != 0)
{
for(GList *l = paper_list; l; l = g_list_next(l))
{
const dt_paper_info_t *p = l->data;
if(p->dm_paper_id == from_dm->dm_paper_id) return p;
}
}
// fall back to dimension matching if no ID, or ID didn't match anything
for(GList *l = paper_list; l; l = g_list_next(l))
{
const dt_paper_info_t *p = l->data;
if(fabs(p->width - from_dm->width) < 0.5 && fabs(p->height - from_dm->height) < 0.5)
return p;
}
return NULL;
}
static void _sync_print_widgets_from_pinfo(dt_lib_print_settings_t *ps)
{
dt_bauhaus_combobox_set(ps->orientation, ps->prt.page.landscape ? 1 : 0);
const dt_paper_info_t *matched = _find_paper_by_dm(ps->paper_list, &ps->prt.paper);
if(matched)
{
memcpy(&ps->prt.paper, matched, sizeof(dt_paper_info_t)); // pulls in the correct name/common_name/id
dt_bauhaus_combobox_set_from_text(ps->papers, matched->common_name);
}
}
static void
_print_settings_button_clicked(GtkWidget *widget, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = (dt_lib_print_settings_t *)self->data;
if(!ps) return;
// safer: use the toplevel window as owner
GtkWidget *toplevel = gtk_widget_get_toplevel(self->widget);
HWND hwnd_owner = NULL;
if(GTK_IS_WINDOW(toplevel))
{
GdkWindow *gdk_win = gtk_widget_get_window(toplevel);
if(gdk_win)
hwnd_owner = gdk_win32_window_get_handle(gdk_win);
}
if(ps->settings_ctx)
{
dt_win_open_printer_settings(ps->settings_ctx, hwnd_owner);
dt_win_sync_cached_dm_to_pinfo(ps->settings_ctx);
_sync_print_widgets_from_pinfo(ps);
}
else
{
dt_control_log(_("no settings context"));
}
// schedule redraw on idle so it runs after dialog closes
g_idle_add(_redraw_later, self->widget);
}
// Convert the cached DEVMODE in ctx into a PrintTicket buffer the
// background job can carry across without touching any live COM/GDI
// handles itself. Returns TRUE and sets *out_data/*out_size on success;
// on failure, leaves *out_data NULL and logs — caller decides whether
// that's fatal or just "print with driver defaults".
static gboolean _win_build_print_ticket(dt_win32_print_ctx_t *ctx,
void **out_data,
size_t *out_size)
{
*out_data = NULL;
*out_size = 0;
if(!ctx || !ctx->cached_dm || !ctx->base
|| !*ctx->base->printer.name)
return FALSE;
gunichar2 *wprinter = g_utf8_to_utf16(ctx->base->printer.name,
-1, NULL, NULL, NULL);
if(!wprinter) return FALSE;
HPTPROVIDER hProvider = NULL;
HRESULT hr = PTOpenProvider((PCWSTR)wprinter, 1, &hProvider);
g_free(wprinter);
if(FAILED(hr) || !hProvider)
{
dt_control_log(_("could not open print ticket provider"));
return FALSE;
}
gboolean ok = FALSE;
IStream *pStream = NULL;
hr = CreateStreamOnHGlobal(NULL, TRUE, &pStream);
if(SUCCEEDED(hr) && pStream)
{
hr = PTConvertDevModeToPrintTicket(hProvider,
ctx->cached_dm->dmSize
+ ctx->cached_dm->dmDriverExtra,
(PDEVMODE)ctx->cached_dm,
kPTJobScope,
pStream);
if(SUCCEEDED(hr))
{
// pull the raw bytes out of the stream into a plain buffer —
// deliberately not handing the job an IStream/COM object, since
// the job runs on a different thread and shouldn't inherit any
// COM apartment or lifetime concerns from this one.
STATSTG statstg = { 0 };
hr = pStream->lpVtbl->Stat(pStream, &statstg, STATFLAG_NONAME);
if(SUCCEEDED(hr))
{
const size_t actual_size = (size_t)statstg.cbSize.QuadPart;
HGLOBAL hGlobal = NULL;
if(actual_size >0 && SUCCEEDED(GetHGlobalFromStream(pStream, &hGlobal)) && hGlobal)
{
void *src = GlobalLock(hGlobal);
if(src)
{
void *buf = malloc(actual_size);
if(buf)
{
memcpy(buf, src, actual_size);
*out_data = buf;
*out_size = actual_size;
ok = TRUE;
}
GlobalUnlock(hGlobal);
}
}
}
}
else
{
dt_control_log(_("could not convert printer settings to print ticket"));
}
pStream->lpVtbl->Release(pStream);
}
PTCloseProvider(hProvider);
return ok;
}
#endif
static void _print_button_clicked(GtkWidget *widget, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
dt_imgid_t imgid = NO_IMGID;
// at least one image on a box
for(int k=0; k<ps->imgs.count; k++)
{
if(dt_is_valid_imgid(ps->imgs.box[k].imgid))
{
imgid = ps->imgs.box[k].imgid;
break;
}
}
if(!dt_is_valid_imgid(imgid))
{
dt_control_log(_("cannot print until a picture is selected"));
return;
}
if(strlen(ps->prt.printer.name) == 0 || ps->prt.printer.resolution == 0)
{
dt_control_log(_("cannot print until a printer is selected"));
return;
}
if(ps->prt.paper.width == 0 || ps->prt.paper.height == 0)
{
dt_control_log(_("cannot print until a paper is selected"));
return;
}
#ifdef _WIN32
if(ps->settings_ctx && !ps->settings_ctx->settings_opened)
{
GtkWidget *toplevel = gtk_widget_get_toplevel(self->widget);
HWND hwnd_owner = NULL;
if(GTK_IS_WINDOW(toplevel))
{
GdkWindow *gdk_win = gtk_widget_get_window(toplevel);
if(gdk_win)
hwnd_owner = gdk_win32_window_get_handle(gdk_win);
}
gboolean updated = dt_win_open_printer_settings(ps->settings_ctx, hwnd_owner);
if(updated)
{
dt_win_sync_cached_dm_to_pinfo(ps->settings_ctx);
_sync_print_widgets_from_pinfo(ps);
}
ps->settings_ctx->settings_opened = TRUE;
// schedule redraw on idle so it runs after dialog closes
g_idle_add(_redraw_later, self->widget);
}
#endif
dt_job_t *job = dt_control_job_create(&_print_job_run, "print image %d", imgid);
if(!job) return;
dt_lib_print_job_t *params = calloc(1, sizeof(dt_lib_print_job_t));
dt_control_job_set_params(job, params, _print_job_cleanup);
memcpy(¶ms->prt, &ps->prt, sizeof(dt_print_info_t));
memcpy(¶ms->imgs, &ps->imgs, sizeof(ps->imgs));
#ifdef _WIN32
if(ps->settings_ctx)
{
if(!_win_build_print_ticket(ps->settings_ctx,
¶ms->print_ticket_data,
¶ms->print_ticket_size))
{
// not fatal — dt_win_print_file can fall back to driver
// defaults if print_ticket_data is NULL
dt_control_log(_("printing with default printer settings"));
}
}
#endif
// what to call the image?
GList *res = dt_metadata_get_lock(imgid, "Xmp.dc.title", NULL);
if(res != NULL)
{
// FIXME: in metadata_view.c, non-printables are filtered, should
// we do this here?
params->job_title = g_strdup((gchar *)res->data);
g_list_free_full(res, &g_free);
}
else
{
const dt_image_t *img = dt_image_cache_get(imgid, 'r');
if(!img)
{
// in this case no need to release from cache what we couldn't get
dt_control_log(_("cannot get image %d for printing"), imgid);
dt_control_job_dispose(job);
return;
}
params->job_title = g_strdup(img->filename);
dt_image_cache_read_release(img);
}
// FIXME: ellipsize title/printer as the export completed message is ellipsized
gchar *message = g_strdup_printf(_("processing `%s' for `%s'"),
params->job_title, params->prt.printer.name);
dt_control_job_add_progress(job, message, TRUE);
g_free(message);
// FIXME: getting this from conf as w/prior code, but switch to getting from ps
params->style = dt_conf_get_string(PRINT_CONFIG_PREFIX "style");
params->style_append = ps->v_style_append;
// FIXME: getting these from conf as w/prior code, but switch to getting them from ps
params->buf_icc_type = dt_conf_get_int(PRINT_CONFIG_PREFIX "icctype");
params->buf_icc_profile = dt_conf_get_string(PRINT_CONFIG_PREFIX "iccprofile");
params->buf_icc_intent = dt_conf_get_int(PRINT_CONFIG_PREFIX "iccintent");
params->p_icc_type = ps->v_picctype;
params->p_icc_profile = g_strdup(ps->v_piccprofile);
params->p_icc_intent = ps->v_pintent;
params->black_point_compensation = ps->v_black_point_compensation;
#ifdef _WIN32
params->is_color_device = ps->settings_ctx ? ps->settings_ctx->is_color_device : TRUE;
#endif
// Propagate the printer profile selection so dt_print_file() can
// tell CUPS to skip color management when dt already did the
// ICC conversion. See cups_print.c for platform-specific details.
if(ps->v_piccprofile && *ps->v_piccprofile)
g_strlcpy(params->prt.printer.profile, ps->v_piccprofile,
sizeof(params->prt.printer.profile));
dt_control_add_job(DT_JOB_QUEUE_USER_EXPORT, job);
}
static void _set_printer(const dt_lib_module_t *self,
const char *printer_name)
{
dt_lib_print_settings_t *ps = self->data;
#ifdef _WIN32
const gboolean orient_state = ps->prt.page.landscape;
#endif
dt_get_printer_info(printer_name, &ps->prt.printer);
// if this is a turboprint printer, disable the printer profile
if(ps->prt.printer.is_turboprint)
dt_bauhaus_combobox_set(ps->pprofile, 0);
dt_conf_set_string(PRINT_CONFIG_PREFIX "printer", printer_name);
// add papers for the given printer
dt_bauhaus_combobox_clear(ps->papers);
if(ps->paper_list) g_list_free_full(ps->paper_list, free);
ps->paper_list = dt_get_papers (&ps->prt.printer);
for(const GList *papers = ps->paper_list; papers; papers = g_list_next (papers))
{
const dt_paper_info_t *p = papers->data;
dt_bauhaus_combobox_add(ps->papers, p->common_name);
}
const char *default_paper = dt_conf_get_string_const(PRINT_CONFIG_PREFIX "paper");
//Paper reset to first entry if the default paper is not available for the selected printer and write to ps not relying on value-changed
const dt_paper_info_t *chosen_paper = dt_get_paper(ps->paper_list, default_paper);
if(!chosen_paper && ps->paper_list)
chosen_paper = ps->paper_list->data; // fall back to the first real entry
if(chosen_paper)
{
//make sure the paper change is actually reflected in the printer info, not just the combobox
memcpy(&ps->prt.paper, chosen_paper, sizeof(dt_paper_info_t));
dt_bauhaus_combobox_set_from_text(ps->papers, chosen_paper->common_name);
}
// add corresponding supported media
dt_bauhaus_combobox_clear(ps->media);
if(ps->media_list) g_list_free_full(ps->media_list, free);
ps->media_list = dt_get_media_type(&ps->prt.printer);
for(const GList *media = ps->media_list; media; media = g_list_next (media))
{
const dt_medium_info_t *m = media->data;
dt_bauhaus_combobox_add(ps->media, m->common_name);
}
const char *default_medium = dt_conf_get_string_const(PRINT_CONFIG_PREFIX "medium");
//Medium reset to first entry if the default medium is not available for the selected printer and write to ps not relying on value-changed
const dt_medium_info_t *chosen_medium = dt_get_medium(ps->media_list, default_medium);
if(!chosen_medium && ps->media_list)
chosen_medium = ps->media_list->data; // fall back to the first real entry
if(chosen_medium)
{
//make sure the medium change is actually reflected in the printer info, not just the combobox
memcpy(&ps->prt.medium, chosen_medium, sizeof(dt_medium_info_t));
dt_bauhaus_combobox_set_from_text(ps->media, chosen_medium->common_name);
}
dt_view_print_settings(darktable.view_manager, &ps->prt, &ps->imgs);
#ifdef _WIN32
// free old context
if(ps->settings_ctx)
dt_win32_print_ctx_free(ps->settings_ctx);
// populate for this printer to settings_ctx for windows print dialog caching
ps->settings_ctx = dt_win32_print_ctx_new(&ps->prt);
if(ps->settings_ctx)
{
// sync the cached DEVMODE to the printer info to align state
dt_win_sync_cached_dm_to_pinfo(ps->settings_ctx);
ps->prt.page.landscape = orient_state; // restore orientation state after printer change
if(ps->settings_ctx->cached_dm) dt_sync_print_settings_to_dm(ps->settings_ctx->cached_dm, ps->settings_ctx->base);
_sync_print_widgets_from_pinfo(ps);
}
#endif
}
//Callback when printer details are ready (Windows only)
#ifdef _WIN32
static void _printer_ready_cb(dt_printer_info_t *pinfo, void *user_data)
{
dt_lib_module_t *self = (dt_lib_module_t *)user_data;
dt_lib_print_settings_t *ps = self->data;
// What’s currently selected in the combo box?
const gchar *current = dt_bauhaus_combobox_get_text(self->widget);
if((ps->waiting_for_printer &&
g_strcmp0(ps->waiting_for_printer, pinfo->name) == 0) ||
(current && g_strcmp0(current, pinfo->name) == 0))
{
_set_printer(self, pinfo->name);
dt_conf_set_string("plugins/lighttable/print/printer", pinfo->name);
g_clear_pointer(&ps->waiting_for_printer, g_free);
// Re‑enable the combo box now that details are ready
gtk_widget_set_sensitive(ps->printers, TRUE);
gtk_widget_set_sensitive(ps->papers, TRUE);
// Now force a GUI refresh of the module
if(self->gui_update)
{
self->gui_update(self);
}
}
}
#endif
static void _printer_changed(GtkWidget *combo, const dt_lib_module_t *self)
{
const gchar *printer_name = dt_bauhaus_combobox_get_text(combo);
if(!printer_name) return;
#ifdef _WIN32
dt_lib_print_settings_t *ps = self->data;
// Ask backend if details are ready
if(!dt_printer_details_valid(printer_name))
{
g_free(ps->waiting_for_printer);
ps->waiting_for_printer = g_strdup(printer_name);
// Disable combo box to prevent multiple overlapping jobs
gtk_widget_set_sensitive(ps->printers, FALSE);
gtk_widget_set_sensitive(ps->papers, FALSE);
// gtk_widget_set_sensitive(ps->papers, FALSE);
dt_control_log(_("loading printer details for %s…"), printer_name);
return;
}
#endif
// Default path (Linux, or Windows once details are valid)
_set_printer(self, printer_name);
dt_conf_set_string("plugins/lighttable/print/printer", printer_name);
}
static void
_paper_changed(GtkWidget *combo, const dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
const gchar *paper_name = dt_bauhaus_combobox_get_text(combo);
if(!paper_name) return;
const dt_paper_info_t *paper = dt_get_paper(ps->paper_list, paper_name);
if(paper)
memcpy(&ps->prt.paper, paper, sizeof(dt_paper_info_t));
float width, height;
_get_page_dimension(&ps->prt, &width, &height);
dt_printing_setup_page(&ps->imgs, width, height, ps->prt.printer.resolution);
dt_conf_set_string(PRINT_CONFIG_PREFIX "paper", paper_name);
dt_view_print_settings(darktable.view_manager, &ps->prt, &ps->imgs);
#ifdef _WIN32
// Change in paper size should get pushed to the cached DEVMODE so that the print dialog reflects the new size.
// This is important for Windows printing, as the DEVMODE is used to communicate settings to the printer driver.
if(ps->settings_ctx && ps->settings_ctx->cached_dm)
dt_sync_print_settings_to_dm(ps->settings_ctx->cached_dm, ps->settings_ctx->base);
#endif
_update_slider(ps);
}
static void
_media_changed(GtkWidget *combo, const dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
const gchar *medium_name = dt_bauhaus_combobox_get_text(combo);
if(!medium_name) return;
const dt_medium_info_t *medium = dt_get_medium(ps->media_list, medium_name);
if(medium)
{
memcpy(&ps->prt.medium, medium, sizeof(dt_medium_info_t));
dt_conf_set_string(PRINT_CONFIG_PREFIX "medium", medium_name);
dt_view_print_settings(darktable.view_manager, &ps->prt, &ps->imgs);
// Print media selection is currently hidden on Windows, but if it were to be exposed, we would want to sync the DEVMODE with the new media selection.
#ifdef _WIN32
if(ps->settings_ctx && ps->settings_ctx->cached_dm)
dt_sync_print_settings_to_dm(ps->settings_ctx->cached_dm, ps->settings_ctx->base);
#endif
_update_slider(ps);
}
else
{
// No matching medium found: clear or fall back gracefully for NULL state
memset(&ps->prt.medium, 0, sizeof(dt_medium_info_t));
dt_bauhaus_combobox_set(ps->media, 0); // fallback to first entry if any
}
}
static void
_update_slider(dt_lib_print_settings_t *ps)
{
dt_view_print_settings(darktable.view_manager, &ps->prt, &ps->imgs);
// if widget are created, let's display the current image size
// Use the last selected image box if none is currently selected to allow for updating the size/DPI/scale information
int sel = (ps->selected != -1) ? ps->selected : ps->last_selected;
if(sel != -1
&& dt_is_valid_imgid(ps->imgs.box[sel].imgid)
&& ps->width && ps->height
&& ps->info)
{
const dt_image_box *box = &ps->imgs.box[sel];
dt_image_pos box_size_mm, box_size;
dt_printing_get_image_pos_mm(&ps->imgs, box, &box_size_mm);
dt_printing_get_image_pos(&ps->imgs, box, &box_size);
const double w = box_size_mm.width * units[ps->unit];
const double h = box_size_mm.height * units[ps->unit];
char *value, *precision;
int n_digits;
_precision_by_unit(ps->unit, &n_digits, NULL, &precision);
value = g_strdup_printf(precision, w);
gtk_label_set_text(GTK_LABEL(ps->width), value);
g_free(value);
value = g_strdup_printf(precision, h);
gtk_label_set_text(GTK_LABEL(ps->height), value);
g_free(value);
g_free(precision);
// compute the image down/up scale and report information
const float iwidth = box->img_width;
const float iheight = box->img_height;
const float awidth = box_size.width;
const float aheight = box_size.height;
const double scale = (iwidth <= awidth)
? awidth / iwidth
: aheight/ iheight;
value = g_strdup_printf(_("%3.2f (dpi:%d)"), scale,
scale <= 1.0
? (int)ps->prt.printer.resolution
: (int)(ps->prt.printer.resolution / scale));
gtk_label_set_text(GTK_LABEL(ps->info), value);
g_free(value);
}
}
static void
_top_border_callback(GtkWidget *spin, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
const double value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin));
dt_conf_set_float(PRINT_CONFIG_PREFIX "top_margin", value);
ps->prt.page.margin_top = _to_mm(ps, value);
if(ps->lock_activated)
{
ps->prt.page.margin_bottom = _to_mm(ps, value);
ps->prt.page.margin_left = _to_mm(ps, value);
ps->prt.page.margin_right = _to_mm(ps, value);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_bottom), value);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_left), value);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_right), value);
dt_conf_set_float(PRINT_CONFIG_PREFIX "bottom_margin", value);
dt_conf_set_float(PRINT_CONFIG_PREFIX "left_margin", value);
dt_conf_set_float(PRINT_CONFIG_PREFIX "right_margin", value);
}
_update_slider(ps);
}
static void
_bottom_border_callback(GtkWidget *spin, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
const double value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin));
dt_conf_set_float(PRINT_CONFIG_PREFIX "bottom_margin", value);
ps->prt.page.margin_bottom = _to_mm(ps, value);
_update_slider(ps);
}
static void
_left_border_callback(GtkWidget *spin, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
const double value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin));
dt_conf_set_float(PRINT_CONFIG_PREFIX "left_margin", value);
ps->prt.page.margin_left = _to_mm(ps, value);
_update_slider(ps);
}
static void
_right_border_callback(GtkWidget *spin, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
const double value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin));
dt_conf_set_float(PRINT_CONFIG_PREFIX "right_margin", value);
ps->prt.page.margin_right = _to_mm(ps, value);
_update_slider(ps);
}
static void
_lock_callback(GtkWidget *button, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
ps->lock_activated = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
dt_conf_set_bool(PRINT_CONFIG_PREFIX "lock_borders", ps->lock_activated);
gtk_widget_set_sensitive(GTK_WIDGET(ps->b_bottom), !ps->lock_activated);
gtk_widget_set_sensitive(GTK_WIDGET(ps->b_left), !ps->lock_activated);
gtk_widget_set_sensitive(GTK_WIDGET(ps->b_right), !ps->lock_activated);
// get value of top and set it to all other borders
const double value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ps->b_top));
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_bottom), value);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_left), value);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_right), value);
_update_slider (ps);
}
static void
_alignment_callback(GtkWidget *tb, dt_lib_module_t *self)
{
DT_GUARD_GUI_UPDATE();
int index=-1;
dt_lib_print_settings_t *ps = self->data;
for(int i=0; i<9; i++)
{
/* block signal handler */
g_signal_handlers_block_by_func(ps->dtba[i],_alignment_callback, self);
if(GTK_WIDGET(ps->dtba[i]) == tb)
{
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ps->dtba[i]), TRUE);
index=i;
}
else gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ps->dtba[i]), FALSE);
/* unblock signal handler */
g_signal_handlers_unblock_by_func(ps->dtba[i], _alignment_callback, self);
}
if(ps->last_selected != -1)
{
dt_printing_setup_image(&ps->imgs, ps->last_selected,
ps->imgs.box[ps->last_selected].imgid, 100, 100, index);
}
_update_slider(ps);
}
static void
_orientation_changed(GtkWidget *combo, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
ps->prt.page.landscape = dt_bauhaus_combobox_get(combo);
#ifdef _WIN32
if(ps->settings_ctx && ps->settings_ctx->cached_dm)
dt_sync_print_settings_to_dm(ps->settings_ctx->cached_dm, ps->settings_ctx->base);
#endif
_update_slider(ps);
}
static void
_snap_grid_callback(GtkWidget *widget, dt_lib_module_t *self)
{
dt_control_queue_redraw_center();
}
static void
_grid_callback(GtkWidget *widget, dt_lib_module_t *self)
{
dt_control_queue_redraw_center();
}
static void _grid_size_changed(GtkWidget *widget, dt_lib_module_t *self)
{
DT_GUARD_GUI_UPDATE();
dt_lib_print_settings_t *ps = self->data;
const float value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ps->grid_size));
dt_conf_set_float(PRINT_CONFIG_PREFIX "grid_size", _to_mm(ps, value));
dt_control_queue_redraw_center();
}
static void
_unit_changed(GtkWidget *combo, dt_lib_module_t *self)
{
DT_TRY_GUI_UPDATE();
dt_lib_print_settings_t *ps = self->data;
const float grid_size = dt_conf_get_float(PRINT_CONFIG_PREFIX "grid_size");
const int unit = dt_bauhaus_combobox_get(combo);
if(unit < 0) return; // shouldn't happen, but it could be -1 if nothing is selected
ps->unit = unit;
dt_conf_set_string(PRINT_CONFIG_PREFIX "unit", _unit_names[unit]);
const double margin_top = ps->prt.page.margin_top;
const double margin_left = ps->prt.page.margin_left;
const double margin_right = ps->prt.page.margin_right;
const double margin_bottom = ps->prt.page.margin_bottom;
int n_digits;
float incr;
_precision_by_unit(ps->unit, &n_digits, &incr, NULL);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(ps->b_top), n_digits);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(ps->b_bottom), n_digits);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(ps->b_left), n_digits);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(ps->b_right), n_digits);
gtk_spin_button_set_increments(GTK_SPIN_BUTTON(ps->b_top), incr, 10.f*incr);
gtk_spin_button_set_increments(GTK_SPIN_BUTTON(ps->b_bottom), incr, 10.f*incr);
gtk_spin_button_set_increments(GTK_SPIN_BUTTON(ps->b_left), incr, 10.f*incr);
gtk_spin_button_set_increments(GTK_SPIN_BUTTON(ps->b_right), incr, 10.f*incr);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(ps->b_x), n_digits);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(ps->b_y), n_digits);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(ps->b_width), n_digits);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(ps->b_height), n_digits);
gtk_spin_button_set_increments(GTK_SPIN_BUTTON(ps->b_x), incr, 10.f*incr);
gtk_spin_button_set_increments(GTK_SPIN_BUTTON(ps->b_y), incr, 10.f*incr);
gtk_spin_button_set_increments(GTK_SPIN_BUTTON(ps->b_width), incr, 10.f*incr);
gtk_spin_button_set_increments(GTK_SPIN_BUTTON(ps->b_height), incr, 10.f*incr);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(ps->grid_size), n_digits);
gtk_spin_button_set_increments(GTK_SPIN_BUTTON(ps->grid_size), incr, 10.f*incr);
_update_slider(ps);
// convert margins to new unit
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_top),
margin_top * units[ps->unit]);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_bottom),
margin_bottom * units[ps->unit]);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_left),
margin_left * units[ps->unit]);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_right),
margin_right * units[ps->unit]);
// grid size
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->grid_size),
grid_size * units[ps->unit]);
DT_LEAVE_GUI_UPDATE();
_fill_box_values(ps);
}
static inline gboolean _is_style_set(const char *name)
{
return name && name[0] && strcmp(name, _("none")) && dt_styles_exists(name);
}
static void _update_style_label(dt_lib_print_settings_t *ps, const char *name)
{
if(!ps)
return;
const gboolean is_style_set = _is_style_set(name);
if(ps->style_mode)
{
gtk_widget_set_visible(GTK_WIDGET(ps->style_mode), is_style_set);
}
// We use the string "none" to indicate that we don't apply any style to the export
char *localized_style = is_style_set
? dt_util_localize_segmented_name(name, TRUE)
: g_strdup(_("none"));
// Use only the leaf part of the segmented style name in the tooltip
char *leaf = strrchr(localized_style, '|');
leaf = leaf ? leaf+1 : localized_style;
gtk_label_set_text(GTK_LABEL(ps->style), leaf);
char *tooltip = g_strdup_printf(_("style to be applied on print:\n<b>%s</b>"),
localized_style);
g_free(localized_style);
gtk_widget_set_tooltip_markup(ps->style, tooltip);
g_free(tooltip);
g_free(ps->v_style);
ps->v_style = g_strdup(is_style_set ? name : "");
dt_conf_set_string(PRINT_CONFIG_PREFIX "style", is_style_set ? name : "");
}
static void _update_style(const dt_stylemenu_data_t *menu_data)
{
_update_style_label(menu_data->user_data, menu_data->name);
}
static void _apply_style_activate_callback(GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
const dt_stylemenu_data_t *menu_data = (dt_stylemenu_data_t *)g_variant_get_uint64(parameter);
_update_style(menu_data);
dtgtk_stylemenu_free_menu_data();
}
static void _style_popupmenu_callback(GtkWidget *w, gpointer user_data)
{
GActionGroup *action_group = gtk_widget_get_action_group(w, "styles");
if(action_group == NULL)
{
GActionEntry action_entries[] =
{
{ "activate", _apply_style_activate_callback, "t", NULL },
};
action_group = G_ACTION_GROUP(g_simple_action_group_new());
g_action_map_add_action_entries(G_ACTION_MAP(action_group),
action_entries,
G_N_ELEMENTS(action_entries),
NULL);
gtk_widget_insert_action_group(w,
"styles",
G_ACTION_GROUP(action_group));
g_object_unref(action_group);
}
/* if we got any styles, lets popup menu for selection */
GtkWidget *popover_menu = dtgtk_build_style_popover_menu(w, TRUE, user_data);
if(popover_menu)
{
gtk_popover_popup(GTK_POPOVER(popover_menu));
}
else
dt_control_log(_("no styles have been created yet"));
}
static void
_style_mode_changed(GtkWidget *widget, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
if(dt_bauhaus_combobox_get(ps->style_mode) == 0)
ps->v_style_append = FALSE;
else
ps->v_style_append = TRUE;
dt_conf_set_bool(PRINT_CONFIG_PREFIX "style_append", ps->v_style_append);
}
static void
_profile_changed(GtkWidget *widget, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
const int pos = dt_bauhaus_combobox_get(widget);
for(const GList *prof = ps->profiles; prof; prof = g_list_next(prof))
{
dt_lib_export_profile_t *pp = prof->data;
if(pp->pos == pos)
{
dt_conf_set_int(PRINT_CONFIG_PREFIX "icctype", pp->type);
dt_conf_set_string(PRINT_CONFIG_PREFIX "iccprofile", pp->filename);
g_free(ps->v_iccprofile);
ps->v_icctype = pp->type;
ps->v_iccprofile = g_strdup(pp->filename);
return;
}
}
dt_conf_set_int(PRINT_CONFIG_PREFIX "icctype", DT_COLORSPACE_NONE);
dt_conf_set_string(PRINT_CONFIG_PREFIX "iccprofile", "");
g_free(ps->v_iccprofile);
ps->v_icctype = DT_COLORSPACE_NONE;
ps->v_iccprofile = g_strdup("");
}
#ifdef _WIN32
/* Helper to rebuild the image/export profile combo on windows depending on driver
or printer color management to prevent misinterpretation of color data within
the printer driver which likely expects sRGB or AdobeRGB only. Still allow
anything wider (ProPhoto, Rec2020, "image settings") when darktable is managing
color through internal color management controls. */
static void _rebuild_image_profile_combo(dt_lib_print_settings_t *ps,
const gboolean driver_managed)
{
dt_bauhaus_combobox_clear(ps->profile);
int n = 0;
int srgb_pos = -1, adobergb_pos = -1;
if(!driver_managed)
{
dt_bauhaus_combobox_add(ps->profile, _("image settings"));
n = 1;
}
// pos 0 reserved for "image settings" in unrestricted mode;
// in driver-managed mode there is no pos 0 placeholder — first
// real entry starts the list.
for(GList *l = ps->profiles; l; l = g_list_next(l))
{
dt_lib_export_profile_t *prof = l->data;
if(driver_managed && prof->type != DT_COLORSPACE_SRGB
&& prof->type != DT_COLORSPACE_ADOBERGB)
continue; // restricted list: skip everything else
dt_bauhaus_combobox_add(ps->profile, prof->name);
prof->pos = n++;
if(driver_managed)
{
if(prof->type == DT_COLORSPACE_SRGB) srgb_pos = prof->pos;
if(prof->type == DT_COLORSPACE_ADOBERGB) adobergb_pos = prof->pos;
}
}
int target = 0; // "image settings" fallback for the !driver_managed case
if(driver_managed)
{
// preserve AdobeRGB if that's already the selection; otherwise sRGB
target = (ps->v_icctype == DT_COLORSPACE_ADOBERGB && adobergb_pos >= 0)
? adobergb_pos : srgb_pos;
// set state first, matching gui_init's own convention — never rely
// on dt_bauhaus_combobox_set to trigger _profile_changed as a side
// effect; it does not reliably do so
dt_lib_export_profile_t *chosen = NULL;
for(GList *l = ps->profiles; l; l = g_list_next(l))
{
dt_lib_export_profile_t *prof = l->data;
if(prof->pos == target) { chosen = prof; break; }
}
if(chosen)
{
dt_conf_set_int(PRINT_CONFIG_PREFIX "icctype", chosen->type);
dt_conf_set_string(PRINT_CONFIG_PREFIX "iccprofile", chosen->filename);
g_free(ps->v_iccprofile);
ps->v_icctype = chosen->type;
ps->v_iccprofile = g_strdup(chosen->filename);
}
}
else
{
// returning to the full list: ps->v_icctype/v_iccprofile are already
// correct (unchanged by this direction) — just find where that same
// selection landed in the newly-expanded list
gboolean found = FALSE;
for(GList *l = ps->profiles; l; l = g_list_next(l))
{
dt_lib_export_profile_t *prof = l->data;
if(prof->type == ps->v_icctype
&& (prof->type != DT_COLORSPACE_FILE
|| g_strcmp0(prof->filename, ps->v_iccprofile) == 0))
{
target = prof->pos;
found = TRUE;
break;
}
}
if(!found)
{
// safety net only — sRGB/AdobeRGB should always be present, so
// this shouldn't normally trigger. If it does, mirror gui_init's
// own combo_idx==-1 fallback and reset state to match pos 0.
dt_conf_set_int(PRINT_CONFIG_PREFIX "icctype", DT_COLORSPACE_NONE);
dt_conf_set_string(PRINT_CONFIG_PREFIX "iccprofile", "");
g_free(ps->v_iccprofile);
ps->v_icctype = DT_COLORSPACE_NONE;
ps->v_iccprofile = g_strdup("");
}
}
dt_bauhaus_combobox_set(ps->profile, target);
}
#endif
static void
_printer_profile_changed(GtkWidget *widget, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
const int pos = dt_bauhaus_combobox_get(widget);
#ifdef _WIN32
_rebuild_image_profile_combo(ps, pos == 0); // triggers a limitation in image profiles to Windows printer driver typical supported profiles (sRGB, AdobeRGB)
#endif
for(const GList *prof = ps->profiles; prof; prof = g_list_next(prof))
{
dt_lib_export_profile_t *pp = prof->data;
if(pp->ppos == pos)
{
dt_conf_set_int("plugins/print/printer/icctype", pp->type);
dt_conf_set_string("plugins/print/printer/iccprofile", pp->filename);
g_free(ps->v_piccprofile);
ps->v_picctype = pp->type;
ps->v_piccprofile = g_strdup(pp->filename);
// activate the black compensation and printer intent
gtk_widget_set_sensitive(GTK_WIDGET(ps->black_point_compensation), TRUE);
return;
}
}
dt_conf_set_int("plugins/print/printer/icctype", DT_COLORSPACE_NONE);
dt_conf_set_string("plugins/print/printer/iccprofile", "");
g_free(ps->v_piccprofile);
ps->v_picctype = DT_COLORSPACE_NONE;
ps->v_piccprofile = g_strdup("");
gtk_widget_set_sensitive(GTK_WIDGET(ps->black_point_compensation), FALSE);
}
static void
_printer_intent_callback (GtkWidget *widget, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
const int pos = dt_bauhaus_combobox_get(widget);
dt_conf_set_int("plugins/print/printer/iccintent", pos);
ps->v_pintent = pos;
ps->prt.printer.intent = pos;
}
static void
_printer_bpc_callback(GtkWidget *widget, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
ps->v_black_point_compensation =
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ps->black_point_compensation));
dt_conf_set_bool(PRINT_CONFIG_PREFIX "black_point_compensation",
ps->v_black_point_compensation);
}
static void
_intent_callback(GtkWidget *widget, dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
const int pos = dt_bauhaus_combobox_get(widget);
// record the intent that will override the out rendering module on export
dt_conf_set_int(PRINT_CONFIG_PREFIX "iccintent", pos - 1);
ps->v_intent = pos - 1;
}
static void _set_orientation(dt_lib_print_settings_t *ps, dt_imgid_t imgid)
{
dt_mipmap_buffer_t buf;
dt_mipmap_cache_get(&buf, imgid, DT_MIPMAP_0, DT_MIPMAP_BEST_EFFORT, 'r');
// If there's a mipmap available, figure out orientation based upon
// its dimensions. Otherwise, don't touch orientation until the
// mipmap arrives.
if(buf.size != DT_MIPMAP_NONE)
{
ps->prt.page.landscape = (buf.width > buf.height);
dt_view_print_settings(darktable.view_manager, &ps->prt, &ps->imgs);
dt_bauhaus_combobox_set(ps->orientation, ps->prt.page.landscape ? 1 : 0);
// Make sure the Windows DEVMODE is updated to match the new orientation
#ifdef _WIN32
if(ps->settings_ctx && ps->settings_ctx->cached_dm)
dt_sync_print_settings_to_dm(ps->settings_ctx->cached_dm, ps->settings_ctx->base);
#endif
}
dt_mipmap_cache_release(&buf);
dt_control_queue_redraw_center();
}
static void _load_image_full_page(dt_lib_print_settings_t *ps,
const dt_imgid_t imgid)
{
_set_orientation(ps, imgid);
dt_printing_setup_box(&ps->imgs, 0,
ps->imgs.screen.page.x, ps->imgs.screen.page.y,
ps->imgs.screen.page.width, ps->imgs.screen.page.height);
float width, height;
_get_page_dimension(&ps->prt, &width, &height);
dt_printing_setup_page(&ps->imgs, width, height, ps->prt.printer.resolution);
dt_printing_setup_image(&ps->imgs, 0, imgid, 100, 100, ALIGNMENT_CENTER);
dt_control_queue_redraw_center();
}
static void _print_settings_update_callback(gpointer instance,
const dt_imgid_t imgid,
dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
// if a mipmap has arrived for an image just activated in fullpage
// mode, reorient the page (landscape or portrait) based on the
// mipmap's orientation
if(ps->imgs.count == 1
&& ps->imgs.box[0].imgid == imgid
&& !ps->has_changed)
{
dt_printing_clear_box(&ps->imgs.box[0]);
_load_image_full_page(ps, imgid);
}
}
static void _print_settings_activate_callback(gpointer instance,
const dt_imgid_t imgid,
dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
// load an image with a simple click on the filmstrip only if a
// single image is present
if(ps->imgs.count == 1)
{
if(ps->has_changed)
{
dt_printing_setup_image(&ps->imgs, 0, imgid, 100, 100, ps->imgs.box[0].alignment);
dt_control_queue_redraw_center();
}
else
{
dt_printing_clear_box(&ps->imgs.box[0]);
_load_image_full_page(ps, imgid);
}
}
}
static GList* _get_profiles()
{
// Create list of profiles
GList *list = NULL;
dt_lib_export_profile_t *prof = g_malloc0(sizeof(dt_lib_export_profile_t));
prof->type = DT_COLORSPACE_SRGB;
dt_utf8_strlcpy(prof->name, _("sRGB"), sizeof(prof->name));
prof->pos = -2;
prof->ppos = -2;
list = g_list_prepend(list, prof);
prof = g_malloc0(sizeof(dt_lib_export_profile_t));
prof->type = DT_COLORSPACE_ADOBERGB;
dt_utf8_strlcpy(prof->name, _("Adobe RGB (compatible)"), sizeof(prof->name));
prof->pos = -2;
prof->ppos = -2;
list = g_list_prepend(list, prof);
// add the profiles from datadir/color/out/*.icc
for(GList *iter = darktable.color_profiles->profiles; iter; iter = g_list_next(iter))
{
dt_colorspaces_color_profile_t *p = iter->data;
if(p->type == DT_COLORSPACE_FILE)
{
prof = g_malloc0(sizeof(dt_lib_export_profile_t));
g_strlcpy(prof->name, p->name, sizeof(prof->name));
g_strlcpy(prof->filename, p->filename, sizeof(prof->filename));
prof->type = DT_COLORSPACE_FILE;
prof->pos = -2;
prof->ppos = -2;
list = g_list_prepend(list, prof);
}
}
return g_list_reverse(list); // list was built in reverse order, so un-reverse it
}
static void _new_printer_callback(dt_printer_info_t *printer,
void *user_data)
{
const dt_lib_module_t *self = (dt_lib_module_t *)user_data;
dt_lib_print_settings_t *d = (dt_lib_print_settings_t*)self->data;
dt_pthread_mutex_lock(&d->printer_list_mutex);
d->printer_list = g_list_append(d->printer_list, g_strdup(printer->name));
dt_pthread_mutex_unlock(&d->printer_list_mutex);
d->prt.num_printers++;
}
void view_enter(struct dt_lib_module_t *self,
struct dt_view_t *old_view,
struct dt_view_t *new_view)
{
dt_lib_print_settings_t *d = (dt_lib_print_settings_t*)self->data;
dt_pthread_mutex_lock(&d->printer_list_mutex);
if(d->printer_list != NULL)
{
// The printer list was filled by dt_printers_discovery() in a background job.
// Now we fill the printer combo with the found printers.
char *default_printer = dt_conf_get_string(PRINT_CONFIG_PREFIX "printer");
for(const GList *iter = d->printer_list; iter; iter = g_list_next(iter))
{
const gchar *printer_name = iter->data;
dt_bauhaus_combobox_add(d->printers, printer_name);
}
if(default_printer[0] == '\0'
|| !dt_bauhaus_combobox_set_from_text(d->printers, default_printer))
{
// no default printer or default printer not found
dt_bauhaus_combobox_set(d->printers, 0);
// no default printer or default printer not found — fall back to
// the first currently-installed printer, and explicitly run setup
// rather than relying on the combobox's "value-changed" signal to
// fire on a programmatic set
if(d->printer_list)
_set_printer(self, (const char *)d->printer_list->data);
}
g_free(default_printer);
g_list_free_full(d->printer_list, g_free);
d->printer_list = NULL;
}
dt_pthread_mutex_unlock(&d->printer_list_mutex);
// user activated a new image via the filmstrip or user entered view
// mode which activates an image: get image_id and orientation
DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE, _print_settings_activate_callback);
// when an updated mipmap, we may have new orientation information
// about the current image.
DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_DEVELOP_MIPMAP_UPDATED, _print_settings_update_callback);
// NOTE: it would be proper to set image_id here to -1, but this
// seems to make no difference
}
void view_leave(struct dt_lib_module_t *self,
struct dt_view_t *old_view,
struct dt_view_t *new_view)
{
DT_CONTROL_SIGNAL_DISCONNECT(_print_settings_activate_callback, self);
DT_CONTROL_SIGNAL_DISCONNECT(_print_settings_update_callback, self);
}
static gboolean _expose_again(gpointer user_data)
{
dt_lib_print_settings_t *ps = (dt_lib_print_settings_t *)user_data;
if(dt_is_valid_imgid(ps->imgs.imgid_to_load))
{
_load_image_full_page(ps, ps->imgs.imgid_to_load);
ps->imgs.imgid_to_load = NO_IMGID;
}
dt_control_queue_redraw_center();
return FALSE;
}
static void _get_control(dt_lib_print_settings_t *ps,
const float x,
const float y)
{
const float dist = 20.0;
const dt_image_box *b = &ps->imgs.box[ps->selected];
ps->sel_controls = 0;
if(fabsf(b->screen.x - x) < dist)
ps->sel_controls |= BOX_LEFT;
if(fabsf(b->screen.y - y) < dist)
ps->sel_controls |= BOX_TOP;
if(fabsf((b->screen.x + b->screen.width) - x) < dist)
ps->sel_controls |= BOX_RIGHT;
if(fabsf((b->screen.y + b->screen.height) - y) < dist)
ps->sel_controls |= BOX_BOTTOM;
if(ps->sel_controls == 0) ps->sel_controls = BOX_ALL;
}
int mouse_leave(struct dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
if(ps->last_selected != -1)
{
dt_control_set_mouse_over_id(ps->imgs.box[ps->last_selected].imgid);
}
return 0;
}
static void _snap_to_grid(dt_lib_print_settings_t *ps,
float *x,
float *y)
{
if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ps->snap_grid)))
{
// V lines
const float step =
gtk_spin_button_get_value(GTK_SPIN_BUTTON(ps->grid_size)) / units[ps->unit];
// only display the grid if a step of 5 pixels
const float diff = DT_PIXEL_APPLY_DPI(5);
float grid_pos = (float)ps->imgs.screen.page.x;
const float h_step = _mm_to_hscreen(ps, step, FALSE);
while(grid_pos < ps->imgs.screen.page.x + ps->imgs.screen.page.width)
{
if(fabsf(*x - grid_pos) < diff) *x = grid_pos;
grid_pos += h_step;
}
// H lines
grid_pos = (float)ps->imgs.screen.page.y;
const float v_step = _mm_to_vscreen(ps, step, FALSE);
while(grid_pos < ps->imgs.screen.page.y + ps->imgs.screen.page.height)
{
if(fabsf(*y - grid_pos) < diff) *y = grid_pos;
grid_pos += v_step;
}
}
// FIXME: should clamp values to page size here?
}
int mouse_moved(struct dt_lib_module_t *self,
const double x,
const double y,
const double pressure,
const int which)
{
dt_lib_print_settings_t *ps = self->data;
gboolean expose = FALSE;
if(ps->creation)
dt_control_change_cursor("cell");
if(ps->creation && ps->dragging)
{
ps->x2 = x;
ps->y2 = y;
_snap_to_grid(ps, &ps->x2, &ps->y2);
expose = TRUE;
}
else if(ps->dragging)
{
dt_image_box *b = &ps->imgs.box[ps->selected];
const float dx = x - ps->click_pos_x;
const float dy = y - ps->click_pos_y;
const float coef = dx / b->screen.width;
switch(ps->sel_controls)
{
case BOX_ALL:
ps->x1 = b->screen.x + dx;
ps->y1 = b->screen.y + dy;
ps->x2 = b->screen.x + b->screen.width + dx;
ps->y2 = b->screen.y + b->screen.height + dy;
break;
case BOX_LEFT:
ps->x1 = b->screen.x + dx;
break;
case BOX_TOP:
ps->y1 = b->screen.y + dy;
break;
case BOX_RIGHT:
ps->x2 = b->screen.x + b->screen.width + dx;
break;
case BOX_BOTTOM:
ps->y2 = b->screen.y + b->screen.height + dy;
break;
case BOX_TOP_LEFT:
ps->x1 = b->screen.x + dx;
ps->y1 = b->screen.y + (coef * b->screen.height);
break;
case BOX_TOP_RIGHT:
ps->x2 = b->screen.x + b->screen.width + dx;
ps->y1 = b->screen.y - (coef * b->screen.height);
break;
case BOX_BOTTOM_LEFT:
ps->x1 = b->screen.x + dx;
ps->y2 = b->screen.y + b->screen.height - (coef * b->screen.height);
break;
case BOX_BOTTOM_RIGHT:
ps->x2 = b->screen.x + b->screen.width + dx;
ps->y2 = b->screen.y + b->screen.height + (coef * b->screen.height);
break;
default:
break;
}
expose = TRUE;
_snap_to_grid(ps, &ps->x1, &ps->y1);
_snap_to_grid(ps, &ps->x2, &ps->y2);
}
else if(!ps->creation)
{
const int bidx = dt_printing_get_image_box(&ps->imgs, x, y);
ps->sel_controls = 0;
if(bidx == -1)
{
if(ps->selected != -1) expose = TRUE;
ps->selected = -1;
}
else
{
expose = TRUE;
ps->selected = bidx;
_fill_box_values(ps);
_get_control(ps, x, y);
}
}
if(expose) dt_control_queue_redraw_center();
return 0;
}
static void _swap(float *a, float *b)
{
const float tmp = *a;
*a = *b;
*b = tmp;
}
int button_released(struct dt_lib_module_t *self,
const double x,
const double y,
const int which,
const uint32_t state)
{
dt_lib_print_settings_t *ps = self->data;
if(ps->dragging)
{
int idx = -1;
gtk_widget_set_sensitive(ps->del, TRUE);
// handle new area
if(ps->creation)
{
idx = ps->imgs.count++;
}
else if(ps->selected != -1)
{
idx = ps->selected;
}
if(idx != -1)
{
// make sure the area is in the the printable area taking into account the margins
// don't allow a too small area
if(ps->x2 < ps->x1) _swap(&ps->x1, &ps->x2);
if(ps->y2 < ps->y1) _swap(&ps->y1, &ps->y2);
const float dx = ps->x2 - ps->x1;
const float dy = ps->y2 - ps->y1;
dt_printing_setup_box(&ps->imgs, idx, ps->x1, ps->y1, dx, dy);
// make the new created box the last edited one
ps->last_selected = idx;
_fill_box_values(ps);
}
}
_update_slider(ps);
ps->creation = FALSE;
ps->dragging = FALSE;
dt_control_change_cursor("default");
return 0;
}
int button_pressed(struct dt_lib_module_t *self,
const double x,
const double y,
const double pressure,
const int which,
const int type,
const uint32_t state)
{
dt_lib_print_settings_t *ps = self->data;
ps->click_pos_x = x;
ps->click_pos_y = y;
ps->last_selected = -1;
if(ps->creation)
{
ps->dragging = TRUE;
ps->selected = -1;
ps->x1 = ps->x2 = x;
ps->y1 = ps->y2 = y;
_snap_to_grid(ps, &ps->x1, &ps->y1);
}
else if(ps->selected > 0
&& (which == GDK_BUTTON_MIDDLE || (which == GDK_BUTTON_PRIMARY && dt_modifier_is(state, GDK_CONTROL_MASK))))
{
// middle click (or ctrl-click), move selected image down
dt_image_box b;
memcpy(&b, &ps->imgs.box[ps->selected], sizeof(dt_image_box));
memcpy(&ps->imgs.box[ps->selected], &ps->imgs.box[ps->selected-1],
sizeof(dt_image_box));
memcpy(&ps->imgs.box[ps->selected-1], &b, sizeof(dt_image_box));
}
else if(ps->selected != -1 && which == GDK_BUTTON_PRIMARY)
{
dt_image_box *b = &ps->imgs.box[ps->selected];
ps->dragging = TRUE;
ps->x1 = b->screen.x;
ps->y1 = b->screen.y;
ps->x2 = b->screen.x + b->screen.width;
ps->y2 = b->screen.y + b->screen.height;
ps->last_selected = ps->selected;
ps->has_changed = TRUE;
_get_control(ps, x, y);
dt_control_change_cursor("pointer");
}
else if(ps->selected != -1 && which == GDK_BUTTON_SECONDARY)
{
dt_image_box *b = &ps->imgs.box[ps->selected];
// if image present remove it, otherwise remove the box
if(dt_is_valid_imgid(b->imgid))
b->imgid = NO_IMGID;
else
_page_delete_area(self, ps->selected);
ps->last_selected = ps->selected;
ps->has_changed = TRUE;
}
return 0;
}
static void _cairo_rectangle(cairo_t *cr,
const int sel_controls,
const int x1,
const int y1,
const int x2,
const int y2)
{
const float sel_width = DT_PIXEL_APPLY_DPI(3.0);
const float std_width = DT_PIXEL_APPLY_DPI(1.0);
const gboolean all = sel_controls == BOX_ALL;
cairo_move_to(cr, x1, y1);
cairo_set_line_width(cr, (all || sel_controls == BOX_LEFT) ? sel_width : std_width);
cairo_line_to(cr, x1, y2);
cairo_stroke(cr);
cairo_move_to(cr, x1, y2);
cairo_set_line_width(cr, (all || sel_controls == BOX_BOTTOM) ? sel_width : std_width);
cairo_line_to(cr, x2, y2);
cairo_stroke(cr);
cairo_move_to(cr, x2, y2);
cairo_set_line_width(cr, (all || sel_controls == BOX_RIGHT) ? sel_width : std_width);
cairo_line_to(cr, x2, y1);
cairo_stroke(cr);
cairo_move_to(cr, x2, y1);
cairo_set_line_width(cr, (all || sel_controls == BOX_TOP) ? sel_width : std_width);
cairo_line_to(cr, x1, y1);
cairo_stroke(cr);
if(sel_controls == 0)
{
const double dash[] = { DT_PIXEL_APPLY_DPI(3.0), DT_PIXEL_APPLY_DPI(3.0) };
cairo_set_dash(cr, dash, 2, 0);
// no image inside
cairo_move_to(cr, x1, y1);
cairo_line_to(cr, x2, y2);
cairo_move_to(cr, x1, y2);
cairo_line_to(cr, x2, y1);
cairo_stroke(cr);
}
cairo_set_dash(cr, NULL, 0, 0);
cairo_set_line_width(cr, sel_width);
if(sel_controls == BOX_TOP_LEFT)
{
cairo_rectangle (cr, x1, y1, DT_PIXEL_APPLY_DPI(15), DT_PIXEL_APPLY_DPI(15));
cairo_stroke(cr);
}
if(sel_controls == BOX_TOP_RIGHT)
{
cairo_rectangle (cr, x2 - DT_PIXEL_APPLY_DPI(15), y1,
DT_PIXEL_APPLY_DPI(15), DT_PIXEL_APPLY_DPI(15));
cairo_stroke(cr);
}
if(sel_controls == BOX_BOTTOM_LEFT)
{
cairo_rectangle (cr, x1, y2 - DT_PIXEL_APPLY_DPI(15),
DT_PIXEL_APPLY_DPI(15), DT_PIXEL_APPLY_DPI(15));
cairo_stroke(cr);
}
if(sel_controls == BOX_BOTTOM_RIGHT)
{
cairo_rectangle (cr, x2 - DT_PIXEL_APPLY_DPI(15), y2 - DT_PIXEL_APPLY_DPI(15),
DT_PIXEL_APPLY_DPI(15), DT_PIXEL_APPLY_DPI(15));
cairo_stroke(cr);
}
}
void gui_post_expose(struct dt_lib_module_t *self,
cairo_t *cr,
const int32_t width,
const int32_t height,
const int32_t pointerx,
const int32_t pointery)
{
dt_lib_print_settings_t *ps = self->data;
if(dt_is_valid_imgid(ps->imgs.imgid_to_load))
{
// we set orientation and delay the reload to ensure the
// page is properly set before trying to display the image.
_set_orientation(ps, ps->imgs.imgid_to_load);
g_timeout_add(250, _expose_again, ps);
}
// display grid
// 1mm
const float step =
gtk_spin_button_get_value(GTK_SPIN_BUTTON(ps->grid_size)) / units[ps->unit];
// only display grid if spacing more than 5 pixels
if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ps->grid))
&& (int)_mm_to_hscreen(ps, step, FALSE) > DT_PIXEL_APPLY_DPI(5))
{
const double dash[] = { DT_PIXEL_APPLY_DPI(5.0), DT_PIXEL_APPLY_DPI(5.0) };
cairo_set_source_rgba(cr, 1, .2, .2, 0.6);
// V lines
float grid_pos = (float)ps->imgs.screen.page.x;
const float h_step = _mm_to_hscreen(ps, step, FALSE);
int n = 0;
while(grid_pos < ps->imgs.screen.page.x + ps->imgs.screen.page.width)
{
cairo_set_dash(cr,
dash, ((n % 5) == 0)
? 0
: 2, DT_PIXEL_APPLY_DPI(5));
cairo_set_line_width(cr,
((n % 5) == 0)
? DT_PIXEL_APPLY_DPI(1.0)
: DT_PIXEL_APPLY_DPI(0.5));
cairo_move_to(cr, grid_pos, ps->imgs.screen.page.y);
cairo_line_to(cr, grid_pos, ps->imgs.screen.page.y + ps->imgs.screen.page.height);
cairo_stroke(cr);
grid_pos += h_step;
n++;
}
// H lines
grid_pos = (float)ps->imgs.screen.page.y;
const float v_step = _mm_to_vscreen(ps, step, FALSE);
n = 0;
while(grid_pos < ps->imgs.screen.page.y + ps->imgs.screen.page.height)
{
cairo_set_dash(cr, dash,
((n % 5) == 0)
? 0
: 2, DT_PIXEL_APPLY_DPI(5));
cairo_set_line_width(cr,
((n % 5) == 0)
? DT_PIXEL_APPLY_DPI(1.0)
: DT_PIXEL_APPLY_DPI(0.5));
cairo_move_to(cr, ps->imgs.screen.page.x, grid_pos);
cairo_line_to(cr, ps->imgs.screen.page.x + ps->imgs.screen.page.width, grid_pos);
cairo_stroke(cr);
grid_pos += v_step;
n++;
}
}
// disable dash
cairo_set_source_rgba(cr, 1, .2, .2, 0.6);
cairo_set_dash(cr, NULL, 0, 0);
const float scaler = 1.0f / darktable.gui->ppd_thb;
for(int k=0; k<ps->imgs.count; k++)
{
dt_image_box *img = &ps->imgs.box[k];
if(dt_is_valid_imgid(img->imgid))
{
cairo_surface_t *surf = NULL;
// screen coordinate default to current box if there is no image
dt_image_pos screen;
dt_printing_setup_image(&ps->imgs, k, img->imgid, 100, 100, img->alignment);
dt_printing_get_screen_pos(&ps->imgs, img, &screen);
const dt_view_surface_value_t res =
dt_view_image_get_surface(img->imgid, screen.width, screen.height, &surf, TRUE);
if(res != DT_VIEW_SURFACE_OK)
{
// if the image is missing, we reload it again
g_timeout_add(250, _expose_again, ps);
if(!ps->busy) dt_control_busy_enter();
ps->busy = TRUE;
}
else
{
cairo_save(cr);
cairo_translate(cr, screen.x, screen.y);
cairo_scale(cr, scaler, scaler);
cairo_set_source_surface(cr, surf, 0, 0);
const double alpha =
(ps->dragging
|| (ps->selected != -1 && ps->selected != k))
? 0.25
: 1.0;
cairo_paint_with_alpha(cr, alpha);
cairo_surface_destroy(surf);
cairo_restore(cr);
if(ps->busy) dt_control_busy_leave();
ps->busy = FALSE;
}
}
if(k == ps->selected || !dt_is_valid_imgid(img->imgid))
{
cairo_set_source_rgba(cr, .4, .4, .4, 1.0);
_cairo_rectangle(cr, (k == ps->selected) ? ps->sel_controls : 0,
img->screen.x, img->screen.y,
img->screen.x + img->screen.width,
img->screen.y + img->screen.height);
cairo_stroke(cr);
}
else if(k == ps->last_selected)
{
const double dash[] = { DT_PIXEL_APPLY_DPI(5.0), DT_PIXEL_APPLY_DPI(5.0) };
cairo_set_line_width(cr, 1);
cairo_set_dash(cr, dash, 1, DT_PIXEL_APPLY_DPI(5));
cairo_set_source_rgba(cr, .6, .6, .6, 1.0);
cairo_rectangle(cr,
img->screen.x, img->screen.y,
img->screen.width, img->screen.height);
cairo_stroke(cr);
}
if(k == ps->imgs.motion_over)
{
cairo_set_source_rgba(cr, .2, .2, .2, 1.0);
cairo_rectangle(cr, img->screen.x, img->screen.y,
img->screen.width, img->screen.height);
cairo_fill(cr);
}
}
// now display new area if any
if(ps->dragging || ps->selected != -1)
{
float dx1, dy1, dx2, dy2, dwidth, dheight; // displayed values
float x1, y1, x2, y2; // box screen coordinates
float pwidth, pheight;
_get_page_dimension(&ps->prt, &pwidth, &pheight);
if(ps->dragging)
{
x1 = ps->x1;
y1 = ps->y1;
x2 = ps->x2;
y2 = ps->y2;
dx1 = _hscreen_to_mm(ps, ps->x1, TRUE) * units[ps->unit];
dy1 = _vscreen_to_mm(ps, ps->y1, TRUE) * units[ps->unit];
dx2 = _hscreen_to_mm(ps, ps->x2, TRUE) * units[ps->unit];
dy2 = _vscreen_to_mm(ps, ps->y2, TRUE) * units[ps->unit];
dwidth = fabsf(dx2 - dx1);
dheight = fabsf(dy2 - dy1);
}
else
{
const dt_image_box *box = &ps->imgs.box[ps->selected];
// we could use a simpler solution but we want to use the same formulae used
// to fill the editable box values to avoid discrepancies between values due
// to rounding errors.
dx1 = _percent_unit_of(ps, pwidth, box->pos.x);
dy1 = _percent_unit_of(ps, pheight, box->pos.y);
dwidth = _percent_unit_of(ps, pwidth, box->pos.width);
dheight = _percent_unit_of(ps, pheight, box->pos.height);
dx2 = dx1 + dwidth;
dy2 = dy1 + dheight;
x1 = box->screen.x;
y1 = box->screen.y;
x2 = box->screen.x + box->screen.width;
y2 = box->screen.y + box->screen.height;
}
cairo_set_source_rgba(cr, .4, .4, .4, 1.0);
_cairo_rectangle(cr, ps->sel_controls, x1, y1, x2, y2);
// display corner coordinates
// FIXME: here and elsewhere eliminate hardcoded RGB values -- use CSS
char dimensions[16];
PangoLayout *layout;
PangoRectangle ext;
PangoFontDescription *desc = dt_gui_get_font();
pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
pango_font_description_set_absolute_size(desc, DT_PIXEL_APPLY_DPI(16) * PANGO_SCALE);
layout = pango_cairo_create_layout(cr);
pango_layout_set_font_description(layout, desc);
const double text_h = DT_PIXEL_APPLY_DPI(16+2);
const double margin = DT_PIXEL_APPLY_DPI(6);
const double dash = DT_PIXEL_APPLY_DPI(4.0);
int n_digits;
char *precision;
_precision_by_unit(ps->unit, &n_digits, NULL, &precision);
double xp, yp;
yp = y1 + (y2 - y1 - text_h) * 0.5;
if(x1 >= ps->imgs.screen.page.x
&& x1 <= (ps->imgs.screen.page.x + ps->imgs.screen.page.width))
{
snprintf(dimensions, sizeof(dimensions), precision, dx1);
pango_layout_set_text(layout, dimensions, -1);
pango_layout_get_pixel_extents(layout, NULL, &ext);
xp = ps->imgs.screen.page.x
+ (x1 - text_h - ps->imgs.screen.page.x - ext.width) * 0.5;
if(xp < ps->imgs.screen.page.x + 3 * margin)
{
xp = x1 + 2 * margin;
// somewhat hacky, assumes that all numeric labels are about
// the same width
yp = MIN(y2 - text_h, yp + ext.width + 0.5 * text_h + margin * 3);
}
cairo_set_source_rgba(cr, .7, .7, .7, .9);
cairo_move_to(cr, ps->imgs.screen.page.x, yp + text_h * 0.5);
cairo_line_to(cr, x1, yp + text_h * 0.5);
cairo_stroke_preserve(cr);
cairo_set_source_rgba(cr, .5, .5, .5, .9);
cairo_set_dash(cr, &dash, 1, dash);
cairo_stroke(cr);
cairo_set_dash(cr, NULL, 0, 0);
dt_gui_draw_rounded_rectangle(cr,
ext.width + 2 * margin,
text_h + 2 * margin,
xp - margin,
yp - margin);
cairo_set_source_rgb(cr, .8, .8, .8);
cairo_move_to(cr, xp, yp);
pango_cairo_show_layout(cr, layout);
}
if(x2 >= ps->imgs.screen.page.x
&& x2 <= (ps->imgs.screen.page.x + ps->imgs.screen.page.width))
{
snprintf(dimensions, sizeof(dimensions), precision, pwidth * units[ps->unit] - dx2);
pango_layout_set_text(layout, dimensions, -1);
pango_layout_get_pixel_extents(layout, NULL, &ext);
xp = x2 + (ps->imgs.screen.page.x
+ ps->imgs.screen.page.width - x2 - ext.width) * 0.5;
if(xp + ext.width + margin > ps->imgs.screen.page.x + ps->imgs.screen.page.width)
xp = x2 - ext.width - 2 * margin;
cairo_set_source_rgba(cr, .7, .7, .7, .9);
cairo_move_to(cr, x2, yp + text_h * 0.5);
cairo_line_to(cr,
ps->imgs.screen.page.x + ps->imgs.screen.page.width,
yp + text_h * 0.5);
cairo_stroke_preserve(cr);
cairo_set_source_rgba(cr, .5, .5, .5, .9);
cairo_set_dash(cr, &dash, 1, dash);
cairo_stroke(cr);
cairo_set_dash(cr, NULL, 0, 0);
dt_gui_draw_rounded_rectangle(cr, ext.width + 2 * margin, text_h + 2 * margin,
xp - margin, yp - margin);
cairo_set_source_rgb(cr, .8, .8, .8);
cairo_move_to(cr, xp, yp);
pango_cairo_show_layout(cr, layout);
}
xp = x1 + (x2 - x1 - text_h) * 0.5;
if(y1 >= ps->imgs.screen.page.y
&& y1 <= (ps->imgs.screen.page.y + ps->imgs.screen.page.height))
{
snprintf(dimensions, sizeof(dimensions), precision, dy1);
pango_layout_set_text(layout, dimensions, -1);
pango_layout_get_pixel_extents(layout, NULL, &ext);
yp = ps->imgs.screen.page.y
+ (y1 - text_h - ps->imgs.screen.page.y - ext.width) * 0.5;
if(yp < ps->imgs.screen.page.y + 3 * margin)
{
xp = MIN(x2 - text_h, xp + ext.width + 0.5 * text_h + margin * 3);
yp = y1 + 2 * margin;
}
cairo_set_source_rgba(cr, .7, .7, .7, .9);
cairo_move_to(cr, xp + text_h * 0.5, ps->imgs.screen.page.y);
cairo_line_to(cr, xp + text_h * 0.5, y1);
cairo_stroke_preserve(cr);
cairo_set_source_rgba(cr, .5, .5, .5, .9);
cairo_set_dash(cr, &dash, 1, dash);
cairo_stroke(cr);
cairo_set_dash(cr, NULL, 0, 0);
dt_gui_draw_rounded_rectangle(cr, text_h + 2 * margin, ext.width + 2 * margin,
xp - margin, yp - margin);
cairo_set_source_rgb(cr, .8, .8, .8);
cairo_move_to(cr, xp + text_h * 0.5, yp + ext.width * 0.5);
cairo_save(cr);
cairo_rotate(cr, -M_PI_2);
cairo_rel_move_to(cr, -0.5 * ext.width, -0.5 * text_h);
pango_cairo_update_layout(cr, layout);
pango_cairo_show_layout(cr, layout);
cairo_restore(cr);
}
if(y2 >= ps->imgs.screen.page.y
&& y2 <= (ps->imgs.screen.page.y + ps->imgs.screen.page.height))
{
snprintf(dimensions, sizeof(dimensions), precision, pheight * units[ps->unit] - dy2);
pango_layout_set_text(layout, dimensions, -1);
pango_layout_get_pixel_extents(layout, NULL, &ext);
yp = y2 + (ps->imgs.screen.page.y
+ ps->imgs.screen.page.height - y2 - ext.width) * 0.5;
if(yp + ext.width + margin > ps->imgs.screen.page.y + ps->imgs.screen.page.height)
yp = y2 - ext.width - 2 * margin;
cairo_set_source_rgba(cr, .7, .7, .7, .9);
cairo_move_to(cr, xp + text_h * 0.5, y2);
cairo_line_to(cr,
xp + text_h * 0.5,
ps->imgs.screen.page.y + ps->imgs.screen.page.height);
cairo_stroke_preserve(cr);
cairo_set_source_rgba(cr, .5, .5, .5, .9);
cairo_set_dash(cr, &dash, 1, dash);
cairo_stroke(cr);
cairo_set_dash(cr, NULL, 0, 0);
dt_gui_draw_rounded_rectangle(cr, text_h + 2 * margin, ext.width + 2 * margin,
xp - margin, yp - margin);
cairo_set_source_rgb(cr, .8, .8, .8);
cairo_move_to(cr, xp + text_h * 0.5, yp + ext.width * 0.5);
cairo_save(cr);
cairo_rotate(cr, -M_PI_2);
cairo_rel_move_to(cr, -0.5 * ext.width, -0.5 * text_h);
pango_cairo_update_layout(cr, layout);
pango_cairo_show_layout(cr, layout);
cairo_restore(cr);
}
// display width and height
snprintf(dimensions, sizeof(dimensions), precision, dwidth);
pango_layout_set_text(layout, dimensions, -1);
pango_layout_get_pixel_extents(layout, NULL, &ext);
xp = (x1 + x2 - ext.width) * .5;
if(y1 > text_h * 0.5 + margin)
yp = y1 - text_h * 0.5;
else
yp = y1 + text_h - 2 * margin;
cairo_set_source_rgba(cr, .5, .5, .5, .9);
dt_gui_draw_rounded_rectangle(cr, ext.width + 2 * margin, text_h + 2 * margin,
xp - margin, yp - margin);
cairo_set_source_rgb(cr, .8, .8, .8);
cairo_move_to(cr, xp, yp);
pango_cairo_show_layout(cr, layout);
snprintf(dimensions, sizeof(dimensions), precision, dheight);
pango_layout_set_text(layout, dimensions, -1);
pango_layout_get_pixel_extents(layout, NULL, &ext);
if(x1 > text_h * 0.5 + margin)
xp = x1 - text_h * 0.5;
else
xp = x1 + text_h - 2 * margin;
yp = (y1 + y2) * .5;
cairo_set_source_rgba(cr, .5, .5, .5, .9);
dt_gui_draw_rounded_rectangle(cr, text_h + 2 * margin, ext.width + 2 * margin,
xp - margin, yp - margin - 0.5 * ext.width);
cairo_set_source_rgb(cr, .8, .8, .8);
cairo_move_to(cr, xp + text_h * 0.5, yp);
cairo_save(cr);
cairo_rotate(cr, -M_PI_2);
cairo_rel_move_to(cr, -0.5 * ext.width, -0.5 * text_h);
pango_cairo_update_layout(cr, layout);
pango_cairo_show_layout(cr, layout);
cairo_restore(cr);
pango_font_description_free(desc);
g_object_unref(layout);
g_free(precision);
}
if(ps->imgs.screen.borderless)
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ps->borderless), TRUE);
else
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ps->borderless), FALSE);
}
static void _width_changed(GtkWidget *widget, gpointer user_data)
{
DT_GUARD_GUI_UPDATE();
dt_lib_print_settings_t *ps = (dt_lib_print_settings_t *)user_data;
const float nv = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
const float nv_mm = nv / units[ps->unit];
dt_image_box *box = &ps->imgs.box[ps->last_selected];
dt_printing_setup_box(&ps->imgs, ps->last_selected,
box->screen.x, box->screen.y,
_mm_to_hscreen(ps, nv_mm, FALSE), box->screen.height);
ps->has_changed = TRUE;
dt_control_queue_redraw_center();
}
static void _height_changed(GtkWidget *widget,
gpointer user_data)
{
DT_GUARD_GUI_UPDATE();
dt_lib_print_settings_t *ps = (dt_lib_print_settings_t *)user_data;
const float nv = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
const float nv_mm = nv / units[ps->unit];
dt_image_box *box = &ps->imgs.box[ps->last_selected];
dt_printing_setup_box(&ps->imgs, ps->last_selected,
box->screen.x, box->screen.y,
box->screen.width, _mm_to_vscreen(ps, nv_mm, FALSE));
ps->has_changed = TRUE;
dt_control_queue_redraw_center();
}
static void _x_changed(GtkWidget *widget,
gpointer user_data)
{
DT_GUARD_GUI_UPDATE();
dt_lib_print_settings_t *ps = (dt_lib_print_settings_t *)user_data;
const float nv = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
const float nv_mm = nv / units[ps->unit];
dt_image_box *box = &ps->imgs.box[ps->last_selected];
dt_printing_setup_box(&ps->imgs, ps->last_selected,
_mm_to_hscreen(ps, nv_mm, TRUE), box->screen.y,
box->screen.width, box->screen.height);
ps->has_changed = TRUE;
dt_control_queue_redraw_center();
}
static void _y_changed(GtkWidget *widget,
gpointer user_data)
{
DT_GUARD_GUI_UPDATE();
dt_lib_print_settings_t *ps = (dt_lib_print_settings_t *)user_data;
const float nv = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
const float nv_mm = nv / units[ps->unit];
dt_image_box *box = &ps->imgs.box[ps->last_selected];
dt_printing_setup_box(&ps->imgs, ps->last_selected,
box->screen.x, _mm_to_vscreen(ps, nv_mm, TRUE),
box->screen.width, box->screen.height);
ps->has_changed = TRUE;
dt_control_queue_redraw_center();
}
void gui_init(dt_lib_module_t *self)
{
dt_lib_print_settings_t *d = malloc(sizeof(dt_lib_print_settings_t));
self->data = d;
self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
dt_gui_add_help_link(self->widget, "print_overview");
GtkWidget *label;
d->paper_list = NULL;
d->media_list = NULL;
#ifdef _WIN32
d->settings_ctx = NULL;
#endif
d->unit = 0;
d->width = d->height = NULL;
d->v_piccprofile = NULL;
d->v_iccprofile = NULL;
d->v_style = NULL;
d->creation = d->dragging = FALSE;
d->selected = -1;
d->last_selected = -1;
d->has_changed = FALSE;
d->printer_list = NULL;
dt_pthread_mutex_init(&d->printer_list_mutex);
dt_init_print_info(&d->prt);
dt_view_print_settings(darktable.view_manager, &d->prt, &d->imgs);
d->profiles = _get_profiles();
d->imgs.motion_over = -1;
// Initialize the new async tracking field (Windows only)
#ifdef _WIN32
d->waiting_for_printer = NULL;
#endif
const char *str = dt_conf_get_string_const(PRINT_CONFIG_PREFIX "unit");
const char **names = _unit_names;
for(_unit_t i=0; *names; names++, i++)
if(g_strcmp0(str, *names) == 0)
d->unit = i;
dt_printing_clear_boxes(&d->imgs);
// set all margins + unit from settings
const float top_b = dt_conf_get_float(PRINT_CONFIG_PREFIX "top_margin");
const float bottom_b = dt_conf_get_float(PRINT_CONFIG_PREFIX "bottom_margin");
const float left_b = dt_conf_get_float(PRINT_CONFIG_PREFIX "left_margin");
const float right_b = dt_conf_get_float(PRINT_CONFIG_PREFIX "right_margin");
d->prt.page.margin_top = _to_mm(d, top_b);
d->prt.page.margin_bottom = _to_mm(d, bottom_b);
d->prt.page.margin_left = _to_mm(d, left_b);
d->prt.page.margin_right = _to_mm(d, right_b);
// create the spin-button now as values could be set when the
// printer has no hardware margin
// FIXME: set digits/increments on all of these by calling _unit_changed() later?
int n_digits;
float incr;
_precision_by_unit(d->unit, &n_digits, &incr, NULL);
d->b_top = gtk_spin_button_new_with_range(0, 1000, incr);
d->b_left = gtk_spin_button_new_with_range(0, 1000, incr);
d->b_right = gtk_spin_button_new_with_range(0, 1000, incr);
d->b_bottom = gtk_spin_button_new_with_range(0, 1000, incr);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(d->b_top), n_digits);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(d->b_bottom), n_digits);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(d->b_left), n_digits);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(d->b_right), n_digits);
d->b_x = gtk_spin_button_new_with_range(0, 1000, incr);
d->b_y = gtk_spin_button_new_with_range(0, 1000, incr);
d->b_width = gtk_spin_button_new_with_range(0, 1000, incr);
d->b_height = gtk_spin_button_new_with_range(0, 1000, incr);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(d->b_x), n_digits);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(d->b_y), n_digits);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(d->b_width), n_digits);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(d->b_height), n_digits);
d->grid_size = gtk_spin_button_new_with_range(0, 100, incr);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(d->grid_size), n_digits);
gtk_entry_set_alignment(GTK_ENTRY(d->b_top), 1);
gtk_entry_set_alignment(GTK_ENTRY(d->b_left), 1);
gtk_entry_set_alignment(GTK_ENTRY(d->b_right), 1);
gtk_entry_set_alignment(GTK_ENTRY(d->b_bottom), 1);
gtk_entry_set_alignment(GTK_ENTRY(d->b_x), 1);
gtk_entry_set_alignment(GTK_ENTRY(d->b_y), 1);
gtk_entry_set_alignment(GTK_ENTRY(d->b_width), 1);
gtk_entry_set_alignment(GTK_ENTRY(d->b_height), 1);
gtk_entry_set_alignment(GTK_ENTRY(d->grid_size), 1);
////////////////////////// PRINTER SETTINGS
// create papers combo as filled when adding printers
d->papers = dt_bauhaus_combobox_new_action(DT_ACTION(self));
label = dt_ui_section_label_new(C_("section", "printer"));
gtk_box_pack_start(GTK_BOX(self->widget), label, TRUE, TRUE, 0);
dt_gui_add_help_link(self->widget, "print_settings_printer");
d->printers = dt_bauhaus_combobox_new_action(DT_ACTION(self));
gtk_box_pack_start(GTK_BOX(self->widget), d->printers, TRUE, TRUE, 0);
g_signal_connect(G_OBJECT(d->printers), "value-changed",
G_CALLBACK(_printer_changed), self);
//// media
d->media = dt_bauhaus_combobox_new_action(DT_ACTION(self));
dt_bauhaus_widget_set_label(d->media, N_("printer"), N_("media"));
g_signal_connect(G_OBJECT(d->media), "value-changed",
G_CALLBACK(_media_changed), self);
#ifdef _WIN32
//TODO: Wire up media selection for windows, for now hide and let media be set in printer settings dialog
#else
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->media), TRUE, TRUE, 0);
#endif
// Add printer profile combo
d->pprofile = dt_bauhaus_combobox_new_action(DT_ACTION(self));
dt_bauhaus_widget_set_label(d->pprofile, N_("printer"), N_("profile"));
int combo_idx, n;
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->pprofile), TRUE, TRUE, 0);
int printer_profile_type =
dt_conf_get_int("plugins/print/printer/icctype");
const char *printer_profile =
dt_conf_get_string_const("plugins/print/printer/iccprofile");
combo_idx = -1;
n = 0;
dt_bauhaus_combobox_add(d->pprofile, _("color management in printer driver"));
for(const GList *l = d->profiles; l; l = g_list_next(l))
{
dt_lib_export_profile_t *prof = l->data;
// do not add built-in profiles, these are in no way for printing
if(prof->type == DT_COLORSPACE_FILE)
{
dt_bauhaus_combobox_add(d->pprofile, prof->name);
prof->ppos = ++n;
if(prof->type == printer_profile_type
&& (prof->type != DT_COLORSPACE_FILE
|| !g_strcmp0(prof->filename, printer_profile)))
{
g_free(d->v_piccprofile);
d->v_picctype = printer_profile_type;
d->v_piccprofile = g_strdup(printer_profile);
combo_idx = n;
}
}
}
// profile not found, maybe a profile has been removed? revert to none
if(combo_idx == -1)
{
dt_conf_set_int("plugins/print/printer/icctype", DT_COLORSPACE_NONE);
dt_conf_set_string("plugins/print/printer/iccprofile", "");
g_free(d->v_piccprofile);
d->v_picctype = DT_COLORSPACE_NONE;
d->v_piccprofile = g_strdup("");
combo_idx = 0;
}
dt_bauhaus_combobox_set(d->pprofile, combo_idx);
char *tooltip = dt_ioppr_get_location_tooltip("out", _("printer ICC profiles"));
gtk_widget_set_tooltip_markup(d->pprofile, tooltip);
g_free(tooltip);
g_signal_connect(G_OBJECT(d->pprofile), "value-changed",
G_CALLBACK(_printer_profile_changed), (gpointer)self);
// Add printer intent combo
d->v_pintent = dt_conf_get_int("plugins/print/printer/iccintent");
DT_BAUHAUS_COMBOBOX_NEW_FULL(d->pintent, self, N_("printer"), N_("intent"), NULL,
d->v_pintent, _printer_intent_callback, self,
N_("perceptual"),
N_("relative colorimetric"),
NC_("rendering intent", "saturation"),
N_("absolute colorimetric"));
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->pintent), TRUE, TRUE, 0);
d->prt.printer.intent = d->v_pintent;
d->black_point_compensation =
gtk_check_button_new_with_label(_("black point compensation"));
gtk_box_pack_start(GTK_BOX(self->widget),
GTK_WIDGET(d->black_point_compensation), TRUE, FALSE, 0);
g_signal_connect(d->black_point_compensation, "toggled",
G_CALLBACK(_printer_bpc_callback), (gpointer)self);
d->v_black_point_compensation =
dt_conf_get_bool(PRINT_CONFIG_PREFIX "black_point_compensation");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->black_point_compensation),
d->v_black_point_compensation);
gtk_widget_set_tooltip_text
(d->black_point_compensation,
_("activate black point compensation when applying the printer profile"));
gtk_widget_set_sensitive(GTK_WIDGET(d->black_point_compensation),
combo_idx == 0 ? FALSE : TRUE);
////////////////////////// PAGE SETTINGS
label = dt_ui_section_label_new(C_("section", "page"));
gtk_box_pack_start(GTK_BOX(self->widget), label, TRUE, TRUE, 0);
dt_gui_add_help_link(self->widget, "print_settings_page");
//// papers
dt_bauhaus_widget_set_label(d->papers, NULL, N_("paper size"));
g_signal_connect(G_OBJECT(d->papers), "value-changed", G_CALLBACK(_paper_changed), self);
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->papers), TRUE, TRUE, 0);
//// portrait / landscape
DT_BAUHAUS_COMBOBOX_NEW_FULL(d->orientation, self, NULL, N_("orientation"), NULL,
d->prt.page.landscape?1:0, _orientation_changed, self,
N_("portrait"), N_("landscape"));
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->orientation), TRUE, TRUE, 0);
// NOTE: units has no label, which makes for cleaner UI but means
// that no action can be assigned
GtkWidget *ucomb =
dt_bauhaus_combobox_new_full(DT_ACTION(self), NULL, NULL,
_("measurement units"),
d->unit, (GtkCallback)_unit_changed, self,
_unit_names);
gtk_box_pack_start(GTK_BOX(self->widget), ucomb, TRUE, TRUE, 0);
//// image dimensions, create them now as we need them
GtkWidget *hboxdim = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,0);
label = gtk_label_new(_("image width/height"));
gtk_box_pack_start(GTK_BOX(hboxdim), GTK_WIDGET(label), TRUE, TRUE,
DT_PIXEL_APPLY_DPI(3));
d->width = gtk_label_new(_("width"));
gtk_box_pack_start(GTK_BOX(hboxdim), GTK_WIDGET(d->width), TRUE, TRUE, 0);
label = gtk_label_new(_(" x "));
gtk_box_pack_start(GTK_BOX(hboxdim), GTK_WIDGET(label), TRUE, TRUE, 0);
d->height = gtk_label_new(_("height"));
gtk_box_pack_start(GTK_BOX(hboxdim), GTK_WIDGET(d->height), TRUE, TRUE, 0);
//// image information (downscale/upscale)
GtkWidget *hboxinfo = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,0);
label = gtk_label_new(_("scale factor"));
gtk_box_pack_start(GTK_BOX(hboxinfo), GTK_WIDGET(label), TRUE, TRUE,
DT_PIXEL_APPLY_DPI(3));
d->info = gtk_label_new("1.0");
gtk_box_pack_start(GTK_BOX(hboxinfo), GTK_WIDGET(d->info), TRUE, TRUE, 0);
gtk_widget_set_tooltip_text(hboxinfo,
_("image scale factor from native printer DPI:\n"
" < 1 means that it is downscaled (best quality)\n"
" > 1 means that the image is upscaled\n"
" a too large value may result in poor print quality"));
//// borders
GtkGrid *bds = GTK_GRID(gtk_grid_new());
gtk_grid_set_row_spacing(bds, DT_PIXEL_APPLY_DPI(3));
gtk_grid_set_column_spacing(bds, DT_PIXEL_APPLY_DPI(3));
d->lock_activated = FALSE;
//d->b_top = gtk_spin_button_new_with_range(0, 10000, 1);
gtk_widget_set_tooltip_text(GTK_WIDGET(d->b_top), _("top margin"));
gtk_grid_attach(bds, GTK_WIDGET(d->b_top), 1, 0, 1, 1);
//d->b_left = gtk_spin_button_new_with_range(0, 10000, 1);
gtk_widget_set_tooltip_text(GTK_WIDGET(d->b_left), _("left margin"));
gtk_grid_attach(bds, GTK_WIDGET(d->b_left), 0, 1, 1, 1);
d->lock_button = GTK_TOGGLE_BUTTON(gtk_toggle_button_new_with_label(_("lock")));
gtk_widget_set_tooltip_text(GTK_WIDGET(d->lock_button),
_("change all margins uniformly"));
gtk_grid_attach(bds, GTK_WIDGET(d->lock_button), 1, 1, 1, 1);
//d->b_right = gtk_spin_button_new_with_range(0, 10000, 1);
gtk_widget_set_tooltip_text(GTK_WIDGET(d->b_right), _("right margin"));
gtk_grid_attach(bds, GTK_WIDGET(d->b_right), 2, 1, 1, 1);
//d->b_bottom = gtk_spin_button_new_with_range(0, 10000, 1);
gtk_widget_set_tooltip_text(GTK_WIDGET(d->b_bottom), _("bottom margin"));
gtk_grid_attach(bds, GTK_WIDGET(d->b_bottom), 1, 2, 1, 1);
gtk_widget_set_halign(GTK_WIDGET(bds), GTK_ALIGN_CENTER);
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(bds), TRUE, TRUE, 0);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->b_top), top_b);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->b_bottom), bottom_b);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->b_left), left_b);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->b_right), right_b);
g_signal_connect(G_OBJECT (d->b_top), "value-changed",
G_CALLBACK (_top_border_callback), self);
g_signal_connect(G_OBJECT (d->b_bottom), "value-changed",
G_CALLBACK (_bottom_border_callback), self);
g_signal_connect(G_OBJECT (d->b_left), "value-changed",
G_CALLBACK (_left_border_callback), self);
g_signal_connect(G_OBJECT (d->b_right), "value-changed",
G_CALLBACK (_right_border_callback), self);
g_signal_connect(G_OBJECT(d->lock_button), "toggled",
G_CALLBACK(_lock_callback), self);
gtk_widget_set_halign(GTK_WIDGET(hboxdim), GTK_ALIGN_CENTER);
gtk_widget_set_halign(GTK_WIDGET(hboxinfo), GTK_ALIGN_CENTER);
const gboolean lock_active = dt_conf_get_bool(PRINT_CONFIG_PREFIX "lock_borders");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->lock_button), lock_active);
// grid & snap grid
{
GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
d->grid = gtk_check_button_new_with_label(_("display grid"));
// d->grid_size = gtk_spin_button_new_with_range(0, 100, 0.1);
gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(d->grid), TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(d->grid_size), TRUE, TRUE, 0);
gtk_spin_button_set_value
(GTK_SPIN_BUTTON(d->grid_size),
dt_conf_get_float(PRINT_CONFIG_PREFIX "grid_size") * units[d->unit]);
gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), TRUE, TRUE, 0);
d->snap_grid = gtk_check_button_new_with_label(_("snap to grid"));
gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(d->snap_grid), TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(vbox), TRUE, TRUE, 0);
g_signal_connect(G_OBJECT(d->grid_size), "value-changed",
G_CALLBACK(_grid_size_changed), self);
g_signal_connect(G_OBJECT(d->grid), "toggled",
G_CALLBACK(_grid_callback), self);
g_signal_connect(d->snap_grid, "toggled",
G_CALLBACK(_snap_grid_callback), (gpointer)self);
}
d->borderless = gtk_check_button_new_with_label(_("borderless mode required"));
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->borderless), TRUE, TRUE, 0);
gtk_widget_set_tooltip_text(d->borderless,
_("indicates that the borderless mode should be activated\n"
"in the printer driver because the selected margins are\n"
"below the printer hardware margins"));
gtk_widget_set_sensitive(d->borderless, FALSE);
// pack image dimension hbox here
label = dt_ui_section_label_new(C_("section", "image layout"));
gtk_box_pack_start(GTK_BOX(self->widget), label, TRUE, TRUE, 0);
dt_gui_add_help_link(self->widget, "print_image_layout");
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hboxdim), TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hboxinfo), TRUE, TRUE, 0);
//// alignments
// Auto-fit: Create the 3x3 gtk table toggle button table...
GtkGrid *bat = GTK_GRID(gtk_grid_new());
gtk_grid_set_row_spacing(bat, DT_PIXEL_APPLY_DPI(3));
gtk_grid_set_column_spacing(bat, DT_PIXEL_APPLY_DPI(3));
for(int i=0; i<9; i++)
{
d->dtba[i]
= DTGTK_TOGGLEBUTTON(dtgtk_togglebutton_new(dtgtk_cairo_paint_alignment,
(CPF_SPECIAL_FLAG << i), NULL));
gtk_grid_attach (GTK_GRID (bat), GTK_WIDGET (d->dtba[i]), (i%3), i/3, 1, 1);
g_signal_connect (G_OBJECT (d->dtba[i]), "toggled",
G_CALLBACK (_alignment_callback), self);
}
GtkWidget *hbox22 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,0);
GtkWidget *label4 = gtk_label_new(_("alignment"));
gtk_box_pack_start(GTK_BOX(hbox22),GTK_WIDGET(label4),TRUE,TRUE,0);
gtk_box_pack_start(GTK_BOX(hbox22), GTK_WIDGET(bat), TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox22), TRUE, TRUE, 0);
// Manual fit
GtkWidget *hfitbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
GtkWidget *mfitbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
GtkGrid *fitbut = GTK_GRID(gtk_grid_new());
gtk_grid_set_row_spacing(fitbut, DT_PIXEL_APPLY_DPI(3));
gtk_grid_set_column_spacing(fitbut, DT_PIXEL_APPLY_DPI(3));
gtk_grid_set_column_homogeneous(fitbut, TRUE);
gtk_grid_set_row_homogeneous(fitbut, TRUE);
GtkWidget *bnew = dt_action_button_new
(self, N_("new image area"),
_page_new_area_clicked, self,
_("add a new image area on the page\n"
"click and drag on the page to place the area\n"
"drag and drop image from film strip on it"), 0, 0);
d->del = dt_action_button_new(self, N_("delete image area"),
_page_delete_area_clicked, self,
_("delete the currently selected image area"), 0, 0);
gtk_widget_set_sensitive(d->del, FALSE);
GtkWidget *bclear = dt_action_button_new(self, N_("clear layout"),
_page_clear_area_clicked, self,
_("remove all image areas from the page"),
0, 0);
gtk_grid_attach(fitbut, GTK_WIDGET(bnew), 0, 0, 2, 1);
gtk_grid_attach(fitbut, GTK_WIDGET(d->del), 0, 1, 1, 1);
gtk_grid_attach(fitbut, GTK_WIDGET(bclear), 1, 1, 1, 1);
gtk_box_pack_start(GTK_BOX(mfitbox), GTK_WIDGET(fitbut), TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(hfitbox), GTK_WIDGET(mfitbox), TRUE, TRUE, 0);
// X x Y
GtkWidget *box;
// FIXME: add labels to x/y/width/height as otherwise are obscure --
// and there is the horizontal space to do this
box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,0);
// d->b_x = gtk_spin_button_new_with_range(0, 1000, 1);
gtk_widget_set_tooltip_text(d->b_x, _("image area x origin (in current unit)"));
gtk_entry_set_width_chars(GTK_ENTRY(d->b_x), 5);
// d->b_y = gtk_spin_button_new_with_range(0, 1000, 1);
gtk_widget_set_tooltip_text(d->b_y, _("image area y origin (in current unit)"));
gtk_entry_set_width_chars(GTK_ENTRY(d->b_y), 5);
gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(d->b_x), TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(d->b_y), TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(hfitbox), GTK_WIDGET(box), TRUE, TRUE, 0);
// width x height
box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,0);
// d->b_width = gtk_spin_button_new_with_range(0, 1000, 1);
gtk_widget_set_tooltip_text(d->b_width, _("image area width (in current unit)"));
gtk_entry_set_width_chars(GTK_ENTRY(d->b_width), 5);
// d->b_height = gtk_spin_button_new_with_range(0, 1000, 1);
gtk_widget_set_tooltip_text(d->b_height, _("image area height (in current unit)"));
gtk_entry_set_width_chars(GTK_ENTRY(d->b_height), 5);
gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(d->b_width), TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(d->b_height), TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(hfitbox), GTK_WIDGET(box), TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hfitbox), TRUE, TRUE, 0);
gtk_widget_add_events(d->b_x, GDK_BUTTON_PRESS_MASK);
gtk_widget_add_events(d->b_y, GDK_BUTTON_PRESS_MASK);
gtk_widget_add_events(d->b_width, GDK_BUTTON_PRESS_MASK);
gtk_widget_add_events(d->b_height, GDK_BUTTON_PRESS_MASK);
g_signal_connect(G_OBJECT(d->b_x), "value-changed",
G_CALLBACK(_x_changed), (gpointer)d);
g_signal_connect(G_OBJECT(d->b_y), "value-changed",
G_CALLBACK(_y_changed), (gpointer)d);
g_signal_connect(G_OBJECT(d->b_width), "value-changed",
G_CALLBACK(_width_changed), (gpointer)d);
g_signal_connect(G_OBJECT(d->b_height), "value-changed",
G_CALLBACK(_height_changed), (gpointer)d);
////////////////////////// PRINT SETTINGS
label = dt_ui_section_label_new(C_("section", "print settings"));
gtk_box_pack_start(GTK_BOX(self->widget), label, TRUE, TRUE, 0);
dt_gui_add_help_link(self->widget, "print_settings");
d->profile = dt_bauhaus_combobox_new_action(DT_ACTION(self));
dt_bauhaus_widget_set_label(d->profile, NULL, N_("profile"));
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->profile), TRUE, TRUE, 0);
// Add export profile combo
#ifdef _WIN32
_rebuild_image_profile_combo(d, combo_idx == 0); // combo_idx here refers to d->pprofile's just-established initial position
#else
dt_bauhaus_combobox_add(d->profile, _("image settings"));
const int icctype = dt_conf_get_int(PRINT_CONFIG_PREFIX "icctype");
const gchar *iccprofile = dt_conf_get_string_const(PRINT_CONFIG_PREFIX "iccprofile");
combo_idx = -1;
n = 0;
for(const GList *l = d->profiles; l; l = g_list_next(l))
{
dt_lib_export_profile_t *prof = l->data;
dt_bauhaus_combobox_add(d->profile, prof->name);
prof->pos = ++n;
if(prof->type == icctype
&& (prof->type != DT_COLORSPACE_FILE || !g_strcmp0(prof->filename, iccprofile)))
{
g_free(d->v_iccprofile);
d->v_icctype = icctype;
d->v_iccprofile = g_strdup(iccprofile);
combo_idx = n;
}
}
if(combo_idx == -1)
{
dt_conf_set_int(PRINT_CONFIG_PREFIX "icctype", DT_COLORSPACE_NONE);
dt_conf_set_string(PRINT_CONFIG_PREFIX "iccprofile", "");
g_free(d->v_iccprofile);
d->v_icctype = DT_COLORSPACE_NONE;
d->v_iccprofile = g_strdup("");
combo_idx = 0;
}
dt_bauhaus_combobox_set(d->profile, combo_idx);
#endif
tooltip = dt_ioppr_get_location_tooltip("out", _("output ICC profiles"));
gtk_widget_set_tooltip_markup(d->profile, tooltip);
g_free(tooltip);
g_signal_connect(G_OBJECT(d->profile), "value-changed",
G_CALLBACK(_profile_changed), (gpointer)self);
// Add export intent combo
DT_BAUHAUS_COMBOBOX_NEW_FULL(d->intent, self, NULL, N_("intent"), NULL,
dt_conf_get_int(PRINT_CONFIG_PREFIX "iccintent") + 1,
_intent_callback, self,
N_("image settings"),
N_("perceptual"),
N_("relative colorimetric"),
NC_("rendering intent", "saturation"),
N_("absolute colorimetric"));
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->intent), TRUE, TRUE, 0);
// Add export style combo
const char *current_style_name = dt_conf_get_string_const(PRINT_CONFIG_PREFIX "style");
GtkWidget *styles_button = dtgtk_button_new_full(dtgtk_cairo_paint_styles, 0, NULL,
&(dtgtk_button_config_t){
.tooltip = _("select style to be applied on printing"),
.clicked_cb = G_CALLBACK(_style_popupmenu_callback),
.clicked_data = (gpointer)d,
});
gtk_widget_set_halign(styles_button,GTK_ALIGN_END);
GtkBox *style_box = (GtkBox*)gtk_box_new(GTK_ORIENTATION_HORIZONTAL,0);
gtk_widget_set_tooltip_text(GTK_WIDGET(style_box), _("temporary style to use while printing"));
GtkWidget *styles_label = gtk_label_new(_("style"));
gtk_box_pack_start(style_box, styles_label, FALSE, FALSE, 0);
GtkWidget *current_style = gtk_label_new("");
gtk_widget_set_halign(current_style,GTK_ALIGN_END);
gtk_label_set_justify(GTK_LABEL(current_style), GTK_JUSTIFY_RIGHT);
gtk_label_set_ellipsize(GTK_LABEL(current_style), PANGO_ELLIPSIZE_MIDDLE);
gtk_box_pack_start(style_box, current_style, TRUE, TRUE, 0);
gtk_box_pack_start(style_box, styles_button, FALSE, FALSE, 0);
d->style = GTK_WIDGET(current_style);
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(style_box), FALSE, TRUE, 0);
// Whether to add/replace style items
d->v_style_append = dt_conf_get_bool(PRINT_CONFIG_PREFIX "style_append");
DT_BAUHAUS_COMBOBOX_NEW_FULL
(d->style_mode, self, NULL, N_("mode"),
_("whether the style items are appended to the history or replacing the history"),
d->v_style_append?1:0, _style_mode_changed, self,
N_("replace history"), N_("append history"));
gtk_widget_set_no_show_all(d->style_mode, TRUE);
const gboolean is_style_set = _is_style_set(current_style_name);
gtk_widget_set_visible(GTK_WIDGET(d->style_mode), is_style_set);
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->style_mode), TRUE, TRUE, 0);
_update_style_label(d, current_style_name ? current_style_name : "");
#ifdef _WIN32
GtkWidget *settings_button = dt_action_button_new(self, N_("print settings"),
_print_settings_button_clicked, self,
_("open printer settings"),
GDK_KEY_s, GDK_CONTROL_MASK);
d->print_settings_button = GTK_BUTTON(settings_button);
gtk_box_pack_start(GTK_BOX(self->widget), settings_button, TRUE, TRUE, 0);
// dt_gui_add_help_link(settings_button, "print_settings_button");
#endif
// Print button
// We use context here because it is an action button, and translators might
// translate "print" as a verb, whereas elsewhere in the UI it is a noun.
GtkWidget *button = dt_action_button_new(self, NC_("actionbutton", "print"),
_print_button_clicked, self,
_("print with current settings"),
GDK_KEY_p, GDK_CONTROL_MASK);
d->print_button = GTK_BUTTON(button);
gtk_box_pack_start(GTK_BOX(self->widget), button, TRUE, TRUE, 0);
dt_gui_add_help_link(button, "print_settings_button");
// Let's start the printer discovery now
#ifdef _WIN32
// Start discovery with both callbacks
dt_win_printers_discovery(_new_printer_callback, _printer_ready_cb, self);
#else
// Linux path: just the discovery callback
dt_printers_discovery(_new_printer_callback, self);
#endif
}
void *legacy_params(dt_lib_module_t *self,
const void *const old_params,
const size_t old_params_size,
const int old_version,
int *new_version,
size_t *new_size)
{
if(old_version == 1)
{
// we added the profile type
//
// old format:
// char *printer
// char *paper
// int32_t landscape
// char *f_profile
// int32_t intent
// char *f_pprofile
// <rest>
//
// new format:
// char *printer
// char *paper
// int32_t landscape
// int32_t f_profile_type
// char *f_profile
// int32_t intent
// int32_t f_pprofile_type
// char *f_pprofile
// <rest>
const char *buf = (const char *)old_params;
// printer
const char *printer = buf;
const int32_t printer_len = strlen(printer) + 1;
buf += printer_len;
// paper
const char *paper = buf;
const int32_t paper_len = strlen(paper) + 1;
buf += paper_len;
// landscape
const int32_t landscape = *(int32_t *)buf;
buf += sizeof(int32_t);
// profile
const char *profile = buf;
const int32_t profile_len = strlen(profile) + 1;
buf += profile_len;
// intent
const int32_t intent = *(int32_t *)buf;
buf += sizeof(int32_t);
// pprofile
const char *pprofile = buf;
const int32_t pprofile_len = strlen(pprofile) + 1;
buf += pprofile_len;
// now we got all fields from the start of the buffer and buf
// points to the beginning or <rest>
// find the new values for the two profiles
dt_colorspaces_color_profile_type_t profile_type, pprofile_type;
const char *profile_filename = "", *pprofile_filename = "";
if(*profile == '\0' || !g_strcmp0(profile, "none"))
{
profile_type = DT_COLORSPACE_NONE;
}
else if(!g_strcmp0(profile, "sRGB"))
{
profile_type = DT_COLORSPACE_SRGB;
}
else if(!g_strcmp0(profile, "adobergb"))
{
profile_type = DT_COLORSPACE_ADOBERGB;
}
else
{
profile_type = DT_COLORSPACE_FILE;
profile_filename = &profile[1]; // the old code had a '/' in the beginning
}
// in theory pprofile can't be srgb or adobergb, but checking for them won't hurt
if(*pprofile == '\0')
{
pprofile_type = DT_COLORSPACE_NONE;
}
else if(!g_strcmp0(pprofile, "sRGB"))
{
pprofile_type = DT_COLORSPACE_SRGB;
}
else if(!g_strcmp0(pprofile, "adobergb"))
{
pprofile_type = DT_COLORSPACE_ADOBERGB;
}
else
{
pprofile_type = DT_COLORSPACE_FILE;
pprofile_filename = &pprofile[1]; // the old code had a '/' in the beginning
}
const int32_t new_profile_len = strlen(profile_filename) + 1;
const int32_t new_pprofile_len = strlen(pprofile_filename) + 1;
// now we got everything to reassemble the new params
size_t new_params_size = old_params_size - profile_len - pprofile_len;
new_params_size += 2 * sizeof(dt_colorspaces_color_profile_type_t);
new_params_size += new_profile_len + new_pprofile_len;
void *new_params = malloc(new_params_size);
size_t pos = 0;
// char *printer
memcpy((uint8_t *)new_params + pos, printer, printer_len);
pos += printer_len;
// char *paper
memcpy((uint8_t *)new_params + pos, paper, paper_len);
pos += paper_len;
// int32_t landscape
memcpy((uint8_t *)new_params + pos, &landscape, sizeof(int32_t));
pos += sizeof(int32_t);
// int32_t f_profile_type
memcpy((uint8_t *)new_params + pos, &profile_type, sizeof(int32_t));
pos += sizeof(int32_t);
// char *f_profile
memcpy((uint8_t *)new_params + pos, profile_filename, new_profile_len);
pos += new_profile_len;
// int32_t intent
memcpy((uint8_t *)new_params + pos, &intent, sizeof(int32_t));
pos += sizeof(int32_t);
// int32_t f_pprofile_type
memcpy((uint8_t *)new_params + pos, &pprofile_type, sizeof(int32_t));
pos += sizeof(int32_t);
// char *f_pprofile
memcpy((uint8_t *)new_params + pos, pprofile_filename, new_pprofile_len);
pos += new_pprofile_len;
// <rest>
memcpy((uint8_t *)new_params + pos,
buf,
old_params_size - ((char *)buf - (char *)old_params));
*new_size = new_params_size;
*new_version = 2;
return new_params;
}
else if(old_version == 2)
{
// add upscale to params
size_t new_params_size = old_params_size + 1;
void *new_params = calloc(1, new_params_size);
memcpy(new_params, old_params, old_params_size);
// no media type specified
((char *)new_params)[old_params_size] = '\0';
*new_size = new_params_size;
*new_version = 3;
return new_params;
}
else if(old_version == 3)
{
// no box
size_t new_params_size = old_params_size + sizeof(int32_t) + 4 * sizeof(float);
void *new_params = calloc(1, new_params_size);
memcpy(new_params, old_params, old_params_size);
// single image box specified (there is no way to create a box on the size
// of the page at this stage).
int32_t idx = old_params_size;
*(int32_t *)((uint8_t *)new_params + idx) = 1;
idx += sizeof(int32_t);
*(float *)((uint8_t *)new_params + idx) = 0.05f;
idx += sizeof(float);
*(float *)((uint8_t *)new_params + idx) = 0.05f;
idx += sizeof(float);
*(float *)((uint8_t *)new_params + idx) = 0.90f;
idx += sizeof(float);
*(float *)((uint8_t *)new_params + idx) = 0.90f;
// idx += sizeof(float);
*new_size = new_params_size;
*new_version = 4;
return new_params;
}
return NULL;
}
int set_params(dt_lib_module_t *self,
const void *params,
const int size)
{
dt_lib_print_settings_t *ps = self->data;
if(!params) return 1;
// get the parameters buffer
const char *buf = (char *)params;
// get individual items
const char *printer = buf;
if(!printer) return 1;
const int32_t printer_len = strlen(printer) + 1;
buf += printer_len;
const char *paper = buf;
if(!paper) return 1;
const int32_t paper_len = strlen(paper) + 1;
buf += paper_len;
const int32_t landscape = *(int32_t *)buf;
buf += sizeof(int32_t);
const int32_t f_profile_type = *(int32_t *)buf;
buf += sizeof(int32_t);
const char *f_profile = buf;
if(!f_profile) return 1;
const int32_t profile_len = strlen(f_profile) + 1;
buf += profile_len;
const int32_t intent = *(int32_t *)buf;
buf += sizeof(int32_t);
const int32_t f_pprofile_type = *(int32_t *)buf;
buf += sizeof(int32_t);
const char *f_pprofile = buf;
if(!f_pprofile) return 1;
const int32_t pprofile_len = strlen(f_pprofile) + 1;
buf += pprofile_len;
const int32_t pintent = *(int32_t *)buf;
buf += sizeof(int32_t);
const int32_t bpc = *(int32_t *)buf;
buf += sizeof(int32_t);
const char *style = buf;
if(!style) return 1;
const int32_t style_len = strlen(style) + 1;
buf += style_len;
const int32_t style_mode = *(int32_t *)buf;
buf += sizeof(int32_t);
const double b_top = *(double *)buf;
buf += sizeof(double);
const double b_bottom = *(double *)buf;
buf += sizeof(double);
const double b_left = *(double *)buf;
buf += sizeof(double);
const double b_right = *(double *)buf;
buf += sizeof(double);
const int32_t alignment = *(int32_t *)buf;
buf += sizeof(int32_t);
const char *media = buf;
if(!media) return 1;
const int32_t media_len = strlen(media) + 1;
buf += media_len;
ps->imgs.count = *(int32_t *)buf;
buf += sizeof(int32_t);
for(int k=0; k<ps->imgs.count; k++)
{
ps->imgs.box[k].pos.x = *(float *)buf;
buf += sizeof(float);
ps->imgs.box[k].pos.y = *(float *)buf;
buf += sizeof(float);
ps->imgs.box[k].pos.width = *(float *)buf;
buf += sizeof(float);
ps->imgs.box[k].pos.height = *(float *)buf;
buf += sizeof(float);
}
// ensure that the size is correct
if(size != printer_len + paper_len + media_len + profile_len
+ pprofile_len + style_len
+ 8 * sizeof(int32_t)
+ 4 * sizeof(double)
+ sizeof(int32_t)
+ (ps->imgs.count * 4 * sizeof(float)))
return 1;
// set the GUI with corresponding values
if(printer[0] != '\0')
dt_bauhaus_combobox_set_from_text(ps->printers, printer);
if(paper[0] != '\0')
dt_bauhaus_combobox_set_from_text(ps->papers, paper);
if(media[0] != '\0')
dt_bauhaus_combobox_set_from_text(ps->media, media);
dt_bauhaus_combobox_set (ps->orientation, landscape);
dt_bauhaus_combobox_set(ps->profile, 0);
for(GList *iter = ps->profiles; iter; iter = g_list_next(iter))
{
dt_lib_export_profile_t *p = iter->data;
if(f_profile_type == p->type && (f_profile_type != DT_COLORSPACE_FILE
|| !g_strcmp0(f_profile, p->filename)))
{
dt_bauhaus_combobox_set(ps->profile, p->pos);
break;
}
}
dt_bauhaus_combobox_set (ps->intent, intent);
dt_bauhaus_combobox_set(ps->pprofile, 0);
for(GList *iter = ps->profiles; iter; iter = g_list_next(iter))
{
dt_lib_export_profile_t *p = iter->data;
if(f_pprofile_type == p->type && (f_pprofile_type != DT_COLORSPACE_FILE
|| !g_strcmp0(f_pprofile, p->filename)))
{
dt_bauhaus_combobox_set(ps->pprofile, p->ppos);
break;
}
}
dt_bauhaus_combobox_set (ps->pintent, pintent);
ps->prt.printer.intent = pintent;
_update_style_label(ps,style);
dt_bauhaus_combobox_set (ps->style_mode, style_mode);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_top), b_top * units[ps->unit]);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_bottom), b_bottom * units[ps->unit]);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_left), b_left * units[ps->unit]);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_right), b_right * units[ps->unit]);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ps->dtba[alignment]), TRUE);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ps->black_point_compensation), bpc);
dt_control_queue_redraw_center();
return 0;
}
void *get_params(dt_lib_module_t *self, int *size)
{
const dt_lib_print_settings_t *ps = self->data;
// get the data
const char *printer = dt_bauhaus_combobox_get_text(ps->printers);
const char *paper = dt_bauhaus_combobox_get_text(ps->papers);
const char *media = dt_bauhaus_combobox_get_text(ps->media);
const int32_t profile_pos = dt_bauhaus_combobox_get(ps->profile);
const int32_t intent = dt_bauhaus_combobox_get(ps->intent);
const char *style = gtk_label_get_text(GTK_LABEL(ps->style));
const int32_t style_mode = dt_bauhaus_combobox_get(ps->style_mode);
const int32_t pprofile_pos = dt_bauhaus_combobox_get(ps->pprofile);
const int32_t pintent = dt_bauhaus_combobox_get(ps->pintent);
const int32_t landscape = dt_bauhaus_combobox_get(ps->orientation);
const int32_t bpc = gtk_toggle_button_get_active
(GTK_TOGGLE_BUTTON(ps->black_point_compensation));
const double b_top = ps->prt.page.margin_top;
const double b_bottom = ps->prt.page.margin_bottom;
const double b_left = ps->prt.page.margin_left;
const double b_right = ps->prt.page.margin_right;
const int32_t alignment = 0;
dt_colorspaces_color_profile_type_t profile_type =
DT_COLORSPACE_NONE, pprofile_type = DT_COLORSPACE_NONE;
const char *profile = "", *pprofile = "";
for(GList *iter = ps->profiles; iter; iter = g_list_next(iter))
{
dt_lib_export_profile_t *p = iter->data;
if(p->pos == profile_pos)
{
profile_type = p->type;
profile = p->filename;
}
if(p->ppos == pprofile_pos)
{
pprofile_type = p->type;
pprofile = p->filename;
}
}
// these will be NULL when no printer is connected/found
if(!printer) printer = "";
if(!paper) paper = "";
if(!media) media = "";
// compute the size of individual items, always get the \0 for strings
const int32_t printer_len = strlen (printer) + 1;
const int32_t paper_len = strlen (paper) + 1;
const int32_t media_len = strlen (media) + 1;
const int32_t profile_len = strlen (profile) + 1;
const int32_t pprofile_len = strlen (pprofile) + 1;
const int32_t style_len = strlen (style) + 1;
// compute the size of all parameters
*size = printer_len + paper_len + media_len
+ profile_len + pprofile_len + style_len
+ 8 * sizeof(int32_t)
+ 4 * sizeof(double)
+ sizeof(int32_t)
+ (ps->imgs.count * 4 * sizeof(float));
// allocate the parameter buffer
char *params = (char *)malloc(*size);
int pos = 0;
memcpy(params+pos, printer, printer_len);
pos += printer_len;
memcpy(params+pos, paper, paper_len);
pos += paper_len;
memcpy(params+pos, &landscape, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params+pos, &profile_type, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params+pos, profile, profile_len);
pos += profile_len;
memcpy(params+pos, &intent, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params+pos, &pprofile_type, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params+pos, pprofile, pprofile_len);
pos += pprofile_len;
memcpy(params+pos, &pintent, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params+pos, &bpc, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params+pos, style, style_len);
pos += style_len;
memcpy(params+pos, &style_mode, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params+pos, &b_top, sizeof(double));
pos += sizeof(double);
memcpy(params+pos, &b_bottom, sizeof(double));
pos += sizeof(double);
memcpy(params+pos, &b_left, sizeof(double));
pos += sizeof(double);
memcpy(params+pos, &b_right, sizeof(double));
pos += sizeof(double);
memcpy(params+pos, &alignment, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(params+pos, media, media_len);
pos += media_len;
// boxes
memcpy(params+pos, &ps->imgs.count, sizeof(int32_t));
pos += sizeof(int32_t);
for(int k=0; k<ps->imgs.count; k++)
{
memcpy(params+pos, &ps->imgs.box[k].pos.x, sizeof(float));
pos += sizeof(int32_t);
memcpy(params+pos, &ps->imgs.box[k].pos.y, sizeof(float));
pos += sizeof(int32_t);
memcpy(params+pos, &ps->imgs.box[k].pos.width, sizeof(float));
pos += sizeof(int32_t);
memcpy(params+pos, &ps->imgs.box[k].pos.height, sizeof(float));
pos += sizeof(int32_t);
}
g_assert(pos == *size);
return params;
}
void gui_cleanup(dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
// these can be called on shutdown, resulting in null-pointer
// dereference and division by zero -- not sure what interaction
// makes them called, but better to disconnect and not have segfault
g_signal_handlers_disconnect_by_func(G_OBJECT(ps->b_top),
G_CALLBACK(_top_border_callback), self);
g_signal_handlers_disconnect_by_func(G_OBJECT(ps->b_bottom),
G_CALLBACK(_bottom_border_callback), self);
g_signal_handlers_disconnect_by_func(G_OBJECT(ps->b_left),
G_CALLBACK(_left_border_callback), self);
g_signal_handlers_disconnect_by_func(G_OBJECT(ps->b_right),
G_CALLBACK(_right_border_callback), self);
g_list_free_full(ps->profiles, g_free);
g_list_free_full(ps->paper_list, free);
g_list_free_full(ps->media_list, free);
g_free(ps->v_iccprofile);
g_free(ps->v_piccprofile);
g_free(ps->v_style);
#ifdef _WIN32
g_free(ps->waiting_for_printer);
if(ps->settings_ctx)
{
dt_win32_print_ctx_free(ps->settings_ctx);
ps->settings_ctx = NULL; // important!
}
#endif
free(self->data);
self->data = NULL;
}
void gui_reset(dt_lib_module_t *self)
{
dt_lib_print_settings_t *ps = self->data;
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_top), 17 * units[ps->unit]);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_bottom), 17 * units[ps->unit]);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_left), 17 * units[ps->unit]);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->b_right), 17 * units[ps->unit]);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(ps->grid_size), 10 * units[ps->unit]);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ps->dtba[ALIGNMENT_CENTER]), TRUE);
ps->prt.printer.intent = DT_INTENT_PERCEPTUAL;
dt_bauhaus_combobox_set(ps->profile, 0);
dt_bauhaus_combobox_set(ps->pprofile, 0);
dt_bauhaus_combobox_set(ps->pintent, 0);
_update_style_label(ps,"");
dt_bauhaus_combobox_set(ps->intent, 0);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ps->black_point_compensation), TRUE);
gtk_widget_set_sensitive(GTK_WIDGET(ps->pintent), TRUE);
gtk_widget_set_sensitive(GTK_WIDGET(ps->black_point_compensation), FALSE);
gtk_widget_set_sensitive(GTK_WIDGET(ps->style_mode), FALSE);
// reset page orientation to fit the picture if a single one is displayed
const dt_imgid_t imgid = (ps->imgs.count > 0) ? ps->imgs.box[0].imgid : NO_IMGID;
dt_printing_clear_boxes(&ps->imgs);
ps->imgs.imgid_to_load = imgid;
ps->creation = ps->dragging = FALSE;
ps->selected = -1;
ps->last_selected = -1;
ps->has_changed = FALSE;
dt_control_queue_redraw_center();
}
// clang-format off
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
// clang-format on