From 686e302fd0fed7758b531ad8329d60963a622a4e Mon Sep 17 00:00:00 2001 From: "Jay Freeman (saurik)" Date: Sun, 3 Feb 2008 07:15:54 +0000 Subject: [PATCH] Drastic upgrades to nearly everything. --- Cydia.mm | 2921 +++++++++++++++++++++++++++-------------- Preferences.mm | 438 ++++++ data/changes-dn.png | Bin 0 -> 2211 bytes data/changes-up.png | Bin 0 -> 1724 bytes data/folder.png | Bin 0 -> 1998 bytes data/icon.png | Bin 4440 -> 4158 bytes data/install-dn.png | Bin 1077 -> 2773 bytes data/install-up.png | Bin 913 -> 2174 bytes data/manage-dn.png | Bin 0 -> 1687 bytes data/manage-up.png | Bin 0 -> 1326 bytes data/reload.png | Bin 0 -> 976 bytes data/search-dn.png | Bin 0 -> 1707 bytes data/search-up.png | Bin 0 -> 1333 bytes data/sources-dn.png | Bin 1015 -> 0 bytes data/sources-up.png | Bin 428 -> 0 bytes data/uninstall-dn.png | Bin 971 -> 0 bytes data/uninstall-up.png | Bin 677 -> 0 bytes data/upgrade-dn.png | Bin 1298 -> 0 bytes data/upgrade-up.png | Bin 905 -> 0 bytes internals.h | 9 + makefile | 2 +- 21 files changed, 2349 insertions(+), 1021 deletions(-) create mode 100644 Preferences.mm create mode 100644 data/changes-dn.png create mode 100644 data/changes-up.png create mode 100644 data/folder.png create mode 100644 data/manage-dn.png create mode 100644 data/manage-up.png create mode 100644 data/reload.png create mode 100644 data/search-dn.png create mode 100644 data/search-up.png delete mode 100644 data/sources-dn.png delete mode 100644 data/sources-up.png delete mode 100644 data/uninstall-dn.png delete mode 100644 data/uninstall-up.png delete mode 100644 data/upgrade-dn.png delete mode 100644 data/upgrade-up.png create mode 100644 internals.h diff --git a/Cydia.mm b/Cydia.mm index a8ef907d..470f276b 100644 --- a/Cydia.mm +++ b/Cydia.mm @@ -20,6 +20,12 @@ #include +extern "C" { +#include +} + +#include + #include #include #include @@ -34,18 +40,141 @@ } \ while (false) /* }}} */ - +/* Miscellaneous Messages {{{ */ @interface WebView - (void) setApplicationNameForUserAgent:(NSString *)applicationName; @end +@interface NSString (Cydia) +- (NSString *) stringByAddingPercentEscapes; +- (NSString *) stringByReplacingCharacter:(unsigned short)arg0 withCharacter:(unsigned short)arg1; +@end +/* }}} */ + +/* Reset View (UIView) {{{ */ +@interface UIView (CYResetView) +- (void) resetViewAnimated:(BOOL)animated; +@end + +@implementation UIView (CYResetView) + +- (void) resetViewAnimated:(BOOL)animated { + fprintf(stderr, "%s\n", self->isa->name); + _assert(false); +} + +@end +/* }}} */ +/* Reset View (UITable) {{{ */ +@interface UITable (CYResetView) +- (void) resetViewAnimated:(BOOL)animated; +@end + +@implementation UITable (CYResetView) + +- (void) resetViewAnimated:(BOOL)animated { + [self selectRow:-1 byExtendingSelection:NO withFade:animated]; +} + +@end +/* }}} */ +/* Reset View (UISectionList) {{{ */ +@interface UISectionList (CYResetView) +- (void) resetViewAnimated:(BOOL)animated; +@end + +@implementation UISectionList (CYResetView) + +- (void) resetViewAnimated:(BOOL)animated { + [[self table] resetViewAnimated:animated]; +} + +@end +/* }}} */ + +/* Perl-Compatible RegEx {{{ */ +class Pcre { + private: + pcre *code_; + pcre_extra *study_; + int capture_; + int *matches_; + const char *data_; + + public: + Pcre(const char *regex) : + study_(NULL) + { + const char *error; + int offset; + code_ = pcre_compile(regex, 0, &error, &offset, NULL); + + if (code_ == NULL) { + fprintf(stderr, "%d:%s\n", offset, error); + _assert(false); + } + + pcre_fullinfo(code_, study_, PCRE_INFO_CAPTURECOUNT, &capture_); + matches_ = new int[(capture_ + 1) * 3]; + } + + ~Pcre() { + pcre_free(code_); + delete matches_; + } + + NSString *operator [](size_t match) { + return [NSString + stringWithCString:(data_ + matches_[match * 2]) + length:(matches_[match * 2 + 1] - matches_[match * 2]) + ]; + } + + bool operator ()(const char *data, size_t size) { + data_ = data; + return pcre_exec(code_, study_, data, size, 0, 0, matches_, (capture_ + 1) * 3) >= 0; + } +}; +/* }}} */ +/* CoreGraphicsServices Primitives {{{ */ +class CGColor { + private: + CGColorRef color_; + + public: + CGColor(CGColorSpaceRef space, float red, float green, float blue, float alpha) { + float color[] = {red, green, blue, alpha}; + color_ = CGColorCreate(space, color); + } + + ~CGColor() { + CGColorRelease(color_); + } + + operator CGColorRef() { + return color_; + } +}; + +class GSFont { + private: + GSFontRef font_; + + public: + ~GSFont() { + /* XXX: no GSFontRelease()? */ + CFRelease(font_); + } +}; +/* }}} */ + static const int PulseInterval_ = 50000; const char *Machine_ = NULL; const char *SerialNumber_ = NULL; -@interface NSString (CydiaBypass) -- (NSString *) stringByAddingPercentEscapes; -@end +static NSMutableDictionary *Metadata_; +static NSMutableDictionary *Packages_; +static NSDate *now_; @protocol ProgressDelegate - (void) setError:(NSString *)error; @@ -66,6 +195,32 @@ NSString *SizeString(double size) { return [NSString stringWithFormat:@"%.1f%s", size, powers_[power]]; } +static const float TextViewOffset_ = 22; + +UITextView *GetTextView(NSString *value, float left, bool html) { + UITextView *text([[[UITextView alloc] initWithFrame:CGRectMake(left, 3, 310 - left, 1000)] autorelease]); + [text setEditable:NO]; + [text setTextSize:16]; + if (html) + [text setHTML:value]; + else + [text setText:value]; + [text setEnabled:NO]; + + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + CGColor clear(space, 0, 0, 0, 0); + [text setBackgroundColor:clear]; + CGColorSpaceRelease(space); + + CGRect frame = [text frame]; + [text setFrame:frame]; + CGRect rect = [text visibleTextRect]; + frame.size.height = rect.size.height; + [text setFrame:frame]; + + return text; +} + /* Status Delegation {{{ */ class Status : public pkgAcquireStatus @@ -169,6 +324,8 @@ extern NSString *kUIButtonBarButtonTitleWidth; extern NSString *kUIButtonBarButtonType; /* }}} */ /* Mime Addresses {{{ */ +Pcre email_r("^\"?(.*)\"? <([^>]*)>$"); + @interface Address : NSObject { NSString *name_; NSString *email_; @@ -206,26 +363,12 @@ extern NSString *kUIButtonBarButtonType; - (Address *) initWithString:(NSString *)string { if ((self = [super init]) != nil) { - const char *error; - int offset; - pcre *code = pcre_compile("^\"?(.*)\"? <([^>]*)>$", 0, &error, &offset, NULL); - - if (code == NULL) { - fprintf(stderr, "%d:%s\n", offset, error); - _assert(false); - } - - pcre_extra *study = NULL; - int capture; - pcre_fullinfo(code, study, PCRE_INFO_CAPTURECOUNT, &capture); - int matches[(capture + 1) * 3]; - - size_t size = [string length]; const char *data = [string UTF8String]; + size_t size = [string length]; - if (pcre_exec(code, study, data, size, 0, 0, matches, sizeof(matches) / sizeof(matches[0])) >= 0) { - name_ = [[NSString stringWithCString:(data + matches[2]) length:(matches[3] - matches[2])] retain]; - email_ = [[NSString stringWithCString:(data + matches[4]) length:(matches[5] - matches[4])] retain]; + if (email_r(data, size)) { + name_ = [email_r[1] retain]; + email_ = [email_r[2] retain]; } else { name_ = [[NSString stringWithCString:data length:size] retain]; email_ = nil; @@ -286,6 +429,7 @@ inline float interpolate(float begin, float end, float fraction) { @class Package; +/* Database Interface {{{ */ @interface Database : NSObject { pkgCacheFile cache_; pkgRecords *records_; @@ -300,6 +444,8 @@ inline float interpolate(float begin, float end, float fraction) { int statusfd_; } +- (void) dealloc; + - (void) _readStatus:(NSNumber *)fd; - (void) _readOutput:(NSNumber *)fd; @@ -319,53 +465,139 @@ inline float interpolate(float begin, float end, float fraction) { - (void) setDelegate:(id)delegate; @end +/* }}} */ /* Reset View {{{ */ @interface ResetView : UIView { + UIPushButton *reload_; + NSMutableArray *views_; UINavigationBar *navbar_; + UITransitionView *transition_; bool resetting_; + id delegate_; } +- (void) dealloc; + - (void) navigationBar:(UINavigationBar *)navbar poppedItem:(UINavigationItem *)item; -- (void) dealloc; +- (id) initWithFrame:(CGRect)frame; +- (void) setDelegate:(id)delegate; +- (void) reloadPushed; + +- (void) pushView:(UIView *)view withTitle:(NSString *)title backButtonTitle:(NSString *)back rightButton:(NSString *)right; +- (void) popViews:(unsigned)views; - (void) resetView; - (void) _resetView; -- (NSString *) leftTitle; -- (NSString *) rightTitle; +- (void) setPrompt; @end @implementation ResetView +- (void) dealloc { + [reload_ release]; + [transition_ release]; + [navbar_ release]; + [views_ release]; + [super dealloc]; +} + - (void) navigationBar:(UINavigationBar *)navbar poppedItem:(UINavigationItem *)item { - if ([[navbar_ navigationItems] count] == 1) + [views_ removeLastObject]; + UIView *view([views_ lastObject]); + [view resetViewAnimated:!resetting_]; + if (!resetting_) + [transition_ transition:2 toView:view]; + + if ([views_ count] == 1) [self _resetView]; } -- (void) dealloc { - [navbar_ release]; - [super dealloc]; +- (id) initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame]) != nil) { + views_ = [[NSMutableArray arrayWithCapacity:4] retain]; + + struct CGRect bounds = [self bounds]; + CGSize navsize = [UINavigationBar defaultSizeWithPrompt]; + CGRect navrect = {{0, 0}, navsize}; + + navbar_ = [[UINavigationBar alloc] initWithFrame:navrect]; + [self addSubview:navbar_]; + + [navbar_ setBarStyle:1]; + [navbar_ setDelegate:self]; + + transition_ = [[UITransitionView alloc] initWithFrame:CGRectMake( + bounds.origin.x, bounds.origin.y + navsize.height, bounds.size.width, bounds.size.height - navsize.height + )]; + + //reload_ = [[UIPushButton alloc] initWithFrame:CGRectMake(284, 8, 29, 23)]; + reload_ = [[UIPushButton alloc] initWithFrame:CGRectMake(282, 5, 29, 23)]; + [reload_ setShowPressFeedback:YES]; + [reload_ setImage:[UIImage applicationImageNamed:@"reload.png"]]; + [reload_ addTarget:self action:@selector(reloadPushed) forEvents:1]; + + [navbar_ addSubview:reload_]; + + [self addSubview:transition_]; + } return self; +} + +- (void) setDelegate:(id)delegate { + delegate_ = delegate; +} + +- (void) reloadPushed { + [delegate_ update]; +} + +- (void) pushView:(UIView *)view withTitle:(NSString *)title backButtonTitle:(NSString *)back rightButton:(NSString *)right { + UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:title] autorelease]; + [navbar_ pushNavigationItem:navitem]; + [navitem setBackButtonTitle:back]; + + [navbar_ showButtonsWithLeftTitle:nil rightTitle:right]; + + [transition_ transition:([views_ count] == 0 ? 0 : 1) toView:view]; + [views_ addObject:view]; +} + +- (void) popViews:(unsigned)views { + resetting_ = true; + for (unsigned i(0); i != views; ++i) + [navbar_ popNavigationItem]; + resetting_ = false; + + [transition_ transition:2 toView:[views_ lastObject]]; } - (void) resetView { resetting_ = true; if ([[navbar_ navigationItems] count] == 1) [self _resetView]; - else while ([[navbar_ navigationItems] count] != 1) + else do [navbar_ popNavigationItem]; + while ([[navbar_ navigationItems] count] != 1); resetting_ = false; + + [transition_ transition:0 toView:[views_ lastObject]]; } - (void) _resetView { - [navbar_ showButtonsWithLeftTitle:[self leftTitle] rightTitle:[self rightTitle]]; } -- (NSString *) leftTitle { - return nil; -} +- (void) setPrompt { + NSDate *update = [Metadata_ objectForKey:@"LastUpdate"]; -- (NSString *) rightTitle { - return nil; + CFLocaleRef locale = CFLocaleCopyCurrent(); + CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, locale, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle); + CFStringRef formatted = CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) update); + + [navbar_ setPrompt:[NSString stringWithFormat:@"Last Updated: %@", (NSString *) formatted]]; + + CFRelease(formatter); + CFRelease(formatted); + CFRelease(locale); } @end @@ -375,24 +607,13 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString if ([packages count] == 0) return; - CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); - float clear[] = {0, 0, 0, 0}; - float blue[] = {0, 0, 0.4, 1}; - - UITextView *text([[[UITextView alloc] initWithFrame: CGRectMake(110, 3, 200, 60)] autorelease]); - [text setEditable:NO]; - [text setTextSize:16]; - [text setBackgroundColor:CGColorCreate(space, clear)]; - [text setTextColor:CGColorCreate(space, blue)]; - [text setText:([packages count] == 0 ? @"n/a" : [packages componentsJoinedByString:@", "])]; - [text setEnabled:NO]; - - CGRect frame([text frame]); - CGSize size([text contentSize]); - frame.size.height = size.height; - [text setFrame:frame]; - + UITextView *text = GetTextView([packages count] == 0 ? @"n/a" : [packages componentsJoinedByString:@", "], 110, false); [fields setObject:text forKey:key]; + + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + CGColor blue(space, 0, 0, 0.4, 1); + [text setTextColor:blue]; + CGColorSpaceRelease(space); } @protocol ConfirmationViewDelegate @@ -498,7 +719,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString return proposed; else { _assert(size_t(row) < [fields_ count]); - return [[[fields_ allValues] objectAtIndex:row] contentSize].height; + return [[[fields_ allValues] objectAtIndex:row] visibleTextRect].size.height + TextViewOffset_; } } @@ -632,26 +853,78 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString /* }}} */ /* Package Class {{{ */ +NSString *Scour(const char *field, const char *begin, const char *end) { + size_t i(0), l(strlen(field)); + + for (;;) { + const char *name = begin + i; + const char *colon = name + l; + const char *value = colon + 1; + + if ( + value < end && + *colon == ':' && + memcmp(name, field, l) == 0 + ) { + while (value != end && value[0] == ' ') + ++value; + const char *line = std::find(value, end, '\n'); + while (line != value && line[-1] == ' ') + --line; + return [NSString stringWithCString:value length:(line - value)]; + } else { + begin = std::find(begin, end, '\n'); + if (begin == end) + return nil; + ++begin; + } + } +} + @interface Package : NSObject { pkgCache::PkgIterator iterator_; Database *database_; - pkgRecords::Parser *parser_; pkgCache::VerIterator version_; pkgCache::VerFileIterator file_; + + NSString *latest_; + NSString *installed_; + + NSString *id_; + NSString *name_; + NSString *tagline_; + NSString *icon_; + NSString *bundle_; } +- (void) dealloc; + - (Package *) initWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database version:(pkgCache::VerIterator)version file:(pkgCache::VerFileIterator)file; + (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database; -- (NSString *) name; - (NSString *) section; -- (NSString *) latest; -- (NSString *) installed; - (Address *) maintainer; - (size_t) size; -- (NSString *) tagline; - (NSString *) description; +- (NSString *) index; + +- (NSDate *) seen; + +- (NSString *) latest; +- (NSString *) installed; +- (BOOL) upgradable; + +- (NSString *) id; +- (NSString *) name; +- (NSString *) tagline; +- (NSString *) icon; +- (NSString *) bundle; + +- (BOOL) matches:(NSString *)text; + +- (NSComparisonResult) compareByName:(Package *)package; - (NSComparisonResult) compareBySectionAndName:(Package *)package; +- (NSComparisonResult) compareForChanges:(Package *)package; - (void) install; - (void) remove; @@ -659,6 +932,22 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString @implementation Package +- (void) dealloc { + [latest_ release]; + if (installed_ != nil) + [installed_ release]; + + [id_ release]; + if (name_ != nil) + [name_ release]; + [tagline_ release]; + if (icon_ != nil) + [icon_ release]; + if (bundle_ != nil) + [bundle_ release]; + [super dealloc]; +} + - (Package *) initWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database version:(pkgCache::VerIterator)version file:(pkgCache::VerFileIterator)file { if ((self = [super init]) != nil) { iterator_ = iterator; @@ -666,7 +955,35 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString version_ = version; file_ = file; - parser_ = &[database_ records]->Lookup(file); + + pkgRecords::Parser *parser = &[database_ records]->Lookup(file_); + + const char *begin, *end; + parser->GetRec(begin, end); + + latest_ = [[NSString stringWithCString:version_.VerStr()] retain]; + installed_ = iterator_.CurrentVer().end() ? nil : [[NSString stringWithCString:iterator_.CurrentVer().VerStr()] retain]; + + id_ = [[[NSString stringWithCString:iterator_.Name()] lowercaseString] retain]; + name_ = Scour("Name", begin, end); + if (name_ != nil) + name_ = [name_ retain]; + tagline_ = [[NSString stringWithCString:parser->ShortDesc().c_str()] retain]; + icon_ = Scour("Icon", begin, end); + if (icon_ != nil) + icon_ = [icon_ retain]; + bundle_ = Scour("Bundle", begin, end); + if (bundle_ != nil) + bundle_ = [bundle_ retain]; + + NSMutableDictionary *metadata = [Packages_ objectForKey:id_]; + if (metadata == nil) { + metadata = [NSMutableDictionary dictionaryWithObjectsAndKeys: + now_, @"FirstSeen", + nil]; + + [Packages_ setObject:metadata forKey:id_]; + } } return self; } @@ -682,43 +999,126 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString return nil; } -- (NSString *) name { - return [[NSString stringWithCString:iterator_.Name()] lowercaseString]; +- (NSString *) section { + return [[NSString stringWithCString:iterator_.Section()] stringByReplacingCharacter:'_' withCharacter:' ']; } -- (NSString *) section { - return [NSString stringWithCString:iterator_.Section()]; +- (Address *) maintainer { + pkgRecords::Parser *parser = &[database_ records]->Lookup(file_); + return [Address addressWithString:[NSString stringWithCString:parser->Maintainer().c_str()]]; +} + +- (size_t) size { + return version_->InstalledSize; +} + +- (NSString *) description { + pkgRecords::Parser *parser = &[database_ records]->Lookup(file_); + NSString *description([NSString stringWithCString:parser->LongDesc().c_str()]); + + NSArray *lines = [description componentsSeparatedByString:@"\n"]; + NSMutableArray *trimmed = [NSMutableArray arrayWithCapacity:([lines count] - 1)]; + if ([lines count] < 2) + return nil; + + NSCharacterSet *whitespace = [NSCharacterSet whitespaceCharacterSet]; + for (size_t i(1); i != [lines count]; ++i) { + NSString *trim = [[lines objectAtIndex:i] stringByTrimmingCharactersInSet:whitespace]; + [trimmed addObject:trim]; + } + + return [trimmed componentsJoinedByString:@"\n"]; +} + +- (NSString *) index { + return [[[self name] substringToIndex:1] uppercaseString]; +} + +- (NSDate *) seen { + return [[Packages_ objectForKey:id_] objectForKey:@"FirstSeen"]; } - (NSString *) latest { - return [NSString stringWithCString:version_.VerStr()]; + return latest_; } - (NSString *) installed { - return iterator_.CurrentVer().end() ? nil : [NSString stringWithCString:iterator_.CurrentVer().VerStr()]; + return installed_; } -- (Address *) maintainer { - return [Address addressWithString:[NSString stringWithCString:parser_->Maintainer().c_str()]]; +- (BOOL) upgradable { + NSString *installed = [self installed]; + return installed != nil && [[self latest] compare:installed] != NSOrderedSame ? YES : NO; } -- (size_t) size { - return version_->InstalledSize; +- (NSString *) id { + return id_; +} + +- (NSString *) name { + return name_ == nil ? id_ : name_; } - (NSString *) tagline { - return [NSString stringWithCString:parser_->ShortDesc().c_str()]; + return tagline_; } -- (NSString *) description { - return [NSString stringWithCString:parser_->LongDesc().c_str()]; +- (NSString *) icon { + return icon_; +} + +- (NSString *) bundle { + return bundle_; +} + +- (BOOL) matches:(NSString *)text { + if (text == nil) + return NO; + + NSRange range; + + range = [[self name] rangeOfString:text options:NSCaseInsensitiveSearch]; + if (range.location != NSNotFound) + return YES; + + range = [[self tagline] rangeOfString:text options:NSCaseInsensitiveSearch]; + if (range.location != NSNotFound) + return YES; + + return NO; +} + +- (NSComparisonResult) compareByName:(Package *)package { + return [[self name] caseInsensitiveCompare:[package name]]; } - (NSComparisonResult) compareBySectionAndName:(Package *)package { - NSComparisonResult result = [[self section] compare:[package section]]; + NSComparisonResult result = [[self section] caseInsensitiveCompare:[package section]]; if (result != NSOrderedSame) return result; - return [[self name] compare:[package name]]; + return [self compareByName:package]; +} + +- (NSComparisonResult) compareForChanges:(Package *)package { + BOOL lhs = [self upgradable]; + BOOL rhs = [package upgradable]; + + if (lhs != rhs) + return lhs ? NSOrderedAscending : NSOrderedDescending; + else if (!lhs) { + switch ([[self seen] compare:[package seen]]) { + case NSOrderedAscending: + return NSOrderedDescending; + case NSOrderedSame: + break; + case NSOrderedDescending: + return NSOrderedAscending; + default: + _assert(false); + } + } + + return [self compareByName:package]; } - (void) install { @@ -750,6 +1150,8 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString - (Section *) initWithName:(NSString *)name row:(size_t)row; - (NSString *) name; - (size_t) row; +- (NSArray *) packages; +- (size_t) count; - (void) addPackage:(Package *)package; @end @@ -777,6 +1179,14 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString return row_; } +- (NSArray *) packages { + return packages_; +} + +- (size_t) count { + return [packages_ count]; +} + - (void) addPackage:(Package *)package { [packages_ addObject:package]; } @@ -785,11 +1195,14 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString /* }}} */ /* Package View {{{ */ +@protocol PackageViewDelegate +- (void) performPackage:(Package *)package; +@end + @interface PackageView : UIView { UIPreferencesTable *table_; Package *package_; - Database *database_; - NSMutableArray *cells_; + UITextView *description_; id delegate_; } @@ -797,13 +1210,16 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString - (int) numberOfGroupsInPreferencesTable:(UIPreferencesTable *)table; - (NSString *) preferencesTable:(UIPreferencesTable *)table titleForGroup:(int)group; +- (float) preferencesTable:(UIPreferencesTable *)table heightForRow:(int)row inGroup:(int)group withProposedHeight:(float)proposed; - (int) preferencesTable:(UIPreferencesTable *)table numberOfRowsInGroup:(int)group; - (UIPreferencesTableCell *) preferencesTable:(UIPreferencesTable *)table cellForRow:(int)row inGroup:(int)group; - (BOOL) canSelectRow:(int)row; - (void) tableRowSelected:(NSNotification *)notification; -- (id) initWithFrame:(struct CGRect)frame database:(Database *)database; +- (Package *) package; + +- (id) initWithFrame:(struct CGRect)frame; - (void) setPackage:(Package *)package; - (void) setDelegate:(id)delegate; @end @@ -813,9 +1229,9 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString - (void) dealloc { if (package_ != nil) [package_ release]; + if (description_ != nil) + [description_ release]; [table_ release]; - [database_ release]; - [cells_ release]; [super dealloc]; } @@ -825,60 +1241,72 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString - (NSString *) preferencesTable:(UIPreferencesTable *)table titleForGroup:(int)group { switch (group) { - case 0: return @"Specifics"; - case 1: return @"Description"; + case 0: return nil; + case 1: return @"Details"; + case 2: return @"Source"; default: _assert(false); } } +- (float) preferencesTable:(UIPreferencesTable *)table heightForRow:(int)row inGroup:(int)group withProposedHeight:(float)proposed { + if (group != 0 || row != 1) + return proposed; + else + return [description_ visibleTextRect].size.height + TextViewOffset_; +} + - (int) preferencesTable:(UIPreferencesTable *)table numberOfRowsInGroup:(int)group { switch (group) { - case 0: return 6; - case 1: return 1; + case 0: return 2; + case 1: return 5; + case 2: return 0; default: _assert(false); } } - (UIPreferencesTableCell *) preferencesTable:(UIPreferencesTable *)table cellForRow:(int)row inGroup:(int)group { - UIPreferencesTableCell *cell; + UIPreferencesTableCell *cell = [[[UIPreferencesTableCell alloc] init] autorelease]; + [cell setShowSelection:NO]; switch (group) { case 0: switch (row) { case 0: - cell = [cells_ objectAtIndex:0]; - [cell setTitle:@"Name"]; - [cell setValue:[package_ name]]; + [cell setTitle:[package_ name]]; + [cell setValue:[package_ latest]]; + break; + + case 1: + [cell addSubview:description_]; + break; + + default: _assert(false); + } break; + + case 1: switch (row) { + case 0: + [cell setTitle:@"Identifier"]; + [cell setValue:[package_ id]]; break; case 1: { - cell = [cells_ objectAtIndex:1]; - [cell setTitle:@"Installed"]; + [cell setTitle:@"Installed Version"]; NSString *installed([package_ installed]); [cell setValue:(installed == nil ? @"n/a" : installed)]; } break; case 2: - cell = [cells_ objectAtIndex:2]; - [cell setTitle:@"Latest"]; - [cell setValue:[package_ latest]]; - break; - - case 3: - cell = [cells_ objectAtIndex:3]; [cell setTitle:@"Section"]; [cell setValue:[package_ section]]; break; - case 4: - cell = [cells_ objectAtIndex:4]; - [cell setTitle:@"Size"]; + case 3: + [cell setTitle:@"Expanded Size"]; [cell setValue:SizeString([package_ size])]; break; - case 5: - cell = [cells_ objectAtIndex:5]; + case 4: [cell setTitle:@"Maintainer"]; [cell setValue:[[package_ maintainer] name]]; [cell setShowDisclosure:YES]; @@ -888,18 +1316,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString default: _assert(false); } break; - case 1: switch (row) { - case 0: - cell = [cells_ objectAtIndex:6]; - [cell setTitle:nil]; - [cell setValue:[package_ tagline]]; - break; - - case 1: - cell = [cells_ objectAtIndex:7]; - [cell setTitle:@"Description"]; - [cell setValue:[package_ description]]; - break; + case 2: switch (row) { } break; default: _assert(false); @@ -913,8 +1330,9 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString } - (void) tableRowSelected:(NSNotification *)notification { + printf("%d\n", [table_ selectedRow]); switch ([table_ selectedRow]) { - case 5: + case 8: [delegate_ openURL:[NSURL URLWithString:[NSString stringWithFormat:@"mailto:%@?subject=%@", [[package_ maintainer] email], [[NSString stringWithFormat:@"regarding apt package \"%@\"", [package_ name]] stringByAddingPercentEscapes] @@ -923,29 +1341,46 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString } } -- (id) initWithFrame:(struct CGRect)frame database:(Database *)database { - if ((self = [super initWithFrame:frame]) != nil) { - database_ = [database retain]; +- (Package *) package { + return package_; +} +- (id) initWithFrame:(struct CGRect)frame { + if ((self = [super initWithFrame:frame]) != nil) { table_ = [[UIPreferencesTable alloc] initWithFrame:[self bounds]]; [self addSubview:table_]; [table_ setDataSource:self]; [table_ setDelegate:self]; - - cells_ = [[NSMutableArray arrayWithCapacity:16] retain]; - - for (unsigned i = 0; i != 8; ++i) { - UIPreferencesTableCell *cell = [[[UIPreferencesTableCell alloc] init] autorelease]; - [cell setShowSelection:NO]; - [cells_ addObject:cell]; - } } return self; } - (void) setPackage:(Package *)package { - package_ = [package retain]; - [table_ reloadData]; + if (package_ != nil) { + [package_ autorelease]; + package_ = nil; + } + + if (description_ != nil) { + [description_ release]; + description_ = nil; + } + + if (package != nil) { + package_ = [package retain]; + + NSString *description([package description]); + if (description == nil) + description = [package tagline]; + description_ = [GetTextView(description, 12, true) retain]; + + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + CGColor black(space, 0, 0, 0, 1); + [description_ setTextColor:black]; + CGColorSpaceRelease(space); + + [table_ reloadData]; + } } - (void) setDelegate:(id)delegate { @@ -955,20 +1390,16 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString @end /* }}} */ /* Package Cell {{{ */ -@protocol PackageCellDelegate -- (NSString *) versionWithPackage:(Package *)package; -@end - @interface PackageCell : UITableCell { UITextLabel *name_; - UIRightTextLabel *version_; + UITextLabel *version_; UITextLabel *description_; - id delegate_; + SEL versioner_; } - (void) dealloc; -- (PackageCell *) initWithDelegate:(id)delegate; +- (PackageCell *) initWithVersioner:(SEL)versioner; - (void) setPackage:(Package *)package; - (void) _setSelected:(float)fraction; @@ -987,33 +1418,36 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString [super dealloc]; } -- (PackageCell *) initWithDelegate:(id)delegate { +- (PackageCell *) initWithVersioner:(SEL)versioner { if ((self = [super init]) != nil) { - delegate_ = delegate; + versioner_ = versioner; GSFontRef bold = GSFontCreateWithName("Helvetica", kGSFontTraitBold, 22); GSFontRef large = GSFontCreateWithName("Helvetica", kGSFontTraitNone, 16); GSFontRef small = GSFontCreateWithName("Helvetica", kGSFontTraitNone, 14); CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); - float clear[] = {0, 0, 0, 0}; + + CGColor clear(space, 0, 0, 0, 0); name_ = [[UITextLabel alloc] initWithFrame:CGRectMake(12, 7, 250, 25)]; - [name_ setBackgroundColor:CGColorCreate(space, clear)]; + [name_ setBackgroundColor:clear]; [name_ setFont:bold]; - version_ = [[UIRightTextLabel alloc] initWithFrame:CGRectMake(290, 7, 70, 25)]; - [version_ setBackgroundColor:CGColorCreate(space, clear)]; + version_ = [[UIRightTextLabel alloc] initWithFrame:CGRectMake(286, 7, 70, 25)]; + [version_ setBackgroundColor:clear]; [version_ setFont:large]; description_ = [[UITextLabel alloc] initWithFrame:CGRectMake(13, 35, 315, 20)]; - [description_ setBackgroundColor:CGColorCreate(space, clear)]; + [description_ setBackgroundColor:clear]; [description_ setFont:small]; [self addSubview:name_]; [self addSubview:version_]; [self addSubview:description_]; + CGColorSpaceRelease(space); + CFRelease(small); CFRelease(large); CFRelease(bold); @@ -1022,34 +1456,36 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString - (void) setPackage:(Package *)package { [name_ setText:[package name]]; - [version_ setText:[delegate_ versionWithPackage:package]]; + [version_ setText:[package latest]]; [description_ setText:[package tagline]]; } - (void) _setSelected:(float)fraction { CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); - float black[] = { + CGColor black(space, interpolate(0.0, 1.0, fraction), interpolate(0.0, 1.0, fraction), interpolate(0.0, 1.0, fraction), - 1.0}; + 1.0); - float blue[] = { + CGColor blue(space, interpolate(0.2, 1.0, fraction), interpolate(0.2, 1.0, fraction), interpolate(1.0, 1.0, fraction), - 1.0}; + 1.0); - float gray[] = { + CGColor gray(space, interpolate(0.4, 1.0, fraction), interpolate(0.4, 1.0, fraction), interpolate(0.4, 1.0, fraction), - 1.0}; + 1.0); + + [name_ setColor:black]; + [version_ setColor:blue]; + [description_ setColor:gray]; - [name_ setColor:CGColorCreate(space, black)]; - [version_ setColor:CGColorCreate(space, blue)]; - [description_ setColor:CGColorCreate(space, gray)]; + CGColorSpaceRelease(space); } - (void) setSelected:(BOOL)selected { @@ -1071,905 +1507,991 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString @end /* }}} */ -/* Source {{{ */ -@interface Source : NSObject { - NSString *description_; - NSString *label_; - NSString *origin_; - - NSString *uri_; - NSString *distribution_; - NSString *type_; +/* Database Implementation {{{ */ +@implementation Database - BOOL trusted_; +- (void) dealloc { + _assert(false); + [super dealloc]; } -- (void) dealloc; +- (void) _readStatus:(NSNumber *)fd { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + __gnu_cxx::stdio_filebuf ib([fd intValue], std::ios::in); + std::istream is(&ib); + std::string line; -- (Source *) initWithMetaIndex:(metaIndex *)index; + const char *error; + int offset; + pcre *code = pcre_compile("^([^:]*):([^:]*):([^:]*):(.*)$", 0, &error, &offset, NULL); -- (BOOL) trusted; + pcre_extra *study = NULL; + int capture; + pcre_fullinfo(code, study, PCRE_INFO_CAPTURECOUNT, &capture); + int matches[(capture + 1) * 3]; -- (NSString *) uri; -- (NSString *) distribution; -- (NSString *) type; + while (std::getline(is, line)) { + const char *data(line.c_str()); -- (NSString *) description; -- (NSString *) label; -- (NSString *) origin; -@end + _assert(pcre_exec(code, study, data, line.size(), 0, 0, matches, sizeof(matches) / sizeof(matches[0])) >= 0); -@implementation Source + std::istringstream buffer(line.substr(matches[6], matches[7] - matches[6])); + float percent; + buffer >> percent; + [delegate_ setPercent:(percent / 100)]; -- (void) dealloc { - [uri_ release]; - [distribution_ release]; - [type_ release]; + NSString *string = [NSString stringWithCString:(data + matches[8]) length:(matches[9] - matches[8])]; + std::string type(line.substr(matches[2], matches[3] - matches[2])); - if (description_ != nil) - [description_ release]; - if (label_ != nil) - [label_ release]; - if (origin_ != nil) - [origin_ release]; + if (type == "pmerror") + [delegate_ setError:string]; + else if (type == "pmstatus") + [delegate_ setTitle:string]; + else if (type == "pmconffile") + ; + else _assert(false); + } - [super dealloc]; + [pool release]; + _assert(false); } -- (Source *) initWithMetaIndex:(metaIndex *)index { - if ((self = [super init]) != nil) { - trusted_ = index->IsTrusted(); +- (void) _readOutput:(NSNumber *)fd { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + __gnu_cxx::stdio_filebuf ib([fd intValue], std::ios::in); + std::istream is(&ib); + std::string line; - uri_ = [[NSString stringWithCString:index->GetURI().c_str()] retain]; - distribution_ = [[NSString stringWithCString:index->GetDist().c_str()] retain]; - type_ = [[NSString stringWithCString:index->GetType()] retain]; + while (std::getline(is, line)) + [delegate_ addOutput:[NSString stringWithCString:line.c_str()]]; - description_ = nil; - label_ = nil; - origin_ = nil; - - debReleaseIndex *dindex(dynamic_cast(index)); - if (dindex != NULL) { - std::ifstream release(dindex->MetaIndexFile("Release").c_str()); - std::string line; - while (std::getline(release, line)) { - std::string::size_type colon(line.find(':')); - if (colon == std::string::npos) - continue; - - std::string name(line.substr(0, colon)); - std::string value(line.substr(colon + 1)); - while (!value.empty() && value[0] == ' ') - value = value.substr(1); - - if (name == "Description") - description_ = [[NSString stringWithCString:value.c_str()] retain]; - else if (name == "Label") - label_ = [[NSString stringWithCString:value.c_str()] retain]; - else if (name == "Origin") - origin_ = [[NSString stringWithCString:value.c_str()] retain]; - } - } - } return self; + [pool release]; + _assert(false); } -- (BOOL) trusted { - return trusted_; +- (Package *) packageWithName:(NSString *)name { + pkgCache::PkgIterator iterator(cache_->FindPkg([name cString])); + return iterator.end() ? nil : [Package packageWithIterator:iterator database:self]; } -- (NSString *) uri { - return uri_; -} +- (Database *) init { + if ((self = [super init]) != nil) { + records_ = NULL; + resolver_ = NULL; + fetcher_ = NULL; + lock_ = NULL; -- (NSString *) distribution { - return distribution_; -} + int fds[2]; -- (NSString *) type { - return type_; -} + _assert(pipe(fds) != -1); + statusfd_ = fds[1]; -- (NSString *) description { - return description_; + [NSThread + detachNewThreadSelector:@selector(_readStatus:) + toTarget:self + withObject:[[NSNumber numberWithInt:fds[0]] retain] + ]; + + _assert(pipe(fds) != -1); + _assert(dup2(fds[1], 1) != -1); + _assert(close(fds[1]) != -1); + + [NSThread + detachNewThreadSelector:@selector(_readOutput:) + toTarget:self + withObject:[[NSNumber numberWithInt:fds[0]] retain] + ]; + } return self; } -- (NSString *) label { - return label_; +- (pkgCacheFile &) cache { + return cache_; } -- (NSString *) origin { - return origin_; +- (pkgRecords *) records { + return records_; } -@end -/* }}} */ -/* Source Cell {{{ */ -@interface SourceCell : UITableCell { - UITextLabel *description_; - UIRightTextLabel *label_; - UITextLabel *origin_; +- (pkgProblemResolver *) resolver { + return resolver_; } -- (void) dealloc; +- (pkgAcquire &) fetcher { + return *fetcher_; +} -- (SourceCell *) initWithSource:(Source *)source; +- (void) reloadData { + _error->Discard(); + manager_ = NULL; + delete lock_; + delete fetcher_; + delete resolver_; + delete records_; + cache_.Close(); + _assert(cache_.Open(progress_, true)); + records_ = new pkgRecords(cache_); + resolver_ = new pkgProblemResolver(cache_); + fetcher_ = new pkgAcquire(&status_); + lock_ = NULL; +} -- (void) _setSelected:(float)fraction; -- (void) setSelected:(BOOL)selected; -- (void) setSelected:(BOOL)selected withFade:(BOOL)fade; -- (void) _setSelectionFadeFraction:(float)fraction; +- (void) prepare { + pkgRecords records(cache_); -@end + lock_ = new FileFd(); + lock_->Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock")); + _assert(!_error->PendingError()); -@implementation SourceCell + pkgSourceList list; + _assert(list.ReadMainList()); -- (void) dealloc { - [description_ release]; - [label_ release]; - [origin_ release]; - [super dealloc]; + manager_ = (_system->CreatePM(cache_)); + _assert(manager_->GetArchives(fetcher_, &list, &records)); + _assert(!_error->PendingError()); } -- (SourceCell *) initWithSource:(Source *)source { - if ((self = [super init]) != nil) { - GSFontRef bold = GSFontCreateWithName("Helvetica", kGSFontTraitBold, 20); - GSFontRef small = GSFontCreateWithName("Helvetica", kGSFontTraitNone, 14); - - CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); - float clear[] = {0, 0, 0, 0}; +- (void) perform { + if (fetcher_->Run(PulseInterval_) != pkgAcquire::Continue) + return; - NSString *description = [source description]; - if (description == nil) - description = [source uri]; + _system->UnLock(); + pkgPackageManager::OrderResult result = manager_->DoInstall(statusfd_); - description_ = [[UITextLabel alloc] initWithFrame:CGRectMake(12, 7, 270, 25)]; - [description_ setBackgroundColor:CGColorCreate(space, clear)]; - [description_ setFont:bold]; - [description_ setText:description]; + if (result == pkgPackageManager::Failed) + return; + if (_error->PendingError()) + return; + if (result != pkgPackageManager::Completed) + return; +} - NSString *label = [source label]; - if (label == nil) - label = [source type]; +- (void) update { + pkgSourceList list; + _assert(list.ReadMainList()); - label_ = [[UIRightTextLabel alloc] initWithFrame:CGRectMake(290, 32, 90, 25)]; - [label_ setBackgroundColor:CGColorCreate(space, clear)]; - [label_ setFont:small]; - [label_ setText:label]; + FileFd lock; + lock.Fd(GetLock(_config->FindDir("Dir::State::Lists") + "lock")); + _assert(!_error->PendingError()); - NSString *origin = [source origin]; - if (origin == nil) - origin = [source distribution]; + pkgAcquire fetcher(&status_); + _assert(list.GetIndexes(&fetcher)); + _assert(fetcher.Run(PulseInterval_) != pkgAcquire::Failed); - origin_ = [[UITextLabel alloc] initWithFrame:CGRectMake(13, 35, 315, 20)]; - [origin_ setBackgroundColor:CGColorCreate(space, clear)]; - [origin_ setFont:small]; - [origin_ setText:origin]; + bool failed = false; + for (pkgAcquire::ItemIterator item = fetcher.ItemsBegin(); item != fetcher.ItemsEnd(); item++) + if ((*item)->Status != pkgAcquire::Item::StatDone) { + (*item)->Finished(); + failed = true; + } - [self addSubview:description_]; - [self addSubview:label_]; - [self addSubview:origin_]; + if (!failed && _config->FindB("APT::Get::List-Cleanup", true) == true) { + _assert(fetcher.Clean(_config->FindDir("Dir::State::lists"))); + _assert(fetcher.Clean(_config->FindDir("Dir::State::lists") + "partial/")); + } - CFRelease(small); - CFRelease(bold); - } return self; + [Metadata_ setObject:[NSDate date] forKey:@"LastUpdate"]; } -- (void) _setSelected:(float)fraction { - CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); - - float black[] = { - interpolate(0.0, 1.0, fraction), - interpolate(0.0, 1.0, fraction), - interpolate(0.0, 1.0, fraction), - 1.0}; - - float blue[] = { - interpolate(0.2, 1.0, fraction), - interpolate(0.2, 1.0, fraction), - interpolate(1.0, 1.0, fraction), - 1.0}; +- (void) upgrade { + _assert(cache_->DelCount() == 0 && cache_->InstCount() == 0); + _assert(pkgApplyStatus(cache_)); - float gray[] = { - interpolate(0.4, 1.0, fraction), - interpolate(0.4, 1.0, fraction), - interpolate(0.4, 1.0, fraction), - 1.0}; + if (cache_->BrokenCount() != 0) { + _assert(pkgFixBroken(cache_)); + _assert(cache_->BrokenCount() == 0); + _assert(pkgMinimizeUpgrade(cache_)); + } - [description_ setColor:CGColorCreate(space, black)]; - [label_ setColor:CGColorCreate(space, blue)]; - [origin_ setColor:CGColorCreate(space, gray)]; + _assert(pkgDistUpgrade(cache_)); } -- (void) setSelected:(BOOL)selected { - [self _setSelected:(selected ? 1.0 : 0.0)]; - [super setSelected:selected]; -} - -- (void) setSelected:(BOOL)selected withFade:(BOOL)fade { - if (!fade) - [self _setSelected:(selected ? 1.0 : 0.0)]; - [super setSelected:selected withFade:fade]; -} - -- (void) _setSelectionFadeFraction:(float)fraction { - [self _setSelected:fraction]; - [super _setSelectionFadeFraction:fraction]; +- (void) setDelegate:(id)delegate { + delegate_ = delegate; + status_.setDelegate(delegate); + progress_.setDelegate(delegate); } @end /* }}} */ -/* Sources View {{{ */ -@interface SourcesView : ResetView { - UISectionList *list_; - Database *database_; - id delegate_; - NSMutableArray *sources_; - UIAlertSheet *alert_; -} - -- (int) numberOfSectionsInSectionList:(UISectionList *)list; -- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section; -- (int) sectionList:(UISectionList *)list rowForSection:(int)section; -- (int) numberOfRowsInTable:(UITable *)table; -- (float) table:(UITable *)table heightForRow:(int)row; -- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col; -- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row; -- (void) tableRowSelected:(NSNotification*)notification; +/* Progress Data {{{ */ +@interface ProgressData : NSObject { + SEL selector_; + id target_; + id object_; +} -- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button; +- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object; -- (void) dealloc; -- (id) initWithFrame:(CGRect)frame database:(Database *)database; -- (void) setDelegate:(id)delegate; -- (void) reloadData; -- (NSString *) leftTitle; -- (NSString *) rightTitle; +- (SEL) selector; +- (id) target; +- (id) object; @end -@implementation SourcesView +@implementation ProgressData -- (int) numberOfSectionsInSectionList:(UISectionList *)list { - return 1; +- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object { + if ((self = [super init]) != nil) { + selector_ = selector; + target_ = target; + object_ = object; + } return self; } -- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section { - return @"sources"; +- (SEL) selector { + return selector_; } -- (int) sectionList:(UISectionList *)list rowForSection:(int)section { - return 0; +- (id) target { + return target_; } -- (int) numberOfRowsInTable:(UITable *)table { - return [sources_ count]; +- (id) object { + return object_; } -- (float) table:(UITable *)table heightForRow:(int)row { - return 64; +@end +/* }}} */ +/* Progress View {{{ */ +@interface ProgressView : UIView < + ProgressDelegate +> { + UIView *view_; + UIView *background_; + UITransitionView *transition_; + UIView *overlay_; + UINavigationBar *navbar_; + UIProgressBar *progress_; + UITextView *output_; + UITextLabel *status_; + id delegate_; } -- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col { - return [[[SourceCell alloc] initWithSource:[sources_ objectAtIndex:row]] autorelease]; -} +- (void) dealloc; -- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row { - return NO; -} +- (ProgressView *) initWithFrame:(struct CGRect)frame delegate:(id)delegate; +- (void) setContentView:(UIView *)view; +- (void) resetView; -- (void) tableRowSelected:(NSNotification*)notification { - UITable *table([list_ table]); - int row([table selectedRow]); - if (row == INT_MAX) - return; +- (void) alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button; - [table selectRow:-1 byExtendingSelection:NO withFade:YES]; -} +- (void) _retachThread; +- (void) _detachNewThreadData:(ProgressData *)data; +- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title; -- (void) alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button { - [alert_ dismiss]; - [alert_ release]; - alert_ = nil; -} +- (void) setError:(NSString *)error; +- (void) _setError:(NSString *)error; -- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button { - switch (button) { - case 0: - alert_ = [[UIAlertSheet alloc] - initWithTitle:@"Unimplemented" - buttons:[NSArray arrayWithObjects:@"Okay", nil] - defaultButtonIndex:0 - delegate:self - context:self - ]; +- (void) setTitle:(NSString *)title; +- (void) _setTitle:(NSString *)title; - [alert_ setBodyText:@"This feature will be implemented soon. In the mean time, you may add sources by adding .list files to '/etc/apt/sources.list.d'. If you'd like to be in the default list, please contact the author of Packager."]; - [alert_ popupAlertAnimated:YES]; - break; +- (void) setPercent:(float)percent; +- (void) _setPercent:(NSNumber *)percent; - case 1: - [delegate_ update]; - break; - } -} +- (void) addOutput:(NSString *)output; +- (void) _addOutput:(NSString *)output; +@end + +@protocol ProgressViewDelegate +- (void) progressViewIsComplete:(ProgressView *)sender; +@end + +@implementation ProgressView - (void) dealloc { - if (sources_ != nil) - [sources_ release]; - [list_ release]; + [view_ release]; + [background_ release]; + [transition_ release]; + [overlay_ release]; + [navbar_ release]; + [progress_ release]; + [output_ release]; + [status_ release]; [super dealloc]; } -- (id) initWithFrame:(CGRect)frame database:(Database *)database { +- (ProgressView *) initWithFrame:(struct CGRect)frame delegate:(id)delegate { if ((self = [super initWithFrame:frame]) != nil) { - database_ = database; - sources_ = nil; + delegate_ = delegate; + + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + + CGColor black(space, 0.0, 0.0, 0.0, 1.0); + CGColor white(space, 1.0, 1.0, 1.0, 1.0); + CGColor clear(space, 0.0, 0.0, 0.0, 0.0); + + background_ = [[UIView alloc] initWithFrame:[self bounds]]; + [background_ setBackgroundColor:black]; + [self addSubview:background_]; + + transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]]; + [self addSubview:transition_]; + + overlay_ = [[UIView alloc] initWithFrame:[transition_ bounds]]; CGSize navsize = [UINavigationBar defaultSize]; CGRect navrect = {{0, 0}, navsize}; - CGRect bounds = [self bounds]; navbar_ = [[UINavigationBar alloc] initWithFrame:navrect]; - [self addSubview:navbar_]; + [overlay_ addSubview:navbar_]; [navbar_ setBarStyle:1]; [navbar_ setDelegate:self]; - UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:@"Sources"] autorelease]; + UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:nil] autorelease]; [navbar_ pushNavigationItem:navitem]; - list_ = [[UISectionList alloc] initWithFrame:CGRectMake( - 0, navsize.height, bounds.size.width, bounds.size.height - navsize.height + CGRect bounds = [overlay_ bounds]; + CGSize prgsize = [UIProgressBar defaultSize]; + + CGRect prgrect = {{ + (bounds.size.width - prgsize.width) / 2, + bounds.size.height - prgsize.height - 20 + }, prgsize}; + + progress_ = [[UIProgressBar alloc] initWithFrame:prgrect]; + [overlay_ addSubview:progress_]; + + status_ = [[UITextLabel alloc] initWithFrame:CGRectMake( + 10, + bounds.size.height - prgsize.height - 50, + bounds.size.width - 20, + 24 )]; - [self addSubview:list_]; + [status_ setColor:white]; + [status_ setBackgroundColor:clear]; - [list_ setDataSource:self]; - [list_ setShouldHideHeaderInShortLists:NO]; + [status_ setCentersHorizontally:YES]; + //[status_ setFont:font]; - UITableColumn *column = [[UITableColumn alloc] - initWithTitle:@"Name" - identifier:@"name" - width:frame.size.width - ]; + output_ = [[UITextView alloc] initWithFrame:CGRectMake( + 10, + navrect.size.height + 20, + bounds.size.width - 20, + bounds.size.height - navsize.height - 62 - navrect.size.height + )]; - UITable *table = [list_ table]; - [table setSeparatorStyle:1]; - [table addTableColumn:column]; - [table setDelegate:self]; - } return self; -} + //[output_ setTextFont:@"Courier New"]; + [output_ setTextSize:12]; -- (void) setDelegate:(id)delegate { - delegate_ = delegate; -} + [output_ setTextColor:white]; + [output_ setBackgroundColor:clear]; -- (void) reloadData { - pkgSourceList list; - _assert(list.ReadMainList()); + [output_ setMarginTop:0]; + [output_ setAllowsRubberBanding:YES]; - if (sources_ != nil) - [sources_ release]; + [overlay_ addSubview:output_]; + [overlay_ addSubview:status_]; - sources_ = [[NSMutableArray arrayWithCapacity:16] retain]; - for (pkgSourceList::const_iterator source = list.begin(); source != list.end(); ++source) - [sources_ addObject:[[[Source alloc] initWithMetaIndex:*source] autorelease]]; + [progress_ setStyle:0]; - [self resetView]; - [list_ reloadData]; + CGColorSpaceRelease(space); + } return self; } -- (NSString *) leftTitle { - return @"Refresh All"; +- (void) setContentView:(UIView *)view { + view_ = [view retain]; } -- (NSString *) rightTitle { - return @"Edit"; +- (void) resetView { + [transition_ transition:6 toView:view_]; } -@end -/* }}} */ +- (void) alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button { + [sheet dismiss]; +} -@implementation Database +- (void) _retachThread { + [delegate_ progressViewIsComplete:self]; + [self resetView]; +} -- (void) _readStatus:(NSNumber *)fd { +- (void) _detachNewThreadData:(ProgressData *)data { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - __gnu_cxx::stdio_filebuf ib([fd intValue], std::ios::in); - std::istream is(&ib); - std::string line; - - const char *error; - int offset; - pcre *code = pcre_compile("^([^:]*):([^:]*):([^:]*):(.*)$", 0, &error, &offset, NULL); - - pcre_extra *study = NULL; - int capture; - pcre_fullinfo(code, study, PCRE_INFO_CAPTURECOUNT, &capture); - int matches[(capture + 1) * 3]; - - while (std::getline(is, line)) { - const char *data(line.c_str()); - - _assert(pcre_exec(code, study, data, line.size(), 0, 0, matches, sizeof(matches) / sizeof(matches[0])) >= 0); - - std::istringstream buffer(line.substr(matches[6], matches[7] - matches[6])); - float percent; - buffer >> percent; - [delegate_ setPercent:(percent / 100)]; - - NSString *string = [NSString stringWithCString:(data + matches[8]) length:(matches[9] - matches[8])]; - std::string type(line.substr(matches[2], matches[3] - matches[2])); + [[data target] performSelector:[data selector] withObject:[data object]]; + [data release]; - if (type == "pmerror") - [delegate_ setError:string]; - else if (type == "pmstatus") - [delegate_ setTitle:string]; - else if (type == "pmconffile") - ; - else _assert(false); - } + [self performSelectorOnMainThread:@selector(_retachThread) withObject:nil waitUntilDone:YES]; [pool release]; } -- (void) _readOutput:(NSNumber *)fd { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - __gnu_cxx::stdio_filebuf ib([fd intValue], std::ios::in); - std::istream is(&ib); - std::string line; +- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title { + [navbar_ popNavigationItem]; + UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:title] autorelease]; + [navbar_ pushNavigationItem:navitem]; - while (std::getline(is, line)) - [delegate_ addOutput:[NSString stringWithCString:line.c_str()]]; + [status_ setText:nil]; + [output_ setText:@""]; + [progress_ setProgress:0]; - [pool release]; -} + [transition_ transition:6 toView:overlay_]; -- (Package *) packageWithName:(NSString *)name { - pkgCache::PkgIterator iterator(cache_->FindPkg([name cString])); - return iterator.end() ? nil : [Package packageWithIterator:iterator database:self]; + [NSThread + detachNewThreadSelector:@selector(_detachNewThreadData:) + toTarget:self + withObject:[[ProgressData alloc] + initWithSelector:selector + target:target + object:object + ] + ]; } -- (Database *) init { - if ((self = [super init]) != nil) { - records_ = NULL; - resolver_ = NULL; - fetcher_ = NULL; - lock_ = NULL; - - int fds[2]; +- (void) setError:(NSString *)error { + [self + performSelectorOnMainThread:@selector(_setError:) + withObject:error + waitUntilDone:YES + ]; +} - _assert(pipe(fds) != -1); - statusfd_ = fds[1]; +- (void) _setError:(NSString *)error { + UIAlertSheet *sheet = [[[UIAlertSheet alloc] + initWithTitle:@"Package Error" + buttons:[NSArray arrayWithObjects:@"Okay", nil] + defaultButtonIndex:0 + delegate:self + context:self + ] autorelease]; - [NSThread - detachNewThreadSelector:@selector(_readStatus:) - toTarget:self - withObject:[[NSNumber numberWithInt:fds[0]] retain] - ]; + [sheet setBodyText:error]; + [sheet popupAlertAnimated:YES]; +} - _assert(pipe(fds) != -1); - _assert(dup2(fds[1], 1) != -1); - _assert(close(fds[1]) != -1); +- (void) setTitle:(NSString *)title { + [self + performSelectorOnMainThread:@selector(_setTitle:) + withObject:title + waitUntilDone:YES + ]; +} - [NSThread - detachNewThreadSelector:@selector(_readOutput:) - toTarget:self - withObject:[[NSNumber numberWithInt:fds[0]] retain] - ]; - } return self; +- (void) _setTitle:(NSString *)title { + [status_ setText:[title stringByAppendingString:@"..."]]; } -- (pkgCacheFile &) cache { - return cache_; +- (void) setPercent:(float)percent { + [self + performSelectorOnMainThread:@selector(_setPercent:) + withObject:[NSNumber numberWithFloat:percent] + waitUntilDone:YES + ]; } -- (pkgRecords *) records { - return records_; +- (void) _setPercent:(NSNumber *)percent { + [progress_ setProgress:[percent floatValue]]; } -- (pkgProblemResolver *) resolver { - return resolver_; +- (void) addOutput:(NSString *)output { + [self + performSelectorOnMainThread:@selector(_addOutput:) + withObject:output + waitUntilDone:YES + ]; } -- (pkgAcquire &) fetcher { - return *fetcher_; +- (void) _addOutput:(NSString *)output { + [output_ setText:[NSString stringWithFormat:@"%@\n%@", [output_ text], output]]; + CGSize size = [output_ contentSize]; + CGRect rect = {{0, size.height}, {size.width, 0}}; + [output_ scrollRectToVisible:rect animated:YES]; } -- (void) reloadData { - _error->Discard(); - manager_ = NULL; - delete lock_; - delete fetcher_; - delete resolver_; - delete records_; - cache_.Close(); - cache_.Open(progress_, true); - records_ = new pkgRecords(cache_); - resolver_ = new pkgProblemResolver(cache_); - fetcher_ = new pkgAcquire(&status_); - lock_ = NULL; +@end +/* }}} */ + +/* Package Table {{{ */ +@protocol PackageTableDelegate +- (void) packageTable:(id)table packageSelected:(Package *)package; +@end + +@interface PackageTable : UIView { + SEL versioner_; + UISectionList *list_; + + id delegate_; + NSArray *packages_; + NSMutableArray *sections_; } -- (void) prepare { - pkgRecords records(cache_); +- (void) dealloc; - lock_ = new FileFd(); - lock_->Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock")); - _assert(!_error->PendingError()); +- (int) numberOfSectionsInSectionList:(UISectionList *)list; +- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section; +- (int) sectionList:(UISectionList *)list rowForSection:(int)section; - pkgSourceList list; - _assert(list.ReadMainList()); +- (int) numberOfRowsInTable:(UITable *)table; +- (float) table:(UITable *)table heightForRow:(int)row; +- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing; +- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row; +- (void) tableRowSelected:(NSNotification *)notification; - manager_ = (_system->CreatePM(cache_)); - _assert(manager_->GetArchives(fetcher_, &list, &records)); - _assert(!_error->PendingError()); +- (id) initWithFrame:(CGRect)frame versioner:(SEL)versioner; + +- (void) setDelegate:(id)delegate; +- (void) setPackages:(NSArray *)packages; + +- (void) resetViewAnimated:(BOOL)animated; +- (UITable *) table; +@end + +@implementation PackageTable + +- (void) dealloc { + [list_ release]; + [sections_ release]; + if (packages_ != nil) + [packages_ release]; + [super dealloc]; } -- (void) perform { - if (fetcher_->Run(PulseInterval_) != pkgAcquire::Continue) - return; +- (int) numberOfSectionsInSectionList:(UISectionList *)list { + return [sections_ count]; +} - _system->UnLock(); - pkgPackageManager::OrderResult result = manager_->DoInstall(statusfd_); +- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section { + return [[sections_ objectAtIndex:section] name]; +} - if (result == pkgPackageManager::Failed) - return; - if (_error->PendingError()) - return; - if (result != pkgPackageManager::Completed) - return; +- (int) sectionList:(UISectionList *)list rowForSection:(int)section { + return [[sections_ objectAtIndex:section] row]; } -- (void) update { - pkgSourceList list; - _assert(list.ReadMainList()); +- (int) numberOfRowsInTable:(UITable *)table { + return [packages_ count]; +} - FileFd lock; - lock.Fd(GetLock(_config->FindDir("Dir::State::Lists") + "lock")); - _assert(!_error->PendingError()); +- (float) table:(UITable *)table heightForRow:(int)row { + return 64; +} - pkgAcquire fetcher(&status_); - _assert(list.GetIndexes(&fetcher)); - _assert(fetcher.Run(PulseInterval_) != pkgAcquire::Failed); +- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing { + if (reusing == nil) + reusing = [[[PackageCell alloc] initWithVersioner:versioner_] autorelease]; + [(PackageCell *)reusing setPackage:[packages_ objectAtIndex:row]]; + return reusing; +} - bool failed = false; - for (pkgAcquire::ItemIterator item = fetcher.ItemsBegin(); item != fetcher.ItemsEnd(); item++) - if ((*item)->Status != pkgAcquire::Item::StatDone) { - (*item)->Finished(); - failed = true; - } +- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row { + return NO; +} - if (!failed && _config->FindB("APT::Get::List-Cleanup", true) == true) { - _assert(fetcher.Clean(_config->FindDir("Dir::State::lists"))); - _assert(fetcher.Clean(_config->FindDir("Dir::State::lists") + "partial/")); - } +- (void) tableRowSelected:(NSNotification *)notification { + int row = [[notification object] selectedRow]; + [delegate_ packageTable:self packageSelected:(row == INT_MAX ? nil : [packages_ objectAtIndex:row])]; } -- (void) upgrade { - _assert(cache_->DelCount() == 0 && cache_->InstCount() == 0); - _assert(pkgApplyStatus(cache_)); +- (id) initWithFrame:(CGRect)frame versioner:(SEL)versioner { + if ((self = [super initWithFrame:frame]) != nil) { + versioner_ = versioner; + sections_ = [[NSMutableArray arrayWithCapacity:16] retain]; - if (cache_->BrokenCount() != 0) { - _assert(pkgFixBroken(cache_)); - _assert(cache_->BrokenCount() == 0); - _assert(pkgMinimizeUpgrade(cache_)); - } + list_ = [[UISectionList alloc] initWithFrame:[self bounds] showSectionIndex:YES]; + [list_ setDataSource:self]; - _assert(pkgDistUpgrade(cache_)); + UITableColumn *column = [[[UITableColumn alloc] + initWithTitle:@"Name" + identifier:@"name" + width:frame.size.width + ] autorelease]; + + UITable *table = [list_ table]; + [table setSeparatorStyle:1]; + [table addTableColumn:column]; + [table setDelegate:self]; + [table setReusesTableCells:YES]; + + [self addSubview:list_]; + } return self; } - (void) setDelegate:(id)delegate { delegate_ = delegate; - status_.setDelegate(delegate); - progress_.setDelegate(delegate); +} + +- (void) setPackages:(NSArray *)packages { + if (packages_ != nil) + [packages_ autorelease]; + _assert(packages != nil); + packages_ = [packages retain]; + + [sections_ removeAllObjects]; + + Section *section = nil; + + for (size_t offset(0); offset != [packages_ count]; ++offset) { + Package *package = [packages_ objectAtIndex:offset]; + NSString *name = [package index]; + + if (section == nil || ![[section name] isEqual:name]) { + section = [[[Section alloc] initWithName:name row:offset] autorelease]; + [sections_ addObject:section]; + } + + [section addPackage:package]; + } + + [list_ reloadData]; +} + +- (void) resetViewAnimated:(BOOL)animated { + [[list_ table] selectRow:-1 byExtendingSelection:NO withFade:animated]; +} + +- (UITable *) table { + return [list_ table]; } @end +/* }}} */ -/* Progress Data {{{ */ -@interface ProgressData : NSObject { - SEL selector_; - id target_; - id object_; +/* Section Cell {{{ */ +@interface SectionCell : UITableCell { + UITextLabel *name_; + UITextLabel *count_; } -- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object; +- (void) dealloc; + +- (id) init; +- (void) setSection:(Section *)section; + +- (void) _setSelected:(float)fraction; +- (void) setSelected:(BOOL)selected; +- (void) setSelected:(BOOL)selected withFade:(BOOL)fade; +- (void) _setSelectionFadeFraction:(float)fraction; -- (SEL) selector; -- (id) target; -- (id) object; @end -@implementation ProgressData +@implementation SectionCell -- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object { +- (void) dealloc { + [name_ release]; + [count_ release]; + [super dealloc]; +} + +- (id) init { if ((self = [super init]) != nil) { - selector_ = selector; - target_ = target; - object_ = object; + GSFontRef bold = GSFontCreateWithName("Helvetica", kGSFontTraitBold, 22); + GSFontRef small = GSFontCreateWithName("Helvetica", kGSFontTraitBold, 12); + + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + CGColor clear(space, 0, 0, 0, 0); + CGColor white(space, 1, 1, 1, 1); + + name_ = [[UITextLabel alloc] initWithFrame:CGRectMake(47, 9, 250, 25)]; + [name_ setBackgroundColor:clear]; + [name_ setFont:bold]; + + count_ = [[UITextLabel alloc] initWithFrame:CGRectMake(11, 7, 29, 32)]; + [count_ setCentersHorizontally:YES]; + [count_ setBackgroundColor:clear]; + [count_ setFont:small]; + [count_ setColor:white]; + + UIImageView *folder = [[[UIImageView alloc] initWithFrame:CGRectMake(8, 7, 32, 32)] autorelease]; + [folder setImage:[UIImage applicationImageNamed:@"folder.png"]]; + + [self addSubview:folder]; + [self addSubview:name_]; + [self addSubview:count_]; + + [self _setSelected:0]; + + CGColorSpaceRelease(space); + + CFRelease(small); + CFRelease(bold); } return self; } -- (SEL) selector { - return selector_; +- (void) setSection:(Section *)section { + if (section == nil) { + [name_ setText:@"All Packages"]; + [count_ setText:nil]; + } else { + [name_ setText:[section name]]; + [count_ setText:[NSString stringWithFormat:@"%d", [section count]]]; + } } -- (id) target { - return target_; +- (void) _setSelected:(float)fraction { + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + + CGColor black(space, + interpolate(0.0, 1.0, fraction), + interpolate(0.0, 1.0, fraction), + interpolate(0.0, 1.0, fraction), + 1.0); + + [name_ setColor:black]; + + CGColorSpaceRelease(space); } -- (id) object { - return object_; +- (void) setSelected:(BOOL)selected { + [self _setSelected:(selected ? 1.0 : 0.0)]; + [super setSelected:selected]; } -@end -/* }}} */ -/* Progress View {{{ */ -@interface ProgressView : UIView < - ProgressDelegate -> { - UIView *view_; - UIView *background_; - UITransitionView *transition_; - UIView *overlay_; - UINavigationBar *navbar_; - UIProgressBar *progress_; - UITextView *output_; - UITextLabel *status_; - id delegate_; +- (void) setSelected:(BOOL)selected withFade:(BOOL)fade { + if (!fade) + [self _setSelected:(selected ? 1.0 : 0.0)]; + [super setSelected:selected withFade:fade]; } -- (void) dealloc; - -- (ProgressView *) initWithFrame:(struct CGRect)frame delegate:(id)delegate; -- (void) setContentView:(UIView *)view; -- (void) resetView; - -- (void) alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button; +- (void) _setSelectionFadeFraction:(float)fraction { + [self _setSelected:fraction]; + [super _setSelectionFadeFraction:fraction]; +} -- (void) _retachThread; -- (void) _detachNewThreadData:(ProgressData *)data; -- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object; +@end +/* }}} */ +/* Install View {{{ */ +@interface InstallView : ResetView < + PackageTableDelegate +> { + NSArray *sections_; + UITable *list_; + PackageTable *table_; + PackageView *view_; + NSString *section_; + NSString *package_; + NSMutableArray *packages_; +} -- (void) setError:(NSString *)error; -- (void) _setError:(NSString *)error; +- (void) dealloc; -- (void) setTitle:(NSString *)title; -- (void) _setTitle:(NSString *)title; +- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button; -- (void) setPercent:(float)percent; -- (void) _setPercent:(NSNumber *)percent; +- (int) numberOfRowsInTable:(UITable *)table; +- (float) table:(UITable *)table heightForRow:(int)row; +- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing; +- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row; +- (void) tableRowSelected:(NSNotification *)notification; -- (void) addOutput:(NSString *)output; -- (void) _addOutput:(NSString *)output; -@end +- (void) packageTable:(id)table packageSelected:(Package *)package; -@protocol ProgressViewDelegate -- (void) progressViewIsComplete:(ProgressView *)sender; +- (id) initWithFrame:(CGRect)frame; +- (void) setPackages:(NSArray *)packages; +- (void) setDelegate:(id)delegate; @end -@implementation ProgressView +@implementation InstallView - (void) dealloc { - [view_ release]; - [background_ release]; - [transition_ release]; - [overlay_ release]; - [navbar_ release]; - [progress_ release]; - [output_ release]; - [status_ release]; + [packages_ release]; + if (sections_ != nil) + [sections_ release]; + if (list_ != nil) + [list_ release]; + if (table_ != nil) + [table_ release]; + if (view_ != nil) + [view_ release]; + if (section_ != nil) + [section_ release]; + if (package_ != nil) + [package_ release]; [super dealloc]; } -- (ProgressView *) initWithFrame:(struct CGRect)frame delegate:(id)delegate { - if ((self = [super initWithFrame:frame]) != nil) { - delegate_ = delegate; - - CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); - float black[] = {0.0, 0.0, 0.0, 1.0}; - float white[] = {1.0, 1.0, 1.0, 1.0}; - float clear[] = {0.0, 0.0, 0.0, 0.0}; +- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button { + if (button == 0) { + [[view_ package] install]; + [delegate_ resolve]; + [delegate_ perform]; + } +} - background_ = [[UIView alloc] initWithFrame:[self bounds]]; - [background_ setBackgroundColor:CGColorCreate(space, black)]; - [self addSubview:background_]; +- (int) numberOfRowsInTable:(UITable *)table { + return [sections_ count] + 1; +} - transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]]; - [self addSubview:transition_]; +- (float) table:(UITable *)table heightForRow:(int)row { + return 45; +} - overlay_ = [[UIView alloc] initWithFrame:[transition_ bounds]]; +- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing { + if (reusing == nil) + reusing = [[[SectionCell alloc] init] autorelease]; + if (row == 0) + [(SectionCell *)reusing setSection:nil]; + else + [(SectionCell *)reusing setSection:[sections_ objectAtIndex:(row - 1)]]; + return reusing; +} - CGSize navsize = [UINavigationBar defaultSize]; - CGRect navrect = {{0, 0}, navsize}; +- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row { + return YES; +} - navbar_ = [[UINavigationBar alloc] initWithFrame:navrect]; - [overlay_ addSubview:navbar_]; +- (void) tableRowSelected:(NSNotification *)notification { + int row = [[notification object] selectedRow]; - [navbar_ setBarStyle:1]; - [navbar_ setDelegate:self]; + if (row == INT_MAX) { + [section_ release]; + section_ = nil; - UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:@"Running..."] autorelease]; - [navbar_ pushNavigationItem:navitem]; + [table_ release]; + table_ = nil; + } else { + _assert(section_ == nil); + _assert(table_ == nil); - CGRect bounds = [overlay_ bounds]; - CGSize prgsize = [UIProgressBar defaultSize]; + Section *section; + NSString *name; - CGRect prgrect = {{ - (bounds.size.width - prgsize.width) / 2, - bounds.size.height - prgsize.height - 20 - }, prgsize}; + if (row == 0) { + section = nil; + section_ = nil; + name = @"All Packages"; + } else { + section = [sections_ objectAtIndex:(row - 1)]; + name = [section name]; + section_ = [name retain]; + } - progress_ = [[UIProgressBar alloc] initWithFrame:prgrect]; - [overlay_ addSubview:progress_]; + table_ = [[PackageTable alloc] initWithFrame:[transition_ bounds] versioner:@selector(latest)]; + [table_ setDelegate:self]; + [table_ setPackages:(section == nil ? packages_ : [section packages])]; - status_ = [[UITextLabel alloc] initWithFrame:CGRectMake( - 10, - bounds.size.height - prgsize.height - 50, - bounds.size.width - 20, - 24 - )]; + [self pushView:table_ withTitle:name backButtonTitle:@"Packages" rightButton:nil]; + } +} - [status_ setColor:CGColorCreate(space, white)]; - [status_ setBackgroundColor:CGColorCreate(space, clear)]; +- (void) packageTable:(id)table packageSelected:(Package *)package { + if (package == nil) { + [package_ release]; + package_ = nil; - [status_ setCentersHorizontally:YES]; - //[status_ setFont:font]; + [view_ release]; + view_ = nil; + } else { + _assert(package_ == nil); + _assert(view_ == nil); - output_ = [[UITextView alloc] initWithFrame:CGRectMake( - 10, - navrect.size.height + 20, - bounds.size.width - 20, - bounds.size.height - navsize.height - 62 - navrect.size.height - )]; + package_ = [[package name] retain]; - //[output_ setTextFont:@"Courier New"]; - [output_ setTextSize:12]; + view_ = [[PackageView alloc] initWithFrame:[transition_ bounds]]; + [view_ setDelegate:delegate_]; - [output_ setTextColor:CGColorCreate(space, white)]; - [output_ setBackgroundColor:CGColorCreate(space, clear)]; + [view_ setPackage:package]; - [output_ setMarginTop:0]; - [output_ setAllowsRubberBanding:YES]; + [self pushView:view_ withTitle:[package name] backButtonTitle:nil rightButton:@"Install"]; + } +} - [overlay_ addSubview:output_]; - [overlay_ addSubview:status_]; +- (id) initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame]) != nil) { + packages_ = [[NSMutableArray arrayWithCapacity:16] retain]; - [progress_ setStyle:0]; - } return self; -} + list_ = [[UITable alloc] initWithFrame:[transition_ bounds]]; + [self pushView:list_ withTitle:@"Install" backButtonTitle:@"Sections" rightButton:nil]; -- (void) setContentView:(UIView *)view { - view_ = view; -} + UITableColumn *column = [[[UITableColumn alloc] + initWithTitle:@"Name" + identifier:@"name" + width:frame.size.width + ] autorelease]; -- (void) resetView { - [transition_ transition:6 toView:view_]; -} + [list_ setDataSource:self]; + [list_ setSeparatorStyle:1]; + [list_ addTableColumn:column]; + [list_ setDelegate:self]; + [list_ setReusesTableCells:YES]; -- (void) alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button { - [sheet dismiss]; + [transition_ transition:0 toView:list_]; + } return self; } -- (void) _retachThread { - [delegate_ progressViewIsComplete:self]; - [self resetView]; -} +- (void) setPackages:(NSArray *)packages { + [packages_ removeAllObjects]; -- (void) _detachNewThreadData:(ProgressData *)data { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + for (size_t i(0); i != [packages count]; ++i) { + Package *package([packages objectAtIndex:i]); + if ([package installed] == nil) + [packages_ addObject:package]; + } - [[data target] performSelector:[data selector] withObject:[data object]]; - [self performSelectorOnMainThread:@selector(_retachThread) withObject:nil waitUntilDone:YES]; + [packages_ sortUsingSelector:@selector(compareBySectionAndName:)]; + NSMutableArray *sections = [NSMutableArray arrayWithCapacity:16]; - [data release]; - [pool release]; -} + Section *nsection = nil; + Package *npackage = nil; -- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object { - [status_ setText:nil]; - [output_ setText:@""]; - [progress_ setProgress:0]; + Section *section = nil; + for (size_t offset = 0, count = [packages_ count]; offset != count; ++offset) { + Package *package = [packages_ objectAtIndex:offset]; + NSString *name = [package section]; - [transition_ transition:6 toView:overlay_]; + if (section == nil || ![[section name] isEqual:name]) { + section = [[[Section alloc] initWithName:name row:offset] autorelease]; - [NSThread - detachNewThreadSelector:@selector(_detachNewThreadData:) - toTarget:self - withObject:[[ProgressData alloc] - initWithSelector:selector - target:target - object:object - ] - ]; -} + if ([name isEqualToString:section_]) + nsection = section; + [sections addObject:section]; + } -- (void) setError:(NSString *)error { - [self - performSelectorOnMainThread:@selector(_setError:) - withObject:error - waitUntilDone:YES - ]; -} + if ([[package name] isEqualToString:package_]) + npackage = package; + [section addPackage:package]; + } -- (void) _setError:(NSString *)error { - UIAlertSheet *sheet = [[[UIAlertSheet alloc] - initWithTitle:@"Package Error" - buttons:[NSArray arrayWithObjects:@"Okay", nil] - defaultButtonIndex:0 - delegate:self - context:self - ] autorelease]; + if (sections_ != nil) + [sections_ release]; + sections_ = [sections retain]; - [sheet setBodyText:error]; - [sheet popupAlertAnimated:YES]; -} + [packages_ sortUsingSelector:@selector(compareByName:)]; -- (void) setTitle:(NSString *)title { - [self - performSelectorOnMainThread:@selector(_setTitle:) - withObject:title - waitUntilDone:YES - ]; -} + [list_ reloadData]; -- (void) _setTitle:(NSString *)title { - [status_ setText:[title stringByAppendingString:@"..."]]; -} + unsigned views(0); -- (void) setPercent:(float)percent { - [self - performSelectorOnMainThread:@selector(_setPercent:) - withObject:[NSNumber numberWithFloat:percent] - waitUntilDone:YES - ]; -} + if (npackage != nil) + [view_ setPackage:npackage]; + else if (package_ != nil) + ++views; -- (void) _setPercent:(NSNumber *)percent { - [progress_ setProgress:[percent floatValue]]; -} + if (nsection != nil) + [table_ setPackages:[nsection packages]]; + else if (section_ != nil) + ++views; -- (void) addOutput:(NSString *)output { - [self - performSelectorOnMainThread:@selector(_addOutput:) - withObject:output - waitUntilDone:YES - ]; + [self popViews:views]; + [self setPrompt]; } -- (void) _addOutput:(NSString *)output { - [output_ setText:[NSString stringWithFormat:@"%@\n%@", [output_ text], output]]; - CGSize size = [output_ contentSize]; - CGRect rect = {{0, size.height}, {size.width, 0}}; - [output_ scrollRectToVisible:rect animated:YES]; +- (void) setDelegate:(id)delegate { + if (view_ != nil) + [view_ setDelegate:delegate]; + [super setDelegate:delegate]; } @end /* }}} */ - -@protocol PackagesViewDelegate -- (void) perform; -- (void) update; -- (void) openURL:(NSString *)url; -@end - -/* PackagesView {{{ */ -@interface PackagesView : ResetView < - PackageCellDelegate +/* Changes View {{{ */ +@interface ChangesView : ResetView < + PackageTableDelegate > { - Database *database_; + UISectionList *list_; NSMutableArray *packages_; NSMutableArray *sections_; - id delegate_; - UISectionList *list_; - UITransitionView *transition_; - Package *package_; - NSString *pkgname_; - PackageView *pkgview_; + PackageView *view_; + NSString *package_; + size_t count_; } +- (void) dealloc; + +- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button; + - (int) numberOfSectionsInSectionList:(UISectionList *)list; - (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section; - (int) sectionList:(UISectionList *)list rowForSection:(int)section; @@ -1978,24 +2500,28 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString - (float) table:(UITable *)table heightForRow:(int)row; - (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing; - (BOOL) table:(UITable *)table showDisclosureForRow:(int)row; -- (void) tableRowSelected:(NSNotification*)notification; +- (void) tableRowSelected:(NSNotification *)notification; -- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button; -- (void) navigationBar:(UINavigationBar *)navbar poppedItem:(UINavigationItem *)item; +- (id) initWithFrame:(CGRect)frame; +- (void) setPackages:(NSArray *)packages; +- (void) _resetView; +- (size_t) count; -- (id) initWithFrame:(struct CGRect)frame database:(Database *)database; - (void) setDelegate:(id)delegate; -- (void) deselect; -- (void) reloadData:(BOOL)reset; - -- (NSMutableArray *) packages; -- (NSString *) title; -- (void) perform:(Package *)package; -- (void) addPackage:(Package *)package; -- (NSString *) versionWithPackage:(Package *)package; @end -@implementation PackagesView +@implementation ChangesView + +- (void) dealloc { + [list_ release]; + [packages_ release]; + [sections_ release]; + if (view_ != nil) + [view_ release]; + if (package_ != nil) + [package_ release]; + [super dealloc]; +} - (int) numberOfSectionsInSectionList:(UISectionList *)list { return [sections_ count]; @@ -2019,86 +2545,64 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString - (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing { if (reusing == nil) - reusing = [[PackageCell alloc] initWithDelegate:self]; + reusing = [[[PackageCell alloc] initWithVersioner:NULL] autorelease]; [(PackageCell *)reusing setPackage:[packages_ objectAtIndex:row]]; return reusing; } - (BOOL) table:(UITable *)table showDisclosureForRow:(int)row { - return YES; + return NO; } -- (void) tableRowSelected:(NSNotification*)notification { - int row = [[list_ table] selectedRow]; - if (row == INT_MAX) - return; - - package_ = [packages_ objectAtIndex:row]; - pkgname_ = [[package_ name] retain]; - - UINavigationItem *navitem = [[UINavigationItem alloc] initWithTitle:[package_ name]]; - [navbar_ pushNavigationItem:navitem]; - - [navbar_ showButtonsWithLeftTitle:nil rightTitle:[self title]]; - - [pkgview_ setPackage:package_]; - [transition_ transition:1 toView:pkgview_]; +- (void) tableRowSelected:(NSNotification *)notification { + int row = [[notification object] selectedRow]; + [self packageTable:self packageSelected:(row == INT_MAX ? nil : [packages_ objectAtIndex:row])]; } - (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button { - if (button == 0) { - [self perform:package_]; - - pkgProblemResolver *resolver = [database_ resolver]; - - resolver->InstallProtect(); - if (!resolver->Resolve(true)) - _error->Discard(); - - [delegate_ perform]; - } -} - -- (void) navigationBar:(UINavigationBar *)navbar poppedItem:(UINavigationItem *)item { - [self deselect]; - [super navigationBar:navbar poppedItem:item]; + if (button == 1) + [delegate_ upgrade]; } -- (id) initWithFrame:(struct CGRect)frame database:(Database *)database { - if ((self = [super initWithFrame:frame]) != nil) { - database_ = [database retain]; +- (void) packageTable:(id)table packageSelected:(Package *)package { + if (package == nil) { + [package_ release]; + package_ = nil; - struct CGRect bounds = [self bounds]; - CGSize navsize = [UINavigationBar defaultSize]; - CGRect navrect = {{0, 0}, navsize}; + [view_ release]; + view_ = nil; + } else { + _assert(package_ == nil); + _assert(view_ == nil); - navbar_ = [[UINavigationBar alloc] initWithFrame:navrect]; - [self addSubview:navbar_]; + package_ = [[package name] retain]; - [navbar_ setBarStyle:1]; - [navbar_ setDelegate:self]; + view_ = [[PackageView alloc] initWithFrame:[transition_ bounds]]; + [view_ setDelegate:delegate_]; - UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:[self title]] autorelease]; - [navbar_ pushNavigationItem:navitem]; - [navitem setBackButtonTitle:@"Packages"]; + [view_ setPackage:package]; - transition_ = [[UITransitionView alloc] initWithFrame:CGRectMake( - bounds.origin.x, bounds.origin.y + navsize.height, bounds.size.width, bounds.size.height - navsize.height + [self pushView:view_ withTitle:[package name] backButtonTitle:nil rightButton:( + [package upgradable] ? @"Upgrade" : @"Install" )]; + } +} - [self addSubview:transition_]; +- (id) initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame]) != nil) { + packages_ = [[NSMutableArray arrayWithCapacity:16] retain]; + sections_ = [[NSMutableArray arrayWithCapacity:16] retain]; list_ = [[UISectionList alloc] initWithFrame:[transition_ bounds] showSectionIndex:NO]; - [list_ setDataSource:self]; [list_ setShouldHideHeaderInShortLists:NO]; + [list_ setDataSource:self]; + //[list_ setSectionListStyle:1]; - [transition_ transition:0 toView:list_]; - - UITableColumn *column = [[UITableColumn alloc] + UITableColumn *column = [[[UITableColumn alloc] initWithTitle:@"Name" identifier:@"name" width:frame.size.width - ]; + ] autorelease]; UITable *table = [list_ table]; [table setSeparatorStyle:1]; @@ -2106,187 +2610,397 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString [table setDelegate:self]; [table setReusesTableCells:YES]; - pkgview_ = [[PackageView alloc] initWithFrame:[transition_ bounds] database:database_]; + [self pushView:list_ withTitle:@"Changes" backButtonTitle:nil rightButton:nil]; } return self; } -- (void) setDelegate:(id)delegate { - delegate_ = delegate; - [pkgview_ setDelegate:delegate]; -} - -- (void) deselect { - [transition_ transition:(resetting_ ? 0 : 2) toView:list_]; - UITable *table = [list_ table]; - [table selectRow:-1 byExtendingSelection:NO withFade:(resetting_ ? NO : YES)]; - package_ = nil; -} - -- (void) reloadData:(BOOL)reset { - if (sections_ != nil) - [sections_ release]; - if (packages_ != nil) - [packages_ release]; - - packages_ = [[NSMutableArray arrayWithCapacity:16] retain]; +- (void) setPackages:(NSArray *)packages { + [packages_ removeAllObjects]; + for (size_t i(0); i != [packages count]; ++i) { + Package *package([packages objectAtIndex:i]); + if ([package installed] == nil || [package upgradable]) + [packages_ addObject:package]; + } - for (pkgCache::PkgIterator iterator = [database_ cache]->PkgBegin(); !iterator.end(); ++iterator) - if (Package *package = [Package packageWithIterator:iterator database:database_]) - [self addPackage:package]; + [packages_ sortUsingSelector:@selector(compareForChanges:)]; - [packages_ sortUsingSelector:@selector(compareBySectionAndName:)]; - sections_ = [[NSMutableArray arrayWithCapacity:16] retain]; + [sections_ removeAllObjects]; + Section *upgradable = [[[Section alloc] initWithName:@"Available Upgrades" row:0] autorelease]; Section *section = nil; + + count_ = 0; + Package *npackage = nil; for (size_t offset = 0, count = [packages_ count]; offset != count; ++offset) { Package *package = [packages_ objectAtIndex:offset]; - NSString *name = [package section]; + if ([[package name] isEqualToString:package_]) + npackage = package; - if (section == nil || ![[section name] isEqual:name]) { - section = [[Section alloc] initWithName:name row:offset]; - [sections_ addObject:section]; - } + if ([package upgradable]) + [upgradable addPackage:package]; + else { + NSDate *seen = [package seen]; - [section addPackage:package]; + CFLocaleRef locale = CFLocaleCopyCurrent(); + CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, locale, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle); + CFStringRef formatted = CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) seen); + + NSString *name = (NSString *) formatted; + + if (section == nil || ![[section name] isEqual:name]) { + section = [[[Section alloc] initWithName:name row:offset] autorelease]; + [sections_ addObject:section]; + } + + [section addPackage:package]; + + CFRelease(formatter); + CFRelease(formatted); + CFRelease(locale); + } } + count_ = [[upgradable packages] count]; + if (count_ != 0) + [sections_ insertObject:upgradable atIndex:0]; + [list_ reloadData]; - if (reset) - [self resetView]; - else if (package_ != nil) { - package_ = [database_ packageWithName:pkgname_]; - [pkgview_ setPackage:package_]; - } -} -- (NSMutableArray *) packages { - return packages_; -} + if (npackage != nil) + [view_ setPackage:npackage]; + else if (package_ != nil) + [self popViews:1]; + if ([views_ count] == 1) + [self _resetView]; -- (NSString *) title { - return nil; + [self setPrompt]; } -- (void) perform:(Package *)package { +- (void) _resetView { + [navbar_ showButtonsWithLeftTitle:(count_ == 0 ? nil : @"Upgrade All") rightTitle:nil]; + [super _resetView]; } -- (void) addPackage:(Package *)package { - [packages_ addObject:package]; +- (size_t) count { + return count_; } -- (NSString *) versionWithPackage:(Package *)package { - return nil; +- (void) setDelegate:(id)delegate { + if (view_ != nil) + [view_ setDelegate:delegate]; + [super setDelegate:delegate]; } @end /* }}} */ - -/* InstallView {{{ */ -@interface InstallView : PackagesView { +/* Manage View {{{ */ +@interface ManageView : ResetView < + PackageTableDelegate +> { + PackageTable *table_; + PackageView *view_; + NSString *package_; } -- (NSString *) title; -- (void) addPackage:(Package *)package; -- (void) perform:(Package *)package; -- (NSString *) versionWithPackage:(Package *)package; +- (void) dealloc; + +- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button; + +- (void) packageTable:(id)table packageSelected:(Package *)package; + +- (id) initWithFrame:(CGRect)frame; +- (void) setPackages:(NSArray *)packages; + +- (void) setDelegate:(id)delegate; @end -@implementation InstallView +@implementation ManageView -- (NSString *) title { - return @"Install"; +- (void) dealloc { + [table_ release]; + if (view_ != nil) + [view_ release]; + if (package_ != nil) + [package_ release]; + [super dealloc]; } -- (void) addPackage:(Package *)package { - if ([package installed] == nil) - [super addPackage:package]; +- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button { + if (button == 0) { + [[view_ package] remove]; + [delegate_ resolve]; + [delegate_ perform]; + } +} + +- (void) packageTable:(id)table packageSelected:(Package *)package { + if (package == nil) { + [package_ release]; + package_ = nil; + + [view_ release]; + view_ = nil; + } else { + _assert(package_ == nil); + _assert(view_ == nil); + + package_ = [[package name] retain]; + + view_ = [[PackageView alloc] initWithFrame:[transition_ bounds]]; + [view_ setDelegate:delegate_]; + + [view_ setPackage:package]; + + [self pushView:view_ withTitle:[package name] backButtonTitle:nil rightButton:@"Uninstall"]; + } +} + +- (id) initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame]) != nil) { + table_ = [[PackageTable alloc] initWithFrame:[transition_ bounds] versioner:@selector(latest)]; + [table_ setDelegate:self]; + + [self pushView:table_ withTitle:@"Uninstall" backButtonTitle:@"Packages" rightButton:nil]; + } return self; } -- (void) perform:(Package *)package { - [package install]; +- (void) setPackages:(NSArray *)packages { + NSMutableArray *local = [NSMutableArray arrayWithCapacity:16]; + for (size_t i(0); i != [packages count]; ++i) { + Package *package([packages objectAtIndex:i]); + if ([package installed] != nil) + [local addObject:package]; + } + + [local sortUsingSelector:@selector(compareByName:)]; + + Package *npackage = nil; + for (size_t offset = 0, count = [local count]; offset != count; ++offset) { + Package *package = [local objectAtIndex:offset]; + if ([[package name] isEqualToString:package_]) + npackage = package; + } + + [table_ setPackages:local]; + + if (npackage != nil) + [view_ setPackage:npackage]; + else if (package_ != nil) + [self popViews:1]; + + [self setPrompt]; } -- (NSString *) versionWithPackage:(Package *)package { - return [package latest]; +- (void) setDelegate:(id)delegate { + if (view_ != nil) + [view_ setDelegate:delegate]; + [super setDelegate:delegate]; } @end /* }}} */ -/* UpgradeView {{{ */ -@interface UpgradeView : PackagesView { +/* Search View {{{ */ +@protocol SearchViewDelegate +- (void) showKeyboard:(BOOL)show; +@end + +@interface SearchView : ResetView < + PackageTableDelegate +> { + NSMutableArray *packages_; + UIView *accessory_; + UISearchField *field_; + PackageTable *table_; + PackageView *view_; + NSString *package_; } +- (void) dealloc; + - (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button; +- (void) packageTable:(id)table packageSelected:(Package *)package; + +- (void) textFieldDidBecomeFirstResponder:(UITextField *)field; +- (void) textFieldDidResignFirstResponder:(UITextField *)field; + +- (void) keyboardInputChanged:(UIFieldEditor *)editor; +- (BOOL) keyboardInput:(id)input shouldInsertText:(NSString *)text isMarkedText:(int)marked; + +- (id) initWithFrame:(CGRect)frame; +- (void) setPackages:(NSArray *)packages; + +- (void) setDelegate:(id)delegate; +- (void) resetPackage:(Package *)package; +- (void) searchPackages; -- (NSString *) title; -- (NSString *) leftTitle; -- (void) addPackage:(Package *)package; -- (void) perform:(Package *)package; -- (NSString *) versionWithPackage:(Package *)package; @end -@implementation UpgradeView +@implementation SearchView + +- (void) dealloc { + [packages_ release]; + [accessory_ release]; + [field_ release]; + [table_ release]; + if (view_ != nil) + [view_ release]; + if (package_ != nil) + [package_ release]; + [super dealloc]; +} - (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button { - if (button != 1) - [super navigationBar:navbar buttonClicked:button]; - else { - [database_ upgrade]; + if (button == 0) { + Package *package = [view_ package]; + if ([package installed] == nil) + [package install]; + else + [package remove]; + [delegate_ resolve]; [delegate_ perform]; } } -- (NSString *) title { - return @"Upgrade"; +- (void) packageTable:(id)table packageSelected:(Package *)package { + if (package == nil) { + [navbar_ setAccessoryView:accessory_ animate:(resetting_ ? NO : YES) goingBack:YES]; + + [package_ release]; + package_ = nil; + + [view_ release]; + view_ = nil; + } else { + [navbar_ setAccessoryView:nil animate:YES goingBack:NO]; + + _assert(package_ == nil); + _assert(view_ == nil); + + package_ = [[package name] retain]; + + view_ = [[PackageView alloc] initWithFrame:[transition_ bounds]]; + [view_ setDelegate:delegate_]; + + [self pushView:view_ withTitle:[package name] backButtonTitle:nil rightButton:nil]; + [self resetPackage:package]; + } } -- (NSString *) leftTitle { - return [packages_ count] == 0 ? nil : @"Upgrade All"; +- (void) textFieldDidBecomeFirstResponder:(UITextField *)field { + [delegate_ showKeyboard:YES]; + [table_ setEnabled:NO]; + + /*CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + CGColor dimmed(alpha, 0, 0, 0, 0.5); + [editor_ setBackgroundColor:dimmed]; + CGColorSpaceRelease(space);*/ } -- (void) addPackage:(Package *)package { - NSString *installed = [package installed]; - if (installed != nil && [[package latest] compare:installed] != NSOrderedSame) - [super addPackage:package]; +- (void) textFieldDidResignFirstResponder:(UITextField *)field { + [table_ setEnabled:YES]; + [delegate_ showKeyboard:NO]; } -- (void) perform:(Package *)package { - [package install]; +- (void) keyboardInputChanged:(UIFieldEditor *)editor { + NSString *text([field_ text]); + [field_ setClearButtonStyle:(text == nil || [text length] == 0 ? 0 : 2)]; } -- (NSString *) versionWithPackage:(Package *)package { - return [package latest]; +- (BOOL) keyboardInput:(id)input shouldInsertText:(NSString *)text isMarkedText:(int)marked { + if ([text length] != 1 || [text characterAtIndex:0] != '\n') + return YES; + + [self searchPackages]; + [field_ resignFirstResponder]; + return NO; } -@end -/* }}} */ -/* UninstallView {{{ */ -@interface UninstallView : PackagesView { +- (id) initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame]) != nil) { + packages_ = [[NSMutableArray arrayWithCapacity:16] retain]; + + table_ = [[PackageTable alloc] initWithFrame:[transition_ bounds] versioner:@selector(latest)]; + [table_ setDelegate:self]; + + CGRect area = [self bounds]; + area.origin.y = 30; + area.origin.x = 0; + area.size.width -= 12; + area.size.height = [UISearchField defaultHeight]; + + field_ = [[UISearchField alloc] initWithFrame:area]; + + GSFontRef font = GSFontCreateWithName("Helvetica", kGSFontTraitNone, 16); + [field_ setFont:font]; + CFRelease(font); + + [field_ setPlaceholder:@"Package Names & Descriptions"]; + [field_ setPaddingTop:5]; + [field_ setDelegate:self]; + + UITextTraits *traits = [field_ textTraits]; + [traits setEditingDelegate:self]; + [traits setReturnKeyType:6]; + + accessory_ = [[UIView alloc] initWithFrame:CGRectMake(6, 6, area.size.width, area.size.height + 30)]; + [accessory_ addSubview:field_]; + + [navbar_ setAccessoryView:accessory_]; + [self pushView:table_ withTitle:nil backButtonTitle:@"Search" rightButton:nil]; + + /* XXX: for the love of god just fix this */ + [navbar_ removeFromSuperview]; + [self addSubview:navbar_]; + } return self; } -- (NSString *) title; -- (void) addPackage:(Package *)package; -- (void) perform:(Package *)package; -- (NSString *) versionWithPackage:(Package *)package; -@end +- (void) setPackages:(NSArray *)packages { + [packages_ removeAllObjects]; + [packages_ addObjectsFromArray:packages]; + [packages_ sortUsingSelector:@selector(compareByName:)]; -@implementation UninstallView + Package *npackage = nil; + for (size_t offset = 0, count = [packages_ count]; offset != count; ++offset) { + Package *package = [packages_ objectAtIndex:offset]; + if ([[package name] isEqualToString:package_]) + npackage = package; + } + + [self searchPackages]; -- (NSString *) title { - return @"Uninstall"; + if (npackage != nil) + [self resetPackage:npackage]; + else if (package_ != nil) + [self popViews:1]; + + [self setPrompt]; } -- (void) addPackage:(Package *)package { - if ([package installed] != nil) - [super addPackage:package]; +- (void) setDelegate:(id)delegate { + if (view_ != nil) + [view_ setDelegate:delegate]; + [super setDelegate:delegate]; } -- (void) perform:(Package *)package { - [package remove]; +- (void) resetPackage:(Package *)package { + [view_ setPackage:package]; + NSString *right = [package installed] == nil ? @"Install" : @"Uninstall"; + [navbar_ showButtonsWithLeftTitle:nil rightTitle:right]; } -- (NSString *) versionWithPackage:(Package *)package { - return [package installed]; +- (void) searchPackages { + NSString *text([field_ text]); + + NSMutableArray *packages([NSMutableArray arrayWithCapacity:16]); + + for (size_t offset(0), count([packages_ count]); offset != count; ++offset) { + Package *package = [packages_ objectAtIndex:offset]; + if ([package matches:text]) + [packages addObject:package]; + } + + [table_ setPackages:packages]; + [[table_ table] scrollPointVisibleAtTopLeft:CGPointMake(0, 0) animated:NO]; } @end @@ -2294,8 +3008,8 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString @interface Cydia : UIApplication < ConfirmationViewDelegate, - PackagesViewDelegate, - ProgressViewDelegate + ProgressViewDelegate, + SearchViewDelegate > { UIWindow *window_; UIView *underlay_; @@ -2316,17 +3030,26 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString UIProgressIndicator *indicator_; InstallView *install_; - UpgradeView *upgrade_; - UninstallView *uninstall_; - SourcesView *sources_; + ChangesView *changes_; + ManageView *manage_; + SearchView *search_; + + bool restart_; + + UIKeyboard *keyboard_; } - (void) loadNews; - (void) reloadData:(BOOL)reset; +- (void) setPrompt; + +- (void) resolve; - (void) perform; +- (void) upgrade; +- (void) update; + - (void) cancel; - (void) confirm; -- (void) update; - (void) progressViewIsComplete:(ProgressView *)progress; @@ -2336,11 +3059,10 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString - (void) view:(UIView *)sender didSetFrame:(CGRect)frame oldFrame:(CGRect)old; +- (void) applicationWillSuspend; - (void) applicationDidFinishLaunching:(id)unused; @end -#include - @implementation Cydia - (void) loadNews { @@ -2359,12 +3081,28 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString - (void) reloadData:(BOOL)reset { [database_ reloadData]; - [install_ reloadData:reset]; - [upgrade_ reloadData:reset]; - [uninstall_ reloadData:reset]; - [sources_ reloadData]; - if (size_t count = [[upgrade_ packages] count]) { + size_t count = 16; + + if (Packages_ == nil) { + Packages_ = [[NSMutableDictionary alloc] initWithCapacity:count]; + [Metadata_ setObject:Packages_ forKey:@"Packages"]; + } + + now_ = [NSDate date]; + + NSMutableArray *packages = [NSMutableArray arrayWithCapacity:count]; + for (pkgCache::PkgIterator iterator = [database_ cache]->PkgBegin(); !iterator.end(); ++iterator) + if (Package *package = [Package packageWithIterator:iterator database:database_]) + [packages addObject:package]; + + [install_ setPackages:packages]; + [changes_ setPackages:packages]; + [manage_ setPackages:packages]; + [search_ setPackages:packages]; + //[self setPrompt]; + + if (size_t count = [changes_ count]) { NSString *badge([[NSNumber numberWithInt:count] stringValue]); [buttonbar_ setBadgeValue:badge forButton:3]; [buttonbar_ setBadgeAnimated:YES forButton:3]; @@ -2374,6 +3112,30 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString [buttonbar_ setBadgeAnimated:NO forButton:3]; [self removeApplicationBadge]; } + + _assert([Metadata_ writeToFile:@"/var/lib/cydia/metadata.plist" atomically:YES] == YES); +} + +- (void) setPrompt { + NSDate *update = [Metadata_ objectForKey:@"LastUpdate"]; + + CFLocaleRef locale = CFLocaleCopyCurrent(); + CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, locale, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle); + CFStringRef formatted = CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) update); + + [navbar_ setPrompt:[NSString stringWithFormat:@"Last Updated: %@", (NSString *) formatted]]; + + CFRelease(formatter); + CFRelease(formatted); + CFRelease(locale); +} + +- (void) resolve { + pkgProblemResolver *resolver = [database_ resolver]; + + resolver->InstallProtect(); + if (!resolver->Resolve(true)) + _error->Discard(); } - (void) perform { @@ -2381,6 +3143,11 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString confirm_ = [[ConfirmationView alloc] initWithView:underlay_ database:database_ delegate:self]; } +- (void) upgrade { + [database_ upgrade]; + [self perform]; +} + - (void) cancel { [self reloadData:NO]; [confirm_ release]; @@ -2389,11 +3156,13 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString - (void) confirm { [overlay_ removeFromSuperview]; + restart_ = true; [progress_ detachNewThreadSelector:@selector(perform) toTarget:database_ withObject:nil + title:@"Running..." ]; } @@ -2402,6 +3171,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString detachNewThreadSelector:@selector(update) toTarget:database_ withObject:nil + title:@"Refreshing Sources..." ]; } @@ -2432,7 +3202,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString ] autorelease]; [sheet setBodyText: - @"Copyright (C) 2007\n" + @"Copyright (C) 2008\n" "Jay Freeman (saurik)\n" "saurik@saurik.com\n" "http://www.saurik.com/\n" @@ -2446,10 +3216,11 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString "http://www.ccs.ucsb.edu/\n" "\n" "Special Thanks:\n" - "bad_, BHSPitMonkey, Cobra, core,\n" - "Corona, cromas, Darken, dtzWill,\n" - "francis, Godores, jerry, Kingstone,\n" - "lounger, rockabilly, tman, Wbiggs" + "bad_, BHSPitMonkey, cash, Cobra,\n" + "core, Corona, crashx, cromas,\n" + "Darken, dtzWill, Erica, francis,\n" + "Godores, jerry, Kingstone, lounger,\n" + "mbranes, rockabilly, tman, Wbiggs" ]; [sheet presentSheetFromButtonBar:buttonbar_]; @@ -2467,9 +3238,9 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString switch ([sender tag]) { case 1: view = featured_; break; case 2: view = install_; break; - case 3: view = upgrade_; break; - case 4: view = uninstall_; break; - case 5: view = sources_; break; + case 3: view = changes_; break; + case 4: view = manage_; break; + case 5: view = search_; break; default: _assert(false); @@ -2485,12 +3256,19 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString [indicator_ stopAnimation]; } +- (void) applicationWillSuspend { + if (restart_) + system("launchctl stop com.apple.SpringBoard"); +} + - (void) applicationDidFinishLaunching:(id)unused { _assert(pkgInitConfig(*_config)); _assert(pkgInitSystem(*_config, _system)); confirm_ = nil; + restart_ = false; + _trace(); CGRect screenrect = [UIHardware fullScreenApplicationContentRect]; window_ = [[UIWindow alloc] initWithContentRect:screenrect]; @@ -2515,7 +3293,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString featured_ = [[UIView alloc] initWithFrame:[transition_ bounds]]; - CGSize navsize = [UINavigationBar defaultSize]; + CGSize navsize = [UINavigationBar defaultSizeWithPrompt]; CGRect navrect = {{0, 0}, navsize}; navbar_ = [[UINavigationBar alloc] initWithFrame:navrect]; @@ -2523,17 +3301,18 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString [navbar_ setBarStyle:1]; [navbar_ setDelegate:self]; + [navbar_ setPrompt:@"Welcome to Cydia Packager"]; [navbar_ showButtonsWithLeftTitle:@"About" rightTitle:@"Reload"]; - UINavigationItem *navitem = [[UINavigationItem alloc] initWithTitle:@"Featured"]; + UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:@"Featured"] autorelease]; [navbar_ pushNavigationItem:navitem]; struct CGRect subbounds = [featured_ bounds]; subbounds.origin.y += navsize.height; subbounds.size.height -= navsize.height; - UIImageView *pinstripe = [[UIImageView alloc] initWithFrame:subbounds]; + UIImageView *pinstripe = [[[UIImageView alloc] initWithFrame:subbounds] autorelease]; [pinstripe setImage:[UIImage applicationImageNamed:@"pinstripe.png"]]; [featured_ addSubview:pinstripe]; @@ -2556,7 +3335,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString [webview_ setDelegate:self]; CGSize indsize = [UIProgressIndicator defaultSizeForStyle:2]; - indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectMake(87, 15, indsize.width, indsize.height)]; + indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectMake(87, 45, indsize.width, indsize.height)]; [indicator_ setStyle:2]; [featured_ addSubview:indicator_]; @@ -2583,18 +3362,18 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString [NSDictionary dictionaryWithObjectsAndKeys: @"buttonBarItemTapped:", kUIButtonBarButtonAction, - @"upgrade-up.png", kUIButtonBarButtonInfo, - @"upgrade-dn.png", kUIButtonBarButtonSelectedInfo, + @"changes-up.png", kUIButtonBarButtonInfo, + @"changes-dn.png", kUIButtonBarButtonSelectedInfo, [NSNumber numberWithInt:3], kUIButtonBarButtonTag, self, kUIButtonBarButtonTarget, - @"Upgrade", kUIButtonBarButtonTitle, + @"Changes", kUIButtonBarButtonTitle, @"0", kUIButtonBarButtonType, nil], [NSDictionary dictionaryWithObjectsAndKeys: @"buttonBarItemTapped:", kUIButtonBarButtonAction, - @"uninstall-up.png", kUIButtonBarButtonInfo, - @"uninstall-dn.png", kUIButtonBarButtonSelectedInfo, + @"manage-up.png", kUIButtonBarButtonInfo, + @"manage-dn.png", kUIButtonBarButtonSelectedInfo, [NSNumber numberWithInt:4], kUIButtonBarButtonTag, self, kUIButtonBarButtonTarget, @"Uninstall", kUIButtonBarButtonTitle, @@ -2603,11 +3382,11 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString [NSDictionary dictionaryWithObjectsAndKeys: @"buttonBarItemTapped:", kUIButtonBarButtonAction, - @"sources-up.png", kUIButtonBarButtonInfo, - @"sources-dn.png", kUIButtonBarButtonSelectedInfo, + @"search-up.png", kUIButtonBarButtonInfo, + @"search-dn.png", kUIButtonBarButtonSelectedInfo, [NSNumber numberWithInt:5], kUIButtonBarButtonTag, self, kUIButtonBarButtonTarget, - @"Sources", kUIButtonBarButtonTitle, + @"Search", kUIButtonBarButtonTitle, @"0", kUIButtonBarButtonType, nil], nil]; @@ -2637,38 +3416,131 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString [buttonbar_ showSelectionForButton:1]; [transition_ transition:0 toView:featured_]; + _trace(); [overlay_ addSubview:buttonbar_]; + _trace(); + [UIKeyboard initImplementationNow]; + + _trace(); + CGRect edtrect = [overlay_ bounds]; + edtrect.origin.y += navsize.height; + edtrect.size.height -= navsize.height; + + _trace(); + CGSize keysize = [UIKeyboard defaultSize]; + CGRect keyrect = {{0, [overlay_ bounds].size.height - keysize.height}, keysize}; + keyboard_ = [[UIKeyboard alloc] initWithFrame:keyrect]; + + _trace(); database_ = [[Database alloc] init]; [database_ setDelegate:progress_]; - install_ = [[InstallView alloc] initWithFrame:[transition_ bounds] database:database_]; + _trace(); + install_ = [[InstallView alloc] initWithFrame:[transition_ bounds]]; [install_ setDelegate:self]; - upgrade_ = [[UpgradeView alloc] initWithFrame:[transition_ bounds] database:database_]; - [upgrade_ setDelegate:self]; + _trace(); + changes_ = [[ChangesView alloc] initWithFrame:[transition_ bounds]]; + [changes_ setDelegate:self]; - uninstall_ = [[UninstallView alloc] initWithFrame:[transition_ bounds] database:database_]; - [uninstall_ setDelegate:self]; + _trace(); + manage_ = [[ManageView alloc] initWithFrame:[transition_ bounds]]; + [manage_ setDelegate:self]; - sources_ = [[SourcesView alloc] initWithFrame:[transition_ bounds] database:database_]; - [sources_ setDelegate:self]; + _trace(); + search_ = [[SearchView alloc] initWithFrame:[transition_ bounds]]; + [search_ setDelegate:self]; + _trace(); [self reloadData:NO]; + _trace(); [progress_ resetView]; + _trace(); Package *package([database_ packageWithName:@"cydia"]); NSString *application = package == nil ? @"Cydia" : [NSString stringWithFormat:@"Cydia/%@", [package installed]]; WebView *webview = [webview_ webView]; [webview setApplicationNameForUserAgent:application]; + _trace(); url_ = [NSURL URLWithString:@"http://cydia.saurik.com/"]; [self loadNews]; + _trace(); +} + +- (void) showKeyboard:(BOOL)show { + if (show) + [overlay_ addSubview:keyboard_]; + else + [keyboard_ removeFromSuperview]; } @end +void AddPreferences(NSString *plist) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSMutableDictionary *settings = [[[NSMutableDictionary alloc] initWithContentsOfFile:plist] autorelease]; + _assert(settings != NULL); + NSMutableArray *items = [settings objectForKey:@"items"]; + + bool cydia(false); + + for (size_t i(0); i != [items count]; ++i) { + NSMutableDictionary *item([items objectAtIndex:i]); + NSString *label = [item objectForKey:@"label"]; + if (label != nil && [label isEqualToString:@"Cydia"]) { + cydia = true; + break; + } + } + + if (!cydia) { + for (size_t i(0); i != [items count]; ++i) { + NSDictionary *item([items objectAtIndex:i]); + NSString *label = [item objectForKey:@"label"]; + if (label != nil && [label isEqualToString:@"General"]) { + [items insertObject:[NSDictionary dictionaryWithObjectsAndKeys: + @"CydiaSettings", @"bundle", + @"PSLinkCell", @"cell", + [NSNumber numberWithBool:YES], @"hasIcon", + [NSNumber numberWithBool:YES], @"isController", + @"Cydia", @"label", + nil] atIndex:(i + 1)]; + + break; + } + } + + _assert([settings writeToFile:plist atomically:YES] == YES); + } + + [pool release]; +} + +/*IMP alloc_; +id Alloc_(id self, SEL selector) { + id object = alloc_(self, selector); + fprintf(stderr, "[%s]A-%p\n", self->isa->name, object); + return object; +}*/ + int main(int argc, char *argv[]) { + struct nlist nl[2]; + memset(nl, 0, sizeof(nl)); + nl[0].n_un.n_name = "_useMDNSResponder"; + nlist("/usr/lib/libc.dylib", nl); + if (nl[0].n_type != N_UNDF) + *(int *) nl[0].n_value = 0; + + setuid(0); + setgid(0); + + /*Method alloc = class_getClassMethod([NSObject class], @selector(alloc)); + alloc_ = alloc->method_imp; + alloc->method_imp = (IMP) &Alloc_;*/ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; size_t size; @@ -2687,6 +3559,15 @@ int main(int argc, char *argv[]) { IOObjectRelease(service); } + AddPreferences(@"/Applications/Preferences.app/Settings-iPhone.plist"); + AddPreferences(@"/Applications/Preferences.app/Settings-iPod.plist"); + + if ((Metadata_ = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/var/lib/cydia/metadata.plist"]) == NULL) + Metadata_ = [[NSMutableDictionary alloc] initWithCapacity:2]; + else + Packages_ = [Metadata_ objectForKey:@"Packages"]; + UIApplicationMain(argc, argv, [Cydia class]); [pool release]; + return 0; } diff --git a/Preferences.mm b/Preferences.mm new file mode 100644 index 00000000..b7ccfff0 --- /dev/null +++ b/Preferences.mm @@ -0,0 +1,438 @@ +/* Source {{{ */ +@interface Source : NSObject { + NSString *description_; + NSString *label_; + NSString *origin_; + + NSString *uri_; + NSString *distribution_; + NSString *type_; + + BOOL trusted_; +} + +- (void) dealloc; + +- (Source *) initWithMetaIndex:(metaIndex *)index; + +- (BOOL) trusted; + +- (NSString *) uri; +- (NSString *) distribution; +- (NSString *) type; + +- (NSString *) description; +- (NSString *) label; +- (NSString *) origin; +@end + +@implementation Source + +- (void) dealloc { + [uri_ release]; + [distribution_ release]; + [type_ release]; + + if (description_ != nil) + [description_ release]; + if (label_ != nil) + [label_ release]; + if (origin_ != nil) + [origin_ release]; + + [super dealloc]; +} + +- (Source *) initWithMetaIndex:(metaIndex *)index { + if ((self = [super init]) != nil) { + trusted_ = index->IsTrusted(); + + uri_ = [[NSString stringWithCString:index->GetURI().c_str()] retain]; + distribution_ = [[NSString stringWithCString:index->GetDist().c_str()] retain]; + type_ = [[NSString stringWithCString:index->GetType()] retain]; + + description_ = nil; + label_ = nil; + origin_ = nil; + + debReleaseIndex *dindex(dynamic_cast(index)); + if (dindex != NULL) { + std::ifstream release(dindex->MetaIndexFile("Release").c_str()); + std::string line; + while (std::getline(release, line)) { + std::string::size_type colon(line.find(':')); + if (colon == std::string::npos) + continue; + + std::string name(line.substr(0, colon)); + std::string value(line.substr(colon + 1)); + while (!value.empty() && value[0] == ' ') + value = value.substr(1); + + if (name == "Description") + description_ = [[NSString stringWithCString:value.c_str()] retain]; + else if (name == "Label") + label_ = [[NSString stringWithCString:value.c_str()] retain]; + else if (name == "Origin") + origin_ = [[NSString stringWithCString:value.c_str()] retain]; + } + } + } return self; +} + +- (BOOL) trusted { + return trusted_; +} + +- (NSString *) uri { + return uri_; +} + +- (NSString *) distribution { + return distribution_; +} + +- (NSString *) type { + return type_; +} + +- (NSString *) description { + return description_; +} + +- (NSString *) label { + return label_; +} + +- (NSString *) origin { + return origin_; +} + +@end +/* }}} */ +/* Source Cell {{{ */ +@interface SourceCell : UITableCell { + UITextLabel *description_; + UIRightTextLabel *label_; + UITextLabel *origin_; +} + +- (void) dealloc; + +- (SourceCell *) initWithSource:(Source *)source; + +- (void) _setSelected:(float)fraction; +- (void) setSelected:(BOOL)selected; +- (void) setSelected:(BOOL)selected withFade:(BOOL)fade; +- (void) _setSelectionFadeFraction:(float)fraction; + +@end + +@implementation SourceCell + +- (void) dealloc { + [description_ release]; + [label_ release]; + [origin_ release]; + [super dealloc]; +} + +- (SourceCell *) initWithSource:(Source *)source { + if ((self = [super init]) != nil) { + GSFontRef bold = GSFontCreateWithName("Helvetica", kGSFontTraitBold, 20); + GSFontRef small = GSFontCreateWithName("Helvetica", kGSFontTraitNone, 14); + + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + float clear[] = {0, 0, 0, 0}; + + NSString *description = [source description]; + if (description == nil) + description = [source uri]; + + description_ = [[UITextLabel alloc] initWithFrame:CGRectMake(12, 7, 270, 25)]; + [description_ setBackgroundColor:CGColorCreate(space, clear)]; + [description_ setFont:bold]; + [description_ setText:description]; + + NSString *label = [source label]; + if (label == nil) + label = [source type]; + + label_ = [[UIRightTextLabel alloc] initWithFrame:CGRectMake(290, 32, 90, 25)]; + [label_ setBackgroundColor:CGColorCreate(space, clear)]; + [label_ setFont:small]; + [label_ setText:label]; + + NSString *origin = [source origin]; + if (origin == nil) + origin = [source distribution]; + + origin_ = [[UITextLabel alloc] initWithFrame:CGRectMake(13, 35, 315, 20)]; + [origin_ setBackgroundColor:CGColorCreate(space, clear)]; + [origin_ setFont:small]; + [origin_ setText:origin]; + + [self addSubview:description_]; + [self addSubview:label_]; + [self addSubview:origin_]; + + CFRelease(small); + CFRelease(bold); + } return self; +} + +- (void) _setSelected:(float)fraction { + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + + float black[] = { + interpolate(0.0, 1.0, fraction), + interpolate(0.0, 1.0, fraction), + interpolate(0.0, 1.0, fraction), + 1.0}; + + float blue[] = { + interpolate(0.2, 1.0, fraction), + interpolate(0.2, 1.0, fraction), + interpolate(1.0, 1.0, fraction), + 1.0}; + + float gray[] = { + interpolate(0.4, 1.0, fraction), + interpolate(0.4, 1.0, fraction), + interpolate(0.4, 1.0, fraction), + 1.0}; + + [description_ setColor:CGColorCreate(space, black)]; + [label_ setColor:CGColorCreate(space, blue)]; + [origin_ setColor:CGColorCreate(space, gray)]; +} + +- (void) setSelected:(BOOL)selected { + [self _setSelected:(selected ? 1.0 : 0.0)]; + [super setSelected:selected]; +} + +- (void) setSelected:(BOOL)selected withFade:(BOOL)fade { + if (!fade) + [self _setSelected:(selected ? 1.0 : 0.0)]; + [super setSelected:selected withFade:fade]; +} + +- (void) _setSelectionFadeFraction:(float)fraction { + [self _setSelected:fraction]; + [super _setSelectionFadeFraction:fraction]; +} + +@end +/* }}} */ + +/* Sources View {{{ */ +@interface SourcesView : UIView { + UISectionList *list_; + Database *database_; + id delegate_; + NSMutableArray *sources_; + UIAlertSheet *alert_; +} + +- (int) numberOfSectionsInSectionList:(UISectionList *)list; +- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section; +- (int) sectionList:(UISectionList *)list rowForSection:(int)section; + +- (int) numberOfRowsInTable:(UITable *)table; +- (float) table:(UITable *)table heightForRow:(int)row; +- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col; +- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row; +- (void) tableRowSelected:(NSNotification*)notification; + +- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button; + +- (void) dealloc; +- (id) initWithFrame:(CGRect)frame database:(Database *)database; +- (void) setDelegate:(id)delegate; +- (void) reloadData; +- (NSString *) leftTitle; +- (NSString *) rightTitle; +@end + +@implementation SourcesView + +- (int) numberOfSectionsInSectionList:(UISectionList *)list { + return 1; +} + +- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section { + return @"sources"; +} + +- (int) sectionList:(UISectionList *)list rowForSection:(int)section { + return 0; +} + +- (int) numberOfRowsInTable:(UITable *)table { + return [sources_ count]; +} + +- (float) table:(UITable *)table heightForRow:(int)row { + return 64; +} + +- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col { + return [[[SourceCell alloc] initWithSource:[sources_ objectAtIndex:row]] autorelease]; +} + +- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row { + return NO; +} + +- (void) tableRowSelected:(NSNotification*)notification { + UITable *table([list_ table]); + int row([table selectedRow]); + if (row == INT_MAX) + return; + + [table selectRow:-1 byExtendingSelection:NO withFade:YES]; +} + +- (void) alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button { + [alert_ dismiss]; + [alert_ release]; + alert_ = nil; +} + +- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button { + switch (button) { + case 0: + alert_ = [[UIAlertSheet alloc] + initWithTitle:@"Unimplemented" + buttons:[NSArray arrayWithObjects:@"Okay", nil] + defaultButtonIndex:0 + delegate:self + context:self + ]; + + [alert_ setBodyText:@"This feature will be implemented soon. In the mean time, you may add sources by adding .list files to '/etc/apt/sources.list.d'. If you'd like to be in the default list, please contact the author of Packager."]; + [alert_ popupAlertAnimated:YES]; + break; + + case 1: + [delegate_ update]; + break; + } +} + +- (void) dealloc { + if (sources_ != nil) + [sources_ release]; + [list_ release]; + [super dealloc]; +} + +- (id) initWithFrame:(CGRect)frame database:(Database *)database { + if ((self = [super initWithFrame:frame]) != nil) { + database_ = database; + sources_ = nil; + + CGSize navsize = [UINavigationBar defaultSize]; + CGRect navrect = {{0, 0}, navsize}; + CGRect bounds = [self bounds]; + + navbar_ = [[UINavigationBar alloc] initWithFrame:navrect]; + [self addSubview:navbar_]; + + [navbar_ setBarStyle:1]; + [navbar_ setDelegate:self]; + + UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:@"Sources"] autorelease]; + [navbar_ pushNavigationItem:navitem]; + + list_ = [[UISectionList alloc] initWithFrame:CGRectMake( + 0, navsize.height, bounds.size.width, bounds.size.height - navsize.height + )]; + + [self addSubview:list_]; + + [list_ setDataSource:self]; + [list_ setShouldHideHeaderInShortLists:NO]; + + UITableColumn *column = [[UITableColumn alloc] + initWithTitle:@"Name" + identifier:@"name" + width:frame.size.width + ]; + + UITable *table = [list_ table]; + [table setSeparatorStyle:1]; + [table addTableColumn:column]; + [table setDelegate:self]; + } return self; +} + +- (void) setDelegate:(id)delegate { + delegate_ = delegate; +} + +- (void) reloadData { + pkgSourceList list; + _assert(list.ReadMainList()); + + if (sources_ != nil) + [sources_ release]; + + sources_ = [[NSMutableArray arrayWithCapacity:16] retain]; + for (pkgSourceList::const_iterator source = list.begin(); source != list.end(); ++source) + [sources_ addObject:[[[Source alloc] initWithMetaIndex:*source] autorelease]]; + + [list_ reloadData]; +} + +- (NSString *) leftTitle { + return @"Refresh All"; +} + +- (NSString *) rightTitle { + return @"Edit"; +} + +@end +/* }}} */ +/* Settings View {{{ */ +@interface SettingsView : ResetView { +} + +- (void) dealloc; +- (void) reloadData; +@end + +@implementation SettingsView + +- (void) dealloc { + [super dealloc]; +} + +- (id) initWithFrame:(CGRect)frame database:(Database *)database { + if ((self = [super initWithFrame:frame]) != nil) { + database_ = database; + sources_ = nil; + + CGSize navsize = [UINavigationBar defaultSize]; + CGRect navrect = {{0, 0}, navsize}; + CGRect bounds = [self bounds]; + + navbar_ = [[UINavigationBar alloc] initWithFrame:navrect]; + [self addSubview:navbar_]; + + [navbar_ setBarStyle:1]; + [navbar_ setDelegate:self]; + + UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:@"Settings"] autorelease]; + [navbar_ pushNavigationItem:navitem]; + } return self; +} + +- (void) reloadData { + [self resetView]; +} + +@end +/* }}} */ diff --git a/data/changes-dn.png b/data/changes-dn.png new file mode 100644 index 0000000000000000000000000000000000000000..cfd31f17af667ee3991a4fd0b12131b09a9e1de6 GIT binary patch literal 2211 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 SL*@Gb0000Pq literal 0 HcmV?d00001 diff --git a/data/folder.png b/data/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..b628e9532a5e6dc8c50edcd2844b167cc1cab0fb GIT binary patch literal 1998 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 IM6N<$f)gFfRsaA1 literal 4440 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 ePnyT8004R=004l4008tQ004y1003=Y008K0 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 delta 845 zcmV-T1G4=75RnHViBL{Q4GJ0x0000DNk~Le0000V0000d2nGNE0QZNJ`H?YGe**_e zL_t(|ob6T#Pg+3`-uaWI)>2}WDuQ@$ON_A;D2EqOfub=OkI+9k-JPA;x8-=rYf9QQ zY&_n~eEWU7`(|bzsJ}R1CH}97U;jb={Yc_JCKE*M20*1BEI|J;&-6eJjX+QcNT!Vi zj3#CI3{;d6zojgk3&n@SA!X6_=WVd|cLWvaBTu7* zBnMLfC!yjj^_FaM$M}VFq4@3XEz#|!(-gbAyF=u}B1TR^u*&uvs2cc0f22x$X0D9% zZaQ4N*XtP`wZEUG*tND5a!G;I9Jrlk8RzCYQeH4mF+LwVSN!7qf|QPqj=eB<*)9Cm6 z;o@7#Evoa;{Rpg`f3zEgsSgL!dh{ypM004R=004l4008tQ004y1003=Y008K0 z002CT000|UgXaf$00009a7bBm000XU000XU0RWnu7ytkT{YgYYRPC2pa1~`1$A8_q z>D(Pp3509}lCZe|BBDVl3JP(eq9R4P0%f_4^WxOFqE$ZVC|W9tl*KS?!=!|i zOv)KRK{TKugxnCa5JE7Z6$s(xZt2|HpAY%kYH~x2m4}(CNxyv6-RFPK@0?!F`4s-g z4rK&r1ZV{4zX^21)S?*+=1yO9ddTrMetx zAW-7^y!!*8fZM6LL^Mh?tfR)O)Ha?z`rI4a{wqXO%+Afv$;e)QSJ_>2XO`S*^yq+ss@d56bTJf0Ny&tcv!0?ka^EFN06a9f~fFszr#fK0$3-6yTovH&=)LaE%1nSoR2SwnT z@W^e_-g@79xF91^J?Z^jyl8IafE3y?IYqS&H| z1qwC3?*)OQ;PJ~0uW)#PEZfHKA_w`4%vC|hx_~9yQ!Opt%(8BPrc~{&~ly~ zvhz^QQZImG(xzc+Vu4y#dyPmZ_JX&D>^xkztgosQHx6491Ju*F!b>znV(H(fl+Db@ zpLbKn>>DPh&2ZRlj-uS82_2o`jx*;1ZD-B}+BWTMsrdJS_WG_POAiAiPuVo>_F1Eg zmfkV0JnI@)cGfkn?6g#STK&mO$3x*r=+M!Q>d(JAyMNC&XPdf@J#Yjdq3CbFQK6>g zUJ>*Y5D7t-W;jt)6g3W16@M@ia64@-32WGfH(LIN!GU^P9`uTkA74cCS5&oDZU5}lj-NV0UEYR^&GK(6CQV2! zC>)oR{p@2UE2reyi-o_d@2}@h=16nc`{qZrYa3cFwbdN&Y?LvK;=+`?^fX8E-3xOT zm(L$N-(%C=io;F(xK9QL>TX#6!|Jz>RJ^_3yH(eXPT2y?QNlWY^a8e(%Ziq)d3pK@ zfTvzO_~F`(_1mN-zTfy^jx07!Q&XRQxcJ`ZpPo{lSh!*8U_hC9V?DE)TYUi;&@Y|z zA3w|y!w$Hgd9-9Pz+3BTcSudx98CroKQ!6MUsd~S;>^iZ z>XEi#^VLuW5X4DGhGoFI(vHQP0yTL}w@iwI>C6vAl`(!zSQ%s&B8IrHQ!HPfIm8WB h8xw0}D?I`<0`z|e^b3SA{+0j$002ovPDHLkV1h>p9-aUI literal 0 HcmV?d00001 diff --git a/data/manage-up.png b/data/manage-up.png new file mode 100644 index 0000000000000000000000000000000000000000..9513a7cfaf9f6c8c514e034ef1bf7fb8bdb7c9fd GIT binary patch literal 1326 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 B5i|e* literal 0 HcmV?d00001 diff --git a/data/search-up.png b/data/search-up.png new file mode 100644 index 0000000000000000000000000000000000000000..4083f1b829147883f48b0fc1c651f470d05755f9 GIT binary patch literal 1333 zcmV-51004R=004l4008tQ004y1003=Y008K0 z002CT000|UgXaf$00009a7bBm000XU000XU0RWnu7ytkSn@L1LRPEROPg7SI2k__C zCA2WH_qGkUGo@hZ%e=sdv=AjGyp%wgwa{eBL>DHw-{fV>wnQ{;OibM3)?v0RLmV0E zs7ng9PRDX*$;f2dmoPwW3#Hy(p(Sg}i-K8Jd-ucqWl^NvbT0|#4|qN~Po8tm_aN{u zEo2q`FTrU5AP0bS01yH|G5{<9fFA(70N?_ETTc?^1HfjvT&~N`&Q>TC3Sp{9#6vM& z(rh@i=(|maoHw2Rk&zLn%jGfxzz_fg|1O-0<9Ll)t=1J57Cz7Aa+8)TFgG{1I5049 zlO#!FAP_hM0KUHp^Cc2VQ&m+}bzWYcWPW~r)@HLAM@L6VkH=#T27?rz&lhJ%B&w{e zY_(FEt6Q7A4z*gZd3t+$dpsVGK9-$71gE5=?5o+m`)yUOQZjaZ^vC{hzx%*ux0#kJ zpj75+%gV~$B#6ucyWQ?NefspVnVFf6c;U32J9nPhrqOJfQ>R{WI2@)}hh@{IP1;xY z?){R9-hZaO{Y1;e#DqR(SfkOLudJ-p3=R(Vb#--B#d}0*YioPAZrxhh*Vkt@o6Xx} zh8r6j$2V>yGEScCc+G0HcEk&-RH}XT_4_`*aeZv!Z~%kT=~`6vnPg! zha2LBb8>RpcfGu8zsv2ib#`{DVup*0ijS9+mb`Aa*?%?~je7uKG!8dQr_*)k<|6deczW)>C}?j$ECuf=gZMJyJJ zGNjVawP;o)MpYzJtr}dk01cR05Avw2r$4f z2mk`Gw73*>yIp6~($cqa(RCYu0YDIdTi#og`ND-|@fD*Fj^mp}B9U4s6mG#VEHf!7 zNw~PQ004R=004l4008tQ004y1003=Y008K0 z002CT000|UgXaf$00009a7bBm000XU000XU0RWnu7ytkRT}ebiRPEJGOdM4J!14dg ztlgb%P-@GzHEk$RzoI8?szcdI42dbVElb@7v>=#x*&r$h4{B^{OnNYqh%Fv8TFa`W z)b3JRxez0;vKY9jsj&hjR>Kxczt{{5Gw*qD*6bR*7)dZ_-dzD#yU<3lI@DMo1s?P%^e+}NqyOnZfy zLG^hNx3A8$9_fliW8ZuX$gDjWs%Y*w)Yz%sSn%2z8%_5j?pU2^J@io|8XL;9&+Idr z-+le*PUY&n*D{yT^i^=DKl9>SpG7`BoBYcvIZ;~ghb{e;_uo||%tb&2R8*gbuPNZZ*2MMK?MY;LUX&9lGtJ~3;3KcPz@kfut7)^-N)dhOzv$NtO2e&zOeVbo$;NUF|1o@6bVvDRKK60UrB~u8qTw^ zcY^VXjTqLAc@PpQC7uF5iIkeGeI=2#i`f_*2sG|5iyS{wa(Ek0#|`4U?O+5D`3sMX9m~=YzEc=ULXVf4*UXK&V3G6z%J&!O~4vw zC51VYt~=$|a^H{@uy)>e#({q4*gSv*m_XXOft?foUt6jC?Q5($lQrjBIM8;kZ_-{0{d^Ll!?1OaJfAXevK1Cp&CAN_$8OS+@4 zBLl;Y*I#8sRss16$sR$z3=CDO3=9p;3=Cg@bjw8shTM1th8H;u3^v^i4AK$J&)F-0 zYB>u$B8wRqxP?KOkzv*x2?hp6Ax{^_khfP1vs(;A4ty+^&{%r^fUaxH1BDj_&eNsm z$S!Rb+4WWU%Epo{^B-($eiR@xS2pkE1;>MqOiE{;nQJVaa3PO<8zwWi& ztYtT4D^EUJ+s4`Sz_2gbjl=+WETY7OpY%`owuFMHf_{{Q=hGWOkzb{GPM zbWX={C|;}a@LKuFX1^IwK*e*O#?lfyl@@{2$a}N%OnaVJ&erI9r*qn>rZvdxYU$p$ zzw7dM{oQ)2XJhZ{UE$kuH)qcJVZgI~&gZ`yV`Fz6m6m|GxLM+e0-ND~-p|aL*@3%& PLB`gTe~DWM4f>^Y{u diff --git a/data/uninstall-dn.png b/data/uninstall-dn.png deleted file mode 100644 index 0533f2c4cdeff315451a43c3de79a1bf88a642c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 971 zcmV;+12p`JP)hxJ&jx+}~Kfa|(}EC^@~4`&Mw$a%m?()P1}!g*4-Q#97%4B;Qi`frWhYhB zEhh^D4$#WlK%G);2sj)oKY!VgIOX?>so;V4DDS1>Aen&nCQ@cE zt2|&9zMwBBgp#s}Im`sF<1ir0DzF9a7$C<`j7PNx-p94D1k+1t59X$tb6*5vEe9#C z+~|wt6)ApUT!)+zgF=g=t9|>Dn=Ek~WwqG*R3IlhfRm*L%rRvOVmWI5WP9aVu=D_B z2jxBAUqRVM+2DKI9=<+!`fac52Ckv0;;wx~OWzGDaedV;9S0OR*2IoB9)QyDP zE$KVa$?XzE8TkDp)|X66v6fiE3Sop{! zJAl8~y6_+_4)7N&Z)3Y*78k*3B%%Clvm9{VRP)aqZF#OZU9zCkrbHCz;P$pPZGBFZ|-R$MZpL*hPrsmI^zlob~kVwmeBGt zDw!f$uNn_=aj1AajzL(8%}lmW;Xqh27j0oFRx|&xL`Ug#jw=^A$5V)M#H`xD8xHij thf{zXwzzVqY@^TrrJjSIE%4t03;;==*-{{6qD24z002ovPDHLkV1mBCz)%1H diff --git a/data/uninstall-up.png b/data/uninstall-up.png deleted file mode 100644 index 3c51c698eedfcd53585d57e263d0b8da171647a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 677 zcmV;W0$TlvP)ZFbvdo`o{KBzCh^%IPrp>3m>3QbR0F7Wm`@HH4KzPlVJwiFkLiCQd4i8$S`{sb+8>s(E{cTPbQriqpc861m-bc6-@){PNp z?f{#ByUt~Qr<@BM$oSt7K(>t%Ij8Et8VedCA94&vO}o;FEWNmJk&(VZiZJQ}psW>c zro9rNJK-8DX;8^FPhYndnX%7pf4AFRZ1C-Nn-g$3xN|1D%OB^ud^j}-uJ(D@Oji39 zrM43Rk50aGIkyzaW;B1B&Bxjt#!_fFdwyC6l7NHg+I;R^C6r(_E3B;DIlWCJnFPi+IWj(vwCMu&;t(obx>%UDt?O*+0dWKP{%c>& z@>cx~-laNd)kUMv=hBexacMCVBJ^$sb%bRmEA@m;e+BP*UmXh2oy`B!DHqIA@T!V` z8F-+b$Ln*sRH)gAOreZ)zeMYcZC=%jnBP|OqP~09`@f?HK)}5D* z5zzppOY0du6P&zU49WGF>c%v$XEYC`Zk+Iqp3U5k(y(f;zo~StXG=11P3KtkLoYFp zluDJPLTjbxNK*?Rzg%RKz-hqp`#cZHdk?uE{N<&Er%XN*2Y8Od(uxDsS`WgWjB_5v z1xTP$#txJ=+>sjK!=VVVJBS^iu+@FE?BN2vh(RYL!Ilvx#93htq&n7U6+YT**VeXo2l$lRJ++xKj(@a&~Tw z!Dai*LJ_5mujQ!cgY0+^G~2*(Z0c6i4eM$k#KijSi%X;k=sN41lL}_4fZr(8b`+0!j5-7Xmo+w0=VF_!S}IBfA{)Hr;iNBf+GnY zcd>waZy=)&r-9Qr&7=8=Ek8=Z?fQ`24ZuVUo*EQ!AqV!Rfz!C2m$9RDW`YlUm@w~y z3u3lAgZVBfF<^Hj=4_}c>75RV)Liv!^3F*6Kw3S35eS#CDq66#OFj(j1gOR$#2yPFO18TMUadH52o4!A3pl~n0E z)4-KE@V(4!L~zk(!0?z5ah*T#t8%qh#ZPD#KRP(>Jq)oRbTENf50xG*Wa`U&Sm*w@{Qz^g{9F(RGmM=LmD+Q?ja=p&G$ zTs?nQ{O4C^Za#MjQh|o8**jXAtpW}Tpa?;i6{L#EB4te`w&w0n#Q6L1aXPtu6lnFVecz)7L} zH}`l-Q5P|TS>Q1+f)z_osWfR$A+AO>inYmz~`eQj1UgNMy5!$j0tKK2cW@Zm{_~n7NWvBY96hv(KPTe%l)~Jit2^X z);(DpjIq&>d2k7P~V6*6OH zk&&2NH;TK(VVrfwJ8K-~oVD7w-l|t{*yF9Ot$pwOvBN=St?c1+omU;%!&~3Na+|x| z15DB@=9?6T(pnk%jbDWx(z^n44>7SQ`K+$y7!C;>x3Jubg$e1@Ivl{mQn6mGK--E3 zoE`Lf+e6XoZEp|ZnL(J3YogaeyN%^=9WiBYFR4Zq_4ouPouIc{e|`*Qp~ zv{lU0iYA~G>r1#m%?e}#GMEGo&<(J5B`q@4if}k(O3mOd zt}g4y-Lq+y!`ib-nxD4u!jzdym$e(bd-M@>byI zc+;ic!G23-!bOohFqbP#E{if<99%93uJ6yIM@5-{JnF_MN6VQ<{aG=e|GP8?pM%fA f=iry|{SjaQFisa->name, sel_getName(selector)); + return [super methodSignatureForSelector:selector]; +} + +- (BOOL) respondsToSelector:(SEL)selector { + fprintf(stderr, "[%s]R-%s\n", self->isa->name, sel_getName(selector)); + return [super respondsToSelector:selector]; +} diff --git a/makefile b/makefile index b0ea3150..ad0db8a7 100644 --- a/makefile +++ b/makefile @@ -6,5 +6,5 @@ test: all scp -p Cydia saurik@$(iPhone):/dat ssh saurik@$(iPhone) /dat/Cydia -Cydia: *.mm makefile +Cydia: *.mm *.h makefile arm-apple-darwin-g++ -fobjc-call-cxx-cdtors -g3 -O2 -Wall -Werror -o $@ $< -framework UIKit -framework IOKit -framework Foundation -framework CoreFoundation -framework CoreGraphics -framework GraphicsServices -lobjc -lapt-pkg -lpcre -fobjc-exceptions -- 2.45.2