summaryrefslogtreecommitdiff
path: root/src/dialogs/ExportDialog.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/dialogs/ExportDialog.vala')
-rw-r--r--src/dialogs/ExportDialog.vala343
1 files changed, 343 insertions, 0 deletions
diff --git a/src/dialogs/ExportDialog.vala b/src/dialogs/ExportDialog.vala
new file mode 100644
index 0000000..5a61dc4
--- /dev/null
+++ b/src/dialogs/ExportDialog.vala
@@ -0,0 +1,343 @@
+/* Copyright 2016 Software Freedom Conservancy Inc.
+ * Copyright 2017 Jens Georg <mail@jensge.org>
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+public class ExportDialog : Gtk.Dialog {
+ public const int DEFAULT_SCALE = 1200;
+
+ // "Unmodified" and "Current," though they appear in the "Format:" popup menu, really
+ // aren't formats so much as they are operating modes that determine specific formats.
+ // Hereafter we'll refer to these as "special formats."
+ public const int NUM_SPECIAL_FORMATS = 2;
+ public const string UNMODIFIED_FORMAT_LABEL = _("Unmodified");
+ public const string CURRENT_FORMAT_LABEL = _("Current");
+
+ public const ScaleConstraint[] CONSTRAINT_ARRAY = { ScaleConstraint.ORIGINAL,
+ ScaleConstraint.DIMENSIONS, ScaleConstraint.WIDTH, ScaleConstraint.HEIGHT };
+
+ public const Jpeg.Quality[] QUALITY_ARRAY = { Jpeg.Quality.LOW, Jpeg.Quality.MEDIUM,
+ Jpeg.Quality.HIGH, Jpeg.Quality.MAXIMUM };
+
+ private static ScaleConstraint current_constraint = ScaleConstraint.ORIGINAL;
+ private static ExportFormatParameters current_parameters = ExportFormatParameters.current();
+ private static int current_scale = DEFAULT_SCALE;
+
+ private Gtk.Grid table = new Gtk.Grid();
+ private Gtk.ComboBoxText quality_combo;
+ private Gtk.ComboBoxText constraint_combo;
+ private Gtk.ComboBoxText format_combo;
+ private Gtk.Switch export_metadata;
+ private Gee.ArrayList<string> format_options = new Gee.ArrayList<string>();
+ private Gtk.Entry pixels_entry;
+ private Gtk.Widget ok_button;
+ private bool in_insert = false;
+
+ public ExportDialog(string title) {
+ Object (use_header_bar: Resources.use_header_bar());
+
+ this.title = title;
+ resizable = false;
+
+ //get information about the export settings out of our config backend
+ Config.Facade config = Config.Facade.get_instance();
+ current_parameters.mode = config.get_export_export_format_mode(); //ExportFormatMode
+ current_parameters.specified_format = config.get_export_photo_file_format(); //PhotoFileFormat
+ current_parameters.quality = config.get_export_quality(); //quality
+ current_parameters.export_metadata = config.get_export_export_metadata(); //export metadata
+ current_constraint = config.get_export_constraint(); //constraint
+ current_scale = config.get_export_scale(); //scale
+
+ quality_combo = new Gtk.ComboBoxText();
+ int ctr = 0;
+ foreach (Jpeg.Quality quality in QUALITY_ARRAY) {
+ quality_combo.append_text(quality.to_string());
+ if (quality == current_parameters.quality)
+ quality_combo.set_active(ctr);
+ ctr++;
+ }
+
+ constraint_combo = new Gtk.ComboBoxText();
+ ctr = 0;
+ foreach (ScaleConstraint constraint in CONSTRAINT_ARRAY) {
+ constraint_combo.append_text(constraint.to_string());
+ if (constraint == current_constraint)
+ constraint_combo.set_active(ctr);
+ ctr++;
+ }
+
+ format_combo = new Gtk.ComboBoxText();
+ format_add_option(UNMODIFIED_FORMAT_LABEL);
+ format_add_option(CURRENT_FORMAT_LABEL);
+ foreach (PhotoFileFormat format in PhotoFileFormat.get_writeable()) {
+ format_add_option(format.get_properties().get_user_visible_name());
+ }
+
+ pixels_entry = new Gtk.Entry();
+ pixels_entry.set_max_length(6);
+ pixels_entry.set_text("%d".printf(current_scale));
+
+ // register after preparation to avoid signals during init
+ constraint_combo.changed.connect(on_constraint_changed);
+ format_combo.changed.connect(on_format_changed);
+ pixels_entry.changed.connect(on_pixels_changed);
+ pixels_entry.insert_text.connect(on_pixels_insert_text);
+ pixels_entry.activate.connect(on_activate);
+
+ // layout controls
+ add_label(_("_Format:"), 0, 0, format_combo);
+ add_control(format_combo, 1, 0);
+
+ add_label(_("_Quality:"), 0, 1, quality_combo);
+ add_control(quality_combo, 1, 1);
+
+ add_label(_("_Scaling constraint:"), 0, 2, constraint_combo);
+ add_control(constraint_combo, 1, 2);
+
+ add_label(_("_Pixels:"), 0, 3, pixels_entry);
+ add_control(pixels_entry, 1, 3);
+
+ export_metadata = new Gtk.Switch ();
+ add_label(_("Export _metadata:"), 0, 4, export_metadata);
+ add_control(export_metadata, 1, 4);
+ export_metadata.active = true;
+ export_metadata.halign = Gtk.Align.START;
+
+ table.set_row_spacing(6);
+ table.set_column_spacing(12);
+ table.set_border_width(18);
+
+ ((Gtk.Box) get_content_area()).add(table);
+
+ // add buttons to action area
+ add_button(Resources.CANCEL_LABEL, Gtk.ResponseType.CANCEL);
+ ok_button = add_button(Resources.OK_LABEL, Gtk.ResponseType.OK);
+ set_default_response(Gtk.ResponseType.OK);
+
+ ok_button.set_can_default(true);
+ ok_button.has_default = true;
+ set_default(ok_button);
+
+ if (current_constraint == ScaleConstraint.ORIGINAL) {
+ pixels_entry.sensitive = false;
+ quality_combo.sensitive = false;
+ }
+
+ ok_button.grab_focus();
+ }
+
+ private void format_add_option(string format_name) {
+ format_options.add(format_name);
+ format_combo.append_text(format_name);
+ }
+
+ private void format_set_active_text(string text) {
+ int selection_ticker = 0;
+
+ foreach (string current_text in format_options) {
+ if (current_text == text) {
+ format_combo.set_active(selection_ticker);
+ return;
+ }
+ selection_ticker++;
+ }
+
+ error("format_set_active_text: text '%s' isn't in combo box", text);
+ }
+
+ private PhotoFileFormat get_specified_format() {
+ int index = format_combo.get_active();
+ if (index < NUM_SPECIAL_FORMATS)
+ index = NUM_SPECIAL_FORMATS;
+
+ index -= NUM_SPECIAL_FORMATS;
+ PhotoFileFormat[] writeable_formats = PhotoFileFormat.get_writeable();
+ return writeable_formats[index];
+ }
+
+ private string get_label_for_parameters(ExportFormatParameters params) {
+ switch(params.mode) {
+ case ExportFormatMode.UNMODIFIED:
+ return UNMODIFIED_FORMAT_LABEL;
+
+ case ExportFormatMode.CURRENT:
+ return CURRENT_FORMAT_LABEL;
+
+ case ExportFormatMode.SPECIFIED:
+ return params.specified_format.get_properties().get_user_visible_name();
+
+ default:
+ error("get_label_for_parameters: unrecognized export format mode");
+ }
+ }
+
+ // unlike other parameters, which should be persisted across dialog executions, the
+ // format parameters must be set each time the dialog is executed -- this is why
+ // it's passed qualified as ref and not as out
+ public bool execute(out int scale, out ScaleConstraint constraint,
+ ref ExportFormatParameters parameters) {
+ show_all();
+
+ // if the export format mode isn't set to last (i.e., don't use the persisted settings),
+ // reset the scale constraint to original size
+ if (parameters.mode != ExportFormatMode.LAST) {
+ current_constraint = constraint = ScaleConstraint.ORIGINAL;
+ constraint_combo.set_active(0);
+ }
+
+ if (parameters.mode == ExportFormatMode.LAST)
+ parameters = current_parameters;
+ else if (parameters.mode == ExportFormatMode.SPECIFIED && !parameters.specified_format.can_write())
+ parameters.specified_format = PhotoFileFormat.get_system_default_format();
+
+ format_set_active_text(get_label_for_parameters(parameters));
+ on_format_changed();
+
+ bool ok = (run() == Gtk.ResponseType.OK);
+ if (ok) {
+ int index = constraint_combo.get_active();
+ assert(index >= 0);
+ constraint = CONSTRAINT_ARRAY[index];
+ current_constraint = constraint;
+
+ scale = int.parse(pixels_entry.get_text());
+ if (constraint != ScaleConstraint.ORIGINAL)
+ assert(scale > 0);
+ current_scale = scale;
+
+ parameters.export_metadata = export_metadata.sensitive ? export_metadata.active : false;
+
+ if (format_combo.get_active_text() == UNMODIFIED_FORMAT_LABEL) {
+ parameters.mode = current_parameters.mode = ExportFormatMode.UNMODIFIED;
+ } else if (format_combo.get_active_text() == CURRENT_FORMAT_LABEL) {
+ parameters.mode = current_parameters.mode = ExportFormatMode.CURRENT;
+ } else {
+ parameters.mode = current_parameters.mode = ExportFormatMode.SPECIFIED;
+ parameters.specified_format = current_parameters.specified_format = get_specified_format();
+ if (current_parameters.specified_format == PhotoFileFormat.JFIF)
+ parameters.quality = current_parameters.quality = QUALITY_ARRAY[quality_combo.get_active()];
+ }
+
+ //save current settings in config backend for reusing later
+ Config.Facade config = Config.Facade.get_instance();
+ config.set_export_export_format_mode(current_parameters.mode); //ExportFormatMode
+ config.set_export_photo_file_format(current_parameters.specified_format); //PhotoFileFormat
+ config.set_export_quality(current_parameters.quality); //quality
+ config.set_export_export_metadata(current_parameters.export_metadata); //export metadata
+ config.set_export_constraint(current_constraint); //constraint
+ config.set_export_scale(current_scale); //scale
+ } else {
+ scale = 0;
+ constraint = ScaleConstraint.ORIGINAL;
+ }
+
+ destroy();
+
+ return ok;
+ }
+
+ private void add_label(string text, int x, int y, Gtk.Widget? widget = null) {
+ Gtk.Label new_label = new Gtk.Label.with_mnemonic(text);
+ new_label.halign = Gtk.Align.END;
+ new_label.valign = Gtk.Align.CENTER;
+ new_label.set_use_underline(true);
+
+ if (widget != null)
+ new_label.set_mnemonic_widget(widget);
+
+ table.attach(new_label, x, y, 1, 1);
+ }
+
+ private void add_control(Gtk.Widget widget, int x, int y) {
+ widget.halign = Gtk.Align.FILL;
+ widget.valign = Gtk.Align.CENTER;
+ widget.hexpand = true;
+ widget.vexpand = true;
+
+ table.attach(widget, x, y, 1, 1);
+ }
+
+ private void on_constraint_changed() {
+ bool original = CONSTRAINT_ARRAY[constraint_combo.get_active()] == ScaleConstraint.ORIGINAL;
+ bool jpeg = format_combo.get_active_text() ==
+ PhotoFileFormat.JFIF.get_properties().get_user_visible_name();
+ pixels_entry.sensitive = !original;
+ quality_combo.sensitive = !original && jpeg;
+ if (original)
+ ok_button.sensitive = true;
+ else
+ on_pixels_changed();
+ }
+
+ private void on_format_changed() {
+ bool original = CONSTRAINT_ARRAY[constraint_combo.get_active()] == ScaleConstraint.ORIGINAL;
+
+ if (format_combo.get_active_text() == UNMODIFIED_FORMAT_LABEL) {
+ // if the user wishes to export the media unmodified, then we just copy the original
+ // files, so parameterizing size, quality, etc. is impossible -- these are all
+ // just as they are in the original file. In this case, we set the scale constraint to
+ // original and lock out all the controls
+ constraint_combo.set_active(0); /* 0 == original size */
+ constraint_combo.set_sensitive(false);
+ quality_combo.set_sensitive(false);
+ pixels_entry.sensitive = false;
+ export_metadata.active = false;
+ export_metadata.sensitive = false;
+ } else if (format_combo.get_active_text() == CURRENT_FORMAT_LABEL) {
+ // if the user wishes to export the media in its current format, we allow sizing but
+ // not JPEG quality customization, because in a batch of many photos, it's not
+ // guaranteed that all of them will be JPEGs or RAWs that get converted to JPEGs. Some
+ // could be PNGs, and PNG has no notion of quality. So lock out the quality control.
+ // If the user wants to set JPEG quality, he or she can explicitly specify the JPEG
+ // format.
+ constraint_combo.set_sensitive(true);
+ quality_combo.set_sensitive(false);
+ pixels_entry.sensitive = !original;
+ export_metadata.sensitive = true;
+ } else {
+ // if the user has chosen a specific format, then allow JPEG quality customization if
+ // the format is JPEG and the user is re-sizing the image, otherwise, disallow JPEG
+ // quality customization; always allow scaling.
+ constraint_combo.set_sensitive(true);
+ bool jpeg = get_specified_format() == PhotoFileFormat.JFIF;
+ quality_combo.sensitive = !original && jpeg;
+ export_metadata.sensitive = true;
+ }
+ }
+
+ private void on_activate() {
+ response(Gtk.ResponseType.OK);
+ }
+
+ private void on_pixels_changed() {
+ ok_button.sensitive = (pixels_entry.get_text_length() > 0) && (int.parse(pixels_entry.get_text()) > 0);
+ }
+
+ private void on_pixels_insert_text(string text, int length, ref int position) {
+ // This is necessary because SignalHandler.block_by_func() is not properly bound
+ if (in_insert)
+ return;
+
+ in_insert = true;
+
+ if (length == -1)
+ length = (int) text.length;
+
+ // only permit numeric text
+ 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)
+ pixels_entry.insert_text(new_text, (int) new_text.length, ref position);
+
+ Signal.stop_emission_by_name(pixels_entry, "insert-text");
+
+ in_insert = false;
+ }
+}