]> git.saurik.com Git - cydia.git/blobdiff - MobileCydia.mm
Move Source{Cell,Controller} lower in MobileCydia.mm.
[cydia.git] / MobileCydia.mm
index b955c08a1d7ac9f35dcf5886a5ba53e32e6ecc31..afd02875176c8ed41183b5ba03a9c1a96bcd23de 100644 (file)
@@ -1346,6 +1346,7 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
     pkgSourceList *list_;
 
     SourceMap sources_;
+    CFMutableArrayRef deadSources_;
     CFMutableArrayRef packages_;
 
     _transient NSObject<ConfigurationDelegate, ProgressDelegate> *delegate_;
@@ -1355,6 +1356,8 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
     int cydiafd_;
     int statusfd_;
     FILE *input_;
+
+    std::map<const char *, _H<NSString> > sections_;
 }
 
 + (Database *) sharedInstance;
@@ -1388,6 +1391,9 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
 
 - (void) setDelegate:(id)delegate;
 - (Source *) getSource:(pkgCache::PkgFileIterator)file;
+
+- (NSString *) mappedSectionForPointer:(const char *)pointer;
+
 @end
 /* }}} */
 /* Delegate Helpers {{{ */
@@ -1825,7 +1831,8 @@ struct ParsedPackage {
 };
 
 @interface Package : NSObject {
-    uint32_t era_ : 29;
+    uint32_t era_ : 26;
+    uint32_t role_ : 3;
     uint32_t essential_ : 1;
     uint32_t obsolete_ : 1;
     uint32_t ignored_ : 1;
@@ -1844,7 +1851,7 @@ struct ParsedPackage {
     CYString latest_;
     CYString installed_;
 
-    CYString section_;
+    const char *section_;
     _transient NSString *section$_;
 
     Source *source_;
@@ -1853,7 +1860,6 @@ struct ParsedPackage {
     ParsedPackage *parsed_;
 
     NSMutableArray *tags_;
-    NSString *role_;
 }
 
 - (Package *) initWithVersion:(pkgCache::VerIterator)version withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database;
@@ -1915,7 +1921,6 @@ struct ParsedPackage {
 - (NSArray *) applications;
 
 - (Source *) source;
-- (NSString *) role;
 
 - (BOOL) matches:(NSString *)text;
 
@@ -2076,15 +2081,10 @@ struct PackageNameOrdering :
 - (void) dealloc {
     if (parsed_ != NULL)
         delete parsed_;
-
     if (source_ != nil)
         [source_ release];
-
     if (tags_ != nil)
         [tags_ release];
-    if (role_ != nil)
-        [role_ release];
-
     [super dealloc];
 }
 
@@ -2218,8 +2218,18 @@ struct PackageNameOrdering :
                     const char *name(tag.Name());
                     [tags_ addObject:[(NSString *)CYStringCreate(name) autorelease]];
 
-                    if (role_ == nil && strncmp(name, "role::", 6) == 0 /*&& strcmp(name, "role::leaper") != 0*/)
-                        role_ = (NSString *) CYStringCreate(name + 6);
+                    if (role_ == 0 && strncmp(name, "role::", 6) == 0 /*&& strcmp(name, "role::leaper") != 0*/) {
+                        if (strcmp(name + 6, "enduser") == 0)
+                            role_ = 1;
+                        else if (strcmp(name + 6, "hacker") == 0)
+                            role_ = 2;
+                        else if (strcmp(name + 6, "developer") == 0)
+                            role_ = 3;
+                        else if (strcmp(name + 6, "cydia") == 0)
+                            role_ = 7;
+                        else
+                            role_ = 4;
+                    }
 
                     if (strncmp(name, "cydia::", 7) == 0) {
                         if (strcmp(name + 7, "essential") == 0)
@@ -2267,7 +2277,7 @@ struct PackageNameOrdering :
         _end
 
         _profile(Package$initWithVersion$Section)
-            section_.set(NULL, iterator.Section());
+            section_ = iterator.Section();
         _end
 
         _profile(Package$initWithVersion$Flags)
@@ -2315,13 +2325,11 @@ struct PackageNameOrdering :
 
 - (NSString *) section {
     if (section$_ == nil) {
-        if (section_.empty())
+        if (section_ == NULL)
             return nil;
 
-        _profile(Package$section)
-            std::replace(section_.data(), section_.data() + section_.size(), '_', ' ');
-            NSString *name(section_);
-            section$_ = [SectionMap_ objectForKey:name] ?: name;
+        _profile(Package$section$mappedSectionForPointer)
+            section$_ = [database_ mappedSectionForPointer:section_];
         _end
     } return section$_;
 }
@@ -2471,12 +2479,12 @@ struct PackageNameOrdering :
 
 - (BOOL) unfiltered {
     _profile(Package$unfiltered$obsolete)
-        if (obsolete_)
+        if (_unlikely(obsolete_))
             return false;
     _end
 
     _profile(Package$unfiltered$hasSupportingRole)
-        if (![self hasSupportingRole])
+        if (_unlikely(![self hasSupportingRole]))
             return false;
     _end
 
@@ -2487,7 +2495,11 @@ struct PackageNameOrdering :
     if (![self unfiltered])
         return false;
 
-    NSString *section([self section]);
+    NSString *section;
+
+    _profile(Package$visible$section)
+        section = [self section];
+    _end
 
     _profile(Package$visible$isSectionVisible)
         if (section != nil && !isSectionVisible(section))
@@ -2710,10 +2722,6 @@ struct PackageNameOrdering :
     return source_ == (Source *) [NSNull null] ? nil : source_;
 }
 
-- (NSString *) role {
-    return role_;
-}
-
 - (BOOL) matches:(NSString *)text {
     if (text == nil)
         return NO;
@@ -2736,17 +2744,17 @@ struct PackageNameOrdering :
 }
 
 - (bool) hasSupportingRole {
-    if (role_ == nil)
+    if (role_ == 0)
         return true;
-    if ([role_ isEqualToString:@"enduser"])
+    if (role_ == 1)
         return true;
     if ([Role_ isEqualToString:@"User"])
         return false;
-    if ([role_ isEqualToString:@"hacker"])
+    if (role_ == 2)
         return true;
     if ([Role_ isEqualToString:@"Hacker"])
         return false;
-    if ([role_ isEqualToString:@"developer"])
+    if (role_ == 3)
         return true;
     if ([Role_ isEqualToString:@"Developer"])
         return false;
@@ -2868,7 +2876,7 @@ struct PackageNameOrdering :
 }
 
 - (bool) isInstalledAndUnfiltered:(NSNumber *)number {
-    return ![self uninstalled] && (![number boolValue] && ![role_ isEqualToString:@"cydia"] || [self unfiltered]);
+    return ![self uninstalled] && (![number boolValue] && role_ != 7 || [self unfiltered]);
 }
 
 - (bool) isVisibleInSection:(NSString *)name {
@@ -3033,6 +3041,8 @@ static NSString *Warning_;
 - (void) dealloc {
     // XXX: actually implement this thing
     _assert(false);
+    if (deadSources_)
+           CFRelease(deadSources_);
     [self releasePackages];
     apr_pool_destroy(pool_);
     NSRecycleZone(zone_);
@@ -3150,6 +3160,7 @@ static NSString *Warning_;
             capacity += 1024;
 
         packages_ = CFArrayCreateMutable(kCFAllocatorDefault, capacity, NULL);
+        deadSources_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
 
         int fds[2];
 
@@ -3224,6 +3235,7 @@ static NSString *Warning_;
     NSMutableArray *sources([NSMutableArray arrayWithCapacity:sources_.size()]);
     for (SourceMap::const_iterator i(sources_.begin()); i != sources_.end(); ++i)
         [sources addObject:i->second];
+    [sources addObjectsFromArray:(NSArray *)deadSources_];
     return sources;
 }
 
@@ -3330,7 +3342,9 @@ static NSString *Warning_;
     ++era_;
 
     [self releasePackages];
+
     sources_.clear();
+    CFArrayRemoveAllValues(deadSources_);
 
     _error->Discard();
 
@@ -3420,14 +3434,19 @@ static NSString *Warning_;
     }
 
     for (pkgSourceList::const_iterator source = list_->begin(); source != list_->end(); ++source) {
+        bool found = false;
         std::vector<pkgIndexFile *> *indices = (*source)->GetIndexFiles();
         for (std::vector<pkgIndexFile *>::const_iterator index = indices->begin(); index != indices->end(); ++index)
             // XXX: this could be more intelligent
             if (dynamic_cast<debPackagesIndex *>(*index) != NULL) {
                 pkgCache::PkgFileIterator cached((*index)->FindInCache(cache_));
-                if (!cached.end())
+                if (!cached.end()) {
                     sources_[cached->ID] = [[[Source alloc] initWithMetaIndex:*source inPool:pool_] autorelease];
+                    found = true;
+                }
             }
+        if (!found)
+            CFArrayAppendValue(deadSources_, [[[Source alloc] initWithMetaIndex:*source inPool:pool_] autorelease]);
     }
 
     {
@@ -3677,6 +3696,37 @@ static NSString *Warning_;
     return i == sources_.end() ? nil : i->second;
 }
 
+- (NSString *) mappedSectionForPointer:(const char *)section {
+    _H<NSString> *mapped;
+
+    _profile(Database$mappedSectionForPointer$Cache)
+        mapped = &sections_[section];
+    _end
+
+    if (*mapped == NULL) {
+        size_t length(strlen(section));
+        char spaced[length + 1];
+
+        _profile(Database$mappedSectionForPointer$Replace)
+            for (size_t index(0); index != length; ++index)
+                spaced[index] = section[index] == '_' ? ' ' : section[index];
+            spaced[length] = '\0';
+        _end
+
+        NSString *string;
+
+        _profile(Database$mappedSectionForPointer$stringWithUTF8String)
+            string = [NSString stringWithUTF8String:spaced];
+        _end
+
+        _profile(Database$mappedSectionForPointer$Map)
+            string = [SectionMap_ objectForKey:string] ?: string;
+        _end
+
+        *mapped = string;
+    } return *mapped;
+}
+
 @end
 /* }}} */
 
@@ -3933,6 +3983,71 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 /* }}} */
 
+/* @ Loading... Indicator {{{ */
+@interface CYLoadingIndicator : UIView {
+    UIActivityIndicatorView *spinner_;
+    UILabel *label_;
+    UIView *container_;
+}
+
+@property (readonly, nonatomic) UILabel *label;
+@property (readonly, nonatomic) UIActivityIndicatorView *activityIndicatorView;
+
+@end
+
+@implementation CYLoadingIndicator
+
+- (id)initWithFrame:(CGRect)frame {
+    if ((self = [super initWithFrame:frame])) {
+        container_ = [[[UIView alloc] init] autorelease];
+        [container_ setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin];
+
+        spinner_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray] autorelease];
+        [spinner_ startAnimating];
+        [container_ addSubview:spinner_];
+
+        label_ = [[[UILabel alloc] init] autorelease];
+        [label_ setFont:[UIFont boldSystemFontOfSize:15.0f]];
+        [label_ setBackgroundColor:[UIColor clearColor]];
+        [label_ setTextColor:[UIColor blackColor]];
+        [label_ setShadowColor:[UIColor whiteColor]];
+        [label_ setShadowOffset:CGSizeMake(0, 1)];
+        [label_ setText:[NSString stringWithFormat:Elision_, UCLocalize("LOADING"), nil]];
+        [container_ addSubview:label_];
+
+        CGSize viewsize = frame.size;
+        CGSize spinnersize = [spinner_ bounds].size;
+        CGSize textsize = [[label_ text] sizeWithFont:[label_ font]];
+        float bothwidth = spinnersize.width + textsize.width + 5.0f;
+
+        CGRect containrect = {
+            CGPointMake(floorf((viewsize.width / 2) - (bothwidth / 2)), floorf((viewsize.height / 2) - (spinnersize.height / 2))),
+            CGSizeMake(bothwidth, spinnersize.height)
+        };
+        CGRect textrect = {
+            CGPointMake(spinnersize.width + 5.0f, floorf((spinnersize.height / 2) - (textsize.height / 2))),
+            textsize
+        };
+        CGRect spinrect = {
+            CGPointZero,
+            spinnersize
+        };
+
+        [container_ setFrame:containrect];
+        [spinner_ setFrame:spinrect];
+        [label_ setFrame:textrect];
+        [self addSubview:container_];
+    }
+
+    return self;
+}
+
+- (UILabel *)label { return label_; }
+- (UIActivityIndicatorView *)activityIndicatorView { return spinner_; }
+
+@end
+/* }}} */
+
 /* Cydia Browser Controller {{{ */
 @interface CYBrowserController : BrowserController {
     CydiaObject *cydia_;
@@ -4772,7 +4887,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (PackageCell *) init;
 - (void) setPackage:(Package *)package;
 
-+ (int) heightForPackage:(Package *)package;
 - (void) drawContentRect:(CGRect)rect;
 
 @end
@@ -4952,10 +5066,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [placard_ drawAtPoint:CGPointMake(width - 52, 9)];
 }
 
-+ (int) heightForPackage:(Package *)package {
-    return 73;
-}
-
 @end
 /* }}} */
 /* Section Cell {{{ */
@@ -5079,7 +5189,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) drawContentRect:(CGRect)rect {
-    bool highlighted(highlighted_);
+    bool highlighted(highlighted_ && !editing_);
 
     [icon_ drawInRect:CGRectMake(8, 7, 32, 32)];
 
@@ -5341,7 +5451,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) reloadButtonClicked {
-    // Don't reload a package view by clicking the button.
+    // Don't reload a commerical package by tapping the loading button,
+    // but if it's not an Install button, we should forward it on.
+    if (![package_ uninstalled])
+        [self _customButtonClicked];
 }
 
 - (void) applyLoadingTitle {
@@ -5470,13 +5583,17 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super dealloc];
 }
 
++ (BOOL) hasIndexedCollation {
+    return NO; // XXX: objc_getClass("UILocalizedIndexedCollation") != nil;
+}
+
 - (NSInteger) numberOfSectionsInTableView:(UITableView *)list {
     NSInteger count([sections_ count]);
     return count == 0 ? 1 : count;
 }
 
 - (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section {
-    if ([sections_ count] == 0)
+    if ([sections_ count] == 0 || [[sections_ objectAtIndex:section] count] == 0)
         return nil;
     return [[sections_ objectAtIndex:section] name];
 }
@@ -5510,10 +5627,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
 }
 
