summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app-window.vala1518
-rw-r--r--src/authorize-dialog.vala71
-rw-r--r--src/autosave-manager.vala10
-rw-r--r--src/book-view.vala176
-rw-r--r--src/book.vala11
-rw-r--r--src/drivers-dialog.vala200
-rw-r--r--src/meson.build6
-rw-r--r--src/page-icon.vala135
-rw-r--r--src/page-texture.vala663
-rw-r--r--src/page-view.vala503
-rw-r--r--src/page.vala30
-rw-r--r--src/preferences-dialog.vala229
-rw-r--r--src/reorder-pages-dialog.vala60
-rw-r--r--src/simple-scan.vala21
14 files changed, 1852 insertions, 1781 deletions
diff --git a/src/app-window.vala b/src/app-window.vala
index c4f9af7..402065a 100644
--- a/src/app-window.vala
+++ b/src/app-window.vala
@@ -14,11 +14,13 @@ private const int DEFAULT_TEXT_DPI = 150;
private const int DEFAULT_PHOTO_DPI = 300;
[GtkTemplate (ui = "/org/gnome/SimpleScan/ui/app-window.ui")]
-public class AppWindow : Hdy.ApplicationWindow
+public class AppWindow : Adw.ApplicationWindow
{
private const GLib.ActionEntry[] action_entries =
{
{ "new_document", new_document_cb },
+ { "scan_type", scan_type_action_cb, "s", "'single'"},
+ { "document_hint", document_hint_action_cb, "s", "'text'"},
{ "scan_single", scan_single_cb },
{ "scan_adf", scan_adf_cb },
{ "scan_batch", scan_batch_cb },
@@ -30,7 +32,7 @@ public class AppWindow : Hdy.ApplicationWindow
{ "copy_page", copy_page_cb },
{ "delete_page", delete_page_cb },
{ "reorder", reorder_document_cb },
- { "save", save_document_activate_cb },
+ { "save", save_document_cb },
{ "email", email_document_cb },
{ "print", print_document_cb },
{ "preferences", preferences_cb },
@@ -38,6 +40,16 @@ public class AppWindow : Hdy.ApplicationWindow
{ "about", about_cb },
{ "quit", quit_cb }
};
+
+ private GLib.SimpleAction scan_type_action;
+ private GLib.SimpleAction document_hint_action;
+
+ private GLib.SimpleAction delete_page_action;
+ private GLib.SimpleAction page_move_left_action;
+ private GLib.SimpleAction page_move_right_action;
+ private GLib.SimpleAction copy_to_clipboard_action;
+
+ private CropActions crop_actions;
private Settings settings;
private ScanType scan_type = ScanType.SINGLE;
@@ -48,52 +60,21 @@ public class AppWindow : Hdy.ApplicationWindow
private bool user_selected_device;
[GtkChild]
- private unowned Hdy.HeaderBar header_bar;
- [GtkChild]
- private unowned Gtk.Menu page_menu;
+ private unowned Gtk.PopoverMenu page_menu;
[GtkChild]
private unowned Gtk.Stack stack;
[GtkChild]
- private unowned Hdy.StatusPage status_page;
+ private unowned Adw.StatusPage status_page;
[GtkChild]
private unowned Gtk.Label status_secondary_label;
- [GtkChild]
- private unowned Gtk.ListStore device_model;
+ private ListStore device_model;
[GtkChild]
private unowned Gtk.Box device_buttons_box;
[GtkChild]
- private unowned Gtk.ComboBox device_combo;
+ private unowned Gtk.DropDown device_drop_down;
[GtkChild]
private unowned Gtk.Box main_vbox;
[GtkChild]
- private unowned Gtk.RadioMenuItem custom_crop_menuitem;
- [GtkChild]
- private unowned Gtk.RadioMenuItem a3_menuitem;
- [GtkChild]
- private unowned Gtk.RadioMenuItem a4_menuitem;
- [GtkChild]
- private unowned Gtk.RadioMenuItem a5_menuitem;
- [GtkChild]
- private unowned Gtk.RadioMenuItem a6_menuitem;
- [GtkChild]
- private unowned Gtk.RadioMenuItem letter_menuitem;
- [GtkChild]
- private unowned Gtk.RadioMenuItem legal_menuitem;
- [GtkChild]
- private unowned Gtk.RadioMenuItem four_by_six_menuitem;
- [GtkChild]
- private unowned Gtk.RadioMenuItem no_crop_menuitem;
- [GtkChild]
- private unowned Gtk.MenuItem page_move_left_menuitem;
- [GtkChild]
- private unowned Gtk.MenuItem page_move_right_menuitem;
- [GtkChild]
- private unowned Gtk.MenuItem page_delete_menuitem;
- [GtkChild]
- private unowned Gtk.MenuItem crop_rotate_menuitem;
- [GtkChild]
- private unowned Gtk.MenuItem copy_to_clipboard_menuitem;
- [GtkChild]
private unowned Gtk.Button save_button;
[GtkChild]
private unowned Gtk.Button stop_button;
@@ -101,23 +82,11 @@ public class AppWindow : Hdy.ApplicationWindow
private unowned Gtk.Button scan_button;
[GtkChild]
private unowned Gtk.ActionBar action_bar;
- private Gtk.ToggleButton crop_button;
- private Gtk.Button delete_button;
-
- [GtkChild]
- private unowned Gtk.Image scan_options_image;
- [GtkChild]
- private unowned Gtk.Image scan_hint_image;
[GtkChild]
- private unowned Gtk.RadioButton scan_single_radio;
- [GtkChild]
- private unowned Gtk.RadioButton scan_adf_radio;
- [GtkChild]
- private unowned Gtk.RadioButton scan_batch_radio;
- [GtkChild]
- private unowned Gtk.RadioButton text_radio;
+ private unowned Gtk.ToggleButton crop_button;
+
[GtkChild]
- private unowned Gtk.RadioButton photo_radio;
+ private unowned Adw.ButtonContent scan_button_content;
[GtkChild]
private unowned Gtk.MenuButton menu_button;
@@ -156,8 +125,8 @@ public class AppWindow : Hdy.ApplicationWindow
{
scanning_ = value;
stack.set_visible_child_name ("document");
- page_delete_menuitem.sensitive = !value;
- delete_button.sensitive = !value;
+
+ delete_page_action.set_enabled (!value);
scan_button.visible = !value;
stop_button.visible = value;
}
@@ -191,22 +160,32 @@ public class AppWindow : Hdy.ApplicationWindow
public signal void start_scan (string? device, ScanOptions options);
public signal void stop_scan ();
public signal void redetect ();
+
+ static string get_device_label (ScanDevice device) {
+ return device.label;
+ }
public AppWindow ()
{
settings = new Settings ("org.gnome.SimpleScan");
- var renderer = new Gtk.CellRendererText ();
- renderer.set_property ("xalign", 0.5);
- device_combo.pack_start (renderer, true);
- device_combo.add_attribute (renderer, "text", 1);
+ device_model = new ListStore (typeof (ScanDevice));
+ device_drop_down.model = device_model;
+ device_drop_down.expression = new Gtk.CClosureExpression (
+ typeof (string),
+ null,
+ {},
+ (Callback) get_device_label,
+ null,
+ null
+ );
book = new Book ();
book.page_added.connect (page_added_cb);
book.reordered.connect (reordered_cb);
book.page_removed.connect (page_removed_cb);
book.changed.connect (book_changed_cb);
-
+
load ();
clear_document ();
@@ -221,29 +200,22 @@ public class AppWindow : Hdy.ApplicationWindow
public 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_markup ("%s", error_text);
- dialog.run ();
- dialog.destroy ();
+ var dialog = new Adw.MessageDialog (this,
+ error_title,
+ error_text);
+ dialog.add_response ("close", _("_Close"));
+ dialog.set_response_appearance ("close", Adw.ResponseAppearance.SUGGESTED);
+ dialog.show ();
}
- public void authorize (string resource, out string username, out string password)
+ public async AuthorizeDialogResponse authorize (string resource)
{
/* 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);
- var authorize_dialog = new AuthorizeDialog (description);
- authorize_dialog.visible = true;
+ var authorize_dialog = new AuthorizeDialog (this, description);
authorize_dialog.transient_for = this;
- authorize_dialog.run ();
- authorize_dialog.destroy ();
- username = authorize_dialog.get_username ();
- password = authorize_dialog.get_password ();
+ return yield authorize_dialog.open ();
}
private void update_scan_status ()
@@ -265,7 +237,7 @@ public class AppWindow : Hdy.ApplicationWindow
status_secondary_label.visible = false;
device_buttons_box.visible = true;
device_buttons_box.sensitive = true;
- device_combo.sensitive = true;
+ device_drop_down.sensitive = true;
}
else if (this.missing_driver != null)
{
@@ -285,7 +257,7 @@ public class AppWindow : Hdy.ApplicationWindow
status_secondary_label.visible = true;
device_buttons_box.visible = true;
device_buttons_box.sensitive = true;
- device_combo.sensitive = false; // We would like to be refresh button to be active
+ device_drop_down.sensitive = false; // We would like to be refresh button to be active
}
}
@@ -294,83 +266,31 @@ public class AppWindow : Hdy.ApplicationWindow
have_devices = true;
this.missing_driver = missing_driver;
+ // Ignore selected events during this code, to prevent updating "selected-device"
setting_devices = true;
-
- /* If the user hasn't chosen a scanner choose the best available one */
- var have_selection = false;
- if (user_selected_device)
- have_selection = device_combo.active >= 0;
-
- /* Add new devices */
- int index = 0;
- Gtk.TreeIter iter;
- foreach (var device in devices)
+
{
- int n_delete = -1;
+ /*
+ Technically this could be optimized, but:
+ a) for the typical amount of scanners that would probably be overkill
+ b) we rescan only on user action so this is rarely called
+ */
+ device_model.remove_all ();
- /* 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);
-#if VALA_0_36
- device_model.remove (ref iter);
-#else
- device_model.remove (iter);
-#endif
- }
- }
- else
+ /* Add new devices */
+ foreach (var device in devices)
{
- device_model.insert (out iter, index);
- device_model.set (iter, 0, device.name, 1, device.label, -1);
+ device_model.append (device);
}
- index++;
- }
-
- /* Remove any remaining devices */
- while (device_model.iter_nth_child (out iter, null, index))
-#if VALA_0_36
- device_model.remove (ref iter);
-#else
- device_model.remove (iter);
-#endif
- /* Select the previously selected device or the first available device */
- if (!have_selection)
- {
- var device = settings.get_string ("selected-device");
- if (device != null && find_scan_device (device, out iter))
- device_combo.set_active_iter (iter);
+ /* Select the previously selected device or the first available device */
+ var device_name = settings.get_string ("selected-device");
+
+ uint position = 0;
+ if (device_name != null && find_device_by_name (device_name, out position) != null)
+ device_drop_down.selected = position;
else
- device_combo.set_active (0);
+ device_drop_down.selected = 0;
}
setting_devices = false;
@@ -378,29 +298,40 @@ public class AppWindow : Hdy.ApplicationWindow
update_scan_status ();
}
- private bool prompt_to_load_autosaved_book ()
+ private async bool prompt_to_load_autosaved_book ()
{
- var dialog = new Gtk.MessageDialog (this,
- Gtk.DialogFlags.MODAL,
- Gtk.MessageType.QUESTION,
- Gtk.ButtonsType.YES_NO,
+ var dialog = new Adw.MessageDialog (this,
+ "",
/* Contents of dialog that shows if autosaved book should be loaded. */
_("An autosaved book exists. Do you want to open it?"));
- dialog.set_default_response(Gtk.ResponseType.YES);
- var response = dialog.run ();
- dialog.destroy ();
- return response == Gtk.ResponseType.YES;
+
+ dialog.add_response ("no", _("_No"));
+ dialog.add_response ("yes", _("_Yes"));
+
+ dialog.set_response_appearance ("no", Adw.ResponseAppearance.DESTRUCTIVE);
+ dialog.set_response_appearance ("yes", Adw.ResponseAppearance.SUGGESTED);
+
+ dialog.set_default_response("yes");
+ dialog.show ();
+
+ string response = "yes";
+
+ SourceFunc callback = prompt_to_load_autosaved_book.callback;
+ dialog.response.connect((res) => {
+ response = res;
+ callback();
+ });
+
+ yield;
+
+ return response == "yes";
}
private string? get_selected_device ()
{
- Gtk.TreeIter iter;
-
- if (device_combo.get_active_iter (out iter))
+ if (device_drop_down.selected != Gtk.INVALID_LIST_POSITION)
{
- string device;
- device_model.get (iter, 0, out device, -1);
- return device;
+ return ((ScanDevice) device_model.get_item (device_drop_down.selected)).name;
}
return null;
@@ -408,13 +339,9 @@ public class AppWindow : Hdy.ApplicationWindow
private string? get_selected_device_label ()
{
- Gtk.TreeIter iter;
-
- if (device_combo.get_active_iter (out iter))
+ if (device_drop_down.selected != Gtk.INVALID_LIST_POSITION)
{
- string label;
- device_model.get (iter, 1, out label, -1);
- return label;
+ return ((ScanDevice) device_model.get_item (device_drop_down.selected)).label;
}
return null;
@@ -424,32 +351,31 @@ public class AppWindow : Hdy.ApplicationWindow
{
user_selected_device = true;
- Gtk.TreeIter iter;
- if (!find_scan_device (device, out iter))
+ uint position;
+ find_device_by_name (device, out position);
+
+ if (position != Gtk.INVALID_LIST_POSITION)
return;
- device_combo.set_active_iter (iter);
+ device_drop_down.selected = position;
}
- private bool find_scan_device (string device, out Gtk.TreeIter iter)
+ private ScanDevice? find_device_by_name(string name, out uint position)
{
- bool have_iter = false;
-
- if (device_model.get_iter_first (out iter))
+ for (uint i = 0; i < device_model.get_n_items (); i++)
{
- 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));
+ var item = (ScanDevice?) device_model.get_item (i);
+ if (item.label == name) {
+ position = i;
+ return item;
+ }
}
-
- return have_iter;
+
+ position = Gtk.INVALID_LIST_POSITION;
+ return null;
}
- private string? choose_file_location ()
+ private async string? choose_file_location ()
{
/* Get directory to save to */
string? directory = null;
@@ -457,161 +383,110 @@ public class AppWindow : Hdy.ApplicationWindow
if (directory == null || directory == "")
directory = GLib.Filename.to_uri(Environment.get_user_special_dir (UserDirectory.DOCUMENTS));
+
+ var save_dialog = new Gtk.FileDialog ();
+ save_dialog.title = _("Save As…");
+ save_dialog.modal = true;
+ save_dialog.accept_label = _("_Save");
- var save_dialog = new Gtk.FileChooserNative (/* Save dialog: Dialog title */
- _("Save As…"),
- this,
- Gtk.FileChooserAction.SAVE,
- _("_Save"),
- _("_Cancel"));
- save_dialog.local_only = false;
+ // TODO(gtk4)
+ // save_dialog.local_only = false;
var save_format = settings.get_string ("save-format");
if (book_uri != null)
- save_dialog.set_uri (book_uri);
- else {
- save_dialog.set_current_folder_uri (directory);
+ {
+ save_dialog.initial_file = GLib.File.new_for_uri (book_uri);
+ }
+ else
+ {
+ save_dialog.initial_folder = GLib.File.new_for_uri (directory);
+
/* Default filename to use when saving document. */
/* To that filename the extension will be added, eg. "Scanned Document.pdf" */
- save_dialog.set_current_name (_("Scanned Document") + "." + mime_type_to_extension (save_format));
+ save_dialog.initial_name = (_("Scanned Document") + "." + mime_type_to_extension (save_format));
}
-
- /* Filter to only show images by default */
- var filter = new Gtk.FileFilter ();
- filter.set_filter_name (/* Save dialog: Filter name to show only supported image files */
- _("Image Files"));
- filter.add_mime_type ("image/jpeg");
- filter.add_mime_type ("image/png");
-#if HAVE_WEBP
- filter.add_mime_type ("image/webp");
-#endif
- 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 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, "application/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, "image/jpeg",
- -1);
- file_type_store.append (out iter);
- file_type_store.set (iter,
- /* Save dialog: Label for saving in PNG format */
- 0, _("PNG (lossless)"),
- 1, "image/png",
- -1);
-#if HAVE_WEBP
- file_type_store.append (out iter);
- file_type_store.set (iter,
- /* Save dialog: Label for sabing in WEBP format */
- 0, _("WebP (compressed)"),
- 1, "image/webp",
- -1);
-#endif
-
+
+ var filters = new ListStore (typeof (Gtk.FileFilter));
+
+ var pdf_filter = new Gtk.FileFilter ();
+ pdf_filter.set_filter_name (_("PDF (multi-page document)"));
+ pdf_filter.add_pattern ("*.pdf" );
+ pdf_filter.add_mime_type ("application/pdf");
+ filters.append (pdf_filter);
+
+ var jpeg_filter = new Gtk.FileFilter ();
+ jpeg_filter.set_filter_name (_("JPEG (compressed)"));
+ jpeg_filter.add_pattern ("*.jpg" );
+ jpeg_filter.add_pattern ("*.jpeg" );
+ jpeg_filter.add_mime_type ("image/jpeg");
+ filters.append (jpeg_filter);
+
+ var png_filter = new Gtk.FileFilter ();
+ png_filter.set_filter_name (_("PNG (lossless)"));
+ png_filter.add_pattern ("*.png" );
+ png_filter.add_mime_type ("image/png");
+ filters.append (png_filter);
+
+ var webp_filter = new Gtk.FileFilter ();
+ webp_filter.set_filter_name (_("WebP (compressed)"));
+ webp_filter.add_pattern ("*.webp" );
+ webp_filter.add_mime_type ("image/webp");
+ filters.append (webp_filter);
+
+ var all_filter = new Gtk.FileFilter ();
+ all_filter.set_filter_name (_("All Files"));
+ all_filter.add_pattern ("*");
+ filters.append (all_filter);
+
+ save_dialog.filters = filters;
+
+ switch (save_format)
+ {
+ case "application/pdf":
+ save_dialog.default_filter = pdf_filter;
+ break;
+ case "image/jpeg":
+ save_dialog.default_filter = jpeg_filter;
+ break;
+ case "image/png":
+ save_dialog.default_filter = png_filter;
+ break;
+ case "image/webp":
+ save_dialog.default_filter = webp_filter;
+ break;
+ }
+
var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
box.visible = true;
box.spacing = 10;
- save_dialog.set_extra_widget (box);
-
- /* Label in save dialog beside combo box to choose file format (PDF, JPEG, PNG, WEBP) */
- var label = new Gtk.Label (_("File format:"));
- label.visible = true;
- box.add (label);
-
- var file_type_combo = new Gtk.ComboBox.with_model (file_type_store);
- file_type_combo.visible = true;
- var renderer = new Gtk.CellRendererText ();
- file_type_combo.pack_start (renderer, true);
- file_type_combo.add_attribute (renderer, "text", 0);
- box.add (file_type_combo);
+ box.set_halign (Gtk.Align.CENTER);
- if (file_type_store.get_iter_first (out iter))
- {
- do
- {
- string mime_type;
- file_type_store.get (iter, 1, out mime_type, -1);
- if (mime_type == save_format)
- file_type_combo.set_active_iter (iter);
- } while (file_type_store.iter_next (ref iter));
- }
-
- /* Label in save dialog beside compression slider */
- var quality_label = new Gtk.Label (_("Compression:"));
- box.add (quality_label);
-
- var quality_adjustment = new Gtk.Adjustment (75, 0, 100, 1, 10, 0);
- var quality_scale = new Gtk.Scale (Gtk.Orientation.HORIZONTAL, quality_adjustment);
- quality_scale.width_request = 250;
- quality_scale.draw_value = false;
- var minimum_size_label = "<small>%s</small>".printf (_("Minimum size"));
- quality_scale.add_mark (quality_adjustment.lower, Gtk.PositionType.BOTTOM, minimum_size_label);
- quality_scale.add_mark (75, Gtk.PositionType.BOTTOM, null);
- quality_scale.add_mark (90, Gtk.PositionType.BOTTOM, null);
- var full_detail_label = "<small>%s</small>".printf (_("Full detail"));
- quality_scale.add_mark (quality_adjustment.upper, Gtk.PositionType.BOTTOM, full_detail_label);
- quality_adjustment.value = settings.get_int ("jpeg-quality");
- quality_adjustment.value_changed.connect (() => { settings.set_int ("jpeg-quality", (int) quality_adjustment.value); });
- box.add (quality_scale);
-
- /* Quality not applicable to PNG */
- quality_scale.visible = quality_label.visible = (save_format != "image/png");
-
- file_type_combo.changed.connect (() =>
+ while (true)
{
- var mime_type = "";
- Gtk.TreeIter i;
- if (file_type_combo.get_active_iter (out i))
+ File? file = null;
+ try {
+ file = yield save_dialog.save (this, null);
+ }
+ catch (Error e)
{
- file_type_store.get (i, 1, out mime_type, -1);
- settings.set_string ("save-format", mime_type);
+ warning ("Failed to open save dialog: %s", e.message);
}
- var filename = save_dialog.get_current_name ();
-
- /* Replace extension */
- var extension_index = filename.last_index_of_char ('.');
- if (extension_index >= 0)
- filename = filename.slice (0, extension_index);
- filename = filename + "." + mime_type_to_extension (mime_type);
- save_dialog.set_current_name (filename);
-
- /* Quality not applicable to PNG */
- quality_scale.visible = quality_label.visible = (mime_type != "image/png");
- });
-
- while (true)
- {
- var response = save_dialog.run ();
- if (response != Gtk.ResponseType.ACCEPT)
+ if (file == null)
{
- save_dialog.destroy ();
return null;
}
- var mime_type = "";
- Gtk.TreeIter i;
- if (file_type_combo.get_active_iter (out i))
- file_type_store.get (i, 1, out mime_type, -1);
+ var uri = file.get_uri ();
+
+ var extension = uri_extension(uri);
- var uri = save_dialog.get_uri ();
+ var mime_type = extension_to_mime_type(extension);
+ mime_type = mime_type != null ? mime_type : "application/pdf";
- var extension_index = uri.last_index_of_char ('.');
- if (extension_index < 0)
+ settings.set_string ("save-format", mime_type);
+
+ if (extension == null)
uri += "." + mime_type_to_extension (mime_type);
/* Check the file(s) don't already exist */
@@ -623,35 +498,58 @@ public class AppWindow : Hdy.ApplicationWindow
}
else
files.append (File.new_for_uri (uri));
+
+ var overwrite_check = true;
+
+ // We assume that GTK or system file dialog asked about overwrite already so we reask only if there is more than one file or we changed the name
+ // Ideally in flatpack era we should not modify file name after save dialog is done
+ // but for the sake of keeping old functionality in tact we leave it as it
+ if (files.length () > 1 || file.get_uri () != uri)
+ {
+ overwrite_check = yield check_overwrite (this, files);
+ }
- if (check_overwrite (save_dialog.transient_for, files))
+ if (overwrite_check)
{
var directory_uri = uri.substring (0, uri.last_index_of ("/") + 1);
settings.set_string ("save-directory", directory_uri);
- save_dialog.destroy ();
return uri;
}
}
-
}
- private bool check_overwrite (Gtk.Window parent, List<File> files)
+ private async bool check_overwrite (Gtk.Window parent, List<File> files)
{
foreach (var file in files)
{
if (!file.query_exists ())
continue;
- var dialog = new Gtk.MessageDialog (parent, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE,
+ var title = _("A file named “%s” already exists. Do you want to replace it?").printf(file.get_basename ());
+
+ var dialog = new Adw.MessageDialog (parent,
/* Contents of dialog that shows if saving would overwrite and existing file. %s is replaced with the name of the file. */
- _("A file named “%s” already exists. Do you want to replace it?"), file.get_basename ());
- dialog.add_button (_("_Cancel"), Gtk.ResponseType.CANCEL);
- dialog.add_button (/* Button in dialog that shows if saving would overwrite and existing file. Clicking the button allows simple-scan to overwrite the file. */
- _("_Replace"), Gtk.ResponseType.ACCEPT);
- var response = dialog.run ();
- dialog.destroy ();
-
- if (response != Gtk.ResponseType.ACCEPT)
+ title,
+ null);
+
+ dialog.add_response ("cancel", _("_Cancel"));
+ dialog.add_response ("replace", _("_Replace"));
+
+ dialog.set_response_appearance ("replace", Adw.ResponseAppearance.DESTRUCTIVE);
+
+ SourceFunc callback = check_overwrite.callback;
+ string response = "cancel";
+
+ dialog.response.connect ((res) => {
+ response = res;
+ callback ();
+ });
+
+ dialog.show ();
+
+ yield;
+
+ if (response != "replace")
return false;
}
@@ -677,7 +575,7 @@ public class AppWindow : Hdy.ApplicationWindow
var extension_lower = extension.down ();
if (extension_lower == "pdf")
return "application/pdf";
- else if (extension_lower == "jpg")
+ else if (extension_lower == "jpg" || extension_lower == "jpeg")
return "image/jpeg";
else if (extension_lower == "png")
return "image/png";
@@ -687,12 +585,20 @@ public class AppWindow : Hdy.ApplicationWindow
return null;
}
- private string uri_to_mime_type (string uri)
+ private string? uri_extension (string uri)
{
var extension_index = uri.last_index_of_char ('.');
if (extension_index < 0)
+ return null;
+
+ return uri.substring (extension_index + 1);
+ }
+
+ private string uri_to_mime_type (string uri)
+ {
+ var extension = uri_extension(uri);
+ if (extension == null)
return "image/jpeg";
- var extension = uri.substring (extension_index + 1);
var mime_type = extension_to_mime_type (extension);
if (mime_type == null)
@@ -703,7 +609,7 @@ public class AppWindow : Hdy.ApplicationWindow
private async bool save_document_async ()
{
- var uri = choose_file_location ();
+ var uri = yield choose_file_location ();
if (uri == null)
return false;
@@ -739,7 +645,7 @@ public class AppWindow : Hdy.ApplicationWindow
return false;
}
save_button.sensitive = true;
- progress_bar.destroy_with_delay (500);
+ progress_bar.remove_with_delay (500, action_bar);
book_needs_saving = false;
book_uri = uri;
@@ -751,29 +657,36 @@ public class AppWindow : Hdy.ApplicationWindow
if (!book_needs_saving || (book.n_pages == 0))
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 ();
+ var dialog = new Adw.MessageDialog (this,
+ title,
+ _("If you don’t save, changes will be permanently lost."));
+
+ dialog.add_response ("discard", discard_label);
+ dialog.add_response ("cancel", _("_Cancel"));
+ dialog.add_response ("save", _("_Save"));
+
+ dialog.set_response_appearance ("discard", Adw.ResponseAppearance.DESTRUCTIVE);
+ dialog.set_response_appearance ("save", Adw.ResponseAppearance.SUGGESTED);
+
+ dialog.show ();
+
+ string response = "cancel";
+ SourceFunc callback = prompt_to_save_async.callback;
+ dialog.response.connect((res) => {
+ response = res;
+ callback ();
+ });
+
+ yield;
switch (response)
{
- case Gtk.ResponseType.YES:
+ case "save":
if (yield save_document_async ())
return true;
else
return false;
- case Gtk.ResponseType.NO:
+ case "discard":
return true;
default:
return false;
@@ -786,7 +699,7 @@ public class AppWindow : Hdy.ApplicationWindow
book_needs_saving = false;
book_uri = null;
save_button.sensitive = false;
- copy_to_clipboard_menuitem.sensitive = false;
+ copy_to_clipboard_action.set_enabled (false);
update_scan_status ();
stack.set_visible_child_name ("startup");
}
@@ -814,19 +727,46 @@ public class AppWindow : Hdy.ApplicationWindow
{
if (uri == "install-firmware")
{
- install_drivers ();
+ var dialog = new DriversDialog (this, missing_driver);
+ dialog.open.begin (() => {});
return true;
}
return false;
}
+ [GtkCallback]
private void new_document_cb ()
{
new_document ();
}
[GtkCallback]
+ private void crop_toggle_cb (Gtk.ToggleButton btn)
+ {
+ 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 (btn.active)
+ {
+ // Avoid overwriting crop name if there is already different crop active
+ if (!page.has_crop)
+ set_crop ("custom");
+ }
+ else
+ {
+ set_crop (null);
+ }
+ }
+
+ [GtkCallback]
private void redetect_button_clicked_cb (Gtk.Button button)
{
have_devices = false;
@@ -843,6 +783,31 @@ public class AppWindow : Hdy.ApplicationWindow
start_scan (get_selected_device (), options);
}
+ private void scan_type_action_cb (SimpleAction action, Variant? value)
+ {
+ var type = value.get_string ();
+
+ switch (type) {
+ case "single":
+ set_scan_type (ScanType.SINGLE);
+ break;
+ case "adf":
+ set_scan_type (ScanType.ADF);
+ break;
+ case "batch":
+ set_scan_type (ScanType.BATCH);
+ break;
+ default:
+ return;
+ }
+ }
+
+ private void document_hint_action_cb (SimpleAction action, Variant? value)
+ {
+ var hint = value.get_string ();
+ set_document_hint(hint, true);
+ }
+
private void scan_single_cb ()
{
var options = make_scan_options ();
@@ -871,32 +836,48 @@ public class AppWindow : Hdy.ApplicationWindow
private void rotate_left_cb ()
{
- rotate_left_button_clicked_cb ();
+ if (updating_page_menu)
+ return;
+ var page = book_view.selected_page;
+ if (page != null)
+ page.rotate_left ();
}
private void rotate_right_cb ()
{
- rotate_right_button_clicked_cb ();
+ if (updating_page_menu)
+ return;
+ var page = book_view.selected_page;
+ if (page != null)
+ page.rotate_right ();
}
private void move_left_cb ()
{
- page_move_left_menuitem_activate_cb ();
+ var page = book_view.selected_page;
+ var index = book.get_page_index (page);
+ if (index > 0)
+ book.move_page (page, index - 1);
}
private void move_right_cb ()
{
- page_move_right_menuitem_activate_cb ();
+ 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);
}
private void copy_page_cb ()
{
- copy_to_clipboard_button_clicked_cb ();
+ var page = book_view.selected_page;
+ if (page != null)
+ page.copy_to_clipboard (this);
}
private void delete_page_cb ()
{
- page_delete_menuitem_activate_cb ();
+ book_view.book.delete_page (book_view.selected_page);
}
private void set_scan_type (ScanType scan_type)
@@ -906,83 +887,33 @@ public class AppWindow : Hdy.ApplicationWindow
switch (scan_type)
{
case ScanType.SINGLE:
- scan_single_radio.active = true;
- scan_options_image.icon_name = "scanner-symbolic";
+ scan_type_action.set_state ("single");
+ scan_button_content.icon_name = "scanner-symbolic";
scan_button.tooltip_text = _("Scan a single page from the scanner");
break;
case ScanType.ADF:
- scan_adf_radio.active = true;
- scan_options_image.icon_name = "scan-type-adf-symbolic";
+ scan_type_action.set_state ("adf");
+ scan_button_content.icon_name = "scan-type-adf-symbolic";
scan_button.tooltip_text = _("Scan multiple pages from the scanner");
break;
case ScanType.BATCH:
- scan_batch_radio.active = true;
- scan_options_image.icon_name = "scan-type-batch-symbolic";
+ scan_type_action.set_state ("batch");
+ scan_button_content.icon_name = "scan-type-batch-symbolic";
scan_button.tooltip_text = _("Scan multiple pages from the scanner");
break;
}
}
- [GtkCallback]
- private void scan_single_radio_toggled_cb (Gtk.ToggleButton button)
- {
- if (button.active)
- set_scan_type (ScanType.SINGLE);
- }
-
- [GtkCallback]
- private void scan_adf_radio_toggled_cb (Gtk.ToggleButton button)
- {
- if (button.active)
- set_scan_type (ScanType.ADF);
- }
-
- [GtkCallback]
- private void scan_batch_radio_toggled_cb (Gtk.ToggleButton button)
- {
- if (button.active)
- set_scan_type (ScanType.BATCH);
- }
-
private void set_document_hint (string document_hint, bool save = false)
{
this.document_hint = document_hint;
- if (document_hint == "text")
- {
- text_radio.active = true;
- scan_hint_image.icon_name = "x-office-document-symbolic";
- }
- else if (document_hint == "photo")
- {
- photo_radio.active = true;
- scan_hint_image.icon_name = "image-x-generic-symbolic";
- }
+ document_hint_action.set_state (document_hint);
if (save)
settings.set_string ("document-type", document_hint);
}
- [GtkCallback]
- private void text_radio_toggled_cb (Gtk.ToggleButton button)
- {
- if (button.active)
- set_document_hint ("text", true);
- }
-
- [GtkCallback]
- private void photo_radio_toggled_cb (Gtk.ToggleButton button)
- {
- if (button.active)
- set_document_hint ("photo", true);
- }
-
- [GtkCallback]
- private void preferences_button_clicked_cb (Gtk.Button button)
- {
- preferences_dialog.present ();
- }
-
private ScanOptions make_scan_options ()
{
var options = new ScanOptions ();
@@ -1008,7 +939,7 @@ public class AppWindow : Hdy.ApplicationWindow
}
[GtkCallback]
- private void device_combo_changed_cb (Gtk.Widget widget)
+ private void device_drop_down_changed_cb (Object widget, ParamSpec spec)
{
if (setting_devices)
return;
@@ -1045,14 +976,14 @@ public class AppWindow : Hdy.ApplicationWindow
var page = book_view.selected_page;
if (page == null)
{
- page_move_left_menuitem.sensitive = false;
- page_move_right_menuitem.sensitive = false;
+ page_move_left_action.set_enabled (false);
+ page_move_right_action.set_enabled (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;
+ page_move_left_action.set_enabled (index > 0);
+ page_move_right_action.set_enabled (index < book.n_pages - 1);
}
}
@@ -1064,33 +995,8 @@ public class AppWindow : Hdy.ApplicationWindow
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 == "A3")
- menuitem = a3_menuitem;
- else 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_actions.update_current_crop (page.crop_name);
crop_button.active = page.has_crop;
updating_page_menu = false;
@@ -1113,50 +1019,29 @@ public class AppWindow : Hdy.ApplicationWindow
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);
- }
+ var launcher = new Gtk.FileLauncher(file);
+ launcher.launch.begin (this, null);
}
- private void show_page_menu_cb (BookView view, Gdk.Event event)
+ private void show_page_menu_cb (BookView view, Gtk.Widget from, double x, double y)
{
- page_menu.popup_at_pointer (event);
- }
+ double tx, ty;
+ from.translate_coordinates(this, x, y, out tx, out ty);
- [GtkCallback]
- private void rotate_left_button_clicked_cb ()
- {
- if (updating_page_menu)
- return;
- var page = book_view.selected_page;
- if (page != null)
- page.rotate_left ();
- }
+ Gdk.Rectangle rect = { x: (int) tx, y: (int) ty, w: 1, h: 1 };
- [GtkCallback]
- private void rotate_right_button_clicked_cb ()
- {
- if (updating_page_menu)
- return;
- var page = book_view.selected_page;
- if (page != null)
- page.rotate_right ();
+ page_menu.set_pointing_to (rect);
+ page_menu.popup ();
}
private void set_crop (string? crop_name)
{
- crop_rotate_menuitem.sensitive = crop_name != null;
-
if (updating_page_menu)
return;
+ if (crop_name == "none")
+ crop_name = null;
+
var page = book_view.selected_page;
if (page == null)
{
@@ -1177,73 +1062,17 @@ public class AppWindow : Hdy.ApplicationWindow
}
else
page.set_named_crop (crop_name);
+
+ crop_actions.update_current_crop (crop_name);
+ crop_button.active = page.has_crop;
}
-
- [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 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)
+
+ public void crop_set_action_cb (SimpleAction action, Variant? value)
{
- if (widget.active)
- set_crop ("A4");
+ set_crop (value.get_string ());
}
- [GtkCallback]
- private void a3_menuitem_toggled_cb (Gtk.CheckMenuItem widget)
- {
- if (widget.active)
- set_crop ("A3");
- }
-
- [GtkCallback]
- private void crop_rotate_menuitem_activate_cb ()
+ public void crop_rotate_action_cb ()
{
var page = book_view.selected_page;
if (page == null)
@@ -1251,210 +1080,54 @@ public class AppWindow : Hdy.ApplicationWindow
page.rotate_crop ();
}
- [GtkCallback]
- private void page_move_left_menuitem_activate_cb ()
- {
- 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 ()
- {
- 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 ()
- {
- book_view.book.delete_page (book_view.selected_page);
- }
-
- private void reorder_document ()
+ private void reorder_document_cb ()
{
- 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 = C_("dialog title", "Reorder Pages");
+ var dialog = new ReorderPagesDialog ();
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 (() =>
+
+ /* Button for combining sides in reordering dialog */
+ dialog.combine_sides.clicked.connect (() =>
{
book.combine_sides ();
- dialog.destroy ();
+ dialog.close ();
});
- 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 (() =>
+ /* Button for combining sides in reverse order in reordering dialog */
+ dialog.combine_sides_rev.clicked.connect (() =>
{
book.combine_sides_reverse ();
- dialog.destroy ();
+ dialog.close ();
});
- 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 (() =>
+ /* Button for reversing in reordering dialog */
+ dialog.reverse.clicked.connect (() =>
{
book.reverse ();
- dialog.destroy ();
+ dialog.close ();
});
- 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 (() =>
+ /* Button for keeping the ordering, but flip every second upside down */
+ dialog.flip_odd.clicked.connect (() =>
{
- dialog.destroy ();
+ book.flip_every_second(FlipEverySecond.Odd);
+ dialog.close ();
});
- b.visible = true;
- g.attach (b, 1, 2, 1, 1);
- /* Label on button for keeping the ordering, but flip every second upside down */
- b = make_reorder_button (_("Flip even pages upside-down"), "R1U2R3U4R5U6-R1R2R3R4R5R6");
- b.clicked.connect (() =>
+ /* Button for keeping the ordering, but flip every second upside down */
+ dialog.flip_even.clicked.connect (() =>
{
+ dialog.close ();
book.flip_every_second(FlipEverySecond.Even);
- dialog.destroy ();
});
- b.visible = true;
- g.attach (b, 0, 3, 1, 1);
-
-
- /* Label on button for keeping the ordering, but flip every second upside down */
- b = make_reorder_button (_("Flip odd pages upside-down"), "U1R2U3R4U5R6-R1R2R3R4R5R6");
- b.clicked.connect (() =>
- {
- book.flip_every_second(FlipEverySecond.Odd);
- dialog.destroy ();
- });
- b.visible = true;
- g.attach (b, 1, 3, 1, 1);
dialog.present ();
}
- private void reorder_document_cb ()
- {
- 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;
- label.vexpand = true;
- vbox.add (label);
-
- var rb = make_reorder_box (items);
- rb.visible = true;
- rb.vexpand = true;
- vbox.add (rb);
-
- 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.Label ("➤");
- a.visible = true;
- box.add (a);
- 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.add (page_box);
- }
- if (side == 'U') {
- var icon = new PageIcon (side, items[i] - '1', 180);
- icon.visible = true;
- page_box.add (icon);
- } else {
- var icon = new PageIcon (side, items[i] - '1', 0);
- icon.visible = true;
- page_box.add (icon);
- }
- }
-
- return box;
- }
-
- [GtkCallback]
- private void save_file_button_clicked_cb (Gtk.Widget widget)
- {
- save_document_async.begin ();
- }
-
- public void save_document_activate_cb ()
+ public void save_document_cb ()
{
save_document_async.begin ();
}
- [GtkCallback]
- private void copy_to_clipboard_button_clicked_cb ()
- {
- var page = book_view.selected_page;
- if (page != null)
- page.copy_to_clipboard (this);
- }
-
private void draw_page (Gtk.PrintOperation operation,
Gtk.PrintContext print_context,
int page_number)
@@ -1549,16 +1222,8 @@ public class AppWindow : Hdy.ApplicationWindow
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);
- }
+ var launcher = new Gtk.UriLauncher ("help:simple-scan");
+ launcher.launch.begin (this, null);
}
private void help_cb ()
@@ -1570,23 +1235,22 @@ public class AppWindow : Hdy.ApplicationWindow
{
string[] authors = { "Robert Ancell <robert.ancell@canonical.com>" };
- string title = _("About Document Scanner");
-
- string description = _("Simple document scanning tool");
-
- Gtk.show_about_dialog (this,
- "title", title,
- "authors", authors,
- "translator-credits", _("translator-credits"),
- "comments", description,
- "copyright", "Copyright © 2009-2018 Canonical Ltd.",
- "license-type", Gtk.License.GPL_3_0,
- "program-name", _("Document Scanner"),
- "logo-icon-name", "org.gnome.SimpleScan",
- "version", VERSION,
- "website", "https://gitlab.gnome.org/GNOME/simple-scan",
- "wrap-license", true);
- }
+ var about = new Adw.AboutWindow ()
+ {
+ transient_for = this,
+ developers = authors,
+ translator_credits = _("translator-credits"),
+ copyright = "Copyright © 2009-2018 Canonical Ltd.",
+ license_type = Gtk.License.GPL_3_0,
+ application_name = _("Document Scanner"),
+ application_icon = "org.gnome.SimpleScan",
+ version = VERSION,
+ website = "https://gitlab.gnome.org/GNOME/simple-scan",
+ issue_url = "https://gitlab.gnome.org/GNOME/baobab/-/issues/new",
+ };
+
+ about.present ();
+ }
private void about_cb ()
{
@@ -1617,200 +1281,29 @@ public class AppWindow : Hdy.ApplicationWindow
on_quit ();
}
- public override void size_allocate (Gtk.Allocation allocation)
+ public override void size_allocate (int width, int height, int baseline)
{
- base.size_allocate (allocation);
+ base.size_allocate (width, height, baseline);
if (!window_is_maximized && !window_is_fullscreen)
{
- get_size (out window_width, out window_height);
+ window_width = this.get_width();
+ window_height = this.get_height();
save_state ();
}
}
- private void install_drivers ()
+ public override void unmap ()
{
- var message = "", instructions = "";
- string[] packages_to_install = {};
- switch (missing_driver)
- {
- case "brscan":
- case "brscan2":
- case "brscan3":
- case "brscan4":
- /* Message to indicate a Brother scanner has been detected */
- message = _("You appear to have a Brother scanner.");
- /* Instructions on how to install Brother scanner drivers */
- instructions = _("Drivers for this are available on the <a href=\"http://support.brother.com\">Brother website</a>.");
- break;
- case "pixma":
- /* Message to indicate a Canon Pixma scanner has been detected */
- message = _("You appear to have a Canon scanner, which is supported by the <a href=\"http://www.sane-project.org/man/sane-pixma.5.html\">Pixma SANE backend</a>.");
- /* Instructions on how to resolve issue with SANE scanner drivers */
- instructions = _("Please check if your <a href=\"http://www.sane-project.org/sane-supported-devices.html\">scanner is supported by SANE</a>, otherwise report the issue to the <a href=\"https://alioth-lists.debian.net/cgi-bin/mailman/listinfo/sane-devel\">SANE mailing list</a>.");
- break;
- case "samsung":
- /* Message to indicate a Samsung scanner has been detected */
- message = _("You appear to have a Samsung scanner.");
- /* Instructions on how to install Samsung scanner drivers.
- Because HP acquired Samsung's global printing business in 2017, the support is made on HP site. */
- instructions = _("Drivers for this are available on the <a href=\"https://support.hp.com\">HP website</a> (HP acquired Samsung's printing business).");
- break;
- case "hpaio":
- case "smfp":
- /* Message to indicate a HP scanner has been detected */
- message = _("You appear to have an HP scanner.");
- if (missing_driver == "hpaio")
- packages_to_install = { "libsane-hpaio" };
- else
- /* Instructions on how to install HP scanner drivers.
- smfp is rebranded and slightly modified Samsung devices,
- for example: HP Laser MFP 135a is rebranded Samsung Xpress SL-M2070.
- It require custom drivers, not available in hpaio package */
- instructions = _("Drivers for this are available on the <a href=\"https://support.hp.com\">HP website</a>.");
- break;
- case "epkowa":
- /* Message to indicate an Epson scanner has been detected */
- message = _("You appear to have an Epson scanner.");
- /* Instructions on how to install Epson scanner drivers */
- instructions = _("Drivers for this are available on the <a href=\"http://support.epson.com\">Epson website</a>.");
- break;
- case "lexmark_nscan":
- /* Message to indicate a Lexmark scanner has been detected */
- message = _("You appear to have a Lexmark scanner.");
- /* Instructions on how to install Lexmark scanner drivers */
- instructions = _("Drivers for this are available on the <a href=\"http://support.lexmark.com\">Lexmark website</a>.");
- break;
- }
- var dialog = new Gtk.Dialog.with_buttons (/* Title of dialog giving instructions on how to install drivers */
- _("Install drivers"), this, Gtk.DialogFlags.MODAL, _("_Close"), Gtk.ResponseType.CLOSE);
- dialog.get_content_area ().border_width = 12;
- dialog.get_content_area ().spacing = 6;
-
- var label = new Gtk.Label (message);
- label.visible = true;
- label.xalign = 0f;
- label.vexpand = true;
- label.use_markup = true;
- dialog.get_content_area ().add (label);
-
- var instructions_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
- instructions_box.visible = true;
- instructions_box.vexpand = true;
- dialog.get_content_area ().add (instructions_box);
-
- var stack = new Gtk.Stack ();
- instructions_box.add (stack);
-
- var spinner = new Gtk.Spinner ();
- spinner.visible = true;
- stack.add (spinner);
-
- var status_label = new Gtk.Label ("");
- status_label.visible = true;
- stack.add (status_label);
-
- var instructions_label = new Gtk.Label (instructions);
- instructions_label.visible = true;
- instructions_label.xalign = 0f;
- instructions_label.use_markup = true;
- instructions_box.add (instructions_label);
-
- label = new Gtk.Label (/* Message in driver install dialog */
- _("Once installed you will need to restart this app."));
- label.visible = true;
- label.xalign = 0f;
- label.vexpand = true;
- dialog.get_content_area ().border_width = 12;
- dialog.get_content_area ().add (label);
-
- if (packages_to_install.length > 0)
- {
-#if HAVE_PACKAGEKIT
- stack.visible = true;
- spinner.active = true;
- instructions_label.set_text (/* Label shown while installing drivers */
- _("Installing drivers…"));
- install_packages.begin (packages_to_install, () => {}, (object, result) =>
- {
- status_label.visible = true;
- spinner.active = false;
- status_label.set_text ("☒");
- stack.visible_child = status_label;
- /* Label shown once drivers successfully installed */
- var result_text = _("Drivers installed successfully!");
- try
- {
- var results = install_packages.end (result);
- if (results.get_error_code () == null)
- status_label.set_text ("☑");
- else
- {
- var e = results.get_error_code ();
- /* Label shown if failed to install drivers */
- result_text = _("Failed to install drivers (error code %d).").printf (e.code);
- }
- }
- catch (Error e)
- {
- /* Label shown if failed to install drivers */
- result_text = _("Failed to install drivers.");
- warning ("Failed to install drivers: %s", e.message);
- }
- instructions_label.set_text (result_text);
- });
-#else
- instructions_label.set_text (/* Label shown to prompt user to install packages (when PackageKit not available) */
- ngettext ("You need to install the %s package.", "You need to install the %s packages.", packages_to_install.length).printf (string.joinv (", ", packages_to_install)));
-#endif
- }
-
- dialog.run ();
- dialog.destroy ();
- }
-
-#if HAVE_PACKAGEKIT
- private async Pk.Results? install_packages (string[] packages, Pk.ProgressCallback progress_callback) throws GLib.Error
- {
- var task = new Pk.Task ();
- Pk.Results results;
- results = yield task.resolve_async (Pk.Filter.NOT_INSTALLED, packages, null, progress_callback);
- if (results == null || results.get_error_code () != null)
- return results;
-
- var package_array = results.get_package_array ();
- var package_ids = new string[package_array.length + 1];
- package_ids[package_array.length] = null;
- for (var i = 0; i < package_array.length; i++)
- package_ids[i] = package_array.data[i].get_id ();
-
- return yield task.install_packages_async (package_ids, null, progress_callback);
- }
-#endif
-
- public override bool window_state_event (Gdk.EventWindowState event)
- {
- var result = Gdk.EVENT_PROPAGATE;
-
- if (base.window_state_event != null)
- result = base.window_state_event (event);
-
- if ((event.changed_mask & Gdk.WindowState.MAXIMIZED) != 0)
- {
- window_is_maximized = (event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0;
- save_state ();
- }
- if ((event.changed_mask & Gdk.WindowState.FULLSCREEN) != 0)
- {
- window_is_fullscreen = (event.new_window_state & Gdk.WindowState.FULLSCREEN) != 0;
- save_state ();
- }
-
- return result;
+ window_is_maximized = is_maximized ();
+ window_is_fullscreen = is_fullscreen ();
+ save_state ();
+
+ base.unmap ();
}
[GtkCallback]
- private bool window_delete_event_cb (Gtk.Widget widget, Gdk.EventAny event)
+ private bool window_close_request_cb (Gtk.Window window)
{
on_quit ();
return true; /* Let us quit on our own terms */
@@ -1835,13 +1328,13 @@ public class AppWindow : Hdy.ApplicationWindow
{
save_button.sensitive = true;
book_needs_saving = true;
- copy_to_clipboard_menuitem.sensitive = true;
+ copy_to_clipboard_action.set_enabled (true);
}
private void load ()
{
preferences_dialog = new PreferencesDialog (settings);
- preferences_dialog.delete_event.connect (() => {
+ preferences_dialog.close_request.connect (() => {
preferences_dialog.visible = false;
return true;
});
@@ -1852,13 +1345,18 @@ public class AppWindow : Hdy.ApplicationWindow
var app = Application.get_default () as Gtk.Application;
- /* Set HeaderBar title here because Glade doesn't keep it translated */
- /* https://bugzilla.gnome.org/show_bug.cgi?id=782753 */
- /* Title of scan window */
- header_bar.title = _("Document Scanner");
+ crop_actions = new CropActions (this);
app.add_action_entries (action_entries, this);
+ scan_type_action = (GLib.SimpleAction) app.lookup_action("scan_type");
+ document_hint_action = (GLib.SimpleAction) app.lookup_action("document_hint");
+
+ delete_page_action = (GLib.SimpleAction) app.lookup_action("delete_page");
+ page_move_left_action = (GLib.SimpleAction) app.lookup_action("move_left");
+ page_move_right_action = (GLib.SimpleAction) app.lookup_action("move_right");
+ copy_to_clipboard_action = (GLib.SimpleAction) app.lookup_action("copy_page");
+
app.set_accels_for_action ("app.new_document", { "<Ctrl>N" });
app.set_accels_for_action ("app.scan_single", { "<Ctrl>1" });
app.set_accels_for_action ("app.scan_adf", { "<Ctrl>F" });
@@ -1894,79 +1392,15 @@ public class AppWindow : Hdy.ApplicationWindow
app.add_window (this);
- /* Populate ActionBar (not supported in Glade) */
- /* https://bugzilla.gnome.org/show_bug.cgi?id=769966 */
- var button = new Gtk.Button.with_mnemonic (/* Label on new document button */
- _("_New Document"));
- button.visible = true;
- button.clicked.connect (new_document_cb);
- action_bar.pack_start (button);
-
- var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 10);
- box.visible = true;
- action_bar.set_center_widget (box);
-
- var rotate_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
- rotate_box.get_style_context ().add_class (Gtk.STYLE_CLASS_LINKED);
- rotate_box.visible = true;
- box.add (rotate_box);
-
- button = new Gtk.Button.from_icon_name ("object-rotate-left-symbolic");
- button.visible = true;
- button.image.margin_start = 18;
- button.image.margin_end = 18;
- /* Tooltip for rotate left (counter-clockwise) button */
- button.tooltip_text = _("Rotate the page to the left (counter-clockwise)");
- button.clicked.connect (rotate_left_button_clicked_cb);
- rotate_box.add (button);
-
- button = new Gtk.Button.from_icon_name ("object-rotate-right-symbolic");
- button.visible = true;
- button.image.margin_start = 18;
- button.image.margin_end = 18;
- /* Tooltip for rotate right (clockwise) button */
- button.tooltip_text = _("Rotate the page to the right (clockwise)");
- button.clicked.connect (rotate_right_button_clicked_cb);
- rotate_box.add (button);
-
- crop_button = new Gtk.ToggleButton ();
- crop_button.visible = true;
- var image = new Gtk.Image.from_icon_name ("crop-symbolic", Gtk.IconSize.BUTTON);
- image.visible = true;
- image.margin_start = 18;
- image.margin_end = 18;
- crop_button.add (image);
- /* Tooltip for crop button */
- crop_button.tooltip_text = _("Crop the selected page");
- crop_button.toggled.connect ((widget) =>
- {
- if (updating_page_menu)
- return;
-
- if (widget.active)
- custom_crop_menuitem.active = true;
- else
- no_crop_menuitem.active = true;
- });
- box.add (crop_button);
-
- delete_button = new Gtk.Button.from_icon_name ("user-trash-symbolic");
- delete_button.visible = true;
- delete_button.image.margin_start = 18;
- delete_button.image.margin_end = 18;
- /* Tooltip for delete button */
- delete_button.tooltip_text = _("Delete the selected page");
- delete_button.clicked.connect (() => { book_view.book.delete_page (book_view.selected_page); });
- box.add (delete_button);
-
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;
book_view.vexpand = true;
- main_vbox.add (book_view);
+
+ main_vbox.prepend (book_view);
+
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);
@@ -2099,34 +1533,45 @@ public class AppWindow : Hdy.ApplicationWindow
visible = true;
autosave_manager = new AutosaveManager ();
autosave_manager.book = book;
-
- if (autosave_manager.exists () && prompt_to_load_autosaved_book ())
- autosave_manager.load ();
-
- if (book.n_pages == 0)
- book_needs_saving = false;
- else
+
+ if (autosave_manager.exists ())
{
- stack.set_visible_child_name ("document");
- book_view.selected_page = book.get_page (0);
- book_needs_saving = true;
- book_changed_cb (book);
+ prompt_to_load_autosaved_book.begin ((obj, res) => {
+ bool restore = prompt_to_load_autosaved_book.end (res);
+
+ if (restore)
+ {
+ autosave_manager.load ();
+ }
+
+ if (book.n_pages == 0)
+ book_needs_saving = false;
+ else
+ {
+ stack.set_visible_child_name ("document");
+ book_view.selected_page = book.get_page (0);
+ book_needs_saving = true;
+ book_changed_cb (book);
+ }
+ });
}
}
}
-private class CancellableProgressBar : Gtk.HBox
+private class CancellableProgressBar : Gtk.Box
{
private Gtk.ProgressBar bar;
private Gtk.Button? button;
public CancellableProgressBar (string? text, Cancellable? cancellable)
{
+ this.orientation = Gtk.Orientation.HORIZONTAL;
+
bar = new Gtk.ProgressBar ();
bar.visible = true;
bar.set_text (text);
bar.set_show_text (true);
- pack_start (bar);
+ prepend (bar);
if (cancellable != null)
{
@@ -2138,7 +1583,7 @@ private class CancellableProgressBar : Gtk.HBox
set_visible (false);
cancellable.cancel ();
});
- pack_start (button);
+ prepend (button);
}
}
@@ -2147,14 +1592,49 @@ private class CancellableProgressBar : Gtk.HBox
bar.set_fraction (fraction);
}
- public void destroy_with_delay (uint delay)
+ public void remove_with_delay (uint delay, Gtk.ActionBar parent)
{
button.set_sensitive (false);
Timeout.add (delay, () =>
{
- this.destroy ();
+ parent.remove (this);
return false;
});
}
}
+
+private class CropActions
+{
+ private GLib.SimpleActionGroup group;
+
+ private GLib.SimpleAction crop_set;
+ private GLib.SimpleAction crop_rotate;
+
+ private GLib.ActionEntry[] crop_entries =
+ {
+ { "set", AppWindow.crop_set_action_cb, "s", "'none'" },
+ { "rotate", AppWindow.crop_rotate_action_cb },
+ };
+
+ public CropActions (AppWindow window)
+ {
+ group = new GLib.SimpleActionGroup ();
+ group.add_action_entries (crop_entries, window);
+
+ crop_set = (GLib.SimpleAction) group.lookup_action ("set");
+ crop_rotate = (GLib.SimpleAction) group.lookup_action ("rotate");
+
+ window.insert_action_group ("crop", group);
+ }
+
+ public void update_current_crop (string? crop_name)
+ {
+ crop_rotate.set_enabled (crop_name != null);
+
+ if (crop_name == null)
+ crop_set.set_state ("none");
+ else
+ crop_set.set_state (crop_name);
+ }
+} \ No newline at end of file
diff --git a/src/authorize-dialog.vala b/src/authorize-dialog.vala
index 7cb2a66..ab0237a 100644
--- a/src/authorize-dialog.vala
+++ b/src/authorize-dialog.vala
@@ -11,18 +11,21 @@
*/
[GtkTemplate (ui = "/org/gnome/SimpleScan/ui/authorize-dialog.ui")]
-private class AuthorizeDialog : Gtk.Dialog
+private class AuthorizeDialog : Gtk.Window
{
[GtkChild]
- private unowned Gtk.Label authorize_label;
+ private unowned Adw.PreferencesGroup preferences_group;
[GtkChild]
- private unowned Gtk.Entry username_entry;
+ private unowned Adw.EntryRow username_entry;
[GtkChild]
- private unowned Gtk.Entry password_entry;
+ private unowned Adw.PasswordEntryRow password_entry;
- public AuthorizeDialog (string title)
+ public signal void authorized (AuthorizeDialogResponse res);
+
+ public AuthorizeDialog (Gtk.Window parent, string title)
{
- authorize_label.set_text (title);
+ preferences_group.set_title (title);
+ set_transient_for (parent);
}
public string get_username ()
@@ -34,4 +37,60 @@ private class AuthorizeDialog : Gtk.Dialog
{
return password_entry.text;
}
+
+ [GtkCallback]
+ private void authorize_button_cb ()
+ {
+ authorized (AuthorizeDialogResponse.new_authorized (get_username (), get_password ()));
+ }
+
+ [GtkCallback]
+ private void cancel_button_cb ()
+ {
+ authorized (AuthorizeDialogResponse.new_canceled ());
+ }
+
+ public async AuthorizeDialogResponse open()
+ {
+ SourceFunc callback = open.callback;
+
+ AuthorizeDialogResponse response = {};
+
+ authorized.connect ((res) =>
+ {
+ response = res;
+ callback ();
+ });
+
+ present ();
+ yield;
+ close ();
+
+ return response;
+ }
+}
+
+public struct AuthorizeDialogResponse
+{
+ public string username;
+ public string password;
+ public bool success;
+
+ public static AuthorizeDialogResponse new_canceled ()
+ {
+ return AuthorizeDialogResponse ()
+ {
+ success = false,
+ };
+ }
+
+ public static AuthorizeDialogResponse new_authorized (string username, string password)
+ {
+ return AuthorizeDialogResponse ()
+ {
+ username = username,
+ password = password,
+ success = true,
+ };
+ }
}
diff --git a/src/autosave-manager.vala b/src/autosave-manager.vala
index 7e92d33..31fd328 100644
--- a/src/autosave-manager.vala
+++ b/src/autosave-manager.vala
@@ -117,8 +117,16 @@ public class AutosaveManager
var pixels_filename = get_value (file, page_name, "pixels-filename");
var has_crop = get_boolean (file, page_name, "has-crop");
var crop_name = get_value (file, page_name, "crop-name");
+
if (crop_name == "")
- crop_name = null;
+ {
+ // If it has no crop name but has crop it probably means that it is a custom crop
+ if (has_crop)
+ crop_name = "custom";
+ else
+ crop_name = null;
+ }
+
var crop_x = get_integer (file, page_name, "crop-x");
var crop_y = get_integer (file, page_name, "crop-y");
var crop_width = get_integer (file, page_name, "crop-width");
diff --git a/src/book-view.vala b/src/book-view.vala
index e16dc92..162bdad 100644
--- a/src/book-view.vala
+++ b/src/book-view.vala
@@ -22,7 +22,7 @@ public class BookView : Gtk.Box
private bool need_layout;
private bool laying_out;
private bool show_selected_page;
-
+
/* Currently selected page */
private PageView? selected_page_view = null;
public Page? selected_page
@@ -47,17 +47,24 @@ public class BookView : Gtk.Box
}
/* Widget being rendered to */
- private Gtk.Widget drawing_area;
+ private Gtk.DrawingArea drawing_area;
/* Horizontal scrollbar */
private Gtk.Scrollbar scroll;
private Gtk.Adjustment adjustment;
- private Gdk.CursorType cursor;
+ private new string cursor;
+
+ private Gtk.EventControllerMotion motion_controller;
+ private Gtk.EventControllerKey key_controller;
+ private Gtk.GestureClick primary_click_gesture;
+ private Gtk.GestureClick secondary_click_gesture;
+ private Gtk.EventControllerFocus focus_controller;
+
public signal void page_selected (Page? page);
public signal void show_page (Page page);
- public signal void show_menu (Gdk.Event event);
+ public signal void show_menu (Gtk.Widget from, double x, double y);
public int x_offset
{
@@ -93,27 +100,47 @@ public class BookView : Gtk.Box
need_layout = true;
page_data = new HashTable<Page, PageView> (direct_hash, direct_equal);
- cursor = Gdk.CursorType.ARROW;
+ cursor = "arrow";
drawing_area = new Gtk.DrawingArea ();
drawing_area.set_size_request (200, 100);
drawing_area.can_focus = true;
- drawing_area.events = Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.FOCUS_CHANGE_MASK | Gdk.EventMask.STRUCTURE_MASK | Gdk.EventMask.SCROLL_MASK;
drawing_area.vexpand = true;
- add (drawing_area);
+ drawing_area.set_draw_func(draw_cb);
+
+ append (drawing_area);
scroll = new Gtk.Scrollbar (Gtk.Orientation.HORIZONTAL, null);
adjustment = scroll.adjustment;
- add (scroll);
-
- drawing_area.configure_event.connect (configure_cb);
- drawing_area.draw.connect (draw_cb);
- drawing_area.motion_notify_event.connect (motion_cb);
- drawing_area.key_press_event.connect (key_cb);
- drawing_area.button_press_event.connect (button_cb);
- drawing_area.button_release_event.connect (button_cb);
- drawing_area.focus_in_event.connect_after (focus_cb);
- drawing_area.focus_out_event.connect_after (focus_cb);
+ append (scroll);
+
+ drawing_area.resize.connect (drawing_area_resize_cb);
+
+ motion_controller = new Gtk.EventControllerMotion ();
+ motion_controller.motion.connect (motion_cb);
+ drawing_area.add_controller(motion_controller);
+
+ key_controller = new Gtk.EventControllerKey ();
+ key_controller.key_pressed.connect (key_cb);
+ drawing_area.add_controller(key_controller);
+
+ primary_click_gesture = new Gtk.GestureClick ();
+ primary_click_gesture.button = Gdk.BUTTON_PRIMARY;
+ primary_click_gesture.pressed.connect (primary_pressed_cb);
+ primary_click_gesture.released.connect (primary_released_cb);
+ drawing_area.add_controller(primary_click_gesture);
+
+ secondary_click_gesture = new Gtk.GestureClick ();
+ secondary_click_gesture.button = Gdk.BUTTON_SECONDARY;
+ secondary_click_gesture.pressed.connect (secondary_pressed_cb);
+ secondary_click_gesture.released.connect (secondary_released_cb);
+ drawing_area.add_controller(secondary_click_gesture);
+
+ focus_controller = new Gtk.EventControllerFocus ();
+ focus_controller.enter.connect_after (focus_cb);
+ focus_controller.leave.connect_after (focus_cb);
+ drawing_area.add_controller(focus_controller);
+
adjustment.value_changed.connect (scroll_cb);
drawing_area.visible = true;
@@ -125,14 +152,15 @@ public class BookView : Gtk.Box
book.page_removed.disconnect (remove_cb);
book.reordered.disconnect (reorder_cb);
book.cleared.disconnect (clear_cb);
- drawing_area.configure_event.disconnect (configure_cb);
- drawing_area.draw.disconnect (draw_cb);
- drawing_area.motion_notify_event.disconnect (motion_cb);
- drawing_area.key_press_event.disconnect (key_cb);
- drawing_area.button_press_event.disconnect (button_cb);
- drawing_area.button_release_event.disconnect (button_cb);
- drawing_area.focus_in_event.disconnect (focus_cb);
- drawing_area.focus_out_event.disconnect (focus_cb);
+ drawing_area.resize.disconnect (drawing_area_resize_cb);
+ motion_controller.motion.disconnect (motion_cb);
+ key_controller.key_pressed.disconnect (key_cb);
+ primary_click_gesture.pressed.disconnect (primary_pressed_cb);
+ primary_click_gesture.released.disconnect (primary_released_cb);
+ secondary_click_gesture.pressed.disconnect (secondary_pressed_cb);
+ secondary_click_gesture.released.disconnect (secondary_released_cb);
+ focus_controller.enter.disconnect (focus_cb);
+ focus_controller.leave.disconnect (focus_cb);
adjustment.value_changed.disconnect (scroll_cb);
}
@@ -289,10 +317,12 @@ public class BookView : Gtk.Box
redraw ();
}
- private bool configure_cb (Gtk.Widget widget, Gdk.EventConfigure event)
+ public void drawing_area_resize_cb ()
{
need_layout = true;
- return false;
+ // Let's layout ahead of time
+ // to avoid "Trying to snapshot GtkGizmo without a current allocation" error
+ layout ();
}
private void layout_into (int width, int height, out int book_width, out int book_height)
@@ -392,7 +422,7 @@ public class BookView : Gtk.Box
/* Try and fit without scrollbar */
var width = (int) allocation.width;
- var height = (int) (box_allocation.height - get_border_width () * 2);
+ var height = (int) (box_allocation.height);
int book_width, book_height;
layout_into (width, height, out book_width, out book_height);
@@ -402,17 +432,17 @@ public class BookView : Gtk.Box
/* Re-layout leaving space for scrollbar */
height = allocation.height;
layout_into (width, height, out book_width, out book_height);
-
+
/* Set scrollbar limits */
adjustment.lower = 0;
adjustment.upper = book_width;
adjustment.page_size = allocation.width;
-
+
/* Keep right-aligned */
var max_offset = book_width - allocation.width;
if (right_aligned || x_offset > max_offset)
x_offset = max_offset;
-
+
scroll.visible = true;
}
else
@@ -433,7 +463,7 @@ public class BookView : Gtk.Box
laying_out = false;
}
- private bool draw_cb (Gtk.Widget widget, Cairo.Context context)
+ public void draw_cb (Gtk.DrawingArea drawing_area, Cairo.Context context, int width, int height)
{
layout ();
@@ -444,7 +474,7 @@ public class BookView : Gtk.Box
for (var i = 0; i < book.n_pages; i++)
pages.append (get_nth_page (i));
- var ruler_color = get_style_context ().get_color (get_state_flags ());
+ var ruler_color = get_style_context ().get_color ();
Gdk.RGBA ruler_color_selected = {};
ruler_color_selected.parse("#3584e4"); /* Gnome Blue 3 */
@@ -470,8 +500,6 @@ public class BookView : Gtk.Box
page.width,
page.height);
}
-
- return false;
}
private PageView? get_page_at (int x, int y, out int x_, out int y_)
@@ -495,65 +523,86 @@ public class BookView : Gtk.Box
return null;
}
- private bool button_cb (Gtk.Widget widget, Gdk.EventButton event)
+ private void primary_pressed_cb (Gtk.GestureClick controler, int n_press, double x, double y)
+ {
+ button_cb(controler, Gdk.BUTTON_PRIMARY, true, n_press, x, y);
+ }
+
+ private void primary_released_cb (Gtk.GestureClick controler, int n_press, double x, double y)
+ {
+ button_cb(controler, Gdk.BUTTON_PRIMARY, false, n_press, x, y);
+ }
+
+ private void secondary_pressed_cb (Gtk.GestureClick controler, int n_press, double x, double y)
+ {
+ button_cb(controler, Gdk.BUTTON_SECONDARY, true, n_press, x, y);
+ }
+
+ private void secondary_released_cb (Gtk.GestureClick controler, int n_press, double x, double y)
+ {
+ button_cb(controler, Gdk.BUTTON_SECONDARY, false, n_press, x, y);
+ }
+
+ private void button_cb (Gtk.GestureClick controler, int button, bool press, int n_press, double dx, double dy)
{
layout ();
drawing_area.grab_focus ();
int x = 0, y = 0;
- if (event.type == Gdk.EventType.BUTTON_PRESS)
- select_page_view (get_page_at ((int) (event.x + x_offset), (int) event.y, out x, out y));
+ if (press)
+ select_page_view (get_page_at ((int) ((int) dx + x_offset), (int) dy, out x, out y));
if (selected_page_view == null)
- return false;
+ return;
/* Modify page */
- if (event.button == 1)
+ if (button == Gdk.BUTTON_PRIMARY)
{
- if (event.type == Gdk.EventType.BUTTON_PRESS)
+ if (press)
selected_page_view.button_press (x, y);
- else if (event.type == Gdk.EventType.BUTTON_RELEASE)
- selected_page_view.button_release (x, y);
- else if (event.type == Gdk.EventType.2BUTTON_PRESS)
+ else if (press && n_press == 2)
show_page (selected_page);
+ else if (!press)
+ selected_page_view.button_release (x, y);
}
/* Show pop-up menu on right click */
- if (event.button == 3)
- show_menu (event);
-
- return false;
+ if (button == Gdk.BUTTON_SECONDARY)
+ show_menu (drawing_area, dx, dy);
}
- private void set_cursor (Gdk.CursorType cursor)
+ private new void set_cursor (string cursor)
{
- Gdk.Cursor c;
-
if (this.cursor == cursor)
return;
this.cursor = cursor;
- c = new Gdk.Cursor.for_display (get_display (), cursor);
- drawing_area.get_window ().set_cursor (c);
+ Gdk.Cursor c = new Gdk.Cursor.from_name (cursor, null);
+ drawing_area.set_cursor (c);
}
- private bool motion_cb (Gtk.Widget widget, Gdk.EventMotion event)
+ private void motion_cb (Gtk.EventControllerMotion controler, double dx, double dy)
{
- Gdk.CursorType cursor = Gdk.CursorType.ARROW;
+ string cursor = "arrow";
+
+ int event_x = (int) dx;
+ int event_y = (int) dy;
+
+ var event_state = controler.get_current_event_state();
/* Dragging */
- if (selected_page_view != null && (event.state & Gdk.ModifierType.BUTTON1_MASK) != 0)
+ if (selected_page_view != null && (event_state & Gdk.ModifierType.BUTTON1_MASK) != 0)
{
- var x = (int) (event.x + x_offset - selected_page_view.x_offset);
- var y = (int) (event.y - selected_page_view.y_offset);
+ var x = (int) (event_x + x_offset - selected_page_view.x_offset);
+ var y = (int) (event_y - selected_page_view.y_offset);
selected_page_view.motion (x, y);
cursor = selected_page_view.cursor;
}
else
{
int x, y;
- var over_page = get_page_at ((int) (event.x + x_offset), (int) event.y, out x, out y);
+ var over_page = get_page_at ((int) (event_x + x_offset), (int) event_y, out x, out y);
if (over_page != null)
{
over_page.motion (x, y);
@@ -562,13 +611,11 @@ public class BookView : Gtk.Box
}
set_cursor (cursor);
-
- return false;
}
- private bool key_cb (Gtk.Widget widget, Gdk.EventKey event)
+ private bool key_cb (Gtk.EventControllerKey controler, uint keyval, uint keycode, Gdk.ModifierType state)
{
- switch (event.keyval)
+ switch (keyval)
{
case 0xff50: /* FIXME: GDK_Home */
selected_page = book.get_page (0);
@@ -588,10 +635,9 @@ public class BookView : Gtk.Box
}
}
- private bool focus_cb (Gtk.Widget widget, Gdk.EventFocus event)
+ private void focus_cb (Gtk.EventControllerFocus controler)
{
set_selected_page_view (selected_page_view);
- return false;
}
private void scroll_cb (Gtk.Adjustment adjustment)
diff --git a/src/book.vala b/src/book.vala
index e25eb35..7f8d048 100644
--- a/src/book.vala
+++ b/src/book.vala
@@ -237,7 +237,8 @@ private class BookSaver
encoder = new ThreadPool<EncodeTask>.with_owned_data (encode_delegate, (int) get_num_processors (), false);
/* Configure a writer */
- ThreadFunc<Error?>? write_delegate = null;
+ Thread<Error?> writer;
+
switch (mime_type)
{
case "image/jpeg":
@@ -245,13 +246,15 @@ private class BookSaver
#if HAVE_WEBP
case "image/webp":
#endif
- write_delegate = write_multifile;
+ writer = new Thread<Error?> (null, write_multifile);
break;
case "application/pdf":
- write_delegate = write_pdf;
+ writer = new Thread<Error?> (null, write_pdf);
+ break;
+ default:
+ writer = new Thread<Error?> (null, () => null);
break;
}
- var writer = new Thread<Error?> (null, write_delegate);
/* Issue encode tasks */
for (var i = 0; i < n_pages; i++)
diff --git a/src/drivers-dialog.vala b/src/drivers-dialog.vala
new file mode 100644
index 0000000..5a72376
--- /dev/null
+++ b/src/drivers-dialog.vala
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2023 Bartłomiej Maryńczak
+ * Author: Bartłomiej Maryńczak <marynczakbartlomiej@gmail.com>,
+ *
+ * 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/ui/drivers-dialog.ui")]
+private class DriversDialog : Gtk.Window
+{
+ [GtkChild]
+ private unowned Gtk.Revealer header_revealer;
+
+ [GtkChild]
+ private unowned Gtk.Label main_label;
+ [GtkChild]
+ private unowned Gtk.Label main_sublabel;
+
+ [GtkChild]
+ private unowned Gtk.Revealer progress_revealer;
+ [GtkChild]
+ private unowned Gtk.ProgressBar progress_bar;
+
+ [GtkChild]
+ private unowned Gtk.Label result_label;
+ [GtkChild]
+ private unowned Gtk.Label result_sublabel;
+ [GtkChild]
+ private unowned Gtk.Image result_icon;
+
+ [GtkChild]
+ private unowned Gtk.Stack stack;
+
+ private uint pulse_timer;
+ private string? missing_driver;
+
+ public DriversDialog (Gtk.Window parent, string? missing_driver)
+ {
+ this.missing_driver = missing_driver;
+ set_transient_for (parent);
+ }
+
+ ~DriversDialog () {
+ pulse_stop ();
+ }
+
+ private void pulse_start ()
+ {
+ pulse_stop ();
+ pulse_timer = GLib.Timeout.add(100, () => {
+ progress_bar.pulse ();
+ return Source.CONTINUE;
+ });
+ }
+
+ private void pulse_stop ()
+ {
+ Source.remove (pulse_timer);
+ }
+
+ public async void open ()
+ {
+ var message = "", instructions = "";
+ string[] packages_to_install = {};
+ switch (missing_driver)
+ {
+ case "brscan":
+ case "brscan2":
+ case "brscan3":
+ case "brscan4":
+ /* Message to indicate a Brother scanner has been detected */
+ message = _("You appear to have a Brother scanner.");
+ /* Instructions on how to install Brother scanner drivers */
+ instructions = _("Drivers for this are available on the <a href=\"http://support.brother.com\">Brother website</a>.");
+ break;
+ case "pixma":
+ /* Message to indicate a Canon Pixma scanner has been detected */
+ message = _("You appear to have a Canon scanner, which is supported by the <a href=\"http://www.sane-project.org/man/sane-pixma.5.html\">Pixma SANE backend</a>.");
+ /* Instructions on how to resolve issue with SANE scanner drivers */
+ instructions = _("Please check if your <a href=\"http://www.sane-project.org/sane-supported-devices.html\">scanner is supported by SANE</a>, otherwise report the issue to the <a href=\"https://alioth-lists.debian.net/cgi-bin/mailman/listinfo/sane-devel\">SANE mailing list</a>.");
+ break;
+ case "samsung":
+ /* Message to indicate a Samsung scanner has been detected */
+ message = _("You appear to have a Samsung scanner.");
+ /* Instructions on how to install Samsung scanner drivers.
+ Because HP acquired Samsung's global printing business in 2017, the support is made on HP site. */
+ instructions = _("Drivers for this are available on the <a href=\"https://support.hp.com\">HP website</a> (HP acquired Samsung's printing business).");
+ break;
+ case "hpaio":
+ case "smfp":
+ /* Message to indicate a HP scanner has been detected */
+ message = _("You appear to have an HP scanner.");
+ if (missing_driver == "hpaio")
+ packages_to_install = { "libsane-hpaio" };
+ else
+ /* Instructions on how to install HP scanner drivers.
+ smfp is rebranded and slightly modified Samsung devices,
+ for example: HP Laser MFP 135a is rebranded Samsung Xpress SL-M2070.
+ It require custom drivers, not available in hpaio package */
+ instructions = _("Drivers for this are available on the <a href=\"https://support.hp.com\">HP website</a>.");
+ break;
+ case "epkowa":
+ /* Message to indicate an Epson scanner has been detected */
+ message = _("You appear to have an Epson scanner.");
+ /* Instructions on how to install Epson scanner drivers */
+ instructions = _("Drivers for this are available on the <a href=\"http://support.epson.com\">Epson website</a>.");
+ break;
+ case "lexmark_nscan":
+ /* Message to indicate a Lexmark scanner has been detected */
+ message = _("You appear to have a Lexmark scanner.");
+ /* Instructions on how to install Lexmark scanner drivers */
+ instructions = _("Drivers for this are available on the <a href=\"http://support.lexmark.com\">Lexmark website</a>.");
+ break;
+ }
+
+ main_label.label = message;
+ main_sublabel.label = instructions;
+
+ if (packages_to_install.length > 0)
+ {
+#if HAVE_PACKAGEKIT
+ this.progress_revealer.reveal_child = true;
+ pulse_start();
+
+ main_sublabel.set_text (/* Label shown while installing drivers */
+ _("Installing drivers…"));
+
+ present ();
+
+ /* Label shown once drivers successfully installed */
+ var result_text = _("Drivers installed successfully!");
+ var success = true;
+ try
+ {
+ var results = yield install_packages(packages_to_install, () => {});
+
+ if (results.get_error_code () != null)
+ {
+ var e = results.get_error_code ();
+ /* Label shown if failed to install drivers */
+ result_text = _("Failed to install drivers (error code %d).").printf (e.code);
+ success = false;
+ }
+ }
+ catch (Error e)
+ {
+ /* Label shown if failed to install drivers */
+ result_text = _("Failed to install drivers.");
+ success = false;
+ warning ("Failed to install drivers: %s", e.message);
+ }
+
+ result_label.label = result_text;
+
+ if (success)
+ {
+ result_sublabel.label = _("Once installed you will need to restart this app.");
+ result_icon.icon_name = "emblem-ok-symbolic";
+ }
+ else
+ {
+ result_sublabel.visible = false;
+ result_icon.icon_name = "emblem-important-symbolic";
+ }
+
+ stack.set_visible_child_name ("result");
+ header_revealer.reveal_child = false;
+ progress_revealer.reveal_child = false;
+ pulse_stop ();
+#else
+ main_sublabel.set_text (/* Label shown to prompt user to install packages (when PackageKit not available) */
+ ngettext ("You need to install the %s package.", "You need to install the %s packages.", packages_to_install.length).printf (string.joinv (", ", packages_to_install)));
+ present ();
+#endif
+ }
+ }
+
+#if HAVE_PACKAGEKIT
+ private async Pk.Results? install_packages (string[] packages, Pk.ProgressCallback progress_callback) throws GLib.Error
+ {
+ var task = new Pk.Task ();
+ Pk.Results results;
+ results = yield task.resolve_async (Pk.Filter.NOT_INSTALLED, packages, null, progress_callback);
+ if (results == null || results.get_error_code () != null)
+ return results;
+
+ var package_array = results.get_package_array ();
+ var package_ids = new string[package_array.length + 1];
+ package_ids[package_array.length] = null;
+ for (var i = 0; i < package_array.length; i++)
+ package_ids[i] = package_array.data[i].get_id ();
+
+ return yield task.install_packages_async (package_ids, null, progress_callback);
+ }
+#endif
+}
diff --git a/src/meson.build b/src/meson.build
index 240b56d..3cafc7c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,5 +1,5 @@
vala_args = [ '--pkg=posix', '--vapidir=' + meson.current_source_dir () ]
-dependencies = [ glib_dep, gtk_dep, libhandy_dep, zlib_dep, cairo_dep, gdk_pixbuf_dep, gusb_dep, sane_dep ]
+dependencies = [ glib_dep, gtk_dep, libadwaita_dep, zlib_dep, cairo_dep, gdk_pixbuf_dep, gusb_dep, sane_dep ]
if colord_dep.found ()
vala_args += [ '-D', 'HAVE_COLORD' ]
dependencies += colord_dep
@@ -19,11 +19,13 @@ simple_scan = executable ('simple-scan',
'authorize-dialog.vala',
'book.vala',
'book-view.vala',
+ 'drivers-dialog.vala',
'page.vala',
- 'page-icon.vala',
+ 'page-texture.vala',
'page-view.vala',
'postprocessor.vala',
'preferences-dialog.vala',
+ 'reorder-pages-dialog.vala',
'simple-scan.vala',
'scanner.vala',
'screensaver.vala',
diff --git a/src/page-icon.vala b/src/page-icon.vala
deleted file mode 100644
index 3e3fa93..0000000
--- a/src/page-icon.vala
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2009-2017 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.
- */
-
-public class PageIcon : Gtk.DrawingArea
-{
- private char side;
- private int position;
- private int angle;
- private const int MINIMUM_WIDTH = 20;
-
- public PageIcon (char side, int position, int angle)
- {
- this.side = side;
- this.position = position;
- this.angle = angle;
- }
-
- 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);
-
- bool dark = Hdy.StyleManager.get_default ().dark;
- bool hc = Hdy.StyleManager.get_default ().high_contrast;
-
- if (dark && !hc)
- c.rectangle (1, 1, w - 2, h - 2);
- else
- c.rectangle (0, 0, w, h);
-
- Gdk.RGBA rgba = {};
-
- switch (side)
- {
- case 'F':
- /* Purple 2 */
- rgba.parse ("#c061cb");
- break;
- case 'B':
- /* Orange 3 */
- rgba.parse ("#ff7800");
- break;
- case 'U':
- /* green 4 */
- rgba.parse ("#5cc02e");
- break;
- case 'R':
- /* blue 4 */
- rgba.parse ("#0deee7");
- break;
- default:
- /* Yellow 3 to Red 2 */
- Gdk.RGBA start = {}, end = {};
- start.parse ("#f6d32d");
- end.parse ("#ed333b");
-
- double progress = position / 5.0;
- rgba.red = start.red + (end.red - start.red) * progress;
- rgba.green = start.green + (end.green - start.green) * progress;
- rgba.blue = start.blue + (end.blue - start.blue) * progress;
- break;
- }
-
- rgba.alpha = 0.3;
-
- Gdk.cairo_set_source_rgba (c, rgba);
- c.fill ();
-
- c.set_line_width (1.0);
- if (hc && dark)
- c.set_source_rgba (1, 1, 1, 0.5);
- else if (hc)
- c.set_source_rgba (0, 0, 0, 0.5);
- else
- c.set_source_rgba (0, 0, 0, 0.15);
-
- c.rectangle (0.5, 0.5, w - 1, h - 1);
- c.stroke ();
-
- if (dark)
- c.set_source_rgb (1, 1, 1);
- else
- c.set_source_rgb (0, 0, 0);
-
- var text = @"$(position + 1)";
- Cairo.TextExtents extents;
-
- var rad = Math.PI / 180.0 * angle;
- c.text_extents (text, out extents);
- c.translate ((w - extents.width) * 0.5 - 0.5, extents.height + (h - extents.height) * 0.5 - 0.5);
- c.rotate(rad);
- // only correct for 0 and 180 degree
- var tx = (1.0 - Math.sin(rad)) * extents.width / 2;
- var ty = (1.0 - Math.sin(rad)) * extents.height / 2;
- c.translate(-tx, +ty);
- c.show_text (text);
-
- return true;
- }
-}
diff --git a/src/page-texture.vala b/src/page-texture.vala
new file mode 100644
index 0000000..1e55433
--- /dev/null
+++ b/src/page-texture.vala
@@ -0,0 +1,663 @@
+/*
+ * Copyright (C) 2009-2015 Canonical Ltd.
+ * Author: Robert Ancell <robert.ancell@canonical.com>,
+ * Bartłomiej Maryńczak <marynczakbartlomiej@gmail.com>
+ *
+ * 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.
+ */
+
+private class PageToPixbuf : Object
+{
+ /* Image to render at current resolution */
+ public Gdk.Pixbuf? pixbuf { get { return pixbuf_; } }
+ private Gdk.Pixbuf? pixbuf_ = null;
+
+ /* Direction of currently scanned image */
+ private ScanDirection scan_direction;
+
+ /* Next scan line to render */
+ private int scan_line;
+
+ /* Dimensions of image to generate */
+ public int width;
+ public int height;
+
+ private static uchar get_sample (uchar[] pixels, int offset, int x, int depth, int sample)
+ {
+ // FIXME
+ return 0xFF;
+ }
+
+ private static void get_pixel (Page page, int x, int y, uchar[] pixel)
+ {
+ switch (page.scan_direction)
+ {
+ case ScanDirection.TOP_TO_BOTTOM:
+ break;
+ case ScanDirection.BOTTOM_TO_TOP:
+ x = page.scan_width - x - 1;
+ y = page.scan_height - y - 1;
+ break;
+ case ScanDirection.LEFT_TO_RIGHT:
+ var t = x;
+ x = page.scan_width - y - 1;
+ y = t;
+ break;
+ case ScanDirection.RIGHT_TO_LEFT:
+ var t = x;
+ x = y;
+ y = page.scan_height - t - 1;
+ break;
+ }
+
+ var depth = page.depth;
+ var n_channels = page.n_channels;
+ unowned uchar[] pixels = page.get_pixels ();
+ var offset = page.rowstride * y;
+
+ /* Optimise for 8 bit images */
+ if (depth == 8 && n_channels == 3)
+ {
+ var o = offset + x * n_channels;
+ pixel[0] = pixels[o];
+ pixel[1] = pixels[o+1];
+ pixel[2] = pixels[o+2];
+ return;
+ }
+ else if (depth == 8 && n_channels == 1)
+ {
+ pixel[0] = pixel[1] = pixel[2] = pixels[offset + x];
+ return;
+ }
+
+ /* Optimise for bitmaps */
+ else if (depth == 1 && n_channels == 1)
+ {
+ var o = offset + (x / 8);
+ pixel[0] = pixel[1] = pixel[2] = (pixels[o] & (0x80 >> (x % 8))) != 0 ? 0x00 : 0xFF;
+ return;
+ }
+
+ /* Optimise for 2 bit images */
+ else if (depth == 2 && n_channels == 1)
+ {
+ int block_shift[4] = { 6, 4, 2, 0 };
+
+ var o = offset + (x / 4);
+ var sample = (pixels[o] >> block_shift[x % 4]) & 0x3;
+ sample = sample * 255 / 3;
+
+ pixel[0] = pixel[1] = pixel[2] = (uchar) sample;
+ return;
+ }
+
+ /* Use slow method */
+ pixel[0] = get_sample (pixels, offset, x, depth, x * n_channels);
+ pixel[1] = get_sample (pixels, offset, x, depth, x * n_channels + 1);
+ pixel[2] = get_sample (pixels, offset, x, depth, x * n_channels + 2);
+ }
+
+ private static void set_pixel (Page page, double l, double r, double t, double b, uchar[] output, int offset)
+ {
+ /* Decimation:
+ *
+ * Target pixel is defined by (t,l)-(b,r)
+ * It touches 16 pixels in original image
+ * It completely covers 4 pixels in original image (T,L)-(B,R)
+ * Add covered pixels and add weighted partially covered pixels.
+ * Divide by total area.
+ *
+ * l L R r
+ * +-----+-----+-----+-----+
+ * | | | | |
+ * t | +--+-----+-----+---+ |
+ * T +--+--+-----+-----+---+-+
+ * | | | | | | |
+ * | | | | | | |
+ * +--+--+-----+-----+---+-+
+ * | | | | | | |
+ * | | | | | | |
+ * B +--+--+-----+-----+---+-+
+ * | | | | | | |
+ * b | +--+-----+-----+---+ |
+ * +-----+-----+-----+-----+
+ *
+ *
+ * Interpolation:
+ *
+ * l r
+ * +-----+-----+-----+-----+
+ * | | | | |
+ * | | | | |
+ * +-----+-----+-----+-----+
+ * t | | +-+--+ | |
+ * | | | | | | |
+ * +-----+---+-+--+--+-----+
+ * b | | +-+--+ | |
+ * | | | | |
+ * +-----+-----+-----+-----+
+ * | | | | |
+ * | | | | |
+ * +-----+-----+-----+-----+
+ *
+ * Same again, just no completely covered pixels.
+ */
+
+ var L = (int) l;
+ if (L != l)
+ L++;
+ var R = (int) r;
+ var T = (int) t;
+ if (T != t)
+ T++;
+ var B = (int) b;
+
+ var red = 0.0;
+ var green = 0.0;
+ var blue = 0.0;
+
+ /* Target can fit inside one source pixel
+ * +-----+
+ * | |
+ * | +--+| +-----+-----+ +-----+ +-----+ +-----+
+ * +-+--++ or | +-++ | or | +-+ | or | +--+| or | +--+
+ * | +--+| | +-++ | | +-+ | | | || | | |
+ * | | +-----+-----+ +-----+ +-+--++ +--+--+
+ * +-----+
+ */
+ if ((r - l <= 1.0 && (int)r == (int)l) || (b - t <= 1.0 && (int)b == (int)t))
+ {
+ /* Inside */
+ if ((int)l == (int)r || (int)t == (int)b)
+ {
+ uchar p[3];
+ get_pixel (page, (int)l, (int)t, p);
+ output[offset] = p[0];
+ output[offset+1] = p[1];
+ output[offset+2] = p[2];
+ return;
+ }
+
+ /* Stradling horizontal edge */
+ if (L > R)
+ {
+ uchar p[3];
+ get_pixel (page, R, T-1, p);
+ red += p[0] * (r-l)*(T-t);
+ green += p[1] * (r-l)*(T-t);
+ blue += p[2] * (r-l)*(T-t);
+ for (var y = T; y < B; y++)
+ {
+ get_pixel (page, R, y, p);
+ red += p[0] * (r-l);
+ green += p[1] * (r-l);
+ blue += p[2] * (r-l);
+ }
+ get_pixel (page, R, B, p);
+ red += p[0] * (r-l)*(b-B);
+ green += p[1] * (r-l)*(b-B);
+ blue += p[2] * (r-l)*(b-B);
+ }
+ /* Stradling vertical edge */
+ else
+ {
+ uchar p[3];
+ get_pixel (page, L - 1, B, p);
+ red += p[0] * (b-t)*(L-l);
+ green += p[1] * (b-t)*(L-l);
+ blue += p[2] * (b-t)*(L-l);
+ for (var x = L; x < R; x++) {
+ get_pixel (page, x, B, p);
+ red += p[0] * (b-t);
+ green += p[1] * (b-t);
+ blue += p[2] * (b-t);
+ }
+ get_pixel (page, R, B, p);
+ red += p[0] * (b-t)*(r-R);
+ green += p[1] * (b-t)*(r-R);
+ blue += p[2] * (b-t)*(r-R);
+ }
+
+ var scale = 1.0 / ((r - l) * (b - t));
+ output[offset] = (uchar)(red * scale + 0.5);
+ output[offset+1] = (uchar)(green * scale + 0.5);
+ output[offset+2] = (uchar)(blue * scale + 0.5);
+ return;
+ }
+
+ /* Add the middle pixels */
+ for (var x = L; x < R; x++)
+ {
+ for (var y = T; y < B; y++)
+ {
+ uchar p[3];
+ get_pixel (page, x, y, p);
+ red += p[0];
+ green += p[1];
+ blue += p[2];
+ }
+ }
+
+ /* Add the weighted top and bottom pixels */
+ for (var x = L; x < R; x++)
+ {
+ if (t != T)
+ {
+ uchar p[3];
+ get_pixel (page, x, T - 1, p);
+ red += p[0] * (T - t);
+ green += p[1] * (T - t);
+ blue += p[2] * (T - t);
+ }
+
+ if (b != B)
+ {
+ uchar p[3];
+ get_pixel (page, x, B, p);
+ red += p[0] * (b - B);
+ green += p[1] * (b - B);
+ blue += p[2] * (b - B);
+ }
+ }
+
+ /* Add the left and right pixels */
+ for (var y = T; y < B; y++)
+ {
+ if (l != L)
+ {
+ uchar p[3];
+ get_pixel (page, L - 1, y, p);
+ red += p[0] * (L - l);
+ green += p[1] * (L - l);
+ blue += p[2] * (L - l);
+ }
+
+ if (r != R)
+ {
+ uchar p[3];
+ get_pixel (page, R, y, p);
+ red += p[0] * (r - R);
+ green += p[1] * (r - R);
+ blue += p[2] * (r - R);
+ }
+ }
+
+ /* Add the corner pixels */
+ if (l != L && t != T)
+ {
+ uchar p[3];
+ get_pixel (page, L - 1, T - 1, p);
+ red += p[0] * (L - l)*(T - t);
+ green += p[1] * (L - l)*(T - t);
+ blue += p[2] * (L - l)*(T - t);
+ }
+ if (r != R && t != T)
+ {
+ uchar p[3];
+ get_pixel (page, R, T - 1, p);
+ red += p[0] * (r - R)*(T - t);
+ green += p[1] * (r - R)*(T - t);
+ blue += p[2] * (r - R)*(T - t);
+ }
+ if (r != R && b != B)
+ {
+ uchar p[3];
+ get_pixel (page, R, B, p);
+ red += p[0] * (r - R)*(b - B);
+ green += p[1] * (r - R)*(b - B);
+ blue += p[2] * (r - R)*(b - B);
+ }
+ if (l != L && b != B)
+ {
+ uchar p[3];
+ get_pixel (page, L - 1, B, p);
+ red += p[0] * (L - l)*(b - B);
+ green += p[1] * (L - l)*(b - B);
+ blue += p[2] * (L - l)*(b - B);
+ }
+
+ /* Scale pixel values and clamp in range [0, 255] */
+ var scale = 1.0 / ((r - l) * (b - t));
+ output[offset] = (uchar)(red * scale + 0.5);
+ output[offset+1] = (uchar)(green * scale + 0.5);
+ output[offset+2] = (uchar)(blue * scale + 0.5);
+ }
+
+ public static void update_preview (Page page, ref Gdk.Pixbuf? output_image, int output_width, int output_height,
+ ScanDirection scan_direction, int old_scan_line, int scan_line)
+ {
+ var input_width = page.width;
+ var input_height = page.height;
+
+ /* Create new image if one does not exist or has changed size */
+ int L, R, T, B;
+ if (output_image == null ||
+ output_image.width != output_width ||
+ output_image.height != output_height)
+ {
+ output_image = new Gdk.Pixbuf (Gdk.Colorspace.RGB,
+ false,
+ 8,
+ output_width,
+ output_height);
+
+ /* Update entire image */
+ L = 0;
+ R = output_width - 1;
+ T = 0;
+ B = output_height - 1;
+ }
+ /* Otherwise only update changed area */
+ else
+ {
+ switch (scan_direction)
+ {
+ case ScanDirection.TOP_TO_BOTTOM:
+ L = 0;
+ R = output_width - 1;
+ T = (int)((double)old_scan_line * output_height / input_height);
+ B = (int)((double)scan_line * output_height / input_height + 0.5);
+ break;
+ case ScanDirection.LEFT_TO_RIGHT:
+ L = (int)((double)old_scan_line * output_width / input_width);
+ R = (int)((double)scan_line * output_width / input_width + 0.5);
+ T = 0;
+ B = output_height - 1;
+ break;
+ case ScanDirection.BOTTOM_TO_TOP:
+ L = 0;
+ R = output_width - 1;
+ T = (int)((double)(input_height - scan_line) * output_height / input_height);
+ B = (int)((double)(input_height - old_scan_line) * output_height / input_height + 0.5);
+ break;
+ case ScanDirection.RIGHT_TO_LEFT:
+ L = (int)((double)(input_width - scan_line) * output_width / input_width);
+ R = (int)((double)(input_width - old_scan_line) * output_width / input_width + 0.5);
+ T = 0;
+ B = output_height - 1;
+ break;
+ default:
+ L = R = B = T = 0;
+ break;
+ }
+ }
+
+ /* FIXME: There's an off by one error in there somewhere... */
+ if (R >= output_width)
+ R = output_width - 1;
+ if (B >= output_height)
+ B = output_height - 1;
+
+ return_if_fail (L >= 0);
+ return_if_fail (R < output_width);
+ return_if_fail (T >= 0);
+ return_if_fail (B < output_height);
+ return_if_fail (output_image != null);
+
+ unowned uchar[] output = output_image.get_pixels ();
+ var output_rowstride = output_image.rowstride;
+ var output_n_channels = output_image.n_channels;
+
+ if (!page.has_data)
+ {
+ for (var x = L; x <= R; x++)
+ for (var y = T; y <= B; y++)
+ {
+ var o = output_rowstride * y + x * output_n_channels;
+ output[o] = output[o+1] = output[o+2] = 0xFF;
+ }
+ return;
+ }
+
+ /* Update changed area */
+ for (var x = L; x <= R; x++)
+ {
+ var l = (double)x * input_width / output_width;
+ var r = (double)(x + 1) * input_width / output_width;
+
+ for (var y = T; y <= B; y++)
+ {
+ var t = (double)y * input_height / output_height;
+ var b = (double)(y + 1) * input_height / output_height;
+
+ set_pixel (page,
+ l, r, t, b,
+ output, output_rowstride * y + x * output_n_channels);
+ }
+ }
+ }
+
+ public void update (Page page)
+ {
+ var old_scan_line = scan_line;
+ scan_line = page.scan_line;
+
+ /* Delete old image if scan direction changed */
+ var left_steps = scan_direction - page.scan_direction;
+ if (left_steps != 0 && pixbuf_ != null)
+ pixbuf_ = null;
+ scan_direction = page.scan_direction;
+
+ update_preview (page,
+ ref pixbuf_,
+ width,
+ height,
+ page.scan_direction,
+ old_scan_line,
+ scan_line);
+ }
+}
+
+/**
+ * Just update texture contents
+ */
+private class TextureUpdateTask
+{
+ public Page page;
+}
+
+/**
+ * Resize the texture
+ */
+private class TextureResizeTask: TextureUpdateTask
+{
+ public int width { get; private set; }
+ public int height { get; private set; }
+
+ public TextureResizeTask (int width, int height)
+ {
+ this.width = width;
+ this.height = height;
+ }
+}
+
+public class PageViewTexture : Object
+{
+ public Gdk.Pixbuf? pixbuf { get; private set; }
+ public signal void new_buffer ();
+
+ private int requested_width;
+ private int requested_height;
+ private TextureUpdateTask queued = null;
+
+ private bool in_proggres;
+
+ private ThreadPool<TextureUpdateTask> resize_pool;
+
+ private Page page;
+
+ public PageViewTexture (Page page)
+ {
+ this.page = page;
+
+ try {
+ resize_pool = new ThreadPool<TextureUpdateTask>.with_owned_data (thread_func, 1, false);
+ }
+ catch (ThreadError error)
+ {
+ // Pool is non-exclusive so this should never happen
+ }
+ }
+
+ /**
+ * Notify that data needs updating (eg. pixels changed during scanning process)
+ */
+ public void request_update ()
+ {
+ queued = new TextureUpdateTask ();
+ }
+
+ /**
+ * Set size of the page, ignored if size did not change.
+ */
+ public void request_resize (int width, int height)
+ {
+ if (requested_width == width && requested_height == height)
+ {
+ return;
+ }
+
+ requested_width = width;
+ requested_height = height;
+
+ queued = new TextureResizeTask (requested_width, requested_height);
+ }
+
+ public void queue_update () throws ThreadError
+ {
+ if (in_proggres || queued == null)
+ {
+ return;
+ }
+
+ in_proggres = true;
+
+ // We copy the page as it will be sent to resize thread
+ queued.page = page.copy ();
+ resize_pool.add (queued);
+
+ queued = null;
+ }
+
+ private PageToPixbuf page_view = new PageToPixbuf ();
+ private void thread_func(owned TextureUpdateTask task)
+ {
+ if (task is TextureResizeTask)
+ {
+ page_view.width = task.width;
+ page_view.height = task.height;
+ }
+
+ page_view.update (task.page);
+
+ Gdk.Pixbuf? new_pixbuf = null;
+ if (page_view.pixbuf != null)
+ {
+ // We are sending this buffer back to main thread, therefore copy
+ new_pixbuf = page_view.pixbuf.copy ();
+ }
+
+
+ Idle.add(() => {
+ new_pixbuf_cb (new_pixbuf);
+ return false;
+ });
+ }
+
+ private void new_pixbuf_cb (Gdk.Pixbuf? pixbuf)
+ {
+ in_proggres = false;
+ this.pixbuf = pixbuf;
+ new_buffer ();
+ }
+}
+
+public class PagePaintable: Gdk.Paintable, Object
+{
+ private Page page;
+ private PageViewTexture page_texture;
+ private Gdk.Texture? texture;
+
+ public PagePaintable (Page page)
+ {
+ this.page = page;
+ page.pixels_changed.connect (pixels_changed);
+ page.size_changed.connect (pixels_changed);
+ page.scan_direction_changed.connect (pixels_changed);
+
+ page_texture = new PageViewTexture (page);
+ page_texture.new_buffer.connect (texture_updated);
+
+ pixels_changed ();
+ }
+
+ ~PagePaintable ()
+ {
+ page.pixels_changed.disconnect (pixels_changed);
+ page.size_changed.disconnect (pixels_changed);
+ page.scan_direction_changed.disconnect (pixels_changed);
+ page_texture.new_buffer.disconnect (texture_updated);
+ }
+
+ private void pixels_changed ()
+ {
+ page_texture.request_update ();
+ try {
+ page_texture.queue_update ();
+ }
+ catch (Error e)
+ {
+ warning ("Failed to queue_update of the texture: %s", e.message);
+ invalidate_contents ();
+ }
+ }
+
+ private void texture_updated ()
+ {
+ if (page_texture.pixbuf != null)
+ texture = Gdk.Texture.for_pixbuf(page_texture.pixbuf);
+ else
+ texture = null;
+
+ invalidate_contents ();
+ }
+
+ public override double get_intrinsic_aspect_ratio ()
+ {
+ return (double) page.width / (double) page.height;
+ }
+
+ public void snapshot (Gdk.Snapshot gdk_snapshot, double width, double height) {
+ var snapshot = (Gtk.Snapshot) gdk_snapshot;
+
+ var rect = Graphene.Rect();
+ rect.size.width = (float) width;
+ rect.size.height = (float) height;
+
+ page_texture.request_resize ((int) width, (int) height);
+
+ try {
+ page_texture.queue_update ();
+ }
+ catch (Error e)
+ {
+ warning ("Failed to queue_update of the texture: %s", e.message);
+ // Ask for another redraw
+ invalidate_contents ();
+ }
+
+ if (texture != null)
+ {
+ snapshot.append_texture(texture, rect);
+ }
+ else
+ {
+ snapshot.append_color ({1.0f, 1.0f, 1.0f, 1.0f}, rect);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/page-view.vala b/src/page-view.vala
index 148dcca..9ef83de 100644
--- a/src/page-view.vala
+++ b/src/page-view.vala
@@ -29,7 +29,7 @@ public class PageView : Object
public Page page { get; private set; }
/* Image to render at current resolution */
- private Gdk.Pixbuf? image = null;
+ private PageViewTexture page_texture;
/* Border around image */
private bool selected_ = false;
@@ -52,12 +52,6 @@ public class PageView : Object
/* True if image needs to be regenerated */
private bool update_image = true;
- /* Direction of currently scanned image */
- private ScanDirection scan_direction;
-
- /* Next scan line to render */
- private int scan_line;
-
/* Dimensions of image to generate */
private int width_;
private int height_;
@@ -75,7 +69,7 @@ public class PageView : Object
private int selected_crop_h;
/* Cursor over this page */
- public Gdk.CursorType cursor { get; private set; default = Gdk.CursorType.ARROW; }
+ public string cursor { get; private set; default = "arrow"; }
private int animate_n_segments = 7;
private int animate_segment;
@@ -92,6 +86,9 @@ public class PageView : Object
page.crop_changed.connect (page_overlay_changed_cb);
page.scan_line_changed.connect (page_overlay_changed_cb);
page.scan_direction_changed.connect (scan_direction_changed_cb);
+
+ page_texture = new PageViewTexture(page);
+ page_texture.new_buffer.connect (new_buffer_cb);
}
~PageView ()
@@ -101,411 +98,13 @@ public class PageView : Object
page.crop_changed.disconnect (page_overlay_changed_cb);
page.scan_line_changed.disconnect (page_overlay_changed_cb);
page.scan_direction_changed.disconnect (scan_direction_changed_cb);
- }
-
- private uchar get_sample (uchar[] pixels, int offset, int x, int depth, int sample)
- {
- // FIXME
- return 0xFF;
- }
-
- private void get_pixel (Page page, int x, int y, uchar[] pixel)
- {
- switch (page.scan_direction)
- {
- case ScanDirection.TOP_TO_BOTTOM:
- break;
- case ScanDirection.BOTTOM_TO_TOP:
- x = page.scan_width - x - 1;
- y = page.scan_height - y - 1;
- break;
- case ScanDirection.LEFT_TO_RIGHT:
- var t = x;
- x = page.scan_width - y - 1;
- y = t;
- break;
- case ScanDirection.RIGHT_TO_LEFT:
- var t = x;
- x = y;
- y = page.scan_height - t - 1;
- break;
- }
-
- var depth = page.depth;
- var n_channels = page.n_channels;
- unowned uchar[] pixels = page.get_pixels ();
- var offset = page.rowstride * y;
-
- /* Optimise for 8 bit images */
- if (depth == 8 && n_channels == 3)
- {
- var o = offset + x * n_channels;
- pixel[0] = pixels[o];
- pixel[1] = pixels[o+1];
- pixel[2] = pixels[o+2];
- return;
- }
- else if (depth == 8 && n_channels == 1)
- {
- pixel[0] = pixel[1] = pixel[2] = pixels[offset + x];
- return;
- }
-
- /* Optimise for bitmaps */
- else if (depth == 1 && n_channels == 1)
- {
- var o = offset + (x / 8);
- pixel[0] = pixel[1] = pixel[2] = (pixels[o] & (0x80 >> (x % 8))) != 0 ? 0x00 : 0xFF;
- return;
- }
-
- /* Optimise for 2 bit images */
- else if (depth == 2 && n_channels == 1)
- {
- int block_shift[4] = { 6, 4, 2, 0 };
-
- var o = offset + (x / 4);
- var sample = (pixels[o] >> block_shift[x % 4]) & 0x3;
- sample = sample * 255 / 3;
-
- pixel[0] = pixel[1] = pixel[2] = (uchar) sample;
- return;
- }
-
- /* Use slow method */
- pixel[0] = get_sample (pixels, offset, x, depth, x * n_channels);
- pixel[1] = get_sample (pixels, offset, x, depth, x * n_channels + 1);
- pixel[2] = get_sample (pixels, offset, x, depth, x * n_channels + 2);
- }
-
- private void set_pixel (Page page, double l, double r, double t, double b, uchar[] output, int offset)
- {
- /* Decimation:
- *
- * Target pixel is defined by (t,l)-(b,r)
- * It touches 16 pixels in original image
- * It completely covers 4 pixels in original image (T,L)-(B,R)
- * Add covered pixels and add weighted partially covered pixels.
- * Divide by total area.
- *
- * l L R r
- * +-----+-----+-----+-----+
- * | | | | |
- * t | +--+-----+-----+---+ |
- * T +--+--+-----+-----+---+-+
- * | | | | | | |
- * | | | | | | |
- * +--+--+-----+-----+---+-+
- * | | | | | | |
- * | | | | | | |
- * B +--+--+-----+-----+---+-+
- * | | | | | | |
- * b | +--+-----+-----+---+ |
- * +-----+-----+-----+-----+
- *
- *
- * Interpolation:
- *
- * l r
- * +-----+-----+-----+-----+
- * | | | | |
- * | | | | |
- * +-----+-----+-----+-----+
- * t | | +-+--+ | |
- * | | | | | | |
- * +-----+---+-+--+--+-----+
- * b | | +-+--+ | |
- * | | | | |
- * +-----+-----+-----+-----+
- * | | | | |
- * | | | | |
- * +-----+-----+-----+-----+
- *
- * Same again, just no completely covered pixels.
- */
-
- var L = (int) l;
- if (L != l)
- L++;
- var R = (int) r;
- var T = (int) t;
- if (T != t)
- T++;
- var B = (int) b;
-
- var red = 0.0;
- var green = 0.0;
- var blue = 0.0;
-
- /* Target can fit inside one source pixel
- * +-----+
- * | |
- * | +--+| +-----+-----+ +-----+ +-----+ +-----+
- * +-+--++ or | +-++ | or | +-+ | or | +--+| or | +--+
- * | +--+| | +-++ | | +-+ | | | || | | |
- * | | +-----+-----+ +-----+ +-+--++ +--+--+
- * +-----+
- */
- if ((r - l <= 1.0 && (int)r == (int)l) || (b - t <= 1.0 && (int)b == (int)t))
- {
- /* Inside */
- if ((int)l == (int)r || (int)t == (int)b)
- {
- uchar p[3];
- get_pixel (page, (int)l, (int)t, p);
- output[offset] = p[0];
- output[offset+1] = p[1];
- output[offset+2] = p[2];
- return;
- }
-
- /* Stradling horizontal edge */
- if (L > R)
- {
- uchar p[3];
- get_pixel (page, R, T-1, p);
- red += p[0] * (r-l)*(T-t);
- green += p[1] * (r-l)*(T-t);
- blue += p[2] * (r-l)*(T-t);
- for (var y = T; y < B; y++)
- {
- get_pixel (page, R, y, p);
- red += p[0] * (r-l);
- green += p[1] * (r-l);
- blue += p[2] * (r-l);
- }
- get_pixel (page, R, B, p);
- red += p[0] * (r-l)*(b-B);
- green += p[1] * (r-l)*(b-B);
- blue += p[2] * (r-l)*(b-B);
- }
- /* Stradling vertical edge */
- else
- {
- uchar p[3];
- get_pixel (page, L - 1, B, p);
- red += p[0] * (b-t)*(L-l);
- green += p[1] * (b-t)*(L-l);
- blue += p[2] * (b-t)*(L-l);
- for (var x = L; x < R; x++) {
- get_pixel (page, x, B, p);
- red += p[0] * (b-t);
- green += p[1] * (b-t);
- blue += p[2] * (b-t);
- }
- get_pixel (page, R, B, p);
- red += p[0] * (b-t)*(r-R);
- green += p[1] * (b-t)*(r-R);
- blue += p[2] * (b-t)*(r-R);
- }
-
- var scale = 1.0 / ((r - l) * (b - t));
- output[offset] = (uchar)(red * scale + 0.5);
- output[offset+1] = (uchar)(green * scale + 0.5);
- output[offset+2] = (uchar)(blue * scale + 0.5);
- return;
- }
-
- /* Add the middle pixels */
- for (var x = L; x < R; x++)
- {
- for (var y = T; y < B; y++)
- {
- uchar p[3];
- get_pixel (page, x, y, p);
- red += p[0];
- green += p[1];
- blue += p[2];
- }
- }
-
- /* Add the weighted top and bottom pixels */
- for (var x = L; x < R; x++)
- {
- if (t != T)
- {
- uchar p[3];
- get_pixel (page, x, T - 1, p);
- red += p[0] * (T - t);
- green += p[1] * (T - t);
- blue += p[2] * (T - t);
- }
-
- if (b != B)
- {
- uchar p[3];
- get_pixel (page, x, B, p);
- red += p[0] * (b - B);
- green += p[1] * (b - B);
- blue += p[2] * (b - B);
- }
- }
-
- /* Add the left and right pixels */
- for (var y = T; y < B; y++)
- {
- if (l != L)
- {
- uchar p[3];
- get_pixel (page, L - 1, y, p);
- red += p[0] * (L - l);
- green += p[1] * (L - l);
- blue += p[2] * (L - l);
- }
-
- if (r != R)
- {
- uchar p[3];
- get_pixel (page, R, y, p);
- red += p[0] * (r - R);
- green += p[1] * (r - R);
- blue += p[2] * (r - R);
- }
- }
- /* Add the corner pixels */
- if (l != L && t != T)
- {
- uchar p[3];
- get_pixel (page, L - 1, T - 1, p);
- red += p[0] * (L - l)*(T - t);
- green += p[1] * (L - l)*(T - t);
- blue += p[2] * (L - l)*(T - t);
- }
- if (r != R && t != T)
- {
- uchar p[3];
- get_pixel (page, R, T - 1, p);
- red += p[0] * (r - R)*(T - t);
- green += p[1] * (r - R)*(T - t);
- blue += p[2] * (r - R)*(T - t);
- }
- if (r != R && b != B)
- {
- uchar p[3];
- get_pixel (page, R, B, p);
- red += p[0] * (r - R)*(b - B);
- green += p[1] * (r - R)*(b - B);
- blue += p[2] * (r - R)*(b - B);
- }
- if (l != L && b != B)
- {
- uchar p[3];
- get_pixel (page, L - 1, B, p);
- red += p[0] * (L - l)*(b - B);
- green += p[1] * (L - l)*(b - B);
- blue += p[2] * (L - l)*(b - B);
- }
-
- /* Scale pixel values and clamp in range [0, 255] */
- var scale = 1.0 / ((r - l) * (b - t));
- output[offset] = (uchar)(red * scale + 0.5);
- output[offset+1] = (uchar)(green * scale + 0.5);
- output[offset+2] = (uchar)(blue * scale + 0.5);
+ page_texture.new_buffer.disconnect (new_buffer_cb);
}
-
- private void update_preview (Page page, ref Gdk.Pixbuf? output_image, int output_width, int output_height,
- ScanDirection scan_direction, int old_scan_line, int scan_line)
+
+ private void new_buffer_cb()
{
- var input_width = page.width;
- var input_height = page.height;
-
- /* Create new image if one does not exist or has changed size */
- int L, R, T, B;
- if (output_image == null ||
- output_image.width != output_width ||
- output_image.height != output_height)
- {
- output_image = new Gdk.Pixbuf (Gdk.Colorspace.RGB,
- false,
- 8,
- output_width,
- output_height);
-
- /* Update entire image */
- L = 0;
- R = output_width - 1;
- T = 0;
- B = output_height - 1;
- }
- /* Otherwise only update changed area */
- else
- {
- switch (scan_direction)
- {
- case ScanDirection.TOP_TO_BOTTOM:
- L = 0;
- R = output_width - 1;
- T = (int)((double)old_scan_line * output_height / input_height);
- B = (int)((double)scan_line * output_height / input_height + 0.5);
- break;
- case ScanDirection.LEFT_TO_RIGHT:
- L = (int)((double)old_scan_line * output_width / input_width);
- R = (int)((double)scan_line * output_width / input_width + 0.5);
- T = 0;
- B = output_height - 1;
- break;
- case ScanDirection.BOTTOM_TO_TOP:
- L = 0;
- R = output_width - 1;
- T = (int)((double)(input_height - scan_line) * output_height / input_height);
- B = (int)((double)(input_height - old_scan_line) * output_height / input_height + 0.5);
- break;
- case ScanDirection.RIGHT_TO_LEFT:
- L = (int)((double)(input_width - scan_line) * output_width / input_width);
- R = (int)((double)(input_width - old_scan_line) * output_width / input_width + 0.5);
- T = 0;
- B = output_height - 1;
- break;
- default:
- L = R = B = T = 0;
- break;
- }
- }
-
- /* FIXME: There's an off by one error in there somewhere... */
- if (R >= output_width)
- R = output_width - 1;
- if (B >= output_height)
- B = output_height - 1;
-
- return_if_fail (L >= 0);
- return_if_fail (R < output_width);
- return_if_fail (T >= 0);
- return_if_fail (B < output_height);
- return_if_fail (output_image != null);
-
- unowned uchar[] output = output_image.get_pixels ();
- var output_rowstride = output_image.rowstride;
- var output_n_channels = output_image.n_channels;
-
- if (!page.has_data)
- {
- for (var x = L; x <= R; x++)
- for (var y = T; y <= B; y++)
- {
- var o = output_rowstride * y + x * output_n_channels;
- output[o] = output[o+1] = output[o+2] = 0xFF;
- }
- return;
- }
-
- /* Update changed area */
- for (var x = L; x <= R; x++)
- {
- var l = (double)x * input_width / output_width;
- var r = (double)(x + 1) * input_width / output_width;
-
- for (var y = T; y <= B; y++)
- {
- var t = (double)y * input_height / output_height;
- var b = (double)(y + 1) * input_height / output_height;
-
- set_pixel (page,
- l, r, t, b,
- output, output_rowstride * y + x * output_n_channels);
- }
- }
+ changed ();
}
private int get_preview_width ()
@@ -518,30 +117,6 @@ public class PageView : Object
return height_ - (border_width + ruler_width) * 2;
}
- private void update_page_view ()
- {
- if (!update_image)
- return;
-
- var old_scan_line = scan_line;
- var scan_line = page.scan_line;
-
- /* Delete old image if scan direction changed */
- var left_steps = scan_direction - page.scan_direction;
- if (left_steps != 0 && image != null)
- image = null;
- scan_direction = page.scan_direction;
-
- update_preview (page,
- ref image,
- get_preview_width (),
- get_preview_height (),
- page.scan_direction, old_scan_line, scan_line);
-
- update_image = false;
- this.scan_line = scan_line;
- }
-
private int page_to_screen_x (int x)
{
return (int) ((double)x * get_preview_width () / page.width + 0.5);
@@ -642,38 +217,39 @@ public class PageView : Object
public void motion (int x, int y)
{
var location = get_crop_location (x, y);
- Gdk.CursorType cursor;
+
+ string cursor;
switch (location)
{
case CropLocation.MIDDLE:
- cursor = Gdk.CursorType.HAND1;
+ cursor = "hand1";
break;
case CropLocation.TOP:
- cursor = Gdk.CursorType.TOP_SIDE;
+ cursor = "top_side";
break;
case CropLocation.BOTTOM:
- cursor = Gdk.CursorType.BOTTOM_SIDE;
+ cursor = "bottom_side";
break;
case CropLocation.LEFT:
- cursor = Gdk.CursorType.LEFT_SIDE;
+ cursor = "left_side";
break;
case CropLocation.RIGHT:
- cursor = Gdk.CursorType.RIGHT_SIDE;
+ cursor = "right_side";
break;
case CropLocation.TOP_LEFT:
- cursor = Gdk.CursorType.TOP_LEFT_CORNER;
+ cursor = "top_left_corner";
break;
case CropLocation.TOP_RIGHT:
- cursor = Gdk.CursorType.TOP_RIGHT_CORNER;
+ cursor = "top_right_corner";
break;
case CropLocation.BOTTOM_LEFT:
- cursor = Gdk.CursorType.BOTTOM_LEFT_CORNER;
+ cursor = "bottom_left_corner";
break;
case CropLocation.BOTTOM_RIGHT:
- cursor = Gdk.CursorType.BOTTOM_RIGHT_CORNER;
+ cursor = "bottom_right_corner";
break;
default:
- cursor = Gdk.CursorType.ARROW;
+ cursor = "arrow";
break;
}
@@ -828,7 +404,18 @@ public class PageView : Object
public void render (Cairo.Context context, Gdk.RGBA ruler_color)
{
update_animation ();
- update_page_view ();
+
+ page_texture.request_resize (get_preview_width (), get_preview_height ());
+
+ try {
+ page_texture.queue_update ();
+ }
+ catch (Error e)
+ {
+ warning ("Failed to queue_update of the texture: %s", e.message);
+ // Ask for another redraw
+ changed ();
+ }
var w = get_preview_width ();
var h = get_preview_height ();
@@ -838,8 +425,26 @@ public class PageView : Object
/* Draw image */
context.translate (border_width + ruler_width, border_width + ruler_width);
- Gdk.cairo_set_source_pixbuf (context, image, 0, 0);
- context.paint ();
+
+ if (page_texture.pixbuf != null)
+ {
+ float x_scale = (float) w / (float) page_texture.pixbuf.width;
+ float y_scale = (float) h / (float) page_texture.pixbuf.height;
+
+ context.save ();
+ context.scale(x_scale, y_scale);
+
+ // context.rectangle (0, 0.0, w, h);
+ Gdk.cairo_set_source_pixbuf (context, page_texture.pixbuf, 0, 0);
+ context.paint ();
+ context.restore ();
+ }
+ else
+ {
+ Gdk.cairo_set_source_rgba (context, {1.0f, 1.0f, 1.0f, 1.0f});
+ context.rectangle (0, 0.0, w, h);
+ context.fill ();
+ }
/* Draw page border */
Gdk.cairo_set_source_rgba (context, ruler_color);
@@ -1037,6 +642,7 @@ public class PageView : Object
{
/* Regenerate image */
update_image = true;
+ page_texture.request_update ();
changed ();
}
@@ -1057,6 +663,7 @@ public class PageView : Object
{
/* Regenerate image */
update_image = true;
+ page_texture.request_update ();
size_changed ();
changed ();
}
diff --git a/src/page.vala b/src/page.vala
index cfe70e1..67c8010 100644
--- a/src/page.vala
+++ b/src/page.vala
@@ -219,6 +219,31 @@ public class Page : Object
this.crop_width = (crop_x + crop_width > scan_width) ? scan_width : crop_width;
this.crop_height = (crop_y + crop_height > scan_height) ? scan_height : crop_height;
}
+
+ public Page copy()
+ {
+ var copy = new Page.from_data (
+ scan_width,
+ scan_height,
+ rowstride,
+ n_channels,
+ depth,
+ dpi,
+ scan_direction,
+ color_profile,
+ pixels,
+ has_crop,
+ crop_name,
+ crop_x,
+ crop_y,
+ crop_width,
+ crop_height
+ );
+
+ copy.scan_line = scan_line;
+
+ return copy;
+ }
public void set_page_info (ScanPageInfo info)
{
@@ -649,10 +674,9 @@ public class Page : Object
public void copy_to_clipboard (Gtk.Window window)
{
- var display = window.get_display ();
- var clipboard = Gtk.Clipboard.get_for_display (display, Gdk.SELECTION_CLIPBOARD);
+ var clipboard = window.get_clipboard();
var image = get_image (true);
- clipboard.set_image (image);
+ clipboard.set_value (image);
}
public void save_png (File file) throws Error
diff --git a/src/preferences-dialog.vala b/src/preferences-dialog.vala
index 02bbaf8..07fcf42 100644
--- a/src/preferences-dialog.vala
+++ b/src/preferences-dialog.vala
@@ -10,48 +10,75 @@
* license.
*/
+private class DpiItem: Object
+{
+ public int dpi;
+ public string label;
+
+ public DpiItem(int dpi, string label)
+ {
+ this.dpi = dpi;
+ this.label = label;
+ }
+}
+
+private class PaperSizeItem: Object
+{
+ public string label;
+ public int width;
+ public int height;
+
+ public PaperSizeItem(string label, int width, int height)
+ {
+ this.label = label;
+ this.width = width;
+ this.height = height;
+ }
+}
+
[GtkTemplate (ui = "/org/gnome/SimpleScan/ui/preferences-dialog.ui")]
-private class PreferencesDialog : Hdy.PreferencesWindow
+private class PreferencesDialog : Adw.PreferencesWindow
{
private Settings settings;
[GtkChild]
- private unowned Gtk.ComboBox text_dpi_combo;
+ private unowned Adw.ComboRow text_dpi_row;
[GtkChild]
- private unowned Gtk.ComboBox photo_dpi_combo;
+ private unowned Adw.ComboRow photo_dpi_row;
[GtkChild]
- private unowned Gtk.ComboBox paper_size_combo;
+ private unowned Adw.ComboRow paper_size_row;
[GtkChild]
private unowned Gtk.Scale brightness_scale;
[GtkChild]
private unowned Gtk.Scale contrast_scale;
[GtkChild]
- private unowned Gtk.RadioButton page_delay_0s_button;
- [GtkChild]
- private unowned Gtk.RadioButton page_delay_3s_button;
- [GtkChild]
- private unowned Gtk.RadioButton page_delay_6s_button;
+ private unowned Gtk.Scale compression_scale;
[GtkChild]
- private unowned Gtk.RadioButton page_delay_10s_button;
+ private unowned Gtk.ToggleButton page_delay_0s_button;
[GtkChild]
- private unowned Gtk.RadioButton page_delay_15s_button;
+ private unowned Gtk.ToggleButton page_delay_3s_button;
[GtkChild]
- private unowned Gtk.ListStore text_dpi_model;
+ private unowned Gtk.ToggleButton page_delay_6s_button;
[GtkChild]
- private unowned Gtk.ListStore photo_dpi_model;
+ private unowned Gtk.ToggleButton page_delay_10s_button;
[GtkChild]
- private unowned Gtk.RadioButton front_side_button;
+ private unowned Gtk.ToggleButton page_delay_15s_button;
+ private ListStore text_dpi_model;
+ private ListStore photo_dpi_model;
[GtkChild]
- private unowned Gtk.RadioButton back_side_button;
+ private unowned Gtk.ToggleButton front_side_button;
[GtkChild]
- private unowned Gtk.RadioButton both_side_button;
+ private unowned Gtk.ToggleButton back_side_button;
[GtkChild]
- private unowned Gtk.ListStore paper_size_model;
+ private unowned Gtk.ToggleButton both_side_button;
+ private ListStore paper_size_model;
[GtkChild]
private unowned Gtk.Adjustment brightness_adjustment;
[GtkChild]
private unowned Gtk.Adjustment contrast_adjustment;
[GtkChild]
+ private unowned Gtk.Adjustment compression_adjustment;
+ [GtkChild]
private unowned Gtk.Switch postproc_enable_switch;
[GtkChild]
private unowned Gtk.Entry postproc_script_entry;
@@ -60,50 +87,77 @@ private class PreferencesDialog : Hdy.PreferencesWindow
[GtkChild]
private unowned Gtk.Switch postproc_keep_original_switch;
+ static string get_dpi_label (DpiItem device) {
+ return device.label;
+ }
+
+ static string get_page_size_label (PaperSizeItem size) {
+ return size.label;
+ }
+
public PreferencesDialog (Settings settings)
{
this.settings = settings;
- 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, 2970, 1, 4200, 2, "A3", -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);
+ paper_size_row.expression = new Gtk.CClosureExpression (
+ typeof (string),
+ null,
+ {},
+ (Callback) get_page_size_label,
+ null,
+ null
+ );
+
+ paper_size_model = new ListStore (typeof (PaperSizeItem));
+ /* Combo box value for automatic paper size */
+ paper_size_model.append (new PaperSizeItem (_("Automatic"), 0, 0));
+ paper_size_model.append (new PaperSizeItem ("A6", 1050, 1480));
+ paper_size_model.append (new PaperSizeItem ("A5", 1480, 2100));
+ paper_size_model.append (new PaperSizeItem ("A4", 2100, 2970));
+ paper_size_model.append (new PaperSizeItem ("A3", 2970, 4200));
+ paper_size_model.append (new PaperSizeItem ("Letter", 2159, 2794));
+ paper_size_model.append (new PaperSizeItem ("Legal", 2159, 3556));
+ paper_size_model.append (new PaperSizeItem ("4×6", 1016, 1524));
+ paper_size_row.model = paper_size_model;
+
+ text_dpi_row.expression = new Gtk.CClosureExpression (
+ typeof (string),
+ null,
+ {},
+ (Callback) get_dpi_label,
+ null,
+ null
+ );
+ text_dpi_model = new ListStore (typeof (DpiItem));
+ text_dpi_row.model = text_dpi_model;
+
+ photo_dpi_row.expression = new Gtk.CClosureExpression (
+ typeof (string),
+ null,
+ {},
+ (Callback) get_dpi_label,
+ null,
+ null
+ );
+ photo_dpi_model = new ListStore (typeof (DpiItem));
+ photo_dpi_row.model = photo_dpi_model;
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 ()); });
+ set_dpi_combo (text_dpi_row, DEFAULT_TEXT_DPI, dpi);
+ text_dpi_row.notify["selected"].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 ()); });
+ set_dpi_combo (photo_dpi_row, DEFAULT_PHOTO_DPI, dpi);
+ photo_dpi_row.notify["selected"].connect (() => { settings.set_int ("photo-dpi", get_photo_dpi ()); });
set_page_side ((ScanSide) settings.get_enum ("page-side"));
front_side_button.toggled.connect ((button) => { if (button.active) settings.set_enum ("page-side", ScanSide.FRONT); });
back_side_button.toggled.connect ((button) => { if (button.active) settings.set_enum ("page-side", ScanSide.BACK); });
both_side_button.toggled.connect ((button) => { if (button.active) settings.set_enum ("page-side", ScanSide.BOTH); });
- var renderer = new Gtk.CellRendererText ();
- paper_size_combo.pack_start (renderer, true);
- paper_size_combo.add_attribute (renderer, "text", 2);
-
var lower = brightness_adjustment.lower;
var darker_label = "<small>%s</small>".printf (_("Darker"));
var upper = brightness_adjustment.upper;
@@ -123,11 +177,20 @@ private class PreferencesDialog : Hdy.PreferencesWindow
contrast_scale.add_mark (upper, Gtk.PositionType.BOTTOM, more_label);
contrast_adjustment.value = settings.get_int ("contrast");
contrast_adjustment.value_changed.connect (() => { settings.set_int ("contrast", get_contrast ()); });
+
+ var minimum_size_label = "<small>%s</small>".printf (_("Minimum size"));
+ compression_scale.add_mark (compression_adjustment.lower, Gtk.PositionType.BOTTOM, minimum_size_label);
+ compression_scale.add_mark (75, Gtk.PositionType.BOTTOM, null);
+ compression_scale.add_mark (90, Gtk.PositionType.BOTTOM, null);
+ var full_detail_label = "<small>%s</small>".printf (_("Full detail"));
+ compression_scale.add_mark (compression_adjustment.upper, Gtk.PositionType.BOTTOM, full_detail_label);
+ compression_adjustment.value = settings.get_int ("jpeg-quality");
+ compression_adjustment.value_changed.connect (() => { settings.set_int ("jpeg-quality", (int) compression_adjustment.value); });
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 (() =>
+ paper_size_row.notify["selected"].connect (() =>
{
int w, h;
get_paper_size (out w, out h);
@@ -199,55 +262,47 @@ private class PreferencesDialog : Hdy.PreferencesWindow
public 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))
+ for (uint i = 0; i < paper_size_model.n_items; i++)
{
- int w, h;
- paper_size_model.get (iter, 0, out w, 1, out h, -1);
- if (w == width && h == height)
- break;
+ var item = paper_size_model.get_item (i) as PaperSizeItem;
+ if (item.width == width && item.height == height)
+ {
+ paper_size_row.selected = i;
+ break;
+ }
}
-
- if (!have_iter)
- have_iter = paper_size_model.get_iter_first (out iter);
- if (have_iter)
- paper_size_combo.set_active_iter (iter);
}
public 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);
+ if (text_dpi_row.selected != Gtk.INVALID_LIST_POSITION)
+ {
+ var item = text_dpi_model.get_item (text_dpi_row.selected) as DpiItem;
+ return item.dpi;
+ }
- return dpi;
+ return DEFAULT_TEXT_DPI;
}
public 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);
+ if (photo_dpi_row.selected != Gtk.INVALID_LIST_POSITION)
+ {
+ var item = photo_dpi_model.get_item (photo_dpi_row.selected) as DpiItem;
+ return item.dpi;
+ }
- return dpi;
+ return DEFAULT_PHOTO_DPI;
}
public 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))
+ if (paper_size_row.selected != Gtk.INVALID_LIST_POSITION)
{
- paper_size_model.get (iter, 0, ref width, 1, ref height, -1);
+ var item = paper_size_model.get_item (paper_size_row.selected) as PaperSizeItem;
+ width = item.width;
+ height = item.height;
return true;
}
@@ -302,16 +357,15 @@ private class PreferencesDialog : Hdy.PreferencesWindow
page_delay_0s_button.active = true;
}
- private void set_dpi_combo (Gtk.ComboBox combo, int default_dpi, int current_dpi)
+ private void set_dpi_combo (Adw.ComboRow 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;
+ var model = combo.model as ListStore;
int[] scan_resolutions = {75, 150, 200, 300, 600, 1200, 2400};
- foreach (var dpi in scan_resolutions)
+
+ for (var i = 0; i < scan_resolutions.length; i++)
{
+ var dpi = scan_resolutions[i];
+
string label;
if (dpi == default_dpi)
/* Preferences dialog: Label for default resolution in resolution list */
@@ -325,13 +379,12 @@ private class PreferencesDialog : Hdy.PreferencesWindow
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);
+
+ model.append (new DpiItem (dpi, label));
if (dpi == current_dpi)
- combo.set_active_iter (iter);
+ combo.selected = i;
+
}
}
}
diff --git a/src/reorder-pages-dialog.vala b/src/reorder-pages-dialog.vala
new file mode 100644
index 0000000..eb249ca
--- /dev/null
+++ b/src/reorder-pages-dialog.vala
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 Bartłomiej Maryńczak
+ * Author: Bartłomiej Maryńczak <marynczakbartlomiej@gmail.com>,
+ *
+ * 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/ui/reorder-pages-item.ui")]
+private class ReorderPagesItem : Gtk.Button
+{
+ [GtkChild]
+ private unowned Gtk.Label title;
+ [GtkChild]
+ private unowned Gtk.Image before_image;
+ [GtkChild]
+ private unowned Gtk.Image after_image;
+
+ public new string label
+ {
+ get { return title.label; }
+ set { title.label = value; }
+ }
+
+ public string before
+ {
+ get { return before_image.get_icon_name (); }
+ set { before_image.icon_name = value; }
+ }
+
+ public string after
+ {
+ get { return after_image.get_icon_name (); }
+ set { after_image.icon_name = value; }
+ }
+}
+
+
+[GtkTemplate (ui = "/org/gnome/SimpleScan/ui/reorder-pages-dialog.ui")]
+private class ReorderPagesDialog : Gtk.Window
+{
+ [GtkChild]
+ public unowned ReorderPagesItem combine_sides;
+ [GtkChild]
+ public unowned ReorderPagesItem combine_sides_rev;
+ [GtkChild]
+ public unowned ReorderPagesItem flip_odd;
+ [GtkChild]
+ public unowned ReorderPagesItem flip_even;
+ [GtkChild]
+ public unowned ReorderPagesItem reverse;
+
+ public ReorderPagesDialog ()
+ {
+ add_binding_action (Gdk.Key.Escape, 0, "window.close", null);
+ }
+}
diff --git a/src/simple-scan.vala b/src/simple-scan.vala
index 5deedcf..c2e2da2 100644
--- a/src/simple-scan.vala
+++ b/src/simple-scan.vala
@@ -9,7 +9,7 @@
* license.
*/
-public class SimpleScan : Gtk.Application
+public class SimpleScan : Adw.Application
{
static bool show_version;
static bool debug_enabled;
@@ -40,7 +40,7 @@ public class SimpleScan : Gtk.Application
public SimpleScan (ScanDevice? device = null)
{
/* The inhibit () method use this */
- Object (application_id: "org.gnome.SimpleScan");
+ Object (application_id: "simple-scan");
register_session = true;
default_device = device;
@@ -50,9 +50,6 @@ public class SimpleScan : Gtk.Application
{
base.startup ();
- Hdy.init ();
- Hdy.StyleManager.get_default ().color_scheme = PREFER_LIGHT;
-
app = new AppWindow ();
book = app.book;
app.start_scan.connect (scan_cb);
@@ -1580,9 +1577,14 @@ public class SimpleScan : Gtk.Application
private void authorize_cb (Scanner scanner, string resource)
{
- string username, password;
- app.authorize (resource, out username, out password);
- scanner.authorize (username, password);
+ app.authorize.begin (resource, (obj, res) =>
+ {
+ var data = app.authorize.end(res);
+ if (data.success)
+ {
+ scanner.authorize (data.username, data.password);
+ }
+ });
}
private Page append_page (int width = 100, int height = 100, int dpi = 100)
@@ -1956,7 +1958,6 @@ public class SimpleScan : Gtk.Application
var c = new OptionContext (/* Arguments and description for --help text */
_("[DEVICE…] — Scanning utility"));
c.add_main_entries (options, GETTEXT_PACKAGE);
- c.add_group (Gtk.get_option_group (true));
try
{
c.parse (ref args);
@@ -2014,7 +2015,7 @@ public class SimpleScan : Gtk.Application
debug ("Starting %s %s, PID=%i", args[0], VERSION, Posix.getpid ());
- Gtk.init (ref args);
+ Gtk.init ();
var app = new SimpleScan (device);
return app.run ();