[simple-scan/relayout: 1/2] WIP: Relayout
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [simple-scan/relayout: 1/2] WIP: Relayout
- Date: Sun, 3 Feb 2019 21:53:03 +0000 (UTC)
commit 3a403049abb50c018b26b6116776a0e631910a64
Author: Robert Ancell <robert ancell canonical com>
Date: Fri Aug 24 15:34:40 2018 +1200
WIP: Relayout
src/app-window.ui | 96 +++++++-
src/app-window.vala | 92 ++++----
src/book-view.vala | 612 -----------------------------------------------
src/meson.build | 4 +-
src/page-view.vala | 616 ++++++------------------------------------------
src/page.vala | 6 +-
src/thumbnail-view.vala | 143 +++++++++++
7 files changed, 354 insertions(+), 1215 deletions(-)
---
diff --git a/src/app-window.ui b/src/app-window.ui
index 44a0203..2b5a2d3 100644
--- a/src/app-window.ui
+++ b/src/app-window.ui
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
+<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkMenu" id="page_menu">
@@ -249,10 +249,9 @@
</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"/>
+ <signal name="activate-link" handler="status_label_activate_link_cb" swapped="no"/>
<style>
<class name="dim-label"/>
</style>
@@ -266,18 +265,99 @@
</packing>
</child>
<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="GtkActionBar" id="action_bar">
+ <object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkRevealer" id="thumbnail_revealer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="transition_type">slide-right</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="shadow_type">in</property>
+ <property name="width_request">150</property> <!-- Why is min_content_width
not working?! -->
+ <child>
+ <object class="GtkBox" id="thumbnail_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="valign">start</property>
+ <property name="border_width">12</property>
+ <property name="spacing">12</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="label" translatable="yes">Delete All</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ <property name="margin_left">6</property>
+ <property name="margin_right">6</property>
+ <signal name="clicked" handler="new_button_clicked_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="page_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <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">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
</object>
- <packing>
- <property name="pack_type">end</property>
- </packing>
</child>
</object>
<packing>
diff --git a/src/app-window.vala b/src/app-window.vala
index d673725..688ac89 100644
--- a/src/app-window.vala
+++ b/src/app-window.vala
@@ -45,7 +45,7 @@ public class AppWindow : Gtk.ApplicationWindow
[GtkChild]
private Gtk.Label status_secondary_label;
[GtkChild]
- private Gtk.Box main_vbox;
+ private Gtk.Box page_box;
[GtkChild]
private Gtk.RadioMenuItem custom_crop_menuitem;
[GtkChild]
@@ -101,6 +101,11 @@ public class AppWindow : Gtk.ApplicationWindow
[GtkChild]
private Gtk.MenuButton menu_button;
+ [GtkChild]
+ private Gtk.Revealer thumbnail_revealer;
+ [GtkChild]
+ private Gtk.Box thumbnail_box;
+
private bool have_devices = false;
private string? missing_driver = null;
@@ -114,17 +119,22 @@ public class AppWindow : Gtk.ApplicationWindow
{
get
{
- return book_view.selected_page;
+ return page_view.page;
}
set
{
- book_view.selected_page = value;
+ page_view.page = value;
+ foreach (var child in thumbnail_box.get_children ())
+ {
+ var thumbnail = (ThumbnailView) child;
+ thumbnail.selected = thumbnail.page == value;
+ }
}
}
private AutosaveManager autosave_manager;
- private BookView book_view;
+ private PageView page_view;
private bool updating_page_menu;
private string document_hint = "photo";
@@ -200,7 +210,7 @@ public class AppWindow : Gtk.ApplicationWindow
else
{
stack.set_visible_child_name ("document");
- book_view.selected_page = book.get_page (0);
+ selected_page = book.get_page (0);
book_needs_saving = true;
book_changed_cb (book);
}
@@ -731,7 +741,7 @@ public class AppWindow : Gtk.ApplicationWindow
private void update_page_menu ()
{
- var page = book_view.selected_page;
+ var page = page_view.page;
if (page == null)
{
page_move_left_menuitem.sensitive = false;
@@ -745,7 +755,7 @@ public class AppWindow : Gtk.ApplicationWindow
}
}
- private void page_selected_cb (BookView view, Page? page)
+ /*private void page_selected_cb (BookView view, Page? page)
{
if (page == null)
return;
@@ -783,9 +793,9 @@ public class AppWindow : Gtk.ApplicationWindow
crop_button.active = page.has_crop;
updating_page_menu = false;
- }
+ }*/
- private void show_page_cb (BookView view, Page page)
+ /*private void show_page_cb (BookView view, Page page)
{
File file;
try
@@ -796,7 +806,7 @@ public class AppWindow : Gtk.ApplicationWindow
}
catch (Error e)
{
- show_error_dialog (/* Error message display when unable to save image for preview */
+ show_error_dialog (/* Error message display when unable to save image for preview * /
_("Unable to save image for preview"),
e.message);
return;
@@ -808,23 +818,23 @@ public class AppWindow : Gtk.ApplicationWindow
}
catch (Error e)
{
- show_error_dialog (/* Error message display when unable to preview image */
+ show_error_dialog (/* Error message display when unable to preview image * /
_("Unable to open image preview application"),
e.message);
}
- }
+ }*/
- private void show_page_menu_cb (BookView view, Gdk.Event event)
+ /*private void show_page_menu_cb (BookView view, Gdk.Event event)
{
page_menu.popup_at_pointer (event);
- }
+ }*/
[GtkCallback]
private void rotate_left_button_clicked_cb (Gtk.Widget widget)
{
if (updating_page_menu)
return;
- var page = book_view.selected_page;
+ var page = page_view.page;
if (page != null)
page.rotate_left ();
}
@@ -834,7 +844,7 @@ public class AppWindow : Gtk.ApplicationWindow
{
if (updating_page_menu)
return;
- var page = book_view.selected_page;
+ var page = page_view.page;
if (page != null)
page.rotate_right ();
}
@@ -846,7 +856,7 @@ public class AppWindow : Gtk.ApplicationWindow
if (updating_page_menu)
return;
- var page = book_view.selected_page;
+ var page = page_view.page;
if (page == null)
{
warning ("Trying to set crop but no selected page");
@@ -934,7 +944,7 @@ public class AppWindow : Gtk.ApplicationWindow
[GtkCallback]
private void crop_rotate_menuitem_activate_cb (Gtk.Widget widget)
{
- var page = book_view.selected_page;
+ var page = page_view.page;
if (page == null)
return;
page.rotate_crop ();
@@ -943,7 +953,7 @@ public class AppWindow : Gtk.ApplicationWindow
[GtkCallback]
private void page_move_left_menuitem_activate_cb (Gtk.Widget widget)
{
- var page = book_view.selected_page;
+ var page = page_view.page;
var index = book.get_page_index (page);
if (index > 0)
book.move_page (page, index - 1);
@@ -952,7 +962,7 @@ public class AppWindow : Gtk.ApplicationWindow
[GtkCallback]
private void page_move_right_menuitem_activate_cb (Gtk.Widget widget)
{
- var page = book_view.selected_page;
+ var page = page_view.page;
var index = book.get_page_index (page);
if (index < book.n_pages - 1)
book.move_page (page, book.get_page_index (page) + 1);
@@ -961,7 +971,7 @@ public class AppWindow : Gtk.ApplicationWindow
[GtkCallback]
private void page_delete_menuitem_activate_cb (Gtk.Widget widget)
{
- book_view.book.delete_page (book_view.selected_page);
+ book.delete_page (page_view.page);
}
private void reorder_document ()
@@ -1145,7 +1155,7 @@ public class AppWindow : Gtk.ApplicationWindow
[GtkCallback]
private void copy_to_clipboard_button_clicked_cb (Gtk.Widget widget)
{
- var page = book_view.selected_page;
+ var page = page_view.page;
if (page != null)
page.copy_to_clipboard (this);
}
@@ -1484,16 +1494,30 @@ public class AppWindow : Gtk.ApplicationWindow
private void page_added_cb (Book book, Page page)
{
+ var view = new ThumbnailView ();
+ view.page = page;
+ view.visible = true;
+ view.selected = page == selected_page;
+ view.clicked.connect (select_page_cb);
+ thumbnail_box.pack_start (view);
+ thumbnail_revealer.reveal_child = book.n_pages > 1;
update_page_menu ();
}
+ private void select_page_cb (ThumbnailView view)
+ {
+ selected_page = view.page;
+ }
+
private void reordered_cb (Book book)
{
+ // FIXME: Move thumbnail
update_page_menu ();
}
private void page_removed_cb (Book book, Page page)
{
+ // FIXME: Remove thumbnail
update_page_menu ();
}
@@ -1545,14 +1569,6 @@ public class AppWindow : Gtk.ApplicationWindow
app.add_window (this);
- /* Populate ActionBar (not supported in Glade) */
- /* https://bugzilla.gnome.org/show_bug.cgi?id=769966 */
- var button = new Gtk.Button.with_label (/* Label on new document button */
- _("Start Again…"));
- button.visible = true;
- button.clicked.connect (new_document_cb);
- action_bar.pack_start (button);
-
var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 10);
box.visible = true;
action_bar.set_center_widget (box);
@@ -1562,7 +1578,7 @@ public class AppWindow : Gtk.ApplicationWindow
rotate_box.visible = true;
box.add (rotate_box);
- button = new Gtk.Button.from_icon_name ("object-rotate-left-symbolic");
+ var button = new Gtk.Button.from_icon_name ("object-rotate-left-symbolic");
button.visible = true;
button.image.margin_start = 18;
button.image.margin_end = 18;
@@ -1607,21 +1623,17 @@ public class AppWindow : Gtk.ApplicationWindow
delete_button.image.margin_end = 18;
/* Tooltip for delete button */
delete_button.tooltip_text = _("Delete the selected page");
- delete_button.clicked.connect (() => { book_view.book.delete_page (book_view.selected_page); });
+ delete_button.clicked.connect (() => { book.delete_page (page_view.page); });
box.add (delete_button);
var document_type = settings.get_string ("document-type");
if (document_type != null)
set_document_hint (document_type);
- book_view = new BookView (book);
- book_view.border_width = 18;
- book_view.vexpand = true;
- main_vbox.add (book_view);
- 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;
+ page_view = new PageView ();
+ page_view.margin = 18;
+ page_box.add (page_view);
+ page_view.visible = true;
preferences_dialog.transient_for = this;
diff --git a/src/meson.build b/src/meson.build
index 9e40e42..9e0326e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -21,15 +21,15 @@ simple_scan = executable ('simple-scan',
[ 'config.vapi',
'app-window.vala',
'authorize-dialog.vala',
+ 'autosave-manager.vala',
'book.vala',
- 'book-view.vala',
'page.vala',
'page-view.vala',
'preferences-dialog.vala',
'simple-scan.vala',
'scanner.vala',
'screensaver.vala',
- 'autosave-manager.vala' ] + resources,
+ 'thumbnail-view.vala' ] + resources,
dependencies: dependencies,
vala_args: vala_args,
c_args: [ '-DVERSION="@0@"'.format (meson.project_version ()),
diff --git a/src/page-view.vala b/src/page-view.vala
index 03fecfb..ad83716 100644
--- a/src/page-view.vala
+++ b/src/page-view.vala
@@ -23,47 +23,39 @@ public enum CropLocation
BOTTOM_RIGHT
}
-public class PageView
+public class PageView : Gtk.DrawingArea
{
/* Page being rendered */
- public Page page { get; private set; }
-
- /* Image to render at current resolution */
- private Gdk.Pixbuf? image = null;
-
- /* Border around image */
- private bool selected_ = false;
- public bool selected
- {
- get { return selected_; }
- set
- {
- if ((this.selected && selected) || (!this.selected && !selected))
- return;
- this.selected = selected;
- changed ();
+ private Page page_ = null;
+ public Page page {
+ get {
+ return page_;
+ }
+ set {
+ if (page_ != null) {
+ page_.pixels_changed.disconnect (page_pixels_changed_cb);
+ page_.size_changed.disconnect (page_size_changed_cb);
+ page_.crop_changed.disconnect (page_overlay_changed_cb);
+ page_.scan_line_changed.disconnect (page_overlay_changed_cb);
+ page_.scan_direction_changed.disconnect (scan_direction_changed_cb);
+ }
+ page_ = value;
+ page_.pixels_changed.connect (page_pixels_changed_cb);
+ page_.size_changed.connect (page_size_changed_cb);
+ page_.crop_changed.connect (page_overlay_changed_cb);
+ page_.scan_line_changed.connect (page_overlay_changed_cb);
+ page_.scan_direction_changed.connect (scan_direction_changed_cb);
}
}
private int border_width = 1;
- /* True if image needs to be regenerated */
- private bool update_image = true;
-
/* Direction of currently scanned image */
private ScanDirection scan_direction;
/* Next scan line to render */
private int scan_line;
- /* Dimensions of image to generate */
- private int width_;
- private int height_;
-
- /* Location to place this page */
- public int x_offset { get; set; }
- public int y_offset { get; set; }
-
private CropLocation crop_location;
private double selected_crop_px;
private double selected_crop_py;
@@ -79,485 +71,52 @@ public class PageView
private int animate_segment;
private uint animate_timeout;
- public signal void size_changed ();
- public signal void changed ();
-
- public PageView (Page page)
- {
- this.page = page;
- page.pixels_changed.connect (page_pixels_changed_cb);
- page.size_changed.connect (page_size_changed_cb);
- page.crop_changed.connect (page_overlay_changed_cb);
- page.scan_line_changed.connect (page_overlay_changed_cb);
- page.scan_direction_changed.connect (scan_direction_changed_cb);
- }
-
- ~PageView ()
- {
- page.pixels_changed.disconnect (page_pixels_changed_cb);
- page.size_changed.disconnect (page_size_changed_cb);
- page.crop_changed.disconnect (page_overlay_changed_cb);
- page.scan_line_changed.disconnect (page_overlay_changed_cb);
- page.scan_direction_changed.disconnect (scan_direction_changed_cb);
- }
-
- private uchar get_sample (uchar[] pixels, int offset, int x, int depth, int sample)
+ public PageView ()
{
- // FIXME
- return 0xFF;
+ //add_events (Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK);
}
- private void get_pixel (Page page, int x, int y, uchar[] pixel)
+ private void get_page_size (out int width, out int height)
{
- switch (page.scan_direction)
- {
- case ScanDirection.TOP_TO_BOTTOM:
- break;
- case ScanDirection.BOTTOM_TO_TOP:
- x = page.scan_width - x - 1;
- y = page.scan_height - y - 1;
- break;
- case ScanDirection.LEFT_TO_RIGHT:
- var t = x;
- x = page.scan_width - y - 1;
- y = t;
- break;
- case ScanDirection.RIGHT_TO_LEFT:
- var t = x;
- x = y;
- y = page.scan_height - t - 1;
- break;
- }
-
- var depth = page.depth;
- var n_channels = page.n_channels;
- unowned uchar[] pixels = page.get_pixels ();
- var offset = page.rowstride * y;
-
- /* Optimise for 8 bit images */
- if (depth == 8 && n_channels == 3)
- {
- var o = offset + x * n_channels;
- pixel[0] = pixels[o];
- pixel[1] = pixels[o+1];
- pixel[2] = pixels[o+2];
- return;
- }
- else if (depth == 8 && n_channels == 1)
- {
- pixel[0] = pixel[1] = pixel[2] = pixels[offset + x];
- return;
- }
-
- /* Optimise for bitmaps */
- else if (depth == 1 && n_channels == 1)
- {
- var o = offset + (x / 8);
- pixel[0] = pixel[1] = pixel[2] = (pixels[o] & (0x80 >> (x % 8))) != 0 ? 0x00 : 0xFF;
- return;
- }
-
- /* Optimise for 2 bit images */
- else if (depth == 2 && n_channels == 1)
- {
- int block_shift[4] = { 6, 4, 2, 0 };
-
- var o = offset + (x / 4);
- var sample = (pixels[o] >> block_shift[x % 4]) & 0x3;
- sample = sample * 255 / 3;
-
- pixel[0] = pixel[1] = pixel[2] = (uchar) sample;
- return;
- }
+ var w = get_allocated_width () - border_width * 2;
+ var h = get_allocated_height () - border_width * 2;
- /* Use slow method */
- pixel[0] = get_sample (pixels, offset, x, depth, x * n_channels);
- pixel[1] = get_sample (pixels, offset, x, depth, x * n_channels + 1);
- pixel[2] = get_sample (pixels, offset, x, depth, x * n_channels + 2);
- }
-
- private void set_pixel (Page page, double l, double r, double t, double b, uchar[] output, int offset)
- {
- /* Decimation:
- *
- * Target pixel is defined by (t,l)-(b,r)
- * It touches 16 pixels in original image
- * It completely covers 4 pixels in original image (T,L)-(B,R)
- * Add covered pixels and add weighted partially covered pixels.
- * Divide by total area.
- *
- * l L R r
- * +-----+-----+-----+-----+
- * | | | | |
- * t | +--+-----+-----+---+ |
- * T +--+--+-----+-----+---+-+
- * | | | | | | |
- * | | | | | | |
- * +--+--+-----+-----+---+-+
- * | | | | | | |
- * | | | | | | |
- * B +--+--+-----+-----+---+-+
- * | | | | | | |
- * b | +--+-----+-----+---+ |
- * +-----+-----+-----+-----+
- *
- *
- * Interpolation:
- *
- * l r
- * +-----+-----+-----+-----+
- * | | | | |
- * | | | | |
- * +-----+-----+-----+-----+
- * t | | +-+--+ | |
- * | | | | | | |
- * +-----+---+-+--+--+-----+
- * b | | +-+--+ | |
- * | | | | |
- * +-----+-----+-----+-----+
- * | | | | |
- * | | | | |
- * +-----+-----+-----+-----+
- *
- * Same again, just no completely covered pixels.
- */
-
- var L = (int) l;
- if (L != l)
- L++;
- var R = (int) r;
- var T = (int) t;
- if (T != t)
- T++;
- var B = (int) b;
-
- var red = 0.0;
- var green = 0.0;
- var blue = 0.0;
-
- /* Target can fit inside one source pixel
- * +-----+
- * | |
- * | +--+| +-----+-----+ +-----+ +-----+ +-----+
- * +-+--++ or | +-++ | or | +-+ | or | +--+| or | +--+
- * | +--+| | +-++ | | +-+ | | | || | | |
- * | | +-----+-----+ +-----+ +-+--++ +--+--+
- * +-----+
- */
- if ((r - l <= 1.0 && (int)r == (int)l) || (b - t <= 1.0 && (int)b == (int)t))
- {
- /* Inside */
- if ((int)l == (int)r || (int)t == (int)b)
- {
- uchar p[3];
- get_pixel (page, (int)l, (int)t, p);
- output[offset] = p[0];
- output[offset+1] = p[1];
- output[offset+2] = p[2];
- return;
- }
-
- /* Stradling horizontal edge */
- if (L > R)
- {
- uchar p[3];
- get_pixel (page, R, T-1, p);
- red += p[0] * (r-l)*(T-t);
- green += p[1] * (r-l)*(T-t);
- blue += p[2] * (r-l)*(T-t);
- for (var y = T; y < B; y++)
- {
- get_pixel (page, R, y, p);
- red += p[0] * (r-l);
- green += p[1] * (r-l);
- blue += p[2] * (r-l);
- }
- get_pixel (page, R, B, p);
- red += p[0] * (r-l)*(b-B);
- green += p[1] * (r-l)*(b-B);
- blue += p[2] * (r-l)*(b-B);
- }
- /* Stradling vertical edge */
- else
- {
- uchar p[3];
- get_pixel (page, L - 1, B, p);
- red += p[0] * (b-t)*(L-l);
- green += p[1] * (b-t)*(L-l);
- blue += p[2] * (b-t)*(L-l);
- for (var x = L; x < R; x++) {
- get_pixel (page, x, B, p);
- red += p[0] * (b-t);
- green += p[1] * (b-t);
- blue += p[2] * (b-t);
- }
- get_pixel (page, R, B, p);
- red += p[0] * (b-t)*(r-R);
- green += p[1] * (b-t)*(r-R);
- blue += p[2] * (b-t)*(r-R);
- }
-
- var scale = 1.0 / ((r - l) * (b - t));
- output[offset] = (uchar)(red * scale + 0.5);
- output[offset+1] = (uchar)(green * scale + 0.5);
- output[offset+2] = (uchar)(blue * scale + 0.5);
- return;
- }
-
- /* Add the middle pixels */
- for (var x = L; x < R; x++)
- {
- for (var y = T; y < B; y++)
- {
- uchar p[3];
- get_pixel (page, x, y, p);
- red += p[0];
- green += p[1];
- blue += p[2];
- }
- }
-
- /* Add the weighted top and bottom pixels */
- for (var x = L; x < R; x++)
- {
- if (t != T)
- {
- uchar p[3];
- get_pixel (page, x, T - 1, p);
- red += p[0] * (T - t);
- green += p[1] * (T - t);
- blue += p[2] * (T - t);
- }
-
- if (b != B)
- {
- uchar p[3];
- get_pixel (page, x, B, p);
- red += p[0] * (b - B);
- green += p[1] * (b - B);
- blue += p[2] * (b - B);
- }
- }
-
- /* Add the left and right pixels */
- for (var y = T; y < B; y++)
- {
- if (l != L)
- {
- uchar p[3];
- get_pixel (page, L - 1, y, p);
- red += p[0] * (L - l);
- green += p[1] * (L - l);
- blue += p[2] * (L - l);
- }
-
- if (r != R)
- {
- uchar p[3];
- get_pixel (page, R, y, p);
- red += p[0] * (r - R);
- green += p[1] * (r - R);
- blue += p[2] * (r - R);
- }
- }
-
- /* Add the corner pixels */
- if (l != L && t != T)
- {
- uchar p[3];
- get_pixel (page, L - 1, T - 1, p);
- red += p[0] * (L - l)*(T - t);
- green += p[1] * (L - l)*(T - t);
- blue += p[2] * (L - l)*(T - t);
- }
- if (r != R && t != T)
- {
- uchar p[3];
- get_pixel (page, R, T - 1, p);
- red += p[0] * (r - R)*(T - t);
- green += p[1] * (r - R)*(T - t);
- blue += p[2] * (r - R)*(T - t);
- }
- if (r != R && b != B)
- {
- uchar p[3];
- get_pixel (page, R, B, p);
- red += p[0] * (r - R)*(b - B);
- green += p[1] * (r - R)*(b - B);
- blue += p[2] * (r - R)*(b - B);
- }
- if (l != L && b != B)
- {
- uchar p[3];
- get_pixel (page, L - 1, B, p);
- red += p[0] * (L - l)*(b - B);
- green += p[1] * (L - l)*(b - B);
- blue += p[2] * (L - l)*(b - B);
- }
-
- /* Scale pixel values and clamp in range [0, 255] */
- var scale = 1.0 / ((r - l) * (b - t));
- output[offset] = (uchar)(red * scale + 0.5);
- output[offset+1] = (uchar)(green * scale + 0.5);
- output[offset+2] = (uchar)(blue * scale + 0.5);
- }
-
- private void update_preview (Page page, ref Gdk.Pixbuf? output_image, int output_width, int
output_height,
- ScanDirection scan_direction, int old_scan_line, int scan_line)
- {
- var input_width = page.width;
- var input_height = page.height;
-
- /* Create new image if one does not exist or has changed size */
- int L, R, T, B;
- if (output_image == null ||
- output_image.width != output_width ||
- output_image.height != output_height)
- {
- output_image = new Gdk.Pixbuf (Gdk.Colorspace.RGB,
- false,
- 8,
- output_width,
- output_height);
-
- /* Update entire image */
- L = 0;
- R = output_width - 1;
- T = 0;
- B = output_height - 1;
- }
- /* Otherwise only update changed area */
+ /* Fit page inside available space */
+ if (w * page.height > h * page.width)
+ w = h * page.width / page.height;
else
- {
- switch (scan_direction)
- {
- case ScanDirection.TOP_TO_BOTTOM:
- L = 0;
- R = output_width - 1;
- T = (int)((double)old_scan_line * output_height / input_height);
- B = (int)((double)scan_line * output_height / input_height + 0.5);
- break;
- case ScanDirection.LEFT_TO_RIGHT:
- L = (int)((double)old_scan_line * output_width / input_width);
- R = (int)((double)scan_line * output_width / input_width + 0.5);
- T = 0;
- B = output_height - 1;
- break;
- case ScanDirection.BOTTOM_TO_TOP:
- L = 0;
- R = output_width - 1;
- T = (int)((double)(input_height - scan_line) * output_height / input_height);
- B = (int)((double)(input_height - old_scan_line) * output_height / input_height + 0.5);
- break;
- case ScanDirection.RIGHT_TO_LEFT:
- L = (int)((double)(input_width - scan_line) * output_width / input_width);
- R = (int)((double)(input_width - old_scan_line) * output_width / input_width + 0.5);
- T = 0;
- B = output_height - 1;
- break;
- default:
- L = R = B = T = 0;
- break;
- }
- }
-
- /* FIXME: There's an off by one error in there somewhere... */
- if (R >= output_width)
- R = output_width - 1;
- if (B >= output_height)
- B = output_height - 1;
-
- return_if_fail (L >= 0);
- return_if_fail (R < output_width);
- return_if_fail (T >= 0);
- return_if_fail (B < output_height);
- return_if_fail (output_image != null);
-
- unowned uchar[] output = output_image.get_pixels ();
- var output_rowstride = output_image.rowstride;
- var output_n_channels = output_image.n_channels;
-
- if (!page.has_data)
- {
- for (var x = L; x <= R; x++)
- for (var y = T; y <= B; y++)
- {
- var o = output_rowstride * y + x * output_n_channels;
- output[o] = output[o+1] = output[o+2] = 0xFF;
- }
- return;
- }
-
- /* Update changed area */
- for (var x = L; x <= R; x++)
- {
- var l = (double)x * input_width / output_width;
- var r = (double)(x + 1) * input_width / output_width;
-
- for (var y = T; y <= B; y++)
- {
- var t = (double)y * input_height / output_height;
- var b = (double)(y + 1) * input_height / output_height;
-
- set_pixel (page,
- l, r, t, b,
- output, output_rowstride * y + x * output_n_channels);
- }
- }
- }
-
- private int get_preview_width ()
- {
- return width_ - border_width * 2;
- }
-
- private int get_preview_height ()
- {
- return height_ - border_width * 2;
- }
-
- private void update_page_view ()
- {
- if (!update_image)
- return;
-
- var old_scan_line = scan_line;
- var scan_line = page.scan_line;
+ h = w * page.height / page.width;
- /* Delete old image if scan direction changed */
- var left_steps = scan_direction - page.scan_direction;
- if (left_steps != 0 && image != null)
- image = null;
- scan_direction = page.scan_direction;
-
- update_preview (page,
- ref image,
- get_preview_width (),
- get_preview_height (),
- page.scan_direction, old_scan_line, scan_line);
-
- update_image = false;
- this.scan_line = scan_line;
+ width = w;
+ height = h;
}
private int page_to_screen_x (int x)
{
- return (int) ((double)x * get_preview_width () / page.width + 0.5);
+ int w, h;
+ get_page_size (out w, out h);
+ return (int) ((double)x * w / page.width + 0.5);
}
private int page_to_screen_y (int y)
{
- return (int) ((double)y * get_preview_height () / page.height + 0.5);
+ int w, h;
+ get_page_size (out w, out h);
+ return (int) ((double)y * h / page.height + 0.5);
}
private int screen_to_page_x (int x)
{
- return (int) ((double)x * page.width / get_preview_width () + 0.5);
+ int w, h;
+ get_page_size (out w, out h);
+ return (int) ((double)x * page.width / w + 0.5);
}
private int screen_to_page_y (int y)
{
- return (int) ((double)y * page.height / get_preview_height () + 0.5);
+ int w, h;
+ get_page_size (out w, out h);
+ return (int) ((double)y * page.height / h + 0.5);
}
private CropLocation get_crop_location (int x, int y)
@@ -787,13 +346,13 @@ public class PageView
{
/* Complete crop */
crop_location = CropLocation.NONE;
- changed ();
+ queue_draw ();
}
private bool animation_cb ()
{
animate_segment = (animate_segment + 1) % animate_n_segments;
- changed ();
+ queue_draw ();
return true;
}
@@ -820,30 +379,35 @@ public class PageView
}
}
- public void render (Cairo.Context context)
+ public override bool draw (Cairo.Context context)
{
update_animation ();
- update_page_view ();
- var w = get_preview_width ();
- var h = get_preview_height ();
+ int w, h;
+ get_page_size (out w, out h);
+
+ var x_offset = (get_allocated_width () - w - border_width * 2) / 2;
+ var y_offset = (get_allocated_height () - h - border_width * 2) / 2;
context.set_line_width (1);
- context.translate (x_offset, y_offset);
/* Draw page border */
- context.set_source_rgb (0, 0, 0);
+ context.set_source_rgba (0, 0, 0, 0.25);
context.set_line_width (border_width);
- context.rectangle ((double)border_width / 2,
- (double)border_width / 2,
- width_ - border_width,
- height_ - border_width);
+ context.rectangle (x_offset + (double)border_width / 2,
+ y_offset + (double)border_width / 2,
+ w + border_width,
+ h + border_width);
context.stroke ();
/* Draw image */
- context.translate (border_width, border_width);
+ var image = page.get_image (); // FIXME: Cache and mipmap
+ context.translate (x_offset + border_width, y_offset + border_width);
+ context.save ();
+ context.scale ((double) w / image.width, (double) h / image.height);
Gdk.cairo_set_source_pixbuf (context, image, 0, 0);
context.paint ();
+ context.restore ();
/* Draw throbber */
if (page.is_scanning && !page.has_data)
@@ -947,75 +511,27 @@ public class PageView
context.set_source_rgb (0.0, 0.0, 0.0);
context.stroke ();
}
- }
-
- public int width
- {
- get { return width_; }
- set
- {
- // FIXME: Automatically update when get updated image
- var h = (int) ((double) value * page.height / page.width);
- if (width_ == value && height_ == h)
- return;
-
- width_ = value;
- height_ = h;
- /* Regenerate image */
- update_image = true;
-
- size_changed ();
- changed ();
- }
- }
-
- public int height
- {
- get { return height_; }
- set
- {
- // FIXME: Automatically update when get updated image
- var w = (int) ((double) value * page.width / page.height);
- if (width_ == w && height_ == value)
- return;
-
- width_ = w;
- height_ = value;
-
- /* Regenerate image */
- update_image = true;
-
- size_changed ();
- changed ();
- }
+ return true;
}
private void page_pixels_changed_cb (Page p)
{
- /* Regenerate image */
- update_image = true;
- changed ();
+ queue_draw ();
}
private void page_size_changed_cb (Page p)
{
- /* Regenerate image */
- update_image = true;
- size_changed ();
- changed ();
+ queue_draw ();
}
private void page_overlay_changed_cb (Page p)
{
- changed ();
+ queue_draw ();
}
private void scan_direction_changed_cb (Page p)
{
- /* Regenerate image */
- update_image = true;
- size_changed ();
- changed ();
+ queue_draw ();
}
}
diff --git a/src/page.vala b/src/page.vala
index 13de4d2..35b904a 100644
--- a/src/page.vala
+++ b/src/page.vala
@@ -587,7 +587,7 @@ public class Page
pixel[offset+2] = get_sample (pixels, line_offset, x, depth, n_channels, 2);
}
- public Gdk.Pixbuf get_image (bool apply_crop)
+ public Gdk.Pixbuf get_image (bool apply_crop = true)
{
int l, r, t, b;
if (apply_crop && has_crop)
@@ -651,14 +651,14 @@ public class Page
{
var display = window.get_display ();
var clipboard = Gtk.Clipboard.get_for_display (display, Gdk.SELECTION_CLIPBOARD);
- var image = get_image (true);
+ var image = get_image ();
clipboard.set_image (image);
}
public void save_png (File file) throws Error
{
var stream = file.replace (null, false, FileCreateFlags.NONE, null);
- var image = get_image (true);
+ var image = get_image ();
string? icc_profile_data = null;
if (color_profile != null)
diff --git a/src/thumbnail-view.vala b/src/thumbnail-view.vala
new file mode 100644
index 0000000..d6eadcd
--- /dev/null
+++ b/src/thumbnail-view.vala
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2018 Canonical Ltd.
+ * Author: Robert Ancell <robert ancell canonical 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.
+ */
+
+public class ThumbnailView : Gtk.DrawingArea
+{
+ /* Page being rendered */
+ private Page page_ = null;
+ public Page page {
+ get {
+ return page_;
+ }
+ set {
+ if (page_ != null) {
+ page_.pixels_changed.disconnect (page_changed_cb);
+ page_.size_changed.disconnect (page_changed_cb);
+ page_.scan_line_changed.disconnect (page_changed_cb);
+ page_.scan_direction_changed.disconnect (page_changed_cb);
+ }
+ page_ = value;
+ page_.pixels_changed.connect (page_changed_cb);
+ page_.size_changed.connect (page_changed_cb);
+ page_.scan_line_changed.connect (page_changed_cb);
+ page_.scan_direction_changed.connect (page_changed_cb);
+ }
+ }
+
+ /* Border around image */
+ private bool selected_ = false;
+ public bool selected
+ {
+ get { return selected_; }
+ set
+ {
+ if (selected_ == value)
+ return;
+ selected_ = value;
+ queue_draw ();
+ }
+ }
+
+ private int selected_border_width = 4;
+ private int unselected_border_width = 1;
+
+ public signal void clicked ();
+
+ public ThumbnailView ()
+ {
+ add_events (Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK);
+ }
+
+ private void get_page_size (out int width, out int height)
+ {
+ var w = get_allocated_width () - selected_border_width * 2;
+ var h = get_allocated_height () - selected_border_width * 2;
+
+ /* Fit page inside available space */
+ if (w * page.height > h * page.width)
+ w = h * page.width / page.height;
+ else
+ h = w * page.height / page.width;
+
+ width = w;
+ height = h;
+ }
+
+ public override void get_preferred_height_for_width (int width, out int minimum_height, out int
natural_height)
+ {
+ if (page == null) {
+ minimum_height = natural_height = 1 + selected_border_width * 2;
+ return;
+ }
+ minimum_height = 1 + selected_border_width * 2;
+ natural_height = int.max (width - selected_border_width * 2, 0) * page.height / page.width +
selected_border_width * 2;
+ }
+
+ public override Gtk.SizeRequestMode get_request_mode ()
+ {
+ return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
+ }
+
+ public override bool draw (Cairo.Context context)
+ {
+ int w, h;
+ get_page_size (out w, out h);
+
+ var x_offset = (get_allocated_width () - w - selected_border_width * 2) / 2;
+ var y_offset = (get_allocated_height () - h - selected_border_width * 2) / 2;
+
+ /* Draw page border */
+ context.set_source_rgba (0.0, 0.0, 0.0, 0.25);
+ var border_delta = selected_border_width - unselected_border_width;
+ if (selected)
+ {
+ context.arc (x_offset + selected_border_width, y_offset + selected_border_width,
+ selected_border_width, -Math.PI, -Math.PI / 2);
+ context.arc (x_offset + w + selected_border_width, y_offset + selected_border_width,
+ selected_border_width, -Math.PI / 2, 0);
+ context.arc (x_offset + w + selected_border_width, y_offset + h + selected_border_width,
+ selected_border_width, 0, Math.PI / 2);
+ context.arc (x_offset + selected_border_width, y_offset + h + selected_border_width,
+ selected_border_width, Math.PI / 2, Math.PI);
+ context.close_path ();
+ }
+ else
+ context.rectangle (x_offset + border_delta, y_offset + border_delta,
+ w + unselected_border_width * 2,
+ h + unselected_border_width * 2);
+ context.set_line_width (selected_border_width);
+ context.fill ();
+
+ /* Draw page data */
+ var image = page.get_image (); // FIXME: Cache and mipmap
+ context.translate (x_offset + selected_border_width, y_offset + selected_border_width);
+ context.scale ((double) w / image.width, (double) h / image.height);
+ Gdk.cairo_set_source_pixbuf (context, image, 0, 0);
+ context.paint ();
+
+ return true;
+ }
+
+ public override bool button_release_event (Gdk.EventButton event)
+ {
+ if (event.button == 1) {
+ clicked ();
+ return true;
+ }
+
+ return false;
+ }
+
+ private void page_changed_cb (Page p)
+ {
+ queue_draw ();
+ }
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]