-/*- (CGFloat) tableView:(UITableView *)table heightForRowAtIndexPath:(NSIndexPath *)path {
-    return [PackageCell heightForPackage:[self packageAtIndexPath:path]];
-}*/
-
 - (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path {
     Package *package([self packageAtIndexPath:path]);
     package = [database_ packageWithName:[package id]];
@@ -5522,10 +5635,15 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tableView {
+    // XXX: is 20 the most optimal number here?
     return [packages_ count] > 20 ? index_ : nil;
 }
 
-- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
+- (NSInteger) tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
+    if ([[self class] hasIndexedCollation]) {
+        return [[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionForSectionIndexTitleAtIndex:index];
+    }
+
     return index;
 }
 
@@ -5536,7 +5654,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         target_ = target;
         action_ = action;
 
-        index_ = [[NSMutableArray alloc] initWithCapacity:32];
+        index_ = [[self class] hasIndexedCollation]
+            ? [[[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionIndexTitles] retain]
+            : [[NSMutableArray alloc] initWithCapacity:32];
         indices_ = [[NSMutableDictionary alloc] initWithCapacity:32];
 
         packages_ = [[NSMutableArray arrayWithCapacity:16] retain];
@@ -5544,7 +5664,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
         list_ = [[UITableView alloc] initWithFrame:[self bounds] style:UITableViewStylePlain];
         [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:73.0f];
+        [list_ setRowHeight:73];
         [self addSubview:list_];
 
         [list_ setDataSource:self];
@@ -5573,37 +5693,70 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 [packages_ addObject:package];
     _end
 
-    [index_ removeAllObjects];
     [indices_ removeAllObjects];
 
     Section *section = nil;
 
-    _profile(PackageTable$reloadData$Section)
-        for (size_t offset(0), end([packages_ count]); offset != end; ++offset) {
-            Package *package;
-            unichar index;
+    if ([[self class] hasIndexedCollation]) {
+        id collation = [objc_getClass("UILocalizedIndexedCollation") currentCollation];
+        NSArray *titles = [collation sectionIndexTitles];
+        int secidx = -1;
 
-            _profile(PackageTable$reloadData$Section$Package)
-                package = [packages_ objectAtIndex:offset];
-                index = [package index];
-            _end
+        _profile(PackageTable$reloadData$Section)
+            for (size_t offset(0), end([packages_ count]); offset != end; ++offset) {
+                Package *package;
+                int index;
 
-            if (section == nil || [section index] != index) {
-                _profile(PackageTable$reloadData$Section$Allocate)
-                    section = [[[Section alloc] initWithIndex:index row:offset] autorelease];
+                _profile(PackageTable$reloadData$Section$Package)
+                    package = [packages_ objectAtIndex:offset];
+                    index = [collation sectionForObject:package collationStringSelector:@selector(name)];
                 _end
 
-                [index_ addObject:[section name]];
-                //[indices_ setObject:[NSNumber numberForInt:[sections_ count]] forKey:index];
+                while (secidx < index) {
+                    secidx += 1;
 
-                _profile(PackageTable$reloadData$Section$Add)
-                    [sections_ addObject:section];
-                _end
+                    _profile(PackageTable$reloadData$Section$Allocate)
+                        section = [[[Section alloc] initWithName:[titles objectAtIndex:secidx] row:offset localize:NO] autorelease];
+                    _end
+
+                    _profile(PackageTable$reloadData$Section$Add)
+                        [sections_ addObject:section];
+                    _end
+                }
+
+                [section addToCount];
             }
+        _end
+    } else {
+        [index_ removeAllObjects];
 
-            [section addToCount];
-        }
-    _end
+        _profile(PackageTable$reloadData$Section)
+            for (size_t offset(0), end([packages_ count]); offset != end; ++offset) {
+                Package *package;
+                unichar index;
+
+                _profile(PackageTable$reloadData$Section$Package)
+                    package = [packages_ objectAtIndex:offset];
+                    index = [package index];
+                _end
+
+                if (section == nil || [section index] != index) {
+                    _profile(PackageTable$reloadData$Section$Allocate)
+                        section = [[[Section alloc] initWithIndex:index row:offset] autorelease];
+                    _end
+
+                    [index_ addObject:[section name]];
+                    //[indices_ setObject:[NSNumber numberForInt:[sections_ count]] forKey:index];
+
+                    _profile(PackageTable$reloadData$Section$Add)
+                        [sections_ addObject:section];
+                    _end
+                }
+
+                [section addToCount];
+            }
+        _end
+    }
 
     _profile(PackageTable$reloadData$List)
         [list_ reloadData];
@@ -5754,1979 +5907,1985 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 /* }}} */
 
-/* Source Cell {{{ */
-@interface SourceCell : CYTableViewCell <
-    ContentDelegate
-> {
-    UIImage *icon_;
-    NSString *origin_;
-    NSString *description_;
-    NSString *label_;
+/* Installed Controller {{{ */
+@interface InstalledController : FilteredPackageController {
+    BOOL expert_;
 }
 
-- (void) setSource:(Source *)source;
+- (id) initWithDatabase:(Database *)database;
 
-@end
+- (void) updateRoleButton;
+- (void) queueStatusDidChange;
 
-@implementation SourceCell
+@end
 
-- (void) clearSource {
-    [icon_ release];
-    [origin_ release];
-    [description_ release];
-    [label_ release];
+@implementation InstalledController
 
-    icon_ = nil;
-    origin_ = nil;
-    description_ = nil;
-    label_ = nil;
+- (void) dealloc {
+    [super dealloc];
 }
 
-- (void) setSource:(Source *)source {
-    [self clearSource];
+- (NSString *) title { return UCLocalize("INSTALLED"); }
 
-    if (icon_ == nil)
-        icon_ = [UIImage applicationImageNamed:[NSString stringWithFormat:@"Sources/%@.png", [source host]]];
-    if (icon_ == nil)
-        icon_ = [UIImage applicationImageNamed:@"unknown.png"];
-    icon_ = [icon_ retain];
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED") filter:@selector(isInstalledAndUnfiltered:) with:[NSNumber numberWithBool:YES]]) != nil) {
+        [self updateRoleButton];
+        [self queueStatusDidChange];
+    } return self;
+}
 
-    origin_ = [[source name] retain];
-    label_ = [[source uri] retain];
-    description_ = [[source description] retain];
+#if !AlwaysReload
+- (void) queueButtonClicked {
+    [delegate_ queue];
+}
+#endif
 
-    [content_ setNeedsDisplay];
+- (void) queueStatusDidChange {
+#if !AlwaysReload
+    if (IsWildcat_) {
+        if (Queuing_) {
+            [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+                initWithTitle:UCLocalize("QUEUE")
+                style:UIBarButtonItemStyleDone
+                target:self
+                action:@selector(queueButtonClicked)
+            ] autorelease]];
+        } else {
+            [[self navigationItem] setLeftBarButtonItem:nil];
+        }
+    }
+#endif
 }
 
-- (void) dealloc {
-    [self clearSource];
-    [super dealloc];
+- (void) reloadData {
+    [packages_ reloadData];
 }
 
-- (SourceCell *) initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
-    if ((self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) != nil) {
-        UIView *content([self contentView]);
-        CGRect bounds([content bounds]);
+- (void) updateRoleButton {
+    if (Role_ != nil && ![Role_ isEqualToString:@"Developer"])
+        [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:(expert_ ? UCLocalize("EXPERT") : UCLocalize("SIMPLE"))
+            style:(expert_ ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
+            target:self
+            action:@selector(roleButtonClicked)
+        ] autorelease]];
+}
 
-        content_ = [[ContentView alloc] initWithFrame:bounds];
-        [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [content_ setBackgroundColor:[UIColor whiteColor]];
-        [content addSubview:content_];
+- (void) roleButtonClicked {
+    [packages_ setObject:[NSNumber numberWithBool:expert_]];
+    [packages_ reloadData];
+    expert_ = !expert_;
 
-        [content_ setDelegate:self];
-        [content_ setOpaque:YES];
-    } return self;
+    [self updateRoleButton];
 }
 
-- (void) drawContentRect:(CGRect)rect {
-    bool highlighted(highlighted_);
-    float width(rect.size.width);
+- (void) setDelegate:(id)delegate {
+    [super setDelegate:delegate];
+    [packages_ setDelegate:delegate];
+}
 
-    if (icon_ != nil)
-        [icon_ drawInRect:CGRectMake(10, 10, 30, 30)];
+@end
+/* }}} */
 
-    if (highlighted)
-        UISetColor(White_);
+/* Home Controller {{{ */
+@interface HomeController : CYBrowserController {
+}
 
-    if (!highlighted)
-        UISetColor(Black_);
-    [origin_ drawAtPoint:CGPointMake(48, 8) forWidth:(width - 80) withFont:Font18Bold_ lineBreakMode:UILineBreakModeTailTruncation];
+@end
 
-    if (!highlighted)
-        UISetColor(Blue_);
-    [label_ drawAtPoint:CGPointMake(58, 29) forWidth:(width - 95) withFont:Font12_ lineBreakMode:UILineBreakModeTailTruncation];
+@implementation HomeController
 
-    if (!highlighted)
-        UISetColor(Gray_);
-    [description_ drawAtPoint:CGPointMake(12, 46) forWidth:(width - 40) withFont:Font14_ lineBreakMode:UILineBreakModeTailTruncation];
++ (BOOL)shouldHideNavigationBar {
+    return NO;
 }
 
-@end
-/* }}} */
-/* Source Table {{{ */
-@interface SourceController : CYViewController <
-    UITableViewDataSource,
-    UITableViewDelegate
-> {
-    _transient Database *database_;
-    UITableView *list_;
-    NSMutableArray *sources_;
-    int offset_;
+- (void) _setMoreHeaders:(NSMutableURLRequest *)request {
+    [super _setMoreHeaders:request];
 
-    NSString *href_;
-    UIProgressHUD *hud_;
-    NSError *error_;
+    if (ChipID_ != nil)
+        [request setValue:ChipID_ forHTTPHeaderField:@"X-Chip-ID"];
+    if (UniqueID_ != nil)
+        [request setValue:UniqueID_ forHTTPHeaderField:@"X-Unique-ID"];
+    if (PLMN_ != nil)
+        [request setValue:PLMN_ forHTTPHeaderField:@"X-Carrier-ID"];
+}
 
-    //NSURLConnection *installer_;
-    NSURLConnection *trivial_;
-    NSURLConnection *trivial_bz2_;
-    NSURLConnection *trivial_gz_;
-    //NSURLConnection *automatic_;
+- (void) aboutButtonClicked {
+    UIAlertView *alert([[[UIAlertView alloc] init] autorelease]);
 
-    BOOL cydia_;
+    [alert setTitle:UCLocalize("ABOUT_CYDIA")];
+    [alert addButtonWithTitle:UCLocalize("CLOSE")];
+    [alert setCancelButtonIndex:0];
+
+    [alert setMessage:
+        @"Copyright (C) 2008-2010\n"
+        "Jay Freeman (saurik)\n"
+        "saurik@saurik.com\n"
+        "http://www.saurik.com/"
+    ];
+
+    [alert show];
 }
 
-- (id) initWithDatabase:(Database *)database;
+- (void) viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
 
-- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated;
+    if ([[self class] shouldHideNavigationBar])
+        [[self navigationController] setNavigationBarHidden:YES animated:animated];
+}
 
-@end
+- (void) viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
 
-@implementation SourceController
+    if ([[self class] shouldHideNavigationBar])
+        [[self navigationController] setNavigationBarHidden:NO animated:animated];
+}
 
-- (void) _releaseConnection:(NSURLConnection *)connection {
-    if (connection != nil) {
-        [connection cancel];
-        //[connection setDelegate:nil];
-        [connection release];
-    }
+- (id) init {
+    if ((self = [super init]) != nil) {
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("ABOUT")
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(aboutButtonClicked)
+        ] autorelease]];
+    } return self;
 }
 
-- (void) dealloc {
-    if (href_ != nil)
-        [href_ release];
-    if (hud_ != nil)
-        [hud_ release];
-    if (error_ != nil)
-        [error_ release];
+@end
+/* }}} */
+/* Manage Controller {{{ */
+@interface ManageController : CYBrowserController {
+}
 
-    //[self _releaseConnection:installer_];
-    [self _releaseConnection:trivial_];
-    [self _releaseConnection:trivial_gz_];
-    [self _releaseConnection:trivial_bz2_];
-    //[self _releaseConnection:automatic_];
+- (void) queueStatusDidChange;
+@end
 
-    [sources_ release];
-    [list_ release];
-    [super dealloc];
-}
+@implementation ManageController
 
-- (void) viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
+- (id) init {
+    if ((self = [super init]) != nil) {
+        [[self navigationItem] setTitle:UCLocalize("MANAGE")];
+
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("SETTINGS")
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(settingsButtonClicked)
+        ] autorelease]];
+
+        [self queueStatusDidChange];
+    } return self;
 }
 
-- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
-    return offset_ == 0 ? 1 : 2;
+- (void) settingsButtonClicked {
+    [delegate_ showSettings];
 }
 
-- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
-    switch (section + (offset_ == 0 ? 1 : 0)) {
-        case 0: return UCLocalize("ENTERED_BY_USER");
-        case 1: return UCLocalize("INSTALLED_BY_PACKAGE");
+#if !AlwaysReload
+- (void) queueButtonClicked {
+    [delegate_ queue];
+}
 
-        _nodefault
-    }
+- (void) applyLoadingTitle {
+    // No "Loading" title.
 }
 
-- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    int count = [sources_ count];
-    switch (section) {
-        case 0: return (offset_ == 0 ? count : offset_);
-        case 1: return count - offset_;
+- (void) applyRightButton {
+    // No right button.
+}
+#endif
 
-        _nodefault
+- (void) queueStatusDidChange {
+#if !AlwaysReload
+    if (!IsWildcat_ && Queuing_) {
+        [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("QUEUE")
+            style:UIBarButtonItemStyleDone
+            target:self
+            action:@selector(queueButtonClicked)
+        ] autorelease]];
+    } else {
+        [[self navigationItem] setRightBarButtonItem:nil];
     }
+#endif
 }
 
-- (Source *) sourceAtIndexPath:(NSIndexPath *)indexPath {
-    unsigned idx = 0;
-    switch (indexPath.section) {
-        case 0: idx = indexPath.row; break;
-        case 1: idx = indexPath.row + offset_; break;
+- (bool) isLoading {
+    return false;
+}
 
-        _nodefault
-    }
-    return [sources_ objectAtIndex:idx];
+@end
+/* }}} */
+
+/* Refresh Bar {{{ */
+@interface RefreshBar : UINavigationBar {
+    UIProgressIndicator *indicator_;
+    UITextLabel *prompt_;
+    UIProgressBar *progress_;
+    UINavigationButton *cancel_;
 }
 
-- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
-    Source *source = [self sourceAtIndexPath:indexPath];
-    return [source description] == nil ? 56 : 73;
+@end
+
+@implementation RefreshBar
+
+- (void) dealloc {
+    [indicator_ release];
+    [prompt_ release];
+    [progress_ release];
+    [cancel_ release];
+    [super dealloc];
 }
 
-- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-    static NSString *cellIdentifier = @"SourceCell";
+- (void) positionViews {
+    CGRect frame = [cancel_ frame];
+    frame.size = [cancel_ sizeThatFits:frame.size];
+    frame.origin.x = [self frame].size.width - frame.size.width - 5;
+    frame.origin.y = ([self frame].size.height - frame.size.height) / 2;
+    [cancel_ setFrame:frame];
 
-    SourceCell *cell = (SourceCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
-    if(cell == nil) cell = [[[SourceCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellIdentifier] autorelease];
-    [cell setSource:[self sourceAtIndexPath:indexPath]];
+    CGSize prgsize = {75, 100};
+    CGRect prgrect = {{
+        [self frame].size.width - prgsize.width - 10,
+        ([self frame].size.height - prgsize.height) / 2
+    } , prgsize};
+    [progress_ setFrame:prgrect];
 
-    return cell;
+    CGSize indsize([UIProgressIndicator defaultSizeForStyle:[indicator_ activityIndicatorViewStyle]]);
+    unsigned indoffset = ([self frame].size.height - indsize.height) / 2;
+    CGRect indrect = {{indoffset, indoffset}, indsize};
+    [indicator_ setFrame:indrect];
+
+    CGSize prmsize = {215, indsize.height + 4};
+    CGRect prmrect = {{
+        indoffset * 2 + indsize.width,
+        unsigned([self frame].size.height - prmsize.height) / 2 - 1
+    }, prmsize};
+    [prompt_ setFrame:prmrect];
 }
 
-- (UITableViewCellAccessoryType) tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath {
-    return UITableViewCellAccessoryDisclosureIndicator;
+- (void)setFrame:(CGRect)frame {
+    [super setFrame:frame];
+
+    [self positionViews];
 }
 
-- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
-    Source *source = [self sourceAtIndexPath:indexPath];
+- (id) initWithFrame:(CGRect)frame delegate:(id)delegate {
+    if ((self = [super initWithFrame:frame])) {
+        [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
 
-    FilteredPackageController *packages = [[[FilteredPackageController alloc]
-        initWithDatabase:database_
-        title:[source label]
-        filter:@selector(isVisibleInSource:)
-        with:source
-    ] autorelease];
+        [self setBarStyle:UIBarStyleBlack];
 
-    [packages setDelegate:delegate_];
+        UIBarStyle barstyle([self _barStyle:NO]);
+        bool ugly(barstyle == UIBarStyleDefault);
 
-    [[self navigationController] pushViewController:packages animated:YES];
-}
+        UIProgressIndicatorStyle style = ugly ?
+            UIProgressIndicatorStyleMediumBrown :
+            UIProgressIndicatorStyleMediumWhite;
 
-- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
-    Source *source = [self sourceAtIndexPath:indexPath];
-    return [source record] != nil;
+        indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectZero];
+        [indicator_ setStyle:style];
+        [indicator_ startAnimation];
+        [self addSubview:indicator_];
+
+        prompt_ = [[UITextLabel alloc] initWithFrame:CGRectZero];
+        [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]];
+        [prompt_ setBackgroundColor:[UIColor clearColor]];
+        [prompt_ setFont:[UIFont systemFontOfSize:15]];
+        [self addSubview:prompt_];
+
+        progress_ = [[UIProgressBar alloc] initWithFrame:CGRectZero];
+        [progress_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin];
+        [progress_ setStyle:0];
+        [self addSubview:progress_];
+
+        cancel_ = [[UINavigationButton alloc] initWithTitle:UCLocalize("CANCEL") style:UINavigationButtonStyleHighlighted];
+        [cancel_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+        [cancel_ addTarget:delegate action:@selector(cancelPressed) forControlEvents:UIControlEventTouchUpInside];
+        [cancel_ setBarStyle:barstyle];
+
+        [self positionViews];
+    } return self;
 }
 
-- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
-    Source *source = [self sourceAtIndexPath:indexPath];
-    [Sources_ removeObjectForKey:[source key]];
-    [delegate_ syncData];
+- (void) cancel {
+    [cancel_ removeFromSuperview];
 }
 
-- (void) complete {
-    [Sources_ setObject:[NSDictionary dictionaryWithObjectsAndKeys:
-        @"deb", @"Type",
-        href_, @"URI",
-        @"./", @"Distribution",
-    nil] forKey:[NSString stringWithFormat:@"deb:%@:./", href_]];
+- (void) start {
+    [prompt_ setText:UCLocalize("UPDATING_DATABASE")];
+    [progress_ setProgress:0];
+    [self addSubview:cancel_];
+}
 
-    [delegate_ syncData];
+- (void) stop {
+    [cancel_ removeFromSuperview];
 }
 
-- (NSString *) getWarning {
-    NSString *href(href_);
-    NSRange colon([href rangeOfString:@"://"]);
-    if (colon.location != NSNotFound)
-        href = [href substringFromIndex:(colon.location + 3)];
-    href = [href stringByAddingPercentEscapes];
-    href = [CydiaURL(@"api/repotag/") stringByAppendingString:href];
-    href = [href stringByCachingURLWithCurrentCDN];
+- (void) setPrompt:(NSString *)prompt {
+    [prompt_ setText:prompt];
+}
 
-    NSURL *url([NSURL URLWithString:href]);
+- (void) setProgress:(float)progress {
+    [progress_ setProgress:progress];
+}
 
-    NSStringEncoding encoding;
-    NSError *error(nil);
+@end
+/* }}} */
 
-    if (NSString *warning = [NSString stringWithContentsOfURL:url usedEncoding:&encoding error:&error])
-        return [warning length] == 0 ? nil : warning;
-    return nil;
-}
+@class CYNavigationController;
 
-- (void) _endConnection:(NSURLConnection *)connection {
-    // XXX: the memory management in this method is horribly awkward
+/* Cydia Tab Bar Controller {{{ */
+@interface CYTabBarController : UITabBarController <
+    ProgressDelegate
+> {
+    _transient Database *database_;
+    RefreshBar *refreshbar_;
 
-    NSURLConnection **field = NULL;
-    if (connection == trivial_)
-        field = &trivial_;
-    else if (connection == trivial_bz2_)
-        field = &trivial_bz2_;
-    else if (connection == trivial_gz_)
-        field = &trivial_gz_;
-    _assert(field != NULL);
-    [connection release];
-    *field = nil;
-
-    if (
-        trivial_ == nil &&
-        trivial_bz2_ == nil &&
-        trivial_gz_ == nil
-    ) {
-        bool defer(false);
+    bool dropped_;
+    bool updating_;
+    // XXX: ok, "updatedelegate_"?...
+    _transient NSObject<CydiaDelegate> *updatedelegate_;
 
-        if (cydia_) {
-            if (NSString *warning = [self yieldToSelector:@selector(getWarning)]) {
-                defer = true;
+    id root_;
+}
 
-                UIAlertView *alert = [[[UIAlertView alloc]
-                    initWithTitle:UCLocalize("SOURCE_WARNING")
-                    message:warning
-                    delegate:self
-                    cancelButtonTitle:UCLocalize("CANCEL")
-                    otherButtonTitles:UCLocalize("ADD_ANYWAY"), nil
-                ] autorelease];
+- (void) dropBar:(BOOL)animated;
+- (void) beginUpdate;
+- (void) raiseBar:(BOOL)animated;
+- (BOOL) updating;
 
-                [alert setContext:@"warning"];
-                [alert setNumberOfRows:1];
-                [alert show];
-            } else
-                [self complete];
-        } else if (error_ != nil) {
-            UIAlertView *alert = [[[UIAlertView alloc]
-                initWithTitle:UCLocalize("VERIFICATION_ERROR")
-                message:[error_ localizedDescription]
-                delegate:self
-                cancelButtonTitle:UCLocalize("OK")
-                otherButtonTitles:nil
-            ] autorelease];
+@end
 
-            [alert setContext:@"urlerror"];
-            [alert show];
-        } else {
-            UIAlertView *alert = [[[UIAlertView alloc]
-                initWithTitle:UCLocalize("NOT_REPOSITORY")
-                message:UCLocalize("NOT_REPOSITORY_EX")
-                delegate:self
-                cancelButtonTitle:UCLocalize("OK")
-                otherButtonTitles:nil
-            ] autorelease];
+@implementation CYTabBarController
 
-            [alert setContext:@"trivial"];
-            [alert show];
-        }
+/* XXX: some logic should probably go here related to
+freeing the view controllers on tab change */
 
-        [delegate_ setStatusBarShowsProgress:NO];
-        [delegate_ removeProgressHUD:hud_];
+- (void) reloadData {
+    size_t count([[self viewControllers] count]);
+    for (size_t i(0); i != count; ++i) {
+        CYNavigationController *page([[self viewControllers] objectAtIndex:(count - i - 1)]);
+        [page reloadData];
+    }
+}
 
-        [hud_ autorelease];
-        hud_ = nil;
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
 
-        if (!defer) {
-            [href_ release];
-            href_ = nil;
-        }
+        [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
 
-        if (error_ != nil) {
-            [error_ release];
-            error_ = nil;
-        }
-    }
+        refreshbar_ = [[RefreshBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, [UINavigationBar defaultSize].height) delegate:self];
+    } return self;
 }
 
-- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
-    switch ([response statusCode]) {
-        case 200:
-            cydia_ = YES;
-    }
+- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
+    return IsWildcat_ || orientation == UIInterfaceOrientationPortrait;
 }
 
-- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
-    lprintf("connection:\"%s\" didFailWithError:\"%s\"", [href_ UTF8String], [[error localizedDescription] UTF8String]);
-    if (error_ != nil)
-        error_ = [error retain];
-    [self _endConnection:connection];
+- (void) setUpdate:(NSDate *)date {
+    [self beginUpdate];
 }
 
-- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
-    [self _endConnection:connection];
+- (void) beginUpdate {
+    [refreshbar_ start];
+    [self dropBar:YES];
+
+    [updatedelegate_ retainNetworkActivityIndicator];
+    updating_ = true;
+
+    [NSThread
+        detachNewThreadSelector:@selector(performUpdate)
+        toTarget:self
+        withObject:nil
+    ];
 }
 
-- (NSString *) title { return UCLocalize("SOURCES"); }
+- (void) performUpdate { _pooled
+    Status status;
+    status.setDelegate(self);
+    [database_ updateWithStatus:status];
 
-- (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method {
-    NSMutableURLRequest *request = [NSMutableURLRequest
-        requestWithURL:[NSURL URLWithString:href]
-        cachePolicy:NSURLRequestUseProtocolCachePolicy
-        timeoutInterval:120.0
+    [self
+        performSelectorOnMainThread:@selector(completeUpdate)
+        withObject:nil
+        waitUntilDone:NO
     ];
+}
 
-    [request setHTTPMethod:method];
+- (void) stopUpdateWithSelector:(SEL)selector {
+    updating_ = false;
+    [updatedelegate_ releaseNetworkActivityIndicator];
 
-    if (Machine_ != NULL)
-        [request setValue:[NSString stringWithUTF8String:Machine_] forHTTPHeaderField:@"X-Machine"];
-    if (UniqueID_ != nil)
-        [request setValue:UniqueID_ forHTTPHeaderField:@"X-Unique-ID"];
-    if (Role_ != nil)
-        [request setValue:Role_ forHTTPHeaderField:@"X-Role"];
+    [self raiseBar:YES];
+    [refreshbar_ stop];
 
-    return [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
+    [updatedelegate_ performSelector:selector withObject:nil afterDelay:0];
 }
 
-- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
-    NSString *context([alert context]);
+- (void) completeUpdate {
+    if (!updating_)
+        return;
+    [self stopUpdateWithSelector:@selector(reloadData)];
+}
 
-    if ([context isEqualToString:@"source"]) {
-        switch (button) {
-            case 1: {
-                NSString *href = [[alert textField] text];
+- (void) cancelUpdate {
+    [self stopUpdateWithSelector:@selector(updateData)];
+}
 
-                //installer_ = [[self _requestHRef:href method:@"GET"] retain];
+- (void) cancelPressed {
+    [self cancelUpdate];
+}
 
-                if (![href hasSuffix:@"/"])
-                    href_ = [href stringByAppendingString:@"/"];
-                else
-                    href_ = href;
-                href_ = [href_ retain];
+- (BOOL) updating {
+    return updating_;
+}
 
-                trivial_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages"] method:@"HEAD"] retain];
-                trivial_bz2_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.bz2"] method:@"HEAD"] retain];
-                trivial_gz_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.gz"] method:@"HEAD"] retain];
-                //trivial_bz2_ = [[self _requestHRef:[href stringByAppendingString:@"dists/Release"] method:@"HEAD"] retain];
+- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
+    [refreshbar_ setPrompt:[NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), UCLocalize("ERROR"), error]];
+}
 
-                cydia_ = false;
+- (void) startProgress {
+}
 
-                // XXX: this is stupid
-                hud_ = [[delegate_ addProgressHUD] retain];
-                [hud_ setText:UCLocalize("VERIFYING_URL")];
-            } break;
+- (void) setProgressTitle:(NSString *)title {
+    [self
+        performSelectorOnMainThread:@selector(_setProgressTitle:)
+        withObject:title
+        waitUntilDone:YES
+    ];
+}
 
-            case 0:
-            break;
+- (bool) isCancelling:(size_t)received {
+    return !updating_;
+}
 
-            _nodefault
-        }
+- (void) setProgressPercent:(float)percent {
+    [self
+        performSelectorOnMainThread:@selector(_setProgressPercent:)
+        withObject:[NSNumber numberWithFloat:percent]
+        waitUntilDone:YES
+    ];
+}
 
-        [alert dismissWithClickedButtonIndex:-1 animated:YES];
-    } else if ([context isEqualToString:@"trivial"])
-        [alert dismissWithClickedButtonIndex:-1 animated:YES];
-    else if ([context isEqualToString:@"urlerror"])
-        [alert dismissWithClickedButtonIndex:-1 animated:YES];
-    else if ([context isEqualToString:@"warning"]) {
-        switch (button) {
-            case 1:
-                [self complete];
-            break;
+- (void) addProgressOutput:(NSString *)output {
+    [self
+        performSelectorOnMainThread:@selector(_addProgressOutput:)
+        withObject:output
+        waitUntilDone:YES
+    ];
+}
 
-            case 0:
-            break;
+- (void) _setProgressTitle:(NSString *)title {
+    [refreshbar_ setPrompt:title];
+}
 
-            _nodefault
-        }
+- (void) _setProgressPercent:(NSNumber *)percent {
+    [refreshbar_ setProgress:[percent floatValue]];
+}
 
-        [href_ release];
-        href_ = nil;
+- (void) _addProgressOutput:(NSString *)output {
+}
 
-        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+- (void) setUpdateDelegate:(id)delegate {
+    updatedelegate_ = delegate;
+}
+
+- (CGFloat) statusBarHeight {
+    if (UIInterfaceOrientationIsPortrait([self interfaceOrientation])) {
+        return [[UIApplication sharedApplication] statusBarFrame].size.height;
+    } else {
+        return [[UIApplication sharedApplication] statusBarFrame].size.width;
     }
 }
 
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
-        [[self navigationItem] setTitle:UCLocalize("SOURCES")];
-        [self updateButtonsForEditingStatus:NO animated:NO];
+- (UIView *) transitionView {
+    if ([self respondsToSelector:@selector(_transitionView)])
+        return [self _transitionView];
+    else
+        return MSHookIvar<id>(self, "_viewControllerTransitionView");
+}
 
-        database_ = database;
-        sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
+- (void) dropBar:(BOOL)animated {
+    if (dropped_)
+        return;
+    dropped_ = true;
 
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:list_];
+    UIView *transition([self transitionView]);
+    [[self view] addSubview:refreshbar_];
 
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
+    CGRect barframe([refreshbar_ frame]);
 
-        [self reloadData];
-    } return self;
-}
+    if (false) // XXX: _UIApplicationLinkedOnOrAfter(4)
+        barframe.origin.y = [self statusBarHeight];
+    else
+        barframe.origin.y = 0;
 
-- (void) reloadData {
-    pkgSourceList list;
-    if (!list.ReadMainList())
-        return;
+    [refreshbar_ setFrame:barframe];
 
-    [sources_ removeAllObjects];
-    [sources_ addObjectsFromArray:[database_ sources]];
-    _trace();
-    [sources_ sortUsingSelector:@selector(compareByNameAndType:)];
-    _trace();
+    if (animated)
+        [UIView beginAnimations:nil context:NULL];
 
-    int count([sources_ count]);
-    offset_ = 0;
-    for (int i = 0; i != count; i++) {
-        if ([[sources_ objectAtIndex:i] record] == nil)
-            break;
-        offset_++;
-    }
+    CGRect viewframe = [transition frame];
+    viewframe.origin.y += barframe.size.height;
+    viewframe.size.height -= barframe.size.height;
+    [transition setFrame:viewframe];
 
-    [list_ setEditing:NO];
-    [self updateButtonsForEditingStatus:NO animated:NO];
-    [list_ reloadData];
+    if (animated)
+        [UIView commitAnimations];
+
+    // Ensure bar has the proper width for our view, it might have changed
+    barframe.size.width = viewframe.size.width;
+    [refreshbar_ setFrame:barframe];
+
+    // XXX: fix Apple's layout bug
+    [[root_ selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
 }
 
-- (void) addButtonClicked {
-    /*[book_ pushPage:[[[AddSourceController alloc]
-        initWithBook:book_
-        database:database_
-    ] autorelease]];*/
+- (void) raiseBar:(BOOL)animated {
+    if (!dropped_)
+        return;
+    dropped_ = false;
 
-    UIAlertView *alert = [[[UIAlertView alloc]
-        initWithTitle:UCLocalize("ENTER_APT_URL")
-        message:nil
-        delegate:self
-        cancelButtonTitle:UCLocalize("CANCEL")
-        otherButtonTitles:UCLocalize("ADD_SOURCE"), nil
-    ] autorelease];
+    UIView *transition([self transitionView]);
+    [refreshbar_ removeFromSuperview];
 
-    [alert setContext:@"source"];
-    [alert setTransform:CGAffineTransformTranslate([alert transform], 0.0, 100.0)];
+    CGRect barframe([refreshbar_ frame]);
 
-    [alert setNumberOfRows:1];
-    [alert addTextFieldWithValue:@"http://" label:@""];
+    if (animated)
+        [UIView beginAnimations:nil context:NULL];
 
-    UITextInputTraits *traits = [[alert textField] textInputTraits];
-    [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
-    [traits setAutocorrectionType:UITextAutocorrectionTypeNo];
-    [traits setKeyboardType:UIKeyboardTypeURL];
-    // XXX: UIReturnKeyDone
-    [traits setReturnKeyType:UIReturnKeyNext];
+    CGRect viewframe = [transition frame];
+    viewframe.origin.y -= barframe.size.height;
+    viewframe.size.height += barframe.size.height;
+    [transition setFrame:viewframe];
 
-    [alert show];
+    if (animated)
+        [UIView commitAnimations];
+
+    // XXX: fix Apple's layout bug
+    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
 }
 
-- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated {
-    [[self navigationItem] setLeftBarButtonItem:(editing ? [[[UIBarButtonItem alloc]
-        initWithTitle:UCLocalize("ADD")
-        style:UIBarButtonItemStylePlain
-        target:self
-        action:@selector(addButtonClicked)
-    ] autorelease] : [[self navigationItem] backBarButtonItem]) animated:animated];
+#if 0
+- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
+    // XXX: fix Apple's layout bug
+    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
+}
+#endif
 
-    [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
-        initWithTitle:(editing ? UCLocalize("DONE") : UCLocalize("EDIT"))
-        style:(editing ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
-        target:self
-        action:@selector(editButtonClicked)
-    ] autorelease] animated:animated];
+- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
+    bool dropped(dropped_);
 
-    if (IsWildcat_ && !editing)
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("SETTINGS")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(settingsButtonClicked)
-        ] autorelease]];
-}
+    if (dropped)
+        [self raiseBar:NO];
 
-- (void) settingsButtonClicked {
-    [delegate_ showSettings];
+    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
+
+    if (dropped)
+        [self dropBar:NO];
+
+    // XXX: fix Apple's layout bug
+    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
 }
 
-- (void) editButtonClicked {
-    [list_ setEditing:![list_ isEditing] animated:YES];
+- (void) statusBarFrameChanged:(NSNotification *)notification {
+    if (dropped_) {
+        [self raiseBar:NO];
+        [self dropBar:NO];
+    }
+}
 
-    [self updateButtonsForEditingStatus:[list_ isEditing] animated:YES];
+- (void) dealloc {
+    [refreshbar_ release];
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+    [super dealloc];
 }
 
 @end
 /* }}} */
 
-/* Installed Controller {{{ */
-@interface InstalledController : FilteredPackageController {
-    BOOL expert_;
+/* Cydia Navigation Controller {{{ */
+@interface CYNavigationController : UINavigationController {
+    _transient Database *database_;
+    _transient id<UINavigationControllerDelegate> delegate_;
 }
 
 - (id) initWithDatabase:(Database *)database;
-
-- (void) updateRoleButton;
-- (void) queueStatusDidChange;
+- (void) reloadData;
 
 @end
 
-@implementation InstalledController
-
-- (void) dealloc {
-    [super dealloc];
-}
-
-- (NSString *) title { return UCLocalize("INSTALLED"); }
-
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED") filter:@selector(isInstalledAndUnfiltered:) with:[NSNumber numberWithBool:YES]]) != nil) {
-        [self updateRoleButton];
-        [self queueStatusDidChange];
-    } return self;
-}
 
-#if !AlwaysReload
-- (void) queueButtonClicked {
-    [delegate_ queue];
-}
-#endif
+@implementation CYNavigationController
 
-- (void) queueStatusDidChange {
-#if !AlwaysReload
-    if (IsWildcat_) {
-        if (Queuing_) {
-            [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-                initWithTitle:UCLocalize("QUEUE")
-                style:UIBarButtonItemStyleDone
-                target:self
-                action:@selector(queueButtonClicked)
-            ] autorelease]];
-        } else {
-            [[self navigationItem] setLeftBarButtonItem:nil];
-        }
+- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
+    // Inherit autorotation settings for modal parents.
+    if ([self parentViewController] && [[self parentViewController] modalViewController] == self) {
+        return [[self parentViewController] shouldAutorotateToInterfaceOrientation:orientation];
+    } else if ([self parentViewController]) {
+        return [[self parentViewController] shouldAutorotateToInterfaceOrientation:orientation];
+    } else {
+        return [super shouldAutorotateToInterfaceOrientation:orientation];
     }
-#endif
 }
 
-- (void) reloadData {
-    [packages_ reloadData];
+- (void) dealloc {
+    [super dealloc];
 }
 
-- (void) updateRoleButton {
-    if (Role_ != nil && ![Role_ isEqualToString:@"Developer"])
-        [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:(expert_ ? UCLocalize("EXPERT") : UCLocalize("SIMPLE"))
-            style:(expert_ ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
-            target:self
-            action:@selector(roleButtonClicked)
-        ] autorelease]];
+- (void) reloadData {
+    size_t count([[self viewControllers] count]);
+    for (size_t i(0); i != count; ++i) {
+        CYViewController *page([[self viewControllers] objectAtIndex:(count - i - 1)]);
+        [page reloadData];
+    }
 }
 
-- (void) roleButtonClicked {
-    [packages_ setObject:[NSNumber numberWithBool:expert_]];
-    [packages_ reloadData];
-    expert_ = !expert_;
-
-    [self updateRoleButton];
+- (void) setDelegate:(id<UINavigationControllerDelegate>)delegate {
+    delegate_ = delegate;
 }
 
-- (void) setDelegate:(id)delegate {
-    [super setDelegate:delegate];
-    [packages_ setDelegate:delegate];
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+    } return self;
 }
 
 @end
 /* }}} */
-
-/* Home Controller {{{ */
-@interface HomeController : CYBrowserController {
+/* Cydia:// Protocol {{{ */
+@interface CydiaURLProtocol : NSURLProtocol {
 }
 
 @end
 
-@implementation HomeController
-
-- (void) _setMoreHeaders:(NSMutableURLRequest *)request {
-    [super _setMoreHeaders:request];
+@implementation CydiaURLProtocol
 
-    if (ChipID_ != nil)
-        [request setValue:ChipID_ forHTTPHeaderField:@"X-Chip-ID"];
-    if (UniqueID_ != nil)
-        [request setValue:UniqueID_ forHTTPHeaderField:@"X-Unique-ID"];
-    if (PLMN_ != nil)
-        [request setValue:PLMN_ forHTTPHeaderField:@"X-Carrier-ID"];
-}
-
-- (void) aboutButtonClicked {
-    UIAlertView *alert([[[UIAlertView alloc] init] autorelease]);
-
-    [alert setTitle:UCLocalize("ABOUT_CYDIA")];
-    [alert addButtonWithTitle:UCLocalize("CLOSE")];
-    [alert setCancelButtonIndex:0];
-
-    [alert setMessage:
-        @"Copyright (C) 2008-2010\n"
-        "Jay Freeman (saurik)\n"
-        "saurik@saurik.com\n"
-        "http://www.saurik.com/"
-    ];
-
-    [alert show];
-}
-
-- (void) viewWillAppear:(BOOL)animated {
-    [super viewWillAppear:animated];
-    //[[self navigationController] setNavigationBarHidden:YES animated:animated];
++ (BOOL) canInitWithRequest:(NSURLRequest *)request {
+    NSURL *url([request URL]);
+    if (url == nil)
+        return NO;
+    NSString *scheme([[url scheme] lowercaseString]);
+    if (scheme == nil || ![scheme isEqualToString:@"cydia"])
+        return NO;
+    return YES;
 }
 
-- (void) viewWillDisappear:(BOOL)animated {
-    [super viewWillDisappear:animated];
-    //[[self navigationController] setNavigationBarHidden:NO animated:animated];
++ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
+    return request;
 }
 
-- (id) init {
-    if ((self = [super init]) != nil) {
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("ABOUT")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(aboutButtonClicked)
-        ] autorelease]];
-    } return self;
-}
+- (void) _returnPNGWithImage:(UIImage *)icon forRequest:(NSURLRequest *)request {
+    id<NSURLProtocolClient> client([self client]);
+    if (icon == nil)
+        [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]];
+    else {
+        NSData *data(UIImagePNGRepresentation(icon));
 
-@end
-/* }}} */
-/* Manage Controller {{{ */
-@interface ManageController : CYBrowserController {
+        NSURLResponse *response([[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil] autorelease]);
+        [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+        [client URLProtocol:self didLoadData:data];
+        [client URLProtocolDidFinishLoading:self];
+    }
 }
 
-- (void) queueStatusDidChange;
-@end
-
-@implementation ManageController
-
-- (id) init {
-    if ((self = [super init]) != nil) {
-        [[self navigationItem] setTitle:UCLocalize("MANAGE")];
-
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("SETTINGS")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(settingsButtonClicked)
-        ] autorelease]];
-
-        [self queueStatusDidChange];
-    } return self;
-}
+- (void) startLoading {
+    id<NSURLProtocolClient> client([self client]);
+    NSURLRequest *request([self request]);
 
-- (void) settingsButtonClicked {
-    [delegate_ showSettings];
-}
+    NSURL *url([request URL]);
+    NSString *href([url absoluteString]);
 
-#if !AlwaysReload
-- (void) queueButtonClicked {
-    [delegate_ queue];
-}
+    NSString *path([href substringFromIndex:8]);
+    NSRange slash([path rangeOfString:@"/"]);
 
-- (void) applyLoadingTitle {
-    // No "Loading" title.
-}
+    NSString *command;
+    if (slash.location == NSNotFound) {
+        command = path;
+        path = nil;
+    } else {
+        command = [path substringToIndex:slash.location];
+        path = [path substringFromIndex:(slash.location + 1)];
+    }
 
-- (void) applyRightButton {
-    // No right button.
-}
-#endif
+    Database *database([Database sharedInstance]);
 
-- (void) queueStatusDidChange {
-#if !AlwaysReload
-    if (!IsWildcat_ && Queuing_) {
-        [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("QUEUE")
-            style:UIBarButtonItemStyleDone
-            target:self
-            action:@selector(queueButtonClicked)
-        ] autorelease]];
-    } else {
-        [[self navigationItem] setRightBarButtonItem:nil];
+    if ([command isEqualToString:@"package-icon"]) {
+        if (path == nil)
+            goto fail;
+        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+        Package *package([database packageWithName:path]);
+        if (package == nil)
+            goto fail;
+        UIImage *icon([package icon]);
+        [self _returnPNGWithImage:icon forRequest:request];
+    } else if ([command isEqualToString:@"source-icon"]) {
+        if (path == nil)
+            goto fail;
+        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+        NSString *source(Simplify(path));
+        UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sources/%@.png", App_, source]]);
+        if (icon == nil)
+            icon = [UIImage applicationImageNamed:@"unknown.png"];
+        [self _returnPNGWithImage:icon forRequest:request];
+    } else if ([command isEqualToString:@"uikit-image"]) {
+        if (path == nil)
+            goto fail;
+        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+        UIImage *icon(_UIImageWithName(path));
+        [self _returnPNGWithImage:icon forRequest:request];
+    } else if ([command isEqualToString:@"section-icon"]) {
+        if (path == nil)
+            goto fail;
+        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+        NSString *section(Simplify(path));
+        UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, section]]);
+        if (icon == nil)
+            icon = [UIImage applicationImageNamed:@"unknown.png"];
+        [self _returnPNGWithImage:icon forRequest:request];
+    } else fail: {
+        [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]];
     }
