/* * Copyright (C) 2009-2011 Canonical Ltd. * Author: Robert Ancell * * 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. */ public class SimpleScan { private const int DEFAULT_TEXT_DPI = 150; private const int DEFAULT_PHOTO_DPI = 300; private Settings settings; private Gtk.Builder builder; private Gtk.Window window; private Gtk.VBox 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; private Gtk.MenuItem page_move_left_menuitem; private Gtk.MenuItem page_move_right_menuitem; private Gtk.MenuItem page_delete_menuitem; private Gtk.MenuItem crop_rotate_menuitem; private Gtk.MenuItem save_menuitem; private Gtk.MenuItem save_as_menuitem; private Gtk.ToolButton save_toolbutton; private Gtk.MenuItem stop_menuitem; private Gtk.ToolButton stop_toolbutton; private Gtk.RadioMenuItem text_toolbar_menuitem; private Gtk.RadioMenuItem text_menu_menuitem; private Gtk.RadioMenuItem photo_toolbar_menuitem; private Gtk.RadioMenuItem photo_menu_menuitem; private Gtk.Dialog authorize_dialog; private Gtk.Label authorize_label; private Gtk.Entry username_entry; private Gtk.Entry password_entry; private Gtk.Dialog preferences_dialog; private Gtk.ComboBox device_combo; private Gtk.ComboBox text_dpi_combo; private Gtk.ComboBox photo_dpi_combo; private Gtk.ComboBox page_side_combo; private Gtk.ComboBox paper_size_combo; private Gtk.ListStore device_model; private Gtk.ListStore text_dpi_model; private Gtk.ListStore photo_dpi_model; private Gtk.ListStore page_side_model; private Gtk.ListStore paper_size_model; private bool setting_devices; private bool user_selected_device; private Gtk.FileChooserDialog? save_dialog; private bool have_error; private string error_title; private string error_text; private bool error_change_scanner_hint; private Book book; private string? book_uri = null; 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"; private string default_file_name = _("Scanned Document.pdf"); private bool scanning = false; private int window_width; private int window_height; private bool window_is_maximized; public signal void start_scan (string? device, ScanOptions options); public signal void stop_scan (); public signal void email (string profile); public signal void quit (); public SimpleScan () { book = new Book (); book.page_removed.connect (page_removed_cb); book.page_added.connect (page_added_cb); settings = new Settings ("org.gnome.SimpleScan"); load (); } 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 (window, Gtk.DialogFlags.MODAL, Gtk.MessageType.WARNING, Gtk.ButtonsType.NONE, "%s", error_title); dialog.add_button (Gtk.Stock.CLOSE, 0); dialog.format_secondary_text ("%s", error_text); dialog.destroy (); } public void set_default_file_name (string default_file_name) { this.default_file_name = default_file_name; } 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.set_text (""); password_entry.set_text (""); authorize_label.set_text (description); authorize_dialog.show (); authorize_dialog.run (); authorize_dialog.hide (); username = username_entry.get_text (); password = password_entry.get_text (); } [CCode (cname = "G_MODULE_EXPORT device_combo_changed_cb", instance_pos = -1)] public void device_combo_changed_cb (Gtk.Widget widget) { if (setting_devices) return; user_selected_device = true; } 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 = Gtk.Stock.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 = Gtk.Stock.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.hide (); return; } info_bar.set_message_type (type); info_bar_image.set_from_stock (image_id, Gtk.IconSize.DIALOG); var message = "%s\n\n%s".printf (title, text); info_bar_label.set_markup (message); info_bar_close_button.set_visible (show_close_button); info_bar_change_scanner_button.set_visible (show_change_scanner_button); info_bar.show (); } public void set_scan_devices (List 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.get_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 string? get_selected_device () { 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; } public void set_selected_device (string device) { Gtk.TreeIter iter; if (!find_scan_device (device, out iter)) return; device_combo.set_active_iter (iter); user_selected_device = true; } private void add_default_page () { var page = book.append_page (default_page_width, default_page_height, default_page_dpi, default_page_scan_direction); book_view.select_page (page); } private void on_file_type_changed (Gtk.TreeSelection selection) { Gtk.TreeModel model; Gtk.TreeIter iter; if (!selection.get_selected (out model, out iter)) return; string extension; model.get (iter, 1, out extension, -1); 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 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..."), window, Gtk.FileChooserAction.SAVE, Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL, Gtk.Stock.SAVE, Gtk.ResponseType.ACCEPT, null); save_dialog.set_do_overwrite_confirmation (true); save_dialog.set_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_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_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.set_spacing (5); save_dialog.set_extra_widget (expander); string extension = ""; var index = default_file_name.last_index_of_char ('.'); if (index >= 0) extension = default_file_name.slice (0, 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.set_headers_visible (false); file_type_view.set_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 (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) uri = save_dialog.get_uri (); settings.set_string ("save-directory", save_dialog.get_current_folder ()); 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"; try { book.save (format, file); } catch (Error e) { 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.set_needs_saving (false); return true; } private bool prompt_to_save (string title, string discard_label) { if (!book.get_needs_saving ()) return true; var dialog = new Gtk.MessageDialog (window, 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 (Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL); dialog.add_button (Gtk.Stock.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.set_needs_saving (false); save_as_menuitem.set_sensitive (false); } [CCode (cname = "G_MODULE_EXPORT new_button_clicked_cb", instance_pos = -1)] public void new_button_clicked_cb (Gtk.Widget widget) { 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; clear_document (); } private void set_document_hint (string document_hint) { this.document_hint = document_hint; if (document_hint == "text") { text_toolbar_menuitem.set_active (true); text_menu_menuitem.set_active (true); } else if (document_hint == "photo") { photo_toolbar_menuitem.set_active (true); photo_menu_menuitem.set_active (true); } } [CCode (cname = "G_MODULE_EXPORT text_menuitem_toggled_cb", instance_pos = -1)] public void text_menuitem_toggled_cb (Gtk.CheckMenuItem widget) { if (widget.get_active ()) set_document_hint ("text"); } [CCode (cname = "G_MODULE_EXPORT photo_menuitem_toggled_cb", instance_pos = -1)] public void photo_menuitem_toggled_cb (Gtk.CheckMenuItem widget) { if (widget.get_active ()) set_document_hint ("photo"); } 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; 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 get_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); return options; } [CCode (cname = "G_MODULE_EXPORT scan_button_clicked_cb", instance_pos = -1)] public void scan_button_clicked_cb (Gtk.Widget widget) { var options = get_scan_options (); options.type = ScanType.SINGLE; start_scan (get_selected_device (), options); } [CCode (cname = "G_MODULE_EXPORT stop_scan_button_clicked_cb", instance_pos = -1)] public void stop_scan_button_clicked_cb (Gtk.Widget widget) { stop_scan (); } [CCode (cname = "G_MODULE_EXPORT continuous_scan_button_clicked_cb", instance_pos = -1)] public void continuous_scan_button_clicked_cb (Gtk.Widget widget) { if (scanning) stop_scan (); else { var options = get_scan_options (); options.type = get_page_side (); start_scan (get_selected_device (), options); } } [CCode (cname = "G_MODULE_EXPORT preferences_button_clicked_cb", instance_pos = -1)] public void preferences_button_clicked_cb (Gtk.Widget widget) { preferences_dialog.present (); } [CCode (cname = "G_MODULE_EXPORT preferences_dialog_delete_event_cb", instance_pos = -1)] public bool preferences_dialog_delete_event_cb (Gtk.Widget widget) { return true; } [CCode (cname = "G_MODULE_EXPORT preferences_dialog_response_cb", instance_pos = -1)] public void preferences_dialog_response_cb (Gtk.Widget widget, int response_id) { preferences_dialog.hide (); } private void update_page_menu () { var page = book_view.get_selected (); if (page == null) { page_move_left_menuitem.set_sensitive (false); page_move_right_menuitem.set_sensitive (false); } else { var index = book.get_page_index (page); page_move_left_menuitem.set_sensitive (index > 0); page_move_right_menuitem.set_sensitive (index < book.get_n_pages () - 1); } } private void page_selected_cb (BookView view, Page? page) { if (page == null) return; updating_page_menu = true; update_page_menu (); string? name = null; if (page.has_crop ()) { // FIXME: Make more generic, move into page-size.c and reuse var crop_name = page.get_named_crop (); if (crop_name != null) { if (crop_name == "A4") name = "a4_menuitem"; else if (crop_name == "A5") name = "a5_menuitem"; else if (crop_name == "A6") name = "a6_menuitem"; else if (crop_name == "letter") name = "letter_menuitem"; else if (crop_name == "legal") name = "legal_menuitem"; else if (crop_name == "4x6") name = "4x6_menuitem"; } else name = "custom_crop_menuitem"; } else name = "no_crop_menuitem"; var menuitem = (Gtk.RadioMenuItem) builder.get_object (name); menuitem.set_active (true); var toolbutton = (Gtk.ToggleToolButton) builder.get_object ("crop_toolbutton"); toolbutton.set_active (page.has_crop ()); updating_page_menu = false; } // 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 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", 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 (window.get_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) { var menu = (Gtk.Menu) builder.get_object ("page_menu"); menu.popup (null, null, null, 3, Gtk.get_current_event_time()); } [CCode (cname = "G_MODULE_EXPORT rotate_left_button_clicked_cb", instance_pos = -1)] public void rotate_left_button_clicked_cb (Gtk.Widget widget) { if (updating_page_menu) return; var page = book_view.get_selected (); if (page != null) page.rotate_left (); } [CCode (cname = "G_MODULE_EXPORT rotate_right_button_clicked_cb", instance_pos = -1)] public void rotate_right_button_clicked_cb (Gtk.Widget widget) { if (updating_page_menu) return; var page = book_view.get_selected (); if (page != null) page.rotate_right (); } private void set_crop (string? crop_name) { crop_rotate_menuitem.set_sensitive (crop_name != null); if (updating_page_menu) return; var page = book_view.get_selected (); if (page == null) return; if (crop_name == null) { page.set_no_crop (); return; } else if (crop_name == "custom") { var width = page.get_width (); var height = page.get_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); } [CCode (cname = "G_MODULE_EXPORT no_crop_menuitem_toggled_cb", instance_pos = -1)] public void no_crop_menuitem_toggled_cb (Gtk.CheckMenuItem widget) { if (widget.get_active ()) set_crop (null); } [CCode (cname = "G_MODULE_EXPORT custom_crop_menuitem_toggled_cb", instance_pos = -1)] public void custom_crop_menuitem_toggled_cb (Gtk.CheckMenuItem widget) { if (widget.get_active ()) set_crop ("custom"); } [CCode (cname = "G_MODULE_EXPORT crop_toolbutton_toggled_cb", instance_pos = -1)] public void crop_toolbutton_toggled_cb (Gtk.ToggleToolButton widget) { if (updating_page_menu) return; Gtk.RadioMenuItem menuitem; if (widget.get_active ()) menuitem = (Gtk.RadioMenuItem) builder.get_object ("custom_crop_menuitem"); else menuitem = (Gtk.RadioMenuItem) builder.get_object ("no_crop_menuitem"); menuitem.set_active (true); } [CCode (cname = "G_MODULE_EXPORT four_by_six_menuitem_toggled_cb", instance_pos = -1)] public void four_by_six_menuitem_toggled_cb (Gtk.CheckMenuItem widget) { if (widget.get_active ()) set_crop ("4x6"); } [CCode (cname = "G_MODULE_EXPORT legal_menuitem_toggled_cb", instance_pos = -1)] public void legal_menuitem_toggled_cb (Gtk.CheckMenuItem widget) { if (widget.get_active ()) set_crop ("legal"); } [CCode (cname = "G_MODULE_EXPORT letter_menuitem_toggled_cb", instance_pos = -1)] public void letter_menuitem_toggled_cb (Gtk.CheckMenuItem widget) { if (widget.get_active ()) set_crop ("letter"); } [CCode (cname = "G_MODULE_EXPORT a6_menuitem_toggled_cb", instance_pos = -1)] public void a6_menuitem_toggled_cb (Gtk.CheckMenuItem widget) { if (widget.get_active ()) set_crop ("A6"); } [CCode (cname = "G_MODULE_EXPORT a5_menuitem_toggled_cb", instance_pos = -1)] public void a5_menuitem_toggled_cb (Gtk.CheckMenuItem widget) { if (widget.get_active ()) set_crop ("A5"); } [CCode (cname = "G_MODULE_EXPORT a4_menuitem_toggled_cb", instance_pos = -1)] public void a4_menuitem_toggled_cb (Gtk.CheckMenuItem widget) { if (widget.get_active ()) set_crop ("A4"); } [CCode (cname = "G_MODULE_EXPORT crop_rotate_menuitem_activate_cb", instance_pos = -1)] public void crop_rotate_menuitem_activate_cb (Gtk.Widget widget) { var page = book_view.get_selected (); if (page == null) return; page.rotate_crop (); } [CCode (cname = "G_MODULE_EXPORT page_move_left_menuitem_activate_cb", instance_pos = -1)] public void page_move_left_menuitem_activate_cb (Gtk.Widget widget) { var page = book_view.get_selected (); var index = book.get_page_index (page); if (index > 0) book.move_page (page, index - 1); update_page_menu (); } [CCode (cname = "G_MODULE_EXPORT page_move_right_menuitem_activate_cb", instance_pos = -1)] public void page_move_right_menuitem_activate_cb (Gtk.Widget widget) { var page = book_view.get_selected (); var index = book.get_page_index (page); if (index < book.get_n_pages () - 1) book.move_page (page, book.get_page_index (page) + 1); update_page_menu (); } [CCode (cname = "G_MODULE_EXPORT page_delete_menuitem_activate_cb", instance_pos = -1)] public void page_delete_menuitem_activate_cb (Gtk.Widget widget) { book_view.get_book ().delete_page (book_view.get_selected ()); } [CCode (cname = "G_MODULE_EXPORT save_file_button_clicked_cb", instance_pos = -1)] public void save_file_button_clicked_cb (Gtk.Widget widget) { save_document (false); } [CCode (cname = "G_MODULE_EXPORT save_as_file_button_clicked_cb", instance_pos = -1)] public void save_as_file_button_clicked_cb (Gtk.Widget widget) { 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.get_dpi (), print_context.get_dpi_y () / page.get_dpi ()); var image = page.get_image (true); Gdk.cairo_set_source_pixbuf (context, image, 0, 0); context.paint (); } [CCode (cname = "G_MODULE_EXPORT email_button_clicked_cb", instance_pos = -1)] public void email_button_clicked_cb (Gtk.Widget widget) { email (document_hint); } [CCode (cname = "G_MODULE_EXPORT print_button_clicked_cb", instance_pos = -1)] public void print_button_clicked_cb (Gtk.Widget widget) { var print = new Gtk.PrintOperation (); print.set_n_pages ((int) book.get_n_pages ()); print.draw_page.connect (draw_page); try { print.run (Gtk.PrintOperationAction.PRINT_DIALOG, window); } catch (Error e) { warning ("Error printing: %s", e.message); } } [CCode (cname = "G_MODULE_EXPORT help_contents_menuitem_activate_cb", instance_pos = -1)] public void help_contents_menuitem_activate_cb (Gtk.Widget widget) { try { Gtk.show_uri (window.get_screen (), "ghelp: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); } } [CCode (cname = "G_MODULE_EXPORT about_menuitem_activate_cb", instance_pos = -1)] public void about_menuitem_activate_cb (Gtk.Widget widget) { string[] authors = { "Robert Ancell " }; /* 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 ."); /* Title of about dialog */ string title = _("About Simple Scan"); /* Description of program */ string description = _("Simple document scanning tool"); Gtk.show_about_dialog (window, "title", title, "program-name", "Simple Scan", "version", Config.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); } 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; var device = get_selected_device (); int paper_width = 0, paper_height = 0; get_paper_size (out paper_width, out paper_height); if (device != null) settings.set_string ("selected-device", device); settings.set_string ("document-type", document_hint); settings.set_int ("text-dpi", get_text_dpi ()); settings.set_int ("photo-dpi", get_photo_dpi ()); settings.set_enum ("page-side", get_page_side ()); settings.set_int ("paper-width", paper_width); settings.set_int ("paper-height", paper_height); settings.set_int ("window-width", window_width); settings.set_int ("window-height", window_height); settings.set_boolean ("window-is-maximized", window_is_maximized); settings.set_enum ("scan-direction", default_page_scan_direction); settings.set_int ("page-width", default_page_width); settings.set_int ("page-height", default_page_height); settings.set_int ("page-dpi", default_page_dpi); quit (); return true; } [CCode (cname = "G_MODULE_EXPORT quit_menuitem_activate_cb", instance_pos = -1)] public void quit_menuitem_activate_cb (Gtk.Widget widget) { on_quit (); } [CCode (cname = "G_MODULE_EXPORT simple_scan_window_configure_event_cb", instance_pos = -1)] public 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; } 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 (); } } [CCode (cname = "G_MODULE_EXPORT simple_scan_window_window_state_event_cb", instance_pos = -1)] public 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; return false; } [CCode (cname = "G_MODULE_EXPORT window_delete_event_cb", instance_pos = -1)] public bool window_delete_event_cb (Gtk.Widget widget, Gdk.Event event) { return !on_quit (); } private void page_size_changed_cb (Page page) { default_page_width = page.get_width (); default_page_height = page.get_height (); default_page_dpi = page.get_dpi (); } private void page_scan_direction_changed_cb (Page page) { default_page_scan_direction = page.get_scan_direction (); } private void page_added_cb (Book book, Page page) { default_page_width = page.get_width (); default_page_height = page.get_height (); default_page_dpi = page.get_dpi (); default_page_scan_direction = page.get_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 page_removed_cb (Book book, Page page) { /* If this is the last page add a new blank one */ if (book.get_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 = (Gtk.ListStore) combo.get_model (); 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.set_sensitive (book.get_needs_saving ()); save_toolbutton.set_sensitive (book.get_needs_saving ()); if (book.get_needs_saving ()) save_as_menuitem.set_sensitive (true); } private void load () { Gtk.IconTheme.get_default ().append_search_path (Config.ICON_DIR); Gtk.Window.set_default_icon_name ("scanner"); builder = new Gtk.Builder (); var filename = Path.build_filename (Config.UI_DIR, "simple-scan.ui", null); try { builder.add_from_file (filename); } catch (Error e) { critical ("Unable to load UI %s: %s\n", filename, e.message); show_error_dialog (/* Title of dialog when cannot load required files */ _("Files missing"), /* Description in dialog when cannot load required files */ _("Please check your installation")); Posix.exit (Posix.EXIT_FAILURE); } builder.connect_signals (this); window = (Gtk.Window) builder.get_object ("simple_scan_window"); main_vbox = (Gtk.VBox) builder.get_object ("main_vbox"); page_move_left_menuitem = (Gtk.MenuItem) builder.get_object ("page_move_left_menuitem"); page_move_right_menuitem = (Gtk.MenuItem) builder.get_object ("page_move_right_menuitem"); page_delete_menuitem = (Gtk.MenuItem) builder.get_object ("page_delete_menuitem"); crop_rotate_menuitem = (Gtk.MenuItem) builder.get_object ("crop_rotate_menuitem"); save_menuitem = (Gtk.MenuItem) builder.get_object ("save_menuitem"); save_as_menuitem = (Gtk.MenuItem) builder.get_object ("save_as_menuitem"); save_toolbutton = (Gtk.ToolButton) builder.get_object ("save_toolbutton"); stop_menuitem = (Gtk.MenuItem) builder.get_object ("stop_scan_menuitem"); stop_toolbutton = (Gtk.ToolButton) builder.get_object ("stop_toolbutton"); text_toolbar_menuitem = (Gtk.RadioMenuItem) builder.get_object ("text_toolbutton_menuitem"); text_menu_menuitem = (Gtk.RadioMenuItem) builder.get_object ("text_menuitem"); photo_toolbar_menuitem = (Gtk.RadioMenuItem) builder.get_object ("photo_toolbutton_menuitem"); photo_menu_menuitem = (Gtk.RadioMenuItem) builder.get_object ("photo_menuitem"); authorize_dialog = (Gtk.Dialog) builder.get_object ("authorize_dialog"); authorize_label = (Gtk.Label) builder.get_object ("authorize_label"); username_entry = (Gtk.Entry) builder.get_object ("username_entry"); password_entry = (Gtk.Entry) builder.get_object ("password_entry"); preferences_dialog = (Gtk.Dialog) builder.get_object ("preferences_dialog"); device_combo = (Gtk.ComboBox) builder.get_object ("device_combo"); device_model = (Gtk.ListStore) device_combo.get_model (); text_dpi_combo = (Gtk.ComboBox) builder.get_object ("text_dpi_combo"); text_dpi_model = (Gtk.ListStore) text_dpi_combo.get_model (); photo_dpi_combo = (Gtk.ComboBox) builder.get_object ("photo_dpi_combo"); photo_dpi_model = (Gtk.ListStore) photo_dpi_combo.get_model (); page_side_combo = (Gtk.ComboBox) builder.get_object ("page_side_combo"); page_side_model = (Gtk.ListStore) page_side_combo.get_model (); paper_size_combo = (Gtk.ComboBox) builder.get_object ("paper_size_combo"); paper_size_model = (Gtk.ListStore) paper_size_combo.get_model (); /* 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.HBox (false, 12); var content_area = (Gtk.Container) info_bar.get_content_area (); content_area.add (hbox); hbox.show (); info_bar_image = new Gtk.Image.from_stock (Gtk.Stock.DIALOG_WARNING, Gtk.IconSize.DIALOG); hbox.pack_start (info_bar_image, false, true, 0); info_bar_image.show (); 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.show (); info_bar_close_button = (Gtk.Button) info_bar.add_button (Gtk.Stock.CLOSE, Gtk.ResponseType.CLOSE); info_bar_change_scanner_button = (Gtk.Button) info_bar.add_button (/* Button in error infobar to open preferences dialog and change scanner */ _("Change _Scanner"), 1); 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); dpi = settings.get_int ("photo-dpi"); if (dpi <= 0) dpi = DEFAULT_PHOTO_DPI; set_dpi_combo (photo_dpi_combo, DEFAULT_PHOTO_DPI, 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")); 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); 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.set_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.show (); /* Find default page details */ default_page_scan_direction = (ScanDirection) settings.get_enum ("scan-direction"); default_page_width = settings.get_int ("page-width"); if (default_page_width <= 0) default_page_width = 595; default_page_height = settings.get_int ("page-height"); if (default_page_height <= 0) default_page_height = 842; default_page_dpi = settings.get_int ("page-dpi"); if (default_page_dpi <= 0) default_page_dpi = 72; /* Restore window size */ window_width = settings.get_int ("window-width"); if (window_width <= 0) window_width = 600; window_height = settings.get_int ("window-height"); if (window_height <= 0) window_height = 400; debug ("Restoring window to %dx%d pixels", window_width, window_height); window.set_default_size (window_width, window_height); window_is_maximized = settings.get_boolean ("window-is-maximized"); if (window_is_maximized) { debug ("Restoring window to maximized"); window.maximize (); } if (book.get_n_pages () == 0) add_default_page (); book.set_needs_saving (false); book.needs_saving_changed.connect (needs_saving_cb); } public Book get_book () { return book; } public void set_selected_page (Page page) { book_view.select_page (page); } public Page get_selected_page () { return book_view.get_selected (); } public void set_scanning (bool scanning) { this.scanning = scanning; page_delete_menuitem.set_sensitive (!scanning); stop_menuitem.set_sensitive (scanning); stop_toolbutton.set_sensitive (scanning); } 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 () { window.show (); } }