diff options
| author | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2017-11-11 18:47:31 +0100 | 
|---|---|---|
| committer | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2017-11-11 18:47:31 +0100 | 
| commit | ab6556e393162fff0e0e0c80a9fff689b4e2ca05 (patch) | |
| tree | 74d12800f57397f6123cac49f814e5cabb1df205 /src | |
| parent | 657d8b8812f16b2c377e5b77ff2ffcc4046a7dce (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.ui | 141 | ||||
| -rw-r--r-- | src/authorize-dialog.vala | 37 | ||||
| -rw-r--r-- | src/book-view.vala | 38 | ||||
| -rw-r--r-- | src/book.vala | 781 | ||||
| -rw-r--r-- | src/help-overlay.ui | 128 | ||||
| -rw-r--r-- | src/libwebp.vapi | 55 | ||||
| -rw-r--r-- | src/libwebpmux.vapi | 128 | ||||
| -rw-r--r-- | src/meson.build | 9 | ||||
| -rw-r--r-- | src/page.vala | 63 | ||||
| -rw-r--r-- | src/preferences-dialog.ui | 608 | ||||
| -rw-r--r-- | src/preferences-dialog.vala | 534 | ||||
| -rw-r--r-- | src/screensaver.vala | 25 | ||||
| -rw-r--r-- | src/simple-scan.gresource.xml | 7 | ||||
| -rw-r--r-- | src/simple-scan.vala | 175 | 
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"><ctrl>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"><ctrl>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"><ctrl>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"><ctrl>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"><ctrl>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"><ctrl>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"><ctrl>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"><ctrl>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;          } | 