-#endif
 }
 
-- (bool) isLoading {
-    return false;
+- (void) stopLoading {
 }
 
 @end
 /* }}} */
 
-/* Refresh Bar {{{ */
-@interface RefreshBar : UINavigationBar {
-    UIProgressIndicator *indicator_;
-    UITextLabel *prompt_;
-    UIProgressBar *progress_;
-    UINavigationButton *cancel_;
+/* Sections Controller {{{ */
+@interface CYSectionsController : CYViewController <
+    UITableViewDataSource,
+    UITableViewDelegate
+> {
+    _transient Database *database_;
+    NSMutableArray *sections_;
+    NSMutableArray *filtered_;
+    UITableView *list_;
+    UIView *accessory_;
+    BOOL editing_;
 }
 
+- (id) initWithDatabase:(Database *)database;
+- (void) reloadData;
+- (void) resetView;
+
+- (void) editButtonClicked;
+
 @end
 
-@implementation RefreshBar
+@implementation CYSectionsController
 
 - (void) dealloc {
-    [indicator_ release];
-    [prompt_ release];
-    [progress_ release];
-    [cancel_ release];
+    [list_ setDataSource:nil];
+    [list_ setDelegate:nil];
+
+    [sections_ release];
+    [filtered_ release];
+    [list_ release];
+    [accessory_ release];
     [super dealloc];
 }
 
-- (void) positionViews {
-    CGRect frame = [cancel_ frame];
-    frame.size = [cancel_ sizeThatFits:frame.size];
-    frame.origin.x = [self frame].size.width - frame.size.width - 5;
-    frame.origin.y = ([self frame].size.height - frame.size.height) / 2;
-    [cancel_ setFrame:frame];
+- (void) setEditing:(BOOL)editing {
+    if ((editing_ = editing))
+        [list_ reloadData];
+    else
+        [delegate_ updateData];
 
-    CGSize prgsize = {75, 100};
-    CGRect prgrect = {{
-        [self frame].size.width - prgsize.width - 10,
-        ([self frame].size.height - prgsize.height) / 2
-    } , prgsize};
-    [progress_ setFrame:prgrect];
+    [[self navigationItem] setTitle:editing_ ? UCLocalize("SECTION_VISIBILITY") : UCLocalize("SECTIONS")];
+    [[[self navigationItem] rightBarButtonItem] setTitle:[sections_ count] == 0 ? nil : editing_ ? UCLocalize("DONE") : UCLocalize("EDIT")];
+    [[[self navigationItem] rightBarButtonItem] setStyle:editing_ ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain];
+}
 
-    CGSize indsize([UIProgressIndicator defaultSizeForStyle:[indicator_ activityIndicatorViewStyle]]);
-    unsigned indoffset = ([self frame].size.height - indsize.height) / 2;
-    CGRect indrect = {{indoffset, indoffset}, indsize};
-    [indicator_ setFrame:indrect];
+- (void) viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
+}
 
-    CGSize prmsize = {215, indsize.height + 4};
-    CGRect prmrect = {{
-        indoffset * 2 + indsize.width,
-        unsigned([self frame].size.height - prmsize.height) / 2 - 1
-    }, prmsize};
-    [prompt_ setFrame:prmrect];
+- (void) viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+    if (editing_) [self setEditing:NO];
 }
 
-- (void)setFrame:(CGRect)frame {
-    [super setFrame:frame];
+- (Section *) sectionAtIndexPath:(NSIndexPath *)indexPath {
+    Section *section = (editing_ ? [sections_ objectAtIndex:[indexPath row]] : ([indexPath row] == 0 ? nil : [filtered_ objectAtIndex:([indexPath row] - 1)]));
+    return section;
+}
 
-    [self positionViews];
+- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    return editing_ ? [sections_ count] : [filtered_ count] + 1;
 }
 
-- (id) initWithFrame:(CGRect)frame delegate:(id)delegate {
-    if ((self = [super initWithFrame:frame])) {
-        [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
+/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
+    return 45.0f;
+}*/
 
-        [self setBarStyle:UIBarStyleBlack];
+- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    static NSString *reuseIdentifier = @"SectionCell";
 
-        UIBarStyle barstyle([self _barStyle:NO]);
-        bool ugly(barstyle == UIBarStyleDefault);
+    SectionCell *cell = (SectionCell *) [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
+    if (cell == nil)
+        cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease];
 
-        UIProgressIndicatorStyle style = ugly ?
-            UIProgressIndicatorStyleMediumBrown :
-            UIProgressIndicatorStyleMediumWhite;
-
-        indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectZero];
-        [indicator_ setStyle:style];
-        [indicator_ startAnimation];
-        [self addSubview:indicator_];
+    [cell setSection:[self sectionAtIndexPath:indexPath] editing:editing_];
 
-        prompt_ = [[UITextLabel alloc] initWithFrame:CGRectZero];
-        [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]];
-        [prompt_ setBackgroundColor:[UIColor clearColor]];
-        [prompt_ setFont:[UIFont systemFontOfSize:15]];
-        [self addSubview:prompt_];
+    return cell;
+}
 
-        progress_ = [[UIProgressBar alloc] initWithFrame:CGRectZero];
-        [progress_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin];
-        [progress_ setStyle:0];
-        [self addSubview:progress_];
+- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    if (editing_)
+        return;
 
-        cancel_ = [[UINavigationButton alloc] initWithTitle:UCLocalize("CANCEL") style:UINavigationButtonStyleHighlighted];
-        [cancel_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [cancel_ addTarget:delegate action:@selector(cancelPressed) forControlEvents:UIControlEventTouchUpInside];
-        [cancel_ setBarStyle:barstyle];
+    Section *section = [self sectionAtIndexPath:indexPath];
+    NSString *name = [section name];
+    NSString *title;
 
-        [self positionViews];
-    } return self;
-}
+    if ([indexPath row] == 0) {
+        section = nil;
+        name = nil;
+        title = UCLocalize("ALL_PACKAGES");
+    } else {
+        if (name != nil) {
+            name = [NSString stringWithString:name];
+            title = [[NSBundle mainBundle] localizedStringForKey:Simplify(name) value:nil table:@"Sections"];
+        } else {
+            name = @"";
+            title = UCLocalize("NO_SECTION");
+        }
+    }
 
-- (void) cancel {
-    [cancel_ removeFromSuperview];
-}
+    FilteredPackageController *table = [[[FilteredPackageController alloc]
+        initWithDatabase:database_
+        title:title
+        filter:@selector(isVisibleInSection:)
+        with:name
+    ] autorelease];
 
-- (void) start {
-    [prompt_ setText:UCLocalize("UPDATING_DATABASE")];
-    [progress_ setProgress:0];
-    [self addSubview:cancel_];
-}
+    [table setDelegate:delegate_];
 
-- (void) stop {
-    [cancel_ removeFromSuperview];
+    [[self navigationController] pushViewController:table animated:YES];
 }
 
-- (void) setPrompt:(NSString *)prompt {
-    [prompt_ setText:prompt];
-}
+- (NSString *) title { return UCLocalize("SECTIONS"); }
 
-- (void) setProgress:(float)progress {
-    [progress_ setProgress:progress];
-}
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
 
-@end
-/* }}} */
+        [[self navigationItem] setTitle:UCLocalize("SECTIONS")];
 
-@class CYNavigationController;
+        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
+        filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
 
-/* Cydia Tab Bar Controller {{{ */
-@interface CYTabBarController : UITabBarController <
-    ProgressDelegate
-> {
-    _transient Database *database_;
-    RefreshBar *refreshbar_;
+        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
+        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [list_ setRowHeight:45.0f];
+        [[self view] addSubview:list_];
 
-    bool dropped_;
-    bool updating_;
-    // XXX: ok, "updatedelegate_"?...
-    _transient NSObject<CydiaDelegate> *updatedelegate_;
+        [list_ setDataSource:self];
+        [list_ setDelegate:self];
 
-    id root_;
+        [self reloadData];
+    } return self;
 }
 
-- (void) dropBar:(BOOL)animated;
-- (void) beginUpdate;
-- (void) raiseBar:(BOOL)animated;
-- (BOOL) updating;
+- (void) reloadData {
+    NSArray *packages = [database_ packages];
 
-@end
+    [sections_ removeAllObjects];
+    [filtered_ removeAllObjects];
 
-@implementation CYTabBarController
+    NSMutableDictionary *sections([NSMutableDictionary dictionaryWithCapacity:32]);
 
-/* XXX: some logic should probably go here related to
-freeing the view controllers on tab change */
+    _trace();
+    for (Package *package in packages) {
+        NSString *name([package section]);
+        NSString *key(name == nil ? @"" : name);
 
-- (void) reloadData {
-    size_t count([[self viewControllers] count]);
-    for (size_t i(0); i != count; ++i) {
-        CYNavigationController *page([[self viewControllers] objectAtIndex:(count - i - 1)]);
-        [page reloadData];
-    }
-}
+        Section *section;
 
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
-        database_ = database;
+        _profile(SectionsView$reloadData$Section)
+            section = [sections objectForKey:key];
+            if (section == nil) {
+                _profile(SectionsView$reloadData$Section$Allocate)
+                    section = [[[Section alloc] initWithName:name localize:YES] autorelease];
+                    [sections setObject:section forKey:key];
+                _end
+            }
+        _end
 
-        [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
+        [section addToCount];
 
-        refreshbar_ = [[RefreshBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, [UINavigationBar defaultSize].height) delegate:self];
-    } return self;
-}
+        _profile(SectionsView$reloadData$Filter)
+            if (![package valid] || ![package visible])
+                continue;
+        _end
 
-- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
-    return IsWildcat_ || orientation == UIInterfaceOrientationPortrait;
-}
+        [section addToRow];
+    }
+    _trace();
 
-- (void) setUpdate:(NSDate *)date {
-    [self beginUpdate];
-}
+    [sections_ addObjectsFromArray:[sections allValues]];
 
-- (void) beginUpdate {
-    [refreshbar_ start];
-    [self dropBar:YES];
+    [sections_ sortUsingSelector:@selector(compareByLocalized:)];
 
-    [updatedelegate_ retainNetworkActivityIndicator];
-    updating_ = true;
+    for (Section *section in sections_) {
+        size_t count([section row]);
+        if (count == 0)
+            continue;
 
-    [NSThread
-        detachNewThreadSelector:@selector(performUpdate)
-        toTarget:self
-        withObject:nil
-    ];
-}
+        section = [[[Section alloc] initWithName:[section name] localized:[section localized]] autorelease];
+        [section setCount:count];
+        [filtered_ addObject:section];
+    }
 
-- (void) performUpdate { _pooled
-    Status status;
-    status.setDelegate(self);
-    [database_ updateWithStatus:status];
+    [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
+        initWithTitle:([sections_ count] == 0 ? nil : UCLocalize("EDIT"))
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(editButtonClicked)
+    ] autorelease] animated:([[self navigationItem] rightBarButtonItem] != nil)];
 
-    [self
-        performSelectorOnMainThread:@selector(completeUpdate)
-        withObject:nil
-        waitUntilDone:NO
-    ];
+    [list_ reloadData];
+    _trace();
 }
 
-- (void) stopUpdateWithSelector:(SEL)selector {
-    updating_ = false;
-    [updatedelegate_ releaseNetworkActivityIndicator];
-
-    [self raiseBar:YES];
-    [refreshbar_ stop];
-
-    [updatedelegate_ performSelector:selector withObject:nil afterDelay:0];
+- (void) resetView {
+    if (editing_)
+        [self editButtonClicked];
 }
 
-- (void) completeUpdate {
-    if (!updating_)
-        return;
-    [self stopUpdateWithSelector:@selector(reloadData)];
+- (void)editButtonClicked {
+    [self setEditing:!editing_];
 }
 
-- (void) cancelUpdate {
-    [self stopUpdateWithSelector:@selector(updateData)];
+- (UIView *) accessoryView {
+    return accessory_;
 }
 
-- (void) cancelPressed {
-    [self cancelUpdate];
+@end
+/* }}} */
+/* Changes Controller {{{ */
+@interface ChangesController : CYViewController <
+    UITableViewDataSource,
+    UITableViewDelegate
+> {
+    _transient Database *database_;
+    unsigned era_;
+    CFMutableArrayRef packages_;
+    NSMutableArray *sections_;
+    UITableView *list_;
+    unsigned upgrades_;
+    BOOL hasSentFirstLoad_;
 }
 
-- (BOOL) updating {
-    return updating_;
-}
+- (id) initWithDatabase:(Database *)database delegate:(id)delegate;
+- (void) reloadData;
 
-- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
-    [refreshbar_ setPrompt:[NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), UCLocalize("ERROR"), error]];
-}
+@end
 
-- (void) startProgress {
-}
+@implementation ChangesController
 
-- (void) setProgressTitle:(NSString *)title {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressTitle:)
-        withObject:title
-        waitUntilDone:YES
-    ];
-}
+- (void) dealloc {
+    [list_ setDelegate:nil];
+    [list_ setDataSource:nil];
 
-- (bool) isCancelling:(size_t)received {
-    return !updating_;
-}
+    CFRelease(packages_);
 
-- (void) setProgressPercent:(float)percent {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressPercent:)
-        withObject:[NSNumber numberWithFloat:percent]
-        waitUntilDone:YES
-    ];
+    [sections_ release];
+    [list_ release];
+    [super dealloc];
 }
 
-- (void) addProgressOutput:(NSString *)output {
-    [self
-        performSelectorOnMainThread:@selector(_addProgressOutput:)
-        withObject:output
-        waitUntilDone:YES
-    ];
+- (void) viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    if (!hasSentFirstLoad_) {
+        hasSentFirstLoad_ = YES;
+        [self performSelector:@selector(reloadData) withObject:nil afterDelay:0.0];
+    } else {
+        [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
+    }
 }
 
-- (void) _setProgressTitle:(NSString *)title {
-    [refreshbar_ setPrompt:title];
+- (NSInteger) numberOfSectionsInTableView:(UITableView *)list {
+    NSInteger count([sections_ count]);
+    return count == 0 ? 1 : count;
 }
 
-- (void) _setProgressPercent:(NSNumber *)percent {
-    [refreshbar_ setProgress:[percent floatValue]];
+- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section {
+    if ([sections_ count] == 0)
+        return nil;
+    return [[sections_ objectAtIndex:section] name];
 }
 
-- (void) _addProgressOutput:(NSString *)output {
+- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section {
+    if ([sections_ count] == 0)
+        return 0;
+    return [[sections_ objectAtIndex:section] count];
 }
 
-- (void) setUpdateDelegate:(id)delegate {
-    updatedelegate_ = delegate;
+- (Package *) packageAtIndex:(NSUInteger)index {
+    return (Package *) CFArrayGetValueAtIndex(packages_, index);
 }
 
-- (CGFloat) statusBarHeight {
-    if (UIInterfaceOrientationIsPortrait([self interfaceOrientation])) {
-        return [[UIApplication sharedApplication] statusBarFrame].size.height;
-    } else {
-        return [[UIApplication sharedApplication] statusBarFrame].size.width;
-    }
-}
+- (Package *) packageAtIndexPath:(NSIndexPath *)path {
+@synchronized (database_) {
+    if ([database_ era] != era_)
+        return nil;
 
-- (UIView *) transitionView {
-    if ([self respondsToSelector:@selector(_transitionView)])
-        return [self _transitionView];
-    else
-        return MSHookIvar<id>(self, "_viewControllerTransitionView");
+    NSUInteger sectionIndex([path section]);
+    if (sectionIndex >= [sections_ count])
+        return nil;
+    Section *section([sections_ objectAtIndex:sectionIndex]);
+    NSInteger row([path row]);
+    return [[[self packageAtIndex:([section row] + row)] retain] autorelease];
+} }
+
+- (UITableViewCell *) tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)path {
+    PackageCell *cell((PackageCell *) [table dequeueReusableCellWithIdentifier:@"Package"]);
+    if (cell == nil)
+        cell = [[[PackageCell alloc] init] autorelease];
+    [cell setPackage:[self packageAtIndexPath:path]];
+    return cell;
 }
 
-- (void) dropBar:(BOOL)animated {
-    if (dropped_)
-        return;
-    dropped_ = true;
+- (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path {
+    Package *package([self packageAtIndexPath:path]);
+    PackageController *view([delegate_ packageController]);
+    [view setDelegate:delegate_];
+    [view setPackage:package];
+    [[self navigationController] pushViewController:view animated:YES];
+    return path;
+}
 
-    UIView *transition([self transitionView]);
-    [[self view] addSubview:refreshbar_];
+- (void) refreshButtonClicked {
+    [delegate_ beginUpdate];
+    [[self navigationItem] setLeftBarButtonItem:nil animated:YES];
+}
 
-    CGRect barframe([refreshbar_ frame]);
+- (void) upgradeButtonClicked {
+    [delegate_ distUpgrade];
+}
 
-    if (false) // XXX: _UIApplicationLinkedOnOrAfter(4)
-        barframe.origin.y = [self statusBarHeight];
-    else
-        barframe.origin.y = 0;
+- (NSString *) title { return UCLocalize("CHANGES"); }
 
-    [refreshbar_ setFrame:barframe];
+- (id) initWithDatabase:(Database *)database delegate:(id)delegate {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+        [[self navigationItem] setTitle:UCLocalize("CHANGES")];
 
-    if (animated)
-        [UIView beginAnimations:nil context:NULL];
+        packages_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
 
-    CGRect viewframe = [transition frame];
-    viewframe.origin.y += barframe.size.height;
-    viewframe.size.height -= barframe.size.height;
-    [transition setFrame:viewframe];
+        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
 
-    if (animated)
-        [UIView commitAnimations];
+        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
+        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [list_ setRowHeight:73];
+        [[self view] addSubview:list_];
 
-    // Ensure bar has the proper width for our view, it might have changed
-    barframe.size.width = viewframe.size.width;
-    [refreshbar_ setFrame:barframe];
+        [list_ setDataSource:self];
+        [list_ setDelegate:self];
 
-    // XXX: fix Apple's layout bug
-    [[root_ selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
+        delegate_ = delegate;
+    } return self;
 }
 
-- (void) raiseBar:(BOOL)animated {
-    if (!dropped_)
-        return;
-    dropped_ = false;
+- (void) _reloadPackages:(NSArray *)packages {
+    CFRelease(packages_);
+    packages_ = CFArrayCreateMutable(kCFAllocatorDefault, [packages count], NULL);
 
-    UIView *transition([self transitionView]);
-    [refreshbar_ removeFromSuperview];
+    _trace();
+    _profile(ChangesController$_reloadPackages$Filter)
+        for (Package *package in packages)
+            if ([package upgradableAndEssential:YES] || [package visible])
+                CFArrayAppendValue(packages_, package);
+    _end
+    _trace();
+    _profile(ChangesController$_reloadPackages$radixSort)
+        [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<SKRadixFunction>(&PackageChangesRadix) withContext:NULL];
+    _end
+    _trace();
+}
 
-    CGRect barframe([refreshbar_ frame]);
+- (void) reloadData {
+@synchronized (database_) {
+    era_ = [database_ era];
+    NSArray *packages = [database_ packages];
 
-    if (animated)
-        [UIView beginAnimations:nil context:NULL];
+    [sections_ removeAllObjects];
 
-    CGRect viewframe = [transition frame];
-    viewframe.origin.y -= barframe.size.height;
-    viewframe.size.height += barframe.size.height;
-    [transition setFrame:viewframe];
+#if 1
+    UIProgressHUD *hud([delegate_ addProgressHUD]);
+    [hud setText:UCLocalize("LOADING")];
+    //NSLog(@"HUD:%@::%@", delegate_, hud);
+    [self yieldToSelector:@selector(_reloadPackages:) withObject:packages];
+    [delegate_ removeProgressHUD:hud];
+#else
+    [self _reloadPackages:packages];
+#endif
 
-    if (animated)
-        [UIView commitAnimations];
+    Section *upgradable = [[[Section alloc] initWithName:UCLocalize("AVAILABLE_UPGRADES") localize:NO] autorelease];
+    Section *ignored = nil;
+    Section *section = nil;
+    time_t last = 0;
 
-    // XXX: fix Apple's layout bug
-    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
-}
+    upgrades_ = 0;
+    bool unseens = false;
 
-#if 0
-- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
-    // XXX: fix Apple's layout bug
-    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
-}
-#endif
+    CFDateFormatterRef formatter(CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle));
 
-- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
-    bool dropped(dropped_);
+    for (size_t offset = 0, count = CFArrayGetCount(packages_); offset != count; ++offset) {
+        Package *package = [self packageAtIndex:offset];
 
-    if (dropped)
-        [self raiseBar:NO];
+        BOOL uae = [package upgradableAndEssential:YES];
 
-    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
+        if (!uae) {
+            unseens = true;
+            time_t seen([package seen]);
 
-    if (dropped)
-        [self dropBar:NO];
+            if (section == nil || last != seen) {
+                last = seen;
 
-    // XXX: fix Apple's layout bug
-    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
-}
+                NSString *name;
+                name = (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) [NSDate dateWithTimeIntervalSince1970:seen]);
+                [name autorelease];
 
-- (void) statusBarFrameChanged:(NSNotification *)notification {
-    if (dropped_) {
-        [self raiseBar:NO];
-        [self dropBar:NO];
+                _profile(ChangesController$reloadData$Allocate)
+                    name = [NSString stringWithFormat:UCLocalize("NEW_AT"), name];
+                    section = [[[Section alloc] initWithName:name row:offset localize:NO] autorelease];
+                    [sections_ addObject:section];
+                _end
+            }
+
+            [section addToCount];
+        } else if ([package ignored]) {
+            if (ignored == nil) {
+                ignored = [[[Section alloc] initWithName:UCLocalize("IGNORED_UPGRADES") row:offset localize:NO] autorelease];
+            }
+            [ignored addToCount];
+        } else {
+            ++upgrades_;
+            [upgradable addToCount];
+        }
     }
-}
+    _trace();
 
-- (void) dealloc {
-    [refreshbar_ release];
-    [[NSNotificationCenter defaultCenter] removeObserver:self];
-    [super dealloc];
-}
+    CFRelease(formatter);
 
-@end
-/* }}} */
+    if (unseens) {
+        Section *last = [sections_ lastObject];
+        size_t count = [last count];
+        CFArrayReplaceValues(packages_, CFRangeMake(CFArrayGetCount(packages_) - count, count), NULL, 0);
+        [sections_ removeLastObject];
+    }
 
-/* Cydia Navigation Controller {{{ */
-@interface CYNavigationController : UINavigationController {
-    _transient Database *database_;
-    _transient id<UINavigationControllerDelegate> delegate_;
-}
+    if ([ignored count] != 0)
+        [sections_ insertObject:ignored atIndex:0];
+    if (upgrades_ != 0)
+        [sections_ insertObject:upgradable atIndex:0];
 
-- (id) initWithDatabase:(Database *)database;
-- (void) reloadData;
+    [list_ reloadData];
 
-@end
+    if (upgrades_ > 0)
+        [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:[NSString stringWithFormat:UCLocalize("PARENTHETICAL"), UCLocalize("UPGRADE"), [NSString stringWithFormat:@"%u", upgrades_]]
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(upgradeButtonClicked)
+        ] autorelease]];
 
+    if (![delegate_ updating])
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("REFRESH")
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(refreshButtonClicked)
+        ] autorelease]];
 
-@implementation CYNavigationController
-
-- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
-    // Inherit autorotation settings for modal parents.
-    if ([self parentViewController] && [[self parentViewController] modalViewController] == self) {
-        return [[self parentViewController] shouldAutorotateToInterfaceOrientation:orientation];
-    } else {
-        return [super shouldAutorotateToInterfaceOrientation:orientation];
-    }
-}
-
-- (void) dealloc {
-    [super dealloc];
-}
-
-- (void) reloadData {
-    size_t count([[self viewControllers] count]);
-    for (size_t i(0); i != count; ++i) {
-        CYViewController *page([[self viewControllers] objectAtIndex:(count - i - 1)]);
-        [page reloadData];
-    }
-}
-
-- (void) setDelegate:(id<UINavigationControllerDelegate>)delegate {
-    delegate_ = delegate;
-}
-
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
-        database_ = database;
-    } return self;
-}
+    PrintTimes();
+} }
 
 @end
 /* }}} */
