Commit 176c2ef7 authored by Jim Nelson's avatar Jim Nelson

#439: Resorting photos with large collection does not take so much time. This...

#439: Resorting photos with large collection does not take so much time.  This one has been bugging me for a 
long time.  I replaced SortedList's inane insertion sort with a less-inane binary insertion sort.  Photo now 
caches exposure_time to speed up comparisons, as going out to the database every time was too expensive.  Both 
changes result in a marked improvement for the user.  Also made Sort By Exposure Time / Descending the default, 
as that's more interesting for the user.  Also spotted a subtle bug in fullscreen window; fixed.
parent 4e5ac0b8
......@@ -14,7 +14,7 @@ public abstract class BatchImportJob {
// it into the system, including database additions, thumbnail creation, and reporting it to AppWindow
// so it's properly added to various views and events.
public class BatchImport {
public static const int IMPORT_DIRECTORY_DEPTH = 3;
public const int IMPORT_DIRECTORY_DEPTH = 3;
private class DateComparator : Comparator<Photo> {
public override int64 compare(Photo photo_a, Photo photo_b) {
......@@ -179,7 +179,7 @@ public class BatchImport {
private bool perform_import() {
starting();
success = new SortedList<Photo>(new Gee.ArrayList<Photo>(), new DateComparator());
success = new SortedList<Photo>(new DateComparator());
failed = new Gee.ArrayList<string>();
skipped = new Gee.ArrayList<string>();
import_id = (new PhotoTable()).generate_import_id();
......
......@@ -5,14 +5,14 @@
*/
public abstract class LayoutItem : Gtk.Alignment {
public static const int LABEL_PADDING = 4;
public static const int FRAME_PADDING = 4;
public const int LABEL_PADDING = 4;
public const int FRAME_PADDING = 4;
public static const string TEXT_COLOR = "#FFF";
public static const string SELECTED_COLOR = "#0FF";
public static const string UNSELECTED_COLOR = "#FFF";
public const string TEXT_COLOR = "#FFF";
public const string SELECTED_COLOR = "#0FF";
public const string UNSELECTED_COLOR = "#FFF";
public static const int BRIGHTEN_SHIFT = 0x18;
public const int BRIGHTEN_SHIFT = 0x18;
// Due to the potential for thousands or tens of thousands of thumbnails being present in the
// system, all widgets used here and by subclasses should be NOWINDOW widgets.
......@@ -172,16 +172,16 @@ public abstract class LayoutItem : Gtk.Alignment {
}
public class CollectionLayout : Gtk.Layout {
public static const int TOP_PADDING = 16;
public static const int BOTTOM_PADDING = 16;
public static const int ROW_GUTTER_PADDING = 24;
public const int TOP_PADDING = 16;
public const int BOTTOM_PADDING = 16;
public const int ROW_GUTTER_PADDING = 24;
// the following are minimums, as the pads and gutters expand to fill up the window width
public static const int LEFT_PADDING = 16;
public static const int RIGHT_PADDING = 16;
public static const int COLUMN_GUTTER_PADDING = 24;
public const int LEFT_PADDING = 16;
public const int RIGHT_PADDING = 16;
public const int COLUMN_GUTTER_PADDING = 24;
public SortedList<LayoutItem> items = new SortedList<LayoutItem>(new Gee.ArrayList<LayoutItem>());
public SortedList<LayoutItem> items = new SortedList<LayoutItem>();
private Gtk.Label message = new Gtk.Label("");
private bool in_view = false;
......@@ -227,16 +227,7 @@ public class CollectionLayout : Gtk.Layout {
}
public void set_comparator(Comparator<LayoutItem> cmp) {
// re-sort list with new comparator
SortedList<LayoutItem> resorted = new SortedList<LayoutItem>(new Gee.ArrayList<LayoutItem>(), cmp);
foreach (LayoutItem item in items) {
// add to new list and remove from Gtk.Layout
resorted.add(item);
remove(item);
}
items = resorted;
items.resort(cmp);
}
public void add_item(LayoutItem item) {
......
......@@ -5,9 +5,9 @@
*/
class SlideshowPage : SinglePhotoPage {
public static const int DELAY_SEC = 3;
public const int DELAY_SEC = 3;
private static const int CHECK_ADVANCE_MSEC = 250;
private const int CHECK_ADVANCE_MSEC = 250;
private CheckerboardPage controller;
private Thumbnail thumbnail;
......@@ -141,22 +141,25 @@ class SlideshowPage : SinglePhotoPage {
}
public class CollectionPage : CheckerboardPage {
public static const int SORT_BY_MIN = 0;
public static const int SORT_BY_NAME = 0;
public static const int SORT_BY_EXPOSURE_DATE = 1;
public static const int SORT_BY_MAX = 1;
public static const int SORT_ORDER_MIN = 0;
public static const int SORT_ORDER_ASCENDING = 0;
public static const int SORT_ORDER_DESCENDING = 1;
public static const int SORT_ORDER_MAX = 1;
public const int SORT_BY_MIN = 0;
public const int SORT_BY_NAME = 0;
public const int SORT_BY_EXPOSURE_DATE = 1;
public const int SORT_BY_MAX = 1;
public const int SORT_ORDER_MIN = 0;
public const int SORT_ORDER_ASCENDING = 0;
public const int SORT_ORDER_DESCENDING = 1;
public const int SORT_ORDER_MAX = 1;
public const int DEFAULT_SORT_BY = SORT_BY_EXPOSURE_DATE;
public const int DEFAULT_SORT_ORDER = SORT_ORDER_DESCENDING;
// steppings should divide evenly into (Thumbnail.MAX_SCALE - Thumbnail.MIN_SCALE)
public static const int MANUAL_STEPPING = 16;
public static const int SLIDER_STEPPING = 2;
public const int MANUAL_STEPPING = 16;
public const int SLIDER_STEPPING = 2;
private static const int IMPROVAL_PRIORITY = Priority.LOW;
private static const int IMPROVAL_DELAY_MS = 250;
private const int IMPROVAL_PRIORITY = Priority.LOW;
private const int IMPROVAL_DELAY_MS = 250;
private class CompareName : Comparator<LayoutItem> {
public override int64 compare(LayoutItem a, LayoutItem b) {
......@@ -248,8 +251,8 @@ public class CollectionPage : CheckerboardPage {
base(page_name != null ? page_name : "Photos");
init_ui_start("collection.ui", "CollectionActionGroup", ACTIONS, TOGGLE_ACTIONS);
action_group.add_radio_actions(SORT_CRIT_ACTIONS, SORT_BY_NAME, on_sort_changed);
action_group.add_radio_actions(SORT_ORDER_ACTIONS, SORT_ORDER_ASCENDING, on_sort_changed);
action_group.add_radio_actions(SORT_CRIT_ACTIONS, DEFAULT_SORT_BY, on_sort_changed);
action_group.add_radio_actions(SORT_ORDER_ACTIONS, DEFAULT_SORT_ORDER, on_sort_changed);
if (ui_filename != null)
init_load_ui(ui_filename);
......@@ -260,7 +263,7 @@ public class CollectionPage : CheckerboardPage {
init_ui_bind("/CollectionMenuBar");
init_context_menu("/CollectionContextMenu");
set_layout_comparator(new CompareName());
set_layout_comparator(get_sort_comparator());
// adjustment which is shared by all sliders in the application
if (slider_adjustment == null)
......@@ -860,33 +863,34 @@ public class CollectionPage : CheckerboardPage {
return value;
}
private bool is_sort_ascending() {
return get_sort_order() == SORT_ORDER_ASCENDING;
}
private void on_sort_changed() {
Comparator<LayoutItem> cmp = null;
set_layout_comparator(get_sort_comparator());
refresh();
}
private Comparator<LayoutItem> get_sort_comparator() {
switch (get_sort_criteria()) {
case SORT_BY_NAME:
if (get_sort_order() == SORT_ORDER_ASCENDING)
cmp = new CompareName();
if (is_sort_ascending())
return new CompareName();
else
cmp = new ReverseCompareName();
break;
return new ReverseCompareName();
case SORT_BY_EXPOSURE_DATE:
if (get_sort_order() == SORT_ORDER_ASCENDING)
cmp = new CompareDate();
if (is_sort_ascending())
return new CompareDate();
else
cmp = new ReverseCompareDate();
break;
return new ReverseCompareDate();
default:
error("Unknown sort criteria: %d", get_sort_criteria());
break;
return new CompareName();
}
if (cmp == null)
return;
set_layout_comparator(cmp);
refresh();
}
}
......@@ -5,8 +5,8 @@
*/
class ImportPreview : LayoutItem {
public static const int MAX_SCALE = 128;
public static const Gdk.InterpType INTERP = Gdk.InterpType.BILINEAR;
public const int MAX_SCALE = 128;
public const Gdk.InterpType INTERP = Gdk.InterpType.BILINEAR;
public int fsid;
public string folder;
......@@ -592,8 +592,7 @@ public class ImportPage : CheckerboardPage {
int failed = 0;
ulong total_bytes = 0;
SortedList<CameraImportJob> jobs = new SortedList<CameraImportJob>(
new Gee.ArrayList<CameraImportJob>(), new CameraImportComparator());
SortedList<CameraImportJob> jobs = new SortedList<CameraImportJob>(new CameraImportComparator());
foreach (LayoutItem item in items) {
ImportPreview preview = (ImportPreview) item;
......
......@@ -37,6 +37,10 @@ public class Photo : Object {
private PhotoID photo_id;
// because fetching some items from the database is high-overhead, certain items are cached
// here ... really want to be frugal about this, as maintaining coherency is complicated enough
private time_t exposure_time = -1;
public static void init() {
photo_map = new Gee.HashMap<int64?, Photo>(int64_hash, int64_equal, direct_equal);
photo_table = new PhotoTable();
......@@ -182,7 +186,10 @@ public class Photo : Object {
}
public time_t get_exposure_time() {
return photo_table.get_exposure_time(photo_id);
if (exposure_time == -1)
exposure_time = photo_table.get_exposure_time(photo_id);
return exposure_time;
}
public time_t get_timestamp() {
......@@ -803,6 +810,9 @@ public class Photo : Object {
if (photo_table.update(photo_id, dim, info.get_size(), timestamp.tv_sec, exposure_time,
orientation)) {
// cache coherency
this.exposure_time = exposure_time;
photo_altered();
}
}
......
......@@ -262,7 +262,10 @@ public class PhotoPage : SinglePhotoPage {
// Return true to block the DnD handler from activating a drag
private override bool on_left_click(Gdk.EventButton event) {
if (event.type == Gdk.EventType.2BUTTON_PRESS && current_tool == null) {
// on double-click, if not editing and not hosted by a fullscreen window, return to the
// controller collection
if (event.type == Gdk.EventType.2BUTTON_PRESS && current_tool == null
&& !(container is FullscreenWindow)) {
on_return_to_collection();
return true;
......
......@@ -12,8 +12,8 @@ public class SortedList<G> : Object, Gee.Iterable<G> {
private Gee.List<G> list;
private Comparator<G> cmp;
public SortedList(Gee.List<G> list, Comparator<G>? cmp = null) {
this.list = list;
public SortedList(Comparator<G>? cmp = null) {
this.list = new Gee.ArrayList<G>();
this.cmp = cmp;
}
......@@ -26,33 +26,10 @@ public class SortedList<G> : Object, Gee.Iterable<G> {
}
public bool add(G? item) {
if (cmp == null) {
list.insert(list.size, item);
return true;
}
int ctr = 0;
bool insert = false;
foreach (G added in list) {
if (cmp.compare(item, added) < 0) {
// smaller, insert before this element
insert = true;
break;
}
ctr++;
}
if (insert) {
list.insert(ctr, item);
return true;
}
// went off the end of the list, so add at end
list.insert(list.size, item);
if (cmp == null)
list.add(item);
else
list.insert(get_sorted_insert_pos(item), item);
return true;
}
......@@ -92,6 +69,35 @@ public class SortedList<G> : Object, Gee.Iterable<G> {
public void remove_at(int index) {
list.remove_at(index);
}
}
public void resort(Comparator<G> new_cmp) {
cmp = new_cmp;
Gee.List<G> old_list = list;
list = new Gee.ArrayList<G>();
foreach (G item in old_list)
list.insert(get_sorted_insert_pos(item), item);
}
private int get_sorted_insert_pos(G? item) {
int low = 0;
int high = list.size;
for (;;) {
if (low == high)
return low;
int mid = low + ((high - low) / 2);
int64 result = cmp.compare(item, list.get(mid));
if (result < 0)
high = mid;
else if (result > 0)
low = mid + 1;
else
return mid;
}
}
}
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