Bug fix #160: Replace print dialog by standard GTK dialog
[geeqie.git] / src / print.c
1 /*
2  * Copyright (C) 2018 The Geeqie Team
3  *
4  * Author: Colin Clark
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include "main.h"
22 #include "print.h"
23
24 #include "filedata.h"
25 #include "image-load.h"
26 #include "ui_misc.h"
27 #include "ui_fileops.h"
28
29 #define PRINT_SETTINGS "print_settings" // filename save printer settings
30 #define PAGE_SETUP "page_setup" // filename save page setup
31
32 /* method to use when scaling down image data */
33 #define PRINT_MAX_INTERP GDK_INTERP_HYPER
34
35 typedef enum {
36         TEXT_INFO_FILENAME = 1 << 0,
37         TEXT_INFO_FILEDATE = 1 << 1,
38         TEXT_INFO_FILESIZE = 1 << 2,
39         TEXT_INFO_DIMENSIONS = 1 << 3,
40         TEXT_INFO_FILEPATH = 1 << 4
41 } TextInfo;
42
43 typedef struct _PrintWindow PrintWindow;
44 struct _PrintWindow
45 {
46         GtkWidget *vbox;
47         GList *source_selection;
48
49         TextInfo        text_fields;
50         gint             job_page;
51         ImageLoader     *job_loader;
52
53         GList *print_pixbuf_queue;
54         gboolean job_render_finished;
55 };
56
57 static gint print_layout_page_count(PrintWindow *pw)
58 {
59         gint images;
60
61         images = g_list_length(pw->source_selection);
62
63         if (images < 1 ) return 0;
64
65         return images;
66 }
67
68 static gboolean print_job_render_image(PrintWindow *pw);
69
70 static void print_job_render_image_loader_done(ImageLoader *il, gpointer data)
71 {
72         PrintWindow *pw = data;
73         GdkPixbuf *pixbuf;
74
75         pixbuf = image_loader_get_pixbuf(il);
76
77         g_object_ref(pixbuf);
78         pw->print_pixbuf_queue = g_list_append(pw->print_pixbuf_queue, pixbuf);
79
80         image_loader_free(pw->job_loader);
81         pw->job_loader = NULL;
82
83         pw->job_page++;
84
85         if (!print_job_render_image(pw))
86                 {
87                 pw->job_render_finished = TRUE;
88                 }
89 }
90
91 static gboolean print_job_render_image(PrintWindow *pw)
92 {
93         FileData *fd = NULL;
94
95         fd = g_list_nth_data(pw->source_selection, pw->job_page);
96         if (!fd) return FALSE;
97
98         image_loader_free(pw->job_loader);
99         pw->job_loader = NULL;
100
101         pw->job_loader = image_loader_new(fd);
102         g_signal_connect(G_OBJECT(pw->job_loader), "done",
103                                                 (GCallback)print_job_render_image_loader_done, pw);
104
105         if (!image_loader_start(pw->job_loader))
106                 {
107                 image_loader_free(pw->job_loader);
108                 pw->job_loader= NULL;
109                 }
110
111         return TRUE;
112 }
113
114 static void print_text_field_set(PrintWindow *pw, TextInfo field, gboolean active)
115 {
116         if (active)
117                 {
118                 pw->text_fields |= field;
119                 }
120         else
121                 {
122                 pw->text_fields &= ~field;
123                 }
124 }
125
126 static void print_text_cb_name(GtkWidget *widget, gpointer data)
127 {
128         PrintWindow *pw = data;
129         gboolean active;
130
131         active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
132         print_text_field_set(pw, TEXT_INFO_FILENAME, active);
133 }
134
135 static void print_text_cb_path(GtkWidget *widget, gpointer data)
136 {
137         PrintWindow *pw = data;
138         gboolean active;
139
140         active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
141         print_text_field_set(pw, TEXT_INFO_FILEPATH, active);
142 }
143
144 static void print_text_cb_date(GtkWidget *widget, gpointer data)
145 {
146         PrintWindow *pw = data;
147         gboolean active;
148
149         active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
150         print_text_field_set(pw, TEXT_INFO_FILEDATE, active);
151 }
152
153 static void print_text_cb_size(GtkWidget *widget, gpointer data)
154 {
155         PrintWindow *pw = data;
156         gboolean active;
157
158         active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
159         print_text_field_set(pw, TEXT_INFO_FILESIZE, active);
160 }
161
162 static void print_text_cb_dims(GtkWidget *widget, gpointer data)
163 {
164         PrintWindow *pw = data;
165         gboolean active;
166
167         active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
168         print_text_field_set(pw, TEXT_INFO_DIMENSIONS, active);
169 }
170
171 static void print_set_font_cb(GtkWidget *widget, gpointer data)
172 {
173 #if GTK_CHECK_VERSION(3,4,0)
174         GtkWidget *dialog;
175         char *font;
176         PangoFontDescription *font_desc;
177
178         dialog = gtk_font_chooser_dialog_new("Printer Font", GTK_WINDOW(gtk_widget_get_toplevel(widget)));
179         gtk_font_chooser_set_font(GTK_FONT_CHOOSER(dialog), options->printer.font);
180
181         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_CANCEL)
182                 {
183                 font_desc = gtk_font_chooser_get_font_desc(GTK_FONT_CHOOSER(dialog));
184                 font = pango_font_description_to_string(font_desc);
185                 g_free(options->printer.font);
186                 options->printer.font = g_strdup(font);
187                 g_free(font);
188                 }
189
190         gtk_widget_destroy(dialog);
191 #else
192         const char *font;
193
194         font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(widget));
195         options->printer.font = g_strdup(font);
196 #endif
197 }
198
199 static void print_text_menu(GtkWidget *box, PrintWindow *pw)
200 {
201         GtkWidget *group;
202         GtkWidget *hbox;
203         GtkWidget *button;
204
205         group = pref_group_new(box, FALSE, _("Show"), GTK_ORIENTATION_VERTICAL);
206
207         pref_checkbox_new(group, _("Name"), (pw->text_fields & TEXT_INFO_FILENAME),
208                           G_CALLBACK(print_text_cb_name), pw);
209         pref_checkbox_new(group, _("Path"), (pw->text_fields & TEXT_INFO_FILEPATH),
210                           G_CALLBACK(print_text_cb_path), pw);
211         pref_checkbox_new(group, _("Date"), (pw->text_fields & TEXT_INFO_FILEDATE),
212                           G_CALLBACK(print_text_cb_date), pw);
213         pref_checkbox_new(group, _("Size"), (pw->text_fields & TEXT_INFO_FILESIZE),
214                           G_CALLBACK(print_text_cb_size), pw);
215         pref_checkbox_new(group, _("Dimensions"), (pw->text_fields & TEXT_INFO_DIMENSIONS),
216                           G_CALLBACK(print_text_cb_dims), pw);
217
218         group = pref_group_new(box, FALSE, _("Font"), GTK_ORIENTATION_VERTICAL);
219
220         hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_BUTTON_GAP);
221
222 #if GTK_CHECK_VERSION(3,4,0)
223         button = pref_button_new(NULL, GTK_STOCK_SELECT_FONT, _("Font"), FALSE,
224                                  G_CALLBACK(print_set_font_cb), pw);
225 #else
226         button = gtk_font_button_new();
227         gtk_font_button_set_title(GTK_FONT_BUTTON(button), "Printer Font");
228         gtk_font_button_set_font_name(GTK_FONT_BUTTON(button), options->printer.font);
229         g_signal_connect(G_OBJECT(button), "font-set",
230                                  G_CALLBACK(print_set_font_cb),NULL);
231 #endif
232         gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
233         gtk_widget_show(button);
234 }
235
236 static gboolean paginate_cb(GtkPrintOperation *operation,
237                                                                         GtkPrintContext *context,
238                                                                         gpointer data)
239 {
240         PrintWindow *pw = data;
241
242         if (pw->job_render_finished)
243                 {
244                 return TRUE;
245                 }
246         else
247                 {
248                 return FALSE;
249                 }
250 }
251
252 /* Returns the "depth" of a layout, that is the distance from the
253  * top of the layout to the baseline of the first line in the
254  * layout. */
255 int get_layout_depth(PangoLayout *layout)
256 {
257   PangoLayoutLine *layout_line = pango_layout_get_line(layout,0);
258   PangoRectangle rect;
259
260   pango_layout_line_get_extents(layout_line, NULL, &rect);
261
262   return PANGO_ASCENT(rect);
263 }
264
265 static void draw_page(GtkPrintOperation *operation, GtkPrintContext *context,
266                                                                         gint page_nr, gpointer data)
267 {
268         FileData *fd;
269         PrintWindow *pw = data;
270         cairo_t *cr;
271         gdouble width, height;
272         gdouble width_pixbuf_image, height_pixbuf_image;
273         gdouble width_offset;
274         gdouble height_offset;
275         GdkPixbuf *pixbuf;
276         GdkPixbuf *pixbuf_scaled;
277         PangoLayout *layout;
278         PangoFontDescription *desc;
279         GString *text = g_string_new(NULL);
280         PangoRectangle ink_rect, logical_rect;
281         gdouble depth;
282         gdouble text_padding;
283         gdouble x, y, w, h, scale;
284         gdouble pango_height;
285
286         pixbuf = g_list_nth_data(pw->print_pixbuf_queue, page_nr);
287         width_pixbuf_image = gdk_pixbuf_get_width(pixbuf);
288         height_pixbuf_image = gdk_pixbuf_get_height(pixbuf);
289
290         fd = g_list_nth_data(pw->source_selection, page_nr);
291
292         if (pw->text_fields & TEXT_INFO_FILENAME)
293                 {
294                 text = g_string_append(text, g_strdup(fd->name));
295                 text = g_string_append(text, "\n");
296                 }
297         if (pw->text_fields & TEXT_INFO_FILEDATE)
298                 {
299                 text = g_string_append(text, g_strdup(text_from_time(fd->date)));
300                 text = g_string_append(text, "\n");
301                 }
302         if (pw->text_fields & TEXT_INFO_FILESIZE)
303                 {
304                 text = g_string_append(text, g_strdup(text_from_size(fd->size)));
305                 text = g_string_append(text, "\n");
306                 }
307         if (pw->text_fields & TEXT_INFO_DIMENSIONS)
308                 {
309                 g_string_append_printf(text, "%d x %d", (gint)width_pixbuf_image,
310                                                                                         (gint)height_pixbuf_image);
311                 text = g_string_append(text, "\n");
312                 }
313         if (pw->text_fields & TEXT_INFO_FILEPATH)
314                 {
315                 text = g_string_append(text, g_strdup(fd->path));
316                 text = g_string_append(text, "\n");
317                 }
318
319         cr = gtk_print_context_get_cairo_context(context);
320         width = gtk_print_context_get_width(context);
321         height = gtk_print_context_get_height(context);
322
323         if (text->len > 0)
324                 {
325                 text = g_string_truncate(text, text->len - 1);
326
327                 layout = pango_cairo_create_layout(cr);
328
329                 pango_layout_set_text(layout, text->str, -1);
330                 desc = pango_font_description_from_string(options->printer.font);
331                 pango_layout_set_font_description(layout, desc);
332
333                 pango_layout_get_extents(layout, &ink_rect, &logical_rect);
334                 x = ((gdouble)logical_rect.width / PANGO_SCALE) ;
335                 y = ((gdouble)logical_rect.height / PANGO_SCALE);
336
337                 pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
338                 pango_layout_set_text(layout, text->str, -1);
339
340                 depth = (gdouble)get_layout_depth(layout);
341                 text_padding = depth / 2 / PANGO_SCALE ;
342
343                 pango_height = y + text_padding * 2;
344
345                 }
346         else
347                 {
348                 pango_height = 0;
349                 depth = 0;
350                 text_padding = 0;
351                 x = 0;
352                 y = 0;
353                 }
354
355         if ((width / width_pixbuf_image) < ((height - pango_height) / height_pixbuf_image))
356                 {
357                 w = width;
358                 scale = width / width_pixbuf_image;
359                 h = height_pixbuf_image * scale;
360                 height_offset = (height - (h + pango_height)) / 2;
361                 width_offset = 0;
362                 }
363         else
364                 {
365                 h = height - pango_height ;
366                 scale = (height - pango_height) / height_pixbuf_image;
367                 w = width_pixbuf_image * scale;
368                 height_offset = 0;
369                 width_offset = (width - (width_pixbuf_image * scale)) / 2;
370                 }
371
372         if (text->len > 0)
373                 {
374                 cairo_move_to(cr, (w / 2) - (x / 2) + width_offset, h + height_offset + text_padding);
375                 pango_cairo_show_layout(cr, layout);
376                 }
377
378         pixbuf_scaled = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
379         gdk_pixbuf_scale(pixbuf, pixbuf_scaled, 0, 0, w, h, 0, 0,  scale, scale, PRINT_MAX_INTERP);
380
381         cairo_rectangle(cr, width_offset, height_offset, w, h);
382
383         gdk_cairo_set_source_pixbuf(cr, pixbuf_scaled, width_offset, height_offset);
384         cairo_fill(cr);
385
386         if (text->len > 0)
387                 {
388                 g_object_unref(layout);
389                 g_string_free(text, TRUE);
390                 pango_font_description_free(desc);
391                 }
392
393         g_object_unref(pixbuf_scaled);
394
395         return;
396 }
397
398 static void begin_print(GtkPrintOperation *operation,
399                                                 GtkPrintContext *context,
400                                                 gpointer user_data)
401 {
402         PrintWindow *pw = user_data;
403         gint page_count;
404
405         page_count = print_layout_page_count(pw);
406         gtk_print_operation_set_n_pages (operation, page_count);
407
408         print_job_render_image(pw);
409 }
410
411
412 GObject *option_tab_cb(GtkPrintOperation *operation, gpointer user_data)
413 {
414         PrintWindow *pw = user_data;
415
416         return G_OBJECT(pw->vbox);
417 }
418
419 static void print_pref_store(PrintWindow *pw)
420 {
421         options->printer.text_fields = pw->text_fields;
422 }
423
424 static void end_print_cb(GtkPrintOperation *operation,
425                                                                 GtkPrintContext *context, gpointer data)
426 {
427         PrintWindow *pw = data;
428         GList *work;
429         GdkPixbuf *pixbuf;
430         gchar *path;
431         GtkPrintSettings *print_settings;
432         GtkPageSetup *page_setup;
433         GError *error = NULL;
434
435         print_settings = gtk_print_operation_get_print_settings(operation);
436         path = g_build_filename(get_rc_dir(), PRINT_SETTINGS, NULL);
437
438         gtk_print_settings_to_file(print_settings, path, &error);
439         if (error)
440                 {
441                 log_printf("Error: Print settings save failed:\n%s", error->message);
442                 g_error_free(error);
443                 error = NULL;
444                 }
445         g_free(path);
446         g_object_unref(print_settings);
447
448         page_setup = gtk_print_operation_get_default_page_setup(operation);
449         path = g_build_filename(get_rc_dir(), PAGE_SETUP, NULL);
450
451         gtk_page_setup_to_file(page_setup, path, &error);
452         if (error)
453                 {
454                 log_printf("Error: Print page setup save failed:\n%s", error->message);
455                 g_error_free(error);
456                 error = NULL;
457                 }
458         g_free(path);
459         g_object_unref(page_setup);
460
461         print_pref_store(pw);
462
463         work = pw->print_pixbuf_queue;
464         while (work)
465                 {
466                 pixbuf = work->data;
467                 if (pixbuf)
468                         {
469                         g_object_unref(pixbuf);
470                         }
471                 work = work->next;
472                 }
473         g_list_free(pw->print_pixbuf_queue);
474         g_free(pw);
475 }
476
477 void print_window_new(FileData *fd, GList *selection, GList *list, GtkWidget *parent)
478 {
479         PrintWindow *pw;
480         GtkWidget *vbox;
481         GtkPrintOperation *operation;
482         GtkPageSetup *page_setup;
483         gchar *uri;
484         const gchar *dir;
485         GError *error = NULL;
486         gchar *path;
487         GtkPrintSettings *settings;
488
489         pw = g_new0(PrintWindow, 1);
490
491         pw->source_selection = file_data_process_groups_in_selection(selection, FALSE, NULL);
492         pw->text_fields = options->printer.text_fields;
493
494         if (print_layout_page_count(pw) == 0)
495                 {
496                 return;
497                 }
498
499         vbox = gtk_vbox_new(FALSE, 0);
500         gtk_container_set_border_width(GTK_CONTAINER(vbox), PREF_PAD_BORDER);
501         gtk_widget_show(vbox);
502
503         print_text_menu(vbox, pw);
504         pw->vbox = vbox;
505
506         pw->print_pixbuf_queue = NULL;
507         pw->job_render_finished = FALSE;
508         pw->job_page = 0;
509
510         operation = gtk_print_operation_new();
511         settings = gtk_print_settings_new();
512
513         gtk_print_operation_set_custom_tab_label(operation, "Options");
514         gtk_print_operation_set_use_full_page(operation, TRUE);
515         gtk_print_operation_set_unit(operation, GTK_UNIT_POINTS);
516         gtk_print_operation_set_embed_page_setup(operation, TRUE);
517         gtk_print_operation_set_allow_async (operation, TRUE);
518         dir = g_get_user_special_dir(G_USER_DIRECTORY_DOCUMENTS);
519         if (dir == NULL)
520                 {
521                 dir = g_get_home_dir();
522                 }
523
524         uri = g_build_filename("file:/", dir, "geeqie-file.pdf", NULL);
525         gtk_print_settings_set(settings, GTK_PRINT_SETTINGS_OUTPUT_URI, uri);
526         g_free(uri);
527
528         path = g_build_filename(get_rc_dir(), PRINT_SETTINGS, NULL);
529         gtk_print_settings_load_file(settings, path, &error);
530         if (error)
531                 {
532                 log_printf("Error: Printer settings load failed:\n%s", error->message);
533                 g_error_free(error);
534                 error = NULL;
535                 }
536         gtk_print_operation_set_print_settings(operation, settings);
537         g_free(path);
538
539         page_setup = gtk_page_setup_new();
540         path = g_build_filename(get_rc_dir(), PAGE_SETUP, NULL);
541         gtk_page_setup_load_file(page_setup, path, &error);
542         if (error)
543                 {
544                 log_printf("Error: Print page setup load failed:\n%s", error->message);
545                 g_error_free(error);
546                 error = NULL;
547                 }
548         gtk_print_operation_set_default_page_setup(operation, page_setup);
549         g_free(path);
550
551         g_signal_connect (G_OBJECT (operation), "begin-print",
552                                         G_CALLBACK (begin_print), pw);
553         g_signal_connect (G_OBJECT (operation), "draw-page",
554                                         G_CALLBACK (draw_page), pw);
555         g_signal_connect (G_OBJECT (operation), "end-print",
556                                         G_CALLBACK (end_print_cb), pw);
557         g_signal_connect (G_OBJECT (operation), "create-custom-widget",
558                                         G_CALLBACK (option_tab_cb), pw);
559         g_signal_connect (G_OBJECT (operation), "paginate",
560                                         G_CALLBACK (paginate_cb), pw);
561
562         gtk_print_operation_set_n_pages(operation, print_layout_page_count(pw));
563
564         gtk_print_operation_run(operation, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
565                                                                                                 GTK_WINDOW (parent), &error);
566
567         if (error)
568                 {
569                 GtkWidget *dialog;
570
571                 dialog = gtk_message_dialog_new(GTK_WINDOW (parent),
572                                                                 GTK_DIALOG_DESTROY_WITH_PARENT,
573                                                                 GTK_MESSAGE_ERROR,
574                                                                 GTK_BUTTONS_CLOSE,
575                                                                 "%s", error->message);
576                 g_error_free (error);
577
578                 g_signal_connect(dialog, "response", G_CALLBACK(gtk_widget_destroy), NULL);
579
580                 gtk_widget_show (dialog);
581                 }
582
583         g_object_unref(page_setup);
584         g_object_unref(settings);
585 }
586 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */