Commit c73e07c2 authored by Jim Nelson's avatar Jim Nelson

Widgetless thumbnail implementation. This commit assists with #136. Also...

Widgetless thumbnail implementation.  This commit assists with #136.  Also added aggregate debug logging to 
ThumbnailCache, as its log messages interfered with performance testing.  CameraTable initializes after a delay 
to shave even more time off the app startup time.
parent 3fa29f91
......@@ -49,14 +49,21 @@ public class CameraTable {
if (!hal_context.set_device_removed(on_device_removed))
error("Unable to register device-removed callback");
// initialize and update camera table ... since this is the constructor, no observers
// are signalled
// because loading the camera abilities list takes a bit of time and slows down app
// startup, delay loading it (and notifying any observers) for a small period of time,
// after the dust has settled
Timeout.add(500, delayed_init);
}
private bool delayed_init() {
try {
init_camera_table();
update_camera_table();
} catch (GPhotoError err) {
error("%s", err.message);
}
return false;
}
public static CameraTable get_instance() {
......
This diff is collapsed.
......@@ -399,6 +399,9 @@ public class CollectionPage : CheckerboardPage {
toolbar.insert(toolitem, -1);
// initialize scale from slider (since the scale adjustment may be modified from default)
scale = slider_to_scale(slider.get_value());
// scrollbar policy
set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
......@@ -407,11 +410,6 @@ public class CollectionPage : CheckerboardPage {
get_hadjustment().value_changed += schedule_thumbnail_improval;
get_vadjustment().value_changed += schedule_thumbnail_improval;
// turn this off until we're switched to
set_refresh_on_resize(false);
refresh();
show_all();
schedule_thumbnail_improval();
......@@ -423,17 +421,9 @@ public class CollectionPage : CheckerboardPage {
return toolbar;
}
public override void switching_from() {
base.switching_from();
set_refresh_on_resize(false);
}
public override void switched_to() {
base.switched_to();
set_refresh_on_resize(true);
// if the thumbnails were resized while viewing another page, resize the ones on this page
// now ... set_thumb_size does the refresh and thumbnail improval, so don't schedule if
// going this route
......@@ -678,7 +668,6 @@ public class CollectionPage : CheckerboardPage {
private bool improve_thumbnail_quality() {
if (reschedule_improval) {
debug("rescheduled improval");
reschedule_improval = false;
return true;
......
......@@ -404,13 +404,6 @@ public abstract class Page : Gtk.ScrolledWindow, SidebarPage {
Gdk.ModifierType mask;
if (event.is_hint) {
event_source.window.get_pointer(out x, out y, out mask);
Gtk.Adjustment hadj = get_hadjustment();
Gtk.Adjustment vadj = get_vadjustment();
// adjust x and y to viewport values
x = (x + (int) hadj.get_value()).clamp((int) hadj.get_lower(), (int) hadj.get_upper());
y = (y + (int) vadj.get_value()).clamp((int) vadj.get_lower(), (int) vadj.get_upper());
} else {
x = (int) event.x;
y = (int) event.y;
......@@ -437,6 +430,7 @@ public abstract class CheckerboardPage : Page {
private Gtk.Menu context_menu = null;
private CollectionLayout layout = new CollectionLayout();
private Gtk.Viewport viewport = new Gtk.Viewport(null, null);
private Gee.HashSet<LayoutItem> selected_items = new Gee.HashSet<LayoutItem>();
private LayoutItem last_clicked_item = null;
private LayoutItem highlighted = null;
......@@ -456,7 +450,37 @@ public abstract class CheckerboardPage : Page {
layout.expose_after += on_layout_exposed;
layout.map += on_layout_mapped;
add(layout);
get_hadjustment().value_changed += on_viewport_shifted;
get_vadjustment().value_changed += on_viewport_shifted;
viewport.size_allocate += on_viewport_resized;
viewport.set_border_width(0);
viewport.set_shadow_type(Gtk.ShadowType.NONE);
viewport.add(layout);
add(viewport);
}
private void on_viewport_resized() {
Gtk.Requisition layout_size;
layout.size_request(out layout_size);
if (!layout.in_message_mode()) {
// set the layout's new size to be the same as the viewport's width but maintain
// it's own height
layout.set_size_request(viewport.allocation.width, layout_size.height);
} else {
// set the layout's width and height to always match the viewport's
layout.set_size_request(viewport.allocation.width, viewport.allocation.height);
}
// report size change
layout.set_visible_page(get_adjustment_page(get_hadjustment(), get_vadjustment()));
}
private void on_viewport_shifted() {
layout.set_visible_page(get_adjustment_page(get_hadjustment(), get_vadjustment()));
}
public void init_context_menu(string path) {
......@@ -482,6 +506,7 @@ public abstract class CheckerboardPage : Page {
public override void switched_to() {
layout.set_in_view(true);
layout.set_visible_page(get_adjustment_page(get_hadjustment(), get_vadjustment()));
base.switched_to();
}
......@@ -497,10 +522,9 @@ public abstract class CheckerboardPage : Page {
public void set_page_message(string message) {
layout.set_message(message);
}
public void set_refresh_on_resize(bool refresh_on_resize) {
layout.set_refresh_on_resize(refresh_on_resize);
// set the layout's size to be exactly the same as the viewport's
layout.set_size_request(viewport.allocation.width, viewport.allocation.height);
}
public void set_layout_comparator(Comparator<LayoutItem> cmp) {
......@@ -825,7 +849,7 @@ public abstract class CheckerboardPage : Page {
selection_band.height = 0;
// force a repaint to remove the selection band
layout.bin_window.invalidate_rect(null, false);
layout.window.invalidate_rect(null, false);
return true;
}
......@@ -937,9 +961,11 @@ public abstract class CheckerboardPage : Page {
if (!drag_select)
return false;
// bound the drag rectangle to left-top edges (so Box doesn't complain) ... expose code
// takes care of other boundaries
Gdk.Point drag_end = Gdk.Point();
drag_end.x = x;
drag_end.y = y;
drag_end.x = x.clamp(0, int.MAX);
drag_end.y = y.clamp(0, int.MAX);
// save new drag rectangle
selection_band = Box.from_points(drag_start, drag_end).get_rectangle();
......@@ -977,7 +1003,7 @@ public abstract class CheckerboardPage : Page {
select(item);
// for a refresh to paint the selection band
layout.bin_window.invalidate_rect(null, false);
layout.window.invalidate_rect(null, false);
}
private bool selection_autoscroll() {
......@@ -992,13 +1018,14 @@ public abstract class CheckerboardPage : Page {
int x, y;
Gdk.ModifierType mask;
layout.bin_window.get_pointer(out x, out y, out mask);
layout.window.get_pointer(out x, out y, out mask);
int new_value = (int) vadj.get_value();
switch (get_adjustment_relation(vadj, y)) {
case AdjustmentRelation.BELOW:
new_value -= AUTOSCROLL_PIXELS;
selection_band.y -= AUTOSCROLL_PIXELS;
selection_band.height += AUTOSCROLL_PIXELS;
if (selection_band.y < (int) vadj.get_lower())
selection_band.y = (int) vadj.get_lower();
break;
......@@ -1029,7 +1056,7 @@ public abstract class CheckerboardPage : Page {
private void on_layout_mapped() {
// set up selection colors
Gdk.Color selection_color = fetch_color(LayoutItem.SELECTED_COLOR, layout.bin_window);
Gdk.Color selection_color = fetch_color(LayoutItem.SELECTED_COLOR, layout.window);
selection_transparency_color = convert_rgba(selection_color, 0x40);
// set up GC's for painting selection
......@@ -1045,7 +1072,7 @@ public abstract class CheckerboardPage : Page {
| Gdk.GCValuesMask.FILL
| Gdk.GCValuesMask.LINE_WIDTH;
selection_gc = new Gdk.GC.with_values(layout.bin_window, gc_values, mask);
selection_gc = new Gdk.GC.with_values(layout.window, gc_values, mask);
}
private void on_layout_exposed() {
......@@ -1084,12 +1111,12 @@ public abstract class CheckerboardPage : Page {
selection_interior.fill(selection_transparency_color);
}
layout.bin_window.draw_pixbuf(selection_gc, selection_interior, 0, 0, visible_x, visible_y,
layout.window.draw_pixbuf(selection_gc, selection_interior, 0, 0, visible_x, visible_y,
visible_width, visible_height, Gdk.RgbDither.NORMAL, 0, 0);
}
// border
Gdk.draw_rectangle(layout.bin_window, selection_gc, false, selection_band.x, selection_band.y,
Gdk.draw_rectangle(layout.window, selection_gc, false, selection_band.x, selection_band.y,
selection_band.width - 1, selection_band.height - 1);
}
......
......@@ -31,7 +31,7 @@ public class Thumbnail : LayoutItem, PhotoSource {
// not present, the widget will collapse, and so the layout manager won't account for it
// properly when it's off the viewport. The solution is to manually set the widget's
// requisition size, even when it contains no pixbuf
set_image_size(dim.width, dim.height);
clear_image(dim.width, dim.height);
photo.thumbnail_altered += on_thumbnail_altered;
}
......@@ -52,9 +52,9 @@ public class Thumbnail : LayoutItem, PhotoSource {
interp = LOW_QUALITY_INTERP;
set_image(pixbuf);
} else {
clear_image(dim.width, dim.height);
}
set_image_size(dim.width, dim.height);
}
public void resize(int new_scale) {
......@@ -100,7 +100,6 @@ public class Thumbnail : LayoutItem, PhotoSource {
interp = LOW_QUALITY_INTERP;
set_image(pixbuf);
set_image_size(dim.width, dim.height);
thumb_exposed = true;
}
......@@ -109,8 +108,7 @@ public class Thumbnail : LayoutItem, PhotoSource {
if (!thumb_exposed)
return;
clear_image();
set_image_size(dim.width, dim.height);
clear_image(dim.width, dim.height);
thumb_exposed = false;
}
......
......@@ -26,6 +26,35 @@ public class ThumbnailCache : Object {
private static ThumbnailCache medium = null;
private static ThumbnailCache small = null;
private static int cycle_fetched_thumbnails = 0;
private static int cycle_overflow_thumbnails = 0;
private static bool debug_scheduled = false;
private File cache_dir;
private int scale;
private ulong max_cached_bytes;
private Gdk.InterpType interp;
private string jpeg_quality;
private Gee.HashMap<int64?, ImageData> cache_map = new Gee.HashMap<int64?, ImageData>(
int64_hash, int64_equal, direct_equal);
private Gee.ArrayList<int64?> cache_lru = new Gee.ArrayList<int64?>(int64_equal);
private ulong cached_bytes = 0;
private ThumbnailCacheTable cache_table;
private PhotoTable photo_table = new PhotoTable();
private ThumbnailCache(int scale, ulong max_cached_bytes, Gdk.InterpType interp = DEFAULT_INTERP,
int jpeg_quality = DEFAULT_JPEG_QUALITY) {
assert(scale != 0);
assert((jpeg_quality >= 0) && (jpeg_quality <= 100));
this.cache_dir = AppWindow.get_data_subdir("thumbs", "thumbs%d".printf(scale));
this.scale = scale;
this.max_cached_bytes = max_cached_bytes;
this.interp = interp;
this.jpeg_quality = "%d".printf(jpeg_quality);
this.cache_table = new ThumbnailCacheTable(scale);
}
// Doing this because static construct {} not working nor new'ing in the above statement
public static void init() {
big = new ThumbnailCache(BIG_SCALE, MAX_BIG_CACHED_BYTES);
......@@ -108,29 +137,31 @@ public class ThumbnailCache : Object {
}
}
private File cache_dir;
private int scale;
private ulong max_cached_bytes;
private Gdk.InterpType interp;
private string jpeg_quality;
private Gee.HashMap<int64?, ImageData> cache_map = new Gee.HashMap<int64?, ImageData>(
int64_hash, int64_equal, direct_equal);
private Gee.ArrayList<int64?> cache_lru = new Gee.ArrayList<int64?>(int64_equal);
private ulong cached_bytes = 0;
private ThumbnailCacheTable cache_table;
private PhotoTable photo_table = new PhotoTable();
private ThumbnailCache(int scale, ulong max_cached_bytes, Gdk.InterpType interp = DEFAULT_INTERP,
int jpeg_quality = DEFAULT_JPEG_QUALITY) {
assert(scale != 0);
assert((jpeg_quality >= 0) && (jpeg_quality <= 100));
// Displaying a debug message for each thumbnail loaded and dropped can cause a ton of messages
// and slow down scrolling operations ... this delays reporting them, and only then reporting
// them in one aggregate sum
private static void schedule_debug() {
if (debug_scheduled)
return;
this.cache_dir = AppWindow.get_data_subdir("thumbs", "thumbs%d".printf(scale));
this.scale = scale;
this.max_cached_bytes = max_cached_bytes;
this.interp = interp;
this.jpeg_quality = "%d".printf(jpeg_quality);
this.cache_table = new ThumbnailCacheTable(scale);
Timeout.add_full(Priority.LOW, 500, report_cycle);
debug_scheduled = true;
}
private static bool report_cycle() {
if (cycle_fetched_thumbnails > 0) {
debug("%d thumbnails fetched into memory", cycle_fetched_thumbnails);
cycle_fetched_thumbnails = 0;
}
if (cycle_overflow_thumbnails > 0) {
debug("%d thumbnails overflowed from memory cache", cycle_overflow_thumbnails);
cycle_overflow_thumbnails = 0;
}
debug_scheduled = false;
return false;
}
private Gdk.Pixbuf? _fetch(PhotoID photo_id) {
......@@ -141,9 +172,6 @@ public class ThumbnailCache : Object {
File file = get_cached_file(photo_id);
debug("Fetching thumbnail for %s from disk [%lld] %s", photo_table.get_name(photo_id),
photo_id.id, file.get_path());
Gdk.Pixbuf pixbuf = null;
try {
pixbuf = new Gdk.Pixbuf.from_file(file.get_path());
......@@ -151,6 +179,9 @@ public class ThumbnailCache : Object {
error("%s", err.message);
}
cycle_fetched_thumbnails++;
schedule_debug();
int filesize = cache_table.get_filesize(photo_id);
if(filesize > MAX_INMEMORY_DATA_SIZE) {
// too big to store in memory, so return the pixbuf straight from disk
......@@ -284,8 +315,8 @@ public class ThumbnailCache : Object {
assert(data.bytes <= cached_bytes);
cached_bytes -= data.bytes;
debug("Thumbnail cache %d overflow: removed %lu bytes, now %lu", scale, data.bytes,
cached_bytes);
cycle_overflow_thumbnails++;
schedule_debug();
bool removed = cache_map.remove(id);
assert(removed);
......
......@@ -174,6 +174,25 @@ public AdjustmentRelation get_adjustment_relation(Gtk.Adjustment adjustment, int
return AdjustmentRelation.IN_RANGE;
}
public Gdk.Rectangle get_adjustment_page(Gtk.Adjustment hadj, Gtk.Adjustment vadj) {
Gdk.Rectangle rect = Gdk.Rectangle();
rect.x = (int) hadj.get_value();
rect.y = (int) vadj.get_value();
rect.width = (int) hadj.get_page_size();
rect.height = (int) vadj.get_page_size();
return rect;
}
public bool rectangles_equal(Gdk.Rectangle a, Gdk.Rectangle b) {
return (a.x == b.x) && (a.y == b.y) && (a.width == b.width) && (a.height == b.height);
}
public string rectangle_to_string(Gdk.Rectangle rect) {
return "%dx%d %d,%d".printf(rect.x, rect.y, rect.width, rect.height);
}
public enum CompassPoint {
NORTH,
SOUTH,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment