summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2017-11-11 18:47:31 +0100
committerJörg Frings-Fürst <debian@jff-webhosting.net>2017-11-11 18:47:31 +0100
commitab6556e393162fff0e0e0c80a9fff689b4e2ca05 (patch)
tree74d12800f57397f6123cac49f814e5cabb1df205 /src
parent657d8b8812f16b2c377e5b77ff2ffcc4046a7dce (diff)
New upstream version 3.26.2upstream/3.26.2
Diffstat (limited to 'src')
-rw-r--r--src/app-window.ui (renamed from src/simple-scan.ui)1044
-rw-r--r--src/app-window.vala (renamed from src/ui.vala)1196
-rw-r--r--src/authorize-dialog.ui141
-rw-r--r--src/authorize-dialog.vala37
-rw-r--r--src/book-view.vala38
-rw-r--r--src/book.vala781
-rw-r--r--src/help-overlay.ui128
-rw-r--r--src/libwebp.vapi55
-rw-r--r--src/libwebpmux.vapi128
-rw-r--r--src/meson.build9
-rw-r--r--src/page.vala63
-rw-r--r--src/preferences-dialog.ui608
-rw-r--r--src/preferences-dialog.vala534
-rw-r--r--src/screensaver.vala25
-rw-r--r--src/simple-scan.gresource.xml7
-rw-r--r--src/simple-scan.vala175
16 files changed, 2851 insertions, 2118 deletions
diff --git a/src/simple-scan.ui b/src/app-window.ui
index 6e1d15a..bbdf1c7 100644
--- a/src/simple-scan.ui
+++ b/src/app-window.ui
@@ -1,170 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
<interface>
- <!-- interface-requires gtk+ 3.10 -->
- <object class="GtkDialog" id="authorize_dialog">
- <property name="can_focus">False</property>
- <property name="border_width">12</property>
- <property name="resizable">False</property>
- <property name="modal">True</property>
- <property name="type_hint">normal</property>
- <property name="urgency_hint">True</property>
- <child internal-child="vbox">
- <object class="GtkBox" id="dialog-vbox1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="orientation">vertical</property>
- <property name="spacing">12</property>
- <child internal-child="action_area">
- <object class="GtkButtonBox" id="dialog-action_area1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="layout_style">end</property>
- <child>
- <object class="GtkButton" id="authorize_button">
- <property name="label" translatable="yes" comments="Button to submit authorization dialog">_Authorize</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_underline">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">0</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="pack_type">end</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkBox" id="vbox5">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="border_width">5</property>
- <property name="orientation">vertical</property>
- <property name="spacing">12</property>
- <child>
- <object class="GtkLabel" id="authorize_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" comments="This label is set dynamically and is not translated">To connect to ? you need to authorize</property>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkGrid" id="grid2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="row_spacing">6</property>
- <property name="column_spacing">6</property>
- <child>
- <object class="GtkEntry" id="username_entry">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="hexpand">True</property>
- <property name="invisible_char">●</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">0</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkEntry" id="password_entry">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="hexpand">True</property>
- <property name="visibility">False</property>
- <property name="invisible_char">●</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">1</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="username_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes" comments="Label beside username entry">_Username for resource:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">username_entry</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">0</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="password_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes" comments="Label beside password entry">_Password:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">password_entry</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">1</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- </child>
- <action-widgets>
- <action-widget response="0">authorize_button</action-widget>
- </action-widgets>
- </object>
- <object class="GtkAdjustment" id="brightness_adjustment">
- <property name="lower">-100</property>
- <property name="upper">100</property>
- <property name="step_increment">1</property>
- <property name="page_increment">10</property>
- </object>
- <object class="GtkAdjustment" id="contrast_adjustment">
- <property name="lower">-100</property>
- <property name="upper">100</property>
- <property name="step_increment">1</property>
- <property name="page_increment">10</property>
- </object>
- <object class="GtkListStore" id="device_model">
- <columns>
- <!-- column-name device_name -->
- <column type="gchararray"/>
- <!-- column-name label -->
- <column type="gchararray"/>
- </columns>
- </object>
+ <requires lib="gtk+" version="3.12"/>
<object class="GtkImage" id="email_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -175,70 +12,77 @@
<property name="can_focus">False</property>
<property name="stock">gtk-help</property>
</object>
- <object class="GtkListStore" id="page_side_model">
- <columns>
- <!-- column-name side -->
- <column type="gint"/>
- <!-- column-name label -->
- <column type="gchararray"/>
- </columns>
- <data>
- <row>
- <col id="0">3</col>
- <col id="1" translatable="yes" comments="Combo box label for scanning both sides of a page">Front and Back</col>
- </row>
- <row>
- <col id="0">1</col>
- <col id="1" translatable="yes" comments="Combo box label for scanning the front side of a page">Front</col>
- </row>
- <row>
- <col id="0">2</col>
- <col id="1" translatable="yes" comments="Combo box label for scanning the back side of a page">Back</col>
- </row>
- </data>
- </object>
- <object class="GtkListStore" id="paper_size_model">
- <columns>
- <!-- column-name width -->
- <column type="gint"/>
- <!-- column-name height -->
- <column type="gint"/>
- <!-- column-name label -->
- <column type="gchararray"/>
- </columns>
- </object>
- <object class="GtkListStore" id="photo_dpi_model">
- <columns>
- <!-- column-name dpi -->
- <column type="gint"/>
- <!-- column-name label -->
- <column type="gchararray"/>
- </columns>
- </object>
- <object class="GtkAdjustment" id="quality_adjustment">
- <property name="upper">100</property>
- <property name="step_increment">1</property>
- <property name="page_increment">10</property>
- </object>
- <object class="GtkAdjustment" id="page_delay_adjustment">
- <property name="lower">0</property>
- <property name="upper">10000</property>
- <property name="step_increment">100</property>
- <property name="page_increment">1000</property>
+ <object class="GtkMenu" id="scan_button_hb_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="scan_single_button_hb_menuitem">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Toolbar scan menu item to scan a single page from the scanner">Single _Page</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="scan_button_clicked_cb" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="scan_all_button_hb_menuitem">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Toolbar scan menu item to scan all pages from a document feeder">All Pages From _Feeder</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="continuous_scan_button_clicked_cb" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="batch_button_hb_menuitem">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Toolbar scan menu item to scan continuously from the flatbed">_Multiple Pages From Flatbed</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="batch_button_clicked_cb" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="menuitem3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRadioMenuItem" id="text_button_hb_menuitem">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Text</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ <signal name="toggled" handler="text_menuitem_toggled_cb" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRadioMenuItem" id="photo_button_hb_menuitem">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Photo</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">text_button_hb_menuitem</property>
+ <signal name="toggled" handler="photo_menuitem_toggled_cb" swapped="no"/>
+ </object>
+ </child>
</object>
- <template class="UserInterface" parent="GtkApplicationWindow">
+ <template class="AppWindow" parent="GtkApplicationWindow">
<property name="can_focus">False</property>
<property name="title" translatable="yes" comments="Title of scan window">Simple Scan</property>
<property name="icon_name">scanner</property>
<signal name="delete-event" handler="window_delete_event_cb" swapped="no"/>
<child>
- <object class="GtkBox" id="main_vbox">
+ <object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkMenuBar" id="menubar">
- <property name="visible">False</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="document_menuitem">
@@ -257,8 +101,8 @@
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
- <accelerator key="n" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<signal name="activate" handler="new_button_clicked_cb" swapped="no"/>
+ <accelerator key="n" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
@@ -278,18 +122,18 @@
<property name="can_focus">False</property>
<property name="label" translatable="yes" comments="Scan menu item to scan a single page from the scanner">Single _Page</property>
<property name="use_underline">True</property>
- <accelerator key="1" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<signal name="activate" handler="scan_button_clicked_cb" swapped="no"/>
+ <accelerator key="1" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
- </child>
+ </child>
<child>
<object class="GtkMenuItem" id="scan_all_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes" comments="Scan menu item to scan all pages from a document feeder">All Pages From _Feeder</property>
<property name="use_underline">True</property>
- <accelerator key="f" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<signal name="activate" handler="continuous_scan_button_clicked_cb" swapped="no"/>
+ <accelerator key="f" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
@@ -298,10 +142,10 @@
<property name="can_focus">False</property>
<property name="label" translatable="yes" comments="Scan menu item to scan continuously from the flatbed">_Multiple Pages From Flatbed</property>
<property name="use_underline">True</property>
- <accelerator key="m" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<signal name="activate" handler="batch_button_clicked_cb" swapped="no"/>
+ <accelerator key="m" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
- </child>
+ </child>
<child>
<object class="GtkMenuItem" id="stop_scan_menuitem">
<property name="visible">True</property>
@@ -309,8 +153,8 @@
<property name="can_focus">False</property>
<property name="label" translatable="yes" comments="Menu entry to stop current scan">_Stop Scan</property>
<property name="use_underline">True</property>
- <accelerator key="Escape" signal="activate"/>
<signal name="activate" handler="stop_scan_button_clicked_cb" swapped="no"/>
+ <accelerator key="Escape" signal="activate"/>
</object>
</child>
<child>
@@ -362,8 +206,8 @@
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
- <accelerator key="s" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<signal name="activate" handler="save_file_button_clicked_cb" swapped="no"/>
+ <accelerator key="s" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
@@ -375,8 +219,8 @@
<property name="use_underline">True</property>
<property name="image">email_image</property>
<property name="use_stock">False</property>
- <accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<signal name="activate" handler="email_button_clicked_cb" swapped="no"/>
+ <accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
@@ -387,8 +231,8 @@
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
- <accelerator key="p" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<signal name="activate" handler="print_button_clicked_cb" swapped="no"/>
+ <accelerator key="p" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
@@ -420,9 +264,9 @@
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
+ <signal name="activate" handler="quit_menuitem_activate_cb" swapped="no"/>
<accelerator key="w" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="q" signal="activate" modifiers="GDK_CONTROL_MASK"/>
- <signal name="activate" handler="quit_menuitem_activate_cb" swapped="no"/>
</object>
</child>
</object>
@@ -445,8 +289,8 @@
<property name="can_focus">False</property>
<property name="label" translatable="yes" comments="Menu item to rotate page to left (anti-clockwise)">Rotate _Left</property>
<property name="use_underline">True</property>
- <accelerator key="bracketleft" signal="activate"/>
<signal name="activate" handler="rotate_left_button_clicked_cb" swapped="no"/>
+ <accelerator key="bracketleft" signal="activate"/>
</object>
</child>
<child>
@@ -455,8 +299,8 @@
<property name="can_focus">False</property>
<property name="label" translatable="yes" comments="Menu item to rotate page to right (clockwise)">Rotate _Right</property>
<property name="use_underline">True</property>
- <accelerator key="bracketright" signal="activate"/>
<signal name="activate" handler="rotate_right_button_clicked_cb" swapped="no"/>
+ <accelerator key="bracketright" signal="activate"/>
</object>
</child>
<child>
@@ -582,8 +426,8 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes" comments="Menu item to move the selected page to the left">Move Left</property>
- <accelerator key="less" signal="activate"/>
<signal name="activate" handler="page_move_left_menuitem_activate_cb" swapped="no"/>
+ <accelerator key="less" signal="activate"/>
</object>
</child>
<child>
@@ -592,8 +436,8 @@
<property name="can_focus">False</property>
<property name="label" translatable="yes" comments="Menu item to move the selected page to the right">Move Right</property>
<property name="use_underline">True</property>
- <accelerator key="greater" signal="activate"/>
<signal name="activate" handler="page_move_right_menuitem_activate_cb" swapped="no"/>
+ <accelerator key="greater" signal="activate"/>
</object>
</child>
<child>
@@ -604,8 +448,8 @@
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
- <accelerator key="c" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<signal name="activate" handler="copy_to_clipboard_button_clicked_cb" swapped="no"/>
+ <accelerator key="c" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
@@ -615,8 +459,8 @@
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
- <accelerator key="Delete" signal="activate"/>
<signal name="activate" handler="page_delete_menuitem_activate_cb" swapped="no"/>
+ <accelerator key="Delete" signal="activate"/>
</object>
</child>
</object>
@@ -641,8 +485,8 @@
<property name="use_underline">True</property>
<property name="image">help_image</property>
<property name="use_stock">False</property>
- <accelerator key="F1" signal="activate"/>
<signal name="activate" handler="help_contents_menuitem_activate_cb" swapped="no"/>
+ <accelerator key="F1" signal="activate"/>
</object>
</child>
<child>
@@ -668,26 +512,7 @@
</child>
<child>
<object class="GtkToolbar" id="toolbar">
- <property name="visible">False</property>
<property name="can_focus">False</property>
- <style>
- <class name="primary-toolbar"/>
- </style>
- <child>
- <object class="GtkToolButton" id="new_toolbutton">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="tooltip_text" translatable="yes" comments="Tooltip for new document button">Start a new document</property>
- <property name="label" translatable="yes">New</property>
- <property name="use_underline">True</property>
- <property name="stock_id">gtk-new</property>
- <signal name="clicked" handler="new_button_clicked_cb" swapped="no"/>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="homogeneous">True</property>
- </packing>
- </child>
<child>
<object class="GtkMenuToolButton" id="scan_toolbutton">
<property name="visible">True</property>
@@ -738,601 +563,253 @@
<property name="homogeneous">True</property>
</packing>
</child>
+ <style>
+ <class name="primary-toolbar"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
- <object class="GtkSeparatorToolItem" id="toolbutton2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="homogeneous">True</property>
- </packing>
- </child>
- <child>
- <object class="GtkToolButton" id="rotate_left_toolbutton">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="tooltip_text" translatable="yes" comments="Tooltip for rotate left (counter-clockwise) button">Rotate the page to the left (counter-clockwise)</property>
- <property name="label" translatable="yes" comments="Label on rotate page left (anti-clockwise) item">Rotate Left</property>
- <property name="use_underline">True</property>
- <property name="icon_name">object-rotate-left</property>
- <signal name="clicked" handler="rotate_left_button_clicked_cb" swapped="no"/>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="homogeneous">True</property>
- </packing>
- </child>
- <child>
- <object class="GtkToolButton" id="rotate_right_toolbutton">
+ <object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="tooltip_text" translatable="yes" comments="Tooltip for rotate right (clockwise) button">Rotate the page to the right (clockwise)</property>
- <property name="label" translatable="yes" comments="Label on rotate page right (clockwise) item">Rotate Right</property>
- <property name="use_underline">True</property>
- <property name="icon_name">object-rotate-right</property>
- <signal name="clicked" handler="rotate_right_button_clicked_cb" swapped="no"/>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="opacity">0.5</property>
+ <property name="pixel_size">120</property>
+ <property name="icon_name">scanner-symbolic</property>
+ <property name="icon_size">6</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status_primary_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Label shown when searching for scanners">Searching for Scanners…</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.5"/>
+ </attributes>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status_secondary_label">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ <property name="track_visited_links">False</property>
+ <signal name="activate_link" handler="status_label_activate_link_cb" swapped="no"/>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
</object>
<packing>
- <property name="expand">False</property>
- <property name="homogeneous">True</property>
+ <property name="name">startup</property>
</packing>
</child>
<child>
- <object class="GtkToggleToolButton" id="crop_toolbutton">
+ <object class="GtkBox" id="main_vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="tooltip_text" translatable="yes">Crop the selected page</property>
- <property name="is_important">True</property>
- <property name="label" translatable="yes">Crop</property>
- <property name="use_underline">True</property>
- <property name="icon_name">object-crop</property>
- <signal name="toggled" handler="crop_toolbutton_toggled_cb" swapped="no"/>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkActionBar" id="action_bar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
</object>
<packing>
- <property name="expand">False</property>
- <property name="homogeneous">True</property>
+ <property name="name">document</property>
+ <property name="position">1</property>
</packing>
</child>
</object>
<packing>
- <property name="expand">False</property>
+ <property name="expand">True</property>
<property name="fill">True</property>
- <property name="position">1</property>
+ <property name="position">2</property>
</packing>
</child>
- <child>
- <placeholder/>
- </child>
</object>
</child>
<child type="titlebar">
- <object class="GtkHeaderBar" id="headerbar">
+ <object class="GtkHeaderBar" id="header_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="vexpand">True</property>
<property name="show_close_button">True</property>
- <property name="title" translatable="yes" comments="Title of scan window">Simple Scan</property>
- <style>
- <class name="titlebar"/>
- </style>
<child>
<object class="GtkBox" id="open_box">
<property name="visible">True</property>
- <property name="orientation">horizontal</property>
+ <property name="can_focus">False</property>
<property name="valign">center</property>
- <style>
- <class name="linked"/>
- </style>
<child>
<object class="GtkButton" id="stop_button">
- <property name="visible">False</property>
- <property name="tooltip_text" translatable="yes" comments="Tooltip for stop button">Stop the current scan</property>
<property name="label" translatable="yes">Stop</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes" comments="Tooltip for stop button">Stop the current scan</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="stop_scan_button_clicked_cb" swapped="no"/>
<style>
<class name="text-button"/>
+ <class name="destructive-action"/>
</style>
</object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
</child>
<child>
<object class="GtkButton" id="scan_button">
+ <property name="label" translatable="yes" comments="Label on scan toolbar item">Scan</property>
<property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes" comments="Tooltip for scan toolbar button">Scan a single page from the scanner</property>
- <property name="label" translatable="yes" comments="Label on scan toolbar item">Scan</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="scan_button_clicked_cb" swapped="no"/>
<style>
<class name="text-button"/>
+ <class name="suggested-action"/>
</style>
</object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
</child>
<child>
<object class="GtkMenuButton" id="open_button">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
<property name="popup">scan_button_hb_menu</property>
+ <child>
+ <placeholder/>
+ </child>
<style>
<class name="text-button"/>
</style>
</object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
</child>
- </object>
- <packing>
- <property name="pack_type">start</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="save_button">
- <property name="visible">True</property>
- <property name="sensitive">False</property>
- <property name="can_focus">False</property>
- <property name="tooltip_text" translatable="yes" comments="Tooltip for save toolbar button">Save document to a file</property>
- <property name="use_underline">True</property>
- <signal name="clicked" handler="save_file_button_clicked_cb" swapped="no"/>
<style>
- <class name="image-button"/>
+ <class name="linked"/>
</style>
- <child>
- <object class="GtkImage" id="save_image">
- <property name="visible">True</property>
- <property name="icon_size">1</property>
- <property name="icon_name">document-save-symbolic</property>
- </object>
- </child>
</object>
- <packing>
- </packing>
</child>
<child>
- <object class="GtkButton" id="new_button">
+ <object class="GtkMenuButton" id="menu_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="tooltip_text" translatable="yes" comments="Tooltip for new document button">Start a new document</property>
+ <property name="receives_default">False</property>
<property name="use_underline">True</property>
- <signal name="clicked" handler="new_button_clicked_cb" swapped="no"/>
- <style>
- <class name="image-button"/>
- </style>
<child>
- <object class="GtkImage" id="new_image">
+ <object class="GtkImage">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">open-menu-symbolic</property>
<property name="icon_size">1</property>
- <property name="icon_name">document-new-symbolic</property>
</object>
</child>
- </object>
- <packing>
- </packing>
- </child>
- <child>
- <object class="GtkBox" id="rotate_box">
- <property name="visible">True</property>
- <property name="orientation">horizontal</property>
- <property name="valign">center</property>
<style>
- <class name="linked"/>
+ <class name="image-button"/>
</style>
- <child>
- <object class="GtkButton" id="rotate_right_button">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="tooltip_text" translatable="yes" comments="Tooltip for rotate right (clockwise) button">Rotate the page to the right (clockwise)</property>
- <property name="use_underline">True</property>
- <signal name="clicked" handler="rotate_right_button_clicked_cb" swapped="no"/>
- <style>
- <class name="image-button"/>
- </style>
- <child>
- <object class="GtkImage" id="objectrotateright-button">
- <property name="visible">True</property>
- <property name="icon_size">1</property>
- <property name="icon_name">object-rotate-right-symbolic</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="pack_type">end</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="rotate_left_button">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="tooltip_text" translatable="yes" comments="Tooltip for rotate left (counter-clockwise) button">Rotate the page to the left (counter-clockwise)</property>
- <property name="use_underline">True</property>
- <signal name="clicked" handler="rotate_left_button_clicked_cb" swapped="no"/>
- <style>
- <class name="image-button"/>
- </style>
- <child>
- <object class="GtkImage" id="objectrotateleft-button">
- <property name="visible">True</property>
- <property name="icon_size">1</property>
- <property name="icon_name">object-rotate-left-symbolic</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="pack_type">end</property>
- </packing>
- </child>
</object>
<packing>
<property name="pack_type">end</property>
+ <property name="position">2</property>
</packing>
</child>
<child>
- <object class="GtkToggleButton" id="crop_button">
+ <object class="GtkButton" id="save_button">
<property name="visible">True</property>
+ <property name="sensitive">False</property>
<property name="can_focus">False</property>
- <property name="tooltip_text" translatable="yes">Crop the selected page</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes" comments="Tooltip for save toolbar button">Save document to a file</property>
<property name="use_underline">True</property>
- <signal name="toggled" handler="crop_button_toggled_cb" swapped="no"/>
- <style>
- <class name="image-button"/>
- </style>
+ <signal name="clicked" handler="save_file_button_clicked_cb" swapped="no"/>
<child>
- <object class="GtkImage" id="objectcrop-button">
+ <object class="GtkImage" id="save_image">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-save-symbolic</property>
<property name="icon_size">1</property>
- <property name="icon_name">edit-cut-symbolic</property>
</object>
</child>
+ <style>
+ <class name="image-button"/>
+ </style>
</object>
<packing>
<property name="pack_type">end</property>
+ <property name="position">3</property>
</packing>
</child>
+ <style>
+ <class name="titlebar"/>
+ </style>
</object>
- <packing>
- </packing>
</child>
</template>
- <object class="GtkListStore" id="text_dpi_model">
- <columns>
- <!-- column-name dpi -->
- <column type="gint"/>
- <!-- column-name label -->
- <column type="gchararray"/>
- </columns>
- </object>
- <object class="GtkDialog" id="preferences_dialog">
- <property name="can_focus">False</property>
- <property name="border_width">7</property>
- <property name="title" translatable="yes" comments="Title of preferences dialog">Preferences</property>
- <property name="resizable">False</property>
- <property name="icon_name">scanner</property>
- <property name="type_hint">normal</property>
- <signal name="delete-event" handler="preferences_dialog_delete_event_cb" swapped="no"/>
- <signal name="response" handler="preferences_dialog_response_cb" swapped="no"/>
- <child internal-child="vbox">
- <object class="GtkBox" id="dialog-vbox2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="orientation">vertical</property>
- <property name="spacing">2</property>
- <child internal-child="action_area">
- <object class="GtkButtonBox" id="dialog-action_area2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="layout_style">end</property>
- <child>
- <object class="GtkButton" id="preferences_close_button">
- <property name="label">gtk-close</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_stock">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">0</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="pack_type">end</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkGrid" id="grid3">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="border_width">5</property>
- <property name="row_spacing">6</property>
- <property name="column_spacing">6</property>
- <child>
- <object class="GtkLabel" id="source_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes" comments="Label beside scan source combo box">Scan S_ource:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">device_combo</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">0</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkComboBox" id="device_combo">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="hexpand">True</property>
- <property name="model">device_model</property>
- <signal name="changed" handler="device_combo_changed_cb" swapped="no"/>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">0</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="text_dpi_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes" comments="Label beside scan source combo box">_Text Resolution:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">text_dpi_combo</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">1</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="photo_dpi_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes" comments="Label beside scan source combo box">_Photo Resolution:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">photo_dpi_combo</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">2</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkComboBox" id="text_dpi_combo">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="hexpand">True</property>
- <property name="model">text_dpi_model</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">1</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkComboBox" id="photo_dpi_combo">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="hexpand">True</property>
- <property name="model">photo_dpi_model</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">2</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="page_side_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes" comments="Label beside scan side combo box">Scan Side:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">photo_dpi_combo</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">3</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkComboBox" id="page_side_combo">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="hexpand">True</property>
- <property name="model">page_side_model</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">3</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="paper_size_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes" comments="Label beside page size combo box">Page Size:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">photo_dpi_combo</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">4</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkComboBox" id="paper_size_combo">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="hexpand">True</property>
- <property name="model">paper_size_model</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">4</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="brightness_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes" comments="Label beside brightness scale">Brightness:</property>
- <property name="use_underline">True</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">5</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkScale" id="brightness_scale">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="hexpand">True</property>
- <property name="adjustment">brightness_adjustment</property>
- <property name="draw_value">False</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">5</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="contrast_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes" comments="Label beside contrast scale">Contrast:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">contrast_scale</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">6</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkScale" id="contrast_scale">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="hexpand">True</property>
- <property name="adjustment">contrast_adjustment</property>
- <property name="draw_value">False</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">6</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="quality_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes" comments="Label beside quality scale">Quality:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">quality_scale</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">7</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkScale" id="quality_scale">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="hexpand">True</property>
- <property name="adjustment">quality_adjustment</property>
- <property name="draw_value">False</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">7</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="page_delay_label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes" comments="Label beside page delay scale">Delay between pages:</property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">page_delay_scale</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">8</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkScale" id="page_delay_scale">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="hexpand">True</property>
- <property name="adjustment">page_delay_adjustment</property>
- <property name="draw_value">True</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">8</property>
- <property name="width">1</property>
- <property name="height">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- </child>
- <action-widgets>
- <action-widget response="1">preferences_close_button</action-widget>
- </action-widgets>
- </object>
<object class="GtkMenu" id="scan_button_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -1362,7 +839,7 @@
<property name="use_underline">True</property>
<signal name="activate" handler="batch_button_clicked_cb" swapped="no"/>
</object>
- </child>
+ </child>
<child>
<object class="GtkSeparatorMenuItem" id="menuitem1">
<property name="visible">True</property>
@@ -1392,63 +869,4 @@
</object>
</child>
</object>
- <object class="GtkMenu" id="scan_button_hb_menu">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkMenuItem" id="scan_single_button_hb_menuitem">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes" comments="Toolbar scan menu item to scan a single page from the scanner">Single _Page</property>
- <property name="use_underline">True</property>
- <signal name="activate" handler="scan_button_clicked_cb" swapped="no"/>
- </object>
- </child>
- <child>
- <object class="GtkMenuItem" id="scan_all_button_hb_menuitem">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes" comments="Toolbar scan menu item to scan all pages from a document feeder">All Pages From _Feeder</property>
- <property name="use_underline">True</property>
- <signal name="activate" handler="continuous_scan_button_clicked_cb" swapped="no"/>
- </object>
- </child>
- <child>
- <object class="GtkMenuItem" id="batch_button_hb_menuitem">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes" comments="Toolbar scan menu item to scan continuously from the flatbed">_Multiple Pages From Flatbed</property>
- <property name="use_underline">True</property>
- <signal name="activate" handler="batch_button_clicked_cb" swapped="no"/>
- </object>
- </child>
- <child>
- <object class="GtkSeparatorMenuItem" id="menuitem3">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- </object>
- </child>
- <child>
- <object class="GtkRadioMenuItem" id="text_button_hb_menuitem">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">Text</property>
- <property name="use_underline">True</property>
- <property name="draw_as_radio">True</property>
- <signal name="toggled" handler="text_menuitem_toggled_cb" swapped="no"/>
- </object>
- </child>
- <child>
- <object class="GtkRadioMenuItem" id="photo_button_hb_menuitem">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">Photo</property>
- <property name="use_underline">True</property>
- <property name="active">True</property>
- <property name="draw_as_radio">True</property>
- <property name="group">text_button_hb_menuitem</property>
- <signal name="toggled" handler="photo_menuitem_toggled_cb" swapped="no"/>
- </object>
- </child>
- </object>
</interface>
diff --git a/src/ui.vala b/src/app-window.vala
index 554b161..2cd75ee 100644
--- a/src/ui.vala
+++ b/src/app-window.vala
@@ -10,12 +10,12 @@
* license.
*/
-[GtkTemplate (ui = "/org/gnome/SimpleScan/simple-scan.ui")]
-public class UserInterface : Gtk.ApplicationWindow
-{
- private const int DEFAULT_TEXT_DPI = 150;
- private const int DEFAULT_PHOTO_DPI = 300;
+private const int DEFAULT_TEXT_DPI = 150;
+private const int DEFAULT_PHOTO_DPI = 300;
+[GtkTemplate (ui = "/org/gnome/SimpleScan/app-window.ui")]
+public class AppWindow : Gtk.ApplicationWindow
+{
private const GLib.ActionEntry[] action_entries =
{
{ "new_document", new_document_activate_cb },
@@ -31,6 +31,10 @@ public class UserInterface : Gtk.ApplicationWindow
private Settings settings;
+ private PreferencesDialog preferences_dialog;
+
+ [GtkChild]
+ private Gtk.HeaderBar header_bar;
[GtkChild]
private Gtk.MenuBar menubar;
[GtkChild]
@@ -38,13 +42,13 @@ public class UserInterface : Gtk.ApplicationWindow
[GtkChild]
private Gtk.Menu page_menu;
[GtkChild]
+ private Gtk.Stack stack;
+ [GtkChild]
+ private Gtk.Label status_primary_label;
+ [GtkChild]
+ private Gtk.Label status_secondary_label;
+ [GtkChild]
private Gtk.Box main_vbox;
- private Gtk.InfoBar info_bar;
- private Gtk.Image info_bar_image;
- private Gtk.Label info_bar_label;
- private Gtk.Button info_bar_close_button;
- private Gtk.Button info_bar_change_scanner_button;
- private Gtk.Button info_bar_install_button;
[GtkChild]
private Gtk.RadioMenuItem custom_crop_menuitem;
[GtkChild]
@@ -86,13 +90,13 @@ public class UserInterface : Gtk.ApplicationWindow
[GtkChild]
private Gtk.ToolButton stop_toolbutton;
[GtkChild]
- private Gtk.ToggleButton crop_button;
- [GtkChild]
- private Gtk.ToggleToolButton crop_toolbutton;
- [GtkChild]
private Gtk.Button stop_button;
[GtkChild]
private Gtk.Button scan_button;
+ [GtkChild]
+ private Gtk.ActionBar action_bar;
+ private Gtk.ToggleButton crop_button;
+ private Gtk.Button delete_button;
[GtkChild]
private Gtk.RadioMenuItem text_button_menuitem;
@@ -108,63 +112,11 @@ public class UserInterface : Gtk.ApplicationWindow
private Gtk.RadioMenuItem photo_menuitem;
[GtkChild]
- private Gtk.Dialog authorize_dialog;
- [GtkChild]
- private Gtk.Label authorize_label;
- [GtkChild]
- private Gtk.Entry username_entry;
- [GtkChild]
- private Gtk.Entry password_entry;
+ private Gtk.MenuButton menu_button;
- [GtkChild]
- private Gtk.Dialog preferences_dialog;
- [GtkChild]
- private Gtk.ComboBox device_combo;
- [GtkChild]
- private Gtk.ComboBox text_dpi_combo;
- [GtkChild]
- private Gtk.ComboBox photo_dpi_combo;
- [GtkChild]
- private Gtk.ComboBox page_side_combo;
- [GtkChild]
- private Gtk.ComboBox paper_size_combo;
- [GtkChild]
- private Gtk.Scale brightness_scale;
- [GtkChild]
- private Gtk.Scale contrast_scale;
- [GtkChild]
- private Gtk.Scale quality_scale;
- [GtkChild]
- private Gtk.Scale page_delay_scale;
- [GtkChild]
- private Gtk.ListStore device_model;
- [GtkChild]
- private Gtk.ListStore text_dpi_model;
- [GtkChild]
- private Gtk.ListStore photo_dpi_model;
- [GtkChild]
- private Gtk.ListStore page_side_model;
- [GtkChild]
- private Gtk.ListStore paper_size_model;
- [GtkChild]
- private Gtk.Adjustment brightness_adjustment;
- [GtkChild]
- private Gtk.Adjustment contrast_adjustment;
- [GtkChild]
- private Gtk.Adjustment quality_adjustment;
- [GtkChild]
- private Gtk.Adjustment page_delay_adjustment;
- private bool setting_devices;
private string? missing_driver = null;
- private bool user_selected_device;
private Gtk.FileChooserDialog? save_dialog;
- private ProgressBarDialog progress_dialog;
-
- private bool have_error;
- private string error_title;
- private string error_text;
- private bool error_change_scanner_hint;
public Book book { get; private set; }
private bool book_needs_saving;
@@ -186,10 +138,6 @@ public class UserInterface : Gtk.ApplicationWindow
private BookView book_view;
private bool updating_page_menu;
- private int default_page_width;
- private int default_page_height;
- private int default_page_dpi;
- private ScanDirection default_page_scan_direction;
private string document_hint = "photo";
@@ -200,7 +148,9 @@ public class UserInterface : Gtk.ApplicationWindow
set
{
scanning_ = value;
+ stack.set_visible_child_name ("document");
page_delete_menuitem.sensitive = !value;
+ delete_button.sensitive = !value;
stop_scan_menuitem.sensitive = value;
stop_toolbutton.sensitive = value;
scan_button.visible = !value;
@@ -211,66 +161,38 @@ public class UserInterface : Gtk.ApplicationWindow
private int window_width;
private int window_height;
private bool window_is_maximized;
- private bool window_is_fullscreen;
+ private bool window_is_fullscreen;
private uint save_state_timeout;
public int brightness
{
- get { return (int) brightness_adjustment.value; }
- set { brightness_adjustment.value = value; }
+ get { return preferences_dialog.get_brightness (); }
+ set { preferences_dialog.set_brightness (value); }
}
public int contrast
{
- get { return (int) contrast_adjustment.value; }
- set { contrast_adjustment.value = value; }
- }
-
- public int quality
- {
- get { return (int) quality_adjustment.value; }
- set { quality_adjustment.value = value; }
+ get { return preferences_dialog.get_contrast (); }
+ set { preferences_dialog.set_contrast (value); }
}
public int page_delay
{
- get { return (int) page_delay_adjustment.value; }
- set { page_delay_adjustment.value = value; }
+ get { return preferences_dialog.get_page_delay (); }
+ set { preferences_dialog.set_page_delay (value); }
}
public string? selected_device
{
- owned get
- {
- Gtk.TreeIter iter;
-
- if (device_combo.get_active_iter (out iter))
- {
- string device;
- device_model.get (iter, 0, out device, -1);
- return device;
- }
-
- return null;
- }
-
- set
- {
- Gtk.TreeIter iter;
- if (!find_scan_device (value, out iter))
- return;
-
- device_combo.set_active_iter (iter);
- user_selected_device = true;
- }
+ owned get { return preferences_dialog.get_selected_device (); }
+ set { preferences_dialog.set_selected_device (value); }
}
public signal void start_scan (string? device, ScanOptions options);
public signal void stop_scan ();
- public signal void email (string profile, int quality);
- public UserInterface ()
+ public AppWindow ()
{
settings = new Settings ("org.gnome.SimpleScan");
@@ -291,38 +213,21 @@ public class UserInterface : Gtk.ApplicationWindow
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);
}
}
- ~UserInterface ()
+ ~AppWindow ()
{
book.page_added.disconnect (page_added_cb);
book.reordered.disconnect (reordered_cb);
book.page_removed.disconnect (page_removed_cb);
}
- private bool find_scan_device (string device, out Gtk.TreeIter iter)
- {
- bool have_iter = false;
-
- if (device_model.get_iter_first (out iter))
- {
- do
- {
- string d;
- device_model.get (iter, 0, out d, -1);
- if (d == device)
- have_iter = true;
- } while (!have_iter && device_model.iter_next (ref iter));
- }
-
- return have_iter;
- }
-
- private void show_error_dialog (string error_title, string error_text)
+ public void show_error_dialog (string error_title, string error_text)
{
var dialog = new Gtk.MessageDialog (this,
Gtk.DialogFlags.MODAL,
@@ -337,164 +242,47 @@ public class UserInterface : Gtk.ApplicationWindow
public void authorize (string resource, out string username, out string password)
{
- /* Label in authorization dialog. '%s' is replaced with the name of the resource requesting authorization */
- var description = _("Username and password required to access '%s'").printf (resource);
-
- username_entry.text = "";
- password_entry.text = "";
- authorize_label.set_text (description);
-
+ /* 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;
+ authorize_dialog.transient_for = this;
authorize_dialog.run ();
- authorize_dialog.visible = false;
+ authorize_dialog.destroy ();
- username = username_entry.text;
- password = password_entry.text;
+ username = authorize_dialog.get_username ();
+ password = authorize_dialog.get_password ();
}
- [GtkCallback]
- private void device_combo_changed_cb (Gtk.Widget widget)
+ public void set_scan_devices (List<ScanDevice> devices, string? missing_driver = null)
{
- if (setting_devices)
- return;
- user_selected_device = true;
- if (selected_device != null)
- settings.set_string ("selected-device", selected_device);
- }
+ this.missing_driver = missing_driver;
- private void update_info_bar ()
- {
- Gtk.MessageType type;
- string title, text, image_id;
- bool show_close_button = false;
- bool show_install_button = false;
- bool show_change_scanner_button = false;
+ preferences_dialog.set_scan_devices (devices);
- if (have_error)
+ if (devices != null)
{
- type = Gtk.MessageType.ERROR;
- image_id = "dialog-error";
- title = error_title;
- text = error_text;
- show_close_button = true;
- show_change_scanner_button = error_change_scanner_hint;
+ status_primary_label.set_text (/* Label shown when detected a scanner */
+ _("Ready to Scan"));
+ status_secondary_label.set_text (preferences_dialog.get_selected_device_label ());
+ status_secondary_label.visible = true;
}
- else if (device_model.iter_n_children (null) == 0)
+ else if (missing_driver != null)
{
- type = Gtk.MessageType.WARNING;
- image_id = "dialog-warning";
- if (missing_driver == null)
- {
- /* Warning displayed when no scanners are detected */
- title = _("No scanners detected");
- /* Hint to user on why there are no scanners detected */
- text = _("Please check your scanner is connected and powered on");
- }
- else
- {
- /* Warning displayed when no drivers are installed but a compatible scanner is detected */
- title = _("Additional software needed");
- /* Instructions to install driver software */
- text = _("You need to install driver software for your scanner.");
- show_install_button = true;
- }
+ status_primary_label.set_text (/* Warning displayed when no drivers are installed but a compatible scanner is detected */
+ _("Additional software needed"));
+ /* Instructions to install driver software */
+ status_secondary_label.set_markup (_("You need to <a href=\"install-firmware\">install driver software</a> for your scanner."));
+ status_secondary_label.visible = true;
}
else
{
- info_bar.visible = false;
- return;
- }
-
- info_bar.message_type = type;
- info_bar_image.set_from_icon_name (image_id, Gtk.IconSize.DIALOG);
- var message = "<big><b>%s</b></big>\n\n%s".printf (title, text);
- info_bar_label.set_markup (message);
- info_bar_close_button.visible = show_close_button;
- info_bar_change_scanner_button.visible = show_change_scanner_button;
- info_bar_install_button.visible = show_install_button;
- info_bar.visible = true;
- }
-
- public void set_scan_devices (List<ScanDevice> devices, string? missing_driver = null)
- {
- bool have_selection = false;
- int index;
- Gtk.TreeIter iter;
-
- setting_devices = true;
-
- this.missing_driver = missing_driver;
-
- /* If the user hasn't chosen a scanner choose the best available one */
- if (user_selected_device)
- have_selection = device_combo.active >= 0;
-
- /* Add new devices */
- index = 0;
- foreach (var device in devices)
- {
- int n_delete = -1;
-
- /* Find if already exists */
- if (device_model.iter_nth_child (out iter, null, index))
- {
- int i = 0;
- do
- {
- string name;
- bool matched;
-
- device_model.get (iter, 0, out name, -1);
- matched = name == device.name;
-
- if (matched)
- {
- n_delete = i;
- break;
- }
- i++;
- } while (device_model.iter_next (ref iter));
- }
-
- /* If exists, remove elements up to this one */
- if (n_delete >= 0)
- {
- int i;
-
- /* Update label */
- device_model.set (iter, 1, device.label, -1);
-
- for (i = 0; i < n_delete; i++)
- {
- device_model.iter_nth_child (out iter, null, index);
- device_model.remove (iter);
- }
- }
- else
- {
- device_model.insert (out iter, index);
- device_model.set (iter, 0, device.name, 1, device.label, -1);
- }
- index++;
- }
-
- /* Remove any remaining devices */
- while (device_model.iter_nth_child (out iter, null, index))
- device_model.remove (iter);
-
- /* Select the 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);
- else
- device_combo.set_active (0);
+ /* Warning displayed when no scanners are detected */
+ status_primary_label.set_text (_("No scanners detected"));
+ /* Hint to user on why there are no scanners detected */
+ status_secondary_label.set_text (_("Please check your scanner is connected and powered on"));
+ status_secondary_label.visible = true;
}
-
- setting_devices = false;
-
- update_info_bar ();
}
private string choose_file_location ()
@@ -507,12 +295,12 @@ public class UserInterface : Gtk.ApplicationWindow
directory = Environment.get_user_special_dir (UserDirectory.DOCUMENTS);
save_dialog = new Gtk.FileChooserDialog (/* Save dialog: Dialog title */
- _("Save As..."),
+ _("Save As…"),
this,
Gtk.FileChooserAction.SAVE,
_("_Cancel"), Gtk.ResponseType.CANCEL,
_("_Save"), Gtk.ResponseType.ACCEPT,
- null);
+ null);
save_dialog.local_only = false;
if (book_uri != null)
save_dialog.set_uri (book_uri);
@@ -524,9 +312,13 @@ public class UserInterface : Gtk.ApplicationWindow
/* Filter to only show images by default */
var filter = new Gtk.FileFilter ();
- filter.set_filter_name (/* Save dialog: Filter name to show only image files */
+ filter.set_filter_name (/* Save dialog: Filter name to show only supported image files */
_("Image Files"));
- filter.add_pixbuf_formats ();
+ 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 ();
@@ -555,12 +347,20 @@ public class UserInterface : Gtk.ApplicationWindow
0, _("PNG (lossless)"),
1, ".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, ".webp",
+ -1);
+#endif
var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
box.visible = true;
save_dialog.set_extra_widget (box);
- /* Label in save dialog beside combo box to choose file format (PDF, JPEG, PNG) */
+ /* 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.pack_start (label, false, false, 0);
@@ -570,6 +370,23 @@ public class UserInterface : Gtk.ApplicationWindow
var renderer = new Gtk.CellRendererText ();
file_type_combo.pack_start (renderer, true);
file_type_combo.add_attribute (renderer, "text", 0);
+ box.pack_start (file_type_combo, false, true, 0);
+
+ /* Label in save dialog beside compression slider */
+ var quality_label = new Gtk.Label (_("Compression:"));
+ box.pack_start (quality_label, false, false, 0);
+
+ 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 = 200;
+ quality_scale.draw_value = false;
+ quality_scale.add_mark (0, Gtk.PositionType.BOTTOM, null);
+ quality_scale.add_mark (75, Gtk.PositionType.BOTTOM, null);
+ quality_scale.add_mark (90, Gtk.PositionType.BOTTOM, null);
+ quality_scale.add_mark (100, Gtk.PositionType.BOTTOM, null);
+ quality_adjustment.value = settings.get_int ("jpeg-quality");
+ quality_adjustment.value_changed.connect (() => { settings.set_int ("jpeg-quality", (int) quality_adjustment.value); });
+ box.pack_start (quality_scale, false, false, 0);
file_type_combo.set_active (0);
file_type_combo.changed.connect (() =>
@@ -588,8 +405,10 @@ public class UserInterface : Gtk.ApplicationWindow
filename = filename.slice (0, extension_index);
filename = filename + extension;
save_dialog.set_current_name (filename);
+
+ /* Quality not applicable to PNG */
+ quality_scale.visible = quality_label.visible = (extension != ".png");
});
- box.pack_start (file_type_combo, false, false, 0);
string? uri = null;
while (true)
@@ -615,10 +434,14 @@ public class UserInterface : Gtk.ApplicationWindow
/* Check the file(s) don't already exist */
var files = new List<File> ();
var format = uri_to_format (uri);
+#if HAVE_WEBP
+ if (format == "jpeg" || format == "png" || format == "webp")
+#else
if (format == "jpeg" || format == "png")
+#endif
{
for (var j = 0; j < book.n_pages; j++)
- files.append (book.make_indexed_file (uri, j));
+ files.append (make_indexed_file (uri, j, book.n_pages));
}
else
files.append (File.new_for_uri (uri));
@@ -650,7 +473,7 @@ public class UserInterface : Gtk.ApplicationWindow
_("_Replace"), Gtk.ResponseType.ACCEPT);
var response = dialog.run ();
dialog.destroy ();
-
+
if (response != Gtk.ResponseType.ACCEPT)
return false;
}
@@ -665,11 +488,15 @@ public class UserInterface : Gtk.ApplicationWindow
return "pdf";
else if (uri_lower.has_suffix (".png"))
return "png";
+#if HAVE_WEBP
+ else if (uri_lower.has_suffix (".webp"))
+ return "webp";
+#endif
else
return "jpeg";
}
- private bool save_document ()
+ private async bool save_document_async ()
{
var uri = choose_file_location ();
if (uri == null)
@@ -681,28 +508,34 @@ public class UserInterface : Gtk.ApplicationWindow
var format = uri_to_format (uri);
- show_progress_dialog ();
+ var cancellable = new Cancellable ();
+ var progress_bar = new CancellableProgressBar (_("Saving"), cancellable);
+ action_bar.pack_end (progress_bar);
+ progress_bar.visible = true;
try
{
- book.save (format, quality, file);
+ yield book.save_async (format, settings.get_int ("jpeg-quality"), file, (fraction) =>
+ {
+ progress_bar.set_fraction (fraction);
+ }, cancellable);
}
catch (Error e)
{
- hide_progress_dialog ();
+ progress_bar.destroy ();
warning ("Error saving file: %s", e.message);
- show_error (/* Title of error dialog when save failed */
- _("Failed to save file"),
- e.message,
- false);
+ show_error_dialog (/* Title of error dialog when save failed */
+ _("Failed to save file"),
+ e.message);
return false;
}
+ progress_bar.destroy_with_delay (500);
book_needs_saving = false;
book_uri = uri;
return true;
}
- private bool prompt_to_save (string title, string discard_label)
+ private async bool prompt_to_save_async (string title, string discard_label)
{
if (!book_needs_saving)
return true;
@@ -714,7 +547,7 @@ public class UserInterface : Gtk.ApplicationWindow
"%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."));
+ _("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);
@@ -725,7 +558,7 @@ public class UserInterface : Gtk.ApplicationWindow
switch (response)
{
case Gtk.ResponseType.YES:
- if (save_document ())
+ if (yield save_document_async ())
return true;
else
return false;
@@ -738,32 +571,47 @@ public class UserInterface : Gtk.ApplicationWindow
private void clear_document ()
{
- book_view.default_page = new Page (default_page_width,
- default_page_height,
- default_page_dpi,
- default_page_scan_direction);
book.clear ();
book_needs_saving = false;
book_uri = null;
save_menuitem.sensitive = false;
email_menuitem.sensitive = false;
- print_menuitem.sensitive = false;
+ print_menuitem.sensitive = false;
save_button.sensitive = false;
save_toolbutton.sensitive = false;
copy_to_clipboard_menuitem.sensitive = false;
+ status_primary_label.set_text (/* Label shown when detected a scanner */
+ _("Ready to Scan"));
+ stack.set_visible_child_name ("startup");
}
private void new_document ()
{
- if (!prompt_to_save (/* Text in dialog warning when a document is about to be lost */
- _("Save current document?"),
- /* Button in dialog to create new document and discard unsaved document */
- _("Discard Changes")))
- return;
+ prompt_to_save_async.begin (/* Text in dialog warning when a document is about to be lost */
+ _("Save current document?"),
+ /* Button in dialog to create new document and discard unsaved document */
+ _("Discard Changes"), (obj, res) =>
+ {
+ if (!prompt_to_save_async.end(res))
+ return;
- if (scanning)
- stop_scan ();
- clear_document ();
+ if (scanning)
+ stop_scan ();
+
+ clear_document ();
+ });
+ }
+
+ [GtkCallback]
+ private bool status_label_activate_link_cb (Gtk.Label label, string uri)
+ {
+ if (uri == "install-firmware")
+ {
+ install_drivers ();
+ return true;
+ }
+
+ return false;
}
[GtkCallback]
@@ -812,109 +660,22 @@ public class UserInterface : Gtk.ApplicationWindow
set_document_hint ("photo", true);
}
- private void set_page_side (ScanType page_side)
- {
- Gtk.TreeIter iter;
-
- if (page_side_model.get_iter_first (out iter))
- {
- do
- {
- int s;
- page_side_model.get (iter, 0, out s, -1);
- if (s == page_side)
- {
- page_side_combo.set_active_iter (iter);
- return;
- }
- } while (page_side_model.iter_next (ref iter));
- }
- }
-
- private void set_paper_size (int width, int height)
- {
- Gtk.TreeIter iter;
- bool have_iter;
-
- for (have_iter = paper_size_model.get_iter_first (out iter);
- have_iter;
- have_iter = paper_size_model.iter_next (ref iter))
- {
- int w, h;
- paper_size_model.get (iter, 0, out w, 1, out h, -1);
- if (w == width && h == height)
- break;
- }
-
- if (!have_iter)
- have_iter = paper_size_model.get_iter_first (out iter);
- if (have_iter)
- paper_size_combo.set_active_iter (iter);
- }
-
- private int get_text_dpi ()
- {
- Gtk.TreeIter iter;
- int dpi = DEFAULT_TEXT_DPI;
-
- if (text_dpi_combo.get_active_iter (out iter))
- text_dpi_model.get (iter, 0, out dpi, -1);
-
- return dpi;
- }
-
- private int get_photo_dpi ()
- {
- Gtk.TreeIter iter;
- int dpi = DEFAULT_PHOTO_DPI;
-
- if (photo_dpi_combo.get_active_iter (out iter))
- photo_dpi_model.get (iter, 0, out dpi, -1);
-
- return dpi;
- }
-
- private ScanType get_page_side ()
- {
- Gtk.TreeIter iter;
- int page_side = ScanType.ADF_BOTH;
-
- if (page_side_combo.get_active_iter (out iter))
- page_side_model.get (iter, 0, out page_side, -1);
-
- return (ScanType) page_side;
- }
-
- private bool get_paper_size (out int width, out int height)
- {
- Gtk.TreeIter iter;
-
- width = height = 0;
- if (paper_size_combo.get_active_iter (out iter))
- {
- paper_size_model.get (iter, 0, ref width, 1, ref height, -1);
- return true;
- }
-
- return false;
- }
-
private ScanOptions make_scan_options ()
{
var options = new ScanOptions ();
if (document_hint == "text")
{
options.scan_mode = ScanMode.GRAY;
- options.dpi = get_text_dpi ();
+ options.dpi = preferences_dialog.get_text_dpi ();
options.depth = 2;
}
else
{
options.scan_mode = ScanMode.COLOR;
- options.dpi = get_photo_dpi ();
+ options.dpi = preferences_dialog.get_photo_dpi ();
options.depth = 8;
}
- get_paper_size (out options.paper_width, out options.paper_height);
+ preferences_dialog.get_paper_size (out options.paper_width, out options.paper_height);
options.brightness = brightness;
options.contrast = contrast;
options.page_delay = page_delay;
@@ -927,6 +688,8 @@ public class UserInterface : Gtk.ApplicationWindow
{
var options = make_scan_options ();
options.type = ScanType.SINGLE;
+ status_primary_label.set_text (/* Label shown when scan started */
+ _("Contacting scanner…"));
start_scan (selected_device, options);
}
@@ -944,7 +707,7 @@ public class UserInterface : Gtk.ApplicationWindow
else
{
var options = make_scan_options ();
- options.type = get_page_side ();
+ options.type = preferences_dialog.get_page_side ();
start_scan (selected_device, options);
}
}
@@ -968,18 +731,6 @@ public class UserInterface : Gtk.ApplicationWindow
preferences_dialog.present ();
}
- [GtkCallback]
- private bool preferences_dialog_delete_event_cb (Gtk.Widget widget, Gdk.EventAny event)
- {
- return true;
- }
-
- [GtkCallback]
- private void preferences_dialog_response_cb (Gtk.Widget widget, int response_id)
- {
- preferences_dialog.visible = false;
- }
-
private void update_page_menu ()
{
var page = book_view.selected_page;
@@ -1030,21 +781,18 @@ public class UserInterface : Gtk.ApplicationWindow
menuitem.active = true;
crop_button.active = page.has_crop;
- crop_toolbutton.active = page.has_crop;
updating_page_menu = false;
}
private void show_page_cb (BookView view, Page page)
{
- var path = get_temporary_filename ("scanned-page", "png");
- if (path == null)
- return;
- var file = File.new_for_path (path);
-
+ File file;
try
{
- page.save ("png", quality, file);
+ var dir = DirUtils.make_tmp ("simple-scan-XXXXXX");
+ file = File.new_for_path (Path.build_filename (dir, "scan.png"));
+ page.save_png (file);
}
catch (Error e)
{
@@ -1135,30 +883,6 @@ public class UserInterface : Gtk.ApplicationWindow
}
[GtkCallback]
- private void crop_button_toggled_cb (Gtk.ToggleButton widget)
- {
- if (updating_page_menu)
- return;
-
- if (widget.active)
- custom_crop_menuitem.active = true;
- else
- no_crop_menuitem.active = true;
- }
-
- [GtkCallback]
- private void crop_toolbutton_toggled_cb (Gtk.ToggleToolButton widget)
- {
- if (updating_page_menu)
- return;
-
- if (widget.active)
- custom_crop_menuitem.active = true;
- else
- no_crop_menuitem.active = true;
- }
-
- [GtkCallback]
private void four_by_six_menuitem_toggled_cb (Gtk.CheckMenuItem widget)
{
if (widget.active)
@@ -1407,12 +1131,12 @@ public class UserInterface : Gtk.ApplicationWindow
[GtkCallback]
private void save_file_button_clicked_cb (Gtk.Widget widget)
{
- save_document ();
+ save_document_async.begin ();
}
public void save_document_activate_cb ()
{
- save_document ();
+ save_document_async.begin ();
}
[GtkCallback]
@@ -1451,12 +1175,38 @@ public class UserInterface : Gtk.ApplicationWindow
[GtkCallback]
private void email_button_clicked_cb (Gtk.Widget widget)
{
- email (document_hint, quality);
+ email_document_async.begin ();
}
public void email_document_activate_cb ()
{
- email (document_hint, quality);
+ email_document_async.begin ();
+ }
+
+ private async void email_document_async ()
+ {
+ try
+ {
+ var dir = DirUtils.make_tmp ("simple-scan-XXXXXX");
+ var type = document_hint == "text" ? "pdf" : "jpeg";
+ var file = File.new_for_path (Path.build_filename (dir, "scan." + type));
+ yield book.save_async (type, settings.get_int ("jpeg-quality"), file, null, null);
+ var command_line = "xdg-email";
+ if (type == "pdf")
+ command_line += "--attach %s".printf (file.get_path ());
+ else
+ {
+ for (var i = 0; i < book.n_pages; i++) {
+ var indexed_file = make_indexed_file (file.get_uri (), i, book.n_pages);
+ command_line += " --attach %s".printf (indexed_file.get_path ());
+ }
+ }
+ Process.spawn_command_line_async (command_line);
+ }
+ catch (Error e)
+ {
+ warning ("Unable to email document: %s", e.message);
+ }
}
private void print_document ()
@@ -1552,22 +1302,23 @@ public class UserInterface : Gtk.ApplicationWindow
show_about ();
}
- private bool on_quit ()
+ private void on_quit ()
{
- if (!prompt_to_save (/* Text in dialog warning when a document is about to be lost */
- _("Save document before quitting?"),
- /* Button in dialog to quit and discard unsaved document */
- _("Quit without Saving")))
- return false;
-
- destroy ();
+ prompt_to_save_async.begin (/* Text in dialog warning when a document is about to be lost */
+ _("Save document before quitting?"),
+ /* Text in dialog warning when a document is about to be lost */
+ _("Quit without Saving"), (obj, res) =>
+ {
+ if (!prompt_to_save_async.end(res))
+ return;
- if (save_state_timeout != 0)
- save_state (true);
+ destroy ();
- autosave_manager.cleanup ();
+ if (save_state_timeout != 0)
+ save_state (true);
- return true;
+ autosave_manager.cleanup ();
+ });
}
[GtkCallback]
@@ -1592,28 +1343,6 @@ public class UserInterface : Gtk.ApplicationWindow
}
}
- private void info_bar_response_cb (Gtk.InfoBar widget, int response_id)
- {
- switch (response_id)
- {
- /* Change scanner */
- case 1:
- device_combo.grab_focus ();
- preferences_dialog.present ();
- break;
- /* Install drivers */
- case 2:
- install_drivers ();
- break;
- default:
- have_error = false;
- error_title = null;
- error_text = null;
- update_info_bar ();
- break;
- }
- }
-
private void install_drivers ()
{
var message = "", instructions = "";
@@ -1660,7 +1389,7 @@ public class UserInterface : Gtk.ApplicationWindow
var instructions_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
instructions_box.visible = true;
dialog.get_content_area ().pack_start (instructions_box, true, true, 0);
-
+
var stack = new Gtk.Stack ();
instructions_box.pack_start (stack, false, false, 0);
@@ -1674,14 +1403,14 @@ public class UserInterface : Gtk.ApplicationWindow
var instructions_label = new Gtk.Label (instructions);
instructions_label.visible = true;
- instructions_label.xalign = 0f;
+ instructions_label.xalign = 0f;
instructions_label.use_markup = true;
instructions_box.pack_start (instructions_label, false, false, 0);
label = new Gtk.Label (/* Message in driver install dialog */
_("Once installed you will need to restart Simple Scan."));
label.visible = true;
- label.xalign = 0f;
+ label.xalign = 0f;
dialog.get_content_area ().border_width = 12;
dialog.get_content_area ().pack_start (label, true, true, 0);
@@ -1691,7 +1420,7 @@ public class UserInterface : Gtk.ApplicationWindow
stack.visible = true;
spinner.active = true;
instructions_label.set_text (/* Label shown while installing drivers */
- _("Installing drivers..."));
+ _("Installing drivers…"));
install_packages.begin (packages_to_install, () => {}, (object, result) =>
{
status_label.visible = true;
@@ -1722,7 +1451,7 @@ public class UserInterface : Gtk.ApplicationWindow
});
#else
instructions_label.set_text (/* Label shown to prompt user to install packages (when PackageKit not available) */
- _("You need to install the %s package(s).").printf (string.joinv (", ", packages_to_install)));
+ 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
}
@@ -1773,30 +1502,12 @@ public class UserInterface : Gtk.ApplicationWindow
[GtkCallback]
private bool window_delete_event_cb (Gtk.Widget widget, Gdk.EventAny event)
{
- return !on_quit ();
- }
-
- private void page_size_changed_cb (Page page)
- {
- default_page_width = page.width;
- default_page_height = page.height;
- default_page_dpi = page.dpi;
- save_state ();
- }
-
- private void page_scan_direction_changed_cb (Page page)
- {
- default_page_scan_direction = page.scan_direction;
- save_state ();
+ on_quit ();
+ return true; /* Let us quit on our own terms */
}
private void page_added_cb (Book book, Page page)
{
- page_size_changed_cb (page);
- default_page_scan_direction = page.scan_direction;
- page.size_changed.connect (page_size_changed_cb);
- page.scan_direction_changed.connect (page_scan_direction_changed_cb);
-
update_page_menu ();
}
@@ -1807,50 +1518,14 @@ public class UserInterface : Gtk.ApplicationWindow
private void page_removed_cb (Book book, Page page)
{
- page.size_changed.disconnect (page_size_changed_cb);
- page.scan_direction_changed.disconnect (page_scan_direction_changed_cb);
-
update_page_menu ();
}
- private void set_dpi_combo (Gtk.ComboBox combo, int default_dpi, int current_dpi)
- {
- var renderer = new Gtk.CellRendererText ();
- combo.pack_start (renderer, true);
- combo.add_attribute (renderer, "text", 1);
-
- var model = combo.model as Gtk.ListStore;
- int[] scan_resolutions = {75, 150, 300, 600, 1200, 2400};
- foreach (var dpi in scan_resolutions)
- {
- string label;
- if (dpi == default_dpi)
- /* Preferences dialog: Label for default resolution in resolution list */
- label = _("%d dpi (default)").printf (dpi);
- else if (dpi == 75)
- /* Preferences dialog: Label for minimum resolution in resolution list */
- label = _("%d dpi (draft)").printf (dpi);
- else if (dpi == 1200)
- /* Preferences dialog: Label for maximum resolution in resolution list */
- label = _("%d dpi (high resolution)").printf (dpi);
- else
- /* Preferences dialog: Label for resolution value in resolution list (dpi = dots per inch) */
- label = _("%d dpi").printf (dpi);
-
- Gtk.TreeIter iter;
- model.append (out iter);
- model.set (iter, 0, dpi, 1, label, -1);
-
- if (dpi == current_dpi)
- combo.set_active_iter (iter);
- }
- }
-
private void book_changed_cb (Book book)
{
save_menuitem.sensitive = true;
email_menuitem.sensitive = true;
- print_menuitem.sensitive = true;
+ print_menuitem.sensitive = true;
save_button.sensitive = true;
save_toolbutton.sensitive = true;
book_needs_saving = true;
@@ -1859,13 +1534,19 @@ public class UserInterface : Gtk.ApplicationWindow
private void load ()
{
+ var use_header_bar = !is_traditional_desktop ();
+
+ preferences_dialog = new PreferencesDialog (settings, use_header_bar);
+ preferences_dialog.delete_event.connect (() => { return true; });
+ preferences_dialog.response.connect (() => { preferences_dialog.visible = false; });
+
Gtk.IconTheme.get_default ().append_search_path (ICON_DIR);
Gtk.Window.set_default_icon_name ("scanner");
var app = Application.get_default () as Gtk.Application;
- if (is_traditional_desktop ())
+ if (!use_header_bar)
{
set_titlebar (null);
menubar.visible = true;
@@ -1873,28 +1554,22 @@ public class UserInterface : Gtk.ApplicationWindow
}
else
{
+ /* 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 = _("Simple Scan");
+
app.add_action_entries (action_entries, this);
var appmenu = new Menu ();
- var section = new Menu ();
- appmenu.append_section (null, section);
- section.append (_("New Document"), "app.new_document");
- section = new Menu ();
- appmenu.append_section (null, section);
- var menu = new Menu ();
- section.append_submenu (_("Document"), menu);
- menu.append (_("Reorder Pages"), "app.reorder");
- menu.append (_("Save"), "app.save");
- menu.append (_("Email..."), "app.email");
- menu.append (_("Print..."), "app.print");
-
- section = new Menu ();
+ var section = new Menu ();
appmenu.append_section (null, section);
section.append (_("Preferences"), "app.preferences");
section = new Menu ();
appmenu.append_section (null, section);
+ section.append (_("Keyboard Shortcuts"), "win.show-help-overlay");
section.append (_("Help"), "app.help");
section.append (_("About"), "app.about");
section.append (_("Quit"), "app.quit");
@@ -1907,127 +1582,73 @@ public class UserInterface : Gtk.ApplicationWindow
app.add_accelerator ("<Ctrl>P", "app.print", null);
app.add_accelerator ("F1", "app.help", null);
app.add_accelerator ("<Ctrl>Q", "app.quit", null);
+
+ var gear_menu = new Menu ();
+ section = new Menu ();
+ gear_menu.append_section (null, section);
+ section.append (_("Email"), "app.email");
+ section.append (_("Reorder Pages"), "app.reorder");
+ section.append (_("Preferences"), "app.preferences");
+ menu_button.set_menu_model (gear_menu);
}
app.add_window (this);
- /* Add InfoBar (not supported in Glade) */
- info_bar = new Gtk.InfoBar ();
- info_bar.response.connect (info_bar_response_cb);
- main_vbox.pack_start (info_bar, false, true, 0);
- var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12);
- var content_area = info_bar.get_content_area () as Gtk.Container;
- content_area.add (hbox);
- hbox.visible = true;
-
- info_bar_image = new Gtk.Image.from_icon_name ("dialog-warning", Gtk.IconSize.DIALOG);
- hbox.pack_start (info_bar_image, false, true, 0);
- info_bar_image.visible = true;
-
- info_bar_label = new Gtk.Label (null);
- info_bar_label.set_alignment (0.0f, 0.5f);
- hbox.pack_start (info_bar_label, true, true, 0);
- info_bar_label.visible = true;
-
- info_bar_close_button = info_bar.add_button (_("_Close"), Gtk.ResponseType.CLOSE) as Gtk.Button;
- info_bar_change_scanner_button = info_bar.add_button (/* Button in error infobar to open preferences dialog and change scanner */
- _("Change _Scanner"), 1) as Gtk.Button;
- info_bar_install_button = info_bar.add_button (/* Button in error infobar to prompt user to install drivers */
- _("_Install Drivers"), 2) as Gtk.Button;
+ /* Populate ActionBar (not supported in Glade) */
+ /* https://bugzilla.gnome.org/show_bug.cgi?id=769966 */
+ var button = new Gtk.Button.with_label (/* Label on new document button */
+ _("Start Again…"));
+ button.visible = true;
+ button.clicked.connect (new_button_clicked_cb);
+ action_bar.pack_start (button);
- Gtk.TreeIter iter;
- paper_size_model.append (out iter);
- paper_size_model.set (iter, 0, 0, 1, 0, 2,
- /* Combo box value for automatic paper size */
- _("Automatic"), -1);
- paper_size_model.append (out iter);
- paper_size_model.set (iter, 0, 1050, 1, 1480, 2, "A6", -1);
- paper_size_model.append (out iter);
- paper_size_model.set (iter, 0, 1480, 1, 2100, 2, "A5", -1);
- paper_size_model.append (out iter);
- paper_size_model.set (iter, 0, 2100, 1, 2970, 2, "A4", -1);
- paper_size_model.append (out iter);
- paper_size_model.set (iter, 0, 2159, 1, 2794, 2, "Letter", -1);
- paper_size_model.append (out iter);
- paper_size_model.set (iter, 0, 2159, 1, 3556, 2, "Legal", -1);
- paper_size_model.append (out iter);
- paper_size_model.set (iter, 0, 1016, 1, 1524, 2, "4×6", -1);
-
- var dpi = settings.get_int ("text-dpi");
- if (dpi <= 0)
- dpi = DEFAULT_TEXT_DPI;
- set_dpi_combo (text_dpi_combo, DEFAULT_TEXT_DPI, dpi);
- text_dpi_combo.changed.connect (() => { settings.set_int ("text-dpi", get_text_dpi ()); });
- dpi = settings.get_int ("photo-dpi");
- if (dpi <= 0)
- dpi = DEFAULT_PHOTO_DPI;
- set_dpi_combo (photo_dpi_combo, DEFAULT_PHOTO_DPI, dpi);
- photo_dpi_combo.changed.connect (() => { settings.set_int ("photo-dpi", get_photo_dpi ()); });
+ var 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.pack_start (rotate_box, false, true, 0);
+
+ button = new Gtk.Button.from_icon_name ("object-rotate-left-symbolic");
+ button.visible = true;
+ /* 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.pack_start (button, false, true, 0);
+
+ button = new Gtk.Button.from_icon_name ("object-rotate-right-symbolic");
+ button.visible = true;
+ /* 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.pack_start (button, false, true, 0);
+
+ crop_button = new Gtk.ToggleButton ();
+ crop_button.visible = true;
+ var image = new Gtk.Image.from_icon_name ("edit-cut-symbolic", Gtk.IconSize.BUTTON);
+ image.visible = true;
+ 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;
- var renderer = new Gtk.CellRendererText ();
- device_combo.pack_start (renderer, true);
- device_combo.add_attribute (renderer, "text", 1);
-
- renderer = new Gtk.CellRendererText ();
- page_side_combo.pack_start (renderer, true);
- page_side_combo.add_attribute (renderer, "text", 1);
- set_page_side ((ScanType) settings.get_enum ("page-side"));
- page_side_combo.changed.connect (() => { settings.set_enum ("page-side", get_page_side ()); });
-
- renderer = new Gtk.CellRendererText ();
- paper_size_combo.pack_start (renderer, true);
- paper_size_combo.add_attribute (renderer, "text", 2);
- var paper_width = settings.get_int ("paper-width");
- var paper_height = settings.get_int ("paper-height");
- set_paper_size (paper_width, paper_height);
- paper_size_combo.changed.connect (() =>
- {
- int w, h;
- get_paper_size (out w, out h);
- settings.set_int ("paper-width", w);
- settings.set_int ("paper-height", h);
+ if (widget.active)
+ custom_crop_menuitem.active = true;
+ else
+ no_crop_menuitem.active = true;
});
+ box.pack_start (crop_button, false, true, 0);
- var lower = brightness_adjustment.lower;
- var darker_label = "<small>%s</small>".printf (_("Darker"));
- var upper = brightness_adjustment.upper;
- var lighter_label = "<small>%s</small>".printf (_("Lighter"));
- brightness_scale.add_mark (lower, Gtk.PositionType.BOTTOM, darker_label);
- brightness_scale.add_mark (0, Gtk.PositionType.BOTTOM, null);
- brightness_scale.add_mark (upper, Gtk.PositionType.BOTTOM, lighter_label);
- brightness = settings.get_int ("brightness");
- brightness_adjustment.value_changed.connect (() => { settings.set_int ("brightness", brightness); });
-
- lower = contrast_adjustment.lower;
- var less_label = "<small>%s</small>".printf (_("Less"));
- upper = contrast_adjustment.upper;
- var more_label = "<small>%s</small>".printf (_("More"));
- contrast_scale.add_mark (lower, Gtk.PositionType.BOTTOM, less_label);
- contrast_scale.add_mark (0, Gtk.PositionType.BOTTOM, null);
- contrast_scale.add_mark (upper, Gtk.PositionType.BOTTOM, more_label);
- contrast = settings.get_int ("contrast");
- contrast_adjustment.value_changed.connect (() => { settings.set_int ("contrast", contrast); });
-
- lower = quality_adjustment.lower;
- var minimum_label = "<small>%s</small>".printf (_("Minimum"));
- upper = quality_adjustment.upper;
- var maximum_label = "<small>%s</small>".printf (_("Maximum"));
- quality_scale.add_mark (lower, Gtk.PositionType.BOTTOM, minimum_label);
- quality_scale.add_mark (75, Gtk.PositionType.BOTTOM, null);
- quality_scale.add_mark (upper, Gtk.PositionType.BOTTOM, maximum_label);
- quality = settings.get_int ("jpeg-quality");
- quality_adjustment.value_changed.connect (() => { settings.set_int ("jpeg-quality", quality); });
-
- page_delay_scale.add_mark (0, Gtk.PositionType.BOTTOM, null);
- page_delay_scale.add_mark (500, Gtk.PositionType.BOTTOM, null);
- page_delay_scale.add_mark (1000, Gtk.PositionType.BOTTOM, null);
- page_delay_scale.add_mark (2000, Gtk.PositionType.BOTTOM, null);
- page_delay_scale.add_mark (4000, Gtk.PositionType.BOTTOM, null);
- page_delay_scale.add_mark (6000, Gtk.PositionType.BOTTOM, null);
- page_delay_scale.add_mark (8000, Gtk.PositionType.BOTTOM, null);
- page_delay_scale.add_mark (10000, Gtk.PositionType.BOTTOM, null);
- page_delay = settings.get_int ("page-delay");
- page_delay_scale.format_value.connect ((value) => { return "%.1fs".printf (value / 1000.0); });
- page_delay_adjustment.value_changed.connect (() => { settings.set_int ("page-delay", page_delay); });
+ delete_button = new Gtk.Button.from_icon_name ("user-trash-symbolic");
+ delete_button.visible = true;
+ /* 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.pack_start (delete_button, false, true, 0);
var document_type = settings.get_string ("document-type");
if (document_type != null)
@@ -2035,13 +1656,12 @@ public class UserInterface : Gtk.ApplicationWindow
book_view = new BookView (book);
book_view.border_width = 18;
- main_vbox.pack_end (book_view, true, true, 0);
+ main_vbox.pack_start (book_view, true, true, 0);
book_view.page_selected.connect (page_selected_cb);
book_view.show_page.connect (show_page_cb);
book_view.show_menu.connect (show_page_menu_cb);
book_view.visible = true;
- authorize_dialog.transient_for = this;
preferences_dialog.transient_for = this;
/* Load previous state */
@@ -2060,9 +1680,6 @@ public class UserInterface : Gtk.ApplicationWindow
debug ("Restoring window to fullscreen");
fullscreen ();
}
-
- progress_dialog = new ProgressBarDialog (this, _("Saving document..."));
- book.saving.connect (book_saving_cb);
}
private bool is_desktop (string name)
@@ -2080,12 +1697,12 @@ public class UserInterface : Gtk.ApplicationWindow
private bool is_traditional_desktop ()
{
- const string[] traditional_desktops = { "Unity", "XFCE", "MATE", "LXDE", "Cinnamon", "X-Cinnamon" };
+ const string[] traditional_desktops = { "Unity", "XFCE", "MATE", "LXDE", "Cinnamon", "X-Cinnamon", "i3" };
foreach (var name in traditional_desktops)
if (is_desktop (name))
return true;
return false;
- }
+ }
private string state_filename
{
@@ -2114,25 +1731,6 @@ public class UserInterface : Gtk.ApplicationWindow
window_height = 400;
window_is_maximized = state_get_boolean (f, "window", "is-maximized");
window_is_fullscreen = state_get_boolean (f, "window", "is-fullscreen");
- default_page_width = state_get_integer (f, "last-page", "width", 595);
- default_page_height = state_get_integer (f, "last-page", "height", 842);
- default_page_dpi = state_get_integer (f, "last-page", "dpi", 72);
- switch (state_get_string (f, "last-page", "scan-direction", "top-to-bottom"))
- {
- default:
- case "top-to-bottom":
- default_page_scan_direction = ScanDirection.TOP_TO_BOTTOM;
- break;
- case "bottom-to-top":
- default_page_scan_direction = ScanDirection.BOTTOM_TO_TOP;
- break;
- case "left-to-right":
- default_page_scan_direction = ScanDirection.LEFT_TO_RIGHT;
- break;
- case "right-to-left":
- default_page_scan_direction = ScanDirection.RIGHT_TO_LEFT;
- break;
- }
}
private int state_get_integer (KeyFile f, string group_name, string key, int default = 0)
@@ -2159,18 +1757,6 @@ public class UserInterface : Gtk.ApplicationWindow
}
}
- private string state_get_string (KeyFile f, string group_name, string key, string default = "")
- {
- try
- {
- return f.get_string (group_name, key);
- }
- catch
- {
- return default;
- }
- }
-
private void save_state (bool force = false)
{
if (!force)
@@ -2192,25 +1778,7 @@ public class UserInterface : Gtk.ApplicationWindow
f.set_integer ("window", "width", window_width);
f.set_integer ("window", "height", window_height);
f.set_boolean ("window", "is-maximized", window_is_maximized);
- f.set_boolean ("window", "is-fullscreen", window_is_fullscreen);
- f.set_integer ("last-page", "width", default_page_width);
- f.set_integer ("last-page", "height", default_page_height);
- f.set_integer ("last-page", "dpi", default_page_dpi);
- switch (default_page_scan_direction)
- {
- case ScanDirection.TOP_TO_BOTTOM:
- f.set_value ("last-page", "scan-direction", "top-to-bottom");
- break;
- case ScanDirection.BOTTOM_TO_TOP:
- f.set_value ("last-page", "scan-direction", "bottom-to-top");
- break;
- case ScanDirection.LEFT_TO_RIGHT:
- f.set_value ("last-page", "scan-direction", "left-to-right");
- break;
- case ScanDirection.RIGHT_TO_LEFT:
- f.set_value ("last-page", "scan-direction", "right-to-left");
- break;
- }
+ f.set_boolean ("window", "is-fullscreen", window_is_fullscreen);
try
{
FileUtils.set_contents (state_filename, f.to_data ());
@@ -2221,178 +1789,52 @@ public class UserInterface : Gtk.ApplicationWindow
}
}
- private void book_saving_cb (int page_number)
- {
- /* Prevent GUI from freezing */
- while (Gtk.events_pending ())
- Gtk.main_iteration ();
-
- var total = (int) book.n_pages;
- var fraction = (page_number + 1.0) / total;
- var complete = fraction == 1.0;
- if (complete)
- Timeout.add (500, () => {
- progress_dialog.visible = false;
- return false;
- });
- var message = _("Saving page %d out of %d").printf (page_number + 1, total);
-
- progress_dialog.fraction = fraction;
- progress_dialog.message = message;
- }
-
- public void show_progress_dialog ()
- {
- progress_dialog.visible = true;
- }
-
- public void hide_progress_dialog ()
- {
- progress_dialog.visible = false;
- }
-
- public void show_error (string error_title, string error_text, bool change_scanner_hint)
- {
- have_error = true;
- this.error_title = error_title;
- this.error_text = error_text;
- error_change_scanner_hint = change_scanner_hint;
- update_info_bar ();
- }
-
public void start ()
{
visible = true;
}
}
-private class ProgressBarDialog : Gtk.Window
+private class CancellableProgressBar : Gtk.HBox
{
private Gtk.ProgressBar bar;
+ private Gtk.Button? button;
- public double fraction
- {
- get { return bar.fraction; }
- set { bar.fraction = value; }
- }
-
- public string message
- {
- get { return bar.text; }
- set { bar.text = value; }
- }
-
- public ProgressBarDialog (Gtk.ApplicationWindow parent, string title)
+ public CancellableProgressBar (string? text, Cancellable? cancellable)
{
bar = new Gtk.ProgressBar ();
- var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 5);
- var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 5);
- hbox.hexpand = true;
-
- bar.text = "";
- bar.show_text = true;
- bar.set_size_request (225, 25);
- set_size_request (250, 50);
-
- vbox.pack_start (bar, true, false, 0);
- hbox.pack_start (vbox, true, false, 0);
- add (hbox);
- this.title = title;
-
- transient_for = parent;
- set_position (Gtk.WindowPosition.CENTER_ON_PARENT);
- modal = true;
- resizable = false;
-
- hbox.visible = true;
- vbox.visible = true;
bar.visible = true;
- }
-}
-
-// FIXME: Duplicated from simple-scan.vala
-private string? get_temporary_filename (string prefix, string extension)
-{
- /* NOTE: I'm not sure if this is a 100% safe strategy to use g_file_open_tmp(), close and
- * use the filename but it appears to work in practise */
-
- var filename = "%sXXXXXX.%s".printf (prefix, extension);
- string path;
- try
- {
- var fd = FileUtils.open_tmp (filename, out path);
- Posix.close (fd);
- }
- catch (Error e)
- {
- warning ("Error saving email attachment: %s", e.message);
- return null;
- }
-
- return path;
-}
-
-private class PageIcon : Gtk.DrawingArea
-{
- private string text;
- private double r;
- private double g;
- private double b;
- private const int MINIMUM_WIDTH = 20;
-
- public PageIcon (string text, double r = 1.0, double g = 1.0, double b = 1.0)
- {
- this.text = text;
- this.r = r;
- this.g = g;
- this.b = b;
- }
-
- public override void get_preferred_width (out int minimum_width, out int natural_width)
- {
- minimum_width = natural_width = MINIMUM_WIDTH;
- }
-
- public override void get_preferred_height (out int minimum_height, out int natural_height)
- {
- minimum_height = natural_height = (int) Math.round (MINIMUM_WIDTH * Math.SQRT2);
- }
+ bar.set_text (text);
+ bar.set_show_text (true);
+ pack_start (bar);
- 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);
+ if (cancellable != null)
+ {
+ button = new Gtk.Button.with_label (/* Text of button for cancelling save */
+ _("Cancel"));
+ button.visible = true;
+ button.clicked.connect (() =>
+ {
+ set_visible (false);
+ cancellable.cancel ();
+ });
+ pack_start (button);
+ }
}
- public override void get_preferred_width_for_height (int height, out int minimum_width, out int natural_width)
+ public void set_fraction (double fraction)
{
- minimum_width = natural_width = (int) (height / Math.SQRT2);
+ bar.set_fraction (fraction);
}
- public override bool draw (Cairo.Context c)
+ public void destroy_with_delay (uint delay)
{
- var w = get_allocated_width ();
- var h = get_allocated_height ();
- if (w * Math.SQRT2 > h)
- w = (int) Math.round (h / Math.SQRT2);
- else
- h = (int) Math.round (w * Math.SQRT2);
-
- c.translate ((get_allocated_width () - w) / 2, (get_allocated_height () - h) / 2);
-
- c.rectangle (0.5, 0.5, w - 1, h - 1);
-
- c.set_source_rgb (r, g, b);
- c.fill_preserve ();
-
- c.set_line_width (1.0);
- c.set_source_rgb (0.0, 0.0, 0.0);
- c.stroke ();
+ button.set_sensitive (false);
- Cairo.TextExtents extents;
- c.text_extents (text, out extents);
- c.translate ((w - extents.width) * 0.5 - 0.5, (h + extents.height) * 0.5 - 0.5);
- c.show_text (text);
-
- return true;
+ Timeout.add (delay, () =>
+ {
+ this.destroy ();
+ return false;
+ });
}
}
diff --git a/src/authorize-dialog.ui b/src/authorize-dialog.ui
new file mode 100644
index 0000000..c099563
--- /dev/null
+++ b/src/authorize-dialog.ui
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface>
+ <requires lib="gtk+" version="3.10"/>
+ <template class="AuthorizeDialog" parent="GtkDialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="type_hint">normal</property>
+ <property name="urgency_hint">True</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="authorize_button">
+ <property name="label" translatable="yes" comments="Button to submit authorization dialog">_Authorize</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="authorize_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" comments="This label is set dynamically and is not translated">To connect to ? you need to authorize</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkEntry" id="username_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="password_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="username_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Label beside username entry">_Username for resource:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">username_entry</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="password_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Label beside password entry">_Password:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">password_entry</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">authorize_button</action-widget>
+ </action-widgets>
+ </template>
+</interface>
diff --git a/src/authorize-dialog.vala b/src/authorize-dialog.vala
new file mode 100644
index 0000000..a6e5ab0
--- /dev/null
+++ b/src/authorize-dialog.vala
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+[GtkTemplate (ui = "/org/gnome/SimpleScan/authorize-dialog.ui")]
+private class AuthorizeDialog : Gtk.Dialog
+{
+ [GtkChild]
+ private Gtk.Label authorize_label;
+ [GtkChild]
+ private Gtk.Entry username_entry;
+ [GtkChild]
+ private Gtk.Entry password_entry;
+
+ public AuthorizeDialog (string title)
+ {
+ authorize_label.set_text (title);
+ }
+
+ public string get_username ()
+ {
+ return username_entry.text;
+ }
+
+ public string get_password ()
+ {
+ return password_entry.text;
+ }
+}
diff --git a/src/book-view.vala b/src/book-view.vala
index 182edc7..9a9c9bb 100644
--- a/src/book-view.vala
+++ b/src/book-view.vala
@@ -23,20 +23,6 @@ public class BookView : Gtk.Box
private bool laying_out;
private bool show_selected_page;
- /* Page to show when book empty */
- private PageView? default_page_view = null;
- public Page default_page
- {
- set
- {
- if (value == null)
- default_page_view = null;
- else
- default_page_view = new PageView (value);
- need_layout = true;
- }
- }
-
/* Currently selected page */
private PageView? selected_page_view = null;
public Page? selected_page
@@ -310,16 +296,8 @@ public class BookView : Gtk.Box
private void layout_into (int width, int height, out int book_width, out int book_height)
{
var pages = new List<PageView> ();
- if (book.n_pages == 0)
- {
- if (default_page_view != null)
- pages.append (default_page_view);
- }
- else
- {
- for (var i = 0; i < book.n_pages; i++)
- pages.append (get_nth_page (i));
- }
+ for (var i = 0; i < book.n_pages; i++)
+ pages.append (get_nth_page (i));
/* Get maximum page resolution */
int max_dpi = 0;
@@ -461,16 +439,8 @@ public class BookView : Gtk.Box
context.clip_extents (out left, out top, out right, out bottom);
var pages = new List<PageView> ();
- if (book.n_pages == 0)
- {
- if (default_page_view != null)
- pages.append (default_page_view);
- }
- else
- {
- for (var i = 0; i < book.n_pages; i++)
- pages.append (get_nth_page (i));
- }
+ for (var i = 0; i < book.n_pages; i++)
+ pages.append (get_nth_page (i));
/* Render each page */
foreach (var page in pages)
diff --git a/src/book.vala b/src/book.vala
index a843981..d53b31a 100644
--- a/src/book.vala
+++ b/src/book.vala
@@ -9,6 +9,8 @@
* license.
*/
+public delegate void ProgressionCallback (double fraction);
+
public class Book
{
private List<Page> pages;
@@ -20,7 +22,6 @@ public class Book
public signal void reordered ();
public signal void cleared ();
public signal void changed ();
- public signal void saving (int i);
public Book ()
{
@@ -135,92 +136,440 @@ public class Book
return pages.index (page);
}
- public File make_indexed_file (string uri, int i)
+ public async void save_async (string t, int q, File f, ProgressionCallback? p, Cancellable? c) throws Error
{
- if (n_pages == 1)
- return File.new_for_uri (uri);
-
- /* Insert index before extension */
- var basename = Path.get_basename (uri);
- string prefix = uri, suffix = "";
- var extension_index = basename.last_index_of_char ('.');
- if (extension_index >= 0)
- {
- suffix = basename.slice (extension_index, basename.length);
- prefix = uri.slice (0, uri.length - suffix.length);
- }
- var width = n_pages.to_string().length;
- var number_format = "%%0%dd".printf (width);
- var filename = prefix + "-" + number_format.printf (i + 1) + suffix;
- return File.new_for_uri (filename);
+ var book_saver = new BookSaver ();
+ yield book_saver.save_async (this, t, q, f, p, c);
}
+}
- private void save_multi_file (string type, int quality, File file) throws Error
+private class BookSaver
+{
+ private uint n_pages;
+ private int quality;
+ private File file;
+ private unowned ProgressionCallback progression_callback;
+ private double progression;
+ private Mutex progression_mutex;
+ private Cancellable? cancellable;
+ private AsyncQueue<WriteTask> write_queue;
+ private ThreadPool<EncodeTask> encoder;
+ private SourceFunc save_async_callback;
+
+ /* save_async get called in the main thread to start saving. It
+ * distributes all encode tasks to other threads then yield so
+ * the ui can continue operating. The method then return once saving
+ * is completed, cancelled, or failed */
+ public async void save_async (Book book, string type, int quality, File file, ProgressionCallback? progression_callback, Cancellable? cancellable) throws Error
{
+ var timer = new Timer ();
+
+ this.n_pages = book.n_pages;
+ this.quality = quality;
+ this.file = file;
+ this.cancellable = cancellable;
+ this.save_async_callback = save_async.callback;
+ this.write_queue = new AsyncQueue<WriteTask> ();
+ this.progression = 0;
+ this.progression_mutex = Mutex ();
+
+ /* Configure a callback that monitor saving progression */
+ if (progression_callback == null)
+ this.progression_callback = (fraction) =>
+ {
+ debug ("Save progression: %f%%", fraction*100.0);
+ };
+ else
+ this.progression_callback = progression_callback;
+
+ /* Configure an encoder */
+ ThreadPoolFunc<EncodeTask>? encode_delegate = null;
+ switch (type)
+ {
+ case "jpeg":
+ encode_delegate = encode_jpeg;
+ break;
+ case "png":
+ encode_delegate = encode_png;
+ break;
+#if HAVE_WEBP
+ case "webp":
+ encode_delegate = encode_webp;
+ break;
+#endif
+ case "pdf":
+ encode_delegate = encode_pdf;
+ break;
+ }
+ encoder = new ThreadPool<EncodeTask>.with_owned_data (encode_delegate, (int) get_num_processors (), false);
+
+ /* Configure a writer */
+ ThreadFunc<Error?>? write_delegate = null;
+ switch (type)
+ {
+ case "jpeg":
+ case "png":
+#if HAVE_WEBP
+ case "webp":
+#endif
+ write_delegate = write_multifile;
+ break;
+ case "pdf":
+ write_delegate = write_pdf;
+ break;
+ }
+ var writer = new Thread<Error?> (null, write_delegate);
+
+ /* Issue encode tasks */
for (var i = 0; i < n_pages; i++)
{
- var page = get_page (i);
- page.save (type, quality, make_indexed_file (file.get_uri (), i));
- saving (i);
+ var encode_task = new EncodeTask ();
+ encode_task.number = i;
+ encode_task.page = book.get_page(i);
+ encoder.add ((owned) encode_task);
}
+
+ /* Waiting for saving to finish */
+ yield;
+
+ /* Any error from any thread ends up here */
+ var error = writer.join ();
+ if (error != null)
+ throw error;
+
+ timer.stop ();
+ debug ("Save time: %f seconds", timer.elapsed (null));
}
- private uint8[]? compress_zlib (uint8[] data)
+ /* Those methods are run in the encoder threads pool. It process
+ * one encode_task issued by save_async and reissue the result with
+ * a write_task */
+
+ private void encode_png (owned EncodeTask encode_task)
{
- var stream = ZLib.DeflateStream (ZLib.Level.BEST_COMPRESSION);
- var out_data = new uint8[data.length];
+ var page = encode_task.page;
+ var icc_data = page.get_icc_data_encoded ();
+ var write_task = new WriteTask ();
+ var image = page.get_image (true);
- stream.next_in = data;
- stream.next_out = out_data;
- while (stream.avail_in > 0)
+ string[] keys = { "x-dpi", "y-dpi", "icc-profile", null };
+ string[] values = { "%d".printf (page.dpi), "%d".printf (page.dpi), icc_data, null };
+ if (icc_data == null)
+ keys[2] = null;
+
+ try
{
- if (stream.deflate (ZLib.Flush.FINISH) == ZLib.Status.STREAM_ERROR)
- break;
+ image.save_to_bufferv (out write_task.data, "png", keys, values);
}
+ catch (Error error)
+ {
+ write_task.error = error;
+ }
+ write_task.number = encode_task.number;
+ write_queue.push ((owned) write_task);
- if (stream.avail_in > 0)
- return null;
+ update_progression ();
+ }
- var n_written = data.length - stream.avail_out;
- out_data.resize ((int) n_written);
+ private void encode_jpeg (owned EncodeTask encode_task)
+ {
+ var page = encode_task.page;
+ var icc_data = page.get_icc_data_encoded ();
+ var write_task = new WriteTask ();
+ var image = page.get_image (true);
- return out_data;
+ string[] keys = { "x-dpi", "y-dpi", "quality", "icc-profile", null };
+ string[] values = { "%d".printf (page.dpi), "%d".printf (page.dpi), "%d".printf (quality), icc_data, null };
+ if (icc_data == null)
+ keys[3] = null;
+
+ try
+ {
+ image.save_to_bufferv (out write_task.data, "jpeg", keys, values);
+ }
+ catch (Error error)
+ {
+ write_task.error = error;
+ }
+ write_task.number = encode_task.number;
+ write_queue.push ((owned) write_task);
+
+ update_progression ();
}
- private ByteArray jpeg_data;
+#if HAVE_WEBP
+ private void encode_webp (owned EncodeTask encode_task)
+ {
+ var page = encode_task.page;
+ var icc_data = page.get_icc_data_encoded ();
+ var write_task = new WriteTask ();
+ var image = page.get_image (true);
+ var webp_data = WebP.encode_rgb (image.get_pixels (),
+ image.get_width (),
+ image.get_height (),
+ image.get_rowstride (),
+ (float) quality);
+#if HAVE_COLORD
+ WebP.MuxError mux_error;
+ var mux = WebP.Mux.new_mux ();
+ uint8[] output;
+
+ mux_error = mux.set_image (webp_data, false);
+ debug ("mux.set_image: %s", mux_error.to_string ());
+
+ if (icc_data != null)
+ {
+ mux_error = mux.set_chunk ("ICCP", icc_data.data, false);
+ debug ("mux.set_chunk: %s", mux_error.to_string ());
+ if (mux_error != WebP.MuxError.OK)
+ warning ("icc profile data not saved with page %i", encode_task.number);
+ }
+
+ mux_error = mux.assemble (out output);
+ debug ("mux.assemble: %s", mux_error.to_string ());
+ if (mux_error != WebP.MuxError.OK)
+ write_task.error = new FileError.FAILED (_("Unable to encode page %i").printf (encode_task.number));
- private uint8[] compress_jpeg (Gdk.Pixbuf image, int quality, int dpi)
+ write_task.data = (owned) output;
+#else
+
+ if (webp_data.length == 0)
+ write_task.error = new FileError.FAILED (_("Unable to encode page %i").printf (encode_task.number));
+
+ write_task.data = (owned) webp_data;
+#endif
+ write_task.number = encode_task.number;
+ write_queue.push ((owned) write_task);
+
+ update_progression ();
+ }
+#endif
+
+ private void encode_pdf (owned EncodeTask encode_task)
{
- jpeg_data = new ByteArray ();
- string[] keys = { "quality", "density-unit", "x-density", "y-density", null };
- string[] values = { "%d".printf (quality), "dots-per-inch", "%d".printf (dpi), "%d".printf (dpi), null };
+ var page = encode_task.page;
+ var image = page.get_image (true);
+ var width = image.width;
+ var height = image.height;
+ unowned uint8[] pixels = image.get_pixels ();
+ int depth = 8;
+ string color_space = "DeviceRGB";
+ string? filter = null;
+ uint8[] data;
+
+ if (page.is_color)
+ {
+ depth = 8;
+ color_space = "DeviceRGB";
+ var data_length = height * width * 3;
+ data = new uint8[data_length];
+ for (var row = 0; row < height; row++)
+ {
+ var in_offset = row * image.rowstride;
+ var out_offset = row * width * 3;
+ for (var x = 0; x < width; x++)
+ {
+ var in_o = in_offset + x*3;
+ var out_o = out_offset + x*3;
+
+ data[out_o] = pixels[in_o];
+ data[out_o+1] = pixels[in_o+1];
+ data[out_o+2] = pixels[in_o+2];
+ }
+ }
+ }
+ else if (page.depth == 2)
+ {
+ int shift_count = 6;
+ depth = 2;
+ color_space = "DeviceGray";
+ var data_length = height * ((width * 2 + 7) / 8);
+ data = new uint8[data_length];
+ var offset = 0;
+ for (var row = 0; row < height; row++)
+ {
+ /* Pad to the next line */
+ if (shift_count != 6)
+ {
+ offset++;
+ shift_count = 6;
+ }
+
+ var in_offset = row * image.rowstride;
+ for (var x = 0; x < width; x++)
+ {
+ /* Clear byte */
+ if (shift_count == 6)
+ data[offset] = 0;
+
+ /* Set bits */
+ var p = pixels[in_offset + x*3];
+ if (p >= 192)
+ data[offset] |= 3 << shift_count;
+ else if (p >= 128)
+ data[offset] |= 2 << shift_count;
+ else if (p >= 64)
+ data[offset] |= 1 << shift_count;
+
+ /* Move to the next position */
+ if (shift_count == 0)
+ {
+ offset++;
+ shift_count = 6;
+ }
+ else
+ shift_count -= 2;
+ }
+ }
+ }
+ else if (page.depth == 1)
+ {
+ int mask = 0x80;
+
+ depth = 1;
+ color_space = "DeviceGray";
+ var data_length = height * ((width + 7) / 8);
+ data = new uint8[data_length];
+ var offset = 0;
+ for (var row = 0; row < height; row++)
+ {
+ /* Pad to the next line */
+ if (mask != 0x80)
+ {
+ offset++;
+ mask = 0x80;
+ }
+
+ var in_offset = row * image.rowstride;
+ for (var x = 0; x < width; x++)
+ {
+ /* Clear byte */
+ if (mask == 0x80)
+ data[offset] = 0;
+
+ /* Set bit */
+ if (pixels[in_offset+x*3] != 0)
+ data[offset] |= (uint8) mask;
+
+ /* Move to the next bit */
+ mask >>= 1;
+ if (mask == 0)
+ {
+ offset++;
+ mask = 0x80;
+ }
+ }
+ }
+ }
+ else
+ {
+ depth = 8;
+ color_space = "DeviceGray";
+ var data_length = height * width;
+ data = new uint8 [data_length];
+ for (var row = 0; row < height; row++)
+ {
+ var in_offset = row * image.rowstride;
+ var out_offset = row * width;
+ for (var x = 0; x < width; x++)
+ data[out_offset+x] = pixels[in_offset+x*3];
+ }
+ }
+
+ /* Compress data and use zlib compression if it is smaller than JPEG.
+ * zlib compression is slower in the worst case, so do JPEG first
+ * and stop zlib if it exceeds the JPEG size */
+ var write_task = new WriteTaskPDF ();
+ uint8[]? jpeg_data = null;
try
{
- image.save_to_callbackv (write_pixbuf_data, "jpeg", keys, values);
+ jpeg_data = compress_jpeg (image, quality, page.dpi);
}
- catch (Error e)
+ catch (Error error)
+ {
+ write_task.error = error;
+ }
+ var zlib_data = compress_zlib (data, jpeg_data.length);
+ if (zlib_data != null)
{
+ filter = "FlateDecode";
+ data = zlib_data;
+ }
+ else
+ {
+ filter = "DCTDecode";
+ data = jpeg_data;
}
- var data = (owned) jpeg_data.data;
- jpeg_data = null;
- return data;
+ write_task.number = encode_task.number;
+ write_task.data = data;
+ write_task.width = width;
+ write_task.height = height;
+ write_task.color_space = color_space;
+ write_task.depth = depth;
+ write_task.filter = filter;
+ write_task.dpi = page.dpi;
+ write_queue.push (write_task);
+
+ update_progression ();
}
- private bool write_pixbuf_data (uint8[] buf) throws Error
+ private Error? write_multifile ()
{
- jpeg_data.append (buf);
- return true;
+ for (var i=0; i < n_pages; i++)
+ {
+ if (cancellable.is_cancelled ())
+ {
+ finished_saving ();
+ return null;
+ }
+
+ var write_task = write_queue.pop ();
+ if (write_task.error != null)
+ {
+ finished_saving ();
+ return write_task.error;
+ }
+
+ var indexed_file = make_indexed_file (file.get_uri (), write_task.number, n_pages);
+ try
+ {
+ var stream = indexed_file.replace (null, false, FileCreateFlags.NONE);
+ stream.write_all (write_task.data, null);
+ }
+ catch (Error error)
+ {
+ finished_saving ();
+ return error;
+ }
+ }
+
+ update_progression ();
+ finished_saving ();
+ return null;
}
- private void save_pdf (File file, int quality) throws Error
+ /* Those methods are run in the writer thread. It receive all
+ * write_tasks sent to it by the encoder threads and write those to
+ * disk. */
+
+ private Error? write_pdf ()
{
/* Generate a random ID for this file */
var id = "";
for (var i = 0; i < 4; i++)
id += "%08x".printf (Random.next_int ());
- var stream = file.replace (null, false, FileCreateFlags.NONE, null);
+ FileOutputStream? stream = null;
+ try
+ {
+ stream = file.replace (null, false, FileCreateFlags.NONE, null);
+ }
+ catch (Error error)
+ {
+ finished_saving ();
+ return error;
+ }
var writer = new PDFWriter (stream);
/* Choose object numbers */
@@ -304,163 +653,41 @@ public class Book
writer.write_string (">>\n");
writer.write_string ("endobj\n");
- for (var i = 0; i < n_pages; i++)
+ /* Process each page in order */
+ var tasks_in_standby = new Queue<WriteTaskPDF> ();
+ for (int i = 0; i < n_pages; i++)
{
- var page = get_page (i);
- var image = page.get_image (true);
- var width = image.width;
- var height = image.height;
- unowned uint8[] pixels = image.get_pixels ();
- var page_width = width * 72.0 / page.dpi;
- var page_height = height * 72.0 / page.dpi;
-
- int depth = 8;
- string color_space = "DeviceRGB";
- string? filter = null;
- char[] width_buffer = new char[double.DTOSTR_BUF_SIZE];
- char[] height_buffer = new char[double.DTOSTR_BUF_SIZE];
- uint8[] data;
- if (page.is_color)
+ if (cancellable.is_cancelled ())
{
- depth = 8;
- color_space = "DeviceRGB";
- var data_length = height * width * 3;
- data = new uint8[data_length];
- for (var row = 0; row < height; row++)
- {
- var in_offset = row * image.rowstride;
- var out_offset = row * width * 3;
- for (var x = 0; x < width; x++)
- {
- var in_o = in_offset + x*3;
- var out_o = out_offset + x*3;
-
- data[out_o] = pixels[in_o];
- data[out_o+1] = pixels[in_o+1];
- data[out_o+2] = pixels[in_o+2];
- }
- }
- }
- else if (page.depth == 2)
- {
- int shift_count = 6;
- depth = 2;
- color_space = "DeviceGray";
- var data_length = height * ((width * 2 + 7) / 8);
- data = new uint8[data_length];
- var offset = 0;
- for (var row = 0; row < height; row++)
- {
- /* Pad to the next line */
- if (shift_count != 6)
- {
- offset++;
- shift_count = 6;
- }
-
- var in_offset = row * image.rowstride;
- for (var x = 0; x < width; x++)
- {
- /* Clear byte */
- if (shift_count == 6)
- data[offset] = 0;
-
- /* Set bits */
- var p = pixels[in_offset + x*3];
- if (p >= 192)
- data[offset] |= 3 << shift_count;
- else if (p >= 128)
- data[offset] |= 2 << shift_count;
- else if (p >= 64)
- data[offset] |= 1 << shift_count;
-
- /* Move to the next position */
- if (shift_count == 0)
- {
- offset++;
- shift_count = 6;
- }
- else
- shift_count -= 2;
- }
- }
+ finished_saving ();
+ return null;
}
- else if (page.depth == 1)
- {
- int mask = 0x80;
-
- depth = 1;
- color_space = "DeviceGray";
- var data_length = height * ((width + 7) / 8);
- data = new uint8[data_length];
- var offset = 0;
- for (var row = 0; row < height; row++)
- {
- /* Pad to the next line */
- if (mask != 0x80)
- {
- offset++;
- mask = 0x80;
- }
- var in_offset = row * image.rowstride;
- for (var x = 0; x < width; x++)
- {
- /* Clear byte */
- if (mask == 0x80)
- data[offset] = 0;
-
- /* Set bit */
- if (pixels[in_offset+x*3] != 0)
- data[offset] |= (uint8) mask;
-
- /* Move to the next bit */
- mask >>= 1;
- if (mask == 0)
- {
- offset++;
- mask = 0x80;
- }
- }
- }
- }
+ var write_task = tasks_in_standby.peek_head ();
+ if (write_task != null && write_task.number == i)
+ tasks_in_standby.pop_head ();
else
{
- depth = 8;
- color_space = "DeviceGray";
- var data_length = height * width;
- data = new uint8 [data_length];
- for (var row = 0; row < height; row++)
+ while (true)
{
- var in_offset = row * image.rowstride;
- var out_offset = row * width;
- for (var x = 0; x < width; x++)
- data[out_offset+x] = pixels[in_offset+x*3];
- }
- }
-
- /* Compress data */
- var compressed_data = compress_zlib (data);
- if (compressed_data != null)
- {
- /* Try if JPEG compression is better */
- if (depth > 1)
- {
- var jpeg_data = compress_jpeg (image, quality, page.dpi);
- if (jpeg_data.length < compressed_data.length)
+ write_task = (WriteTaskPDF) write_queue.pop ();
+ if (write_task.error != null)
{
- filter = "DCTDecode";
- data = jpeg_data;
+ finished_saving ();
+ return write_task.error;
}
- }
+ if (write_task.number == i)
+ break;
- if (filter == null)
- {
- filter = "FlateDecode";
- data = compressed_data;
+ tasks_in_standby.insert_sorted (write_task, (a, b) => {return a.number - b.number;});
}
}
+ var page_width = write_task.width * 72.0 / write_task.dpi;
+ var page_height = write_task.height * 72.0 / write_task.dpi;
+ var width_buffer = new char[double.DTOSTR_BUF_SIZE];
+ var height_buffer = new char[double.DTOSTR_BUF_SIZE];
+
/* Page */
writer.write_string ("\n");
writer.start_object (page_numbers[i]);
@@ -481,16 +708,16 @@ public class Book
writer.write_string ("<<\n");
writer.write_string ("/Type /XObject\n");
writer.write_string ("/Subtype /Image\n");
- writer.write_string ("/Width %d\n".printf (width));
- writer.write_string ("/Height %d\n".printf (height));
- writer.write_string ("/ColorSpace /%s\n".printf (color_space));
- writer.write_string ("/BitsPerComponent %d\n".printf (depth));
- writer.write_string ("/Length %d\n".printf (data.length));
- if (filter != null)
- writer.write_string ("/Filter /%s\n".printf (filter));
+ writer.write_string ("/Width %d\n".printf (write_task.width));
+ writer.write_string ("/Height %d\n".printf (write_task.height));
+ writer.write_string ("/ColorSpace /%s\n".printf (write_task.color_space));
+ writer.write_string ("/BitsPerComponent %d\n".printf (write_task.depth));
+ writer.write_string ("/Length %d\n".printf (write_task.data.length));
+ if (write_task.filter != null)
+ writer.write_string ("/Filter /%s\n".printf (write_task.filter));
writer.write_string (">>\n");
writer.write_string ("stream\n");
- writer.write (data);
+ writer.write (write_task.data);
writer.write_string ("\n");
writer.write_string ("endstream\n");
writer.write_string ("endobj\n");
@@ -500,7 +727,7 @@ public class Book
writer.start_object (struct_tree_root_number);
writer.write_string ("%u 0 obj\n".printf (struct_tree_root_number));
writer.write_string ("<<\n");
- writer.write_string ("/Type /StructTreeRoot\n");
+ writer.write_string ("/Type /StructTreeRoot\n");
writer.write_string (">>\n");
writer.write_string ("endobj\n");
@@ -517,8 +744,6 @@ public class Book
writer.write_string ("\n");
writer.write_string ("endstream\n");
writer.write_string ("endobj\n");
-
- saving (i);
}
/* Info */
@@ -535,10 +760,10 @@ public class Book
var xref_offset = writer.offset;
writer.write_string ("xref\n");
writer.write_string ("0 %zu\n".printf (writer.object_offsets.length + 1));
- writer.write_string ("%010zu 65535 f \n".printf (next_empty_object (writer, 0)));
+ writer.write_string ("%010zu 65535 f \n".printf (writer.next_empty_object (0)));
for (var i = 0; i < writer.object_offsets.length; i++)
if (writer.object_offsets[i] == 0)
- writer.write_string ("%010zu 65535 f \n".printf (next_empty_object (writer, i + 1)));
+ writer.write_string ("%010zu 65535 f \n".printf (writer.next_empty_object (i + 1)));
else
writer.write_string ("%010zu 00000 n \n".printf (writer.object_offsets[i]));
@@ -554,31 +779,102 @@ public class Book
writer.write_string ("startxref\n");
writer.write_string ("%zu\n".printf (xref_offset));
writer.write_string ("%%EOF\n");
+
+ update_progression ();
+ finished_saving ();
+ return null;
}
- static int next_empty_object (PDFWriter writer, int start)
+ /* update_progression is called once by page by encoder threads and
+ * once at the end by writer thread. */
+ private void update_progression ()
{
- for (var i = start; i < writer.object_offsets.length; i++)
- if (writer.object_offsets[i] == 0)
- return i + 1;
- return 0;
+ double step = 1.0 / (double)(n_pages+1);
+ progression_mutex.lock ();
+ progression += step;
+ progression_mutex.unlock ();
+ Idle.add (() =>
+ {
+ progression_callback (progression);
+ return false;
+ });
}
- public void save (string type, int quality, File file) throws Error
+ /* finished_saving is called by the writer thread when it's done,
+ * meaning there is nothing left to do or saving has been
+ * cancelled */
+ private void finished_saving ()
{
- switch (type)
+ /* At this point, any remaining encode_task ought to remain unprocessed */
+ ThreadPool.free ((owned) encoder, true, true);
+
+ /* Wake-up save_async method in main thread */
+ Idle.add ((owned)save_async_callback);
+ }
+
+ /* Utility methods */
+
+ private static uint8[]? compress_zlib (uint8[] data, uint max_size)
+ {
+ var stream = ZLib.DeflateStream (ZLib.Level.BEST_COMPRESSION);
+ var out_data = new uint8[max_size];
+
+ stream.next_in = data;
+ stream.next_out = out_data;
+ while (true)
{
- case "jpeg":
- case "png":
- save_multi_file (type, quality, file);
- break;
- case "pdf":
- save_pdf (file, quality);
- break;
+ /* Compression complete */
+ if (stream.avail_in == 0)
+ break;
+
+ /* Out of space */
+ if (stream.avail_out == 0)
+ return null;
+
+ if (stream.deflate (ZLib.Flush.FINISH) == ZLib.Status.STREAM_ERROR)
+ return null;
}
+
+ var n_written = out_data.length - stream.avail_out;
+ out_data.resize ((int) n_written);
+
+ return out_data;
+ }
+
+ private static uint8[] compress_jpeg (Gdk.Pixbuf image, int quality, int dpi) throws Error
+ {
+ uint8[] jpeg_data;
+ string[] keys = { "quality", "x-dpi", "y-dpi", null };
+ string[] values = { "%d".printf (quality), "%d".printf (dpi), "%d".printf (dpi), null };
+
+ image.save_to_bufferv (out jpeg_data, "jpeg", keys, values);
+ return jpeg_data;
}
}
+private class EncodeTask
+{
+ public int number;
+ public Page page;
+}
+
+private class WriteTask
+{
+ public int number;
+ public uint8[] data;
+ public Error error;
+}
+
+private class WriteTaskPDF : WriteTask
+{
+ public int width;
+ public int height;
+ public string color_space;
+ public int depth;
+ public string? filter;
+ public int dpi;
+}
+
private class PDFWriter
{
public size_t offset = 0;
@@ -621,31 +917,32 @@ private class PDFWriter
{
object_offsets[index - 1] = (uint)offset;
}
-}
-
-public class PsWriter
-{
- public Cairo.PsSurface surface;
- public FileOutputStream stream;
- public PsWriter (FileOutputStream stream)
+ public int next_empty_object (int start)
{
- this.stream = stream;
- surface = new Cairo.PsSurface.for_stream (write_cairo_data, 0, 0);
+ for (var i = start; i < object_offsets.length; i++)
+ if (object_offsets[i] == 0)
+ return i + 1;
+ return 0;
}
+}
- private Cairo.Status write_cairo_data (uint8[] data)
+public File make_indexed_file (string uri, uint i, uint n_pages)
+{
+ if (n_pages == 1)
+ return File.new_for_uri (uri);
+
+ /* Insert index before extension */
+ var basename = Path.get_basename (uri);
+ string prefix = uri, suffix = "";
+ var extension_index = basename.last_index_of_char ('.');
+ if (extension_index >= 0)
{
- try
- {
- stream.write_all (data, null, null);
- }
- catch (Error e)
- {
- warning ("Error writing data: %s", e.message);
- return Cairo.Status.WRITE_ERROR;
- }
-
- return Cairo.Status.SUCCESS;
+ suffix = basename.slice (extension_index, basename.length);
+ prefix = uri.slice (0, uri.length - suffix.length);
}
+ var width = n_pages.to_string().length;
+ var number_format = "%%0%dd".printf (width);
+ var filename = prefix + "-" + number_format.printf (i + 1) + suffix;
+ return File.new_for_uri (filename);
}
diff --git a/src/help-overlay.ui b/src/help-overlay.ui
new file mode 100644
index 0000000..dabec9f
--- /dev/null
+++ b/src/help-overlay.ui
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.17 -->
+ <object class="GtkShortcutsWindow" id="help_overlay">
+ <property name="modal">1</property>
+ <child>
+ <object class="GtkShortcutsSection">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Scanning</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;1</property>
+ <property name="title" translatable="yes" context="shortcut window">Scan a single page</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;f</property>
+ <property name="title" translatable="yes" context="shortcut window">Scan all pages from document feeder</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;m</property>
+ <property name="title" translatable="yes" context="shortcut window">Scan continuously from a flatbed scanner</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">Escape</property>
+ <property name="title" translatable="yes" context="shortcut window">Stop scan in progress</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Document Modification</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">less</property>
+ <property name="title" translatable="yes" context="shortcut window">Move page left</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">greater</property>
+ <property name="title" translatable="yes" context="shortcut window">Move page right</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">bracketleft</property>
+ <property name="title" translatable="yes" context="shortcut window">Rotate page to the left (anti-clockwise)</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">bracketright</property>
+ <property name="title" translatable="yes" context="shortcut window">Rotate page to the right (clockwise)</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">Delete</property>
+ <property name="title" translatable="yes" context="shortcut window">Delete page</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Document Management</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;n</property>
+ <property name="title" translatable="yes" context="shortcut window">Start new document</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;s</property>
+ <property name="title" translatable="yes" context="shortcut window">Save scanned document</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;e</property>
+ <property name="title" translatable="yes" context="shortcut window">Email scanned document</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;p</property>
+ <property name="title" translatable="yes" context="shortcut window">Print scanned document</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;c</property>
+ <property name="title" translatable="yes" context="shortcut window">Copy current page to clipboard</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/src/libwebp.vapi b/src/libwebp.vapi
new file mode 100644
index 0000000..74bfdd5
--- /dev/null
+++ b/src/libwebp.vapi
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 Stéphane Fillion
+ * Authors: Stéphane Fillion <stphanef3724@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.
+ */
+
+namespace WebP
+{
+ // Returns the size of the compressed data (pointed to by *output), or 0 if
+ // an error occurred. The compressed data must be released by the caller
+ // using the call 'free(*output)'.
+ // These functions compress using the lossy format, and the quality_factor
+ // can go from 0 (smaller output, lower quality) to 100 (best quality,
+ // larger output).
+ [CCode (cheader_filename = "webp/encode.h", cname = "WebPEncodeRGB")]
+ private size_t _encode_rgb ([CCode (array_length = false)] uint8[] rgb,
+ int width,
+ int height,
+ int stride,
+ float quality_factor,
+ [CCode (array_length = false)] out uint8[] output);
+ [CCode (cname = "vala_encode_rgb")]
+ public uint8[] encode_rgb (uint8[] rgb, int width, int height, int stride, float quality_factor)
+ {
+ uint8[] output;
+ size_t length;
+ length = _encode_rgb (rgb, width, height, stride, quality_factor, out output);
+ output.length = (int) length;
+ return output;
+ }
+
+ // These functions are the equivalent of the above, but compressing in a
+ // lossless manner. Files are usually larger than lossy format, but will
+ // not suffer any compression loss.
+ [CCode (cheader_filename = "webp/encode.h", cname = "WebPEncodeLosslessRGB")]
+ private size_t _encode_lossless_rgb ([CCode (array_length = false)] uint8[] rgb,
+ int width,
+ int height,
+ int stride,
+ [CCode (array_length = false)] out uint8[] output);
+ [CCode (cname = "vala_encode_lossless_rgb")]
+ public uint8[] encode_lossless_rgb (uint8[] rgb, int width, int height, int stride)
+ {
+ uint8[] output;
+ size_t length;
+ length = _encode_lossless_rgb (rgb, width, height, stride, out output);
+ output.length = (int) length;
+ return output;
+ }
+}
diff --git a/src/libwebpmux.vapi b/src/libwebpmux.vapi
new file mode 100644
index 0000000..f2461a2
--- /dev/null
+++ b/src/libwebpmux.vapi
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 Stéphane Fillion
+ * Authors: Stéphane Fillion <stphanef3724@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.
+ */
+
+namespace WebP
+{
+ // Error codes
+ [CCode (cheader_filename = "webp/mux.h", cname = "WebPMuxError", cprefix = "WEBP_MUX_", has_type_id = false)]
+ public enum MuxError
+ {
+ OK = 1,
+ NOT_FOUND = 0,
+ INVALID_ARGUMENT = -1,
+ BAD_DATA = -2,
+ MEMORY_ERROR = -3,
+ NOT_ENOUGH_DATA = -4
+ }
+
+ // Data type used to describe 'raw' data, e.g., chunk data
+ // (ICC profile, metadata) and WebP compressed image data.
+ [CCode (cheader_filename = "webp/mux.h", cname = "WebPData", destroy_function = "", has_type_id = false)]
+ private struct Data
+ {
+ [CCode (array_length = false)] unowned uint8[] bytes;
+ size_t size;
+ }
+
+ // main opaque object.
+ [CCode (cheader_filename = "webp/mux.h", cname = "WebPMux", free_function = "WebPMuxDelete")]
+ [Compact]
+ public class Mux
+ {
+ // Creates an empty mux object.
+ // Returns:
+ // A pointer to the newly created empty mux object.
+ // Or NULL in case of memory error.
+ [CCode (cname = "WebPMuxNew")]
+ public static Mux? new_mux ();
+
+ // Sets the (non-animated and non-fragmented) image in the mux object.
+ // Note: Any existing images (including frames/fragments) will be removed.
+ // Parameters:
+ // mux - (in/out) object in which the image is to be set
+ // bitstream - (in) can be a raw VP8/VP8L bitstream or a single-image
+ // WebP file (non-animated and non-fragmented)
+ // copy_data - (in) value 1 indicates given data WILL be copied to the mux
+ // object and value 0 indicates data will NOT be copied.
+ // Returns:
+ // WEBP_MUX_INVALID_ARGUMENT - if mux is NULL or bitstream is NULL.
+ // WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+ // WEBP_MUX_OK - on success.
+ [CCode (cname = "WebPMuxSetImage")]
+ private MuxError _set_image (Data bitstream, bool copy_data);
+ [CCode (cname = "vala_set_image")]
+ public MuxError set_image (uint8[] bitstream, bool copy_data)
+ {
+ Data data;
+ data.bytes = bitstream;
+ data.size = bitstream.length;
+ return _set_image (data, copy_data);
+ }
+
+ // Adds a chunk with id 'fourcc' and data 'chunk_data' in the mux object.
+ // Any existing chunk(s) with the same id will be removed.
+ // Parameters:
+ // mux - (in/out) object to which the chunk is to be added
+ // fourcc - (in) a character array containing the fourcc of the given chunk;
+ // e.g., "ICCP", "XMP ", "EXIF" etc.
+ // chunk_data - (in) the chunk data to be added
+ // copy_data - (in) value 1 indicates given data WILL be copied to the mux
+ // object and value 0 indicates data will NOT be copied.
+ // Returns:
+ // WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL
+ // or if fourcc corresponds to an image chunk.
+ // WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+ // WEBP_MUX_OK - on success.
+ [CCode (cname = "WebPMuxSetChunk")]
+ private MuxError _set_chunk ([CCode (array_length = false)] uchar[] fourcc,
+ Data chunk_data,
+ bool copy_data);
+ [CCode (cname = "vala_set_chunk")]
+ public MuxError set_chunk (string fourcc, uint8[] chunk_data, bool copy_data)
+ requires (fourcc.length == 4)
+ {
+ Data data;
+ data.bytes = chunk_data;
+ data.size = chunk_data.length;
+ return _set_chunk ((uchar[]) fourcc, data, copy_data);
+ }
+
+ // Assembles all chunks in WebP RIFF format and returns in 'assembled_data'.
+ // This function also validates the mux object.
+ // Note: The content of 'assembled_data' will be ignored and overwritten.
+ // Also, the content of 'assembled_data' is allocated using malloc(), and NOT
+ // owned by the 'mux' object. It MUST be deallocated by the caller by calling
+ // WebPDataClear(). It's always safe to call WebPDataClear() upon return,
+ // even in case of error.
+ // Parameters:
+ // mux - (in/out) object whose chunks are to be assembled
+ // assembled_data - (out) assembled WebP data
+ // Returns:
+ // WEBP_MUX_BAD_DATA - if mux object is invalid.
+ // WEBP_MUX_INVALID_ARGUMENT - if mux or assembled_data is NULL.
+ // WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+ // WEBP_MUX_OK - on success.
+ [CCode (cname = "WebPMuxAssemble")]
+ private MuxError _assemble (out Data assembled_data);
+ [CCode (cname = "vala_assemble")]
+ public MuxError assemble (out uint8[] assembled_data)
+ {
+ Data data;
+ MuxError mux_error;
+ unowned uint8[] out_array;
+ mux_error = _assemble (out data);
+ out_array = data.bytes;
+ out_array.length = (int) data.size;
+ assembled_data = out_array;
+ return mux_error;
+ }
+ }
+}
diff --git a/src/meson.build b/src/meson.build
index cfda86d..9e40e42 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -12,16 +12,23 @@ if packagekit_dep.found ()
vala_args += [ '-D', 'HAVE_PACKAGEKIT' ]
dependencies += packagekit_dep
endif
+if webp_dep.found () and (not colord_dep.found () or webpmux_dep.found ()) # Webpmux only required if colord
+ vala_args += [ '-D', 'HAVE_WEBP' ]
+ dependencies += [ webp_dep, webpmux_dep ]
+endif
simple_scan = executable ('simple-scan',
[ 'config.vapi',
+ 'app-window.vala',
+ 'authorize-dialog.vala',
'book.vala',
'book-view.vala',
'page.vala',
'page-view.vala',
+ 'preferences-dialog.vala',
'simple-scan.vala',
'scanner.vala',
- 'ui.vala',
+ 'screensaver.vala',
'autosave-manager.vala' ] + resources,
dependencies: dependencies,
vala_args: vala_args,
diff --git a/src/page.vala b/src/page.vala
index 8936187..582aef8 100644
--- a/src/page.vala
+++ b/src/page.vala
@@ -624,13 +624,16 @@ public class Page
return image;
}
- private string? get_icc_data_encoded (string icc_profile_filename)
+ public string? get_icc_data_encoded ()
{
+ if (color_profile == null)
+ return null;
+
/* Get binary data */
string contents;
try
{
- FileUtils.get_contents (icc_profile_filename, out contents);
+ FileUtils.get_contents (color_profile, out contents);
}
catch (Error e)
{
@@ -641,63 +644,33 @@ public class Page
/* Encode into base64 */
return Base64.encode ((uchar[]) contents.to_utf8 ());
}
-
+
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 image = get_image (true);
clipboard.set_image (image);
}
- public void save (string type, int quality, File file) throws Error
+ public void save_png (File file) throws Error
{
var stream = file.replace (null, false, FileCreateFlags.NONE, null);
- var writer = new PixbufWriter (stream);
var image = get_image (true);
string? icc_profile_data = null;
if (color_profile != null)
- icc_profile_data = get_icc_data_encoded (color_profile);
+ icc_profile_data = get_icc_data_encoded ();
- if (strcmp (type, "jpeg") == 0)
- {
- string[] keys = { "x-dpi", "y-dpi", "quality", "icc-profile", null };
- string[] values = { "%d".printf (dpi), "%d".printf (dpi), "%d".printf (quality), icc_profile_data, null };
- if (icc_profile_data == null)
- keys[3] = null;
- writer.save (image, "jpeg", keys, values);
- }
- else if (strcmp (type, "png") == 0)
- {
- string[] keys = { "x-dpi", "y-dpi", "icc-profile", null };
- string[] values = { "%d".printf (dpi), "%d".printf (dpi), icc_profile_data, null };
- if (icc_profile_data == null)
- keys[2] = null;
- writer.save (image, "png", keys, values);
- }
- else
- throw new FileError.INVAL ("Unknown file type: %s".printf (type));
- }
-}
+ string[] keys = { "x-dpi", "y-dpi", "icc-profile", null };
+ string[] values = { "%d".printf (dpi), "%d".printf (dpi), icc_profile_data, null };
+ if (icc_profile_data == null)
+ keys[2] = null;
-public class PixbufWriter
-{
- public FileOutputStream stream;
-
- public PixbufWriter (FileOutputStream stream)
- {
- this.stream = stream;
- }
-
- public void save (Gdk.Pixbuf image, string type, string[] option_keys, string[] option_values) throws Error
- {
- image.save_to_callbackv (write_pixbuf_data, type, option_keys, option_values);
- }
-
- private bool write_pixbuf_data (uint8[] buf) throws Error
- {
- stream.write_all (buf, null, null);
- return true;
+ image.save_to_callbackv ((buf) =>
+ {
+ stream.write_all (buf, null, null);
+ return true;
+ }, "png", keys, values);
}
}
diff --git a/src/preferences-dialog.ui b/src/preferences-dialog.ui
new file mode 100644
index 0000000..2272b77
--- /dev/null
+++ b/src/preferences-dialog.ui
@@ -0,0 +1,608 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface>
+ <requires lib="gtk+" version="3.10"/>
+ <object class="GtkAdjustment" id="brightness_adjustment">
+ <property name="lower">-100</property>
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="contrast_adjustment">
+ <property name="lower">-100</property>
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkListStore" id="device_model">
+ <columns>
+ <!-- column-name device_name -->
+ <column type="gchararray"/>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkListStore" id="paper_size_model">
+ <columns>
+ <!-- column-name width -->
+ <column type="gint"/>
+ <!-- column-name height -->
+ <column type="gint"/>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkListStore" id="photo_dpi_model">
+ <columns>
+ <!-- column-name dpi -->
+ <column type="gint"/>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkListStore" id="text_dpi_model">
+ <columns>
+ <!-- column-name dpi -->
+ <column type="gint"/>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <template class="PreferencesDialog" parent="GtkDialog">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes" comments="Title of preferences dialog">Preferences</property>
+ <property name="resizable">False</property>
+ <property name="icon_name">scanner</property>
+ <property name="type_hint">normal</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">30</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="preferences_close_button">
+ <property name="label">gtk-close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_border">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">30</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">30</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">15</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="source_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Label beside scan source combo box">_Scanner</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">device_combo</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="device_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="model">device_model</property>
+ <signal name="changed" handler="device_combo_changed_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="page_side_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Label beside scan side combo box">Scan Sides</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">scan_side_box</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="paper_size_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Label beside page size combo box">Page Size</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="paper_size_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="model">paper_size_model</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="scan_side_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkRadioButton" id="front_side_button">
+ <property name="label" translatable="yes" comments="Preferences Dialog: Toggle button to select scanning on front side of a page">Front</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="back_side_button">
+ <property name="label" translatable="yes" comments="Preferences Dialog: Toggle button to select scanning on the back side of a page">Back</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="draw_indicator">False</property>
+ <property name="group">front_side_button</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="both_side_button">
+ <property name="label" translatable="yes" comments="Preferences Dialog: Toggle button to select scanning on both sides of a page">Both</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="draw_indicator">False</property>
+ <property name="group">front_side_button</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">15</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="page_delay_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Label beside page delay scale">Delay</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Preferences dialog: Label above settings for scanning multiple pages from a flatbed">Multiple pages from flatbed</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkRadioButton" id="page_delay_3s_button">
+ <property name="label" translatable="yes" comments="Preferences Dialog: Toggle button to select scanning on front side of a page">3</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="page_delay_5s_button">
+ <property name="label" translatable="yes" comments="Preferences Dialog: Toggle button to select scanning on front side of a page">5</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">False</property>
+ <property name="group">page_delay_3s_button</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="page_delay_7s_button">
+ <property name="label" translatable="yes" comments="Preferences Dialog: Toggle button to select scanning on front side of a page">7</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">False</property>
+ <property name="group">page_delay_3s_button</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="page_delay_10s_button">
+ <property name="label" translatable="yes" comments="Preferences Dialog: Toggle button to select scanning on front side of a page">10</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">False</property>
+ <property name="group">page_delay_3s_button</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="page_delay_15s_button">
+ <property name="label" translatable="yes" comments="Preferences Dialog: Toggle button to select scanning on front side of a page">15</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">False</property>
+ <property name="group">page_delay_3s_button</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Label after page delay radio buttons">Seconds</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Preferences Dialog: Tab label for scanning settings">Scanning</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">30</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">30</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">14</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="text_dpi_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Label beside scan source combo box">_Text Resolution</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="photo_dpi_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Label beside scan source combo box">_Photo Resolution</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="text_dpi_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="model">text_dpi_model</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="photo_dpi_combo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="model">photo_dpi_model</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">15</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="brightness_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Label beside brightness scale">Brightness</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="contrast_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Label beside contrast scale">Contrast</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">1</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScale" id="brightness_scale">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="adjustment">brightness_adjustment</property>
+ <property name="draw_value">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScale" id="contrast_scale">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="adjustment">contrast_adjustment</property>
+ <property name="draw_value">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_expand">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes" comments="Preferences Dialog: Tab for quality settings">Quality</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="1">preferences_close_button</action-widget>
+ </action-widgets>
+ </template>
+ <object class="GtkSizeGroup" id="label_size_group">
+ <widgets>
+ <widget name="source_label"/>
+ <widget name="page_side_label"/>
+ <widget name="paper_size_label"/>
+ <widget name="page_delay_label"/>
+ <widget name="text_dpi_label"/>
+ <widget name="photo_dpi_label"/>
+ <widget name="brightness_label"/>
+ <widget name="contrast_label"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/src/preferences-dialog.vala b/src/preferences-dialog.vala
new file mode 100644
index 0000000..bf213fb
--- /dev/null
+++ b/src/preferences-dialog.vala
@@ -0,0 +1,534 @@
+/*
+ * 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.
+ */
+
+[GtkTemplate (ui = "/org/gnome/SimpleScan/preferences-dialog.ui")]
+private class PreferencesDialog : Gtk.Dialog
+{
+ private Settings settings;
+
+ private bool setting_devices;
+ private bool user_selected_device;
+
+ [GtkChild]
+ private Gtk.ComboBox device_combo;
+ [GtkChild]
+ private Gtk.ComboBox text_dpi_combo;
+ [GtkChild]
+ private Gtk.ComboBox photo_dpi_combo;
+ [GtkChild]
+ private Gtk.ComboBox paper_size_combo;
+ [GtkChild]
+ private Gtk.Scale brightness_scale;
+ [GtkChild]
+ private Gtk.Scale contrast_scale;
+ [GtkChild]
+ private Gtk.ListStore device_model;
+ [GtkChild]
+ private Gtk.RadioButton page_delay_3s_button;
+ [GtkChild]
+ private Gtk.RadioButton page_delay_5s_button;
+ [GtkChild]
+ private Gtk.RadioButton page_delay_7s_button;
+ [GtkChild]
+ private Gtk.RadioButton page_delay_10s_button;
+ [GtkChild]
+ private Gtk.RadioButton page_delay_15s_button;
+ [GtkChild]
+ private Gtk.ListStore text_dpi_model;
+ [GtkChild]
+ private Gtk.ListStore photo_dpi_model;
+ [GtkChild]
+ private Gtk.RadioButton front_side_button;
+ [GtkChild]
+ private Gtk.RadioButton back_side_button;
+ [GtkChild]
+ private Gtk.RadioButton both_side_button;
+ [GtkChild]
+ private Gtk.ListStore paper_size_model;
+ [GtkChild]
+ private Gtk.Adjustment brightness_adjustment;
+ [GtkChild]
+ private Gtk.Adjustment contrast_adjustment;
+ [GtkChild]
+ private Gtk.Button preferences_close_button;
+
+ public PreferencesDialog (Settings settings, bool use_header_bar)
+ {
+ Object (use_header_bar: use_header_bar ? 1 : -1);
+
+ if (use_header_bar)
+ preferences_close_button.visible = false;
+
+ 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, 2159, 1, 2794, 2, "Letter", -1);
+ paper_size_model.append (out iter);
+ paper_size_model.set (iter, 0, 2159, 1, 3556, 2, "Legal", -1);
+ paper_size_model.append (out iter);
+ paper_size_model.set (iter, 0, 1016, 1, 1524, 2, "4×6", -1);
+
+ var renderer = new Gtk.CellRendererText ();
+ device_combo.pack_start (renderer, true);
+ device_combo.add_attribute (renderer, "text", 1);
+
+ var dpi = settings.get_int ("text-dpi");
+ if (dpi <= 0)
+ dpi = DEFAULT_TEXT_DPI;
+ set_dpi_combo (text_dpi_combo, DEFAULT_TEXT_DPI, dpi);
+ text_dpi_combo.changed.connect (() => { settings.set_int ("text-dpi", get_text_dpi ()); });
+ dpi = settings.get_int ("photo-dpi");
+ if (dpi <= 0)
+ dpi = DEFAULT_PHOTO_DPI;
+ set_dpi_combo (photo_dpi_combo, DEFAULT_PHOTO_DPI, dpi);
+ photo_dpi_combo.changed.connect (() => { settings.set_int ("photo-dpi", get_photo_dpi ()); });
+
+ set_page_side ((ScanType) settings.get_enum ("page-side"));
+ front_side_button.toggled.connect ((button) => { if (button.active) settings.set_enum ("page-side", ScanType.ADF_FRONT); });
+ back_side_button.toggled.connect ((button) => { if (button.active) settings.set_enum ("page-side", ScanType.ADF_BACK); });
+ both_side_button.toggled.connect ((button) => { if (button.active) settings.set_enum ("page-side", ScanType.ADF_BOTH); });
+
+ 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;
+ var lighter_label = "<small>%s</small>".printf (_("Lighter"));
+ brightness_scale.add_mark (lower, Gtk.PositionType.BOTTOM, darker_label);
+ brightness_scale.add_mark (0, Gtk.PositionType.BOTTOM, null);
+ brightness_scale.add_mark (upper, Gtk.PositionType.BOTTOM, lighter_label);
+ brightness_adjustment.value = settings.get_int ("brightness");
+ brightness_adjustment.value_changed.connect (() => { settings.set_int ("brightness", get_brightness ()); });
+
+ lower = contrast_adjustment.lower;
+ var less_label = "<small>%s</small>".printf (_("Less"));
+ upper = contrast_adjustment.upper;
+ var more_label = "<small>%s</small>".printf (_("More"));
+ contrast_scale.add_mark (lower, Gtk.PositionType.BOTTOM, less_label);
+ contrast_scale.add_mark (0, Gtk.PositionType.BOTTOM, null);
+ contrast_scale.add_mark (upper, Gtk.PositionType.BOTTOM, more_label);
+ contrast_adjustment.value = settings.get_int ("contrast");
+ contrast_adjustment.value_changed.connect (() => { settings.set_int ("contrast", get_contrast ()); });
+
+ var paper_width = settings.get_int ("paper-width");
+ var paper_height = settings.get_int ("paper-height");
+ set_paper_size (paper_width, paper_height);
+ paper_size_combo.changed.connect (() =>
+ {
+ int w, h;
+ get_paper_size (out w, out h);
+ settings.set_int ("paper-width", w);
+ settings.set_int ("paper-height", h);
+ });
+
+ set_page_delay (settings.get_int ("page-delay"));
+ page_delay_3s_button.toggled.connect ((button) => { if (button.active) settings.set_int ("page-delay", 3); });
+ page_delay_5s_button.toggled.connect ((button) => { if (button.active) settings.set_int ("page-delay", 5); });
+ page_delay_7s_button.toggled.connect ((button) => { if (button.active) settings.set_int ("page-delay", 7); });
+ page_delay_10s_button.toggled.connect ((button) => { if (button.active) settings.set_int ("page-delay", 10); });
+ page_delay_15s_button.toggled.connect ((button) => { if (button.active) settings.set_int ("page-delay", 15); });
+ }
+
+ public void set_scan_devices (List<ScanDevice> devices)
+ {
+ 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;
+
+ /* 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
+ {
+ device_model.insert (out iter, index);
+ device_model.set (iter, 0, device.name, 1, device.label, -1);
+ }
+ index++;
+ }
+
+ /* Remove any remaining devices */
+ while (device_model.iter_nth_child (out iter, null, index))
+#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);
+ else
+ device_combo.set_active (0);
+ }
+
+ setting_devices = false;
+ }
+
+ public string? get_selected_device ()
+ {
+ Gtk.TreeIter iter;
+
+ if (device_combo.get_active_iter (out iter))
+ {
+ string device;
+ device_model.get (iter, 0, out device, -1);
+ return device;
+ }
+
+ return null;
+ }
+
+ public string? get_selected_device_label ()
+ {
+ Gtk.TreeIter iter;
+
+ if (device_combo.get_active_iter (out iter))
+ {
+ string label;
+ device_model.get (iter, 1, out label, -1);
+ return label;
+ }
+
+ return null;
+ }
+
+ public void set_selected_device (string device)
+ {
+ user_selected_device = true;
+
+ Gtk.TreeIter iter;
+ if (!find_scan_device (device, out iter))
+ return;
+
+ device_combo.set_active_iter (iter);
+ }
+
+ private bool find_scan_device (string device, out Gtk.TreeIter iter)
+ {
+ bool have_iter = false;
+
+ if (device_model.get_iter_first (out iter))
+ {
+ do
+ {
+ string d;
+ device_model.get (iter, 0, out d, -1);
+ if (d == device)
+ have_iter = true;
+ } while (!have_iter && device_model.iter_next (ref iter));
+ }
+
+ return have_iter;
+ }
+
+ private void set_page_side (ScanType page_side)
+ {
+ switch (page_side)
+ {
+ case ScanType.ADF_FRONT:
+ front_side_button.active = true;
+ break;
+ case ScanType.ADF_BACK:
+ back_side_button.active = true;
+ break;
+ default:
+ case ScanType.ADF_BOTH:
+ both_side_button.active = true;
+ break;
+ }
+ }
+
+ public ScanType get_page_side ()
+ {
+ if (front_side_button.active)
+ return ScanType.ADF_FRONT;
+ else if (back_side_button.active)
+ return ScanType.ADF_BACK;
+ else
+ return ScanType.ADF_BOTH;
+ }
+
+ 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))
+ {
+ int w, h;
+ paper_size_model.get (iter, 0, out w, 1, out h, -1);
+ if (w == width && h == height)
+ break;
+ }
+
+ if (!have_iter)
+ have_iter = paper_size_model.get_iter_first (out iter);
+ if (have_iter)
+ paper_size_combo.set_active_iter (iter);
+ }
+
+ 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);
+
+ return 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);
+
+ return 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))
+ {
+ paper_size_model.get (iter, 0, ref width, 1, ref height, -1);
+ return true;
+ }
+
+ return false;
+ }
+
+ public int get_brightness ()
+ {
+ return (int) brightness_adjustment.value;
+ }
+
+ public void set_brightness (int brightness)
+ {
+ brightness_adjustment.value = brightness;
+ }
+
+ public int get_contrast ()
+ {
+ return (int) contrast_adjustment.value;
+ }
+
+ public void set_contrast (int contrast)
+ {
+ contrast_adjustment.value = contrast;
+ }
+
+ public int get_page_delay ()
+ {
+ if (page_delay_15s_button.active)
+ return 15;
+ else if (page_delay_10s_button.active)
+ return 10;
+ else if (page_delay_7s_button.active)
+ return 7;
+ else if (page_delay_5s_button.active)
+ return 5;
+ else
+ return 3;
+ }
+
+ public void set_page_delay (int page_delay)
+ {
+ if (page_delay >= 15)
+ page_delay_15s_button.active = true;
+ else if (page_delay >= 10)
+ page_delay_10s_button.active = true;
+ else if (page_delay >= 7)
+ page_delay_7s_button.active = true;
+ else if (page_delay >= 5)
+ page_delay_5s_button.active = true;
+ else
+ page_delay_3s_button.active = true;
+ }
+
+ private void set_dpi_combo (Gtk.ComboBox combo, int default_dpi, int current_dpi)
+ {
+ var renderer = new Gtk.CellRendererText ();
+ combo.pack_start (renderer, true);
+ combo.add_attribute (renderer, "text", 1);
+
+ var model = combo.model as Gtk.ListStore;
+ int[] scan_resolutions = {75, 150, 300, 600, 1200, 2400};
+ foreach (var dpi in scan_resolutions)
+ {
+ string label;
+ if (dpi == default_dpi)
+ /* Preferences dialog: Label for default resolution in resolution list */
+ label = _("%d dpi (default)").printf (dpi);
+ else if (dpi == 75)
+ /* Preferences dialog: Label for minimum resolution in resolution list */
+ label = _("%d dpi (draft)").printf (dpi);
+ else if (dpi == 1200)
+ /* Preferences dialog: Label for maximum resolution in resolution list */
+ label = _("%d dpi (high resolution)").printf (dpi);
+ else
+ /* Preferences dialog: Label for resolution value in resolution list (dpi = dots per inch) */
+ label = _("%d dpi").printf (dpi);
+
+ Gtk.TreeIter iter;
+ model.append (out iter);
+ model.set (iter, 0, dpi, 1, label, -1);
+
+ if (dpi == current_dpi)
+ combo.set_active_iter (iter);
+ }
+ }
+
+ [GtkCallback]
+ private void device_combo_changed_cb (Gtk.Widget widget)
+ {
+ if (setting_devices)
+ return;
+ user_selected_device = true;
+ if (get_selected_device () != null)
+ settings.set_string ("selected-device", get_selected_device ());
+ }
+}
+
+private class PageIcon : Gtk.DrawingArea
+{
+ private string text;
+ private double r;
+ private double g;
+ private double b;
+ private const int MINIMUM_WIDTH = 20;
+
+ public PageIcon (string text, double r = 1.0, double g = 1.0, double b = 1.0)
+ {
+ this.text = text;
+ this.r = r;
+ this.g = g;
+ this.b = b;
+ }
+
+ public override void get_preferred_width (out int minimum_width, out int natural_width)
+ {
+ minimum_width = natural_width = MINIMUM_WIDTH;
+ }
+
+ public override void get_preferred_height (out int minimum_height, out int natural_height)
+ {
+ minimum_height = natural_height = (int) Math.round (MINIMUM_WIDTH * Math.SQRT2);
+ }
+
+ public override void get_preferred_height_for_width (int width, out int minimum_height, out int natural_height)
+ {
+ minimum_height = natural_height = (int) (width * Math.SQRT2);
+ }
+
+ public override void get_preferred_width_for_height (int height, out int minimum_width, out int natural_width)
+ {
+ minimum_width = natural_width = (int) (height / Math.SQRT2);
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ var w = get_allocated_width ();
+ var h = get_allocated_height ();
+ if (w * Math.SQRT2 > h)
+ w = (int) Math.round (h / Math.SQRT2);
+ else
+ h = (int) Math.round (w * Math.SQRT2);
+
+ c.translate ((get_allocated_width () - w) / 2, (get_allocated_height () - h) / 2);
+
+ c.rectangle (0.5, 0.5, w - 1, h - 1);
+
+ c.set_source_rgb (r, g, b);
+ c.fill_preserve ();
+
+ c.set_line_width (1.0);
+ c.set_source_rgb (0.0, 0.0, 0.0);
+ c.stroke ();
+
+ Cairo.TextExtents extents;
+ c.text_extents (text, out extents);
+ c.translate ((w - extents.width) * 0.5 - 0.5, (h + extents.height) * 0.5 - 0.5);
+ c.show_text (text);
+
+ return true;
+ }
+}
diff --git a/src/screensaver.vala b/src/screensaver.vala
new file mode 100644
index 0000000..ef2dfb8
--- /dev/null
+++ b/src/screensaver.vala
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 Stéphane Fillion
+ * Authors: Stéphane Fillion <stphanef3724@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.
+ */
+
+[DBus (name = "org.freedesktop.ScreenSaver")]
+public interface FreedesktopScreensaver : Object
+{
+ public static FreedesktopScreensaver get_proxy () throws IOError
+ {
+ return Bus.get_proxy_sync (BusType.SESSION, "org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver");
+ }
+
+ [DBus (name = "Inhibit")]
+ public abstract uint32 inhibit (string application_name, string reason_for_inhibit) throws IOError;
+
+ [DBus (name = "UnInhibit")]
+ public abstract void uninhibit (uint32 cookie) throws IOError;
+}
diff --git a/src/simple-scan.gresource.xml b/src/simple-scan.gresource.xml
index b6fe6a6..a62619e 100644
--- a/src/simple-scan.gresource.xml
+++ b/src/simple-scan.gresource.xml
@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/SimpleScan">
- <file preprocess="xml-stripblanks">simple-scan.ui</file>
+ <file preprocess="xml-stripblanks">app-window.ui</file>
+ <file preprocess="xml-stripblanks">preferences-dialog.ui</file>
+ <file preprocess="xml-stripblanks">authorize-dialog.ui</file>
+ </gresource>
+ <gresource prefix="/org/gnome/SimpleScan/gtk">
+ <file preprocess="xml-stripblanks">help-overlay.ui</file>
</gresource>
</gresources>
diff --git a/src/simple-scan.vala b/src/simple-scan.vala
index 2ab83f0..841e702 100644
--- a/src/simple-scan.vala
+++ b/src/simple-scan.vala
@@ -23,7 +23,7 @@ public class SimpleScan : Gtk.Application
/* Help string for command line --debug flag */
N_("Print debugging messages"), null},
{ "fix-pdf", 0, 0, OptionArg.STRING, ref fix_pdf_filename,
- N_("Fix PDF files generated with older versions of Simple Scan"), "FILENAME..."},
+ N_("Fix PDF files generated with older versions of Simple Scan"), "FILENAME…"},
{ null }
};
private static Timer log_timer;
@@ -32,12 +32,16 @@ public class SimpleScan : Gtk.Application
private ScanDevice? default_device = null;
private bool have_devices = false;
private GUsb.Context usb_context;
- private UserInterface ui;
+ private AppWindow app;
private Scanner scanner;
private Book book;
public SimpleScan (ScanDevice? device = null)
{
+ /* The inhibit () method use this */
+ Object (application_id: "org.gnome.SimpleScan");
+ register_session = true;
+
default_device = device;
}
@@ -45,11 +49,10 @@ public class SimpleScan : Gtk.Application
{
base.startup ();
- ui = new UserInterface ();
- book = ui.book;
- ui.start_scan.connect (scan_cb);
- ui.stop_scan.connect (cancel_cb);
- ui.email.connect (email_cb);
+ app = new AppWindow ();
+ book = app.book;
+ app.start_scan.connect (scan_cb);
+ app.stop_scan.connect (cancel_cb);
scanner = Scanner.get_instance ();
scanner.update_devices.connect (update_scan_devices_cb);
@@ -78,15 +81,15 @@ public class SimpleScan : Gtk.Application
List<ScanDevice> device_list = null;
device_list.append (default_device);
- ui.set_scan_devices (device_list);
- ui.selected_device = default_device.name;
+ app.set_scan_devices (device_list);
+ app.selected_device = default_device.name;
}
}
public override void activate ()
{
base.activate ();
- ui.start ();
+ app.start ();
scanner.start ();
}
@@ -94,7 +97,7 @@ public class SimpleScan : Gtk.Application
{
base.shutdown ();
book = null;
- ui = null;
+ app = null;
usb_context = null;
scanner.free ();
}
@@ -127,7 +130,7 @@ public class SimpleScan : Gtk.Application
if (!have_devices)
missing_driver = suggest_driver ();
- ui.set_scan_devices (devices_copy, missing_driver);
+ app.set_scan_devices (devices_copy, missing_driver);
}
/* Taken from /usr/local/Brother/sane/Brsane.ini from brscan driver */
@@ -212,7 +215,7 @@ public class SimpleScan : Gtk.Application
private void authorize_cb (Scanner scanner, string resource)
{
string username, password;
- ui.authorize (resource, out username, out password);
+ app.authorize (resource, out username, out password);
scanner.authorize (username, password);
}
@@ -222,7 +225,7 @@ public class SimpleScan : Gtk.Application
var page = book.get_page (-1);
if (page != null && !page.has_data)
{
- ui.selected_page = page;
+ app.selected_page = page;
page.start ();
return page;
}
@@ -262,7 +265,7 @@ public class SimpleScan : Gtk.Application
page.set_custom_crop (cw, ch);
page.move_crop (cx, cy);
}
- ui.selected_page = page;
+ app.selected_page = page;
page.start ();
return page;
@@ -385,118 +388,80 @@ public class SimpleScan : Gtk.Application
remove_empty_page ();
if (error_code != Sane.Status.CANCELLED)
{
- ui.show_error (/* Title of error dialog when scan failed */
- _("Failed to scan"),
- error_string,
- have_devices);
+ app.show_error_dialog (/* Title of error dialog when scan failed */
+ _("Failed to scan"),
+ error_string);
}
}
- private void scanner_scanning_changed_cb (Scanner scanner)
- {
- ui.scanning = scanner.is_scanning ();
- }
-
- private void scan_cb (UserInterface ui, string? device, ScanOptions options)
- {
- debug ("Requesting scan at %d dpi from device '%s'", options.dpi, device);
+ private uint inhibit_cookie;
+ private FreedesktopScreensaver? fdss;
- if (!scanner.is_scanning ())
- append_page ();
-
- scanner.scan (device, options);
- }
-
- private void cancel_cb (UserInterface ui)
- {
- scanner.cancel ();
- }
-
- private string? get_temporary_filename (string prefix, string extension)
+ private void scanner_scanning_changed_cb (Scanner scanner)
{
- /* NOTE: I'm not sure if this is a 100% safe strategy to use g_file_open_tmp(), close and
- * use the filename but it appears to work in practise */
+ var is_scanning = scanner.is_scanning ();
- var filename = "%sXXXXXX.%s".printf (prefix, extension);
- string path;
- try
- {
- var fd = FileUtils.open_tmp (filename, out path);
- Posix.close (fd);
- }
- catch (Error e)
+ if (is_scanning)
{
- warning ("Error saving email attachment: %s", e.message);
- return null;
- }
+ /* Attempt to inhibit the screensaver when scanning */
+ var reason = _("Scan in progress");
- return path;
- }
+ /* This should work on Gnome, Budgie, Cinnamon, Mate, Unity, ...
+ * but will not work on KDE, LXDE, XFCE, ... */
+ inhibit_cookie = inhibit (app, Gtk.ApplicationInhibitFlags.IDLE, reason);
- private void email_cb (UserInterface ui, string profile, int quality)
- {
- var saved = false;
- var command_line = "xdg-email";
-
- /* Save text files as PDFs */
- if (profile == "text")
- {
- /* Open a temporary file */
- var path = get_temporary_filename ("scan", "pdf");
- if (path != null)
+ if (!is_inhibited (Gtk.ApplicationInhibitFlags.IDLE))
{
- var file = File.new_for_path (path);
- ui.show_progress_dialog ();
+ /* If the previous method didn't work, try the one
+ * provided by Freedesktop. It should work with KDE,
+ * LXDE, XFCE, and maybe others as well. */
try
{
- book.save ("pdf", quality, file);
+ if ((fdss = FreedesktopScreensaver.get_proxy ()) != null)
+ {
+ inhibit_cookie = fdss.inhibit ("Simple-Scan", reason);
+ }
}
- catch (Error e)
- {
- ui.hide_progress_dialog ();
- warning ("Unable to save email file: %s", e.message);
- return;
- }
- command_line += " --attach %s".printf (path);
+ catch (IOError error) {}
}
}
else
{
- for (var i = 0; i < book.n_pages; i++)
+ /* When finished scanning, uninhibit if inhibit was working */
+ if (inhibit_cookie != 0)
{
- var path = get_temporary_filename ("scan", "jpg");
- if (path == null)
- {
- saved = false;
- break;
- }
-
- var file = File.new_for_path (path);
- try
- {
- book.get_page (i).save ("jpeg", quality, file);
- }
- catch (Error e)
+ if (fdss == null)
+ uninhibit (inhibit_cookie);
+ else
{
- warning ("Unable to save email file: %s", e.message);
- return;
+ try
+ {
+ fdss.uninhibit (inhibit_cookie);
+ }
+ catch (IOError error) {}
+ fdss = null;
}
- command_line += " --attach %s".printf (path);
- if (!saved)
- break;
+ inhibit_cookie = 0;
}
}
- debug ("Launching email client: %s", command_line);
- try
- {
- Process.spawn_command_line_async (command_line);
- }
- catch (Error e)
- {
- warning ("Unable to start email: %s", e.message);
- }
+ app.scanning = is_scanning;
+ }
+
+ private void scan_cb (AppWindow ui, string? device, ScanOptions options)
+ {
+ debug ("Requesting scan at %d dpi from device '%s'", options.dpi, device);
+
+ if (!scanner.is_scanning ())
+ append_page ();
+
+ scanner.scan (device, options);
+ }
+
+ private void cancel_cb (AppWindow ui)
+ {
+ scanner.cancel ();
}
private static void log_cb (string? log_domain, LogLevelFlags log_level, string message)
@@ -616,7 +581,7 @@ public class SimpleScan : Gtk.Application
Intl.textdomain (GETTEXT_PACKAGE);
var c = new OptionContext (/* Arguments and description for --help text */
- _("[DEVICE...] - Scanning utility"));
+ _("[DEVICE…] — Scanning utility"));
c.add_main_entries (options, GETTEXT_PACKAGE);
c.add_group (Gtk.get_option_group (true));
try
@@ -627,7 +592,7 @@ public class SimpleScan : Gtk.Application
{
stderr.printf ("%s\n", e.message);
stderr.printf (/* Text printed out when an unknown command-line argument provided */
- _("Run '%s --help' to see a full list of available command line options."), args[0]);
+ _("Run “%s --help” to see a full list of available command line options."), args[0]);
stderr.printf ("\n");
return Posix.EXIT_FAILURE;
}