]> git.saurik.com Git - cydia.git/blobdiff - MobileCydia.mm
Remove extraneous [[self navigationItem] setTitle:] calls.
[cydia.git] / MobileCydia.mm
index 7a7a6a7f8326fd5f32e07342eb9caba0c7855aea..2b6bd1e011ab114b0b3a4bca47b356082b8945c6 100644 (file)
@@ -1,5 +1,5 @@
 /* Cydia - iPhone UIKit Front-End for Debian APT
- * Copyright (C) 2008-2010  Jay Freeman (saurik)
+ * Copyright (C) 2008-2011  Jay Freeman (saurik)
 */
 
 /* Modified BSD License {{{ */
@@ -217,14 +217,6 @@ union SplitHash {
 
 static const NSUInteger UIViewAutoresizingFlexibleBoth(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
 
-void NSLogPoint(const char *fix, const CGPoint &point) {
-    NSLog(@"%s(%g,%g)", fix, point.x, point.y);
-}
-
-void NSLogRect(const char *fix, const CGRect &rect) {
-    NSLog(@"%s(%g,%g)+(%g,%g)", fix, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
-}
-
 static _finline NSString *CydiaURL(NSString *path) {
     char page[25];
     page[0] = 'h'; page[1] = 't'; page[2] = 't'; page[3] = 'p'; page[4] = ':';
@@ -309,15 +301,15 @@ static _finline void UpdateExternalStatus(uint64_t newStatus) {
 @end
 /* }}} */
 
-/* Cydia Action Sheet {{{ */
-@interface CYActionSheet : UIAlertView {
+/* Cydia Alert View {{{ */
+@interface CYAlertView : UIAlertView {
     unsigned button_;
 }
 
 - (int) yieldToPopupAlertAnimated:(BOOL)animated;
 @end
 
-@implementation CYActionSheet
+@implementation CYAlertView
 
 - (id) initWithTitle:(NSString *)title buttons:(NSArray *)buttons defaultButtonIndex:(int)index {
     if ((self = [super init])) {
@@ -363,32 +355,6 @@ static const NSStringCompareOptions MatchCompareOptions_ = NSLiteralSearch | NSC
 static const NSStringCompareOptions LaxCompareOptions_ = NSNumericSearch | NSDiacriticInsensitiveSearch | NSWidthInsensitiveSearch | NSCaseInsensitiveSearch;
 static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareCaseInsensitive | kCFCompareNonliteral | kCFCompareLocalized | kCFCompareNumerically | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
 
-/* Information Dictionaries {{{ */
-@interface NSMutableArray (Cydia)
-- (void) addInfoDictionary:(NSDictionary *)info;
-@end
-
-@implementation NSMutableArray (Cydia)
-
-- (void) addInfoDictionary:(NSDictionary *)info {
-    [self addObject:info];
-}
-
-@end
-
-@interface NSMutableDictionary (Cydia)
-- (void) addInfoDictionary:(NSDictionary *)info;
-@end
-
-@implementation NSMutableDictionary (Cydia)
-
-- (void) addInfoDictionary:(NSDictionary *)info {
-    [self setObject:info forKey:[info objectForKey:@"CFBundleIdentifier"]];
-}
-
-@end
-/* }}} */
-
 #define lprintf(args...) fprintf(stderr, args)
 
 #define ForRelease 1
@@ -400,8 +366,8 @@ static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareCaseInsensitive |
 #define TrackResize (0 && !ForRelease)
 #define ManualRefresh (1 && !ForRelease)
 #define ShowInternals (0 && !ForRelease)
-#define IgnoreInstall (0 && !ForRelease)
 #define AlwaysReload (0 && !ForRelease)
+#define TryIndexedCollation (0 && !ForRelease)
 
 #if !TraceLogging
 #undef _trace
@@ -1013,6 +979,8 @@ class CYColor {
 /* Random Global Variables {{{ */
 static const int PulseInterval_ = 50000;
 
+static const NSString *UI_;
+
 static int Finish_;
 static NSArray *Finishes_;
 
@@ -1071,7 +1039,8 @@ static _transient NSMutableDictionary *Sources_;
 static bool Changed_;
 static time_t now_;
 
-static bool IsWildcat_;
+bool IsWildcat_;
+static CGFloat ScreenScale_;
 /* }}} */
 
 /* Display Helpers {{{ */
@@ -1184,7 +1153,6 @@ bool isSectionVisible(NSString *section) {
 @protocol CydiaDelegate
 - (void) retainNetworkActivityIndicator;
 - (void) releaseNetworkActivityIndicator;
-- (void) setPackageController:(CYPackageController *)view;
 - (void) clearPackage:(Package *)package;
 - (void) installPackage:(Package *)package;
 - (void) installPackages:(NSArray *)packages;
@@ -1461,7 +1429,7 @@ struct MetaValue :
 static Cytore::File<MetaValue> MetaFile_;
 // }}}
 // Cytore Helper Functions {{{
-static PackageValue *PackageFind(const char *name, size_t length) {
+static PackageValue *PackageFind(const char *name, size_t length, bool *fail = NULL) {
     SplitHash nhash = { hashlittle(name, length) };
 
     PackageValue *metadata;
@@ -1471,6 +1439,14 @@ static PackageValue *PackageFind(const char *name, size_t length) {
         *offset = MetaFile_.New<PackageValue>(length + 1);
         metadata = &MetaFile_.Get(*offset);
 
+        if (metadata == NULL) {
+            if (fail != NULL)
+                *fail = true;
+
+            metadata = new PackageValue();
+            memset(metadata, 0, sizeof(*metadata));
+        }
+
         memcpy(metadata->name_, name, length + 1);
         metadata->nhash_ = nhash.u16[1];
     } else {
@@ -1486,13 +1462,15 @@ static PackageValue *PackageFind(const char *name, size_t length) {
 }
 
 static void PackageImport(const void *key, const void *value, void *context) {
+    bool &fail(*reinterpret_cast<bool *>(context));
+
     char buffer[1024];
     if (!CFStringGetCString((CFStringRef) key, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
         NSLog(@"failed to import package %@", key);
         return;
     }
 
-    PackageValue *metadata(PackageFind(buffer, strlen(buffer)));
+    PackageValue *metadata(PackageFind(buffer, strlen(buffer), &fail));
     NSDictionary *package((NSDictionary *) value);
 
     if (NSNumber *subscribed = [package objectForKey:@"IsSubscribed"])
@@ -2735,6 +2713,8 @@ struct PackageNameOrdering :
     if (range.location != NSNotFound)
         return YES;
 
+    [self parse];
+
     range = [[self shortDescription] rangeOfString:text options:MatchCompareOptions_];
     if (range.location != NSNotFound)
         return YES;
@@ -3375,7 +3355,7 @@ static NSString *Warning_;
     NSString *title(UCLocalize("DATABASE"));
 
     _trace();
-    if (!cache_.Open(progress_, true)) { pop:
+    while (!cache_.Open(progress_, true)) { pop:
         std::string error;
         bool warning(!_error->PopMessage(error));
         lprintf("cache_.Open():[%s]\n", error.c_str());
@@ -3385,15 +3365,17 @@ static NSString *Warning_;
         else if (error == "The package lists or status file could not be parsed or opened.")
             [delegate_ repairWithSelector:@selector(update)];
         // else if (error == "Could not open lock file /var/lib/dpkg/lock - open (13 Permission denied)")
-        // else if (error == "Could not get lock /var/lib/dpkg/lock - open (35 Resource temporarily unavailable)")
+        else if (error == "Could not get lock /var/lib/dpkg/lock - open (35 Resource temporarily unavailable)")
+            [delegate_ _setProgressError:[NSString stringWithUTF8String:error.c_str()] withTitle:[NSString stringWithFormat:Colon_, Error_, title]];
         // else if (error == "The list of sources could not be read.")
-        else
+        else {
             [delegate_ _setProgressError:[NSString stringWithUTF8String:error.c_str()] withTitle:[NSString stringWithFormat:Colon_, warning ? Warning_ : Error_, title]];
+            return;
+        }
 
         if (warning)
             goto pop;
         _error->Discard();
-        return;
     }
     _trace();
 
@@ -3951,7 +3933,7 @@ static NSString *Warning_;
     id values[count];
     for (unsigned i(0); i != count; ++i)
         values[i] = [arguments objectAtIndex:i];
-    return [[[NSString alloc] initWithFormat:format arguments:*(reinterpret_cast<va_list *>(&values))] autorelease];
+    return [[[NSString alloc] initWithFormat:format arguments:reinterpret_cast<va_list>(values)] autorelease];
 }
 
 - (NSString *) localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)table {
@@ -4030,7 +4012,11 @@ static NSString *Warning_;
 @end
 /* }}} */
 /* Emulated Loading Controller {{{ */
-@interface CYEmulatedLoadingController : UIViewController {
+@interface CYEmulatedLoadingController : CYViewController <
+    ProgressDelegate,
+    ConfigurationDelegate
+> {
+    _transient Database *database_;
     CYLoadingIndicator *indicator_;
     UITabBar *tabbar_;
     UINavigationBar *navbar_;
@@ -4039,31 +4025,74 @@ static NSString *Warning_;
 
 @implementation CYEmulatedLoadingController
 
-- (CYEmulatedLoadingController *) init {
+- (void) dealloc {
+    [self releaseSubviews];
+    [database_ setDelegate:nil];
+
+    [super dealloc];
+}
+
+- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
+    CYAlertView *sheet([[[CYAlertView alloc]
+        initWithTitle:title
+        buttons:[NSArray arrayWithObjects:UCLocalize("OKAY"), nil]
+        defaultButtonIndex:0
+    ] autorelease]);
+
+    [sheet setMessage:error];
+    [sheet yieldToPopupAlertAnimated:YES];
+    [sheet dismiss];
+}
+
+- (void) setProgressTitle:(NSString *)title { }
+- (void) setProgressPercent:(float)percent { }
+- (void) startProgress { }
+- (void) addProgressOutput:(NSString *)output { }
+- (bool) isCancelling:(size_t)received { return NO; }
+- (void) setConfigurationData:(NSString *)data { }
+
+- (void) repairWithSelector:(SEL)selector {
+    [[indicator_ label] performSelectorOnMainThread:@selector(setText:) withObject:[NSString stringWithFormat:Elision_, UCLocalize("REPAIRING"), nil] waitUntilDone:YES];
+    [database_ performSelector:selector];
+    sleep(10);
+    [[indicator_ label] performSelectorOnMainThread:@selector(setText:) withObject:[NSString stringWithFormat:Elision_, UCLocalize("LOADING"), nil] waitUntilDone:YES];
+}
+
+- (id) initWithDatabase:(Database *)database {
     if ((self = [super init])) {
-        [[self view] setBackgroundColor:[UIColor pinStripeColor]];
-
-        indicator_ = [[CYLoadingIndicator alloc] initWithFrame:[[self view] bounds]];
-        [indicator_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:indicator_];
-        [indicator_ release];
-
-        tabbar_ = [[UITabBar alloc] initWithFrame:CGRectMake(0, 0, 0, 49.0f)];
-        [tabbar_ setFrame:CGRectMake(0.0f, [[self view] bounds].size.height - [tabbar_ bounds].size.height, [[self view] bounds].size.width, [tabbar_ bounds].size.height)];
-        [tabbar_ setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth];
-        [[self view] addSubview:tabbar_];
-        [tabbar_ release];
-
-        navbar_ = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 0, 44.0f)];
-        [navbar_ setFrame:CGRectMake(0.0f, 0.0f, [[self view] bounds].size.width, [navbar_ bounds].size.height)];
-        [navbar_ setAutoresizingMask:UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth];
-        [[self view] addSubview:navbar_];
-        [navbar_ release];
+        database_ = database;
+        [database_ setDelegate:self];
     } return self;
 }
 
-- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
-    return (IsWildcat_ || orientation == UIInterfaceOrientationPortrait);
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+    [[self view] setBackgroundColor:[UIColor pinStripeColor]];
+
+    indicator_ = [[CYLoadingIndicator alloc] initWithFrame:[[self view] bounds]];
+    [indicator_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [[self view] addSubview:indicator_];
+
+    tabbar_ = [[UITabBar alloc] initWithFrame:CGRectMake(0, 0, 0, 49.0f)];
+    [tabbar_ setFrame:CGRectMake(0.0f, [[self view] bounds].size.height - [tabbar_ bounds].size.height, [[self view] bounds].size.width, [tabbar_ bounds].size.height)];
+    [tabbar_ setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth];
+    [[self view] addSubview:tabbar_];
+
+    navbar_ = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 0, 44.0f)];
+    [navbar_ setFrame:CGRectMake(0.0f, 0.0f, [[self view] bounds].size.width, [navbar_ bounds].size.height)];
+    [navbar_ setAutoresizingMask:UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth];
+    [[self view] addSubview:navbar_];
+}
+
+- (void) releaseSubviews {
+    [indicator_ release];
+    indicator_ = nil;
+
+    [tabbar_ release];
+    tabbar_ = nil;
+
+    [navbar_ release];
+    navbar_ = nil;
 }
 
 @end
@@ -4083,6 +4112,10 @@ static NSString *Warning_;
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://url/%@", [[[webview_ request] URL] absoluteString]]];
+}
+
 - (void) setHeaders:(NSDictionary *)headers forHost:(NSString *)host {
 }
 
@@ -4091,18 +4124,16 @@ static NSString *Warning_;
 
     WebDataSource *source([frame dataSource]);
     NSURLResponse *response([source response]);
+
     NSURL *url([response URL]);
     NSString *scheme([url scheme]);
-
-    NSHTTPURLResponse *http;
-    if (scheme != nil && ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"]))
-        http = (NSHTTPURLResponse *) response;
-    else
-        http = nil;
-
-    NSDictionary *headers([http allHeaderFields]);
     NSString *host([url host]);
-    [self setHeaders:headers forHost:host];
+
+    if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
+        NSHTTPURLResponse *http((NSHTTPURLResponse *) response);
+        NSDictionary *headers([http allHeaderFields]);
+        [self setHeaders:headers forHost:host];
+    }
 
     if (
         [host isEqualToString:@"cydia.saurik.com"] ||
@@ -4251,8 +4282,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if ((self = [super init]) != nil) {
         database_ = database;
 
-        [[self navigationItem] setTitle:UCLocalize("CONFIRM")];
-
         NSMutableArray *installing = [NSMutableArray arrayWithCapacity:16];
         NSMutableArray *reinstalling = [NSMutableArray arrayWithCapacity:16];
         NSMutableArray *upgrading = [NSMutableArray arrayWithCapacity:16];
@@ -4299,7 +4328,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 message:UCLocalize("REMOVING_ESSENTIALS_EX")
                 delegate:self
                 cancelButtonTitle:[NSString stringWithFormat:parenthetical, UCLocalize("CANCEL_OPERATION"), UCLocalize("SAFE")]
-                otherButtonTitles:[NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")], nil
+                otherButtonTitles:
+                    [NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")],
+                nil
             ];
 
             [essential_ setContext:@"remove"];
@@ -4332,11 +4363,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             SizeString([database_ fetcher].PartialPresent()),
         nil];
 
-        [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"confirm" ofType:@"html"]]];
+        [self loadURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/confirm/", UI_]]];
 
         [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
             initWithTitle:UCLocalize("CANCEL")
-            // OLD: [NSString stringWithFormat:UCLocalize("SLASH_DELIMITED"), UCLocalize("CANCEL"), UCLocalize("QUEUE")]
             style:UIBarButtonItemStylePlain
             target:self
             action:@selector(cancelButtonClicked)
@@ -4345,11 +4375,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) applyRightButton {
-#if !AlwaysReload && !IgnoreInstall
+#if !AlwaysReload
     if (issues_ == nil && ![self isLoading])
         [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
             initWithTitle:UCLocalize("CONFIRM")
-            style:UIBarButtonItemStylePlain
+            style:UIBarButtonItemStyleDone
             target:self
             action:@selector(confirmButtonClicked)
         ] autorelease]];
@@ -4367,9 +4397,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 #if !AlwaysReload
 - (void) confirmButtonClicked {
-#if IgnoreInstall
-    return;
-#endif
     if (essential_ != nil)
         [essential_ show];
     else {
@@ -4485,7 +4512,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         //[status_ setFont:font];
 
         output_ = [[UITextView alloc] init];
-
         [output_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
         //[output_ setTextFont:@"Courier New"];
         [output_ setFont:[[output_ font] fontWithSize:12]];
@@ -4531,7 +4557,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         10,
         20,
         bounds.size.width - 20,
-        bounds.size.height - 62
+        bounds.size.height - 96
     )];
     [close_ setFrame:CGRectMake(
         (bounds.size.width - closewidth) / 2,
@@ -4741,7 +4767,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
-    CYActionSheet *sheet([[[CYActionSheet alloc]
+    CYAlertView *sheet([[[CYAlertView alloc]
         initWithTitle:title
         buttons:[NSArray arrayWithObjects:UCLocalize("OKAY"), nil]
         defaultButtonIndex:0
@@ -4799,8 +4825,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         message:[NSString stringWithFormat:@"%@\n\n%@", UCLocalize("CONFIGURATION_UPGRADE_EX"), ofile]
         delegate:self
         cancelButtonTitle:UCLocalize("KEEP_OLD_COPY")
-        otherButtonTitles:UCLocalize("ACCEPT_NEW_COPY"),
-        // XXX: UCLocalize("SEE_WHAT_CHANGED"),
+        otherButtonTitles:
+            UCLocalize("ACCEPT_NEW_COPY"),
+            // XXX: UCLocalize("SEE_WHAT_CHANGED"),
         nil
     ] autorelease];
 
@@ -4985,16 +5012,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
-- (void) _setBackgroundColor {
-    UIColor *color;
-    if (NSString *mode = [package_ mode]) {
-        bool remove([mode isEqualToString:@"REMOVE"] || [mode isEqualToString:@"PURGE"]);
-        color = remove ? RemovingColor_ : InstallingColor_;
-    } else
-        color = [UIColor whiteColor];
-
-    [content_ setBackgroundColor:color];
-    [self setNeedsDisplay];
+- (NSString *) accessibilityLabel {
+    return [NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), name_, description_];
 }
 
 - (void) setPackage:(Package *)package {
@@ -5043,11 +5062,36 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         if ((badge_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Purposes/%@.png", App_, purpose]]) != nil)
             badge_ = [badge_ retain];
 
-    if ([package installed] != nil)
-        if ((placard_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/installed.png", App_]]) != nil)
+    UIColor *color;
+    NSString *placard;
+
+    if (NSString *mode = [package_ mode]) {
+        if ([mode isEqualToString:@"REMOVE"] || [mode isEqualToString:@"PURGE"]) {
+            color = RemovingColor_;
+            //placard = @"removing";
+        } else {
+            color = InstallingColor_;
+            //placard = @"installing";
+        }
+
+        // XXX: the removing/installing placards are not @2x
+        placard = nil;
+    } else {
+        color = [UIColor whiteColor];
+
+        if ([package installed] != nil)
+            placard = @"installed";
+        else
+            placard = nil;
+    }
+
+    [content_ setBackgroundColor:color];
+
+    if (placard != nil)
+        if ((placard_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/%@.png", App_, placard]]) != nil)
             placard_ = [placard_ retain];
 
-    [self _setBackgroundColor];
+    [self setNeedsDisplay];
     [content_ setNeedsDisplay];
 }
 
@@ -5225,6 +5269,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [switch_ setFrame:CGRectMake(frame.size.width - 102, 9, rect.size.width, rect.size.height)];
 }
 
+- (NSString *) accessibilityLabel {
+    return name_;
+}
+
 - (void) drawContentRect:(CGRect)rect {
     bool highlighted(highlighted_ && !editing_);
 
@@ -5271,12 +5319,12 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @implementation FileTable
 
 - (void) dealloc {
-    if (package_ != nil)
-        [package_ release];
-    if (name_ != nil)
-        [name_ release];
+    [self releaseSubviews];
+
+    [package_ release];
+    [name_ release];
     [files_ release];
-    [list_ release];
+
     [super dealloc];
 }
 
@@ -5302,21 +5350,35 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return cell;
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/files", [package_ id]]];
+}
+
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+
+    list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
+    [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [list_ setRowHeight:24.0f];
+    [list_ setDataSource:self];
+    [list_ setDelegate:self];
+    [[self view] addSubview:list_];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("INSTALLED_FILES")];
+}
+
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
+
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
 
-        [[self navigationItem] setTitle:UCLocalize("INSTALLED_FILES")];
-
         files_ = [[NSMutableArray arrayWithCapacity:32] retain];
-
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:24.0f];
-        [[self view] addSubview:list_];
-
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
     } return self;
 }
 
@@ -5366,6 +5428,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     [self setPackage:[database_ packageWithName:name_]];
 }
 
@@ -5383,8 +5447,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     UIBarButtonItem *button_;
 }
 
-- (id) initWithDatabase:(Database *)database;
-- (void) setPackage:(Package *)package;
+- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name;
 
 @end
 
@@ -5404,10 +5467,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super dealloc];
 }
 
-- (void) release {
-    if ([self retainCount] == 1)
-        [delegate_ setPackageController:self];
-    [super release];
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@", name_]];
 }
 
 /* XXX: this is not safe at all... localization of /fail/ */
@@ -5438,11 +5499,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
-- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
-    [super webView:view didClearWindowObject:window forFrame:frame];
-    [window setValue:package_ forKey:@"package"];
-}
-
 - (bool) _allowJavaScriptPanel {
     return commercial_;
 }
@@ -5503,33 +5559,28 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 #endif
 
-- (id) initWithDatabase:(Database *)database {
+- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name {
     if ((self = [super init]) != nil) {
         database_ = database;
         buttons_ = [[NSMutableArray alloc] initWithCapacity:4];
-        [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"package" ofType:@"html"]]];
+        name_ = [[NSString alloc] initWithString:name];
     } return self;
 }
 
-- (void) setPackage:(Package *)package {
-    if (package_ != nil) {
-        [package_ autorelease];
-        package_ = nil;
-    }
+- (void) reloadData {
+    [super reloadData];
 
-    if (name_ != nil) {
-        [name_ release];
-        name_ = nil;
-    }
+    if (package_ != nil)
+        [package_ autorelease];
+    package_ = [database_ packageWithName:name_];
 
     [buttons_ removeAllObjects];
 
-    if (package != nil) {
-        [package parse];
+    if (package_ != nil) {
+        [package_ parse];
 
-        package_ = [package retain];
-        name_ = [[package id] retain];
-        commercial_ = [package isCommercial];
+        package_ = [package_ retain];
+        commercial_ = [package_ isCommercial];
 
         if ([package_ mode] != nil)
             [buttons_ addObject:UCLocalize("CLEAR")];
@@ -5561,22 +5612,18 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         action:@selector(customButtonClicked)
     ];
 
-    [self reloadURL];
+    [self loadURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/package/#!/%@", UI_, name_]]];
 }
 
 - (bool) isLoading {
     return commercial_ ? [super isLoading] : false;
 }
 
-- (void) reloadData {
-    [self setPackage:[database_ packageWithName:name_]];
-}
-
 @end
 /* }}} */
 
-/* Package Table {{{ */
-@interface PackageTable : UIView <
+/* Package List Controller {{{ */
+@interface PackageListController : CYViewController <
     UITableViewDataSource,
     UITableViewDelegate
 > {
@@ -5587,29 +5634,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     UITableView *list_;
     NSMutableArray *index_;
     NSMutableDictionary *indices_;
-    // XXX: this target_ seems to be delegate_. :(
-    _transient id target_;
-    SEL action_;
-    // XXX: why do we even have this delegate_?
-    _transient id delegate_;
+    NSString *title_;
 }
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action;
-
+- (id) initWithDatabase:(Database *)database title:(NSString *)title;
 - (void) setDelegate:(id)delegate;
-
-- (void) reloadData;
 - (void) resetCursor;
 
-- (UITableView *) list;
-
-- (void) setShouldHideHeaderInShortLists:(BOOL)hide;
-
-- (void) deselectWithAnimation:(BOOL)animated;
-
 @end
 
-@implementation PackageTable
+@implementation PackageListController
 
 - (void) dealloc {
     [packages_ release];
@@ -5617,13 +5651,97 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [list_ release];
     [index_ release];
     [indices_ release];
+    [title_ release];
 
     [super dealloc];
 }
 
+- (void) deselectWithAnimation:(BOOL)animated {
+    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
+}
+
+- (void) resizeForKeyboardBounds:(CGRect)bounds duration:(NSTimeInterval)duration curve:(UIViewAnimationCurve)curve {
+    CGRect base = [[self view] bounds];
+    base.size.height -= bounds.size.height;
+    base.origin = [list_ frame].origin;
+
+    [UIView beginAnimations:nil context:NULL];
+    [UIView setAnimationBeginsFromCurrentState:YES];
+    [UIView setAnimationCurve:curve];
+    [UIView setAnimationDuration:duration];
+    [list_ setFrame:base];
+    [UIView commitAnimations];
+}
+
+- (void) resizeForKeyboardBounds:(CGRect)bounds duration:(NSTimeInterval)duration {
+    [self resizeForKeyboardBounds:bounds duration:duration curve:UIViewAnimationCurveLinear];
+}
+
+- (void) resizeForKeyboardBounds:(CGRect)bounds {
+    [self resizeForKeyboardBounds:bounds duration:0];
+}
+
+- (void) keyboardWillShow:(NSNotification *)notification {
+    CGRect bounds;
+    CGPoint center;
+    NSTimeInterval duration;
+    UIViewAnimationCurve curve;
+    [[[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey] getValue:&bounds];
+    [[[notification userInfo] objectForKey:UIKeyboardCenterEndUserInfoKey] getValue:&center];
+    [[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&curve];
+    [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&duration];
+
+    CGRect kbframe = CGRectMake(round(center.x - bounds.size.width / 2.0), round(center.y - bounds.size.height / 2.0), bounds.size.width, bounds.size.height);
+    UIViewController *base = self;
+    while ([base parentViewController] != nil)
+        base = [base parentViewController];
+    CGRect viewframe = [[base view] convertRect:[list_ frame] fromView:[list_ superview]];
+    CGRect intersection = CGRectIntersection(viewframe, kbframe);
+
+    [self resizeForKeyboardBounds:intersection duration:duration curve:curve];
+}
+
+- (void) keyboardWillHide:(NSNotification *)notification {
+    NSTimeInterval duration;
+    UIViewAnimationCurve curve;
+    [[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&curve];
+    [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&duration];
+
+    [self resizeForKeyboardBounds:CGRectZero duration:duration curve:curve];
+}
+
+- (void) viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+
+    [self resizeForKeyboardBounds:CGRectZero];
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
+}
+
+- (void) viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+
+    [self resizeForKeyboardBounds:CGRectZero];
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
+}
+
+- (void) viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    [self deselectWithAnimation:animated];
+}
+
+- (void) didSelectPackage:(Package *)package {
+    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id]] autorelease]);
+    [view setDelegate:delegate_];
+    [[self navigationController] pushViewController:view animated:YES];
+}
+
+#if TryIndexedCollation
 + (BOOL) hasIndexedCollation {
     return NO; // XXX: objc_getClass("UILocalizedIndexedCollation") != nil;
 }
+#endif
 
 - (NSInteger) numberOfSectionsInTableView:(UITableView *)list {
     NSInteger count([sections_ count]);
@@ -5661,15 +5779,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return cell;
 }
 
-- (void) deselectWithAnimation:(BOOL)animated {
-    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
-}
-
-- (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path {
+- (void) tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)path {
     Package *package([self packageAtIndexPath:path]);
     package = [database_ packageWithName:[package id]];
-    [target_ performSelector:action_ withObject:package];
-    return path;
+    [self didSelectPackage:package];
 }
 
 - (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tableView {
@@ -5678,32 +5791,37 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (NSInteger) tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
+#if TryIndexedCollation
     if ([[self class] hasIndexedCollation]) {
         return [[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionForSectionIndexTitleAtIndex:index];
     }
+#endif
 
     return index;
 }
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action {
-    if ((self = [super initWithFrame:frame]) != nil) {
+- (id) initWithDatabase:(Database *)database title:(NSString *)title {
+    if ((self = [super init]) != nil) {
         database_ = database;
+        title_ = [title copy];
+        [[self navigationItem] setTitle:title_];
 
-        target_ = target;
-        action_ = action;
+#if TryIndexedCollation
+        if ([[self class] hasIndexedCollation])
+            index_ = [[[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionIndexTitles] retain]
+        else
+#endif
+            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];
         sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
 
-        list_ = [[UITableView alloc] initWithFrame:[self bounds] style:UITableViewStylePlain];
+        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
         [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
         [list_ setRowHeight:73];
-        [self addSubview:list_];
+        [[self view] addSubview:list_];
 
         [list_ setDataSource:self];
         [list_ setDelegate:self];
@@ -5719,6 +5837,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     era_ = [database_ era];
     NSArray *packages = [database_ packages];
 
@@ -5735,6 +5855,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     Section *section = nil;
 
+#if TryIndexedCollation
     if ([[self class] hasIndexedCollation]) {
         id collation = [objc_getClass("UILocalizedIndexedCollation") currentCollation];
         NSArray *titles = [collation sectionIndexTitles];
@@ -5765,7 +5886,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 [section addToCount];
             }
         _end
-    } else {
+    } else
+#endif
+    {
         [index_ removeAllObjects];
 
         _profile(PackageTable$reloadData$Section)
@@ -5805,18 +5928,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [list_ scrollRectToVisible:CGRectMake(0, 0, 0, 0) animated:NO];
 }
 
-- (UITableView *) list {
-    return list_;
-}
-
-- (void) setShouldHideHeaderInShortLists:(BOOL)hide {
-    //XXX:[list_ setShouldHideHeaderInShortLists:hide];
-}
-
 @end
 /* }}} */
-/* Filtered Package Table {{{ */
-@interface FilteredPackageTable : PackageTable {
+/* Filtered Package List Controller {{{ */
+@interface FilteredPackageListController : PackageListController {
     SEL filter_;
     IMP imp_;
     id object_;
@@ -5825,11 +5940,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (void) setObject:(id)object;
 - (void) setObject:(id)object forFilter:(SEL)filter;
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action filter:(SEL)filter with:(id)object;
+- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object;
 
 @end
 
-@implementation FilteredPackageTable
+@implementation FilteredPackageListController
 
 - (void) dealloc {
     if (object_ != nil)
@@ -5867,92 +5982,30 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _end
 }
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action filter:(SEL)filter with:(id)object {
-    if ((self = [super initWithFrame:frame database:database target:target action:action]) != nil) {
+- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object {
+    if ((self = [super initWithDatabase:database title:title]) != nil) {
         [self setFilter:filter];
-        object_ = [object retain];
+        [self setObject:object];
         [self reloadData];
     } return self;
 }
 
 @end
 /* }}} */
-/* Filtered Package Controller {{{ */
-@interface FilteredPackageController : CYViewController {
-    _transient Database *database_;
-    FilteredPackageTable *packages_;
-    NSString *title_;
-}
-
-- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object;
 
+/* Home Controller {{{ */
+@interface HomeController : CYBrowserController {
+}
 @end
 
-@implementation FilteredPackageController
-
-- (void) dealloc {
-    [packages_ release];
-    [title_ release];
+@implementation HomeController
 
-    [super dealloc];
++ (BOOL) shouldHideNavigationBar {
+    return NO;
 }
 
-- (void) viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [packages_ deselectWithAnimation:animated];
-}
-
-- (void) didSelectPackage:(Package *)package {
-    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_] autorelease]);
-    [view setPackage:package];
-    [view setDelegate:delegate_];
-    [[self navigationController] pushViewController:view animated:YES];
-}
-
-- (NSString *) title { return title_; }
-
-- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object {
-    if ((self = [super init]) != nil) {
-        database_ = database;
-        title_ = [title copy];
-        [[self navigationItem] setTitle:title_];
-
-        packages_ = [[FilteredPackageTable alloc]
-            initWithFrame:[[self view] bounds]
-            database:database
-            target:self
-            action:@selector(didSelectPackage:)
-            filter:filter
-            with:object
-        ];
-
-        [packages_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:packages_];
-    } return self;
-}
-
-- (void) reloadData {
-    [packages_ reloadData];
-}
-
-- (void) setDelegate:(id)delegate {
-    [super setDelegate:delegate];
-    [packages_ setDelegate:delegate];
-}
-
-@end
-
-/* }}} */
-
-/* Home Controller {{{ */
-@interface HomeController : CYBrowserController {
-}
-@end
-
-@implementation HomeController
-
-+ (BOOL)shouldHideNavigationBar {
-    return NO;
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://home"];
 }
 
 - (void) _setMoreHeaders:(NSMutableURLRequest *)request {
@@ -5974,7 +6027,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [alert setCancelButtonIndex:0];
 
     [alert setMessage:
-        @"Copyright (C) 2008-2010\n"
+        @"Copyright (C) 2008-2011\n"
         "Jay Freeman (saurik)\n"
         "saurik@saurik.com\n"
         "http://www.saurik.com/"
@@ -5983,13 +6036,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [alert show];
 }
 
-- (void) viewWillAppear:(BOOL)animated {
-    [super viewWillAppear:animated];
-
-    if ([[self class] shouldHideNavigationBar])
-        [[self navigationController] setNavigationBarHidden:YES animated:animated];
-}
-
 - (void) viewWillDisappear:(BOOL)animated {
     [super viewWillDisappear:animated];
 
@@ -5997,17 +6043,23 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [[self navigationController] setNavigationBarHidden:NO animated:animated];
 }
 
-- (id) init {
-    if ((self = [super init]) != nil) {
-        [self loadURL:[NSURL URLWithString:CydiaURL(@"")]];
+- (void) viewWillAppear:(BOOL)animated {
+    if (![self hasLoaded])
+        [self loadURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/home/", UI_]]];
 
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("ABOUT")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(aboutButtonClicked)
-        ] autorelease]];
-    } return self;
+    [super viewWillAppear:animated];
+
+    if ([[self class] shouldHideNavigationBar])
+        [[self navigationController] setNavigationBarHidden:YES animated:animated];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("ABOUT")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(aboutButtonClicked)
+    ] autorelease]];
 }
 
 @end
@@ -6021,21 +6073,26 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @implementation ManageController
 
-- (id) init {
-    if ((self = [super init]) != nil) {
-        [[self navigationItem] setTitle:UCLocalize("MANAGE")];
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://manage"];
+}
 
-        [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"manage" ofType:@"html"]]];
+- (void) viewWillAppear:(BOOL)animated {
+    if (![self hasLoaded])
+        [self loadURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/manage/", UI_]]];
 
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("SETTINGS")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(settingsButtonClicked)
-        ] autorelease]];
+    [super viewWillAppear:animated];
+}
 
-        [self queueStatusDidChange];
-    } return self;
+- (void) viewDidLoad {
+    [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("SETTINGS")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(settingsButtonClicked)
+    ] autorelease]];
+
+    [self queueStatusDidChange];
 }
 
 - (void) settingsButtonClicked {
@@ -6048,11 +6105,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) applyLoadingTitle {
-    // No "Loading" title.
+    // Disable "Loading" title.
 }
 
 - (void) applyRightButton {
-    // No right button.
+    // Disable right button.
 }
 #endif
 
@@ -6072,6 +6129,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (bool) isLoading {
+    // Never show as loading.
     return false;
 }
 
@@ -6198,6 +6256,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 /* Cydia Tab Bar Controller {{{ */
 @interface CYTabBarController : UITabBarController <
+    UITabBarControllerDelegate,
     ProgressDelegate
 > {
     _transient Database *database_;
@@ -6209,8 +6268,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _transient NSObject<CydiaDelegate> *updatedelegate_;
 
     id root_;
+    UIViewController *remembered_;
+    _transient UIViewController *transient_;
 }
 
+- (NSArray *) navigationURLCollection;
 - (void) dropBar:(BOOL)animated;
 - (void) beginUpdate;
 - (void) raiseBar:(BOOL)animated;
@@ -6220,19 +6282,68 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @implementation CYTabBarController
 
-- (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];
+- (void) setUnselectedViewController:(UIViewController *)transient {
+    NSMutableArray *controllers = [[self viewControllers] mutableCopy];
+    if (transient != nil) {
+        if (transient_ == nil)
+            remembered_ = [[controllers objectAtIndex:0] retain];
+        transient_ = transient;
+        [transient_ setTabBarItem:[remembered_ tabBarItem]];
+        [controllers replaceObjectAtIndex:0 withObject:transient_];
+        [self setSelectedIndex:0];
+        [self setViewControllers:controllers];
+        [self concealTabBarSelection];
+    } else if (remembered_ != nil) {
+        [remembered_ setTabBarItem:[transient_ tabBarItem]];
+        transient_ = transient;
+        [controllers replaceObjectAtIndex:0 withObject:remembered_];
+        [remembered_ release];
+        remembered_ = nil;
+        [self setViewControllers:controllers];
+        [self revealTabBarSelection];
     }
+}
+
+- (UIViewController *) unselectedViewController {
+    return transient_;
+}
 
-    [(CYNavigationController *) [self transientViewController] reloadData];
+- (void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
+    if ([self unselectedViewController])
+        [self setUnselectedViewController:nil];
+}
+
+- (NSArray *) navigationURLCollection {
+    NSMutableArray *items([NSMutableArray array]);
+
+    // XXX: Should this deal with transient view controllers?
+    for (id navigation in [self viewControllers]) {
+        NSArray *stack = [navigation performSelector:@selector(navigationURLCollection)];
+        if (stack != nil)
+            [items addObject:stack];
+    }
+
+    return items;
+}
+
+- (void) reloadData {
+    for (CYViewController *controller in [self viewControllers])
+        [controller reloadData];
+
+    [(CYNavigationController *)[self unselectedViewController] reloadData];
+}
+
+- (void) dealloc {
+    [refreshbar_ release];
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+    [super dealloc];
 }
 
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
+        [self setDelegate:self];
 
         [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
@@ -6241,10 +6352,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
-- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
-    return IsWildcat_ || orientation == UIInterfaceOrientationPortrait;
-}
-
 - (void) setUpdate:(NSDate *)date {
     [self beginUpdate];
 }
@@ -6378,7 +6485,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     CGRect barframe([refreshbar_ frame]);
 
-    if (false) // XXX: _UIApplicationLinkedOnOrAfter(4)
+    if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_3_0) // XXX: _UIApplicationLinkedOnOrAfter(4)
         barframe.origin.y = [self statusBarHeight];
     else
         barframe.origin.y = 0;
@@ -6458,12 +6565,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
-- (void) dealloc {
-    [refreshbar_ release];
-    [[NSNotificationCenter defaultCenter] removeObserver:self];
-    [super dealloc];
-}
-
 @end
 /* }}} */
 /* Cydia Navigation Controller {{{ */
@@ -6472,6 +6573,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _transient id<UINavigationControllerDelegate> delegate_;
 }
 
+- (NSArray *) navigationURLCollection;
 - (id) initWithDatabase:(Database *)database;
 - (void) reloadData;
 
@@ -6480,26 +6582,25 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @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 if ([self parentViewController]) {
-        return [[self parentViewController] shouldAutorotateToInterfaceOrientation:orientation];
-    } else {
-        return [super shouldAutorotateToInterfaceOrientation:orientation];
+- (NSArray *) navigationURLCollection {
+    NSMutableArray *stack([NSMutableArray array]);
+
+    for (CYViewController *controller in [self viewControllers]) {
+        NSString *url = [[controller navigationURL] absoluteString];
+        if (url != nil)
+            [stack addObject:url];
     }
-}
 
-- (void) dealloc {
-    [super dealloc];
+    return stack;
 }
 
 - (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];
+    for (CYViewController *page in [self viewControllers]) {
+        // Only reload controllers that have already loaded.
+        // This prevents a page from accidentally loading too
+        // early if it hasn't been shown on the screen yet.
+        if ([page hasLoaded])
+            [page reloadData];
     }
 }
 
@@ -6618,7 +6719,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 /* }}} */
 
 /* Section Controller {{{ */
-@interface SectionController : FilteredPackageController {
+@interface SectionController : FilteredPackageListController {
+    NSString *section_;
 }
 
 - (id) initWithDatabase:(Database *)database section:(NSString *)section;
@@ -6627,34 +6729,28 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @implementation SectionController
 
-- (void) dealloc {
-    [super dealloc];
+- (NSURL *) navigationURL {
+    NSString *name = section_;
+    if (name == nil)
+        name = @"all";
+
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sections/%@", name]];
 }
 
 - (id) initWithDatabase:(Database *)database section:(NSString *)name {
     NSString *title;
-
-    if (name == nil) {
+    if (name == nil)
         title = UCLocalize("ALL_PACKAGES");
-    } else if (![name isEqual:@""]) {
+    else if (![name isEqual:@""])
         title = [[NSBundle mainBundle] localizedStringForKey:Simplify(name) value:nil table:@"Sections"];
-    } else {
+    else
         title = UCLocalize("NO_SECTION");
-    }
 
     if ((self = [super initWithDatabase:database title:title filter:@selector(isVisibleInSection:) with:name]) != nil) {
+        section_ = name;
     } return self;
 }
 
-- (void) reloadData {
-    [packages_ reloadData];
-}
-
-- (void) setDelegate:(id)delegate {
-    [super setDelegate:delegate];
-    [packages_ setDelegate:delegate];
-}
-
 @end
 /* }}} */
 /* Sections Controller {{{ */
@@ -6666,14 +6762,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     NSMutableArray *sections_;
     NSMutableArray *filtered_;
     UITableView *list_;
-    UIView *accessory_;
     BOOL editing_;
 }
 
 - (id) initWithDatabase:(Database *)database;
-- (void) reloadData;
-- (void) resetView;
-
 - (void) editButtonClicked;
 
 @end
@@ -6681,25 +6773,41 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @implementation SectionsController
 
 - (void) dealloc {
-    [list_ setDataSource:nil];
-    [list_ setDelegate:nil];
-
+    [self releaseSubviews];
     [sections_ release];
     [filtered_ release];
-    [list_ release];
-    [accessory_ release];
+
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://sections"];
+}
+
+- (void) updateNavigationItem {
+    [[self navigationItem] setTitle:editing_ ? UCLocalize("SECTION_VISIBILITY") : UCLocalize("SECTIONS")];
+    if ([sections_ count] == 0) {
+        [[self navigationItem] setRightBarButtonItem:nil];
+    } else {
+        [[self navigationItem] setRightBarButtonItem:[[UIBarButtonItem alloc]
+            initWithBarButtonSystemItem:(editing_ ? UIBarButtonSystemItemDone : UIBarButtonSystemItemEdit)
+            target:self
+            action:@selector(editButtonClicked)
+        ] animated:([[self navigationItem] rightBarButtonItem] != nil)];
+    }
+}
+
+- (BOOL) isEditing {
+    return editing_;
+}
+
 - (void) setEditing:(BOOL)editing {
     if ((editing_ = editing))
         [list_ reloadData];
     else
         [delegate_ updateData];
 
-    [[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];
+    [self updateNavigationItem];
 }
 
 - (void) viewDidAppear:(BOOL)animated {
@@ -6728,7 +6836,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     static NSString *reuseIdentifier = @"SectionCell";
 
-    SectionCell *cell = (SectionCell *) [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
+    SectionCell *cell = (SectionCell *)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
     if (cell == nil)
         cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease];
 
@@ -6752,28 +6860,38 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [[self navigationController] pushViewController:controller animated:YES];
 }
 
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+
+    list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
+    [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [list_ setRowHeight:45.0f];
+    [list_ setDataSource:self];
+    [list_ setDelegate:self];
+    [[self view] addSubview:list_];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("SECTIONS")];
+}
+
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
+
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
 
-        [[self navigationItem] setTitle:UCLocalize("SECTIONS")];
-
         sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
         filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
-
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:45.0f];
-        [[self view] addSubview:list_];
-
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
-
-        [self reloadData];
     } return self;
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     NSArray *packages = [database_ packages];
 
     [sections_ removeAllObjects];
@@ -6792,7 +6910,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             section = [sections objectForKey:key];
             if (section == nil) {
                 _profile(SectionsView$reloadData$Section$Allocate)
-                    section = [[[Section alloc] initWithName:name localize:YES] autorelease];
+                    section = [[[Section alloc] initWithName:key localize:YES] autorelease];
                     [sections setObject:section forKey:key];
                 _end
             }
@@ -6823,30 +6941,15 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [filtered_ addObject:section];
     }
 
-    [[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 updateNavigationItem];
     [list_ reloadData];
     _trace();
 }
 
-- (void) resetView {
-    if (editing_)
-        [self editButtonClicked];
-}
-
 - (void)editButtonClicked {
     [self setEditing:!editing_];
 }
 
-- (UIView *) accessoryView {
-    return accessory_;
-}
-
 @end
 /* }}} */
 
@@ -6864,26 +6967,33 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     BOOL hasSentFirstLoad_;
 }
 
-- (id) initWithDatabase:(Database *)database delegate:(id)delegate;
-- (void) reloadData;
+- (id) initWithDatabase:(Database *)database;
 
 @end
 
 @implementation ChangesController
 
 - (void) dealloc {
-    [list_ setDelegate:nil];
-    [list_ setDataSource:nil];
-
+    [self releaseSubviews];
     CFRelease(packages_);
-
     [sections_ release];
-    [list_ release];
+
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://changes"];
+}
+
+- (void) viewWillAppear:(BOOL)animated {
+    // Loads after it appears, so don't load beforehand.
+    loaded_ = YES;
+    [super viewWillAppear:animated];
+}
+
 - (void) viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
+
     if (!hasSentFirstLoad_) {
         hasSentFirstLoad_ = YES;
         [self performSelector:@selector(reloadData) withObject:nil afterDelay:0.0];
@@ -6936,9 +7046,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path {
     Package *package([self packageAtIndexPath:path]);
-    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_] autorelease]);
+    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id]] autorelease]);
     [view setDelegate:delegate_];
-    [view setPackage:package];
     [[self navigationController] pushViewController:view animated:YES];
     return path;
 }
@@ -6952,26 +7061,32 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [delegate_ distUpgrade];
 }
 