-/* Cydia:// Protocol {{{ */
-@interface CydiaURLProtocol : NSURLProtocol {
+/* Search Controller {{{ */
+@interface SearchController : FilteredPackageController <
+    UISearchBarDelegate
+> {
+    UISearchBar *search_;
 }
 
+- (id) initWithDatabase:(Database *)database;
+- (void) reloadData;
+
 @end
 
-@implementation CydiaURLProtocol
+@implementation SearchController
 
-+ (BOOL) canInitWithRequest:(NSURLRequest *)request {
-    NSURL *url([request URL]);
-    if (url == nil)
-        return NO;
-    NSString *scheme([[url scheme] lowercaseString]);
-    if (scheme == nil || ![scheme isEqualToString:@"cydia"])
-        return NO;
-    return YES;
+- (void) dealloc {
+    [search_ release];
+    [super dealloc];
 }
 
-+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
-    return request;
+- (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar {
+    [packages_ setObject:[search_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)];
+    [search_ resignFirstResponder];
+    [self reloadData];
 }
 
-- (void) _returnPNGWithImage:(UIImage *)icon forRequest:(NSURLRequest *)request {
-    id<NSURLProtocolClient> client([self client]);
-    if (icon == nil)
-        [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]];
-    else {
-        NSData *data(UIImagePNGRepresentation(icon));
-
-        NSURLResponse *response([[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil] autorelease]);
-        [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
-        [client URLProtocol:self didLoadData:data];
-        [client URLProtocolDidFinishLoading:self];
-    }
+- (void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)text {
+    [packages_ setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)];
+    [self reloadData];
 }
 
-- (void) startLoading {
-    id<NSURLProtocolClient> client([self client]);
-    NSURLRequest *request([self request]);
+- (NSString *) title { return nil; }
 
-    NSURL *url([request URL]);
-    NSString *href([url absoluteString]);
+- (id) initWithDatabase:(Database *)database {
+    return [super initWithDatabase:database title:UCLocalize("SEARCH") filter:@selector(isUnfilteredAndSearchedForBy:) with:nil];
+}
 
-    NSString *path([href substringFromIndex:8]);
-    NSRange slash([path rangeOfString:@"/"]);
+- (void)viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    if (!search_) {
+        search_ = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)];
+        [search_ layoutSubviews];
+        [search_ setPlaceholder:UCLocalize("SEARCH_EX")];
 
-    NSString *command;
-    if (slash.location == NSNotFound) {
-        command = path;
-        path = nil;
-    } else {
-        command = [path substringToIndex:slash.location];
-        path = [path substringFromIndex:(slash.location + 1)];
+        UITextField *textField;
+        if ([search_ respondsToSelector:@selector(searchField)])
+            textField = [search_ searchField];
+        else
+            textField = MSHookIvar<UITextField *>(search_, "_searchField");
+
+        [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
+        [search_ setDelegate:self];
+        [textField setEnablesReturnKeyAutomatically:NO];
+        [[self navigationItem] setTitleView:textField];
     }
+}
 
-    Database *database([Database sharedInstance]);
+- (void) _reloadData {
+}
 
-    if ([command isEqualToString:@"package-icon"]) {
-        if (path == nil)
-            goto fail;
-        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
-        Package *package([database packageWithName:path]);
-        if (package == nil)
-            goto fail;
-        UIImage *icon([package icon]);
-        [self _returnPNGWithImage:icon forRequest:request];
-    } else if ([command isEqualToString:@"source-icon"]) {
-        if (path == nil)
-            goto fail;
-        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
-        NSString *source(Simplify(path));
-        UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sources/%@.png", App_, source]]);
-        if (icon == nil)
-            icon = [UIImage applicationImageNamed:@"unknown.png"];
-        [self _returnPNGWithImage:icon forRequest:request];
-    } else if ([command isEqualToString:@"uikit-image"]) {
-        if (path == nil)
-            goto fail;
-        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
-        UIImage *icon(_UIImageWithName(path));
-        [self _returnPNGWithImage:icon forRequest:request];
-    } else if ([command isEqualToString:@"section-icon"]) {
-        if (path == nil)
-            goto fail;
-        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
-        NSString *section(Simplify(path));
-        UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, section]]);
-        if (icon == nil)
-            icon = [UIImage applicationImageNamed:@"unknown.png"];
-        [self _returnPNGWithImage:icon forRequest:request];
-    } else fail: {
-        [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]];
-    }
+- (void) reloadData {
+    _profile(SearchController$reloadData)
+        [packages_ reloadData];
+    _end
+    PrintTimes();
+    [packages_ resetCursor];
 }
 
-- (void) stopLoading {
+- (void) didSelectPackage:(Package *)package {
+    [search_ resignFirstResponder];
+    [super didSelectPackage:package];
 }
 
 @end
 /* }}} */
-
-/* Sections Controller {{{ */
-@interface CYSectionsController : CYViewController <
+/* Settings Controller {{{ */
+@interface CYPackageSettingsController : CYViewController <
     UITableViewDataSource,
     UITableViewDelegate
 > {
     _transient Database *database_;
-    NSMutableArray *sections_;
-    NSMutableArray *filtered_;
-    UITableView *list_;
-    UIView *accessory_;
-    BOOL editing_;
+    NSString *name_;
+    Package *package_;
+    UITableView *table_;
+    UISwitch *subscribedSwitch_;
+    UISwitch *ignoredSwitch_;
+    UITableViewCell *subscribedCell_;
+    UITableViewCell *ignoredCell_;
 }
 
-- (id) initWithDatabase:(Database *)database;
-- (void) reloadData;
-- (void) resetView;
-
-- (void) editButtonClicked;
+- (id) initWithDatabase:(Database *)database package:(NSString *)package;
 
 @end
 
-@implementation CYSectionsController
+@implementation CYPackageSettingsController
 
 - (void) dealloc {
-    [list_ setDataSource:nil];
-    [list_ setDelegate:nil];
+    [name_ release];
+    if (package_ != nil)
+        [package_ release];
+    [table_ release];
+    [subscribedSwitch_ release];
+    [ignoredSwitch_ release];
+    [subscribedCell_ release];
+    [ignoredCell_ release];
 
-    [sections_ release];
-    [filtered_ release];
-    [list_ release];
-    [accessory_ release];
     [super dealloc];
 }
 
-- (void) setEditing:(BOOL)editing {
-    if ((editing_ = editing))
-        [list_ reloadData];
-    else
-        [delegate_ updateData];
+- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
+    if (package_ == nil)
+        return 0;
 
-    [[self navigationItem] setTitle:editing_ ? UCLocalize("SECTION_VISIBILITY") : UCLocalize("SECTIONS")];
-    [[[self navigationItem] rightBarButtonItem] setTitle:[sections_ count] == 0 ? nil : editing_ ? UCLocalize("DONE") : UCLocalize("EDIT")];
-    [[[self navigationItem] rightBarButtonItem] setStyle:editing_ ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain];
+    return 1;
 }
 
-- (void) viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
-}
+- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    if (package_ == nil)
+        return 0;
 
-- (void) viewWillDisappear:(BOOL)animated {
-    [super viewWillDisappear:animated];
-    if (editing_) [self setEditing:NO];
+    return 2;
 }
 
-- (Section *) sectionAtIndexPath:(NSIndexPath *)indexPath {
-    Section *section = (editing_ ? [sections_ objectAtIndex:[indexPath row]] : ([indexPath row] == 0 ? nil : [filtered_ objectAtIndex:([indexPath row] - 1)]));
-    return section;
+- (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
+    return UCLocalize("SHOW_ALL_CHANGES_EX");
 }
 
-- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    return editing_ ? [sections_ count] : [filtered_ count] + 1;
+- (void) onSubscribed:(id)control {
+    bool value([control isOn]);
+    if (package_ == nil)
+        return;
+    if ([package_ setSubscribed:value])
+        [delegate_ updateData];
 }
 
-/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
-    return 45.0f;
-}*/
+- (void) onIgnored:(id)control {
+    // TODO: set Held state - possibly call out to dpkg, etc.
+}
 
 - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-    static NSString *reuseIdentifier = @"SectionCell";
+    if (package_ == nil)
+        return nil;
 
-    SectionCell *cell = (SectionCell *) [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
-    if (cell == nil)
-        cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease];
-
-    [cell setSection:[self sectionAtIndexPath:indexPath] editing:editing_];
-
-    return cell;
-}
-
-- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
-    if (editing_)
-        return;
-
-    Section *section = [self sectionAtIndexPath:indexPath];
-    NSString *name = [section name];
-    NSString *title;
+    switch ([indexPath row]) {
+        case 0: return subscribedCell_;
+        case 1: return ignoredCell_;
 
-    if ([indexPath row] == 0) {
-        section = nil;
-        name = nil;
-        title = UCLocalize("ALL_PACKAGES");
-    } else {
-        if (name != nil) {
-            name = [NSString stringWithString:name];
-            title = [[NSBundle mainBundle] localizedStringForKey:Simplify(name) value:nil table:@"Sections"];
-        } else {
-            name = @"";
-            title = UCLocalize("NO_SECTION");
-        }
+        _nodefault
     }
 
-    FilteredPackageController *table = [[[FilteredPackageController alloc]
-        initWithDatabase:database_
-        title:title
-        filter:@selector(isVisibleInSection:)
-        with:name
-    ] autorelease];
-
-    [table setDelegate:delegate_];
-
-    [[self navigationController] pushViewController:table animated:YES];
+    return nil;
 }
 
