summaryrefslogtreecommitdiff
path: root/src/Printing.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/Printing.vala')
-rw-r--r--src/Printing.vala1156
1 files changed, 1156 insertions, 0 deletions
diff --git a/src/Printing.vala b/src/Printing.vala
new file mode 100644
index 0000000..8e37997
--- /dev/null
+++ b/src/Printing.vala
@@ -0,0 +1,1156 @@
+/* Copyright 2010-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+public enum ContentLayout {
+ STANDARD_SIZE,
+ CUSTOM_SIZE,
+ IMAGE_PER_PAGE
+}
+
+public class PrintSettings {
+ public const int MIN_CONTENT_PPI = 72; /* 72 ppi is the pixel resolution of a 14" VGA
+ display -- it's standard for historical reasons */
+ public const int MAX_CONTENT_PPI = 1200; /* 1200 ppi is appropriate for a 3600 dpi imagesetter
+ used to produce photographic plates for commercial
+ printing -- it's the highest pixel resolution
+ commonly used */
+ private ContentLayout content_layout;
+ private Measurement content_width;
+ private Measurement content_height;
+ private int content_ppi;
+ private int image_per_page_selection;
+ private int size_selection;
+ private bool match_aspect_ratio;
+ private bool print_titles;
+ private string print_titles_font;
+
+ public PrintSettings() {
+ Config.Facade config = Config.Facade.get_instance();
+
+ MeasurementUnit units = (MeasurementUnit) config.get_printing_content_units();
+
+ content_width = Measurement(config.get_printing_content_width(), units);
+ content_height = Measurement(config.get_printing_content_height(), units);
+ size_selection = config.get_printing_size_selection();
+ content_layout = (ContentLayout) config.get_printing_content_layout();
+ match_aspect_ratio = config.get_printing_match_aspect_ratio();
+ print_titles = config.get_printing_print_titles();
+ print_titles_font = config.get_printing_titles_font();
+ image_per_page_selection = config.get_printing_images_per_page();
+ content_ppi = config.get_printing_content_ppi();
+ }
+
+ public void save() {
+ Config.Facade config = Config.Facade.get_instance();
+
+ config.set_printing_content_units(content_width.unit);
+ config.set_printing_content_width(content_width.value);
+ config.set_printing_content_height(content_height.value);
+ config.set_printing_size_selection(size_selection);
+ config.set_printing_content_layout(content_layout);
+ config.set_printing_match_aspect_ratio(match_aspect_ratio);
+ config.set_printing_print_titles(print_titles);
+ config.set_printing_titles_font(print_titles_font);
+ config.set_printing_images_per_page(image_per_page_selection);
+ config.set_printing_content_ppi(content_ppi);
+ }
+
+
+ public Measurement get_content_width() {
+ switch (get_content_layout()) {
+ case ContentLayout.STANDARD_SIZE:
+ case ContentLayout.IMAGE_PER_PAGE:
+ return (PrintManager.get_instance().get_standard_sizes()[
+ get_size_selection()]).width;
+
+ case ContentLayout.CUSTOM_SIZE:
+ return content_width;
+
+ default:
+ error("unknown ContentLayout enumeration value");
+ }
+ }
+
+ public Measurement get_content_height() {
+ switch (get_content_layout()) {
+ case ContentLayout.STANDARD_SIZE:
+ case ContentLayout.IMAGE_PER_PAGE:
+ return (PrintManager.get_instance().get_standard_sizes()[
+ get_size_selection()]).height;
+
+ case ContentLayout.CUSTOM_SIZE:
+ return content_height;
+
+ default:
+ error("unknown ContentLayout enumeration value");
+ }
+ }
+
+ public Measurement get_minimum_content_dimension() {
+ return Measurement(0.5, MeasurementUnit.INCHES);
+ }
+
+ public Measurement get_maximum_content_dimension() {
+ return Measurement(30, MeasurementUnit.INCHES);
+ }
+
+ public bool is_match_aspect_ratio_enabled() {
+ return match_aspect_ratio;
+ }
+
+ public bool is_print_titles_enabled() {
+ return print_titles;
+ }
+
+ public int get_content_ppi() {
+ return content_ppi;
+ }
+
+ public int get_image_per_page_selection() {
+ return image_per_page_selection;
+ }
+
+ public int get_size_selection() {
+ return size_selection;
+ }
+
+ public ContentLayout get_content_layout() {
+ return content_layout;
+ }
+
+ public void set_content_layout(ContentLayout content_layout) {
+ this.content_layout = content_layout;
+ }
+
+ public void set_content_width(Measurement content_width) {
+ this.content_width = content_width;
+ }
+
+ public void set_content_height(Measurement content_height) {
+ this.content_height = content_height;
+ }
+
+ public void set_content_ppi(int content_ppi) {
+ this.content_ppi = content_ppi;
+ }
+
+ public void set_image_per_page_selection(int image_per_page_selection) {
+ this.image_per_page_selection = image_per_page_selection;
+ }
+
+ public void set_size_selection(int size_selection) {
+ this.size_selection = size_selection;
+ }
+
+ public void set_match_aspect_ratio_enabled(bool enable_state) {
+ this.match_aspect_ratio = enable_state;
+ }
+
+ public void set_print_titles_enabled(bool print_titles) {
+ this.print_titles = print_titles;
+ }
+
+ public void set_print_titles_font(string fontname) {
+ this.print_titles_font = fontname;
+ }
+
+ public string get_print_titles_font() {
+ return this.print_titles_font;
+ }
+}
+
+/* we define our own measurement enum instead of using the Gtk.Unit enum
+ provided by Gtk+ 2.0 because Gtk.Unit doesn't define a CENTIMETERS
+ constant (thout it does define an MM for millimeters). This is
+ unfortunate, because in metric countries people like to think about
+ paper sizes for printing in CM not MM. so, to avoid having to
+ multiply and divide everything by 10 (which is error prone) to convert
+ from CM to MM and vice-versa whenever we read or write measurements, we
+ eschew Gtk.Unit and substitute our own */
+public enum MeasurementUnit {
+ INCHES,
+ CENTIMETERS
+}
+
+public struct Measurement {
+ private const double CENTIMETERS_PER_INCH = 2.54;
+ private const double INCHES_PER_CENTIMETER = (1.0 / 2.54);
+
+ public double value;
+ public MeasurementUnit unit;
+
+ public Measurement(double value, MeasurementUnit unit) {
+ this.value = value;
+ this.unit = unit;
+ }
+
+ public Measurement convert_to(MeasurementUnit to_unit) {
+ if (unit == to_unit)
+ return this;
+
+ if (to_unit == MeasurementUnit.INCHES) {
+ return Measurement(value * INCHES_PER_CENTIMETER, MeasurementUnit.INCHES);
+ } else if (to_unit == MeasurementUnit.CENTIMETERS) {
+ return Measurement(value * CENTIMETERS_PER_INCH, MeasurementUnit.CENTIMETERS);
+ } else {
+ error("unrecognized unit");
+ }
+ }
+
+ public bool is_less_than(Measurement rhs) {
+ Measurement converted_rhs = (unit == rhs.unit) ? rhs : rhs.convert_to(unit);
+ return (value < converted_rhs.value);
+ }
+
+ public bool is_greater_than(Measurement rhs) {
+ Measurement converted_rhs = (unit == rhs.unit) ? rhs : rhs.convert_to(unit);
+ return (value > converted_rhs.value);
+ }
+}
+
+private enum PrintLayout {
+ ENTIRE_PAGE,
+ TWO_PER_PAGE,
+ FOUR_PER_PAGE,
+ SIX_PER_PAGE,
+ EIGHT_PER_PAGE,
+ SIXTEEN_PER_PAGE,
+ THIRTY_TWO_PER_PAGE;
+
+ public static PrintLayout[] get_all() {
+ return {
+ ENTIRE_PAGE,
+ TWO_PER_PAGE,
+ FOUR_PER_PAGE,
+ SIX_PER_PAGE,
+ EIGHT_PER_PAGE,
+ SIXTEEN_PER_PAGE,
+ THIRTY_TWO_PER_PAGE
+ };
+ }
+
+ public int get_per_page() {
+ int[] per_page = { 1, 2, 4, 6, 8, 16, 32 };
+
+ return per_page[this];
+ }
+
+ public int get_x() {
+ int[] x = { 1, 1, 2, 2, 2, 4, 4 };
+
+ return x[this];
+ }
+
+ public int get_y() {
+ int[] y = { 1, 2, 2, 3, 4, 4, 8 };
+
+ return y[this];
+ }
+
+ public string to_string() {
+ string[] labels = {
+ _("Fill the entire page"),
+ _("2 images per page"),
+ _("4 images per page"),
+ _("6 images per page"),
+ _("8 images per page"),
+ _("16 images per page"),
+ _("32 images per page")
+ };
+
+ return labels[this];
+ }
+}
+
+public class CustomPrintTab : Gtk.Fixed {
+ private const int INCHES_COMBO_CHOICE = 0;
+ private const int CENTIMETERS_COMBO_CHOICE = 1;
+
+ private Gtk.Box custom_image_settings_pane = null;
+ private Gtk.RadioButton standard_size_radio = null;
+ private Gtk.RadioButton custom_size_radio = null;
+ private Gtk.RadioButton image_per_page_radio = null;
+ private Gtk.ComboBox image_per_page_combo = null;
+ private Gtk.ComboBox standard_sizes_combo = null;
+ private Gtk.ComboBoxText units_combo = null;
+ private Gtk.Entry custom_width_entry = null;
+ private Gtk.Entry custom_height_entry = null;
+ private Gtk.Entry ppi_entry;
+ private Gtk.CheckButton aspect_ratio_check = null;
+ private Gtk.CheckButton title_print_check = null;
+ private Gtk.FontButton title_print_font = null;
+ private Measurement local_content_width = Measurement(5.0, MeasurementUnit.INCHES);
+ private Measurement local_content_height = Measurement(5.0, MeasurementUnit.INCHES);
+ private int local_content_ppi;
+ private bool is_text_insertion_in_progress = false;
+ private PrintJob source_job;
+
+ public CustomPrintTab(PrintJob source_job) {
+ this.source_job = source_job;
+ Gtk.Builder builder = AppWindow.create_builder();
+
+ // an enclosing box for every widget on this tab...
+ custom_image_settings_pane = builder.get_object("box_ImgSettingsPane") as Gtk.Box;
+
+ standard_size_radio = builder.get_object("radio_UseStandardSize") as Gtk.RadioButton;
+ standard_size_radio.clicked.connect(on_radio_group_click);
+
+ custom_size_radio = builder.get_object("radio_UseCustomSize") as Gtk.RadioButton;
+ custom_size_radio.clicked.connect(on_radio_group_click);
+
+ image_per_page_radio = builder.get_object("radio_Autosize") as Gtk.RadioButton;
+ image_per_page_radio.clicked.connect(on_radio_group_click);
+
+ image_per_page_combo = builder.get_object("combo_Autosize") as Gtk.ComboBox;
+ Gtk.CellRendererText image_per_page_combo_text_renderer =
+ new Gtk.CellRendererText();
+ image_per_page_combo.pack_start(image_per_page_combo_text_renderer, true);
+ image_per_page_combo.add_attribute(image_per_page_combo_text_renderer,
+ "text", 0);
+ Gtk.ListStore image_per_page_combo_store = new Gtk.ListStore(2, typeof(string),
+ typeof(string));
+ foreach (PrintLayout layout in PrintLayout.get_all()) {
+ Gtk.TreeIter iter;
+ image_per_page_combo_store.append(out iter);
+ image_per_page_combo_store.set_value(iter, 0, layout.to_string());
+ }
+ image_per_page_combo.set_model(image_per_page_combo_store);
+
+ StandardPrintSize[] standard_sizes = PrintManager.get_instance().get_standard_sizes();
+ standard_sizes_combo = builder.get_object("combo_StdSizes") as Gtk.ComboBox;
+ Gtk.CellRendererText standard_sizes_combo_text_renderer =
+ new Gtk.CellRendererText();
+ standard_sizes_combo.pack_start(standard_sizes_combo_text_renderer, true);
+ standard_sizes_combo.add_attribute(standard_sizes_combo_text_renderer,
+ "text", 0);
+ standard_sizes_combo.set_row_separator_func(standard_sizes_combo_separator_func);
+ Gtk.ListStore standard_sizes_combo_store = new Gtk.ListStore(1, typeof(string),
+ typeof(string));
+ foreach (StandardPrintSize size in standard_sizes) {
+ Gtk.TreeIter iter;
+ standard_sizes_combo_store.append(out iter);
+ standard_sizes_combo_store.set_value(iter, 0, size.name);
+ }
+ standard_sizes_combo.set_model(standard_sizes_combo_store);
+
+ custom_width_entry = builder.get_object("entry_CustomWidth") as Gtk.Entry;
+ custom_width_entry.insert_text.connect(on_entry_insert_text);
+ custom_width_entry.focus_out_event.connect(on_width_entry_focus_out);
+
+ custom_height_entry = builder.get_object("entry_CustomHeight") as Gtk.Entry;
+ custom_height_entry.insert_text.connect(on_entry_insert_text);
+ custom_height_entry.focus_out_event.connect(on_height_entry_focus_out);
+
+ units_combo = builder.get_object("combo_Units") as Gtk.ComboBoxText;
+ units_combo.append_text(_("in."));
+ units_combo.append_text(_("cm"));
+ units_combo.set_active(0);
+ units_combo.changed.connect(on_units_combo_changed);
+
+ aspect_ratio_check = builder.get_object("check_MatchAspectRatio") as Gtk.CheckButton;
+ title_print_check = builder.get_object("check_PrintImageTitle") as Gtk.CheckButton;
+ title_print_font = builder.get_object("fntbn_TitleFont") as Gtk.FontButton;
+
+ ppi_entry = builder.get_object("entry_PixelsPerInch") as Gtk.Entry;
+ ppi_entry.insert_text.connect(on_ppi_entry_insert_text);
+ ppi_entry.focus_out_event.connect(on_ppi_entry_focus_out);
+
+ this.add(custom_image_settings_pane);
+
+ sync_state_from_job(source_job);
+
+ show_all();
+
+ /* connect this signal after state is sync'd */
+ aspect_ratio_check.clicked.connect(on_aspect_ratio_check_clicked);
+ }
+
+ private void on_aspect_ratio_check_clicked() {
+ if (aspect_ratio_check.get_active()) {
+ local_content_width =
+ Measurement(local_content_height.value * source_job.get_source_aspect_ratio(),
+ local_content_height.unit);
+ custom_width_entry.set_text(format_measurement(local_content_width));
+ }
+ }
+
+ private bool on_width_entry_focus_out(Gdk.EventFocus event) {
+ if (custom_width_entry.get_text() == (format_measurement_as(local_content_width,
+ get_user_unit_choice())))
+ return false;
+
+ Measurement new_width = get_width_entry_value();
+ Measurement min_width = source_job.get_local_settings().get_minimum_content_dimension();
+ Measurement max_width = source_job.get_local_settings().get_maximum_content_dimension();
+
+ if (new_width.is_less_than(min_width) || new_width.is_greater_than(max_width)) {
+ custom_width_entry.set_text(format_measurement(local_content_width));
+ return false;
+ }
+
+ if (is_match_aspect_ratio_enabled()) {
+ Measurement new_height =
+ Measurement(new_width.value / source_job.get_source_aspect_ratio(),
+ new_width.unit);
+ local_content_height = new_height;
+ custom_height_entry.set_text(format_measurement(new_height));
+ }
+
+ local_content_width = new_width;
+ custom_width_entry.set_text(format_measurement(new_width));
+ return false;
+ }
+
+ private string format_measurement(Measurement measurement) {
+ return "%.2f".printf(measurement.value);
+ }
+
+ private string format_measurement_as(Measurement measurement, MeasurementUnit to_unit) {
+ Measurement converted_measurement = (measurement.unit == to_unit) ? measurement :
+ measurement.convert_to(to_unit);
+ return format_measurement(converted_measurement);
+ }
+
+ private bool on_ppi_entry_focus_out(Gdk.EventFocus event) {
+ set_content_ppi(int.parse(ppi_entry.get_text()));
+ return false;
+ }
+
+ private void on_ppi_entry_insert_text(Gtk.Editable editable, string text, int length,
+ ref int position) {
+ Gtk.Entry sender = (Gtk.Entry) editable;
+
+ if (is_text_insertion_in_progress)
+ return;
+
+ is_text_insertion_in_progress = true;
+
+ if (length == -1)
+ length = (int) text.length;
+
+ string new_text = "";
+ for (int ctr = 0; ctr < length; ctr++) {
+ if (text[ctr].isdigit())
+ new_text += ((char) text[ctr]).to_string();
+ }
+
+ if (new_text.length > 0)
+ sender.insert_text(new_text, (int) new_text.length, ref position);
+
+ Signal.stop_emission_by_name(sender, "insert-text");
+
+ is_text_insertion_in_progress = false;
+ }
+
+ private bool on_height_entry_focus_out(Gdk.EventFocus event) {
+ if (custom_height_entry.get_text() == (format_measurement_as(local_content_height,
+ get_user_unit_choice())))
+ return false;
+
+ Measurement new_height = get_height_entry_value();
+ Measurement min_height = source_job.get_local_settings().get_minimum_content_dimension();
+ Measurement max_height = source_job.get_local_settings().get_maximum_content_dimension();
+
+ if (new_height.is_less_than(min_height) || new_height.is_greater_than(max_height)) {
+ custom_height_entry.set_text(format_measurement(local_content_height));
+ return false;
+ }
+
+ if (is_match_aspect_ratio_enabled()) {
+ Measurement new_width =
+ Measurement(new_height.value * source_job.get_source_aspect_ratio(),
+ new_height.unit);
+ local_content_width = new_width;
+ custom_width_entry.set_text(format_measurement(new_width));
+ }
+
+ local_content_height = new_height;
+ custom_height_entry.set_text(format_measurement(new_height));
+ return false;
+ }
+
+ private MeasurementUnit get_user_unit_choice() {
+ if (units_combo.get_active() == INCHES_COMBO_CHOICE) {
+ return MeasurementUnit.INCHES;
+ } else if (units_combo.get_active() == CENTIMETERS_COMBO_CHOICE) {
+ return MeasurementUnit.CENTIMETERS;
+ } else {
+ error("unknown unit combo box choice");
+ }
+ }
+
+ private void set_user_unit_choice(MeasurementUnit unit) {
+ if (unit == MeasurementUnit.INCHES) {
+ units_combo.set_active(INCHES_COMBO_CHOICE);
+ } else if (unit == MeasurementUnit.CENTIMETERS) {
+ units_combo.set_active(CENTIMETERS_COMBO_CHOICE);
+ } else {
+ error("unknown MeasurementUnit enumeration");
+ }
+ }
+
+ private Measurement get_width_entry_value() {
+ return Measurement(double.parse(custom_width_entry.get_text()), get_user_unit_choice());
+ }
+
+ private Measurement get_height_entry_value() {
+ return Measurement(double.parse(custom_height_entry.get_text()), get_user_unit_choice());
+ }
+
+ private void on_entry_insert_text(Gtk.Editable editable, string text, int length,
+ ref int position) {
+
+ Gtk.Entry sender = (Gtk.Entry) editable;
+
+ if (is_text_insertion_in_progress)
+ return;
+
+ is_text_insertion_in_progress = true;
+
+ if (length == -1)
+ length = (int) text.length;
+
+ string decimal_point = Intl.localeconv().decimal_point;
+ bool contains_decimal_point = sender.get_text().contains(decimal_point);
+
+ string new_text = "";
+ for (int ctr = 0; ctr < length; ctr++) {
+ if (text[ctr].isdigit()) {
+ new_text += ((char) text[ctr]).to_string();
+ } else if ((!contains_decimal_point) && (text[ctr] == decimal_point[0])) {
+ new_text += ((char) text[ctr]).to_string();
+ }
+ }
+
+ if (new_text.length > 0)
+ sender.insert_text(new_text, (int) new_text.length, ref position);
+
+ Signal.stop_emission_by_name(sender, "insert-text");
+
+ is_text_insertion_in_progress = false;
+ }
+
+ private void sync_state_from_job(PrintJob job) {
+ assert(job.get_local_settings().get_content_width().unit ==
+ job.get_local_settings().get_content_height().unit);
+
+ Measurement constrained_width = job.get_local_settings().get_content_width();
+ if (job.get_local_settings().is_match_aspect_ratio_enabled())
+ constrained_width = Measurement(job.get_local_settings().get_content_height().value *
+ job.get_source_aspect_ratio(), job.get_local_settings().get_content_height().unit);
+ set_content_width(constrained_width);
+ set_content_height(job.get_local_settings().get_content_height());
+ set_content_layout(job.get_local_settings().get_content_layout());
+ set_content_ppi(job.get_local_settings().get_content_ppi());
+ set_image_per_page_selection(job.get_local_settings().get_image_per_page_selection());
+ set_size_selection(job.get_local_settings().get_size_selection());
+ set_match_aspect_ratio_enabled(job.get_local_settings().is_match_aspect_ratio_enabled());
+ set_print_titles_enabled(job.get_local_settings().is_print_titles_enabled());
+ set_print_titles_font(job.get_local_settings().get_print_titles_font());
+ }
+
+ private void on_radio_group_click(Gtk.Button b) {
+ Gtk.RadioButton sender = (Gtk.RadioButton) b;
+
+ if (sender == standard_size_radio) {
+ set_content_layout_control_state(ContentLayout.STANDARD_SIZE);
+ standard_sizes_combo.grab_focus();
+ } else if (sender == custom_size_radio) {
+ set_content_layout_control_state(ContentLayout.CUSTOM_SIZE);
+ custom_height_entry.grab_focus();
+ } else if (sender == image_per_page_radio) {
+ set_content_layout_control_state(ContentLayout.IMAGE_PER_PAGE);
+ }
+ }
+
+ private void on_units_combo_changed() {
+ custom_height_entry.set_text(format_measurement_as(local_content_height,
+ get_user_unit_choice()));
+ custom_width_entry.set_text(format_measurement_as(local_content_width,
+ get_user_unit_choice()));
+ }
+
+ private void set_content_layout_control_state(ContentLayout layout) {
+ switch (layout) {
+ case ContentLayout.STANDARD_SIZE:
+ standard_sizes_combo.set_sensitive(true);
+ units_combo.set_sensitive(false);
+ custom_width_entry.set_sensitive(false);
+ custom_height_entry.set_sensitive(false);
+ aspect_ratio_check.set_sensitive(false);
+ image_per_page_combo.set_sensitive(false);
+ break;
+
+ case ContentLayout.CUSTOM_SIZE:
+ standard_sizes_combo.set_sensitive(false);
+ units_combo.set_sensitive(true);
+ custom_width_entry.set_sensitive(true);
+ custom_height_entry.set_sensitive(true);
+ aspect_ratio_check.set_sensitive(true);
+ image_per_page_combo.set_sensitive(false);
+ break;
+
+ case ContentLayout.IMAGE_PER_PAGE:
+ standard_sizes_combo.set_sensitive(false);
+ units_combo.set_sensitive(false);
+ custom_width_entry.set_sensitive(false);
+ custom_height_entry.set_sensitive(false);
+ aspect_ratio_check.set_sensitive(false);
+ image_per_page_combo.set_sensitive(true);
+ break;
+
+ default:
+ error("unknown ContentLayout enumeration value");
+ }
+ }
+
+ private static bool standard_sizes_combo_separator_func(Gtk.TreeModel model,
+ Gtk.TreeIter iter) {
+ Value val;
+ model.get_value(iter, 0, out val);
+
+ return (val.dup_string() == "-");
+ }
+
+ private void set_content_layout(ContentLayout content_layout) {
+ set_content_layout_control_state(content_layout);
+ switch (content_layout) {
+ case ContentLayout.STANDARD_SIZE:
+ standard_size_radio.set_active(true);
+ break;
+
+ case ContentLayout.CUSTOM_SIZE:
+ custom_size_radio.set_active(true);
+ break;
+
+ case ContentLayout.IMAGE_PER_PAGE:
+ image_per_page_radio.set_active(true);
+ break;
+
+ default:
+ error("unknown ContentLayout enumeration value");
+ }
+ }
+
+ private ContentLayout get_content_layout() {
+ if (standard_size_radio.get_active())
+ return ContentLayout.STANDARD_SIZE;
+ if (custom_size_radio.get_active())
+ return ContentLayout.CUSTOM_SIZE;
+ if (image_per_page_radio.get_active())
+ return ContentLayout.IMAGE_PER_PAGE;
+
+ error("inconsistent content layout radio button group state");
+ }
+
+ private void set_content_width(Measurement content_width) {
+ if (content_width.unit != local_content_height.unit) {
+ set_user_unit_choice(content_width.unit);
+ local_content_height = local_content_height.convert_to(content_width.unit);
+ custom_height_entry.set_text(format_measurement(local_content_height));
+ }
+ local_content_width = content_width;
+ custom_width_entry.set_text(format_measurement(content_width));
+ }
+
+ private Measurement get_content_width() {
+ return local_content_width;
+ }
+
+ private void set_content_height(Measurement content_height) {
+ if (content_height.unit != local_content_width.unit) {
+ set_user_unit_choice(content_height.unit);
+ local_content_width = local_content_width.convert_to(content_height.unit);
+ custom_width_entry.set_text(format_measurement(local_content_width));
+ }
+ local_content_height = content_height;
+ custom_height_entry.set_text(format_measurement(content_height));
+ }
+
+ private Measurement get_content_height() {
+ return local_content_height;
+ }
+
+ private void set_content_ppi(int content_ppi) {
+ local_content_ppi = content_ppi.clamp(PrintSettings.MIN_CONTENT_PPI,
+ PrintSettings.MAX_CONTENT_PPI);
+
+ ppi_entry.set_text("%d".printf(local_content_ppi));
+ }
+
+ private int get_content_ppi() {
+ return local_content_ppi;
+ }
+
+ private void set_image_per_page_selection(int image_per_page) {
+ image_per_page_combo.set_active(image_per_page);
+ }
+
+ private int get_image_per_page_selection() {
+ return image_per_page_combo.get_active();
+ }
+
+ private void set_size_selection(int size_selection) {
+ standard_sizes_combo.set_active(size_selection);
+ }
+
+ private int get_size_selection() {
+ return standard_sizes_combo.get_active();
+ }
+
+ private void set_match_aspect_ratio_enabled(bool enable_state) {
+ aspect_ratio_check.set_active(enable_state);
+ }
+
+ private void set_print_titles_enabled(bool print_titles) {
+ title_print_check.set_active(print_titles);
+ }
+
+ private void set_print_titles_font(string fontname) {
+ title_print_font.set_font_name(fontname);
+ }
+
+
+ private bool is_match_aspect_ratio_enabled() {
+ return aspect_ratio_check.get_active();
+ }
+
+ private bool is_print_titles_enabled() {
+ return title_print_check.get_active();
+ }
+
+ private string get_print_titles_font() {
+ return title_print_font.get_font_name();
+ }
+
+ public PrintJob get_source_job() {
+ return source_job;
+ }
+
+ public PrintSettings get_local_settings() {
+ PrintSettings result = new PrintSettings();
+
+ result.set_content_width(get_content_width());
+ result.set_content_height(get_content_height());
+ result.set_content_layout(get_content_layout());
+ result.set_content_ppi(get_content_ppi());
+ result.set_image_per_page_selection(get_image_per_page_selection());
+ result.set_size_selection(get_size_selection());
+ result.set_match_aspect_ratio_enabled(is_match_aspect_ratio_enabled());
+ result.set_print_titles_enabled(is_print_titles_enabled());
+ result.set_print_titles_font(get_print_titles_font());
+
+ return result;
+ }
+}
+
+public class PrintJob : Gtk.PrintOperation {
+ private PrintSettings settings;
+ private Gee.ArrayList<Photo> photos = new Gee.ArrayList<Photo>();
+
+ public PrintJob(Gee.Collection<Photo> to_print) {
+ this.settings = PrintManager.get_instance().get_global_settings();
+ photos.add_all(to_print);
+
+ set_embed_page_setup (true);
+ double photo_aspect_ratio = photos[0].get_dimensions().get_aspect_ratio();
+ if (photo_aspect_ratio < 1.0)
+ photo_aspect_ratio = 1.0 / photo_aspect_ratio;
+ }
+
+ public Gee.List<Photo> get_photos() {
+ return photos;
+ }
+
+ public Photo get_source_photo() {
+ return photos[0];
+ }
+
+ public double get_source_aspect_ratio() {
+ double aspect_ratio = photos[0].get_dimensions().get_aspect_ratio();
+ return (aspect_ratio < 1.0) ? (1.0 / aspect_ratio) : aspect_ratio;
+ }
+
+ public PrintSettings get_local_settings() {
+ return settings;
+ }
+
+ public void set_local_settings(PrintSettings settings) {
+ this.settings = settings;
+ }
+}
+
+public class StandardPrintSize {
+ public StandardPrintSize(string name, Measurement width, Measurement height) {
+ this.name = name;
+ this.width = width;
+ this.height = height;
+ }
+
+ public string name;
+ public Measurement width;
+ public Measurement height;
+}
+
+public class PrintManager {
+ private const double IMAGE_DISTANCE = 0.24;
+
+ private static PrintManager instance = null;
+
+ private PrintSettings settings;
+ private Gtk.PageSetup user_page_setup;
+ private CustomPrintTab custom_tab;
+ private ProgressDialog? progress_dialog = null;
+ private Cancellable? cancellable = null;
+
+ private PrintManager() {
+ user_page_setup = new Gtk.PageSetup();
+ settings = new PrintSettings();
+ }
+
+ public StandardPrintSize[] get_standard_sizes() {
+ StandardPrintSize[] result = new StandardPrintSize[0];
+
+ result += new StandardPrintSize(_("Wallet (2 x 3 in.)"),
+ Measurement(3, MeasurementUnit.INCHES),
+ Measurement(2, MeasurementUnit.INCHES));
+ result += new StandardPrintSize(_("Notecard (3 x 5 in.)"),
+ Measurement(5, MeasurementUnit.INCHES),
+ Measurement(3, MeasurementUnit.INCHES));
+ result += new StandardPrintSize(_("4 x 6 in."),
+ Measurement(6, MeasurementUnit.INCHES),
+ Measurement(4, MeasurementUnit.INCHES));
+ result += new StandardPrintSize(_("5 x 7 in."),
+ Measurement(7, MeasurementUnit.INCHES),
+ Measurement(5, MeasurementUnit.INCHES));
+ result += new StandardPrintSize(_("8 x 10 in."),
+ Measurement(10, MeasurementUnit.INCHES),
+ Measurement(8, MeasurementUnit.INCHES));
+ result += new StandardPrintSize(_("11 x 14 in."),
+ Measurement(14, MeasurementUnit.INCHES),
+ Measurement(11, MeasurementUnit.INCHES));
+ result += new StandardPrintSize(_("16 x 20 in."),
+ Measurement(20, MeasurementUnit.INCHES),
+ Measurement(16, MeasurementUnit.INCHES));
+ result += new StandardPrintSize(("-"),
+ Measurement(0, MeasurementUnit.INCHES),
+ Measurement(0, MeasurementUnit.INCHES));
+ result += new StandardPrintSize(_("Metric Wallet (9 x 13 cm)"),
+ Measurement(13, MeasurementUnit.CENTIMETERS),
+ Measurement(9, MeasurementUnit.CENTIMETERS));
+ result += new StandardPrintSize(_("Postcard (10 x 15 cm)"),
+ Measurement(15, MeasurementUnit.CENTIMETERS),
+ Measurement(10, MeasurementUnit.CENTIMETERS));
+ result += new StandardPrintSize(_("13 x 18 cm"),
+ Measurement(18, MeasurementUnit.CENTIMETERS),
+ Measurement(13, MeasurementUnit.CENTIMETERS));
+ result += new StandardPrintSize(_("18 x 24 cm"),
+ Measurement(24, MeasurementUnit.CENTIMETERS),
+ Measurement(18, MeasurementUnit.CENTIMETERS));
+ result += new StandardPrintSize(_("20 x 30 cm"),
+ Measurement(30, MeasurementUnit.CENTIMETERS),
+ Measurement(20, MeasurementUnit.CENTIMETERS));
+ result += new StandardPrintSize(_("24 x 40 cm"),
+ Measurement(40, MeasurementUnit.CENTIMETERS),
+ Measurement(24, MeasurementUnit.CENTIMETERS));
+ result += new StandardPrintSize(_("30 x 40 cm"),
+ Measurement(40, MeasurementUnit.CENTIMETERS),
+ Measurement(30, MeasurementUnit.CENTIMETERS));
+
+ return result;
+ }
+
+ public static PrintManager get_instance() {
+ if (instance == null)
+ instance = new PrintManager();
+
+ return instance;
+ }
+
+ public void spool_photo(Gee.Collection<Photo> to_print) {
+ PrintJob job = new PrintJob(to_print);
+ job.set_custom_tab_label(_("Image Settings"));
+ job.set_unit(Gtk.Unit.INCH);
+ job.set_n_pages(1);
+ job.set_job_name(job.get_source_photo().get_name());
+ job.set_default_page_setup(user_page_setup);
+ job.begin_print.connect(on_begin_print);
+ job.draw_page.connect(on_draw_page);
+ job.create_custom_widget.connect(on_create_custom_widget);
+ job.status_changed.connect(on_status_changed);
+
+ AppWindow.get_instance().set_busy_cursor();
+
+ cancellable = new Cancellable();
+ progress_dialog = new ProgressDialog(AppWindow.get_instance(), _("Printing..."), cancellable);
+
+ string? err_msg = null;
+ try {
+ Gtk.PrintOperationResult result = job.run(Gtk.PrintOperationAction.PRINT_DIALOG,
+ AppWindow.get_instance());
+ if (result == Gtk.PrintOperationResult.APPLY)
+ user_page_setup = job.get_default_page_setup();
+ } catch (Error e) {
+ job.cancel();
+ err_msg = e.message;
+ }
+
+ progress_dialog.close();
+ progress_dialog = null;
+ cancellable = null;
+
+ AppWindow.get_instance().set_normal_cursor();
+
+ if (err_msg != null)
+ AppWindow.error_message(_("Unable to print photo:\n\n%s").printf(err_msg));
+ }
+
+ private void on_begin_print(Gtk.PrintOperation emitting_object, Gtk.PrintContext job_context) {
+ debug("on_begin_print");
+
+ PrintJob job = (PrintJob) emitting_object;
+
+ // cancel() can only be called from "begin-print", "paginate", or "draw-page"
+ if (cancellable != null && cancellable.is_cancelled()) {
+ job.cancel();
+
+ return;
+ }
+
+ Gee.List<Photo> photos = job.get_photos();
+ if (job.get_local_settings().get_content_layout() == ContentLayout.IMAGE_PER_PAGE){
+ PrintLayout layout = (PrintLayout) job.get_local_settings().get_image_per_page_selection();
+ job.set_n_pages((int) Math.ceil((double) photos.size / (double) layout.get_per_page()));
+ } else {
+ job.set_n_pages(photos.size);
+ }
+
+ spin_event_loop();
+ }
+
+ private void on_status_changed(Gtk.PrintOperation job) {
+ debug("on_status_changed: %s", job.get_status_string());
+
+ if (progress_dialog != null) {
+ progress_dialog.set_status(job.get_status_string());
+ spin_event_loop();
+ }
+ }
+
+ private void on_draw_page(Gtk.PrintOperation emitting_object, Gtk.PrintContext job_context,
+ int page_num) {
+ debug("on_draw_page");
+
+ PrintJob job = (PrintJob) emitting_object;
+
+ // cancel() can only be called from "begin-print", "paginate", or "draw-page"
+ if (cancellable != null && cancellable.is_cancelled()) {
+ job.cancel();
+
+ return;
+ }
+
+ spin_event_loop();
+
+ Gtk.PageSetup page_setup = job_context.get_page_setup();
+ double page_width = page_setup.get_page_width(Gtk.Unit.INCH);
+ double page_height = page_setup.get_page_height(Gtk.Unit.INCH);
+
+ double dpi = job.get_local_settings().get_content_ppi();
+ double inv_dpi = 1.0 / dpi;
+ Cairo.Context dc = job_context.get_cairo_context();
+ dc.scale(inv_dpi, inv_dpi);
+ Gee.List<Photo> photos = job.get_photos();
+
+ ContentLayout content_layout = job.get_local_settings().get_content_layout();
+ switch (content_layout) {
+ case ContentLayout.STANDARD_SIZE:
+ case ContentLayout.CUSTOM_SIZE:
+ double canvas_width, canvas_height;
+ if (content_layout == ContentLayout.STANDARD_SIZE) {
+ canvas_width = get_standard_sizes()[job.get_local_settings().get_size_selection()].width.convert_to(
+ MeasurementUnit.INCHES).value;
+ canvas_height = get_standard_sizes()[job.get_local_settings().get_size_selection()].height.convert_to(
+ MeasurementUnit.INCHES).value;
+ } else {
+ assert(content_layout == ContentLayout.CUSTOM_SIZE);
+ canvas_width = job.get_local_settings().get_content_width().convert_to(
+ MeasurementUnit.INCHES).value;
+ canvas_height = job.get_local_settings().get_content_height().convert_to(
+ MeasurementUnit.INCHES).value;
+ }
+
+ if (page_num < photos.size) {
+ Dimensions photo_dimensions = photos[page_num].get_dimensions();
+ double photo_aspect_ratio = photo_dimensions.get_aspect_ratio();
+ double canvas_aspect_ratio = ((double) canvas_width) / canvas_height;
+ if (Math.floor(canvas_aspect_ratio) != Math.floor(photo_aspect_ratio)) {
+ double canvas_tmp = canvas_width;
+ canvas_width = canvas_height;
+ canvas_height = canvas_tmp;
+ }
+
+ double dx = (page_width - canvas_width) / 2.0;
+ double dy = (page_height - canvas_height) / 2.0;
+ fit_image_to_canvas(photos[page_num], dx, dy, canvas_width, canvas_height, true,
+ job, job_context);
+ if (job.get_local_settings().is_print_titles_enabled()) {
+ add_title_to_canvas(page_width / 2, page_height, photos[page_num].get_name(),
+ job, job_context);
+ }
+ }
+
+ if (progress_dialog != null)
+ progress_dialog.monitor(page_num, photos.size);
+ break;
+
+ case ContentLayout.IMAGE_PER_PAGE:
+ PrintLayout layout = (PrintLayout) job.get_local_settings().get_image_per_page_selection();
+ int nx = layout.get_x();
+ int ny = layout.get_y();
+ int start = page_num * layout.get_per_page();
+ double canvas_width = (double) (page_width - IMAGE_DISTANCE * (nx - 1)) / nx;
+ double canvas_height = (double) (page_height - IMAGE_DISTANCE * (ny - 1)) / ny;
+ for (int y = 0; y < ny; y++){
+ for (int x = 0; x < nx; x++){
+ int i = start + y * nx + x;
+ if (i < photos.size) {
+ double dx = x * (canvas_width) + x * IMAGE_DISTANCE;
+ double dy = y * (canvas_height) + y * IMAGE_DISTANCE;
+ fit_image_to_canvas(photos[i], dx, dy, canvas_width, canvas_height, false,
+ job, job_context);
+ if (job.get_local_settings().is_print_titles_enabled()) {
+ add_title_to_canvas(dx + canvas_width / 2, dy + canvas_height,
+ photos[i].get_name(), job, job_context);
+ }
+ }
+
+ if (progress_dialog != null)
+ progress_dialog.monitor(i, photos.size);
+ }
+ }
+ break;
+
+ default:
+ error("unknown or unsupported layout mode");
+ }
+ }
+
+ private unowned Object on_create_custom_widget(Gtk.PrintOperation emitting_object) {
+ custom_tab = new CustomPrintTab((PrintJob) emitting_object);
+ ((PrintJob) emitting_object).custom_widget_apply.connect(on_custom_widget_apply);
+ return custom_tab;
+ }
+
+ private void on_custom_widget_apply(Gtk.Widget custom_widget) {
+ CustomPrintTab tab = (CustomPrintTab) custom_widget;
+ tab.get_source_job().set_local_settings(tab.get_local_settings());
+ set_global_settings(tab.get_local_settings());
+ }
+
+ private void fit_image_to_canvas(Photo photo, double x, double y, double canvas_width, double canvas_height, bool crop, PrintJob job, Gtk.PrintContext job_context) {
+ Cairo.Context dc = job_context.get_cairo_context();
+ Dimensions photo_dimensions = photo.get_dimensions();
+ double photo_aspect_ratio = photo_dimensions.get_aspect_ratio();
+ double canvas_aspect_ratio = ((double) canvas_width) / canvas_height;
+
+ double target_width = 0.0;
+ double target_height = 0.0;
+ double dpi = job.get_local_settings().get_content_ppi();
+
+ if (!crop) {
+ if (canvas_aspect_ratio < photo_aspect_ratio) {
+ target_width = canvas_width;
+ target_height = target_width * (1.0 / photo_aspect_ratio);
+ } else {
+ target_height = canvas_height;
+ target_width = target_height * photo_aspect_ratio;
+ }
+ x += (canvas_width - target_width) / 2.0;
+ y += (canvas_height - target_height) / 2.0;
+ }
+
+ double x_offset = dpi * x;
+ double y_offset = dpi * y;
+ dc.save();
+ dc.translate(x_offset, y_offset);
+
+ int w = (int) (dpi * canvas_width);
+ int h = (int) (dpi * canvas_height);
+ Dimensions viewport = Dimensions(w, h);
+
+ try {
+ if (crop && !are_approximately_equal(canvas_aspect_ratio, photo_aspect_ratio)) {
+ Scaling pixbuf_scaling = Scaling.to_fill_viewport(viewport);
+ Gdk.Pixbuf photo_pixbuf = photo.get_pixbuf(pixbuf_scaling);
+ Dimensions scaled_photo_dimensions = Dimensions.for_pixbuf(photo_pixbuf);
+ int shave_vertical = 0;
+ int shave_horizontal = 0;
+ if (canvas_aspect_ratio < photo_aspect_ratio) {
+ shave_vertical = (int) ((scaled_photo_dimensions.width - (scaled_photo_dimensions.height * canvas_aspect_ratio)) / 2.0);
+ } else {
+ shave_horizontal = (int) ((scaled_photo_dimensions.height - (scaled_photo_dimensions.width * (1.0 / canvas_aspect_ratio))) / 2.0);
+ }
+ Gdk.Pixbuf shaved_pixbuf = new Gdk.Pixbuf.subpixbuf(photo_pixbuf, shave_vertical,shave_horizontal, scaled_photo_dimensions.width - (2 * shave_vertical), scaled_photo_dimensions.height - (2 * shave_horizontal));
+
+ photo_pixbuf = pixbuf_scaling.perform_on_pixbuf(shaved_pixbuf, Gdk.InterpType.HYPER, true);
+ Gdk.cairo_set_source_pixbuf(dc, photo_pixbuf, 0.0, 0.0);
+ } else {
+ Scaling pixbuf_scaling = Scaling.for_viewport(viewport, true);
+ Gdk.Pixbuf photo_pixbuf = photo.get_pixbuf(pixbuf_scaling);
+ photo_pixbuf = pixbuf_scaling.perform_on_pixbuf(photo_pixbuf, Gdk.InterpType.HYPER, true);
+ Gdk.cairo_set_source_pixbuf(dc, photo_pixbuf, 0.0, 0.0);
+ }
+ dc.paint();
+
+ } catch (Error e) {
+ job.cancel();
+ AppWindow.error_message(_("Unable to print photo:\n\n%s").printf(e.message));
+ }
+ dc.restore();
+ }
+
+ private void add_title_to_canvas(double x, double y, string title, PrintJob job, Gtk.PrintContext job_context) {
+ Cairo.Context dc = job_context.get_cairo_context();
+ double dpi = job.get_local_settings().get_content_ppi();
+ var title_font_description = Pango.FontDescription.from_string(job.get_local_settings().get_print_titles_font());
+ var title_layout = Pango.cairo_create_layout(dc);
+ Pango.Context context = title_layout.get_context();
+ Pango.cairo_context_set_resolution (context, dpi);
+ title_layout.set_font_description(title_font_description);
+ title_layout.set_text(title, -1);
+ int title_width, title_height;
+ title_layout.get_pixel_size(out title_width, out title_height);
+ double tx = dpi * x - title_width / 2;
+ double ty = dpi * y - title_height;
+
+ // Transparent title text background
+ dc.rectangle(tx - 10, ty + 2, title_width + 20, title_height);
+ dc.set_source_rgba(1, 1, 1, 1);
+ dc.set_line_width(2);
+ dc.stroke_preserve();
+ dc.set_source_rgba(1, 1, 1, 0.5);
+ dc.fill();
+ dc.set_source_rgba(0, 0, 0, 1);
+
+ dc.move_to(tx, ty + 2);
+ Pango.cairo_show_layout(dc, title_layout);
+ }
+
+ private bool are_approximately_equal(double val1, double val2) {
+ double accept_err = 0.005;
+ return (Math.fabs(val1 - val2) <= accept_err);
+ }
+
+ public PrintSettings get_global_settings() {
+ return settings;
+ }
+
+ public void set_global_settings(PrintSettings settings) {
+ this.settings = settings;
+ settings.save();
+ }
+}