Commit d5fffeeb authored by Jim Nelson's avatar Jim Nelson

More optimizations for #1118.

parent 84ef969a
......@@ -65,7 +65,7 @@ public abstract class LayoutItem : ThumbnailView {
set_title(markup, true);
}
public string get_title() {
public unowned string get_title() {
return (title != null) ? title : "";
}
......
......@@ -31,33 +31,6 @@ public abstract class CollectionPage : CheckerboardPage {
EXPOSURE_DATE = 2;
}
private class CompareTitle : Comparator<Thumbnail> {
private bool ascending;
public CompareTitle(bool ascending) {
this.ascending = ascending;
}
public override int64 compare(Thumbnail a, Thumbnail b) {
return (ascending) ? strcmp(a.get_title(), b.get_title()) : strcmp(b.get_title(), a.get_title());
}
}
private class CompareDate : Comparator<Thumbnail> {
private bool ascending;
public CompareDate(bool ascending) {
this.ascending = ascending;
}
public override int64 compare(Thumbnail a, Thumbnail b) {
time_t timea = a.get_photo().get_exposure_time();
time_t timeb = b.get_photo().get_exposure_time();
return (ascending) ? timea - timeb : timeb - timea;
}
}
private static Gtk.Adjustment slider_adjustment = null;
private Gtk.HScale slider = null;
......@@ -401,7 +374,7 @@ public abstract class CollectionPage : CheckerboardPage {
return sort_order_actions;
}
// This method is called by CollectionViewManager to create thumbnails for the DataSource
// (Photo) objects.
public virtual Thumbnail create_thumbnail(LibraryPhoto photo) {
......@@ -1147,18 +1120,24 @@ public abstract class CollectionPage : CheckerboardPage {
set_config_photos_sort(get_sort_order() == SORT_ORDER_ASCENDING, get_sort_criteria());
}
private Comparator<LayoutItem> get_sort_comparator() {
private Comparator get_sort_comparator() {
switch (get_sort_criteria()) {
case SortBy.TITLE:
return new CompareTitle(is_sort_ascending());
if (is_sort_ascending())
return Thumbnail.title_ascending_comparator;
else
return Thumbnail.title_descending_comparator;
case SortBy.EXPOSURE_DATE:
return new CompareDate(is_sort_ascending());
if (is_sort_ascending())
return Thumbnail.exposure_time_ascending_comparator;
else
return Thumbnail.exposure_time_desending_comparator;
default:
error("Unknown sort criteria: %d", get_sort_criteria());
return new CompareTitle(true);
return Thumbnail.title_ascending_comparator;
}
}
......
......@@ -23,42 +23,33 @@
//
public class DataSet {
private static class OrderAddedComparator : Comparator<DataObject> {
public override int64 compare(DataObject a, DataObject b) {
return a.internal_get_ordinal() - b.internal_get_ordinal();
}
}
private static class ComparatorWrapper : Comparator<DataObject> {
private Comparator<DataObject> comparator;
public ComparatorWrapper(Comparator<DataObject> comparator) {
this.comparator = comparator;
}
public override int64 compare(DataObject a, DataObject b) {
if (a == b)
return 0;
int64 result = comparator.compare(a, b);
if (result == 0)
result = a.internal_get_ordinal() - b.internal_get_ordinal();
assert(result != 0);
return result;
}
}
private static OrderAddedComparator order_added_comparator = null;
private SortedList<DataObject> list = new SortedList<DataObject>();
private Gee.HashSet<DataObject> hash_set = new Gee.HashSet<DataObject>();
private Comparator user_comparator = null;
public DataSet() {
reset_comparator();
}
private int64 order_added_comparator(void *a, void *b) {
return ((DataObject *) a)->internal_get_ordinal() - ((DataObject *) b)->internal_get_ordinal();
}
private int64 comparator_wrapper(void *a, void *b) {
if (a == b)
return 0;
// use the order-added comparator if the user's compare returns equal, to stabilize the
// sort
int64 result = user_comparator(a, b);
if (result == 0)
result = order_added_comparator(a, b);
assert(result != 0);
return result;
}
public bool contains(DataObject object) {
return hash_set.contains(object);
}
......@@ -70,14 +61,13 @@ public class DataSet {
}
public void reset_comparator() {
if (order_added_comparator == null)
order_added_comparator = new OrderAddedComparator();
user_comparator = null;
list.resort(order_added_comparator);
}
public void set_comparator(Comparator<DataObject> comparator) {
list.resort(new ComparatorWrapper(comparator));
public void set_comparator(Comparator user_comparator) {
this.user_comparator = user_comparator;
list.resort(comparator_wrapper);
}
public Gee.Iterable<DataObject> get_all() {
......@@ -383,7 +373,7 @@ public class DataCollection {
return true;
}
public virtual void set_comparator(Comparator<DataObject> comparator) {
public virtual void set_comparator(Comparator comparator) {
dataset.set_comparator(comparator);
notify_ordering_changed();
}
......@@ -442,8 +432,8 @@ public class DataCollection {
int count = objects.size;
for (int ctr = 0; ctr < count; ctr++) {
DataObject object = objects.get(ctr);
assert(valid_type(object));
object.internal_set_membership(this, object_ordinal_generator++);
if (monitor != null)
......@@ -479,8 +469,7 @@ public class DataCollection {
return true;
}
// Returns number of items added to collection. The ProgressMonitor total is reported as
// zero by this method, as the total count is not known.
// Returns number of items added to collection.
public int add_many(Gee.Iterable<DataObject> objects, ProgressMonitor? monitor = null) {
Gee.ArrayList<DataObject> added = new Gee.ArrayList<DataObject>();
foreach (DataObject object in objects) {
......@@ -493,6 +482,9 @@ public class DataCollection {
added.add(object);
}
if (added.size == 0)
return 0;
internal_add_many(added, monitor);
// signal once all have been added
......@@ -974,17 +966,8 @@ public class ViewCollection : DataCollection {
created_views.add(manager.create_view(source));
}
if (created_views.size > 0) {
// add_many() doesn't report totals, and need to hold ref to real_monitor until
// completed
UnknownTotalMonitor real_monitor = null;
if (monitor != null) {
real_monitor = new UnknownTotalMonitor(created_views.size, monitor);
monitor = real_monitor.monitor;
}
if (created_views.size > 0)
add_many(created_views, monitor);
}
}
private void on_sources_removed(Gee.Iterable<DataSource> removed) {
......
......@@ -84,6 +84,10 @@ public abstract class DataObject {
return member_of;
}
public bool has_membership() {
return member_of != null;
}
// This method is only called by DataCollection.
public void internal_set_membership(DataCollection collection, int64 ordinal) {
assert(member_of == null);
......@@ -106,7 +110,7 @@ public abstract class DataObject {
notify_membership_changed(null);
}
// This method is only called by DataCollection
// This method is only called by DataCollection and DataSet
public inline int64 internal_get_ordinal() {
assert(member_of != null);
......@@ -550,7 +554,7 @@ public class DataView : DataObject {
}
public override string to_string() {
return "%s [%s]".printf(get_name(), source.to_string());
return "DataView %s [DataSource %s]".printf(get_name(), source.to_string());
}
public DataSource get_source() {
......
......@@ -27,18 +27,6 @@ public class Event : EventSource, Proxyable {
private const time_t TIME_T_DAY = 24 * 60 * 60;
private class DateComparator : Comparator<LibraryPhoto> {
public override int64 compare(LibraryPhoto a, LibraryPhoto b) {
return a.get_exposure_time() - b.get_exposure_time();
}
}
private class ViewComparator : Comparator<PhotoView> {
public override int64 compare(PhotoView a, PhotoView b) {
return a.get_photo_source().get_exposure_time() - b.get_photo_source().get_exposure_time();
}
}
private class EventManager : ViewManager {
private EventID event_id;
......@@ -135,7 +123,7 @@ public class Event : EventSource, Proxyable {
event_photos.add(LibraryPhoto.global.fetch(photo_id));
view = new ViewCollection("ViewCollection for Event %lld".printf(event_id.id));
view.set_comparator(new ViewComparator());
view.set_comparator(view_comparator);
view.monitor_source_collection(LibraryPhoto.global, new EventManager(event_id), event_photos);
// get the primary photo for monitoring; if not available, use the first photo in the
......@@ -176,20 +164,21 @@ public class Event : EventSource, Proxyable {
foreach (EventID event_id in event_ids)
events.add(new Event(event_id));
// Use a ProgressMonitor wrapper because add_many() doesn't report totals ... need to hold
// on to a ref to real_monitor until method completes
UnknownTotalMonitor real_monitor = null;
if (monitor != null) {
real_monitor = new UnknownTotalMonitor(events.size, monitor);
monitor = real_monitor.monitor;
}
global.add_many(events, monitor);
}
public static void terminate() {
}
private static int64 source_comparator(void *a, void *b) {
return ((PhotoSource *) a)->get_exposure_time() - ((PhotoSource *) b)->get_exposure_time();
}
private static int64 view_comparator(void *a, void *b) {
return ((PhotoView *) a)->get_photo_source().get_exposure_time()
- ((PhotoView *) b)->get_photo_source().get_exposure_time();
}
private void on_photos_added() {
notify_altered();
}
......@@ -255,7 +244,7 @@ public class Event : EventSource, Proxyable {
int total = unsorted_photos.size;
// sort photos by date
SortedList<LibraryPhoto> imported_photos = new SortedList<LibraryPhoto>(new DateComparator());
SortedList<LibraryPhoto> imported_photos = new SortedList<LibraryPhoto>(source_comparator);
imported_photos.add_many(unsorted_photos);
// walk through photos, splitting into new events when the boundary hour is crossed
......
......@@ -122,21 +122,6 @@ class EventDirectoryItem : LayoutItem {
}
public class EventsDirectoryPage : CheckerboardPage {
private class CompareEventItem : Comparator<EventDirectoryItem> {
private bool ascending;
public CompareEventItem(bool ascending) {
this.ascending = ascending;
}
public override int64 compare(EventDirectoryItem a, EventDirectoryItem b) {
int64 start_a = (int64) a.event.get_start_time();
int64 start_b = (int64) b.event.get_start_time();
return (ascending) ? start_a - start_b : start_b - start_a;
}
}
public class EventDirectoryManager : ViewManager {
public override DataView create_view(DataSource source) {
return new EventDirectoryItem((Event) source);
......@@ -153,7 +138,7 @@ public class EventsDirectoryPage : CheckerboardPage {
base(page_name);
// set comparator before monitoring source collection, to prevent a re-sort
get_view().set_comparator(new CompareEventItem(Config.get_instance().get_events_sort_ascending()));
get_view().set_comparator(get_event_comparator());
get_view().monitor_source_collection(Event.global, view_manager, initial_events);
init_ui_start("events_directory.ui", "EventsDirectoryActionGroup", create_actions());
......@@ -184,7 +169,25 @@ public class EventsDirectoryPage : CheckerboardPage {
~EventsDirectoryPage() {
get_view().items_state_changed -= on_selection_changed;
}
private int64 event_ascending_comparator(void *a, void *b) {
time_t start_a = ((EventDirectoryItem *) a)->event.get_start_time();
time_t start_b = ((EventDirectoryItem *) b)->event.get_start_time();
return start_a - start_b;
}
private int64 event_descending_comparator(void *a, void *b) {
return event_ascending_comparator(b, a);
}
private Comparator get_event_comparator() {
if (Config.get_instance().get_events_sort_ascending())
return event_ascending_comparator;
else
return event_descending_comparator;
}
private void on_selection_changed() {
merge_button.sensitive = (get_view().get_selected_count() > 1);
}
......@@ -269,8 +272,8 @@ public class EventsDirectoryPage : CheckerboardPage {
return (page != null) ? page.get_fullscreen_photo() : null;
}
public void notify_sort_changed(int sort) {
get_view().set_comparator(new CompareEventItem(sort == LibraryWindow.SORT_EVENTS_ORDER_ASCENDING));
public void notify_sort_changed() {
get_view().set_comparator(get_event_comparator());
}
private void on_view_menu() {
......
......@@ -206,12 +206,6 @@ public class ImportPage : CheckerboardPage {
}
}
private class CameraImportComparator : Comparator<CameraImportJob> {
public override int64 compare(CameraImportJob a, CameraImportJob b) {
return (int64) a.get_exposure_time() - (int64) b.get_exposure_time();
}
}
public static GPhoto.ContextWrapper null_context = null;
private SourceCollection import_sources = null;
......@@ -336,7 +330,11 @@ public class ImportPage : CheckerboardPage {
~ImportPage() {
LibraryPhoto.global.contents_altered -= on_photos_added_removed;
}
private int64 import_job_comparator(void *a, void *b) {
return ((CameraImportJob *) a)->get_exposure_time() - ((CameraImportJob *) b)->get_exposure_time();
}
private Gtk.ToggleActionEntry[] create_toggle_actions() {
Gtk.ToggleActionEntry[] toggle_actions = new Gtk.ToggleActionEntry[0];
......@@ -880,7 +878,7 @@ public class ImportPage : CheckerboardPage {
progress_bar.visible = false;
uint64 total_bytes = 0;
SortedList<CameraImportJob> jobs = new SortedList<CameraImportJob>(new CameraImportComparator());
SortedList<CameraImportJob> jobs = new SortedList<CameraImportJob>(import_job_comparator);
Gee.ArrayList<CameraImportJob> already_imported = new Gee.ArrayList<CameraImportJob>();
Gee.ArrayList<CameraImportJob> failed = new Gee.ArrayList<CameraImportJob>();
......
......@@ -219,42 +219,6 @@ public class LibraryWindow : AppWindow {
}
}
private class CompareEventBranch : Comparator<SidebarPage> {
private int event_sort;
public CompareEventBranch(int event_sort) {
assert(event_sort == LibraryWindow.SORT_EVENTS_ORDER_ASCENDING || event_sort == LibraryWindow.SORT_EVENTS_ORDER_DESCENDING);
this.event_sort = event_sort;
}
public override int64 compare(SidebarPage a, SidebarPage b) {
int64 start_a, start_b;
if (a is SubEventsDirectoryPageStub && b is SubEventsDirectoryPageStub) {
start_a = (int64) ((((SubEventsDirectoryPageStub) a).get_year() * 100) +
((SubEventsDirectoryPageStub) a).get_month());
start_b = (int64) ((((SubEventsDirectoryPageStub) b).get_year() * 100) +
((SubEventsDirectoryPageStub) b).get_month());
} else {
assert(a is EventPageStub);
assert(b is EventPageStub);
start_a = (int64) ((EventPageStub) a).event.get_start_time();
start_b = (int64) ((EventPageStub) b).event.get_start_time();
}
switch (event_sort) {
case LibraryWindow.SORT_EVENTS_ORDER_ASCENDING:
return start_a - start_b;
case LibraryWindow.SORT_EVENTS_ORDER_DESCENDING:
default:
return start_b - start_a;
}
}
}
// Static (default) pages
private LibraryPage library_page = null;
private MasterEventsDirectoryPage events_directory_page = null;
......@@ -435,6 +399,47 @@ public class LibraryWindow : AppWindow {
return (LibraryWindow) instance;
}
private int64 get_event_directory_page_time(SubEventsDirectoryPageStub *stub) {
return (stub->get_year() * 100) + stub->get_month();
}
private int64 event_branch_comparator(void *aptr, void *bptr) {
SidebarPage *a = (SidebarPage *) aptr;
SidebarPage *b = (SidebarPage *) bptr;
int64 start_a, start_b;
if (a is SubEventsDirectoryPageStub && b is SubEventsDirectoryPageStub) {
start_a = get_event_directory_page_time((SubEventsDirectoryPageStub *) a);
start_b = get_event_directory_page_time((SubEventsDirectoryPageStub *) b);
} else {
assert(a is EventPageStub);
assert(b is EventPageStub);
start_a = ((EventPageStub *) a)->event.get_start_time();
start_b = ((EventPageStub *) b)->event.get_start_time();
}
return start_a - start_b;
}
private int64 event_branch_ascending_comparator(void *a, void *b) {
return event_branch_comparator(a, b);
}
private int64 event_branch_descending_comparator(void *a, void *b) {
return event_branch_comparator(b, a);
}
private Comparator get_event_branch_comparator(int event_sort) {
if (event_sort == LibraryWindow.SORT_EVENTS_ORDER_ASCENDING) {
return event_branch_ascending_comparator;
} else {
assert(event_sort == LibraryWindow.SORT_EVENTS_ORDER_DESCENDING);
return event_branch_descending_comparator;
}
}
public static bool is_mount_uri_supported(string uri) {
foreach (string scheme in SUPPORTED_MOUNT_SCHEMES) {
if (uri.has_prefix(scheme))
......@@ -555,12 +560,12 @@ public class LibraryWindow : AppWindow {
Config.get_instance().set_events_sort_ascending(new_events_sort == SORT_EVENTS_ORDER_ASCENDING);
sidebar.sort_branch(events_directory_page.get_marker(),
new CompareEventBranch(new_events_sort));
get_event_branch_comparator(new_events_sort));
// the events directory pages need to know about resort
foreach (SubEventsDirectoryPageStub events_dir in events_dir_list) {
if (events_dir.has_page())
((SubEventsDirectoryPage) events_dir.get_page()).notify_sort_changed(new_events_sort);
((SubEventsDirectoryPage) events_dir.get_page()).notify_sort_changed();
}
// set the tree cursor to the current page, which might have been lost in the
......@@ -568,7 +573,7 @@ public class LibraryWindow : AppWindow {
sidebar.place_cursor(get_current_page());
// the events directory page needs to know about this
events_directory_page.notify_sort_changed(new_events_sort);
events_directory_page.notify_sort_changed();
}
private void on_display_basic_properties(Gtk.Action action) {
......@@ -822,11 +827,11 @@ public class LibraryWindow : AppWindow {
// add to sidebar again
sidebar.insert_child_sorted(get_parent_page(stub.event).get_marker(), stub,
new CompareEventBranch(get_events_sort()));
get_event_branch_comparator(get_events_sort()));
if (get_current_page() is EventPage &&
((EventPage) get_current_page()).page_event.equals(event))
sidebar.place_cursor(stub);
sidebar.place_cursor(stub);
}
// refresh name
......@@ -865,7 +870,7 @@ public class LibraryWindow : AppWindow {
}
}
CompareEventBranch comparator = new CompareEventBranch(get_events_sort());
Comparator comparator = get_event_branch_comparator(get_events_sort());
// make a new month directory page
SubEventsDirectoryPageStub month =
......@@ -895,7 +900,7 @@ public class LibraryWindow : AppWindow {
EventPageStub event_stub = new EventPageStub(event);
sidebar.insert_child_sorted(parent_page.get_marker(), event_stub,
new CompareEventBranch(get_events_sort()));
get_event_branch_comparator(get_events_sort()));
event_list.add(event_stub);
}
......
......@@ -1810,14 +1810,6 @@ public class LibraryPhoto : TransformablePhoto {
for (int ctr = 0; ctr < count; ctr++)
all_photos.add(new LibraryPhoto(all.get(ctr)));
// need to use a ProgressMonitor wrapper because add_many() doesn't report a total ... have
// to hold a ref on to real_monitor until the method exits
UnknownTotalMonitor real_monitor = null;
if (monitor != null) {
real_monitor = new UnknownTotalMonitor(all_photos.size, monitor);
monitor = real_monitor.monitor;
}
global.add_many(all_photos, monitor);
}
......
......@@ -1413,8 +1413,6 @@ public class LibraryPhotoPage : EditingHostPage {
// files and generating DirectPhotoSource stubs to represent each possible image file in the
// directory, only importing them into the system when selected by the user.
private class DirectViewCollection : ViewCollection {
private static FileComparator file_comparator = new FileComparator();
private class DirectViewManager : ViewManager {
public override DataView create_view(DataSource source) {
return new DataView(source);
......
......@@ -38,7 +38,7 @@ public interface SidebarPage : Object {
public class Sidebar : Gtk.TreeView {
private Gtk.TreeStore store = new Gtk.TreeStore(1, typeof(string));
private Gee.HashSet<SidebarPage> pages = new Gee.HashSet<SidebarPage>();
private Gee.ArrayList<SidebarPage> pages = new Gee.ArrayList<SidebarPage>();
private Gtk.Menu context_menu = null;
private Gtk.TreePath current_path = null;
......@@ -132,11 +132,14 @@ public class Sidebar : Gtk.TreeView {
page.clear_marker();
// remove from master table
pages.remove(page);
bool removed = pages.remove(page);
assert(removed);
}
private SidebarPage? locate_page(Gtk.TreePath path) {
foreach (SidebarPage page in pages) {
int count = pages.size;
for (int ctr = 0; ctr < count; ctr++) {
SidebarPage page = pages.get(ctr);
if (page.get_marker().get_path().compare(path) == 0)
return page;
}
......@@ -218,7 +221,7 @@ public class Sidebar : Gtk.TreeView {
}
public SidebarMarker insert_child_sorted(SidebarMarker parent, SidebarPage child,
Comparator<SidebarPage> comparator) {
Comparator comparator) {
// find parent in sidebar using its row reference
Gtk.TreeIter parent_iter;
bool found = store.get_iter(out parent_iter, parent.get_path());
......@@ -231,7 +234,7 @@ public class Sidebar : Gtk.TreeView {
SidebarPage page = locate_page(store.get_path(child_iter));
if (page != null) {
// look to insert before the current page
if (comparator.compare(child, page) < 0)
if (comparator(child, page) < 0)
return insert_sibling_before(page.get_marker(), child);
}
......@@ -390,7 +393,7 @@ public class Sidebar : Gtk.TreeView {
scroll_to_cell(path, null, false, 0, 0);
}
public void sort_branch(SidebarMarker marker, Comparator<SidebarPage> comparator) {
public void sort_branch(SidebarMarker marker, Comparator comparator) {
Gtk.TreePath path = marker.get_path();
if (path == null)
......@@ -416,7 +419,7 @@ public class Sidebar : Gtk.TreeView {
if (store.iter_nth_child(out iter1, iter, i) &&
store.iter_nth_child(out iter2, iter, i + 1) &&
comparator.compare(locate_page(path1), locate_page(path2)) > 0) {
comparator(locate_page(path1), locate_page(path2)) > 0) {
store.swap(iter1, iter2);
changes_made = true;
......
......@@ -4,22 +4,17 @@
* See the COPYING file in this distribution.
*/
public abstract class Comparator<G> {
public abstract int64 compare(G a, G b);
}
public delegate int64 Comparator(void *a, void *b);
// Common comparators
public class FileComparator : Comparator<File> {
public override int64 compare(File a, File b) {
return strcmp(a.get_path(), b.get_path());
}
public int64 file_comparator(void *a, void *b) {
return strcmp(((File *) a)->get_path(), ((File *) b)->get_path());
}
public class SortedList<G> : Object, Gee.Iterable<G> {
private Gee.ArrayList<G> list;
private Comparator<G> cmp;
private Comparator? cmp;
public SortedList(Comparator<G>? cmp = null) {
public SortedList(Comparator? cmp = null) {
this.list = new Gee.ArrayList<G>();
this.cmp = cmp;
}
......@@ -107,7 +102,7 @@ public class SortedList<G> : Object, Gee.Iterable<G> {
// TODO: Use a binary search.
int count = list.size;
for (int ctr = 0; ctr < count; ctr++) {
if (cmp.compare(list.get(ctr), search) == 0)
if (cmp(list.get(ctr), search) == 0)
return ctr;
}
......@@ -129,7 +124,7 @@ public class SortedList<G> : Object, Gee.Iterable<G> {
list.remove_at(index);
}
public void resort(Comparator<G> new_cmp) {
public void resort(Comparator new_cmp) {
cmp = new_cmp;
merge_sort();
......@@ -142,14 +137,24 @@ public class SortedList<G> : Object, Gee.Iterable<G> {
// Returns true if item has moved.
public bool resort_item(G item) {
int index = locate(item);
assert(index >= 0);
int new_index = get_sorted_insert_pos(item);
if (index == new_index)
return false;
// insert first, as the indexes shift after the remove
list.insert(new_index, item);
list.remove_at(index);
// insert in such a way to avoid index shift (performing the rightmost
// operation before the leftmost)
if (new_index > index) {
list.insert(new_index, item);
G removed_item = list.remove_at(index);
assert(item == removed_item);
} else {
G removed_item = list.remove_at(index);
assert(item == removed_item);
list.insert(new_index, item);
}
#if VERIFY_SORTED_LIST
assert(is_sorted());
......@@ -178,7 +183,7 @@ public class SortedList<G> : Object, Gee.Iterable<G> {
cmp_item = list.get(mid + 1);
}
int64 result = cmp.compare(item, cmp_item);
int64 result = cmp(item, cmp_item);
if (result < 0)
high = mid;
else if (result > 0)
......@@ -203,7 +208,7 @@ public class SortedList<G> : Object, Gee.Iterable<G> {
int length = list.size;
for (int ctr = 1; ctr < length; ctr++) {