diff options
Diffstat (limited to 'src/ui.vala')
-rw-r--r-- | src/ui.vala | 2177 |
1 files changed, 2177 insertions, 0 deletions
diff --git a/src/ui.vala b/src/ui.vala new file mode 100644 index 0000000..2117a9a --- /dev/null +++ b/src/ui.vala @@ -0,0 +1,2177 @@ +/* + * Copyright (C) 2009-2011 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com>, + * Eduard Gotwig <g@ox.io> + * + * This program 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. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +[GtkTemplate (ui = "/org/gnome/SimpleScan/simple-scan.ui")] +public class UserInterface : Gtk.ApplicationWindow +{ + private const int DEFAULT_TEXT_DPI = 150; + private const int DEFAULT_PHOTO_DPI = 300; + + private const GLib.ActionEntry[] action_entries = + { + { "new_document", new_document_activate_cb }, + { "reorder", reorder_document_activate_cb }, + { "save", save_document_activate_cb }, + { "save_as", save_as_document_activate_cb }, + { "email", email_document_activate_cb }, + { "print", print_document_activate_cb }, + { "preferences", preferences_activate_cb }, + { "help", help_contents_activate_cb }, + { "about", about_activate_cb }, + { "quit", quit_activate_cb } + }; + + private Settings settings; + + [GtkChild] + private Gtk.MenuBar menubar; + [GtkChild] + private Gtk.Toolbar toolbar; + [GtkChild] + private Gtk.Menu page_menu; + [GtkChild] + private Gtk.Box main_vbox; + private Gtk.InfoBar info_bar; + private Gtk.Image info_bar_image; + private Gtk.Label info_bar_label; + private Gtk.Button info_bar_close_button; + private Gtk.Button info_bar_change_scanner_button; + [GtkChild] + private Gtk.RadioMenuItem custom_crop_menuitem; + [GtkChild] + private Gtk.RadioMenuItem a4_menuitem; + [GtkChild] + private Gtk.RadioMenuItem a5_menuitem; + [GtkChild] + private Gtk.RadioMenuItem a6_menuitem; + [GtkChild] + private Gtk.RadioMenuItem letter_menuitem; + [GtkChild] + private Gtk.RadioMenuItem legal_menuitem; + [GtkChild] + private Gtk.RadioMenuItem four_by_six_menuitem; + [GtkChild] + private Gtk.RadioMenuItem no_crop_menuitem; + [GtkChild] + private Gtk.MenuItem page_move_left_menuitem; + [GtkChild] + private Gtk.MenuItem page_move_right_menuitem; + [GtkChild] + private Gtk.MenuItem page_delete_menuitem; + [GtkChild] + private Gtk.MenuItem crop_rotate_menuitem; + [GtkChild] + private Gtk.MenuItem save_menuitem; + [GtkChild] + private Gtk.MenuItem save_as_menuitem; + [GtkChild] + private Gtk.MenuItem copy_to_clipboard_menuitem; + [GtkChild] + private Gtk.Button save_button; + [GtkChild] + private Gtk.ToolButton save_toolbutton; + [GtkChild] + private Gtk.MenuItem stop_scan_menuitem; + [GtkChild] + private Gtk.ToolButton stop_toolbutton; + [GtkChild] + private Gtk.ToggleButton crop_button; + [GtkChild] + private Gtk.ToggleToolButton crop_toolbutton; + [GtkChild] + private Gtk.Button stop_button; + [GtkChild] + private Gtk.Button scan_button; + + [GtkChild] + private Gtk.RadioMenuItem text_button_menuitem; + [GtkChild] + private Gtk.RadioMenuItem text_button_hb_menuitem; + [GtkChild] + private Gtk.RadioMenuItem text_menuitem; + [GtkChild] + private Gtk.RadioMenuItem photo_button_menuitem; + [GtkChild] + private Gtk.RadioMenuItem photo_button_hb_menuitem; + [GtkChild] + private Gtk.RadioMenuItem photo_menuitem; + + [GtkChild] + private Gtk.Dialog authorize_dialog; + [GtkChild] + private Gtk.Label authorize_label; + [GtkChild] + private Gtk.Entry username_entry; + [GtkChild] + private Gtk.Entry password_entry; + + [GtkChild] + private Gtk.Dialog preferences_dialog; + [GtkChild] + private Gtk.ComboBox device_combo; + [GtkChild] + private Gtk.ComboBox text_dpi_combo; + [GtkChild] + private Gtk.ComboBox photo_dpi_combo; + [GtkChild] + private Gtk.ComboBox page_side_combo; + [GtkChild] + private Gtk.ComboBox paper_size_combo; + [GtkChild] + private Gtk.Scale brightness_scale; + [GtkChild] + private Gtk.Scale contrast_scale; + [GtkChild] + private Gtk.Scale quality_scale; + [GtkChild] + private Gtk.ListStore device_model; + [GtkChild] + private Gtk.ListStore text_dpi_model; + [GtkChild] + private Gtk.ListStore photo_dpi_model; + [GtkChild] + private Gtk.ListStore page_side_model; + [GtkChild] + private Gtk.ListStore paper_size_model; + [GtkChild] + private Gtk.Adjustment brightness_adjustment; + [GtkChild] + private Gtk.Adjustment contrast_adjustment; + [GtkChild] + private Gtk.Adjustment quality_adjustment; + private bool setting_devices; + private bool user_selected_device; + + private Gtk.FileChooserDialog? save_dialog; + private ProgressBarDialog progress_dialog; + + private bool have_error; + private string error_title; + private string error_text; + private bool error_change_scanner_hint; + + public Book book { get; private set; } + private string? book_uri = null; + + public Page selected_page + { + get + { + return book_view.selected_page; + } + set + { + book_view.selected_page = value; + } + } + + private AutosaveManager autosave_manager; + + private BookView book_view; + private bool updating_page_menu; + private int default_page_width; + private int default_page_height; + private int default_page_dpi; + private ScanDirection default_page_scan_direction; + + private string document_hint = "photo"; + + public string default_file_name { get; set; default = _("Scanned Document.pdf"); } + private bool scanning_ = false; + public bool scanning + { + get { return scanning_; } + set + { + scanning_ = value; + page_delete_menuitem.sensitive = !value; + stop_scan_menuitem.sensitive = value; + stop_toolbutton.sensitive = value; + scan_button.visible = !value; + stop_button.visible = value; + } + } + + private int window_width; + private int window_height; + private bool window_is_maximized; + + private uint save_state_timeout; + + public int brightness + { + get { return (int) brightness_adjustment.value; } + set { brightness_adjustment.value = value; } + } + + public int contrast + { + get { return (int) contrast_adjustment.value; } + set { contrast_adjustment.value = value; } + } + + public int quality + { + get { return (int) quality_adjustment.value; } + set { quality_adjustment.value = value; } + } + + public string? selected_device + { + owned get + { + Gtk.TreeIter iter; + + if (device_combo.get_active_iter (out iter)) + { + string device; + device_model.get (iter, 0, out device, -1); + return device; + } + + return null; + } + + set + { + Gtk.TreeIter iter; + if (!find_scan_device (value, out iter)) + return; + + device_combo.set_active_iter (iter); + user_selected_device = true; + } + } + + public signal void start_scan (string? device, ScanOptions options); + public signal void stop_scan (); + public signal void email (string profile, int quality); + + public UserInterface () + { + settings = new Settings ("org.gnome.SimpleScan"); + + book = new Book (); + book.page_added.connect (page_added_cb); + book.reordered.connect (reordered_cb); + book.page_removed.connect (page_removed_cb); + + load (); + + autosave_manager = new AutosaveManager (); + autosave_manager.book = book; + autosave_manager.load (); + + if (book.n_pages == 0) + { + add_default_page (); + book.needs_saving = false; + } + else + book_view.selected_page = book.get_page (0); + book.needs_saving_changed.connect (needs_saving_cb); + } + + ~UserInterface () + { + book.page_added.disconnect (page_added_cb); + book.reordered.disconnect (reordered_cb); + book.page_removed.disconnect (page_removed_cb); + } + + private bool find_scan_device (string device, out Gtk.TreeIter iter) + { + bool have_iter = false; + + if (device_model.get_iter_first (out iter)) + { + do + { + string d; + device_model.get (iter, 0, out d, -1); + if (d == device) + have_iter = true; + } while (!have_iter && device_model.iter_next (ref iter)); + } + + return have_iter; + } + + private void show_error_dialog (string error_title, string error_text) + { + var dialog = new Gtk.MessageDialog (this, + Gtk.DialogFlags.MODAL, + Gtk.MessageType.WARNING, + Gtk.ButtonsType.NONE, + "%s", error_title); + dialog.add_button (_("_Close"), 0); + dialog.format_secondary_text ("%s", error_text); + dialog.destroy (); + } + + public void authorize (string resource, out string username, out string password) + { + /* Label in authorization dialog. '%s' is replaced with the name of the resource requesting authorization */ + var description = _("Username and password required to access '%s'").printf (resource); + + username_entry.text = ""; + password_entry.text = ""; + authorize_label.set_text (description); + + authorize_dialog.visible = true; + authorize_dialog.run (); + authorize_dialog.visible = false; + + username = username_entry.text; + password = password_entry.text; + } + + [GtkCallback] + private void device_combo_changed_cb (Gtk.Widget widget) + { + if (setting_devices) + return; + user_selected_device = true; + if (selected_device != null) + settings.set_string ("selected-device", selected_device); + } + + private void update_info_bar () + { + Gtk.MessageType type; + string title, text, image_id; + bool show_close_button = false; + bool show_change_scanner_button = false; + + if (have_error) + { + type = Gtk.MessageType.ERROR; + image_id = "dialog-error"; + title = error_title; + text = error_text; + show_close_button = true; + show_change_scanner_button = error_change_scanner_hint; + } + else if (device_model.iter_n_children (null) == 0) + { + type = Gtk.MessageType.WARNING; + image_id = "dialog-warning"; + /* Warning displayed when no scanners are detected */ + title = _("No scanners detected"); + /* Hint to user on why there are no scanners detected */ + text = _("Please check your scanner is connected and powered on"); + } + else + { + info_bar.visible = false; + return; + } + + info_bar.message_type = type; + info_bar_image.set_from_icon_name (image_id, Gtk.IconSize.DIALOG); + var message = "<big><b>%s</b></big>\n\n%s".printf (title, text); + info_bar_label.set_markup (message); + info_bar_close_button.visible = show_close_button; + info_bar_change_scanner_button.visible = show_change_scanner_button; + info_bar.visible = true; + } + + public void set_scan_devices (List<ScanDevice> devices) + { + bool have_selection = false; + int index; + Gtk.TreeIter iter; + + setting_devices = true; + + /* If the user hasn't chosen a scanner choose the best available one */ + if (user_selected_device) + have_selection = device_combo.active >= 0; + + /* Add new devices */ + index = 0; + foreach (var device in devices) + { + int n_delete = -1; + + /* Find if already exists */ + if (device_model.iter_nth_child (out iter, null, index)) + { + int i = 0; + do + { + string name; + bool matched; + + device_model.get (iter, 0, out name, -1); + matched = name == device.name; + + if (matched) + { + n_delete = i; + break; + } + i++; + } while (device_model.iter_next (ref iter)); + } + + /* If exists, remove elements up to this one */ + if (n_delete >= 0) + { + int i; + + /* Update label */ + device_model.set (iter, 1, device.label, -1); + + for (i = 0; i < n_delete; i++) + { + device_model.iter_nth_child (out iter, null, index); + device_model.remove (iter); + } + } + else + { + device_model.insert (out iter, index); + device_model.set (iter, 0, device.name, 1, device.label, -1); + } + index++; + } + + /* Remove any remaining devices */ + while (device_model.iter_nth_child (out iter, null, index)) + device_model.remove (iter); + + /* Select the first available device */ + if (!have_selection && devices != null) + device_combo.set_active (0); + + setting_devices = false; + + update_info_bar (); + } + + private void add_default_page () + { + var page = new Page (default_page_width, + default_page_height, + default_page_dpi, + default_page_scan_direction); + book.append_page (page); + book_view.selected_page = page; + } + + private void on_file_type_changed (Gtk.TreeSelection selection) + { + var extension = get_selected_extension (selection); + var path = save_dialog.get_filename (); + var filename = Path.get_basename (path); + + /* Replace extension */ + var extension_index = filename.last_index_of_char ('.'); + if (extension_index >= 0) + filename = filename.slice (0, extension_index); + filename = filename + extension; + save_dialog.set_current_name (filename); + } + + private string get_selected_extension (Gtk.TreeSelection selection) + { + Gtk.TreeModel model; + Gtk.TreeIter iter; + string extension = ""; + + if (selection.get_selected (out model, out iter)) + model.get (iter, 1, out extension, -1); + return extension; + } + + private string choose_file_location () + { + /* Get directory to save to */ + string? directory = null; + directory = settings.get_string ("save-directory"); + + if (directory == null || directory == "") + directory = Environment.get_user_special_dir (UserDirectory.DOCUMENTS); + + save_dialog = new Gtk.FileChooserDialog (/* Save dialog: Dialog title */ + _("Save As..."), + this, + Gtk.FileChooserAction.SAVE, + _("_Cancel"), Gtk.ResponseType.CANCEL, + _("_Save"), Gtk.ResponseType.ACCEPT, + null); + save_dialog.do_overwrite_confirmation = true; + save_dialog.local_only = false; + save_dialog.set_current_folder (directory); + save_dialog.set_current_name (default_file_name); + + /* Filter to only show images by default */ + var filter = new Gtk.FileFilter (); + filter.set_filter_name (/* Save dialog: Filter name to show only image files */ + _("Image Files")); + filter.add_pixbuf_formats (); + filter.add_mime_type ("application/pdf"); + save_dialog.add_filter (filter); + filter = new Gtk.FileFilter (); + filter.set_filter_name (/* Save dialog: Filter name to show all files */ + _("All Files")); + filter.add_pattern ("*"); + save_dialog.add_filter (filter); + + var expander = new Gtk.Expander.with_mnemonic (/* */ + _("Select File _Type")); + expander.spacing = 5; + save_dialog.set_extra_widget (expander); + + string default_extension = ""; + var index = default_file_name.last_index_of_char ('.'); + if (index >= 0) + default_extension = default_file_name.substring (index); + + var file_type_store = new Gtk.ListStore (2, typeof (string), typeof (string)); + Gtk.TreeIter iter; + file_type_store.append (out iter); + file_type_store.set (iter, + /* Save dialog: Label for saving in PDF format */ + 0, _("PDF (multi-page document)"), + 1, ".pdf", + -1); + file_type_store.append (out iter); + file_type_store.set (iter, + /* Save dialog: Label for saving in JPEG format */ + 0, _("JPEG (compressed)"), + 1, ".jpg", + -1); + file_type_store.append (out iter); + file_type_store.set (iter, + /* Save dialog: Label for saving in PNG format */ + 0, _("PNG (lossless)"), + 1, ".png", + -1); + + var file_type_view = new Gtk.TreeView.with_model (file_type_store); + file_type_view.headers_visible = false; + file_type_view.rules_hint = true; + var column = new Gtk.TreeViewColumn.with_attributes ("", + new Gtk.CellRendererText (), + "text", 0, null); + file_type_view.append_column (column); + expander.add (file_type_view); + + if (file_type_store.get_iter_first (out iter)) + { + do + { + string e; + file_type_store.get (iter, 1, out e, -1); + if (default_extension == e) + file_type_view.get_selection ().select_iter (iter); + } while (file_type_store.iter_next (ref iter)); + } + file_type_view.get_selection ().changed.connect (on_file_type_changed); + + expander.show_all (); + + var response = save_dialog.run (); + + string? uri = null; + if (response == Gtk.ResponseType.ACCEPT) + { + var selection = file_type_view.get_selection (); + var extension = get_selected_extension (selection); + + var path = save_dialog.get_filename (); + var filename = Path.get_basename (path); + + var extension_index = filename.last_index_of_char ('.'); + if (extension_index < 0) + path += extension; + + uri = File.new_for_path (path).get_uri (); + } + + settings.set_string ("save-directory", save_dialog.get_current_folder ()); + + file_type_view.get_selection ().changed.disconnect (on_file_type_changed); + save_dialog.destroy (); + save_dialog = null; + + return uri; + } + + private bool save_document (bool force_choose_location) + { + string? uri; + if (book_uri != null && !force_choose_location) + uri = book_uri; + else + uri = choose_file_location (); + if (uri == null) + return false; + + var file = File.new_for_uri (uri); + + debug ("Saving to '%s'", uri); + + var uri_lower = uri.down (); + string format = "jpeg"; + if (uri_lower.has_suffix (".pdf")) + format = "pdf"; + else if (uri_lower.has_suffix (".ps")) + format = "ps"; + else if (uri_lower.has_suffix (".png")) + format = "png"; + else if (uri_lower.has_suffix (".tif") || uri_lower.has_suffix (".tiff")) + format = "tiff"; + + show_progress_dialog (); + try + { + book.save (format, quality, file); + } + catch (Error e) + { + hide_progress_dialog (); + warning ("Error saving file: %s", e.message); + show_error (/* Title of error dialog when save failed */ + _("Failed to save file"), + e.message, + false); + return false; + } + + book_uri = uri; + book.needs_saving = false; + return true; + } + + private bool prompt_to_save (string title, string discard_label) + { + if (!book.needs_saving) + return true; + + var dialog = new Gtk.MessageDialog (this, + Gtk.DialogFlags.MODAL, + Gtk.MessageType.WARNING, + Gtk.ButtonsType.NONE, + "%s", title); + dialog.format_secondary_text ("%s", + /* Text in dialog warning when a document is about to be lost*/ + _("If you don't save, changes will be permanently lost.")); + dialog.add_button (discard_label, Gtk.ResponseType.NO); + dialog.add_button (_("_Cancel"), Gtk.ResponseType.CANCEL); + dialog.add_button (_("_Save"), Gtk.ResponseType.YES); + + var response = dialog.run (); + dialog.destroy (); + + switch (response) + { + case Gtk.ResponseType.YES: + if (save_document (false)) + return true; + else + return false; + case Gtk.ResponseType.CANCEL: + return false; + case Gtk.ResponseType.NO: + default: + return true; + } + } + + private void clear_document () + { + book.clear (); + add_default_page (); + book_uri = null; + book.needs_saving = false; + save_as_menuitem.sensitive = false; + copy_to_clipboard_menuitem.sensitive = false; + } + + private void new_document () + { + if (!prompt_to_save (/* Text in dialog warning when a document is about to be lost */ + _("Save current document?"), + /* Button in dialog to create new document and discard unsaved document */ + _("Discard Changes"))) + return; + + if (scanning) + stop_scan (); + clear_document (); + } + + [GtkCallback] + private void new_button_clicked_cb (Gtk.Widget widget) + { + new_document(); + } + + public void new_document_activate_cb () + { + new_document(); + } + + private void set_document_hint (string document_hint, bool save = false) + { + this.document_hint = document_hint; + + if (document_hint == "text") + { + text_button_menuitem.active = true; + text_button_hb_menuitem.active = true; + text_menuitem.active = true; + } + else if (document_hint == "photo") + { + photo_button_menuitem.active = true; + photo_button_hb_menuitem.active = true; + photo_menuitem.active = true; + } + + if (save) + settings.set_string ("document-type", document_hint); + } + + [GtkCallback] + private void text_menuitem_toggled_cb (Gtk.CheckMenuItem widget) + { + if (widget.active) + set_document_hint ("text", true); + } + + [GtkCallback] + private void photo_menuitem_toggled_cb (Gtk.CheckMenuItem widget) + { + if (widget.active) + set_document_hint ("photo", true); + } + + private void set_page_side (ScanType page_side) + { + Gtk.TreeIter iter; + + if (page_side_model.get_iter_first (out iter)) + { + do + { + int s; + page_side_model.get (iter, 0, out s, -1); + if (s == page_side) + { + page_side_combo.set_active_iter (iter); + return; + } + } while (page_side_model.iter_next (ref iter)); + } + } + + private void set_paper_size (int width, int height) + { + Gtk.TreeIter iter; + bool have_iter; + + for (have_iter = paper_size_model.get_iter_first (out iter); + have_iter; + have_iter = paper_size_model.iter_next (ref iter)) + { + int w, h; + paper_size_model.get (iter, 0, out w, 1, out h, -1); + if (w == width && h == height) + break; + } + + if (!have_iter) + have_iter = paper_size_model.get_iter_first (out iter); + if (have_iter) + paper_size_combo.set_active_iter (iter); + } + + private int get_text_dpi () + { + Gtk.TreeIter iter; + int dpi = DEFAULT_TEXT_DPI; + + if (text_dpi_combo.get_active_iter (out iter)) + text_dpi_model.get (iter, 0, out dpi, -1); + + return dpi; + } + + private int get_photo_dpi () + { + Gtk.TreeIter iter; + int dpi = DEFAULT_PHOTO_DPI; + + if (photo_dpi_combo.get_active_iter (out iter)) + photo_dpi_model.get (iter, 0, out dpi, -1); + + return dpi; + } + + private ScanType get_page_side () + { + Gtk.TreeIter iter; + int page_side = ScanType.ADF_BOTH; + + if (page_side_combo.get_active_iter (out iter)) + page_side_model.get (iter, 0, out page_side, -1); + + return (ScanType) page_side; + } + + private bool get_paper_size (out int width, out int height) + { + Gtk.TreeIter iter; + + width = height = 0; + if (paper_size_combo.get_active_iter (out iter)) + { + paper_size_model.get (iter, 0, ref width, 1, ref height, -1); + return true; + } + + return false; + } + + private ScanOptions make_scan_options () + { + var options = new ScanOptions (); + if (document_hint == "text") + { + options.scan_mode = ScanMode.GRAY; + options.dpi = get_text_dpi (); + options.depth = 2; + } + else + { + options.scan_mode = ScanMode.COLOR; + options.dpi = get_photo_dpi (); + options.depth = 8; + } + get_paper_size (out options.paper_width, out options.paper_height); + options.brightness = brightness; + options.contrast = contrast; + + return options; + } + + [GtkCallback] + private void scan_button_clicked_cb (Gtk.Widget widget) + { + var options = make_scan_options (); + options.type = ScanType.SINGLE; + start_scan (selected_device, options); + } + + [GtkCallback] + private void stop_scan_button_clicked_cb (Gtk.Widget widget) + { + stop_scan (); + } + + [GtkCallback] + private void continuous_scan_button_clicked_cb (Gtk.Widget widget) + { + if (scanning) + stop_scan (); + else + { + var options = make_scan_options (); + options.type = get_page_side (); + start_scan (selected_device, options); + } + } + + [GtkCallback] + private void preferences_button_clicked_cb (Gtk.Widget widget) + { + preferences_dialog.present (); + } + + public void preferences_activate_cb () + { + preferences_dialog.present (); + } + + [GtkCallback] + private bool preferences_dialog_delete_event_cb (Gtk.Widget widget, Gdk.EventAny event) + { + return true; + } + + [GtkCallback] + private void preferences_dialog_response_cb (Gtk.Widget widget, int response_id) + { + preferences_dialog.visible = false; + } + + private void update_page_menu () + { + var page = book_view.selected_page; + if (page == null) + { + page_move_left_menuitem.sensitive = false; + page_move_right_menuitem.sensitive = false; + } + else + { + var index = book.get_page_index (page); + page_move_left_menuitem.sensitive = index > 0; + page_move_right_menuitem.sensitive = index < book.n_pages - 1; + } + } + + private void page_selected_cb (BookView view, Page? page) + { + if (page == null) + return; + + updating_page_menu = true; + + update_page_menu (); + + var menuitem = no_crop_menuitem; + if (page.has_crop) + { + var crop_name = page.crop_name; + if (crop_name != null) + { + if (crop_name == "A4") + menuitem = a4_menuitem; + else if (crop_name == "A5") + menuitem = a5_menuitem; + else if (crop_name == "A6") + menuitem = a6_menuitem; + else if (crop_name == "letter") + menuitem = letter_menuitem; + else if (crop_name == "legal") + menuitem = legal_menuitem; + else if (crop_name == "4x6") + menuitem = four_by_six_menuitem; + } + else + menuitem = custom_crop_menuitem; + } + + menuitem.active = true; + crop_button.active = page.has_crop; + crop_toolbutton.active = page.has_crop; + + updating_page_menu = false; + } + + private void show_page_cb (BookView view, Page page) + { + var path = get_temporary_filename ("scanned-page", "tiff"); + if (path == null) + return; + var file = File.new_for_path (path); + + try + { + page.save ("tiff", quality, file); + } + catch (Error e) + { + show_error_dialog (/* Error message display when unable to save image for preview */ + _("Unable to save image for preview"), + e.message); + return; + } + + try + { + Gtk.show_uri (screen, file.get_uri (), Gtk.get_current_event_time ()); + } + catch (Error e) + { + show_error_dialog (/* Error message display when unable to preview image */ + _("Unable to open image preview application"), + e.message); + } + } + + private void show_page_menu_cb (BookView view) + { + page_menu.popup (null, null, null, 3, Gtk.get_current_event_time ()); + } + + [GtkCallback] + private void rotate_left_button_clicked_cb (Gtk.Widget widget) + { + if (updating_page_menu) + return; + var page = book_view.selected_page; + if (page != null) + page.rotate_left (); + } + + [GtkCallback] + private void rotate_right_button_clicked_cb (Gtk.Widget widget) + { + if (updating_page_menu) + return; + var page = book_view.selected_page; + if (page != null) + page.rotate_right (); + } + + private void set_crop (string? crop_name) + { + crop_rotate_menuitem.sensitive = crop_name != null; + + if (updating_page_menu) + return; + + var page = book_view.selected_page; + if (page == null) + { + warning ("Trying to set crop but no selected page"); + return; + } + + if (crop_name == null) + page.set_no_crop (); + else if (crop_name == "custom") + { + var width = page.width; + var height = page.height; + var crop_width = (int) (width * 0.8 + 0.5); + var crop_height = (int) (height * 0.8 + 0.5); + page.set_custom_crop (crop_width, crop_height); + page.move_crop ((width - crop_width) / 2, (height - crop_height) / 2); + } + else + page.set_named_crop (crop_name); + } + + [GtkCallback] + private void no_crop_menuitem_toggled_cb (Gtk.CheckMenuItem widget) + { + if (widget.active) + set_crop (null); + } + + [GtkCallback] + private void custom_crop_menuitem_toggled_cb (Gtk.CheckMenuItem widget) + { + if (widget.active) + set_crop ("custom"); + } + + [GtkCallback] + private void crop_button_toggled_cb (Gtk.ToggleButton widget) + { + if (updating_page_menu) + return; + + if (widget.active) + custom_crop_menuitem.active = true; + else + no_crop_menuitem.active = true; + } + + [GtkCallback] + private void crop_toolbutton_toggled_cb (Gtk.ToggleToolButton widget) + { + if (updating_page_menu) + return; + + if (widget.active) + custom_crop_menuitem.active = true; + else + no_crop_menuitem.active = true; + } + + [GtkCallback] + private void four_by_six_menuitem_toggled_cb (Gtk.CheckMenuItem widget) + { + if (widget.active) + set_crop ("4x6"); + } + + [GtkCallback] + private void legal_menuitem_toggled_cb (Gtk.CheckMenuItem widget) + { + if (widget.active) + set_crop ("legal"); + } + + [GtkCallback] + private void letter_menuitem_toggled_cb (Gtk.CheckMenuItem widget) + { + if (widget.active) + set_crop ("letter"); + } + + [GtkCallback] + private void a6_menuitem_toggled_cb (Gtk.CheckMenuItem widget) + { + if (widget.active) + set_crop ("A6"); + } + + [GtkCallback] + private void a5_menuitem_toggled_cb (Gtk.CheckMenuItem widget) + { + if (widget.active) + set_crop ("A5"); + } + + [GtkCallback] + private void a4_menuitem_toggled_cb (Gtk.CheckMenuItem widget) + { + if (widget.active) + set_crop ("A4"); + } + + [GtkCallback] + private void crop_rotate_menuitem_activate_cb (Gtk.Widget widget) + { + var page = book_view.selected_page; + if (page == null) + return; + page.rotate_crop (); + } + + [GtkCallback] + private void page_move_left_menuitem_activate_cb (Gtk.Widget widget) + { + var page = book_view.selected_page; + var index = book.get_page_index (page); + if (index > 0) + book.move_page (page, index - 1); + } + + [GtkCallback] + private void page_move_right_menuitem_activate_cb (Gtk.Widget widget) + { + var page = book_view.selected_page; + var index = book.get_page_index (page); + if (index < book.n_pages - 1) + book.move_page (page, book.get_page_index (page) + 1); + } + + [GtkCallback] + private void page_delete_menuitem_activate_cb (Gtk.Widget widget) + { + book_view.book.delete_page (book_view.selected_page); + } + + private void reorder_document () + { + var dialog = new Gtk.Window (); + dialog.type_hint = Gdk.WindowTypeHint.DIALOG; + dialog.modal = true; + dialog.border_width = 12; + /* Title of dialog to reorder pages */ + dialog.title = _("Reorder Pages"); + dialog.set_transient_for (this); + dialog.key_press_event.connect ((e) => + { + if (e.state == 0 && e.keyval == Gdk.Key.Escape) + { + dialog.destroy (); + return true; + } + + return false; + }); + dialog.visible = true; + + var g = new Gtk.Grid (); + g.row_homogeneous = true; + g.row_spacing = 6; + g.column_homogeneous = true; + g.column_spacing = 6; + g.visible = true; + dialog.add (g); + + /* Label on button for combining sides in reordering dialog */ + var b = make_reorder_button (_("Combine sides"), "F1F2F3B1B2B3-F1B1F2B2F3B3"); + b.clicked.connect (() => + { + book.combine_sides (); + dialog.destroy (); + }); + b.visible = true; + g.attach (b, 0, 0, 1, 1); + + /* Label on button for combining sides in reverse order in reordering dialog */ + b = make_reorder_button (_("Combine sides (reverse)"), "F1F2F3B3B2B1-F1B1F2B2F3B3"); + b.clicked.connect (() => + { + book.combine_sides_reverse (); + dialog.destroy (); + }); + b.visible = true; + g.attach (b, 1, 0, 1, 1); + + /* Label on button for reversing in reordering dialog */ + b = make_reorder_button (_("Reverse"), "C1C2C3C4C5C6-C6C5C4C3C2C1"); + b.clicked.connect (() => + { + book.reverse (); + dialog.destroy (); + }); + b.visible = true; + g.attach (b, 0, 2, 1, 1); + + /* Label on button for cancelling page reordering dialog */ + b = make_reorder_button (_("Keep unchanged"), "C1C2C3C4C5C6-C1C2C3C4C5C6"); + b.clicked.connect (() => + { + dialog.destroy (); + }); + b.visible = true; + g.attach (b, 1, 2, 1, 1); + + dialog.present (); + } + + public void reorder_document_activate_cb () + { + reorder_document (); + } + + [GtkCallback] + private void reorder_menuitem_activate_cb (Gtk.Widget widget) + { + reorder_document (); + } + + private Gtk.Button make_reorder_button (string text, string items) + { + var b = new Gtk.Button (); + + var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); + vbox.visible = true; + b.add (vbox); + + var label = new Gtk.Label (text); + label.visible = true; + vbox.pack_start (label, true, true, 0); + + var rb = make_reorder_box (items); + rb.visible = true; + vbox.pack_start (rb, true, true, 0); + + return b; + } + + private Gtk.Box make_reorder_box (string items) + { + var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); + box.visible = true; + + Gtk.Box? page_box = null; + for (var i = 0; items[i] != '\0'; i++) + { + if (items[i] == '-') + { + var a = new Gtk.Arrow (Gtk.ArrowType.RIGHT, Gtk.ShadowType.NONE); + a.visible = true; + box.pack_start (a, false, false, 0); + page_box = null; + continue; + } + + /* First character describes side */ + var side = items[i]; + i++; + if (items[i] == '\0') + break; + + if (page_box == null) + { + page_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 3); + page_box.visible = true; + box.pack_start (page_box, false, false, 0); + } + + /* Get colours for each page (from Tango palette) */ + var r = 1.0; + var g = 1.0; + var b = 1.0; + switch (side) + { + case 'F': + /* Plum */ + r = 0x75 / 255.0; + g = 0x50 / 255.0; + b = 0x7B / 255.0; + break; + case 'B': + /* Orange */ + r = 0xF5 / 255.0; + g = 0x79 / 255.0; + b = 0.0; + break; + case 'C': + /* Butter to Scarlet Red */ + var p = (items[i] - '1') / 5.0; + r = (0xED / 255.0) * (1 - p) + 0xCC * p; + g = (0xD4 / 255.0) * (1 - p); + b = 0; + break; + } + + /* Mix with white to look more paper like */ + r = r + (1.0 - r) * 0.7; + g = g + (1.0 - g) * 0.7; + b = b + (1.0 - b) * 0.7; + + var icon = new PageIcon ("%c".printf (items[i]), r, g, b); + icon.visible = true; + page_box.pack_start (icon, false, false, 0); + } + + return box; + } + + [GtkCallback] + private void save_file_button_clicked_cb (Gtk.Widget widget) + { + save_document (false); + } + + public void save_document_activate_cb () + { + save_document (false); + } + + [GtkCallback] + private void copy_to_clipboard_button_clicked_cb (Gtk.Widget widget) + { + var page = book_view.selected_page; + if (page != null) + page.copy_to_clipboard (this); + } + + [GtkCallback] + private void save_as_file_button_clicked_cb (Gtk.Widget widget) + { + save_document (true); + } + + public void save_as_document_activate_cb () + { + save_document (true); + } + + private void draw_page (Gtk.PrintOperation operation, + Gtk.PrintContext print_context, + int page_number) + { + var context = print_context.get_cairo_context (); + var page = book.get_page (page_number); + + /* Rotate to same aspect */ + bool is_landscape = false; + if (print_context.get_width () > print_context.get_height ()) + is_landscape = true; + if (page.is_landscape != is_landscape) + { + context.translate (print_context.get_width (), 0); + context.rotate (Math.PI_2); + } + + context.scale (print_context.get_dpi_x () / page.dpi, + print_context.get_dpi_y () / page.dpi); + + var image = page.get_image (true); + Gdk.cairo_set_source_pixbuf (context, image, 0, 0); + context.paint (); + } + + [GtkCallback] + private void email_button_clicked_cb (Gtk.Widget widget) + { + email (document_hint, quality); + } + + public void email_document_activate_cb () + { + email (document_hint, quality); + } + + private void print_document () + { + var print = new Gtk.PrintOperation (); + print.n_pages = (int) book.n_pages; + print.draw_page.connect (draw_page); + + try + { + print.run (Gtk.PrintOperationAction.PRINT_DIALOG, this); + } + catch (Error e) + { + warning ("Error printing: %s", e.message); + } + + print.draw_page.disconnect (draw_page); + } + + [GtkCallback] + private void print_button_clicked_cb (Gtk.Widget widget) + { + print_document (); + } + + public void print_document_activate_cb () + { + print_document (); + } + + private void launch_help () + { + try + { + Gtk.show_uri (screen, "help:simple-scan", Gtk.get_current_event_time ()); + } + catch (Error e) + { + show_error_dialog (/* Error message displayed when unable to launch help browser */ + _("Unable to open help file"), + e.message); + } + } + + [GtkCallback] + private void help_contents_menuitem_activate_cb (Gtk.Widget widget) + { + launch_help (); + } + + public void help_contents_activate_cb () + { + launch_help (); + } + + private void show_about () + { + string[] authors = { "Robert Ancell <robert.ancell@canonical.com>" }; + + /* The license this software is under (GPL3+) */ + string license = _("This program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>."); + + /* Title of about dialog */ + string title = _("About Simple Scan"); + + /* Description of program */ + string description = _("Simple document scanning tool"); + + Gtk.show_about_dialog (this, + "title", title, + "program-name", "Simple Scan", + "version", VERSION, + "comments", description, + "logo-icon-name", "scanner", + "authors", authors, + "translator-credits", _("translator-credits"), + "website", "https://launchpad.net/simple-scan", + "copyright", "Copyright © 2009-2011 Canonical Ltd.", + "license", license, + "wrap-license", true, + null); + } + + [GtkCallback] + private void about_menuitem_activate_cb (Gtk.Widget widget) + { + show_about (); + } + + public void about_activate_cb () + { + show_about (); + } + + private bool on_quit () + { + if (!prompt_to_save (/* Text in dialog warning when a document is about to be lost */ + _("Save document before quitting?"), + /* Button in dialog to quit and discard unsaved document */ + _("Quit without Saving"))) + return false; + + destroy (); + + if (save_state_timeout != 0) + save_state (true); + + autosave_manager.cleanup (); + + return true; + } + + [GtkCallback] + private void quit_menuitem_activate_cb (Gtk.Widget widget) + { + on_quit (); + } + + public void quit_activate_cb () + { + on_quit (); + } + + [GtkCallback] + private bool simple_scan_window_configure_event_cb (Gtk.Widget widget, Gdk.EventConfigure event) + { + if (!window_is_maximized) + { + window_width = event.width; + window_height = event.height; + save_state (); + } + + return false; + } + + private void info_bar_response_cb (Gtk.InfoBar widget, int response_id) + { + if (response_id == 1) + { + device_combo.grab_focus (); + preferences_dialog.present (); + } + else + { + have_error = false; + error_title = null; + error_text = null; + update_info_bar (); + } + } + + [GtkCallback] + private bool simple_scan_window_window_state_event_cb (Gtk.Widget widget, Gdk.EventWindowState event) + { + if ((event.changed_mask & Gdk.WindowState.MAXIMIZED) != 0) + { + window_is_maximized = (event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0; + save_state (); + } + return false; + } + + [GtkCallback] + private bool window_delete_event_cb (Gtk.Widget widget, Gdk.EventAny event) + { + return !on_quit (); + } + + private void page_size_changed_cb (Page page) + { + default_page_width = page.width; + default_page_height = page.height; + default_page_dpi = page.dpi; + save_state (); + } + + private void page_scan_direction_changed_cb (Page page) + { + default_page_scan_direction = page.scan_direction; + save_state (); + } + + private void page_added_cb (Book book, Page page) + { + page_size_changed_cb (page); + default_page_scan_direction = page.scan_direction; + page.size_changed.connect (page_size_changed_cb); + page.scan_direction_changed.connect (page_scan_direction_changed_cb); + + update_page_menu (); + } + + private void reordered_cb (Book book) + { + update_page_menu (); + } + + private void page_removed_cb (Book book, Page page) + { + page.size_changed.disconnect (page_size_changed_cb); + page.scan_direction_changed.disconnect (page_scan_direction_changed_cb); + + /* If this is the last page add a new blank one */ + if (book.n_pages == 1) + add_default_page (); + + update_page_menu (); + } + + private void set_dpi_combo (Gtk.ComboBox combo, int default_dpi, int current_dpi) + { + var renderer = new Gtk.CellRendererText (); + combo.pack_start (renderer, true); + combo.add_attribute (renderer, "text", 1); + + var model = combo.model as Gtk.ListStore; + int[] scan_resolutions = {75, 150, 300, 600, 1200, 2400}; + foreach (var dpi in scan_resolutions) + { + string label; + if (dpi == default_dpi) + /* Preferences dialog: Label for default resolution in resolution list */ + label = _("%d dpi (default)").printf (dpi); + else if (dpi == 75) + /* Preferences dialog: Label for minimum resolution in resolution list */ + label = _("%d dpi (draft)").printf (dpi); + else if (dpi == 1200) + /* Preferences dialog: Label for maximum resolution in resolution list */ + label = _("%d dpi (high resolution)").printf (dpi); + else + /* Preferences dialog: Label for resolution value in resolution list (dpi = dots per inch) */ + label = _("%d dpi").printf (dpi); + + Gtk.TreeIter iter; + model.append (out iter); + model.set (iter, 0, dpi, 1, label, -1); + + if (dpi == current_dpi) + combo.set_active_iter (iter); + } + } + + private void needs_saving_cb (Book book) + { + save_menuitem.sensitive = book.needs_saving; + save_button.sensitive = book.needs_saving; + save_toolbutton.sensitive = book.needs_saving; + if (book.needs_saving) + save_as_menuitem.sensitive = true; + copy_to_clipboard_menuitem.sensitive = true; + } + + private bool shell_shows_menubar + { + get + { + bool shell_shows_menubar; + Gtk.Settings.get_default ().get ("gtk-shell-shows-menubar", out shell_shows_menubar); + return shell_shows_menubar; + } + } + + private void load () + { + Gtk.IconTheme.get_default ().append_search_path (ICON_DIR); + + Gtk.Window.set_default_icon_name ("scanner"); + + var app = Application.get_default () as Gtk.Application; + + if (!shell_shows_menubar) + { + app.add_action_entries (action_entries, this); + + var appmenu = new Menu (); + var section = new Menu (); + appmenu.append_section (null, section); + section.append (_("New Document"), "app.new_document"); + + section = new Menu (); + appmenu.append_section (null, section); + var menu = new Menu (); + section.append_submenu (_("Document"), menu); + menu.append (_("Reorder Pages"), "app.reorder"); + menu.append (_("Save"), "app.save"); + menu.append (_("Save As..."), "app.save_as"); + menu.append (_("Email..."), "app.email"); + menu.append (_("Print..."), "app.print"); + + section = new Menu (); + appmenu.append_section (null, section); + section.append (_("Preferences"), "app.preferences"); + + section = new Menu (); + appmenu.append_section (null, section); + section.append (_("Help"), "app.help"); + section.append (_("About"), "app.about"); + section.append (_("Quit"), "app.quit"); + + app.app_menu = appmenu; + + app.add_accelerator ("<Ctrl>N", "app.new_document", null); + app.add_accelerator ("<Ctrl>S", "app.save", null); + app.add_accelerator ("<Shift><Ctrl>S", "app.save_as", null); + app.add_accelerator ("<Ctrl>E", "app.email", null); + app.add_accelerator ("<Ctrl>P", "app.print", null); + app.add_accelerator ("F1", "app.help", null); + app.add_accelerator ("<Ctrl>Q", "app.quit", null); + } + else + { + set_titlebar (null); + menubar.visible = true; + toolbar.visible = true; + } + app.add_window (this); + + /* Add InfoBar (not supported in Glade) */ + info_bar = new Gtk.InfoBar (); + info_bar.response.connect (info_bar_response_cb); + main_vbox.pack_start (info_bar, false, true, 0); + var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12); + var content_area = info_bar.get_content_area () as Gtk.Container; + content_area.add (hbox); + hbox.visible = true; + + info_bar_image = new Gtk.Image.from_icon_name ("dialog-warning", Gtk.IconSize.DIALOG); + hbox.pack_start (info_bar_image, false, true, 0); + info_bar_image.visible = true; + + info_bar_label = new Gtk.Label (null); + info_bar_label.set_alignment (0.0f, 0.5f); + hbox.pack_start (info_bar_label, true, true, 0); + info_bar_label.visible = true; + + info_bar_close_button = info_bar.add_button (_("_Close"), Gtk.ResponseType.CLOSE) as Gtk.Button; + info_bar_change_scanner_button = info_bar.add_button (/* Button in error infobar to open preferences dialog and change scanner */ + _("Change _Scanner"), 1) as Gtk.Button; + + Gtk.TreeIter iter; + paper_size_model.append (out iter); + paper_size_model.set (iter, 0, 0, 1, 0, 2, + /* Combo box value for automatic paper size */ + _("Automatic"), -1); + paper_size_model.append (out iter); + paper_size_model.set (iter, 0, 1050, 1, 1480, 2, "A6", -1); + paper_size_model.append (out iter); + paper_size_model.set (iter, 0, 1480, 1, 2100, 2, "A5", -1); + paper_size_model.append (out iter); + paper_size_model.set (iter, 0, 2100, 1, 2970, 2, "A4", -1); + paper_size_model.append (out iter); + paper_size_model.set (iter, 0, 2159, 1, 2794, 2, "Letter", -1); + paper_size_model.append (out iter); + paper_size_model.set (iter, 0, 2159, 1, 3556, 2, "Legal", -1); + paper_size_model.append (out iter); + paper_size_model.set (iter, 0, 1016, 1, 1524, 2, "4×6", -1); + + var dpi = settings.get_int ("text-dpi"); + if (dpi <= 0) + dpi = DEFAULT_TEXT_DPI; + set_dpi_combo (text_dpi_combo, DEFAULT_TEXT_DPI, dpi); + text_dpi_combo.changed.connect (() => { settings.set_int ("text-dpi", get_text_dpi ()); }); + dpi = settings.get_int ("photo-dpi"); + if (dpi <= 0) + dpi = DEFAULT_PHOTO_DPI; + set_dpi_combo (photo_dpi_combo, DEFAULT_PHOTO_DPI, dpi); + photo_dpi_combo.changed.connect (() => { settings.set_int ("photo-dpi", get_photo_dpi ()); }); + + var renderer = new Gtk.CellRendererText (); + device_combo.pack_start (renderer, true); + device_combo.add_attribute (renderer, "text", 1); + + renderer = new Gtk.CellRendererText (); + page_side_combo.pack_start (renderer, true); + page_side_combo.add_attribute (renderer, "text", 1); + set_page_side ((ScanType) settings.get_enum ("page-side")); + page_side_combo.changed.connect (() => { settings.set_enum ("page-side", get_page_side ()); }); + + renderer = new Gtk.CellRendererText (); + paper_size_combo.pack_start (renderer, true); + paper_size_combo.add_attribute (renderer, "text", 2); + var paper_width = settings.get_int ("paper-width"); + var paper_height = settings.get_int ("paper-height"); + set_paper_size (paper_width, paper_height); + paper_size_combo.changed.connect (() => + { + int w, h; + get_paper_size (out w, out h); + settings.set_int ("paper-width", w); + settings.set_int ("paper-height", h); + }); + + var lower = brightness_adjustment.lower; + var darker_label = "<small>%s</small>".printf (_("Darker")); + var upper = brightness_adjustment.upper; + var lighter_label = "<small>%s</small>".printf (_("Lighter")); + brightness_scale.add_mark (lower, Gtk.PositionType.BOTTOM, darker_label); + brightness_scale.add_mark (0, Gtk.PositionType.BOTTOM, null); + brightness_scale.add_mark (upper, Gtk.PositionType.BOTTOM, lighter_label); + brightness = settings.get_int ("brightness"); + brightness_adjustment.value_changed.connect (() => { settings.set_int ("brightness", brightness); }); + + lower = contrast_adjustment.lower; + var less_label = "<small>%s</small>".printf (_("Less")); + upper = contrast_adjustment.upper; + var more_label = "<small>%s</small>".printf (_("More")); + contrast_scale.add_mark (lower, Gtk.PositionType.BOTTOM, less_label); + contrast_scale.add_mark (0, Gtk.PositionType.BOTTOM, null); + contrast_scale.add_mark (upper, Gtk.PositionType.BOTTOM, more_label); + contrast = settings.get_int ("contrast"); + contrast_adjustment.value_changed.connect (() => { settings.set_int ("contrast", contrast); }); + + lower = quality_adjustment.lower; + var minimum_label = "<small>%s</small>".printf (_("Minimum")); + upper = quality_adjustment.upper; + var maximum_label = "<small>%s</small>".printf (_("Maximum")); + quality_scale.add_mark (lower, Gtk.PositionType.BOTTOM, minimum_label); + quality_scale.add_mark (75, Gtk.PositionType.BOTTOM, null); + quality_scale.add_mark (upper, Gtk.PositionType.BOTTOM, maximum_label); + quality = settings.get_int ("jpeg-quality"); + quality_adjustment.value_changed.connect (() => { settings.set_int ("jpeg-quality", quality); }); + + var device = settings.get_string ("selected-device"); + if (device != null) + { + if (find_scan_device (device, out iter)) + device_combo.set_active_iter (iter); + } + + var document_type = settings.get_string ("document-type"); + if (document_type != null) + set_document_hint (document_type); + + book_view = new BookView (book); + book_view.border_width = 18; + main_vbox.pack_end (book_view, true, true, 0); + book_view.page_selected.connect (page_selected_cb); + book_view.show_page.connect (show_page_cb); + book_view.show_menu.connect (show_page_menu_cb); + book_view.visible = true; + + authorize_dialog.transient_for = this; + preferences_dialog.transient_for = this; + + /* Load previous state */ + load_state (); + + /* Restore window size */ + debug ("Restoring window to %dx%d pixels", window_width, window_height); + set_default_size (window_width, window_height); + if (window_is_maximized) + { + debug ("Restoring window to maximized"); + maximize (); + } + + progress_dialog = new ProgressBarDialog (this, _("Saving document...")); + book.saving.connect (book_saving_cb); + } + + private string state_filename + { + owned get { return Path.build_filename (Environment.get_user_cache_dir (), "simple-scan", "state"); } + } + + private void load_state () + { + debug ("Loading state from %s", state_filename); + + var f = new KeyFile (); + try + { + f.load_from_file (state_filename, KeyFileFlags.NONE); + } + catch (Error e) + { + if (!(e is FileError.NOENT)) + warning ("Failed to load state: %s", e.message); + } + window_width = state_get_integer (f, "window", "width", 600); + if (window_width <= 0) + window_width = 600; + window_height = state_get_integer (f, "window", "height", 400); + if (window_height <= 0) + window_height = 400; + window_is_maximized = state_get_boolean (f, "window", "is-maximized"); + default_page_width = state_get_integer (f, "last-page", "width", 595); + default_page_height = state_get_integer (f, "last-page", "height", 842); + default_page_dpi = state_get_integer (f, "last-page", "dpi", 72); + switch (state_get_string (f, "last-page", "scan-direction", "top-to-bottom")) + { + default: + case "top-to-bottom": + default_page_scan_direction = ScanDirection.TOP_TO_BOTTOM; + break; + case "bottom-to-top": + default_page_scan_direction = ScanDirection.BOTTOM_TO_TOP; + break; + case "left-to-right": + default_page_scan_direction = ScanDirection.LEFT_TO_RIGHT; + break; + case "right-to-left": + default_page_scan_direction = ScanDirection.RIGHT_TO_LEFT; + break; + } + } + + private int state_get_integer (KeyFile f, string group_name, string key, int default = 0) + { + try + { + return f.get_integer (group_name, key); + } + catch + { + return default; + } + } + + private bool state_get_boolean (KeyFile f, string group_name, string key, bool default = false) + { + try + { + return f.get_boolean (group_name, key); + } + catch + { + return default; + } + } + + private string state_get_string (KeyFile f, string group_name, string key, string default = "") + { + try + { + return f.get_string (group_name, key); + } + catch + { + return default; + } + } + + private void save_state (bool force = false) + { + if (!force) + { + if (save_state_timeout != 0) + Source.remove (save_state_timeout); + save_state_timeout = Timeout.add (100, () => + { + save_state (true); + save_state_timeout = 0; + return false; + }); + return; + } + + debug ("Saving state to %s", state_filename); + + var f = new KeyFile (); + f.set_integer ("window", "width", window_width); + f.set_integer ("window", "height", window_height); + f.set_boolean ("window", "is-maximized", window_is_maximized); + f.set_integer ("last-page", "width", default_page_width); + f.set_integer ("last-page", "height", default_page_height); + f.set_integer ("last-page", "dpi", default_page_dpi); + switch (default_page_scan_direction) + { + case ScanDirection.TOP_TO_BOTTOM: + f.set_value ("last-page", "scan-direction", "top-to-bottom"); + break; + case ScanDirection.BOTTOM_TO_TOP: + f.set_value ("last-page", "scan-direction", "bottom-to-top"); + break; + case ScanDirection.LEFT_TO_RIGHT: + f.set_value ("last-page", "scan-direction", "left-to-right"); + break; + case ScanDirection.RIGHT_TO_LEFT: + f.set_value ("last-page", "scan-direction", "right-to-left"); + break; + } + try + { + FileUtils.set_contents (state_filename, f.to_data ()); + } + catch (Error e) + { + warning ("Failed to write state: %s", e.message); + } + } + + private void book_saving_cb (int page_number) + { + /* Prevent GUI from freezing */ + while (Gtk.events_pending ()) + Gtk.main_iteration (); + + var total = (int) book.n_pages; + var fraction = (page_number + 1.0) / total; + var complete = fraction == 1.0; + if (complete) + Timeout.add (500, () => { + progress_dialog.visible = false; + return false; + }); + var message = _("Saving page %d out of %d").printf (page_number + 1, total); + + progress_dialog.fraction = fraction; + progress_dialog.message = message; + } + + public void show_progress_dialog () + { + progress_dialog.visible = true; + } + + public void hide_progress_dialog () + { + progress_dialog.visible = false; + } + + public void show_error (string error_title, string error_text, bool change_scanner_hint) + { + have_error = true; + this.error_title = error_title; + this.error_text = error_text; + error_change_scanner_hint = change_scanner_hint; + update_info_bar (); + } + + public void start () + { + visible = true; + } +} + +private class ProgressBarDialog : Gtk.Window +{ + private Gtk.ProgressBar bar; + + public double fraction + { + get { return bar.fraction; } + set { bar.fraction = value; } + } + + public string message + { + get { return bar.text; } + set { bar.text = value; } + } + + public ProgressBarDialog (Gtk.ApplicationWindow parent, string title) + { + bar = new Gtk.ProgressBar (); + var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 5); + var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 5); + hbox.hexpand = true; + + bar.text = ""; + bar.show_text = true; + bar.set_size_request (225, 25); + set_size_request (250, 50); + + vbox.pack_start (bar, true, false, 0); + hbox.pack_start (vbox, true, false, 0); + add (hbox); + this.title = title; + + transient_for = parent; + set_position (Gtk.WindowPosition.CENTER_ON_PARENT); + modal = true; + resizable = false; + + hbox.visible = true; + vbox.visible = true; + bar.visible = true; + } +} + +// FIXME: Duplicated from simple-scan.vala +private string? get_temporary_filename (string prefix, string extension) +{ + /* NOTE: I'm not sure if this is a 100% safe strategy to use g_file_open_tmp(), close and + * use the filename but it appears to work in practise */ + + var filename = "%sXXXXXX.%s".printf (prefix, extension); + string path; + try + { + var fd = FileUtils.open_tmp (filename, out path); + Posix.close (fd); + } + catch (Error e) + { + warning ("Error saving email attachment: %s", e.message); + return null; + } + + return path; +} + +private class PageIcon : Gtk.DrawingArea +{ + private string text; + private double r; + private double g; + private double b; + private const int MINIMUM_WIDTH = 20; + + public PageIcon (string text, double r = 1.0, double g = 1.0, double b = 1.0) + { + this.text = text; + this.r = r; + this.g = g; + this.b = b; + } + + public override void get_preferred_width (out int minimum_width, out int natural_width) + { + minimum_width = natural_width = MINIMUM_WIDTH; + } + + public override void get_preferred_height (out int minimum_height, out int natural_height) + { + minimum_height = natural_height = (int) Math.round (MINIMUM_WIDTH * Math.SQRT2); + } + + public override void get_preferred_height_for_width (int width, out int minimum_height, out int natural_height) + { + minimum_height = natural_height = (int) (width * Math.SQRT2); + } + + public override void get_preferred_width_for_height (int height, out int minimum_width, out int natural_width) + { + minimum_width = natural_width = (int) (height / Math.SQRT2); + } + + public override bool draw (Cairo.Context c) + { + var w = get_allocated_width (); + var h = get_allocated_height (); + if (w * Math.SQRT2 > h) + w = (int) Math.round (h / Math.SQRT2); + else + h = (int) Math.round (w * Math.SQRT2); + + c.translate ((get_allocated_width () - w) / 2, (get_allocated_height () - h) / 2); + + c.rectangle (0.5, 0.5, w - 1, h - 1); + + c.set_source_rgb (r, g, b); + c.fill_preserve (); + + c.set_line_width (1.0); + c.set_source_rgb (0.0, 0.0, 0.0); + c.stroke (); + + Cairo.TextExtents extents; + c.text_extents (text, out extents); + c.translate ((w - extents.width) * 0.5 - 0.5, (h + extents.height) * 0.5 - 0.5); + c.show_text (text); + + return true; + } +} |