1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 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;
}
}
|