-- (NSString *) title { return UCLocalize("CHANGES"); }
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
 
-- (id) initWithDatabase:(Database *)database delegate:(id)delegate {
+    list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
+    [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [list_ setRowHeight:73];
+    [list_ setDataSource:self];
+    [list_ setDelegate:self];
+    [[self view] addSubview:list_];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("CHANGES")];
+}
+
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
+
+- (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
-        [[self navigationItem] setTitle:UCLocalize("CHANGES")];
 
         packages_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
-
         sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
-
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:73];
-        [[self view] addSubview:list_];
-
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
-
-        delegate_ = delegate;
     } return self;
 }
 
@@ -7093,10 +7208,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 /* }}} */
 /* Search Controller {{{ */
-@interface SearchController : FilteredPackageController <
+@interface SearchController : FilteredPackageListController <
     UISearchBarDelegate
 > {
     UISearchBar *search_;
+    BOOL searchloaded_;
 }
 
 - (id) initWithDatabase:(Database *)database;
@@ -7112,29 +7228,41 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    if ([search_ text] == nil || [[search_ text] isEqualToString:@""])
+        return [NSURL URLWithString:@"cydia://search"];
+    else
+        return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://search/%@", [search_ text]]];
+}
+
 - (void) setSearchTerm:(NSString *)searchTerm {
     [search_ setText:searchTerm];
+    [self reloadData];
 }
 
 - (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar {
-    [packages_ setObject:[search_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)];
+    [self setObject:[search_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)];
     [search_ resignFirstResponder];
     [self reloadData];
 }
 
 - (void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)text {
-    [packages_ setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)];
+    [self setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)];
     [self reloadData];
 }
 
 - (id) initWithDatabase:(Database *)database {
-    return [super initWithDatabase:database title:UCLocalize("SEARCH") filter:@selector(isUnfilteredAndSearchedForBy:) with:nil];
+    if ((self = [super initWithDatabase:database title:UCLocalize("SEARCH") filter:@selector(isUnfilteredAndSearchedForBy:) with:nil])) {
+        search_ = [[UISearchBar alloc] init];
+    } return self;
 }
 
 - (void)viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
-    if (!search_) {
-        search_ = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)];
+
+    if (!searchloaded_) {
+        searchloaded_ = YES;
+        [search_ setFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)];
         [search_ layoutSubviews];
         [search_ setPlaceholder:UCLocalize("SEARCH_EX")];
 
@@ -7151,15 +7279,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
-- (void) _reloadData {
-}
-
 - (void) reloadData {
-    _profile(SearchController$reloadData)
-        [packages_ reloadData];
-    _end
-    PrintTimes();
-    [packages_ resetCursor];
+    [self setObject:[search_ text]];
+    [super reloadData];
+    [self resetCursor];
 }
 
 - (void) didSelectPackage:(Package *)package {
@@ -7191,18 +7314,17 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @implementation PackageSettingsController
 
 - (void) dealloc {
+    [self releaseSubviews];
     [name_ release];
-    if (package_ != nil)
-        [package_ release];
-    [table_ release];
-    [subscribedSwitch_ release];
-    [ignoredSwitch_ release];
-    [subscribedCell_ release];
-    [ignoredCell_ release];
+    [package_ release];
 
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/settings", [package_ id]]];
+}
+
 - (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
     if (package_ == nil)
         return 0;
@@ -7251,48 +7373,69 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return nil;
 }
 
-- (NSString *) title { return UCLocalize("SETTINGS"); }
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package {
-    if ((self = [super init])) {
-        database_ = database;
-        name_ = [package retain];
+    table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
+    [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [table_ setDataSource:self];
+    [table_ setDelegate:self];
+    [[self view] addSubview:table_];
 
-        [[self navigationItem] setTitle:UCLocalize("SETTINGS")];
+    subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
+    [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+    [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged];
 
-        table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
-        [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:table_];
+    ignoredSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
+    [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+    [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged];
+    // Disable this switch, since it only reflects (not modifies) the ignored state.
+    [ignoredSwitch_ setUserInteractionEnabled:NO];
 
-        subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
-        [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged];
+    subscribedCell_ = [[UITableViewCell alloc] init];
+    [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
+    [subscribedCell_ setAccessoryView:subscribedSwitch_];
+    [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
 
-        ignoredSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
-        [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged];
-        // Disable this switch, since it only reflects (not modifies) the ignored state.
-        [ignoredSwitch_ setUserInteractionEnabled:NO];
+    ignoredCell_ = [[UITableViewCell alloc] init];
+    [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
+    [ignoredCell_ setAccessoryView:ignoredSwitch_];
+    [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+    // FIXME: Ignored state is not saved.
+    [ignoredCell_ setUserInteractionEnabled:NO];
+}
 
-        subscribedCell_ = [[UITableViewCell alloc] init];
-        [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
-        [subscribedCell_ setAccessoryView:subscribedSwitch_];
-        [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("SETTINGS")];
+}
 
-        ignoredCell_ = [[UITableViewCell alloc] init];
-        [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
-        [ignoredCell_ setAccessoryView:ignoredSwitch_];
-        [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
-        // FIXME: Ignored state is not saved.
-        [ignoredCell_ setUserInteractionEnabled:NO];
+- (void) releaseSubviews {
+    [ignoredCell_ release];
+    ignoredCell_ = nil;
 
-        [table_ setDataSource:self];
-        [table_ setDelegate:self];
-        [self reloadData];
+    [subscribedCell_ release];
+    subscribedCell_ = nil;
+
+    [table_ release];
+    table_ = nil;
+
+    [ignoredSwitch_ release];
+    ignoredSwitch_ = nil;
+
+    [subscribedSwitch_ release];
+    subscribedSwitch_ = nil;
+}
+
+- (id) initWithDatabase:(Database *)database package:(NSString *)package {
+    if ((self = [super init])) {
+        database_ = database;
+        name_ = [package retain];
     } return self;
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     if (package_ != nil)
         [package_ autorelease];
     package_ = [database_ packageWithName:name_];
@@ -7305,47 +7448,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [table_ reloadData];
 }
 
-@end
-/* }}} */
-/* Signature Controller {{{ */
-@interface SignatureController : CYBrowserController {
-    _transient Database *database_;
-    NSString *package_;
-}
-
-- (id) initWithDatabase:(Database *)database package:(NSString *)package;
-
-@end
-
-@implementation SignatureController
-
-- (void) dealloc {
-    [package_ release];
-    [super dealloc];
-}
-
-- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
-    // XXX: dude!
-    [super webView:view didClearWindowObject:window forFrame:frame];
-}
-
-- (id) initWithDatabase:(Database *)database package:(NSString *)package {
-    if ((self = [super init]) != nil) {
-        database_ = database;
-        package_ = [package retain];
-        [self reloadData];
-    } return self;
-}
-
-- (void) reloadData {
-    [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"signature" ofType:@"html"]]];
-}
-
 @end
 /* }}} */
 
 /* Installed Controller {{{ */
-@interface InstalledController : FilteredPackageController {
+@interface InstalledController : FilteredPackageListController {
     BOOL expert_;
 }
 
@@ -7362,7 +7469,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super dealloc];
 }
 
-- (NSString *) title { return UCLocalize("INSTALLED"); }
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://installed"];
+}
 
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED") filter:@selector(isInstalledAndUnfiltered:) with:[NSNumber numberWithBool:YES]]) != nil) {
@@ -7394,10 +7503,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 #endif
 }
 
-- (void) reloadData {
-    [packages_ reloadData];
-}
-
 - (void) updateRoleButton {
     if (Role_ != nil && ![Role_ isEqualToString:@"Developer"])
         [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
@@ -7409,18 +7514,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) roleButtonClicked {
-    [packages_ setObject:[NSNumber numberWithBool:expert_]];
-    [packages_ reloadData];
+    [self setObject:[NSNumber numberWithBool:expert_]];
+    [self reloadData];
     expert_ = !expert_;
 
     [self updateRoleButton];
 }
 
-- (void) setDelegate:(id)delegate {
-    [super setDelegate:delegate];
-    [packages_ setDelegate:delegate];
-}
-
 @end
 /* }}} */
 
@@ -7484,6 +7584,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
+- (NSString *) accessibilityLabel {
+    return label_;
+}
+
 - (void) drawContentRect:(CGRect)rect {
     bool highlighted(highlighted_);
     float width(rect.size.width);
@@ -7505,7 +7609,31 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @end
 /* }}} */
-/* Source Table {{{ */
+/* Source Controller {{{ */
+@interface SourceController : FilteredPackageListController {
+    Source *source_;
+}
+
+- (id) initWithDatabase:(Database *)database source:(Source *)source;
+
+@end
+
+@implementation SourceController
+
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sources/%@", [source_ name]]];
+}
+
+- (id) initWithDatabase:(Database *)database source:(Source *)source {
+    source_ = source;
+
+    if ((self = [super initWithDatabase:database title:[source label] filter:@selector(isVisibleInSource:) with:source]) != nil) {
+    } return self;
+}
+
+@end
+/* }}} */
+/* Sources Controller {{{ */
 @interface SourcesController : CYViewController <
     UITableViewDataSource,
     UITableViewDelegate
@@ -7529,7 +7657,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (id) initWithDatabase:(Database *)database;
-
 - (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated;
 
 @end
@@ -7545,12 +7672,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) dealloc {
-    if (href_ != nil)
-        [href_ release];
-    if (hud_ != nil)
-        [hud_ release];
-    if (error_ != nil)
-        [error_ release];
+    [self releaseSubviews];
+
+    [href_ release];
+    [hud_ release];
+    [error_ release];
 
     //[self _releaseConnection:installer_];
     [self _releaseConnection:trivial_];
@@ -7559,10 +7685,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     //[self _releaseConnection:automatic_];
 
     [sources_ release];
-    [list_ release];
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://sources"];
+}
+
 - (void) viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
     [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
@@ -7608,27 +7737,22 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     SourceCell *cell = (SourceCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
     if(cell == nil) cell = [[[SourceCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellIdentifier] autorelease];
     [cell setSource:[self sourceAtIndexPath:indexPath]];
+    [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
 
     return cell;
 }
 
-- (UITableViewCellAccessoryType) tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath {
-    return UITableViewCellAccessoryDisclosureIndicator;
-}
-
 - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     Source *source = [self sourceAtIndexPath:indexPath];
 
-    FilteredPackageController *packages = [[[FilteredPackageController alloc]
+    SourceController *controller = [[[SourceController alloc]
         initWithDatabase:database_
-        title:[source label]
-        filter:@selector(isVisibleInSource:)
-        with:source
+        source:source
     ] autorelease];
 
-    [packages setDelegate:delegate_];
+    [controller setDelegate:delegate_];
 
-    [[self navigationController] pushViewController:packages animated:YES];
+    [[self navigationController] pushViewController:controller animated:YES];
 }
 
 - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
@@ -7701,7 +7825,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                     message:warning
                     delegate:self
                     cancelButtonTitle:UCLocalize("CANCEL")
-                    otherButtonTitles:UCLocalize("ADD_ANYWAY"), nil
+                    otherButtonTitles:
+                        UCLocalize("ADD_ANYWAY"),
+                    nil
                 ] autorelease];
 
                 [alert setContext:@"warning"];
@@ -7769,8 +7895,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [self _endConnection:connection];
 }
 
-- (NSString *) title { return UCLocalize("SOURCES"); }
-
 - (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method {
     NSMutableURLRequest *request = [NSMutableURLRequest
         requestWithURL:[NSURL URLWithString:href]
@@ -7848,27 +7972,37 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
-        [[self navigationItem] setTitle:UCLocalize("SOURCES")];
-        [self updateButtonsForEditingStatus:NO animated:NO];
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
 
-        database_ = database;
-        sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
+    list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
+    [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [list_ setRowHeight:56];
+    [list_ setDataSource:self];
+    [list_ setDelegate:self];
+    [[self view] addSubview:list_];
+}
 
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:56];
-        [[self view] addSubview:list_];
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("SOURCES")];
+    [self updateButtonsForEditingStatus:NO animated:NO];
+}
 
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
 
-        [self reloadData];
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+        sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
     } return self;
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     pkgSourceList list;
     if (!list.ReadMainList())
         return;
@@ -7898,7 +8032,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         message:nil
         delegate:self
         cancelButtonTitle:UCLocalize("CANCEL")
-        otherButtonTitles:UCLocalize("ADD_SOURCE"), nil
+        otherButtonTitles:
+            UCLocalize("ADD_SOURCE"),
+        nil
     ] autorelease];
 
     [alert setContext:@"source"];
@@ -7977,48 +8113,63 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 
 @implementation SettingsController
+
 - (void) dealloc {
+    [self releaseSubviews];
+
+    [super dealloc];
+}
+
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+
+    table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
+    [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [table_ setDelegate:self];
+    [table_ setDataSource:self];
+    [[self view] addSubview:table_];
+
+    NSArray *items = [NSArray arrayWithObjects:
+        UCLocalize("USER"),
+        UCLocalize("HACKER"),
+        UCLocalize("DEVELOPER"),
+    nil];
+    segment_ = [[UISegmentedControl alloc] initWithItems:items];
+    container_ = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, 44.0f)];
+    [container_ addSubview:segment_];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("WHO_ARE_YOU")];
+
+    int index = -1;
+    if ([Role_ isEqualToString:@"User"]) index = 0;
+    if ([Role_ isEqualToString:@"Hacker"]) index = 1;
+    if ([Role_ isEqualToString:@"Developer"]) index = 2;
+    if (index != -1) {
+        [segment_ setSelectedSegmentIndex:index];
+        [self showDoneButton];
+    }
+
+    [segment_ addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged];
+    [self resizeSegmentedControl];
+}
+
+- (void) releaseSubviews {
     [table_ release];
+    table_ = nil;
+
     [segment_ release];
-    [container_ release];
+    segment_ = nil;
 
-    [super dealloc];
+    [container_ release];
+    container_ = nil;
 }
 
 - (id) initWithDatabase:(Database *)database delegate:(id)delegate {
     if ((self = [super init])) {
         database_ = database;
         roledelegate_ = delegate;
-
-        [[self navigationItem] setTitle:UCLocalize("WHO_ARE_YOU")];
-
-        NSArray *items = [NSArray arrayWithObjects:
-            UCLocalize("USER"),
-            UCLocalize("HACKER"),
-            UCLocalize("DEVELOPER"),
-        nil];
-        segment_ = [[UISegmentedControl alloc] initWithItems:items];
-        container_ = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, 44.0f)];
-        [container_ addSubview:segment_];
-
-        int index = -1;
-        if ([Role_ isEqualToString:@"User"]) index = 0;
-        if ([Role_ isEqualToString:@"Hacker"]) index = 1;
-        if ([Role_ isEqualToString:@"Developer"]) index = 2;
-        if (index != -1) {
-            [segment_ setSelectedSegmentIndex:index];
-            [self showDoneButton];
-        }
-
-        [segment_ addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged];
-        [self resizeSegmentedControl];
-
-        table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
-        [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [table_ setDelegate:self];
-        [table_ setDataSource:self];
-        [[self view] addSubview:table_];
-        [table_ reloadData];
     } return self;
 }
 
@@ -8133,66 +8284,84 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return section == 3 ? container_ : nil;
 }
 
+- (void) reloadData {
+    [super reloadData];
+    [table_ reloadData];
+}
+
 @end
 /* }}} */
 /* Stash Controller {{{ */
 @interface StashController : CYViewController {
-    // XXX: just delete these things
-    _transient UIActivityIndicatorView *spinner_;
-    _transient UILabel *status_;
-    _transient UILabel *caption_;
+    UIActivityIndicatorView *spinner_;
+    UILabel *status_;
+    UILabel *caption_;
 }
 @end
 
 @implementation StashController
-- (id) init {
-    if ((self = [super init])) {
-        [[self view] setBackgroundColor:[UIColor viewFlipsideBackgroundColor]];
 
-        spinner_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
-        CGRect spinrect = [spinner_ frame];
-        spinrect.origin.x = ([[self view] frame].size.width / 2) - (spinrect.size.width / 2);
-        spinrect.origin.y = [[self view] frame].size.height - 80.0f;
-        [spinner_ setFrame:spinrect];
-        [spinner_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin];
-        [[self view] addSubview:spinner_];
-        [spinner_ startAnimating];
+- (void) dealloc {
+    [self releaseSubviews];
 
-        CGRect captrect;
-        captrect.size.width = [[self view] frame].size.width;
-        captrect.size.height = 40.0f;
-        captrect.origin.x = 0;
-        captrect.origin.y = ([[self view] frame].size.height / 2) - (captrect.size.height * 2);
-        caption_ = [[[UILabel alloc] initWithFrame:captrect] autorelease];
-        [caption_ setText:UCLocalize("PREPARING_FILESYSTEM")];
-        [caption_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
-        [caption_ setFont:[UIFont boldSystemFontOfSize:28.0f]];
-        [caption_ setTextColor:[UIColor whiteColor]];
-        [caption_ setBackgroundColor:[UIColor clearColor]];
-        [caption_ setShadowColor:[UIColor blackColor]];
-        [caption_ setTextAlignment:UITextAlignmentCenter];
-        [[self view] addSubview:caption_];
-
-        CGRect statusrect;
-        statusrect.size.width = [[self view] frame].size.width;
-        statusrect.size.height = 30.0f;
-        statusrect.origin.x = 0;
-        statusrect.origin.y = ([[self view] frame].size.height / 2) - statusrect.size.height;
-        status_ = [[[UILabel alloc] initWithFrame:statusrect] autorelease];
-        [status_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
-        [status_ setText:UCLocalize("EXIT_WHEN_COMPLETE")];
-        [status_ setFont:[UIFont systemFontOfSize:16.0f]];
-        [status_ setTextColor:[UIColor whiteColor]];
-        [status_ setBackgroundColor:[UIColor clearColor]];
-        [status_ setShadowColor:[UIColor blackColor]];
-        [status_ setTextAlignment:UITextAlignmentCenter];
-        [[self view] addSubview:status_];
-    } return self;
+    [super dealloc];
+}
+
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+    [[self view] setBackgroundColor:[UIColor viewFlipsideBackgroundColor]];
+
+    spinner_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
+    CGRect spinrect = [spinner_ frame];
+    spinrect.origin.x = ([[self view] frame].size.width / 2) - (spinrect.size.width / 2);
+    spinrect.origin.y = [[self view] frame].size.height - 80.0f;
+    [spinner_ setFrame:spinrect];
+    [spinner_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin];
+    [[self view] addSubview:spinner_];
+    [spinner_ startAnimating];
+
+    CGRect captrect;
+    captrect.size.width = [[self view] frame].size.width;
+    captrect.size.height = 40.0f;
+    captrect.origin.x = 0;
+    captrect.origin.y = ([[self view] frame].size.height / 2) - (captrect.size.height * 2);
+    caption_ = [[[UILabel alloc] initWithFrame:captrect] autorelease];
+    [caption_ setText:UCLocalize("PREPARING_FILESYSTEM")];
+    [caption_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
+    [caption_ setFont:[UIFont boldSystemFontOfSize:28.0f]];
+    [caption_ setTextColor:[UIColor whiteColor]];
+    [caption_ setBackgroundColor:[UIColor clearColor]];
+    [caption_ setShadowColor:[UIColor blackColor]];
+    [caption_ setTextAlignment:UITextAlignmentCenter];
+    [[self view] addSubview:caption_];
+
+    CGRect statusrect;
+    statusrect.size.width = [[self view] frame].size.width;
+    statusrect.size.height = 30.0f;
+    statusrect.origin.x = 0;
+    statusrect.origin.y = ([[self view] frame].size.height / 2) - statusrect.size.height;
+    status_ = [[[UILabel alloc] initWithFrame:statusrect] autorelease];
+    [status_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
+    [status_ setText:UCLocalize("EXIT_WHEN_COMPLETE")];
+    [status_ setFont:[UIFont systemFontOfSize:16.0f]];
+    [status_ setTextColor:[UIColor whiteColor]];
+    [status_ setBackgroundColor:[UIColor clearColor]];
+    [status_ setShadowColor:[UIColor blackColor]];
+    [status_ setTextAlignment:UITextAlignmentCenter];
+    [[self view] addSubview:status_];
 }
 
-- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
-    return IsWildcat_ || orientation == UIInterfaceOrientationPortrait;
+- (void) releaseSubviews {
+    [spinner_ release];
+    spinner_ = nil;
+
+    [status_ release];
+    status_ = nil;
+
+    [caption_ release];
+    caption_ = nil;
 }
+
 @end
 /* }}} */
 
@@ -8246,7 +8415,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             message:UCLocalize("HALFINSTALLED_PACKAGE_EX")
             delegate:self
             cancelButtonTitle:UCLocalize("FORCIBLY_CLEAR")
-            otherButtonTitles:UCLocalize("TEMPORARY_IGNORE"), nil
+            otherButtonTitles:
+                UCLocalize("TEMPORARY_IGNORE"),
+            nil
         ] autorelease];
 
         [alert setContext:@"fixhalf"];
@@ -8259,7 +8430,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             message:UCLocalize("ESSENTIAL_UPGRADE_EX")
             delegate:self
             cancelButtonTitle:UCLocalize("TEMPORARY_IGNORE")
-            otherButtonTitles:UCLocalize("UPGRADE_ESSENTIAL"), UCLocalize("COMPLETE_UPGRADE"), nil
+            otherButtonTitles:
+                UCLocalize("UPGRADE_ESSENTIAL"),
+                UCLocalize("COMPLETE_UPGRADE"),
+            nil
         ] autorelease];
 
         [alert setContext:@"upgrade"];
@@ -8328,8 +8502,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if (recently || loaded_ || ManualRefresh) {
         [self performSelectorOnMainThread:@selector(_loaded) withObject:nil waitUntilDone:NO];
 
-        // If we are cancelling due to ManualRefresh or a recent refresh
-        // we need to make sure it knows it's already loaded.
+        // If we are cancelling, we need to make sure it knows it's already loaded.
         loaded_ = true;
         return;
     } else {
@@ -8564,41 +8737,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [self complete];
 }
 
-- (void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
-    CYNavigationController *controller = (CYNavigationController *) viewController;
-
-    if ([[controller viewControllers] count] == 0) {
-        int index = [tabbar_ selectedIndex];
-        CYViewController *root = nil;
-
-        if (index == 0)
-            root = [[[HomeController alloc] init] autorelease];
-        else if (index == 1)
-            root = [[[SectionsController alloc] initWithDatabase:database_] autorelease];
-        else if (index == 2)
-            root = [[[ChangesController alloc] initWithDatabase:database_ delegate:self] autorelease];
-
-        if (IsWildcat_) {
-            if (index == 3)
-                root = [[[InstalledController alloc] initWithDatabase:database_] autorelease];
-            else if (index == 4)
-                root = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
-            else if (index == 5)
-                root = [[[SearchController alloc] initWithDatabase:database_] autorelease];
-        } else {
-            if (index == 3)
-                root = [[[ManageController alloc] init] autorelease];
-            else if (index == 4)
-                root = [[[SearchController alloc] initWithDatabase:database_] autorelease];
-        }
-
-        [root setDelegate:self];
-
-        if (root != nil)
-            [controller setViewControllers:[NSArray arrayWithObject:root]];
-    }
-}
-
 - (void) showSettings {
     SettingsController *role = [[[SettingsController alloc] initWithDatabase:database_ delegate:self] autorelease];
     CYNavigationController *nav = [[[CYNavigationController alloc] initWithRootViewController:role] autorelease];
@@ -8617,12 +8755,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [self setNetworkActivityIndicatorVisible:NO];
 }
 