-- (NSString *) title { return UCLocalize("SECTIONS"); }
+- (NSString *) title { return UCLocalize("SETTINGS"); }
 
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
+- (id) initWithDatabase:(Database *)database package:(NSString *)package {
+    if ((self = [super init])) {
         database_ = database;
+        name_ = [package retain];
 
-        [[self navigationItem] setTitle:UCLocalize("SECTIONS")];
+        [[self navigationItem] setTitle:UCLocalize("SETTINGS")];
 
-        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
-        filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
+        table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
+        [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [[self view] addSubview:table_];
 
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:45.0f];
-        [[self view] addSubview:list_];
+        subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
+        [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+        [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged];
 
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
+        ignoredSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
+        [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+        [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged];
+
+        subscribedCell_ = [[UITableViewCell alloc] init];
+        [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
+        [subscribedCell_ setAccessoryView:subscribedSwitch_];
+        [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+
+        ignoredCell_ = [[UITableViewCell alloc] init];
+        [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
+        [ignoredCell_ setAccessoryView:ignoredSwitch_];
+        [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+        // FIXME: Ignored state is not saved.
+        [ignoredCell_ setUserInteractionEnabled:NO];
 
+        [table_ setDataSource:self];
+        [table_ setDelegate:self];
         [self reloadData];
     } return self;
 }
 
 - (void) reloadData {
-    NSArray *packages = [database_ packages];
-
-    [sections_ removeAllObjects];
-    [filtered_ removeAllObjects];
-
-    NSMutableDictionary *sections([NSMutableDictionary dictionaryWithCapacity:32]);
-
-    _trace();
-    for (Package *package in packages) {
-        NSString *name([package section]);
-        NSString *key(name == nil ? @"" : name);
-
-        Section *section;
-
-        _profile(SectionsView$reloadData$Section)
-            section = [sections objectForKey:key];
-            if (section == nil) {
-                _profile(SectionsView$reloadData$Section$Allocate)
-                    section = [[[Section alloc] initWithName:name localize:YES] autorelease];
-                    [sections setObject:section forKey:key];
-                _end
-            }
-        _end
-
-        [section addToCount];
-
-        _profile(SectionsView$reloadData$Filter)
-            if (![package valid] || ![package visible])
-                continue;
-        _end
-
-        [section addToRow];
+    if (package_ != nil)
+        [package_ autorelease];
+    package_ = [database_ packageWithName:name_];
+    if (package_ != nil) {
+        [package_ retain];
+        [subscribedSwitch_ setOn:([package_ subscribed] ? 1 : 0) animated:NO];
+        [ignoredSwitch_ setOn:([package_ ignored] ? 1 : 0) animated:NO];
     }
-    _trace();
 
-    [sections_ addObjectsFromArray:[sections allValues]];
+    [table_ reloadData];
+}
 
-    [sections_ sortUsingSelector:@selector(compareByLocalized:)];
+@end
+/* }}} */
+/* Signature Controller {{{ */
+@interface SignatureController : CYBrowserController {
+    _transient Database *database_;
+    NSString *package_;
+}
 
-    for (Section *section in sections_) {
-        size_t count([section row]);
-        if (count == 0)
-            continue;
+- (id) initWithDatabase:(Database *)database package:(NSString *)package;
 
-        section = [[[Section alloc] initWithName:[section name] localized:[section localized]] autorelease];
-        [section setCount:count];
-        [filtered_ addObject:section];
-    }
+@end
 
-    [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
-        initWithTitle:([sections_ count] == 0 ? nil : UCLocalize("EDIT"))
-        style:UIBarButtonItemStylePlain
-        target:self
-        action:@selector(editButtonClicked)
-    ] autorelease] animated:([[self navigationItem] rightBarButtonItem] != nil)];
+@implementation SignatureController
 
-    [list_ reloadData];
-    _trace();
+- (void) dealloc {
+    [package_ release];
+    [super dealloc];
 }
 
-- (void) resetView {
-    if (editing_)
-        [self editButtonClicked];
+- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+    // XXX: dude!
+    [super webView:view didClearWindowObject:window forFrame:frame];
 }
 
-- (void)editButtonClicked {
-    [self setEditing:!editing_];
+- (id) initWithDatabase:(Database *)database package:(NSString *)package {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+        package_ = [package retain];
+        [self reloadData];
+    } return self;
 }
 
-- (UIView *) accessoryView {
-    return accessory_;
+- (void) reloadData {
+    [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"signature" ofType:@"html"]]];
 }
 
 @end
 /* }}} */
-/* Changes Controller {{{ */
-@interface ChangesController : CYViewController <
-    UITableViewDataSource,
-    UITableViewDelegate
+
+/* Source Cell {{{ */
+@interface SourceCell : CYTableViewCell <
+    ContentDelegate
 > {
-    _transient Database *database_;
-    unsigned era_;
-    CFMutableArrayRef packages_;
-    NSMutableArray *sections_;
-    UITableView *list_;
-    unsigned upgrades_;
-    BOOL hasSentFirstLoad_;
+    UIImage *icon_;
+    NSString *origin_;
+    NSString *label_;
 }
 
-- (id) initWithDatabase:(Database *)database delegate:(id)delegate;
-- (void) reloadData;
+- (void) setSource:(Source *)source;
 
 @end
 
-@implementation ChangesController
-
-- (void) dealloc {
-    [list_ setDelegate:nil];
-    [list_ setDataSource:nil];
+@implementation SourceCell
 
-    CFRelease(packages_);
+- (void) clearSource {
+    [icon_ release];
+    [origin_ release];
+    [label_ release];
 
-    [sections_ release];
-    [list_ release];
-    [super dealloc];
+    icon_ = nil;
+    origin_ = nil;
+    label_ = nil;
 }
 
-- (void) viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    if (!hasSentFirstLoad_) {
-        hasSentFirstLoad_ = YES;
-        [self performSelector:@selector(reloadData) withObject:nil afterDelay:0.0];
-    } else {
-        [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
-    }
-}
+- (void) setSource:(Source *)source {
+    [self clearSource];
 
-- (NSInteger) numberOfSectionsInTableView:(UITableView *)list {
-    NSInteger count([sections_ count]);
-    return count == 0 ? 1 : count;
-}
+    if (icon_ == nil)
+        icon_ = [UIImage applicationImageNamed:[NSString stringWithFormat:@"Sources/%@.png", [source host]]];
+    if (icon_ == nil)
+        icon_ = [UIImage applicationImageNamed:@"unknown.png"];
+    icon_ = [icon_ retain];
 
-- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section {
-    if ([sections_ count] == 0)
-        return nil;
-    return [[sections_ objectAtIndex:section] name];
-}
+    origin_ = [[source name] retain];
+    label_ = [[source uri] retain];
 
-- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section {
-    if ([sections_ count] == 0)
-        return 0;
-    return [[sections_ objectAtIndex:section] count];
+    [content_ setNeedsDisplay];
 }
 
-- (Package *) packageAtIndex:(NSUInteger)index {
-    return (Package *) CFArrayGetValueAtIndex(packages_, index);
+- (void) dealloc {
+    [self clearSource];
+    [super dealloc];
 }
 
-- (Package *) packageAtIndexPath:(NSIndexPath *)path {
-@synchronized (database_) {
-    if ([database_ era] != era_)
-        return nil;
+- (SourceCell *) initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
+    if ((self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) != nil) {
+        UIView *content([self contentView]);
+        CGRect bounds([content bounds]);
 
-    Section *section([sections_ objectAtIndex:[path section]]);
-    NSInteger row([path row]);
-    return [[[self packageAtIndex:([section row] + row)] retain] autorelease];
-} }
-
-- (UITableViewCell *) tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)path {
-    PackageCell *cell((PackageCell *) [table dequeueReusableCellWithIdentifier:@"Package"]);
-    if (cell == nil)
-        cell = [[[PackageCell alloc] init] autorelease];
-    [cell setPackage:[self packageAtIndexPath:path]];
-    return cell;
-}
-
-/*- (CGFloat) tableView:(UITableView *)table heightForRowAtIndexPath:(NSIndexPath *)path {
-    return [PackageCell heightForPackage:[self packageAtIndexPath:path]];
-}*/
-
-- (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path {
-    Package *package([self packageAtIndexPath:path]);
-    PackageController *view([delegate_ packageController]);
-    [view setDelegate:delegate_];
-    [view setPackage:package];
-    [[self navigationController] pushViewController:view animated:YES];
-    return path;
-}
-
-- (void) refreshButtonClicked {
-    [delegate_ beginUpdate];
-    [[self navigationItem] setLeftBarButtonItem:nil animated:YES];
-}
+        content_ = [[ContentView alloc] initWithFrame:bounds];
+        [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [content_ setBackgroundColor:[UIColor whiteColor]];
+        [content addSubview:content_];
 
-- (void) upgradeButtonClicked {
-    [delegate_ distUpgrade];
+        [content_ setDelegate:self];
+        [content_ setOpaque:YES];
+    } return self;
 }
 
-- (NSString *) title { return UCLocalize("CHANGES"); }
+- (void) drawContentRect:(CGRect)rect {
+    bool highlighted(highlighted_);
+    float width(rect.size.width);
 
-- (id) initWithDatabase:(Database *)database delegate:(id)delegate {
-    if ((self = [super init]) != nil) {
-        database_ = database;
-        [[self navigationItem] setTitle:UCLocalize("CHANGES")];
+    if (icon_ != nil)
+        [icon_ drawInRect:CGRectMake(10, 10, 30, 30)];
 
-        packages_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
+    if (highlighted)
+        UISetColor(White_);
 
-        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
+    if (!highlighted)
+        UISetColor(Black_);
+    [origin_ drawAtPoint:CGPointMake(48, 8) forWidth:(width - 80) withFont:Font18Bold_ lineBreakMode:UILineBreakModeTailTruncation];
 
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:73.0f];
-        [[self view] addSubview:list_];
+    if (!highlighted)
+        UISetColor(Blue_);
+    [label_ drawAtPoint:CGPointMake(58, 29) forWidth:(width - 95) withFont:Font12_ lineBreakMode:UILineBreakModeTailTruncation];
+}
 
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
+@end
+/* }}} */
+/* Source Table {{{ */
+@interface SourceController : CYViewController <
+    UITableViewDataSource,
+    UITableViewDelegate
+> {
+    _transient Database *database_;
+    UITableView *list_;
+    NSMutableArray *sources_;
+    int offset_;
 
-        delegate_ = delegate;
-    } return self;
-}
+    NSString *href_;
+    UIProgressHUD *hud_;
+    NSError *error_;
 
-- (void) _reloadPackages:(NSArray *)packages {
-    _trace();
-    for (Package *package in packages)
-        if ([package upgradableAndEssential:YES] || [package visible])
-            CFArrayAppendValue(packages_, package);
+    //NSURLConnection *installer_;
+    NSURLConnection *trivial_;
+    NSURLConnection *trivial_bz2_;
+    NSURLConnection *trivial_gz_;
+    //NSURLConnection *automatic_;
 
-    _trace();
-    [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<SKRadixFunction>(&PackageChangesRadix) withContext:NULL];
-    _trace();
+    BOOL cydia_;
 }
 
-- (void) reloadData {
-    era_ = [database_ era];
-    NSArray *packages = [database_ packages];
+- (id) initWithDatabase:(Database *)database;
 
-    CFArrayRemoveAllValues(packages_);
+- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated;
 
-    [sections_ removeAllObjects];
+@end
 
-#if 1
-    UIProgressHUD *hud([delegate_ addProgressHUD]);
-    [hud setText:UCLocalize("LOADING")];
-    //NSLog(@"HUD:%@::%@", delegate_, hud);
-    [self yieldToSelector:@selector(_reloadPackages:) withObject:packages];
-    [delegate_ removeProgressHUD:hud];
-#else
-    [self _reloadPackages:packages];
-#endif
+@implementation SourceController
 
-    Section *upgradable = [[[Section alloc] initWithName:UCLocalize("AVAILABLE_UPGRADES") localize:NO] autorelease];
-    Section *ignored = nil;
-    Section *section = nil;
-    time_t last = 0;
+- (void) _releaseConnection:(NSURLConnection *)connection {
+    if (connection != nil) {
+        [connection cancel];
+        //[connection setDelegate:nil];
+        [connection release];
+    }
+}
 
-    upgrades_ = 0;
-    bool unseens = false;
+- (void) dealloc {
+    if (href_ != nil)
+        [href_ release];
+    if (hud_ != nil)
+        [hud_ release];
+    if (error_ != nil)
+        [error_ release];
 
-    CFDateFormatterRef formatter(CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle));
+    //[self _releaseConnection:installer_];
+    [self _releaseConnection:trivial_];
+    [self _releaseConnection:trivial_gz_];
+    [self _releaseConnection:trivial_bz2_];
+    //[self _releaseConnection:automatic_];
 
-    for (size_t offset = 0, count = CFArrayGetCount(packages_); offset != count; ++offset) {
-        Package *package = [self packageAtIndex:offset];
+    [sources_ release];
+    [list_ release];
+    [super dealloc];
+}
 
-        BOOL uae = [package upgradableAndEssential:YES];
+- (void) viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
+}
 
-        if (!uae) {
-            unseens = true;
-            time_t seen([package seen]);
+- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
+    return offset_ == 0 ? 1 : 2;
+}
 
-            if (section == nil || last != seen) {
-                last = seen;
+- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+    switch (section + (offset_ == 0 ? 1 : 0)) {
+        case 0: return UCLocalize("ENTERED_BY_USER");
+        case 1: return UCLocalize("INSTALLED_BY_PACKAGE");
 
-                NSString *name;
-                name = (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) [NSDate dateWithTimeIntervalSince1970:seen]);
-                [name autorelease];
+        _nodefault
+    }
+}
 
-                _profile(ChangesController$reloadData$Allocate)
-                    name = [NSString stringWithFormat:UCLocalize("NEW_AT"), name];
-                    section = [[[Section alloc] initWithName:name row:offset localize:NO] autorelease];
-                    [sections_ addObject:section];
-                _end
-            }
+- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    int count = [sources_ count];
+    switch (section) {
+        case 0: return (offset_ == 0 ? count : offset_);
+        case 1: return count - offset_;
 
-            [section addToCount];
-        } else if ([package ignored]) {
-            if (ignored == nil) {
-                ignored = [[[Section alloc] initWithName:UCLocalize("IGNORED_UPGRADES") row:offset localize:NO] autorelease];
-            }
-            [ignored addToCount];
-        } else {
-            ++upgrades_;
-            [upgradable addToCount];
-        }
+        _nodefault
     }
-    _trace();
+}
 
-    CFRelease(formatter);
+- (Source *) sourceAtIndexPath:(NSIndexPath *)indexPath {
+    unsigned idx = 0;
+    switch (indexPath.section) {
+        case 0: idx = indexPath.row; break;
+        case 1: idx = indexPath.row + offset_; break;
 
-    if (unseens) {
-        Section *last = [sections_ lastObject];
-        size_t count = [last count];
-        CFArrayReplaceValues(packages_, CFRangeMake(CFArrayGetCount(packages_) - count, count), NULL, 0);
-        [sections_ removeLastObject];
+        _nodefault
     }
+    return [sources_ objectAtIndex:idx];
+}
 
-    if ([ignored count] != 0)
-        [sections_ insertObject:ignored atIndex:0];
-    if (upgrades_ != 0)
-        [sections_ insertObject:upgradable atIndex:0];
-
-    [list_ reloadData];
+- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    static NSString *cellIdentifier = @"SourceCell";
 
-    if (upgrades_ > 0)
-        [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:[NSString stringWithFormat:UCLocalize("PARENTHETICAL"), UCLocalize("UPGRADE"), [NSString stringWithFormat:@"%u", upgrades_]]
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(upgradeButtonClicked)
-        ] autorelease]];
+    SourceCell *cell = (SourceCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
+    if(cell == nil) cell = [[[SourceCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellIdentifier] autorelease];
+    [cell setSource:[self sourceAtIndexPath:indexPath]];
 
-    if (![delegate_ updating])
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("REFRESH")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(refreshButtonClicked)
-        ] autorelease]];
+    return cell;
 }
 
-@end
-/* }}} */
-/* Search Controller {{{ */
-@interface SearchController : FilteredPackageController <
-    UISearchBarDelegate
-> {
-    UISearchBar *search_;
+- (UITableViewCellAccessoryType) tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath {
+    return UITableViewCellAccessoryDisclosureIndicator;
 }
 
-- (id) initWithDatabase:(Database *)database;
-- (void) reloadData;
+- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    Source *source = [self sourceAtIndexPath:indexPath];
 
-@end
+    FilteredPackageController *packages = [[[FilteredPackageController alloc]
+        initWithDatabase:database_
+        title:[source label]
+        filter:@selector(isVisibleInSource:)
+        with:source
+    ] autorelease];
 
-@implementation SearchController
+    [packages setDelegate:delegate_];
 
-- (void) dealloc {
-    [search_ release];
-    [super dealloc];
+    [[self navigationController] pushViewController:packages animated:YES];
 }
 
-- (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar {
-    [packages_ setObject:[search_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)];
-    [search_ resignFirstResponder];
-    [self reloadData];
+- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
+    Source *source = [self sourceAtIndexPath:indexPath];
+    return [source record] != nil;
 }
 
-- (void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)text {
-    [packages_ setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)];
-    [self reloadData];
+- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
+    Source *source = [self sourceAtIndexPath:indexPath];
+    [Sources_ removeObjectForKey:[source key]];
+    [delegate_ syncData];
 }
 
-- (NSString *) title { return nil; }
+- (void) complete {
+    [Sources_ setObject:[NSDictionary dictionaryWithObjectsAndKeys:
+        @"deb", @"Type",
+        href_, @"URI",
+        @"./", @"Distribution",
+    nil] forKey:[NSString stringWithFormat:@"deb:%@:./", href_]];
 
-- (id) initWithDatabase:(Database *)database {
-    return [super initWithDatabase:database title:UCLocalize("SEARCH") filter:@selector(isUnfilteredAndSearchedForBy:) with:nil];
+    [delegate_ syncData];
 }
 
-- (void)viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    if (!search_) {
-        search_ = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)];
-        [search_ layoutSubviews];
-        [search_ setPlaceholder:UCLocalize("SEARCH_EX")];
+- (NSString *) getWarning {
+    NSString *href(href_);
+    NSRange colon([href rangeOfString:@"://"]);
+    if (colon.location != NSNotFound)
+        href = [href substringFromIndex:(colon.location + 3)];
+    href = [href stringByAddingPercentEscapes];
+    href = [CydiaURL(@"api/repotag/") stringByAppendingString:href];
+    href = [href stringByCachingURLWithCurrentCDN];
 
-        UITextField *textField;
-        if ([search_ respondsToSelector:@selector(searchField)])
-            textField = [search_ searchField];
-        else
-            textField = MSHookIvar<UITextField *>(search_, "_searchField");
+    NSURL *url([NSURL URLWithString:href]);
 
-        [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
-        [search_ setDelegate:self];
-        [textField setEnablesReturnKeyAutomatically:NO];
-        [[self navigationItem] setTitleView:textField];
-    }
-}
+    NSStringEncoding encoding;
+    NSError *error(nil);
 
-- (void) _reloadData {
+    if (NSString *warning = [NSString stringWithContentsOfURL:url usedEncoding:&encoding error:&error])
+        return [warning length] == 0 ? nil : warning;
+    return nil;
 }
 
-- (void) reloadData {
-    _profile(SearchController$reloadData)
-        [packages_ reloadData];
-    _end
-    PrintTimes();
-    [packages_ resetCursor];
-}
+- (void) _endConnection:(NSURLConnection *)connection {
+    // XXX: the memory management in this method is horribly awkward
 
-- (void) didSelectPackage:(Package *)package {
-    [search_ resignFirstResponder];
-    [super didSelectPackage:package];
-}
+    NSURLConnection **field = NULL;
+    if (connection == trivial_)
+        field = &trivial_;
+    else if (connection == trivial_bz2_)
+        field = &trivial_bz2_;
+    else if (connection == trivial_gz_)
+        field = &trivial_gz_;
+    _assert(field != NULL);
+    [connection release];
+    *field = nil;
 
-@end
-/* }}} */
-/* Settings Controller {{{ */
-@interface CYPackageSettingsController : CYViewController <
-    UITableViewDataSource,
-    UITableViewDelegate
-> {
-    _transient Database *database_;
-    NSString *name_;
-    Package *package_;
-    UITableView *table_;
-    UISwitch *subscribedSwitch_;
-    UISwitch *ignoredSwitch_;
-    UITableViewCell *subscribedCell_;
-    UITableViewCell *ignoredCell_;
-}
+    if (
+        trivial_ == nil &&
+        trivial_bz2_ == nil &&
+        trivial_gz_ == nil
+    ) {
+        bool defer(false);
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package;
+        if (cydia_) {
+            if (NSString *warning = [self yieldToSelector:@selector(getWarning)]) {
+                defer = true;
 
-@end
+                UIAlertView *alert = [[[UIAlertView alloc]
+                    initWithTitle:UCLocalize("SOURCE_WARNING")
+                    message:warning
+                    delegate:self
+                    cancelButtonTitle:UCLocalize("CANCEL")
+                    otherButtonTitles:UCLocalize("ADD_ANYWAY"), nil
+                ] autorelease];
 
-@implementation CYPackageSettingsController
+                [alert setContext:@"warning"];
+                [alert setNumberOfRows:1];
+                [alert show];
+            } else
+                [self complete];
+        } else if (error_ != nil) {
+            UIAlertView *alert = [[[UIAlertView alloc]
+                initWithTitle:UCLocalize("VERIFICATION_ERROR")
+                message:[error_ localizedDescription]
+                delegate:self
+                cancelButtonTitle:UCLocalize("OK")
+                otherButtonTitles:nil
+            ] autorelease];
 
-- (void) dealloc {
-    [name_ release];
-    if (package_ != nil)
-        [package_ release];
-    [table_ release];
-    [subscribedSwitch_ release];
-    [ignoredSwitch_ release];
-    [subscribedCell_ release];
-    [ignoredCell_ release];
+            [alert setContext:@"urlerror"];
+            [alert show];
+        } else {
+            UIAlertView *alert = [[[UIAlertView alloc]
+                initWithTitle:UCLocalize("NOT_REPOSITORY")
+                message:UCLocalize("NOT_REPOSITORY_EX")
+                delegate:self
+                cancelButtonTitle:UCLocalize("OK")
+                otherButtonTitles:nil
+            ] autorelease];
+
+            [alert setContext:@"trivial"];
+            [alert show];
+        }
+
+        [delegate_ setStatusBarShowsProgress:NO];
+        [delegate_ removeProgressHUD:hud_];
+
+        [hud_ autorelease];
+        hud_ = nil;
+
+        if (!defer) {
+            [href_ release];
+            href_ = nil;
+        }
+
+        if (error_ != nil) {
+            [error_ release];
+            error_ = nil;
+        }
+    }
+}
+
+- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
+    switch ([response statusCode]) {
+        case 200:
+            cydia_ = YES;
+    }
+}
+
+- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
+    lprintf("connection:\"%s\" didFailWithError:\"%s\"", [href_ UTF8String], [[error localizedDescription] UTF8String]);
+    if (error_ != nil)
+        error_ = [error retain];
+    [self _endConnection:connection];
+}
 
-    [super dealloc];
+- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
+    [self _endConnection:connection];
 }
 
-- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
-    if (package_ == nil)
-        return 0;
+- (NSString *) title { return UCLocalize("SOURCES"); }
 
-    return 1;
-}
+- (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method {
+    NSMutableURLRequest *request = [NSMutableURLRequest
+        requestWithURL:[NSURL URLWithString:href]
+        cachePolicy:NSURLRequestUseProtocolCachePolicy
+        timeoutInterval:120.0
+    ];
 
-- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    if (package_ == nil)
-        return 0;
+    [request setHTTPMethod:method];
 
-    return 2;
-}
+    if (Machine_ != NULL)
+        [request setValue:[NSString stringWithUTF8String:Machine_] forHTTPHeaderField:@"X-Machine"];
+    if (UniqueID_ != nil)
+        [request setValue:UniqueID_ forHTTPHeaderField:@"X-Unique-ID"];
+    if (Role_ != nil)
+        [request setValue:Role_ forHTTPHeaderField:@"X-Role"];
 
-- (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
-    return UCLocalize("SHOW_ALL_CHANGES_EX");
+    return [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
 }
 
-- (void) onSubscribed:(id)control {
-    bool value([control isOn]);
-    if (package_ == nil)
-        return;
-    if ([package_ setSubscribed:value])
-        [delegate_ updateData];
-}
+- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
+    NSString *context([alert context]);
 
-- (void) onIgnored:(id)control {
-    // TODO: set Held state - possibly call out to dpkg, etc.
-}
+    if ([context isEqualToString:@"source"]) {
+        switch (button) {
+            case 1: {
+                NSString *href = [[alert textField] text];
 
-- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-    if (package_ == nil)
-        return nil;
+                //installer_ = [[self _requestHRef:href method:@"GET"] retain];
 
-    switch ([indexPath row]) {
-        case 0: return subscribedCell_;
-        case 1: return ignoredCell_;
+                if (![href hasSuffix:@"/"])
+                    href_ = [href stringByAppendingString:@"/"];
+                else
+                    href_ = href;
+                href_ = [href_ retain];
 
-        _nodefault
-    }
+                trivial_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages"] method:@"HEAD"] retain];
+                trivial_bz2_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.bz2"] method:@"HEAD"] retain];
+                trivial_gz_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.gz"] method:@"HEAD"] retain];
+                //trivial_bz2_ = [[self _requestHRef:[href stringByAppendingString:@"dists/Release"] method:@"HEAD"] retain];
 
-    return nil;
-}
+                cydia_ = false;
 
-- (NSString *) title { return UCLocalize("SETTINGS"); }
+                // XXX: this is stupid
+                hud_ = [[delegate_ addProgressHUD] retain];
+                [hud_ setText:UCLocalize("VERIFYING_URL")];
+            } break;
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package {
-    if ((self = [super init])) {
-        database_ = database;
-        name_ = [package retain];
+            case 0:
+            break;
 
-        [[self navigationItem] setTitle:UCLocalize("SETTINGS")];
+            _nodefault
+        }
 
-        table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
-        [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:table_];
+        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+    } else if ([context isEqualToString:@"trivial"])
+        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+    else if ([context isEqualToString:@"urlerror"])
+        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+    else if ([context isEqualToString:@"warning"]) {
+        switch (button) {
+            case 1:
+                [self complete];
+            break;
 
-        subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
-        [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged];
+            case 0:
+            break;
 
-        ignoredSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
-        [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged];
+            _nodefault
+        }
 
-        subscribedCell_ = [[UITableViewCell alloc] init];
-        [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
-        [subscribedCell_ setAccessoryView:subscribedSwitch_];
-        [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+        [href_ release];
+        href_ = nil;
 
-        ignoredCell_ = [[UITableViewCell alloc] init];
-        [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
-        [ignoredCell_ setAccessoryView:ignoredSwitch_];
-        [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+    }
+}
+
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        [[self navigationItem] setTitle:UCLocalize("SOURCES")];
+        [self updateButtonsForEditingStatus:NO animated:NO];
+
+        database_ = database;
+        sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
+
+        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
+        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [list_ setRowHeight:56];
+        [[self view] addSubview:list_];
+
+        [list_ setDataSource:self];
+        [list_ setDelegate:self];
 
-        [table_ setDataSource:self];
-        [table_ setDelegate:self];
         [self reloadData];
     } return self;
 }
 
 - (void) reloadData {
-    if (package_ != nil)
-        [package_ autorelease];
-    package_ = [database_ packageWithName:name_];
-    if (package_ != nil) {
-        [package_ retain];
-        [subscribedSwitch_ setOn:([package_ subscribed] ? 1 : 0) animated:NO];
-        [ignoredSwitch_ setOn:([package_ ignored] ? 1 : 0) animated:NO];
+    pkgSourceList list;
+    if (!list.ReadMainList())
+        return;
+
+    [sources_ removeAllObjects];
+    [sources_ addObjectsFromArray:[database_ sources]];
+    _trace();
+    [sources_ sortUsingSelector:@selector(compareByNameAndType:)];
+    _trace();
+
+    int count([sources_ count]);
+    offset_ = 0;
+    for (int i = 0; i != count; i++) {
+        if ([[sources_ objectAtIndex:i] record] == nil)
+            break;
+        offset_++;
     }
 
-    [table_ reloadData];
+    [list_ setEditing:NO];
+    [self updateButtonsForEditingStatus:NO animated:NO];
+    [list_ reloadData];
 }
 
-@end
-/* }}} */
-/* Signature Controller {{{ */
-@interface SignatureController : CYBrowserController {
-    _transient Database *database_;
-    NSString *package_;
-}
+- (void) addButtonClicked {
+    /*[book_ pushPage:[[[AddSourceController alloc]
+        initWithBook:book_
+        database:database_
+    ] autorelease]];*/
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package;
+    UIAlertView *alert = [[[UIAlertView alloc]
+        initWithTitle:UCLocalize("ENTER_APT_URL")
+        message:nil
+        delegate:self
+        cancelButtonTitle:UCLocalize("CANCEL")
+        otherButtonTitles:UCLocalize("ADD_SOURCE"), nil
+    ] autorelease];
 
-@end
+    [alert setContext:@"source"];
+    [alert setTransform:CGAffineTransformTranslate([alert transform], 0.0, 100.0)];
 
-@implementation SignatureController
+    [alert setNumberOfRows:1];
+    [alert addTextFieldWithValue:@"http://" label:@""];
 
-- (void) dealloc {
-    [package_ release];
-    [super dealloc];
+    UITextInputTraits *traits = [[alert textField] textInputTraits];
+    [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
+    [traits setAutocorrectionType:UITextAutocorrectionTypeNo];
+    [traits setKeyboardType:UIKeyboardTypeURL];
+    // XXX: UIReturnKeyDone
+    [traits setReturnKeyType:UIReturnKeyNext];
+
+    [alert show];
 }
 
-- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
-    // XXX: dude!
-    [super webView:view didClearWindowObject:window forFrame:frame];
+- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated {
+    [[self navigationItem] setLeftBarButtonItem:(editing ? [[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("ADD")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(addButtonClicked)
+    ] autorelease] : [[self navigationItem] backBarButtonItem]) animated:animated];
+
+    [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
+        initWithTitle:(editing ? UCLocalize("DONE") : UCLocalize("EDIT"))
+        style:(editing ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
+        target:self
+        action:@selector(editButtonClicked)
+    ] autorelease] animated:animated];
+
+    if (IsWildcat_ && !editing)
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("SETTINGS")
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(settingsButtonClicked)
+        ] autorelease]];
 }
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package {
-    if ((self = [super init]) != nil) {
-        database_ = database;
-        package_ = [package retain];
-        [self reloadData];
-    } return self;
+- (void) settingsButtonClicked {
+    [delegate_ showSettings];
 }
 
-- (void) reloadData {
-    [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"signature" ofType:@"html"]]];
+- (void) editButtonClicked {
+    [list_ setEditing:![list_ isEditing] animated:YES];
+
+    [self updateButtonsForEditingStatus:[list_ isEditing] animated:YES];
 }
 
 @end
@@ -8039,10 +8198,6 @@ static _finline void _setHomePage(Cydia *self) {
     return [tabbar_ updating];
 }
 
-- (UIView *) rotatingContentViewForWindow:(UIWindow *)window {
-    return window_;
-}
-
 - (void) _loaded {
     if ([broken_ count] != 0) {
         int count = [broken_ count];
@@ -8289,10 +8444,6 @@ static _finline void _setHomePage(Cydia *self) {
         _error->Discard();
 }
 
-- (CGRect) popUpBounds {
-    return [[tabbar_ view] bounds];
-}
-
 - (bool) perform {
     if (![database_ prepare])
         return false;
@@ -8702,8 +8853,6 @@ static _finline void _setHomePage(Cydia *self) {
     CYViewController *page = nil;
     int tag = 0;
 
-    NSLog(@"open url: %@", url);
-
     if ((page = [self pageForURL:url hasTag:&tag])) {
         [self setPage:page];
         tag_ = tag;
@@ -8715,7 +8864,7 @@ static _finline void _setHomePage(Cydia *self) {
 
 - (void) applicationOpenURL:(NSURL *)url {
     [super applicationOpenURL:url];
-    NSLog(@"first: %@", url);
+
     if (!loaded_) starturl_ = [url retain];
     else [self openCydiaURL:url];
 }
@@ -8790,10 +8939,27 @@ static _finline void _setHomePage(Cydia *self) {
     [tabbar_ setViewControllers:controllers];
 }
 
+- (void)showFakeTabBarInView:(UIView *)view {
+    static UITabBar *fake = [[UITabBar alloc] initWithFrame:CGRectMake(0, 0, 0, 49.0f)];
+    if (view != nil) {
+        CGRect frame = [fake frame];
+        frame.origin.y = [view frame].size.height - frame.size.height;
+        frame.size.width = [view frame].size.width;
+        [fake setFrame:frame];
+        [fake setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin];
+        [view addSubview:fake];
+    } else {
+        [fake removeFromSuperview];
+    }
+}
+
 - (void) applicationDidFinishLaunching:(id)unused {
 _trace();
     CydiaApp = self;
 
+    if ([self respondsToSelector:@selector(setApplicationSupportsShakeToEdit:)])
+        [self setApplicationSupportsShakeToEdit:NO];
+
     [NSURLCache setSharedURLCache:[[[SDURLCache alloc]
         initWithMemoryCapacity:524288
         diskCapacity:10485760
@@ -8815,9 +8981,7 @@ _trace();
     essential_ = [[NSMutableArray alloc] initWithCapacity:4];
     broken_ = [[NSMutableArray alloc] initWithCapacity:4];
 
-    UIScreen *screen([UIScreen mainScreen]);
-
-    window_ = [[UIWindow alloc] initWithFrame:[screen bounds]];
+    window_ = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
     [window_ orderFront:self];
     [window_ makeKey:self];
     [window_ setHidden:NO];
@@ -8850,6 +9014,7 @@ _trace();
 
     // Show pinstripes while loading data.
     [[tabbar_ view] setBackgroundColor:[UIColor pinStripeColor]];
+    [self showFakeTabBarInView:[tabbar_ tabBar]];
 
     [self performSelector:@selector(loadData) withObject:nil afterDelay:0];
 _trace();
@@ -8862,47 +9027,14 @@ _trace();
         return;
     }
 
+    CGRect fixframe = [[tabbar_ view] frame];
+    if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]))
+        fixframe.size = CGSizeMake(fixframe.size.height, fixframe.size.width);
+    CYLoadingIndicator *loading = [[[CYLoadingIndicator alloc] initWithFrame:fixframe] autorelease];
+    [loading setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [[tabbar_ view] addSubview:loading];
     [window_ setUserInteractionEnabled:NO];
 
-    UIView *container = [[[UIView alloc] init] autorelease];
-    [container setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin];
-
-    UIActivityIndicatorView *spinner = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray] autorelease];
-    [spinner startAnimating];
-    [container addSubview:spinner];
-
-    UILabel *label = [[[UILabel alloc] init] autorelease];
-    [label setFont:[UIFont boldSystemFontOfSize:15.0f]];
-    [label setBackgroundColor:[UIColor clearColor]];
-    [label setTextColor:[UIColor blackColor]];
-    [label setShadowColor:[UIColor whiteColor]];
-    [label setShadowOffset:CGSizeMake(0, 1)];
-    [label setText:[NSString stringWithFormat:Elision_, UCLocalize("LOADING"), nil]];
-    [container addSubview:label];
-
-    CGSize viewsize = [[tabbar_ view] frame].size;
-    CGSize spinnersize = [spinner bounds].size;
-    CGSize textsize = [[label text] sizeWithFont:[label font]];
-    float bothwidth = spinnersize.width + textsize.width + 5.0f;
-
-    CGRect containrect = {
-        CGPointMake(floorf((viewsize.width / 2) - (bothwidth / 2)), floorf((viewsize.height / 2) - (spinnersize.height / 2))),
-        CGSizeMake(bothwidth, spinnersize.height)
-    };
-    CGRect textrect = {
-        CGPointMake(spinnersize.width + 5.0f, floorf((spinnersize.height / 2) - (textsize.height / 2))),
-        textsize
-    };
-    CGRect spinrect = {
-        CGPointZero,
-        spinnersize
-    };
-
-    [container setFrame:containrect];
-    [spinner setFrame:spinrect];
-    [label setFrame:textrect];
-    [[tabbar_ view] addSubview:container];
-
     [self reloadData];
     PrintTimes();
 
@@ -8912,6 +9044,8 @@ _trace();
         _setHomePage(self);
     }
 
+    [self showFakeTabBarInView:nil];
+
     [starturl_ release];
     starturl_ = nil;
 
@@ -8919,7 +9053,7 @@ _trace();
 
     // XXX: does this actually slow anything down?
     [[tabbar_ view] setBackgroundColor:[UIColor clearColor]];
-    [container removeFromSuperview];
+    [loading removeFromSuperview];
 }
 
 - (void) showActionSheet:(UIActionSheet *)sheet fromItem:(UIBarButtonItem *)item {