-- (void) setPackageController:(CYPackageController *)view {
-    WebThreadLock();
-    [view setPackage:nil];
-    WebThreadUnlock();
-}
-
 - (void) cancelAndClear:(bool)clear {
     @synchronized (self) {
         if (clear) {
@@ -8640,7 +8772,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     NSString *context([alert context]);
 
     if ([context isEqualToString:@"fixhalf"]) {
-        if (button == [alert firstOtherButtonIndex]) {
+        if (button == [alert cancelButtonIndex]) {
             @synchronized (self) {
                 for (Package *broken in broken_) {
                     [broken remove];
@@ -8655,7 +8787,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 [self resolve];
                 [self perform];
             }
-        } else if (button == [alert cancelButtonIndex]) {
+        } else if (button == [alert firstOtherButtonIndex]) {
             [broken_ removeAllObjects];
             [self _loaded];
         }
@@ -8743,17 +8875,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (CYViewController *) pageForPackage:(NSString *)name {
-    if (Package *package = [database_ packageWithName:name]) {
-        CYPackageController *view = [[[CYPackageController alloc] initWithDatabase:database_] autorelease];
-        [view setPackage:package];
-        return view;
-    } else {
-        NSURL *url([NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"unknown" ofType:@"html"]]);
-        url = [NSURL URLWithString:[[url absoluteString] stringByAppendingString:[NSString stringWithFormat:@"?%@", name]]];
-        CYBrowserController *browser = [[[CYBrowserController alloc] init] autorelease];
-        [browser loadURL:url];
-        return browser;
-    }
+    return [[[CYPackageController alloc] initWithDatabase:database_ forPackage:name] autorelease];
 }
 
 - (CYViewController *) pageForURL:(NSURL *)url {
@@ -8763,7 +8885,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     NSString *path([[url absoluteString] substringFromIndex:[scheme length] + 3]);
     NSArray *components([path pathComponents]);
 
-    if ([scheme isEqualToString:@"apptapp"] && [components count] && [[components objectAtIndex:0] isEqualToString:@"package"])
+    if ([scheme isEqualToString:@"apptapp"] && [components count] > 0 && [[components objectAtIndex:0] isEqualToString:@"package"])
         return [self pageForPackage:[components objectAtIndex:1]];
 
     if ([components count] < 1 || ![scheme isEqualToString:@"cydia"])
@@ -8771,82 +8893,76 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     NSString *base([components objectAtIndex:0]);
 
-    if ([components count] == 1) {
-        if ([base isEqualToString:@"storage"]) {
-            CYBrowserController *browser = [[[CYBrowserController alloc] init] autorelease];
-            [browser loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"storage" ofType:@"html"]]];
-            return browser;
+    CYViewController *controller = nil;
+
+    if ([base isEqualToString:@"url"]) {
+        // This kind of URL can contain slashes in the argument, so we can't parse them below.
+        NSString *destination = [[url absoluteString] substringFromIndex:([scheme length] + [@"://" length] + [base length] + [@"/" length])];
+        controller = [[[CYBrowserController alloc] initWithURL:[NSURL URLWithString:destination]] autorelease];
+    } else if ([components count] == 1) {
+        if ([base isEqualToString:@"manage"]) {
+            controller = [[[ManageController alloc] init] autorelease];
         }
 
         if ([base isEqualToString:@"sources"]) {
-            SourcesController *source = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
-            return source;
+            controller = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
         }
 
         if ([base isEqualToString:@"home"]) {
-            HomeController *home = [[[HomeController alloc] init] autorelease];
-            return home;
+            controller = [[[HomeController alloc] init] autorelease];
         }
 
         if ([base isEqualToString:@"sections"]) {
-            SectionsController *sections = [[[SectionsController alloc] initWithDatabase:database_] autorelease];
-            return sections;
+            controller = [[[SectionsController alloc] initWithDatabase:database_] autorelease];
         }
 
         if ([base isEqualToString:@"search"]) {
-            SearchController *search = [[[SearchController alloc] initWithDatabase:database_] autorelease];
-            return search;
+            controller = [[[SearchController alloc] initWithDatabase:database_] autorelease];
         }
 
         if ([base isEqualToString:@"changes"]) {
-            ChangesController *changes = [[[ChangesController alloc] initWithDatabase:database_ delegate:self] autorelease];
-            return changes;
+            controller = [[[ChangesController alloc] initWithDatabase:database_] autorelease];
         }
 
         if ([base isEqualToString:@"installed"]) {
-            InstalledController *installed = [[[InstalledController alloc] initWithDatabase:database_] autorelease];
-            return installed;
+            controller = [[[InstalledController alloc] initWithDatabase:database_] autorelease];
         }
     } else if ([components count] == 2) {
         NSString *argument = [components objectAtIndex:1];
 
         if ([base isEqualToString:@"package"]) {
-            CYViewController *package = [self pageForPackage:argument];
-            return package;
+            controller = [self pageForPackage:argument];
         }
 
         if ([base isEqualToString:@"search"]) {
-            SearchController *search = [[[SearchController alloc] initWithDatabase:database_] autorelease];
-            [search setSearchTerm:argument];
-            return search;
+            controller = [[[SearchController alloc] initWithDatabase:database_] autorelease];
+            [(SearchController *)controller setSearchTerm:argument];
         }
 
         if ([base isEqualToString:@"sections"]) {
             if ([argument isEqualToString:@"all"])
                 argument = nil;
-            SectionController *section = [[[SectionController alloc] initWithDatabase:database_ section:argument] autorelease];
-            [section setDelegate:self];
-            return section;
+            controller = [[[SectionController alloc] initWithDatabase:database_ section:argument] autorelease];
         }
 
         if ([base isEqualToString:@"sources"]) {
             if ([argument isEqualToString:@"add"]) {
-                SourcesController *source = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
-                [source showAddSourcePrompt];
-                return source;
+                controller = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
+                [(SourcesController *)controller showAddSourcePrompt];
             } else {
-                // XXX: Create page of the source specfified.
+                NSArray *sources = [database_ sources];
+                for (Source *source in sources) {
+                    if ([[source name] caseInsensitiveCompare:argument] == NSOrderedSame) {
+                        controller = [[[SourceController alloc] initWithDatabase:database_ source:source] autorelease];
+                        break;
+                    }
+                }
             }
         }
 
-        if ([base isEqualToString:@"url"]) {
-            CYBrowserController *browser = [[[CYBrowserController alloc] init] autorelease];
-            [browser loadURL:[NSURL URLWithString:argument]];
-            return browser;
-        }
-
         if ([base isEqualToString:@"launch"]) {
             [self launchApplicationWithIdentifier:argument suspended:NO];
+            return nil;
         }
     } else if ([components count] == 3) {
         NSString *arg1 = [components objectAtIndex:1];
@@ -8854,20 +8970,18 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
         if ([base isEqualToString:@"package"]) {
             if ([arg2 isEqualToString:@"settings"]) {
-                return [[[PackageSettingsController alloc] initWithDatabase:database_ package:arg1] autorelease];
-            } else if ([arg2 isEqualToString:@"signature"]) {
-                return [[[SignatureController alloc] initWithDatabase:database_ package:arg1] autorelease];
+                controller = [[[PackageSettingsController alloc] initWithDatabase:database_ package:arg1] autorelease];
             } else if ([arg2 isEqualToString:@"files"]) {
                 if (Package *package = [database_ packageWithName:arg1]) {
-                    FileTable *files = [[[FileTable alloc] initWithDatabase:database_] autorelease];
-                    [files setPackage:package];
-                    return files;
+                    controller = [[[FileTable alloc] initWithDatabase:database_] autorelease];
+                    [(FileTable *)controller setPackage:package];
                 }
             }
         }
     }
 
-    return nil;
+    [controller setDelegate:self];
+    return controller;
 }
 
 - (BOOL) openCydiaURL:(NSURL *)url {
@@ -8876,7 +8990,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if (page != nil) {
         CYNavigationController *nav = [[[CYNavigationController alloc] init] autorelease];
         [nav setViewControllers:[NSArray arrayWithObject:page]];
-        [tabbar_ setTransientViewController:nav];
+        [tabbar_ setUnselectedViewController:nav];
     }
 
     return page != nil;
@@ -8898,6 +9012,15 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [super applicationWillResignActive:application];
 }
 
+- (void) applicationWillTerminate:(UIApplication *)application {
+    Changed_ = true;
+    [Metadata_ setObject:[tabbar_ navigationURLCollection] forKey:@"InterfaceState"];
+    [Metadata_ setObject:[NSDate date] forKey:@"LastClosed"];
+    [Metadata_ setObject:[NSNumber numberWithInt:[tabbar_ selectedIndex]] forKey:@"InterfaceIndex"];
+
+    [self _saveConfig];
+}
+
 - (void) addStashController {
     ++locked_;
     stash_ = [[StashController alloc] init];
@@ -8932,7 +9055,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (void) setupViewControllers {
     tabbar_ = [[CYTabBarController alloc] initWithDatabase:database_];
-    [tabbar_ setDelegate:self];
 
     NSMutableArray *items([NSMutableArray arrayWithObjects:
         [[[UITabBarItem alloc] initWithTitle:@"Cydia" image:[UIImage applicationImageNamed:@"home.png"] tag:0] autorelease],
@@ -8957,16 +9079,22 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [tabbar_ setViewControllers:controllers];
 
     [tabbar_ setUpdateDelegate:self];
-    [window_ addSubview:[tabbar_ view]];
 }
 
-- (void)showEmulatedLoadingControllerInView:(UIView *)view {
-    static CYEmulatedLoadingController *fake = [[CYEmulatedLoadingController alloc] init];
+- (CYEmulatedLoadingController *)showEmulatedLoadingControllerInView:(UIView *)view {
+    static CYEmulatedLoadingController *fake = nil;
+
     if (view != nil) {
+        if (fake == nil)
+            fake = [[CYEmulatedLoadingController alloc] initWithDatabase:database_];
         [view addSubview:[fake view]];
     } else {
         [[fake view] removeFromSuperview];
+        [fake release];
+        fake = nil;
     }
+
+    return fake;
 }
 
 - (void) applicationDidFinishLaunching:(id)unused {
@@ -9033,19 +9161,79 @@ _trace();
 - (void) loadData {
 _trace();
     if (Role_ == nil) {
-        [self showSettings];
+        [window_ setUserInteractionEnabled:YES];
+
+        SettingsController *role = [[[SettingsController alloc] initWithDatabase:database_ delegate:self] autorelease];
+        CYNavigationController *nav = [[[CYNavigationController alloc] initWithRootViewController:role] autorelease];
+        if (IsWildcat_)
+            [nav setModalPresentationStyle:UIModalPresentationFormSheet];
+        [[self showEmulatedLoadingControllerInView:window_] presentModalViewController:nav animated:YES];
+
         return;
+    } else {
+        if ([[self showEmulatedLoadingControllerInView:window_] modalViewController] != nil)
+            [[self showEmulatedLoadingControllerInView:window_] dismissModalViewControllerAnimated:YES];
+        [window_ setUserInteractionEnabled:NO];
     }
 
     [self reloadData];
     PrintTimes();
 
+    [window_ addSubview:[tabbar_ view]];
     [self showEmulatedLoadingControllerInView:nil];
     [window_ setUserInteractionEnabled:YES];
 
-    // Show the home page.
-    CYNavigationController *navigation = [[tabbar_ viewControllers] objectAtIndex:0];
-    [navigation setViewControllers:[NSArray arrayWithObject:[[[HomeController alloc] init] autorelease]]];
+    int selectedIndex = 0;
+    NSMutableArray *items = nil;
+
+    bool recently = false;
+    NSDate *closed([Metadata_ objectForKey:@"LastClosed"]);
+    if (closed != nil) {
+        NSTimeInterval interval([closed timeIntervalSinceNow]);
+        // XXX: Is 15 minutes the optimal time here?
+        if (interval <= 0 && interval > -(15*60))
+            recently = true;
+    }
+
+    items = [[Metadata_ objectForKey:@"InterfaceState"] mutableCopy];
+    selectedIndex = [[Metadata_ objectForKey:@"InterfaceIndex"] intValue];
+
+    BOOL enough = YES;
+    for (NSArray *entry in items)
+        if ([entry count] <= 0)
+            enough = NO;
+
+    if (!recently || !items || !enough) {
+        selectedIndex = 0;
+        items = [NSMutableArray array];
+        [items addObject:[NSArray arrayWithObject:@"cydia://home"]];
+        [items addObject:[NSArray arrayWithObject:@"cydia://sections"]];
+        [items addObject:[NSArray arrayWithObject:@"cydia://changes"]];
+        if (!IsWildcat_) {
+            [items addObject:[NSArray arrayWithObject:@"cydia://manage"]];
+        } else {
+            [items addObject:[NSArray arrayWithObject:@"cydia://installed"]];
+            [items addObject:[NSArray arrayWithObject:@"cydia://sources"]];
+        }
+        [items addObject:[NSArray arrayWithObject:@"cydia://search"]];
+    }
+
+    [tabbar_ setSelectedIndex:selectedIndex];
+    for (unsigned int tab = 0; tab < [[tabbar_ viewControllers] count]; tab++) {
+        NSArray *stack = [items objectAtIndex:tab];
+        CYNavigationController *navigation = [[tabbar_ viewControllers] objectAtIndex:tab];
+        NSMutableArray *current = [NSMutableArray array];
+
+        for (unsigned int nav = 0; nav < [stack count]; nav++) {
+            NSString *addr = [stack objectAtIndex:nav];
+            NSURL *url = [NSURL URLWithString:addr];
+            CYViewController *page = [self pageForURL:url];
+            if (page != nil)
+                [current addObject:page];
+        }
+
+        [navigation setViewControllers:current];
+    }
 
     // (Try to) show the startup URL.
     if (starturl_ != nil) {
@@ -9108,6 +9296,33 @@ MSHook(void, UIHardware$_playSystemSound$, Class self, SEL _cmd, int sound) {
     }
 }
 
+Class $UIApplication;
+
+MSHook(void, UIApplication$_updateApplicationAccessibility, UIApplication *self, SEL _cmd) {
+    static BOOL initialized = NO;
+    static BOOL started = NO;
+
+    NSDictionary *dict([[[NSDictionary alloc] initWithContentsOfFile:@"/var/mobile/Library/Preferences/com.apple.Accessibility.plist"] autorelease]);
+    BOOL enabled = [[dict objectForKey:@"VoiceOverTouchEnabled"] boolValue] || [[dict objectForKey:@"VoiceOverTouchEnabledByiTunes"] boolValue];
+
+    if ([self respondsToSelector:@selector(_accessibilityBundlePrincipalClass)]) {
+        id bundle = [self performSelector:@selector(_accessibilityBundlePrincipalClass)];
+        if (![bundle respondsToSelector:@selector(_accessibilityStopServer)]) return;
+        if (![bundle respondsToSelector:@selector(_accessibilityStartServer)]) return;
+
+        if (initialized && !enabled) {
+            initialized = NO;
+            [bundle performSelector:@selector(_accessibilityStopServer)];
+        } else if (enabled) {
+            initialized = YES;
+            if (!started) {
+                started = YES;
+                [bundle performSelector:@selector(_accessibilityStartServer)];
+            }
+        }
+    }
+}
+
 int main(int argc, char *argv[]) { _pooled
     _trace();
 
@@ -9117,6 +9332,18 @@ int main(int argc, char *argv[]) { _pooled
     } else
         IsWildcat_ = false;
 
+    UIScreen *screen([UIScreen mainScreen]);
+    if ([screen respondsToSelector:@selector(scale)])
+        ScreenScale_ = [screen scale];
+    else
+        ScreenScale_ = 1;
+
+    NSMutableArray *parts([NSMutableArray arrayWithCapacity:2]);
+    if (ScreenScale_ > 1)
+        [parts addObject:@"@2x"];
+    [parts addObject:(IsWildcat_ ? @"~ipad" : @"~iphone")];
+    UI_ = CydiaURL([NSString stringWithFormat:@"ui/ios%@", [parts componentsJoinedByString:@""]]);
+
     PackageName = reinterpret_cast<CYString &(*)(Package *, SEL)>(method_getImplementation(class_getInstanceMethod([Package class], @selector(cyname))));
 
     /* Library Hacks {{{ */
@@ -9135,6 +9362,13 @@ int main(int argc, char *argv[]) { _pooled
         _UIHardware$_playSystemSound$ = reinterpret_cast<void (*)(Class, SEL, int)>(method_getImplementation(UIHardware$_playSystemSound$));
         method_setImplementation(UIHardware$_playSystemSound$, reinterpret_cast<IMP>(&$UIHardware$_playSystemSound$));
     }
+
+    $UIApplication = objc_getClass("UIApplication");
+    Method UIApplication$_updateApplicationAccessibility(class_getInstanceMethod($UIApplication, @selector(_updateApplicationAccessibility)));
+    if (UIApplication$_updateApplicationAccessibility != NULL) {
+        _UIApplication$_updateApplicationAccessibility = reinterpret_cast<void (*)(UIApplication *, SEL)>(method_getImplementation(UIApplication$_updateApplicationAccessibility));
+        method_setImplementation(UIApplication$_updateApplicationAccessibility, reinterpret_cast<IMP>(&$UIApplication$_updateApplicationAccessibility));
+    }
     /* }}} */
     /* Set Locale {{{ */
     Locale_ = CFLocaleCopyCurrent();
@@ -9308,11 +9542,15 @@ int main(int argc, char *argv[]) { _pooled
     _trace();
 
     if (Packages_ != nil) {
-        CFDictionaryApplyFunction((CFDictionaryRef) Packages_, &PackageImport, NULL);
+        bool fail(false);
+        CFDictionaryApplyFunction((CFDictionaryRef) Packages_, &PackageImport, &fail);
         _trace();
-        [Metadata_ removeObjectForKey:@"Packages"];
-        Packages_ = nil;
-        Changed_ = true;
+
+        if (!fail) {
+            [Metadata_ removeObjectForKey:@"Packages"];
+            Packages_ = nil;
+            Changed_ = true;
+        }
     }
 
     Finishes_ = [NSArray arrayWithObjects:@"return", @"reopen", @"restart", @"reload", @"reboot", nil];