From b634a50283eb4cdc16827e41b434c4393c9d815e Mon Sep 17 00:00:00 2001 From: Yann Diorcet Date: Fri, 6 Jul 2012 17:28:38 +0200 Subject: [PATCH] Add NinePath lib Continue chat stuff Fix viewWillAppear without viewDidLoad called with UICompositeController --- Classes/ChatRoomTableViewController.h | 9 +- Classes/ChatRoomTableViewController.m | 61 +- Classes/ChatRoomViewController.h | 5 +- Classes/ChatRoomViewController.m | 57 +- Classes/ChatRoomViewController.xib | 66 ++- Classes/ChatTableViewController.h | 6 +- Classes/ChatTableViewController.m | 49 +- Classes/ChatViewController.h | 4 + Classes/ChatViewController.m | 50 +- Classes/ChatViewController.xib | 44 +- Classes/ContactsViewController.m | 5 +- Classes/DialerViewController.m | 3 +- Classes/FirstLoginViewController.m | 6 +- Classes/HistoryTableViewController.m | 1 + Classes/HistoryViewController.m | 6 +- Classes/InCallTableViewController.m | 53 +- Classes/InCallViewController.m | 8 +- Classes/IncomingCallViewController.m | 1 - Classes/LinphoneManager.m | 25 +- Classes/LinphoneUI/UICallBar.m | 1 - Classes/LinphoneUI/UICallBar.xib | 13 +- Classes/LinphoneUI/UICallCell.h | 2 +- Classes/LinphoneUI/UIChatCell.h | 21 +- Classes/LinphoneUI/UIChatCell.m | 49 +- Classes/LinphoneUI/UIChatCell.xib | 117 +++- Classes/LinphoneUI/UIChatRoomCell.h | 49 ++ Classes/LinphoneUI/UIChatRoomCell.m | 125 ++++ Classes/LinphoneUI/UIChatRoomCell.xib | 389 +++++++++++++ Classes/LinphoneUI/UIChatRoomHeader.h | 39 ++ Classes/LinphoneUI/UIChatRoomHeader.m | 56 ++ Classes/LinphoneUI/UIChatRoomHeader.xib | 244 ++++++++ .../LinphoneUI/UICompositeViewController.m | 1 + Classes/LinphoneUI/UIConferenceHeader.m | 2 +- Classes/LinphoneUI/UIStateBar.m | 4 +- Classes/Model/ChatModel.h | 3 +- Classes/Model/ChatModel.m | 26 +- .../{ => CPAnimation}/CPAnimationSequence.h | 0 .../{ => CPAnimation}/CPAnimationSequence.m | 0 .../Utils/{ => CPAnimation}/CPAnimationStep.h | 0 .../Utils/{ => CPAnimation}/CPAnimationStep.m | 0 Classes/WizardViewController.xib | 154 ++++- NinePatch/NinePatch.h | 15 + NinePatch/NinePatch.xcodeproj/project.pbxproj | 461 +++++++++++++++ NinePatch/NinePatch_Prefix.pch | 157 +++++ NinePatch/TUCachingNinePatch.h | 51 ++ NinePatch/TUCachingNinePatch.m | 125 ++++ NinePatch/TUDebugLoggingAssistant.h | 56 ++ NinePatch/TUDebugLoggingAssistant.m | 362 ++++++++++++ NinePatch/TUFullNinePatch.h | 56 ++ NinePatch/TUFullNinePatch.m | 318 +++++++++++ NinePatch/TUHorizontalNinePatch.h | 37 ++ NinePatch/TUHorizontalNinePatch.m | 124 ++++ NinePatch/TUNinePatch.h | 125 ++++ NinePatch/TUNinePatch.m | 514 +++++++++++++++++ NinePatch/TUNinePatchCache.h | 99 ++++ NinePatch/TUNinePatchCache.m | 133 +++++ NinePatch/TUNinePatchCachingCategories.h | 28 + NinePatch/TUNinePatchCachingCategories.m | 52 ++ NinePatch/TUNinePatchProtocols.h | 121 ++++ NinePatch/TUVerticalNinePatch.h | 36 ++ NinePatch/TUVerticalNinePatch.m | 132 +++++ NinePatch/UIImage-TUNinePatch.h | 77 +++ NinePatch/UIImage-TUNinePatch.m | 539 ++++++++++++++++++ Resources/20-gear2.png | Bin 375 -> 0 bytes Resources/avatar_shadow_small.png | Bin 0 -> 2522 bytes Resources/chat_bubble_incoming.9.png | Bin 0 -> 955 bytes Resources/chat_bubble_incoming.png | Bin 0 -> 2538 bytes Resources/chat_bubble_outgoing.9.png | Bin 0 -> 913 bytes Resources/chat_bubble_outgoing.png | Bin 0 -> 2485 bytes Resources/setup_back_disabled.png | Bin 0 -> 5050 bytes Resources/setup_cancel_disabled.png | Bin 0 -> 5924 bytes Resources/setup_start_disabled.png | Bin 0 -> 5620 bytes linphone.xcodeproj/project.pbxproj | 212 ++++++- 73 files changed, 5405 insertions(+), 179 deletions(-) create mode 100644 Classes/LinphoneUI/UIChatRoomCell.h create mode 100644 Classes/LinphoneUI/UIChatRoomCell.m create mode 100644 Classes/LinphoneUI/UIChatRoomCell.xib create mode 100644 Classes/LinphoneUI/UIChatRoomHeader.h create mode 100644 Classes/LinphoneUI/UIChatRoomHeader.m create mode 100644 Classes/LinphoneUI/UIChatRoomHeader.xib rename Classes/Utils/{ => CPAnimation}/CPAnimationSequence.h (100%) rename Classes/Utils/{ => CPAnimation}/CPAnimationSequence.m (100%) rename Classes/Utils/{ => CPAnimation}/CPAnimationStep.h (100%) rename Classes/Utils/{ => CPAnimation}/CPAnimationStep.m (100%) create mode 100755 NinePatch/NinePatch.h create mode 100755 NinePatch/NinePatch.xcodeproj/project.pbxproj create mode 100755 NinePatch/NinePatch_Prefix.pch create mode 100755 NinePatch/TUCachingNinePatch.h create mode 100755 NinePatch/TUCachingNinePatch.m create mode 100755 NinePatch/TUDebugLoggingAssistant.h create mode 100755 NinePatch/TUDebugLoggingAssistant.m create mode 100755 NinePatch/TUFullNinePatch.h create mode 100755 NinePatch/TUFullNinePatch.m create mode 100755 NinePatch/TUHorizontalNinePatch.h create mode 100755 NinePatch/TUHorizontalNinePatch.m create mode 100755 NinePatch/TUNinePatch.h create mode 100755 NinePatch/TUNinePatch.m create mode 100755 NinePatch/TUNinePatchCache.h create mode 100755 NinePatch/TUNinePatchCache.m create mode 100755 NinePatch/TUNinePatchCachingCategories.h create mode 100755 NinePatch/TUNinePatchCachingCategories.m create mode 100755 NinePatch/TUNinePatchProtocols.h create mode 100755 NinePatch/TUVerticalNinePatch.h create mode 100755 NinePatch/TUVerticalNinePatch.m create mode 100755 NinePatch/UIImage-TUNinePatch.h create mode 100755 NinePatch/UIImage-TUNinePatch.m delete mode 100644 Resources/20-gear2.png create mode 100644 Resources/avatar_shadow_small.png create mode 100644 Resources/chat_bubble_incoming.9.png create mode 100644 Resources/chat_bubble_incoming.png create mode 100644 Resources/chat_bubble_outgoing.9.png create mode 100644 Resources/chat_bubble_outgoing.png create mode 100644 Resources/setup_back_disabled.png create mode 100644 Resources/setup_cancel_disabled.png create mode 100644 Resources/setup_start_disabled.png diff --git a/Classes/ChatRoomTableViewController.h b/Classes/ChatRoomTableViewController.h index 16aba4943..5256ce087 100644 --- a/Classes/ChatRoomTableViewController.h +++ b/Classes/ChatRoomTableViewController.h @@ -21,9 +21,16 @@ #import @interface ChatRoomTableViewController : UITableViewController { +@private + BOOL editMode; NSArray *data; + NSString *remoteContact; } -@property (nonatomic, retain) NSArray *data; +@property (nonatomic, retain) NSString *remoteContact; + +- (void) toggleEditMode; +- (void) enterEditMode; +- (void) exitEditMode; @end diff --git a/Classes/ChatRoomTableViewController.m b/Classes/ChatRoomTableViewController.m index 1600d97c5..fa2250948 100644 --- a/Classes/ChatRoomTableViewController.m +++ b/Classes/ChatRoomTableViewController.m @@ -18,22 +18,37 @@ */ #import "ChatRoomTableViewController.h" -#import "UIChatCell.h" +#import "UIChatRoomCell.h" +#import "UIChatRoomHeader.h" @implementation ChatRoomTableViewController -@synthesize data; - +@synthesize remoteContact; #pragma mark - -- (void)setData:(NSArray *)adata { - if(self->data != nil) - [self->data release]; - self->data = [adata retain]; - [[self tableView] reloadData]; +- (void)reloadData { + if(data != nil) + [data release]; + data = [[ChatModel listMessages:remoteContact] retain]; } +- (void) toggleEditMode { + editMode = !editMode; + [(UITableView*)[self view] reloadData]; +} + +- (void) enterEditMode { + if(!editMode) { + [self toggleEditMode]; + } +} + +- (void) exitEditMode { + if(editMode) { + [self toggleEditMode]; + } +} #pragma mark - UITableViewDataSource Functions @@ -42,19 +57,45 @@ } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + [self reloadData]; return [data count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - UIChatCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UIChatCell"]; + UIChatRoomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UIChatRoomCell"]; if (cell == nil) { - cell = [[UIChatCell alloc] init]; + cell = [[UIChatRoomCell alloc] initWithIdentifier:@"UIChatRoomCell"]; } [cell setChat:[data objectAtIndex:[indexPath row]]]; + if(editMode) + [cell enterEditMode]; + else + [cell exitEditMode]; [cell update]; return cell; } + +#pragma mark - UITableViewelegate Functions + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + UIChatRoomHeader *headerController = [[UIChatRoomHeader alloc] init]; + UIView *headerView = [headerController view]; + [headerController setContact:remoteContact]; + [headerController update]; + [headerController release]; + return headerView; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return [UIChatRoomHeader height]; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + ChatModel *chat = [data objectAtIndex:[indexPath row]]; + return [UIChatRoomCell height:chat]; +} + @end diff --git a/Classes/ChatRoomViewController.h b/Classes/ChatRoomViewController.h index 7890a147c..21c874e9d 100644 --- a/Classes/ChatRoomViewController.h +++ b/Classes/ChatRoomViewController.h @@ -19,6 +19,7 @@ #import +#import "UIToggleButton.h" #import "UICompositeViewController.h" #import "ChatRoomTableViewController.h" #import "ChatModel.h" @@ -27,12 +28,14 @@ ChatRoomTableViewController *tableController; UITextField *messageField; UIButton *sendButton; + NSString *remoteContact; + UIToggleButton *editButton; } - (void) setRemoteContact:(NSString*)remoteContact; @property (nonatomic, retain) IBOutlet ChatRoomTableViewController* tableController; - +@property (nonatomic, retain) IBOutlet UIToggleButton *editButton; @property (nonatomic, retain) IBOutlet UITextField* messageField; @property (nonatomic, retain) IBOutlet UIButton* sendButton; diff --git a/Classes/ChatRoomViewController.m b/Classes/ChatRoomViewController.m index 5f517b280..d51977911 100644 --- a/Classes/ChatRoomViewController.m +++ b/Classes/ChatRoomViewController.m @@ -20,13 +20,15 @@ #import "ChatRoomViewController.h" #import "PhoneMainView.h" +#import + @implementation ChatRoomViewController @synthesize tableController; @synthesize sendButton; @synthesize messageField; - +@synthesize editButton; #pragma mark - Lifecycle Functions @@ -36,7 +38,9 @@ - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; - + [tableController release]; + [messageField release]; + [sendButton release]; [super dealloc]; } @@ -56,8 +60,16 @@ #pragma mark - ViewController Functions + +- (void)viewDidLoad { + // Set selected+over background: IB lack ! + [editButton setImage:[UIImage imageNamed:@"chat_ok_over.png"] + forState:(UIControlStateHighlighted | UIControlStateSelected)]; +} + + - (void)viewWillAppear:(BOOL)animated { - [super viewDidAppear:animated]; + [super viewWillAppear:animated]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification @@ -66,10 +78,13 @@ selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(textReceivedEvent:) + name:@"LinphoneTextReceived" + object:nil]; + + [tableController exitEditMode]; + [editButton setOff]; [[tableController tableView] reloadData]; } @@ -81,13 +96,35 @@ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:@"LinphoneTextReceived" + object:nil]; +} + +-(void)didReceiveMemoryWarning { + [TUNinePatchCache flushCache]; // will remove any images cache (freeing any cached but unused images) } #pragma mark - -- (void)setRemoteContact:(NSString*)remoteContact { - [tableController setData:[ChatModel listMessages:remoteContact]]; +- (void)setRemoteContact:(NSString*)aRemoteContact { + remoteContact = aRemoteContact; + [tableController setRemoteContact: remoteContact]; +} + + +#pragma mark - Event Functions + +- (void)textReceivedEvent:(NSNotification *)notif { + //LinphoneChatRoom *room = [[[notif userInfo] objectForKey:@"room"] pointerValue]; + LinphoneAddress *from = [[[notif userInfo] objectForKey:@"from"] pointerValue]; + //NSString *message = [[notif userInfo] objectForKey:@"message"]; + if([[NSString stringWithUTF8String:linphone_address_get_username(from)] + caseInsensitiveCompare:remoteContact] == NSOrderedSame) { + [[tableController tableView] reloadData]; + } } @@ -142,7 +179,7 @@ } - (IBAction)onEditClick:(id)event { - + [tableController toggleEditMode]; } - (IBAction)onSendClick:(id)event { diff --git a/Classes/ChatRoomViewController.xib b/Classes/ChatRoomViewController.xib index a363aec5e..6e92c38e9 100644 --- a/Classes/ChatRoomViewController.xib +++ b/Classes/ChatRoomViewController.xib @@ -39,6 +39,21 @@ 292 + + + 292 + {320, 460} + + + + _NS:9 + NO + IBCocoaTouchFramework + + NSImage + numpad_background.png + + 290 @@ -102,6 +117,10 @@ NSImage chat_edit_over.png + + NSImage + chat_ok_default.png + NSImage chat_edit_default.png @@ -133,18 +152,19 @@ _NS:9 - 1 - MC45NDExNzY0NzA2IDAuOTY0NzA1ODgyNCAwLjk2NDcwNTg4MjQAA + 3 + MCAwAA YES IBCocoaTouchFramework YES - 1 + 1 + 2 0 YES 44 - 22 - 22 + 10 + 10 @@ -250,7 +270,7 @@ {320, 460} - + _NS:9 3 @@ -304,6 +324,14 @@ 32 + + + editButton + + + + 35 + dataSource @@ -399,6 +427,7 @@ + @@ -466,6 +495,12 @@ tableController + + 34 + + + background + @@ -473,6 +508,7 @@ com.apple.InterfaceBuilder.IBCocoaTouchPlugin UIResponder com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIToggleButton com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin @@ -482,6 +518,7 @@ com.apple.InterfaceBuilder.IBCocoaTouchPlugin ChatRoomTableViewController com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin @@ -492,7 +529,7 @@ - 33 + 35 @@ -532,11 +569,16 @@ + UIToggleButton UITextField UIButton ChatRoomTableViewController + + editButton + UIToggleButton + messageField UITextField @@ -555,6 +597,14 @@ ./Classes/ChatRoomViewController.h + + UIToggleButton + UIButton + + IBProjectSource + ./Classes/UIToggleButton.h + + 0 @@ -571,9 +621,11 @@ {320, 117} {320, 117} {500, 117} + {320, 117} {140, 117} {140, 117} {140, 117} + {640, 523} 1181 diff --git a/Classes/ChatTableViewController.h b/Classes/ChatTableViewController.h index 32cd1f67e..9b5ca4c03 100644 --- a/Classes/ChatTableViewController.h +++ b/Classes/ChatTableViewController.h @@ -20,9 +20,13 @@ #import @interface ChatTableViewController : UITableViewController { +@private + BOOL editMode; NSArray *data; } -@property (nonatomic, retain) NSArray *data; +- (void) toggleEditMode; +- (void) enterEditMode; +- (void) exitEditMode; @end diff --git a/Classes/ChatTableViewController.m b/Classes/ChatTableViewController.m index b72bd9a6f..287d881a1 100644 --- a/Classes/ChatTableViewController.m +++ b/Classes/ChatTableViewController.m @@ -25,16 +25,46 @@ @implementation ChatTableViewController -@synthesize data; + +#pragma mark - Lifecycle Functions + +- (id)init { + if((self = [super init]) != nil) { + self->editMode = false; + } + return self; +} + +- (void)dealloc { + if(data != nil) + [data release]; + [super dealloc]; +} #pragma mark - -- (void)setData:(NSArray *)adata { - if(self->data != nil) - [self->data release]; - self->data = [adata retain]; - [[self tableView] reloadData]; +- (void)reloadData { + if(data != nil) + [data release]; + data = [[ChatModel listConversations] retain]; +} + +- (void) toggleEditMode { + editMode = !editMode; + [(UITableView*)[self view] reloadData]; +} + +- (void) enterEditMode { + if(!editMode) { + [self toggleEditMode]; + } +} + +- (void) exitEditMode { + if(editMode) { + [self toggleEditMode]; + } } #pragma mark - UITableViewDataSource Functions @@ -46,6 +76,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + [self reloadData]; return [data count]; } @@ -53,10 +84,14 @@ { UIChatCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UIChatCell"]; if (cell == nil) { - cell = [[UIChatCell alloc] init]; + cell = [[UIChatCell alloc] initWithIdentifier:@"UIChatCell"]; } [cell setChat:[data objectAtIndex:[indexPath row]]]; + if(editMode) + [cell enterEditMode]; + else + [cell exitEditMode]; [cell update]; return cell; diff --git a/Classes/ChatViewController.h b/Classes/ChatViewController.h index ac61c32c0..999fcfb6f 100644 --- a/Classes/ChatViewController.h +++ b/Classes/ChatViewController.h @@ -19,14 +19,18 @@ #import +#import "UIToggleButton.h" + #import "ChatTableViewController.h" #import "UICompositeViewController.h" @interface ChatViewController : UIViewController { ChatTableViewController *tableController; + UIToggleButton *editButton; } @property (nonatomic, retain) IBOutlet ChatTableViewController* tableController; +@property (nonatomic, retain) IBOutlet UIToggleButton *editButton; - (IBAction)onAddClick:(id) event; - (IBAction)onEditClick:(id) event; diff --git a/Classes/ChatViewController.m b/Classes/ChatViewController.m index 2e769e36c..3e5279bca 100644 --- a/Classes/ChatViewController.m +++ b/Classes/ChatViewController.m @@ -24,7 +24,7 @@ @implementation ChatViewController @synthesize tableController; - +@synthesize editButton; #pragma mark - Lifecycle Functions @@ -33,11 +33,48 @@ } +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [tableController release]; + [editButton release]; + + [super dealloc]; +} + #pragma mark - ViewController Functions +- (void)viewDidLoad { + // Set selected+over background: IB lack ! + [editButton setImage:[UIImage imageNamed:@"chat_ok_over.png"] + forState:(UIControlStateHighlighted | UIControlStateSelected)]; +} + - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [tableController setData:[ChatModel listConversations]]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(textReceivedEvent:) + name:@"LinphoneTextReceived" + object:nil]; + + [tableController exitEditMode]; + [editButton setOff]; + [[tableController tableView] reloadData]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:@"LinphoneTextReceived" + object:nil]; +} + + +#pragma mark - Event Functions + +- (void)textReceivedEvent:(NSNotification *)notif { + [[tableController tableView] reloadData]; } @@ -62,14 +99,7 @@ } - (IBAction)onEditClick:(id)event { - ChatModel* line= [[ChatModel alloc] init]; - line.localContact = @""; - line.remoteContact = @"truc"; - line.message = @"blabla"; - line.direction = [NSNumber numberWithInt:1]; - line.time = [NSDate date]; - [line create]; - [tableController setData:[ChatModel listConversations]]; + [tableController toggleEditMode]; } @end diff --git a/Classes/ChatViewController.xib b/Classes/ChatViewController.xib index bd88f20eb..24b92a3ae 100644 --- a/Classes/ChatViewController.xib +++ b/Classes/ChatViewController.xib @@ -100,6 +100,10 @@ NSImage chat_edit_over.png + + NSImage + chat_ok_default.png + NSImage chat_edit_default.png @@ -128,7 +132,6 @@ {{0, 58}, {320, 402}} - _NS:9 1 @@ -190,6 +193,14 @@ 18 + + + editButton + + + + 22 + view @@ -309,6 +320,7 @@ com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIToggleButton com.apple.InterfaceBuilder.IBCocoaTouchPlugin @@ -316,7 +328,7 @@ - 21 + 22 @@ -345,22 +357,33 @@ id - - tableController - ChatTableViewController - - - tableController - + + UIToggleButton + ChatTableViewController + + + + editButton + UIToggleButton + + tableController ChatTableViewController - + IBProjectSource ./Classes/ChatViewController.h + + UIToggleButton + UIButton + + IBProjectSource + ./Classes/UIToggleButton.h + + 0 @@ -376,6 +399,7 @@ {320, 117} {320, 117} {320, 117} + {320, 117} 1181 diff --git a/Classes/ContactsViewController.m b/Classes/ContactsViewController.m index 436f5739b..1bfd43ea6 100644 --- a/Classes/ContactsViewController.m +++ b/Classes/ContactsViewController.m @@ -68,12 +68,11 @@ typedef enum _HistoryView { #pragma mark - ViewController Functions -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; [self.tableView reloadData]; } - - (void)viewDidLoad { [super viewDidLoad]; [self changeView: History_All]; diff --git a/Classes/DialerViewController.m b/Classes/DialerViewController.m index 90bc9123c..df5cf434e 100644 --- a/Classes/DialerViewController.m +++ b/Classes/DialerViewController.m @@ -115,12 +115,11 @@ selector:@selector(callUpdateEvent:) name:@"LinphoneCallUpdate" object:nil]; - // Update on show LinphoneCall* call = linphone_core_get_current_call([LinphoneManager getLc]); LinphoneCallState state = (call != NULL)?linphone_call_get_state(call): 0; [self callUpdate:call state:state]; -} +} - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; diff --git a/Classes/FirstLoginViewController.m b/Classes/FirstLoginViewController.m index cf47635c9..c8e2f61bf 100644 --- a/Classes/FirstLoginViewController.m +++ b/Classes/FirstLoginViewController.m @@ -68,15 +68,15 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [usernameField setText:[[LinphoneManager instance].settingsStore objectForKey:@"username_preference"]]; - [passwordField setText:[[LinphoneManager instance].settingsStore objectForKey:@"password_preference"]]; - // Set observer [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(registrationUpdateEvent:) name:@"LinphoneRegistrationUpdate" object:nil]; + [usernameField setText:[[LinphoneManager instance].settingsStore objectForKey:@"username_preference"]]; + [passwordField setText:[[LinphoneManager instance].settingsStore objectForKey:@"password_preference"]]; + // Update on show const MSList* list = linphone_core_get_proxy_config_list([LinphoneManager getLc]); if(list != NULL) { diff --git a/Classes/HistoryTableViewController.m b/Classes/HistoryTableViewController.m index 33747e013..99c18004e 100644 --- a/Classes/HistoryTableViewController.m +++ b/Classes/HistoryTableViewController.m @@ -24,6 +24,7 @@ @implementation HistoryTableViewController + #pragma mark - Lifecycle Functions - (id)init { diff --git a/Classes/HistoryViewController.m b/Classes/HistoryViewController.m index 8b2aa9e46..6faf08aa8 100644 --- a/Classes/HistoryViewController.m +++ b/Classes/HistoryViewController.m @@ -68,15 +68,11 @@ typedef enum _HistoryView { #pragma mark - ViewController Functions -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - [self.tableView reloadData]; -} - - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [tableController exitEditMode]; [editButton setOff]; + [self.tableView reloadData]; } - (void)viewDidLoad { diff --git a/Classes/InCallTableViewController.m b/Classes/InCallTableViewController.m index a2908bfec..b4bd3d8c0 100644 --- a/Classes/InCallTableViewController.m +++ b/Classes/InCallTableViewController.m @@ -195,28 +195,6 @@ enum TableSection { #pragma mark - UITableViewDataSource Functions -- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { - if(section == CallSection) { - return [[UIView alloc] initWithFrame:CGRectZero]; - } else if(section == ConferenceSection) { - LinphoneCore* lc = [LinphoneManager getLc]; - if(linphone_core_get_conference_size(lc) > 0){ - UIConferenceHeader *headerController = [[UIConferenceHeader alloc] init]; - [headerController update]; - UIView *headerView = [headerController view]; - [headerController release]; - return headerView; - } else { - return [[UIView alloc] initWithFrame:CGRectZero]; - } - } - return [[UIView alloc] initWithFrame:CGRectZero]; -} - -- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { - return [[UIView alloc] initWithFrame:CGRectZero]; -} - - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UICallCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UICallCell"]; if (cell == nil) { @@ -268,19 +246,39 @@ enum TableSection { return 2; } -- (NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section -{ +- (NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return @""; } -- (NSString*)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section -{ +- (NSString*)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { return @""; } #pragma mark - UITableViewDelegate Functions +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + if(section == CallSection) { + return [[UIView alloc] initWithFrame:CGRectZero]; + } else if(section == ConferenceSection) { + LinphoneCore* lc = [LinphoneManager getLc]; + if(linphone_core_get_conference_size(lc) > 0){ + UIConferenceHeader *headerController = [[UIConferenceHeader alloc] init]; + [headerController update]; + UIView *headerView = [headerController view]; + [headerController release]; + return headerView; + } else { + return [[UIView alloc] initWithFrame:CGRectZero]; + } + } + return [[UIView alloc] initWithFrame:CGRectZero]; +} + +- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { + return [[UIView alloc] initWithFrame:CGRectZero]; +} + - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { LinphoneCore* lc = [LinphoneManager getLc]; if(section == CallSection) { @@ -305,8 +303,7 @@ enum TableSection { return 0.000001f; // Hack UITableView = 0 } -- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath -{ +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { bool inConference = indexPath.section == ConferenceSection; LinphoneCall* call = [InCallTableViewController retrieveCallAtIndex:indexPath.row inConference:inConference]; UICallCellData* data = [callCellData objectForKey:[NSValue valueWithPointer:call]]; diff --git a/Classes/InCallViewController.m b/Classes/InCallViewController.m index e3690a931..f55b992aa 100644 --- a/Classes/InCallViewController.m +++ b/Classes/InCallViewController.m @@ -101,10 +101,6 @@ const NSInteger SECURE_BUTTON_TAG=5; [[UIApplication sharedApplication] setIdleTimerDisabled:YES]; UIDevice *device = [UIDevice currentDevice]; device.proximityMonitoringEnabled = YES; - - if ([[UIDevice currentDevice].systemVersion doubleValue] < 5.0) { - [callTableController viewDidAppear:NO]; - } } - (void)viewWillDisappear:(BOOL)animated { @@ -143,6 +139,10 @@ const NSInteger SECURE_BUTTON_TAG=5; LinphoneCall* call = linphone_core_get_current_call([LinphoneManager getLc]); LinphoneCallState state = (call != NULL)?linphone_call_get_state(call): 0; [self callUpdate:call state:state]; + + if ([[UIDevice currentDevice].systemVersion doubleValue] < 5.0) { + [callTableController viewDidAppear:NO]; + } } - (void)viewDidDisappear:(BOOL)animated { diff --git a/Classes/IncomingCallViewController.m b/Classes/IncomingCallViewController.m index 744422fea..833ddb811 100644 --- a/Classes/IncomingCallViewController.m +++ b/Classes/IncomingCallViewController.m @@ -49,7 +49,6 @@ selector:@selector(callUpdateEvent:) name:@"LinphoneCallUpdate" object:nil]; - [self callUpdate:call state:linphone_call_get_state(call)]; } diff --git a/Classes/LinphoneManager.m b/Classes/LinphoneManager.m index fd56aacc4..3bac80534 100644 --- a/Classes/LinphoneManager.m +++ b/Classes/LinphoneManager.m @@ -29,6 +29,7 @@ #import "LinphoneManager.h" #import "FastAddressBook.h" #import "LinphoneCoreSettingsStore.h" +#import "ChatModel.h" #include "linphonecore_utils.h" #include "lpconfig.h" @@ -355,6 +356,28 @@ static void linphone_iphone_registration_state(LinphoneCore *lc, LinphoneProxyCo [(LinphoneManager*)linphone_core_get_user_data(lc) onRegister:lc cfg:cfg state:state message:message]; } +- (void)onTextReceived:(LinphoneCore *)lc room:(LinphoneChatRoom *)room from:(const LinphoneAddress *)from message:(const char *)message { + + // Save message in database + ChatModel *chat = [[ChatModel alloc] init]; + [chat setRemoteContact:[NSString stringWithUTF8String:linphone_address_get_username(from)]]; + [chat setMessage:[NSString stringWithUTF8String:message]]; + [chat setDirection:[NSNumber numberWithInt:1]]; + [chat create]; + + // Post event + NSDictionary* dict = [[[NSDictionary alloc] initWithObjectsAndKeys: + [NSValue valueWithPointer:room], @"room", + [NSValue valueWithPointer:from], @"from", + [NSString stringWithUTF8String:message], @"message", + nil] autorelease]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"LinphoneTextReceived" object:self userInfo:dict]; +} + +static void linphone_iphone_text_received(LinphoneCore *lc, LinphoneChatRoom *room, const LinphoneAddress *from, const char *message) { + [(LinphoneManager*)linphone_core_get_user_data(lc) onTextReceived:lc room:room from:from message:message]; +} + static LinphoneCoreVTable linphonec_vtable = { .show =NULL, .call_state_changed =(LinphoneCallStateCb)linphone_iphone_call_state, @@ -366,7 +389,7 @@ static LinphoneCoreVTable linphonec_vtable = { .display_message=linphone_iphone_log, .display_warning=linphone_iphone_log, .display_url=NULL, - .text_received=NULL, + .text_received=linphone_iphone_text_received, .dtmf_received=NULL, .transfer_state_changed=linphone_iphone_transfer_state_changed }; diff --git a/Classes/LinphoneUI/UICallBar.m b/Classes/LinphoneUI/UICallBar.m index dad26c74b..e34be52e5 100644 --- a/Classes/LinphoneUI/UICallBar.m +++ b/Classes/LinphoneUI/UICallBar.m @@ -159,7 +159,6 @@ selector:@selector(callUpdateEvent:) name:@"LinphoneCallUpdate" object:nil]; - // Update on show LinphoneCall* call = linphone_core_get_current_call([LinphoneManager getLc]); LinphoneCallState state = (call != NULL)?linphone_call_get_state(call): 0; diff --git a/Classes/LinphoneUI/UICallBar.xib b/Classes/LinphoneUI/UICallBar.xib index 0936438fe..637385568 100644 --- a/Classes/LinphoneUI/UICallBar.xib +++ b/Classes/LinphoneUI/UICallBar.xib @@ -42,7 +42,6 @@ {{0, 335}, {320, 125}} - _NS:9 1 @@ -522,6 +521,7 @@ 0 0 NO + NO NSImage @@ -558,6 +558,7 @@ 0 0 NO + NO NSImage @@ -594,6 +595,7 @@ 0 0 NO + NO NSImage @@ -626,6 +628,7 @@ 0 0 NO + NO NSImage @@ -687,6 +690,7 @@ 0 0 NO + NO NSImage @@ -715,6 +719,7 @@ 0 0 NO + NO NSImage @@ -1140,7 +1145,7 @@ 59 - waitView + videoWaitView 73 @@ -1620,8 +1625,8 @@ {160, 134} {160, 134} {160, 134} - {16, 16} - {16, 16} + {160, 134} + {160, 134} {209, 136} {209, 136} {209, 136} diff --git a/Classes/LinphoneUI/UICallCell.h b/Classes/LinphoneUI/UICallCell.h index ff82a28dd..00e624083 100644 --- a/Classes/LinphoneUI/UICallCell.h +++ b/Classes/LinphoneUI/UICallCell.h @@ -54,7 +54,7 @@ UICallCellData *data; } -@property (weak) UICallCellData *data; +@property (retain) UICallCellData *data; @property (nonatomic, retain) IBOutlet UIImageView* headerBackgroundImage; @property (nonatomic, retain) IBOutlet UIImageView* headerBackgroundHightlightImage; diff --git a/Classes/LinphoneUI/UIChatCell.h b/Classes/LinphoneUI/UIChatCell.h index 2cfdede30..9fdb9f7af 100644 --- a/Classes/LinphoneUI/UIChatCell.h +++ b/Classes/LinphoneUI/UIChatCell.h @@ -22,21 +22,28 @@ #import "ChatModel.h" @interface UIChatCell : UITableViewCell { - UIImageView *avatarView; + UIImageView *avatarImage; UILabel *displayNameLabel; UILabel *chatContentLabel; + UIButton *detailsButton; + UIButton *deleteButton; ChatModel *chat; } -- (void)update; - -@property (weak) ChatModel *chat; - -@property (nonatomic, retain) IBOutlet UIImageView *avatarView; +@property (retain) ChatModel *chat; +@property (nonatomic, retain) IBOutlet UIImageView *avatarImage; @property (nonatomic, retain) IBOutlet UILabel* displayNameLabel; @property (nonatomic, retain) IBOutlet UILabel* chatContentLabel; +@property (nonatomic, retain) IBOutlet UIButton *detailsButton; +@property (nonatomic, retain) IBOutlet UIButton * deleteButton; -- (IBAction)onDetails:(id)event; +- (id)initWithIdentifier:(NSString*)identifier; +- (void)update; +- (void)enterEditMode; +- (void)exitEditMode; + +- (IBAction)onDetailsClick:(id)event; +- (IBAction)onDeleteClick:(id)event; @end diff --git a/Classes/LinphoneUI/UIChatCell.m b/Classes/LinphoneUI/UIChatCell.m index 5f3973769..aa1969582 100644 --- a/Classes/LinphoneUI/UIChatCell.m +++ b/Classes/LinphoneUI/UIChatCell.m @@ -18,19 +18,23 @@ */ #import "UIChatCell.h" +#import "PhoneMainView.h" + @implementation UIChatCell -@synthesize avatarView; +@synthesize avatarImage; @synthesize displayNameLabel; @synthesize chatContentLabel; +@synthesize detailsButton; +@synthesize deleteButton; @synthesize chat; #pragma mark - Lifecycle Functions -- (id)init { - if ((self = [super init]) != nil) { +- (id)initWithIdentifier:(NSString*)identifier { + if ((self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]) != nil) { NSArray *arrayOfViews = [[NSBundle mainBundle] loadNibNamed:@"UIChatCell" owner:self options:nil]; @@ -45,6 +49,11 @@ - (void)dealloc { [displayNameLabel release]; [chatContentLabel release]; + [avatarImage release]; + [detailsButton release]; + [deleteButton release]; + + [chat release]; [super dealloc]; } @@ -52,10 +61,12 @@ #pragma mark - - (void)update { - [avatarView setImage:[UIImage imageNamed:@"avatar_unknown_small.png"]]; - [displayNameLabel setText:[chat remoteContact]]; - [chatContentLabel setText:[chat message]]; + if (chat != nil) { + [avatarImage setImage:[UIImage imageNamed:@"avatar_unknown_small.png"]]; + [displayNameLabel setText:[chat remoteContact]]; + [chatContentLabel setText:[chat message]]; + } // // Adapt size @@ -80,11 +91,33 @@ [chatContentLabel setFrame: chatContentFrame]; } +- (void)enterEditMode { + [deleteButton setHidden:false]; + [detailsButton setHidden:true]; +} + +- (void)exitEditMode { + [detailsButton setHidden:false]; + [deleteButton setHidden:true]; +} #pragma mark - Action Functions -- (IBAction)onDetails: (id) event { - +- (IBAction)onDetailsClick: (id) event { + // Go to dialer view + NSDictionary *dict = [[[NSDictionary alloc] initWithObjectsAndKeys: + [[[NSArray alloc] initWithObjects: [chat remoteContact], nil] autorelease] + , @"setRemoteContact:", + nil] autorelease]; + [[PhoneMainView instance] changeView:PhoneView_ChatRoom dict:dict push:TRUE]; +} + +- (IBAction)onDeleteClick: (id) event { + if(chat != NULL) { + [ChatModel removeConversation:[chat remoteContact]]; + UITableView *parentTable = (UITableView *)self.superview; + [parentTable reloadData]; + } } @end diff --git a/Classes/LinphoneUI/UIChatCell.xib b/Classes/LinphoneUI/UIChatCell.xib index dbf9b0730..f8738e424 100644 --- a/Classes/LinphoneUI/UIChatCell.xib +++ b/Classes/LinphoneUI/UIChatCell.xib @@ -138,7 +138,7 @@ 11 11 11 - + 3 MC41AA @@ -150,16 +150,44 @@ NSImage list_details_default.png - + 2 15 - + Helvetica-Bold 15 16 + + + -2147483356 + {{276, 0}, {44, 44}} + + + _NS:9 + NO + IBCocoaTouchFramework + 0 + 0 + NO + 11 + 11 + 11 + 11 + + + NSImage + list_delete_over.png + + + NSImage + list_delete_default.png + + + + {320, 44} @@ -218,14 +246,6 @@ 24 - - - avatarView - - - - 25 - backgroundView @@ -242,14 +262,47 @@ 29 + + + avatarImage + + + + 30 + + + + deleteButton + + + + 34 + + + + detailsButton + + + + 35 + - onDetails: + onDetailsClick: 7 - 22 + 36 + + + + onDeleteClick: + + + 7 + + 37 @@ -279,6 +332,7 @@ + @@ -292,7 +346,7 @@ 19 - imageView + avatarImage 20 @@ -318,6 +372,12 @@ background + + 32 + + + deleteButton + @@ -334,12 +394,15 @@ com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + - 29 + 37 @@ -347,30 +410,40 @@ UIChatCell UITableViewCell - onDetails: + onDetailsClick: id - onDetails: + onDetailsClick: - onDetails: + onDetailsClick: id - UIImageView + UIImageView UILabel + UIButton + UIButton UILabel - - avatarView + + avatarImage UIImageView chatContentLabel UILabel + + deleteButton + UIButton + + + detailsButton + UIButton + displayNameLabel UILabel @@ -393,6 +466,8 @@ 3 {131, 131} + {45, 45} + {45, 45} {45, 45} {45, 45} {640, 88} diff --git a/Classes/LinphoneUI/UIChatRoomCell.h b/Classes/LinphoneUI/UIChatRoomCell.h new file mode 100644 index 000000000..abc320f0f --- /dev/null +++ b/Classes/LinphoneUI/UIChatRoomCell.h @@ -0,0 +1,49 @@ +/* UIChatRoomCell.h + * + * Copyright (C) 2012 Belledonne Comunications, Grenoble, France + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#import + +#import "ChatModel.h" + +@interface UIChatRoomCell : UITableViewCell { + UIImageView *backgroundImage; + UIView *contentView; + UILabel *messageLabel; + UIButton *deleteButton; + + ChatModel *chat; +} + +@property (retain) ChatModel *chat; +@property (nonatomic, retain) IBOutlet UIView *contentView; +@property (nonatomic, retain) IBOutlet UIImageView* backgroundImage; +@property (nonatomic, retain) IBOutlet UILabel *messageLabel; +@property (nonatomic, retain) IBOutlet UIButton *deleteButton; + +- (id)initWithIdentifier:(NSString*)identifier; ++ (CGFloat)height:(ChatModel*)chat; + +- (void)update; + +- (void)enterEditMode; +- (void)exitEditMode; + +- (IBAction)onDeleteClick:(id)event; + +@end diff --git a/Classes/LinphoneUI/UIChatRoomCell.m b/Classes/LinphoneUI/UIChatRoomCell.m new file mode 100644 index 000000000..5f77fc7a8 --- /dev/null +++ b/Classes/LinphoneUI/UIChatRoomCell.m @@ -0,0 +1,125 @@ +/* UIChatRoomCell.m + * + * Copyright (C) 2012 Belledonne Comunications, Grenoble, France + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#import "UIChatRoomCell.h" + +#import + +@implementation UIChatRoomCell + +@synthesize contentView; +@synthesize backgroundImage; +@synthesize messageLabel; +@synthesize deleteButton; +@synthesize chat; + +static const CGFloat CELL_MIN_HEIGHT = 65.0f; +static const CGFloat CELL_MESSAGE_MAX_WIDTH = 280.0f; +static const CGFloat CELL_FONT_SIZE = 17.0f; +static UIFont *CELL_FONT = nil; + +#pragma mark - Lifecycle Functions + +- (id)initWithIdentifier:(NSString*)identifier { + if ((self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]) != nil) { + [[NSBundle mainBundle] loadNibNamed:@"UIChatRoomCell" + owner:self + options:nil]; + [self addSubview:contentView]; + } + return self; +} + +- (void)dealloc { + [backgroundImage release]; + [contentView release]; + [messageLabel release]; + [deleteButton release]; + + [chat release]; + + [super dealloc]; +} + + +#pragma mark - + +- (void)update { + if(chat != nil) { + if([chat direction]) { + [backgroundImage setImage:[TUNinePatchCache imageOfSize:[backgroundImage bounds].size + forNinePatchNamed:@"chat_bubble_incoming"]]; + } else { + [backgroundImage setImage:[TUNinePatchCache imageOfSize:[backgroundImage bounds].size + forNinePatchNamed:@"chat_bubble_outgoing"]]; + } + [messageLabel setText:[chat message]]; + } + CGRect frame = [messageLabel frame]; + frame.size.height = [UIChatRoomCell messageHeight:[chat message]]; + [messageLabel setFrame:frame]; +} + ++ (CGFloat)messageHeight:(NSString*)message { + if(CELL_FONT == nil) { + CELL_FONT = [UIFont systemFontOfSize:CELL_FONT_SIZE]; + } + CGSize messageSize = [message sizeWithFont: CELL_FONT + constrainedToSize: CGSizeMake(CELL_MESSAGE_MAX_WIDTH, 10000.0f) + lineBreakMode: UILineBreakModeTailTruncation]; + return messageSize.height; +} + ++ (CGFloat)height:(ChatModel*)chat { + CGFloat height = [UIChatRoomCell messageHeight:[chat message]]; + height += 20; + if(height < CELL_MIN_HEIGHT) + height = CELL_MIN_HEIGHT; + return height; +} + +- (void)enterEditMode { + [deleteButton setHidden:false]; +} + +- (void)exitEditMode { + [deleteButton setHidden:true]; +} + +#pragma mark - View Functions + +- (void)layoutSubviews { + // Resize content + CGRect frame = [contentView frame]; + frame.size = [self frame].size; + [contentView setFrame:frame]; +} + + +#pragma mark - Action Functions + +- (IBAction)onDeleteClick: (id) event { + if(chat != NULL) { + [chat delete]; + UITableView *parentTable = (UITableView *)self.superview; + [parentTable reloadData]; + } +} + +@end diff --git a/Classes/LinphoneUI/UIChatRoomCell.xib b/Classes/LinphoneUI/UIChatRoomCell.xib new file mode 100644 index 000000000..57cdb4968 --- /dev/null +++ b/Classes/LinphoneUI/UIChatRoomCell.xib @@ -0,0 +1,389 @@ + + + + 1296 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1181 + + + IBUIButton + IBUIImageView + IBUIView + IBUILabel + IBProxyObject + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + + + + 274 + {{10, 10}, {300, 120}} + + + + _NS:9 + NO + IBCocoaTouchFramework + + NSImage + chat_bubble_incoming.png + + + + + 274 + + + + 274 + {280, 100} + + + + _NS:9 + NO + YES + 7 + NO + IBCocoaTouchFramework + They who can give up essential liberty to obtain a little temporary safety, deserve neither liberty nor safety. + + 3 + MC4zMzMzMzMzMzMzAA + + + 0 + 10 + 100000 + + 1 + 17 + + + Helvetica + 17 + 16 + + NO + + + + -2147483351 + {{236, 28}, {44, 44}} + + + _NS:9 + NO + IBCocoaTouchFramework + 0 + 0 + NO + 11 + 11 + 11 + 11 + + 3 + MC41AA + + + NSImage + list_delete_over.png + + + NSImage + list_delete_default.png + + + 2 + 15 + + + Helvetica-Bold + 15 + 16 + + + + {{20, 25}, {280, 100}} + + + + _NS:9 + + 3 + MCAwAA + + YES + IBCocoaTouchFramework + + + {320, 140} + + + + _NS:9 + + YES + 10 + IBCocoaTouchFramework + + + + 292 + {100, 100} + + + _NS:9 + + IBCocoaTouchFramework + + + + 292 + {100, 100} + + + _NS:9 + + IBCocoaTouchFramework + + + + + + + backgroundView + + + + 10 + + + + backgroundImage + + + + 11 + + + + selectedBackgroundView + + + + 13 + + + + contentView + + + + 14 + + + + messageLabel + + + + 17 + + + + deleteButton + + + + 19 + + + + onDeleteClick: + + + 7 + + 20 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + + + contentView + + + 7 + + + backgroundView + + + 9 + + + selectedBackgroundView + + + 3 + + + backgroundImage + + + 16 + + + + + + + messageView + + + 15 + + + messageLabel + + + 18 + + + deleteButton + + + + + UIChatRoomCell + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 20 + + + + + UIChatRoomCell + UITableViewCell + + onDeleteClick: + id + + + onDeleteClick: + + onDeleteClick: + id + + + + UIImageView + UIView + UIButton + UILabel + + + + backgroundImage + UIImageView + + + contentView + UIView + + + deleteButton + UIButton + + + messageLabel + UILabel + + + + IBProjectSource + ./Classes/UIChatRoomCell.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + YES + 3 + + {71, 46} + {45, 45} + {45, 45} + + 1181 + + diff --git a/Classes/LinphoneUI/UIChatRoomHeader.h b/Classes/LinphoneUI/UIChatRoomHeader.h new file mode 100644 index 000000000..8e67a8fd5 --- /dev/null +++ b/Classes/LinphoneUI/UIChatRoomHeader.h @@ -0,0 +1,39 @@ +/* UIChatRoomHeader.h + * + * Copyright (C) 2012 Belledonne Comunications, Grenoble, France + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#import +#import "ChatModel.h" + +@interface UIChatRoomHeader : UIViewController { + UILabel *contactLabel; + UIImageView *avatarImage; + + NSString *contact; +} + +@property (copy) NSString *contact; + +@property (nonatomic, retain) IBOutlet UILabel *contactLabel; +@property (nonatomic, retain) IBOutlet UIImageView *avatarImage; + ++ (CGFloat)height; + +- (void)update; + +@end diff --git a/Classes/LinphoneUI/UIChatRoomHeader.m b/Classes/LinphoneUI/UIChatRoomHeader.m new file mode 100644 index 000000000..b37ce23f3 --- /dev/null +++ b/Classes/LinphoneUI/UIChatRoomHeader.m @@ -0,0 +1,56 @@ +/* UIChatRoomHeader.m + * + * Copyright (C) 2012 Belledonne Comunications, Grenoble, France + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#import "UIChatRoomHeader.h" + +@implementation UIChatRoomHeader + +@synthesize avatarImage; +@synthesize contactLabel; +@synthesize contact; + + +#pragma mark - Lifecycle Functions + +- (id)init { + return [super initWithNibName:@"UIChatRoomHeader" bundle:[NSBundle mainBundle]]; +} + +- (void)dealloc { + [avatarImage release]; + [contactLabel release]; + [contact release]; + [super dealloc]; +} + + +#pragma mark - + +- (void)update { + if(contact != nil) { + [avatarImage setImage:[UIImage imageNamed:@"avatar_unknown_small.png"]]; + [contactLabel setText:contact]; + } +} + ++ (CGFloat)height { + return 80.0f; +} + +@end diff --git a/Classes/LinphoneUI/UIChatRoomHeader.xib b/Classes/LinphoneUI/UIChatRoomHeader.xib new file mode 100644 index 000000000..937c1726d --- /dev/null +++ b/Classes/LinphoneUI/UIChatRoomHeader.xib @@ -0,0 +1,244 @@ + + + + 1296 + 11E53 + 2182 + 1138.47 + 569.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1181 + + + IBUIImageView + IBUIView + IBUILabel + IBProxyObject + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 292 + + + + 292 + {{-13, -1}, {131, 107}} + + + + _NS:9 + NO + IBCocoaTouchFramework + + NSImage + avatar_shadow_small.png + + + + + 274 + {{20, 10}, {65, 65}} + + + + _NS:9 + NO + IBCocoaTouchFramework + + NSImage + avatar_unknown_small.png + + + + + 292 + {{101, 31}, {199, 43}} + + + _NS:9 + NO + YES + 7 + NO + IBCocoaTouchFramework + Contact1 + + 3 + MC4zMzMzMzMzMzMzAA + + + 0 + 10 + + 1 + 22 + + + Helvetica + 22 + 16 + + + + {320, 80} + + + + _NS:9 + + 3 + MCAwAA + + IBCocoaTouchFramework + + + + + + + view + + + + 5 + + + + avatarImage + + + + 9 + + + + contactLabel + + + + 11 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 4 + + + + + + + + + + 6 + + + avatarImage + + + 7 + + + avatarShadowBackground + + + 8 + + + contactLabel + + + + + UIChatRoomHeader + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 11 + + + + + UIChatRoomHeader + UIViewController + + UIImageView + UILabel + + + + avatarImage + UIImageView + + + contactLabel + UILabel + + + + IBProjectSource + ./Classes/UIChatRoomHeader.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + YES + 3 + + {262, 214} + {131, 131} + + 1181 + + diff --git a/Classes/LinphoneUI/UICompositeViewController.m b/Classes/LinphoneUI/UICompositeViewController.m index 934d17e7c..280bc5834 100644 --- a/Classes/LinphoneUI/UICompositeViewController.m +++ b/Classes/LinphoneUI/UICompositeViewController.m @@ -101,6 +101,7 @@ + (void)addSubView:(UIViewController*)controller view:(UIView*)view { if(controller != nil) { + [controller view]; // Load the view if ([[UIDevice currentDevice].systemVersion doubleValue] < 5.0) { [controller viewWillAppear:NO]; } diff --git a/Classes/LinphoneUI/UIConferenceHeader.m b/Classes/LinphoneUI/UIConferenceHeader.m index 06354bf87..33e4cae34 100644 --- a/Classes/LinphoneUI/UIConferenceHeader.m +++ b/Classes/LinphoneUI/UIConferenceHeader.m @@ -61,7 +61,7 @@ #pragma mark - - (void)update { - [self view]; + [self view]; // Force view load [stateImage setHidden:true]; [pauseButton update]; } diff --git a/Classes/LinphoneUI/UIStateBar.m b/Classes/LinphoneUI/UIStateBar.m index 97ea70e3d..507d9b1fe 100644 --- a/Classes/LinphoneUI/UIStateBar.m +++ b/Classes/LinphoneUI/UIStateBar.m @@ -51,7 +51,6 @@ NSTimer *callQualityTimer; [super viewWillAppear:animated]; // Set callQualityTimer - [callQualityImage setHidden: true]; callQualityTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(callQualityUpdate) @@ -64,6 +63,9 @@ NSTimer *callQualityTimer; name:@"LinphoneRegistrationUpdate" object:nil]; + + [callQualityImage setHidden: true]; + // Update to default state LinphoneProxyConfig* config = NULL; if([LinphoneManager isLcReady]) diff --git a/Classes/Model/ChatModel.h b/Classes/Model/ChatModel.h index 4c2f11963..1d5139b18 100644 --- a/Classes/Model/ChatModel.h +++ b/Classes/Model/ChatModel.h @@ -24,7 +24,7 @@ NSNumber *chatId; NSString *localContact; NSString *remoteContact; - NSNumber *direction; + NSNumber *direction; //0 outgoing 1 incoming NSString *message; NSDate *time; } @@ -43,5 +43,6 @@ + (NSArray *) listConversations; + (NSArray *) listMessages:(NSString *)contact; ++ (void) removeConversation:(NSString *)contact; @end diff --git a/Classes/Model/ChatModel.m b/Classes/Model/ChatModel.m index 5c5ef40ed..bf81c131b 100644 --- a/Classes/Model/ChatModel.m +++ b/Classes/Model/ChatModel.m @@ -139,7 +139,7 @@ return; } - const char *sql = [[NSString stringWithFormat:@"DELETE chat WHERE id=%@", + const char *sql = [[NSString stringWithFormat:@"DELETE FROM chat WHERE id=%@", chatId] UTF8String]; sqlite3_stmt *sqlStatement; if (sqlite3_prepare(database, sql, -1, &sqlStatement, NULL) != SQLITE_OK) { @@ -226,4 +226,28 @@ return fArray; } ++ (void) removeConversation:(NSString *)contact { + sqlite3* database = [[LinphoneManager instance] database]; + if(database == NULL) { + NSLog(@"Database not ready"); + return; + } + + const char *sql = [[NSString stringWithFormat:@"DELETE FROM chat WHERE remoteContact=\"%@\"", + contact] UTF8String]; + sqlite3_stmt *sqlStatement; + if (sqlite3_prepare(database, sql, -1, &sqlStatement, NULL) != SQLITE_OK) { + NSLog(@"Can't prepare the query: %s (%s)", sql, sqlite3_errmsg(database)); + return; + } + + if (sqlite3_step(sqlStatement) != SQLITE_DONE) { + NSLog(@"Error during execution of query: %s (%s)", sql, sqlite3_errmsg(database)); + sqlite3_finalize(sqlStatement); + return; + } + + sqlite3_finalize(sqlStatement); +} + @end diff --git a/Classes/Utils/CPAnimationSequence.h b/Classes/Utils/CPAnimation/CPAnimationSequence.h similarity index 100% rename from Classes/Utils/CPAnimationSequence.h rename to Classes/Utils/CPAnimation/CPAnimationSequence.h diff --git a/Classes/Utils/CPAnimationSequence.m b/Classes/Utils/CPAnimation/CPAnimationSequence.m similarity index 100% rename from Classes/Utils/CPAnimationSequence.m rename to Classes/Utils/CPAnimation/CPAnimationSequence.m diff --git a/Classes/Utils/CPAnimationStep.h b/Classes/Utils/CPAnimation/CPAnimationStep.h similarity index 100% rename from Classes/Utils/CPAnimationStep.h rename to Classes/Utils/CPAnimation/CPAnimationStep.h diff --git a/Classes/Utils/CPAnimationStep.m b/Classes/Utils/CPAnimation/CPAnimationStep.m similarity index 100% rename from Classes/Utils/CPAnimationStep.m rename to Classes/Utils/CPAnimation/CPAnimationStep.m diff --git a/Classes/WizardViewController.xib b/Classes/WizardViewController.xib index 6a6efb1c4..0d4ec330a 100644 --- a/Classes/WizardViewController.xib +++ b/Classes/WizardViewController.xib @@ -43,6 +43,7 @@ 292 {320, 394} + _NS:9 NO @@ -57,6 +58,7 @@ 292 {320, 394} + _NS:9 @@ -75,6 +77,7 @@ 292 {160, 77} + _NS:9 NO @@ -91,6 +94,10 @@ NSImage setup_cancel_over.png + + NSImage + setup_cancel_disabled.png + NSImage setup_cancel_default.png @@ -110,6 +117,7 @@ 292 {{160, 0}, {160, 77}} + _NS:9 NO @@ -123,6 +131,10 @@ NSImage setup_start_over.png + + NSImage + setup_start_disabled.png + NSImage setup_start_default.png @@ -135,6 +147,8 @@ -2147483356 {{160, 0}, {160, 77}} + + _NS:9 NO IBCocoaTouchFramework @@ -147,6 +161,10 @@ NSImage setup_back_over.png + + NSImage + setup_back_disabled.png + NSImage setup_back_default.png @@ -157,6 +175,7 @@ {{0, 383}, {320, 77}} + _NS:9 @@ -165,6 +184,7 @@ {320, 460} + _NS:9 @@ -185,6 +205,7 @@ 274 {{60, 80}, {201, 129}} + _NS:9 NO @@ -199,6 +220,8 @@ 292 {{40, 313}, {240, 44}} + + _NS:9 NO YES @@ -228,6 +251,7 @@ {320, 460} + _NS:9 @@ -242,6 +266,7 @@ 274 {{31, 50}, {258, 24}} + _NS:9 NO @@ -256,6 +281,7 @@ 292 {{33, 141}, {255, 50}} + _NS:9 NO @@ -289,6 +315,7 @@ 292 {{33, 205}, {255, 50}} + _NS:9 NO @@ -309,6 +336,8 @@ 292 {{33, 269}, {255, 50}} + + _NS:9 NO IBCocoaTouchFramework @@ -326,6 +355,7 @@ {320, 460} + _NS:9 @@ -340,6 +370,7 @@ 274 {{31, 50}, {258, 24}} + _NS:9 NO @@ -351,6 +382,7 @@ 292 {{39, 80}, {240, 44}} + _NS:9 NO @@ -374,6 +406,7 @@ 292 {{32, 140}, {255, 31}} + _NS:9 NO @@ -412,6 +445,7 @@ 292 {{32, 185}, {255, 31}} + _NS:9 NO @@ -441,6 +475,7 @@ 292 {{32, 230}, {255, 31}} + _NS:9 NO @@ -470,6 +505,7 @@ 292 {{32, 275}, {255, 31}} + _NS:9 NO @@ -498,6 +534,8 @@ 292 {{33, 330}, {255, 50}} + + _NS:9 NO IBCocoaTouchFramework @@ -522,6 +560,7 @@ {320, 460} + _NS:9 @@ -536,6 +575,7 @@ 274 {{31, 50}, {258, 24}} + _NS:9 NO @@ -547,6 +587,7 @@ 292 {{40, 80}, {240, 44}} + _NS:9 NO @@ -570,6 +611,8 @@ 292 {{32, 330}, {255, 50}} + + _NS:9 NO IBCocoaTouchFramework @@ -589,6 +632,7 @@ 292 {{32, 185}, {255, 31}} + _NS:9 NO @@ -618,6 +662,7 @@ 292 {{32, 140}, {255, 31}} + _NS:9 NO @@ -644,6 +689,7 @@ {320, 460} + _NS:9 @@ -658,6 +704,7 @@ 274 {{31, 50}, {258, 24}} + _NS:9 NO @@ -669,6 +716,7 @@ 292 {{40, 80}, {240, 44}} + _NS:9 NO @@ -692,6 +740,7 @@ 292 {{33, 140}, {255, 31}} + _NS:9 NO @@ -720,6 +769,7 @@ 292 {{33, 185}, {255, 31}} + _NS:9 NO @@ -749,6 +799,7 @@ 292 {{33, 230}, {255, 31}} + _NS:9 NO @@ -777,6 +828,8 @@ 292 {{34, 330}, {255, 50}} + + _NS:9 NO IBCocoaTouchFramework @@ -794,6 +847,7 @@ {320, 460} + _NS:9 @@ -1292,11 +1346,11 @@ com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin - + com.apple.InterfaceBuilder.IBCocoaTouchPlugin - + com.apple.InterfaceBuilder.IBCocoaTouchPlugin - + com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin @@ -1339,7 +1393,96 @@ 88 - + + + + WizardViewController + UIViewController + + id + id + id + id + id + id + + + + onBackClick: + id + + + onCancelClick: + id + + + onConnectAccountClick: + id + + + onCreateAccountClick: + id + + + onExternalAccountClick: + id + + + onStartClick: + id + + + + UIButton + UIView + UIView + UIView + UIView + UIView + UIButton + UIView + + + + backButton + UIButton + + + choiceView + UIView + + + connectAccountView + UIView + + + contentView + UIView + + + createAccountView + UIView + + + externalAccountView + UIView + + + startButton + UIButton + + + welcomeView + UIView + + + + IBProjectSource + ./Classes/WizardViewController.h + + + + 0 IBCocoaTouchFramework @@ -1353,11 +1496,14 @@ {550, 101} {640, 523} {320, 154} + {320, 154} {320, 154} {320, 154} + {320, 154} {320, 154} {542, 88} {320, 154} + {320, 154} {320, 154} {516, 48} {402, 258} diff --git a/NinePatch/NinePatch.h b/NinePatch/NinePatch.h new file mode 100755 index 000000000..29f638fc1 --- /dev/null +++ b/NinePatch/NinePatch.h @@ -0,0 +1,15 @@ +/* + * NinePatch.h + * NinePatch + * + * Copyright 2010 Tortuga 22, Inc. All rights reserved. + * + */ + +#import +#import + +#import "TUNinePatchProtocols.h" +#import "TUNinePatch.h" +#import "TUNinePatchCache.h" +#import "TUDebugLoggingAssistant.h" \ No newline at end of file diff --git a/NinePatch/NinePatch.xcodeproj/project.pbxproj b/NinePatch/NinePatch.xcodeproj/project.pbxproj new file mode 100755 index 000000000..b135d02b2 --- /dev/null +++ b/NinePatch/NinePatch.xcodeproj/project.pbxproj @@ -0,0 +1,461 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 324A2926111CC27800CC0BF0 /* TUNinePatchCachingCategories.h in Headers */ = {isa = PBXBuildFile; fileRef = 324A2924111CC27800CC0BF0 /* TUNinePatchCachingCategories.h */; }; + 324A2927111CC27800CC0BF0 /* TUNinePatchCachingCategories.m in Sources */ = {isa = PBXBuildFile; fileRef = 324A2925111CC27800CC0BF0 /* TUNinePatchCachingCategories.m */; }; + 324A2930111CC2CB00CC0BF0 /* TUCachingNinePatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 324A292E111CC2CB00CC0BF0 /* TUCachingNinePatch.h */; }; + 324A2931111CC2CB00CC0BF0 /* TUCachingNinePatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 324A292F111CC2CB00CC0BF0 /* TUCachingNinePatch.m */; }; + 324A2940111CE12C00CC0BF0 /* TUNinePatchCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 324A293E111CE12C00CC0BF0 /* TUNinePatchCache.h */; }; + 324A2941111CE12C00CC0BF0 /* TUNinePatchCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 324A293F111CE12C00CC0BF0 /* TUNinePatchCache.m */; }; + 325B80EF11B001C300B86EA1 /* TUDebugLoggingAssistant.h in Headers */ = {isa = PBXBuildFile; fileRef = 325B80ED11B001C300B86EA1 /* TUDebugLoggingAssistant.h */; }; + 325B80F011B001C300B86EA1 /* TUDebugLoggingAssistant.m in Sources */ = {isa = PBXBuildFile; fileRef = 325B80EE11B001C300B86EA1 /* TUDebugLoggingAssistant.m */; }; + 325B80F811B00C9800B86EA1 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 325B80F711B00C9800B86EA1 /* CoreFoundation.framework */; }; + 325B821F11B317E400B86EA1 /* NinePatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 325B821E11B317E400B86EA1 /* NinePatch.h */; }; + 32BBFEFF10E95B5700F57FBC /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32BBFEFE10E95B5700F57FBC /* CoreGraphics.framework */; }; + 32BBFF0610E95B6E00F57FBC /* TUNinePatchProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 32BBFF0510E95B6E00F57FBC /* TUNinePatchProtocols.h */; }; + 32BBFF2710E95BCF00F57FBC /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32BBFF2610E95BCF00F57FBC /* UIKit.framework */; }; + 32BBFF2C10E95BF900F57FBC /* TUNinePatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 32BBFF2A10E95BF900F57FBC /* TUNinePatch.h */; }; + 32BBFF2D10E95BF900F57FBC /* TUNinePatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 32BBFF2B10E95BF900F57FBC /* TUNinePatch.m */; }; + 32BBFF3510E95CA700F57FBC /* TUHorizontalNinePatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 32BBFF3310E95CA700F57FBC /* TUHorizontalNinePatch.h */; }; + 32BBFF3610E95CA700F57FBC /* TUHorizontalNinePatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 32BBFF3410E95CA700F57FBC /* TUHorizontalNinePatch.m */; }; + 32BBFF3910E95CD600F57FBC /* TUVerticalNinePatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 32BBFF3710E95CD600F57FBC /* TUVerticalNinePatch.h */; }; + 32BBFF3A10E95CD600F57FBC /* TUVerticalNinePatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 32BBFF3810E95CD600F57FBC /* TUVerticalNinePatch.m */; }; + 32BBFF3D10E95D3800F57FBC /* TUFullNinePatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 32BBFF3B10E95D3800F57FBC /* TUFullNinePatch.h */; }; + 32BBFF3E10E95D3800F57FBC /* TUFullNinePatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 32BBFF3C10E95D3800F57FBC /* TUFullNinePatch.m */; }; + 32BBFF4310E9605300F57FBC /* UIImage-TUNinePatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 32BBFF4110E9605300F57FBC /* UIImage-TUNinePatch.h */; }; + 32BBFF4410E9605300F57FBC /* UIImage-TUNinePatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 32BBFF4210E9605300F57FBC /* UIImage-TUNinePatch.m */; }; + AA747D9F0F9514B9006C5449 /* NinePatch_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = AA747D9E0F9514B9006C5449 /* NinePatch_Prefix.pch */; }; + AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AACBBE490F95108600F1A2B1 /* Foundation.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 324A2924111CC27800CC0BF0 /* TUNinePatchCachingCategories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TUNinePatchCachingCategories.h; sourceTree = ""; }; + 324A2925111CC27800CC0BF0 /* TUNinePatchCachingCategories.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TUNinePatchCachingCategories.m; sourceTree = ""; }; + 324A292E111CC2CB00CC0BF0 /* TUCachingNinePatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TUCachingNinePatch.h; sourceTree = ""; }; + 324A292F111CC2CB00CC0BF0 /* TUCachingNinePatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TUCachingNinePatch.m; sourceTree = ""; }; + 324A293E111CE12C00CC0BF0 /* TUNinePatchCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TUNinePatchCache.h; sourceTree = ""; }; + 324A293F111CE12C00CC0BF0 /* TUNinePatchCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TUNinePatchCache.m; sourceTree = ""; }; + 325B80ED11B001C300B86EA1 /* TUDebugLoggingAssistant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TUDebugLoggingAssistant.h; sourceTree = ""; }; + 325B80EE11B001C300B86EA1 /* TUDebugLoggingAssistant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TUDebugLoggingAssistant.m; sourceTree = ""; }; + 325B80F711B00C9800B86EA1 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 325B821E11B317E400B86EA1 /* NinePatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NinePatch.h; sourceTree = ""; }; + 32BBFEFE10E95B5700F57FBC /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 32BBFF0510E95B6E00F57FBC /* TUNinePatchProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TUNinePatchProtocols.h; sourceTree = ""; }; + 32BBFF2610E95BCF00F57FBC /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 32BBFF2A10E95BF900F57FBC /* TUNinePatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TUNinePatch.h; sourceTree = ""; }; + 32BBFF2B10E95BF900F57FBC /* TUNinePatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TUNinePatch.m; sourceTree = ""; }; + 32BBFF3310E95CA700F57FBC /* TUHorizontalNinePatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TUHorizontalNinePatch.h; sourceTree = ""; }; + 32BBFF3410E95CA700F57FBC /* TUHorizontalNinePatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TUHorizontalNinePatch.m; sourceTree = ""; }; + 32BBFF3710E95CD600F57FBC /* TUVerticalNinePatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TUVerticalNinePatch.h; sourceTree = ""; }; + 32BBFF3810E95CD600F57FBC /* TUVerticalNinePatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TUVerticalNinePatch.m; sourceTree = ""; }; + 32BBFF3B10E95D3800F57FBC /* TUFullNinePatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TUFullNinePatch.h; sourceTree = ""; }; + 32BBFF3C10E95D3800F57FBC /* TUFullNinePatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TUFullNinePatch.m; sourceTree = ""; }; + 32BBFF4110E9605300F57FBC /* UIImage-TUNinePatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage-TUNinePatch.h"; sourceTree = ""; }; + 32BBFF4210E9605300F57FBC /* UIImage-TUNinePatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage-TUNinePatch.m"; sourceTree = ""; }; + AA747D9E0F9514B9006C5449 /* NinePatch_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NinePatch_Prefix.pch; sourceTree = SOURCE_ROOT; }; + AACBBE490F95108600F1A2B1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + D2AAC07E0554694100DB518D /* libNinePatch.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libNinePatch.a; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D2AAC07C0554694100DB518D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */, + 32BBFEFF10E95B5700F57FBC /* CoreGraphics.framework in Frameworks */, + 32BBFF2710E95BCF00F57FBC /* UIKit.framework in Frameworks */, + 325B80F811B00C9800B86EA1 /* CoreFoundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034768DFFF38A50411DB9C8B /* Products */ = { + isa = PBXGroup; + children = ( + D2AAC07E0554694100DB518D /* libNinePatch.a */, + ); + name = Products; + sourceTree = ""; + }; + 0867D691FE84028FC02AAC07 /* NinePatch */ = { + isa = PBXGroup; + children = ( + 08FB77AEFE84172EC02AAC07 /* Classes */, + 32C88DFF0371C24200C91783 /* Other Sources */, + 0867D69AFE84028FC02AAC07 /* Frameworks */, + 034768DFFF38A50411DB9C8B /* Products */, + 32BBFF2610E95BCF00F57FBC /* UIKit.framework */, + 325B80F711B00C9800B86EA1 /* CoreFoundation.framework */, + 325B821E11B317E400B86EA1 /* NinePatch.h */, + ); + name = NinePatch; + sourceTree = ""; + }; + 0867D69AFE84028FC02AAC07 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 32BBFEFE10E95B5700F57FBC /* CoreGraphics.framework */, + AACBBE490F95108600F1A2B1 /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 08FB77AEFE84172EC02AAC07 /* Classes */ = { + isa = PBXGroup; + children = ( + 325B80EC11B0019B00B86EA1 /* Debugging Utilities */, + 324A292C111CC2C100CC0BF0 /* Caching */, + 32BBFF4010E9604300F57FBC /* Categories */, + 32BBFF3F10E9603E00F57FBC /* NinePatch */, + ); + name = Classes; + sourceTree = ""; + }; + 324A292C111CC2C100CC0BF0 /* Caching */ = { + isa = PBXGroup; + children = ( + 324A292E111CC2CB00CC0BF0 /* TUCachingNinePatch.h */, + 324A292F111CC2CB00CC0BF0 /* TUCachingNinePatch.m */, + 324A293E111CE12C00CC0BF0 /* TUNinePatchCache.h */, + 324A293F111CE12C00CC0BF0 /* TUNinePatchCache.m */, + ); + name = Caching; + sourceTree = ""; + }; + 325B80EC11B0019B00B86EA1 /* Debugging Utilities */ = { + isa = PBXGroup; + children = ( + 325B80ED11B001C300B86EA1 /* TUDebugLoggingAssistant.h */, + 325B80EE11B001C300B86EA1 /* TUDebugLoggingAssistant.m */, + ); + name = "Debugging Utilities"; + sourceTree = ""; + }; + 32BBFF3F10E9603E00F57FBC /* NinePatch */ = { + isa = PBXGroup; + children = ( + 32BBFF0510E95B6E00F57FBC /* TUNinePatchProtocols.h */, + 32BBFF2A10E95BF900F57FBC /* TUNinePatch.h */, + 32BBFF2B10E95BF900F57FBC /* TUNinePatch.m */, + 32BBFF3310E95CA700F57FBC /* TUHorizontalNinePatch.h */, + 32BBFF3410E95CA700F57FBC /* TUHorizontalNinePatch.m */, + 32BBFF3710E95CD600F57FBC /* TUVerticalNinePatch.h */, + 32BBFF3810E95CD600F57FBC /* TUVerticalNinePatch.m */, + 32BBFF3B10E95D3800F57FBC /* TUFullNinePatch.h */, + 32BBFF3C10E95D3800F57FBC /* TUFullNinePatch.m */, + ); + name = NinePatch; + sourceTree = ""; + }; + 32BBFF4010E9604300F57FBC /* Categories */ = { + isa = PBXGroup; + children = ( + 32BBFF4110E9605300F57FBC /* UIImage-TUNinePatch.h */, + 32BBFF4210E9605300F57FBC /* UIImage-TUNinePatch.m */, + 324A2924111CC27800CC0BF0 /* TUNinePatchCachingCategories.h */, + 324A2925111CC27800CC0BF0 /* TUNinePatchCachingCategories.m */, + ); + name = Categories; + sourceTree = ""; + }; + 32C88DFF0371C24200C91783 /* Other Sources */ = { + isa = PBXGroup; + children = ( + AA747D9E0F9514B9006C5449 /* NinePatch_Prefix.pch */, + ); + name = "Other Sources"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D2AAC07A0554694100DB518D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + AA747D9F0F9514B9006C5449 /* NinePatch_Prefix.pch in Headers */, + 32BBFF0610E95B6E00F57FBC /* TUNinePatchProtocols.h in Headers */, + 32BBFF2C10E95BF900F57FBC /* TUNinePatch.h in Headers */, + 32BBFF3510E95CA700F57FBC /* TUHorizontalNinePatch.h in Headers */, + 32BBFF3910E95CD600F57FBC /* TUVerticalNinePatch.h in Headers */, + 32BBFF3D10E95D3800F57FBC /* TUFullNinePatch.h in Headers */, + 32BBFF4310E9605300F57FBC /* UIImage-TUNinePatch.h in Headers */, + 324A2926111CC27800CC0BF0 /* TUNinePatchCachingCategories.h in Headers */, + 324A2930111CC2CB00CC0BF0 /* TUCachingNinePatch.h in Headers */, + 324A2940111CE12C00CC0BF0 /* TUNinePatchCache.h in Headers */, + 325B80EF11B001C300B86EA1 /* TUDebugLoggingAssistant.h in Headers */, + 325B821F11B317E400B86EA1 /* NinePatch.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D2AAC07D0554694100DB518D /* NinePatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "NinePatch" */; + buildPhases = ( + D2AAC07A0554694100DB518D /* Headers */, + D2AAC07B0554694100DB518D /* Sources */, + D2AAC07C0554694100DB518D /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NinePatch; + productName = NinePatch; + productReference = D2AAC07E0554694100DB518D /* libNinePatch.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0430; + }; + buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "NinePatch" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + ); + mainGroup = 0867D691FE84028FC02AAC07 /* NinePatch */; + productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D2AAC07D0554694100DB518D /* NinePatch */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + D2AAC07B0554694100DB518D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 32BBFF2D10E95BF900F57FBC /* TUNinePatch.m in Sources */, + 32BBFF3610E95CA700F57FBC /* TUHorizontalNinePatch.m in Sources */, + 32BBFF3A10E95CD600F57FBC /* TUVerticalNinePatch.m in Sources */, + 32BBFF3E10E95D3800F57FBC /* TUFullNinePatch.m in Sources */, + 32BBFF4410E9605300F57FBC /* UIImage-TUNinePatch.m in Sources */, + 324A2927111CC27800CC0BF0 /* TUNinePatchCachingCategories.m in Sources */, + 324A2931111CC2CB00CC0BF0 /* TUCachingNinePatch.m in Sources */, + 324A2941111CE12C00CC0BF0 /* TUNinePatchCache.m in Sources */, + 325B80F011B001C300B86EA1 /* TUDebugLoggingAssistant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB921F08733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + armv6, + "$(ARCHS_STANDARD_32_BIT)", + ); + COPY_PHASE_STRIP = NO; + DSTROOT = /tmp/NinePatch.dst; + GCC_DYNAMIC_NO_PIC = NO; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = NinePatch_Prefix.pch; + GCC_THUMB_SUPPORT = NO; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + INSTALL_PATH = /usr/local/lib; + IPHONEOS_DEPLOYMENT_TARGET = 4.0; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_NAME = NinePatch; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1DEB922008733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + armv6, + "$(ARCHS_STANDARD_32_BIT)", + ); + COPY_PHASE_STRIP = NO; + DSTROOT = /tmp/NinePatch.dst; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = NinePatch_Prefix.pch; + GCC_THUMB_SUPPORT = NO; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + INSTALL_PATH = /usr/local/lib; + IPHONEOS_DEPLOYMENT_TARGET = 4.0; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_NAME = NinePatch; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 1DEB922308733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CODE_SIGN_IDENTITY = "Don't Code Sign"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + OTHER_LDFLAGS = "-ObjC"; + PROVISIONING_PROFILE = ""; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 1DEB922408733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CODE_SIGN_IDENTITY = "Don't Code Sign"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = "-ObjC"; + PROVISIONING_PROFILE = ""; + SDKROOT = iphoneos; + }; + name = Release; + }; + D3D14E7E15A72BD10074A527 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CODE_SIGN_IDENTITY = "Don't Code Sign"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = "-ObjC"; + PROVISIONING_PROFILE = ""; + SDKROOT = iphoneos; + }; + name = Distribution; + }; + D3D14E7F15A72BD10074A527 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + armv6, + "$(ARCHS_STANDARD_32_BIT)", + ); + COPY_PHASE_STRIP = NO; + DSTROOT = /tmp/NinePatch.dst; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = NinePatch_Prefix.pch; + GCC_THUMB_SUPPORT = NO; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + INSTALL_PATH = /usr/local/lib; + IPHONEOS_DEPLOYMENT_TARGET = 4.0; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_NAME = NinePatch; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Distribution; + }; + D3D14E8015A72BD70074A527 /* DistributionAdhoc */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CODE_SIGN_IDENTITY = "Don't Code Sign"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = "-ObjC"; + PROVISIONING_PROFILE = ""; + SDKROOT = iphoneos; + }; + name = DistributionAdhoc; + }; + D3D14E8115A72BD70074A527 /* DistributionAdhoc */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + armv6, + "$(ARCHS_STANDARD_32_BIT)", + ); + COPY_PHASE_STRIP = NO; + DSTROOT = /tmp/NinePatch.dst; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = NinePatch_Prefix.pch; + GCC_THUMB_SUPPORT = NO; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + INSTALL_PATH = /usr/local/lib; + IPHONEOS_DEPLOYMENT_TARGET = 4.0; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_NAME = NinePatch; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DistributionAdhoc; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "NinePatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB921F08733DC00010E9CD /* Debug */, + 1DEB922008733DC00010E9CD /* Release */, + D3D14E7F15A72BD10074A527 /* Distribution */, + D3D14E8115A72BD70074A527 /* DistributionAdhoc */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "NinePatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB922308733DC00010E9CD /* Debug */, + 1DEB922408733DC00010E9CD /* Release */, + D3D14E7E15A72BD10074A527 /* Distribution */, + D3D14E8015A72BD70074A527 /* DistributionAdhoc */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/NinePatch/NinePatch_Prefix.pch b/NinePatch/NinePatch_Prefix.pch new file mode 100755 index 000000000..2b1046d32 --- /dev/null +++ b/NinePatch/NinePatch_Prefix.pch @@ -0,0 +1,157 @@ +// +// Prefix header for all source files of the 'CocoaTouchStaticLibrary' target in the 'CocoaTouchStaticLibrary' project. +// + +#ifdef __OBJC__ + #import +#endif +/** + This struct is used for doing pixel-tasting. We get CoreGraphics to create a bitmap context wherein the memory representation looks like this struct, then we cast the pointer to that memory to be of this struct's type. Pretty self-explanatory. + */ +typedef struct _TURGBAPixel { + UInt8 red; + UInt8 green; + UInt8 blue; + UInt8 alpha; +} TURGBAPixel; + +/** + Defined here, used as part of the pixel-tasting code. Helps make sure the memory representation of the bitmap context is made up of stuff that looks just like TURGBAPixel. + */ +#define TURGBABytesPerPixel (4) + +/** + This tests if a pixel is black. Here "black" means alpha isn't at zero (AKA: it's at least partially opaque) and r == g == b == 0. + */ +#define TURGBAPixelIsBlack(PIXEL) (((PIXEL.red == 0) && (PIXEL.green == 0) && (PIXEL.blue == 0) && (PIXEL.alpha != 0))?(YES):(NO)) + +#define TUNotFoundRange (NSMakeRange(NSNotFound,0)) +#define TUIsNotFoundRange(RANGE) (NSEqualRanges(RANGE, TUNotFoundRange)) + +#define TUTruncateBelow(VALUE, FLOOR) ((( VALUE ) < ( FLOOR ))?(( FLOOR )):(( VALUE ))) +#define TUTruncateAbove(VALUE, CEILING) ((( VALUE ) > ( CEILING ))?(( CEILING )):(( VALUE ))) +#define TUTruncateWithin(VALUE, FLOOR, CEILING) ((( VALUE ) < ( FLOOR ))?(( FLOOR )):((( VALUE ) > ( CEILING ))?(( CEILING )):(( VALUE )))) +#define TUTruncateAtZero(VALUE) TUTruncateBelow(VALUE, 0.0f) + +#define TUForceYesOrNo(ABOOL) ((ABOOL)?(YES):(NO)) +#define TUYesOrNoString(ABOOL) ((( ABOOL ))?(@"YES"):(@"NO")) + +#define TUWithinEpsilon(EPSILON, X, Y) TUForceYesOrNo((((X-Y) > (-1.0f * EPSILON)) || ((X-Y) < EPSILON))) + +//#define DEBUG +//#define NP_ASSERTION_CHECKING +//#define IMAGEDEBUG + +// DLog is almost a drop-in replacement for NSLog +// DLog(); +// DLog(@"here"); +// DLog(@"value: %d", x); +// Unfortunately this doesn't work DLog(aStringVariable); you have to do this instead DLog(@"%@", aStringVariable); +#ifdef DEBUG +#define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); +#else +#define DLog(...) +#endif + +// ALog always displays output regardless of the DEBUG setting +#define ALog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); + +#define LLog(STR) DLog(@"%@",STR) + +#define NPLogException(E) DLog(@"Caught '%@' < '%@', '%@' >.",[E name],[E reason],[E userInfo]) +#define NPLogError(E) DLog(@"Error: '%@', '%@', '%@'.",[E localizedDescription],[E localizedFailureReason],[E localizedRecoveryOptions]); + +#ifdef NP_ASSERTION_CHECKING +#define NPLogExceptionRethrowIfAssertionFailure(E) { \ +NPLogException(E); \ +if (E && [[E name] isEqualToString:NSInternalInconsistencyException]) { \ + @throw E; \ +}} +#else +#define NPLogExceptionRethrowIfAssertionFailure(E) NPLogException(E) +#endif + +#ifdef NP_OUTPUT_LOGGING +#define NPFOutputLog(AFLOAT) DLog(@"returning %s: '%f'.",#AFLOAT,AFLOAT) +#define NPDOutputLog(ANINT) DLog(@"returning %s: '%d'.",#ANINT,ANINT) +#define NPOOutputLog(ANOBJ) DLog(@"returning %s: '%@'.",#ANOBJ,ANOBJ) +#define NPBOutputLog(ABOOL) DLog(@"returning %s: '%@'.",#ABOOL,TUYesOrNoString(ABOOL)) +#define NPCGROutputLog(ARECT) DLog(@"returning %s: '%@'.",#ARECT,NSStringFromCGRect(ARECT)) +#define NPCGSOutputLog(ASIZE) DLog(@"returning %s: '%@'.",#ASIZE,NSStringFromCGSize(ASIZE)) +#define NPCGPOutputLog(APOINT) DLog(@"returning %s: '%@'.",#APOINT,NSStringFromCGPoint(APOINT)) +#define NPNSROutputLog(ARANGE) DLog(@"returning %s: '%@'.",#ARANGE,NSStringFromRange(ARANGE)) +#else +#define NPFOutputLog(...) +#define NPDOutputLog(...) +#define NPOOutputLog(...) +#define NPBOutputLog(...) +#define NPCGROutputLog(...) +#define NPCGSOutputLog(...) +#define NPCGPOutputLog(...) +#define NPNSROutputLog(...) +#endif + +#ifdef NP_INPUT_LOGGING +#define NPAInputLog(...) DLog(##__VA_ARGS__) +// convenience input loggers for single-argument messages +#define NPAFInputLog(AFLOAT) DLog(@"%s: '%f'",#AFLOAT,AFLOAT) +#define NPADInputLog(ANINT) DLog(@"%s: '%d'",#ANINT,ANINT) +#define NPAOInputLog(ANOBJ) DLog(@"%s: '%@'",#ANOBJ,ANOBJ) +#define NPABInputLog(ABOOL) DLog(@"%s: '%@'",#ABOOL,TUYesOrNoString(ABOOL)) +#else +#define NPAInputLog(...) +#define NPAFInputLog(AFLOAT) +#define NPADInputLog(ANINT) +#define NPAOInputLog(ANOBJ) +#define NPABInputLog(ABOOL) +#endif + + +#ifdef NP_ASSERTION_CHECKING +#define NPParameterAssert(COND) NSParameterAssert(COND) +#define NPCParameterAssert(COND) NSCParameterAssert(COND) +#define NPAssert(COND,DESC) NSAssert(COND,DESC) +#define NPCAssert(COND,DESC) NSCAssert(COND,DESC) +#else +#define NPParameterAssert(...) +#define NPCParameterAssert(...) +#define NPAssert(...) +#define NPCAssert(...) +#endif + +#define STRINGIFY2( x) #x +#define STRINGIFY(x) STRINGIFY2(x) +#define PASTE2( a, b) a##b +#define PASTE( a, b) PASTE2( a, b) +#define PASSTHROUGH(X) X + +#define NPOBJCStringOfToken(TOKEN) PASSTHROUGH(PASTE( PASSTHROUGH(@), PASSTHROUGH(STRINGIFY(TOKEN)))) + +#define NPSelfProperty(PROP) +//#define NPSelfProperty(PROP) (self.PROP) +//#define NPSelfProperty(PROP) ([self PROP]) + +#define NPAssertPropertyNonNil(PROP) NPAssert((NPSelfProperty(PROP) != nil), ([NSString stringWithFormat:@"self.%s should never be nil.",( (#PROP) )])) + +#define NPParameterAssertNotNilConformsToProtocol(OBJ,PROT) NPParameterAssert((OBJ != nil) && ([OBJ conformsToProtocol:@protocol(PROT)])) +#define NPParameterAssertNotNilIsKindOfClass(OBJ,CLASS) NPParameterAssert((OBJ != nil) && ([OBJ isKindOfClass:[CLASS class]])) + +#define NPAssertNilOrConformsToProtocol(OBJ,PROT) NPAssert(((OBJ == nil) || ((OBJ != nil) && [OBJ conformsToProtocol:@protocol(PROT)])),([NSString stringWithFormat:@"Variable %s must either be nil or conform to %s protocol.", ( (#OBJ) ), ( (#PROT) )])) +#define NPAssertNilOrIsKindOfClass(OBJ,CLASS) NPAssert(((OBJ == nil) || ((OBJ != nil) && [OBJ isKindOfClass:[CLASS class]])), ([NSString stringWithFormat:@"Variable %s must either be nil or be kind of %s class.", (#OBJ), (#CLASS)])) + +#define NPAssertWithinEpsilon(EPSILON,X,Y) NPAssert( (((X-Y) > (-1.0f * EPSILON)) || ((X-Y) < EPSILON)),([NSString stringWithFormat:@"Should have (%s,%s) within %f but instead (%f,%f).",#X,#Y,EPSILON,X,Y])) +#define NPAssertWithinOne(X,Y) NPAssertWithinEpsilon(1.0f,X,Y) + +#define NPAssertThreeSubSizesSumCorrectlyOnOneAxis(AXIS,MASTERSIZE,SIZE_ONE,SIZE_TWO,SIZE_THREE) NPAssertWithinOne(MASTERSIZE.AXIS,( SIZE_ONE.AXIS + SIZE_TWO.AXIS + SIZE_THREE.AXIS )) +#define NPAssertCorrectSubsizeWidthDecomposition(MASTER,SIZE_ONE,SIZE_TWO,SIZE_THREE) NPAssertThreeSubSizesSumCorrectlyOnOneAxis(width, MASTER, SIZE_ONE, SIZE_TWO, SIZE_THREE) +#define NPAssertCorrectSubsizeHeightDecomposition(MASTER,SIZE_ONE,SIZE_TWO,SIZE_THREE) NPAssertThreeSubSizesSumCorrectlyOnOneAxis(height, MASTER, SIZE_ONE, SIZE_TWO, SIZE_THREE) + +#define NPAssertCorrectSubimageWidthDecomposition(MASTER,IMAGE_ONE,IMAGE_TWO,IMAGE_THREE) NPAssertCorrectSubsizeWidthDecomposition([MASTER size],[IMAGE_ONE size],[IMAGE_TWO size],[IMAGE_THREE size]) +#define NPAssertCorrectSubimageHeightDecomposition(MASTER,IMAGE_ONE,IMAGE_TWO,IMAGE_THREE) NPAssertCorrectSubsizeWidthDecomposition([MASTER size],[IMAGE_ONE size],[IMAGE_TWO size],[IMAGE_THREE size]) + +#ifdef IMAGEDEBUG +#define IMLog(IMAGE, IMAGENAME) TUImageLog(IMAGE,[[NSString stringWithFormat:@"debugImage.%.0f.%u.",[NSDate timeIntervalSinceReferenceDate],((NSUInteger) rand())] stringByAppendingString:( IMAGENAME )]) +#else +#define IMLog(IMAGE, IMAGENAME) +#endif + diff --git a/NinePatch/TUCachingNinePatch.h b/NinePatch/TUCachingNinePatch.h new file mode 100755 index 000000000..6d1a1a99d --- /dev/null +++ b/NinePatch/TUCachingNinePatch.h @@ -0,0 +1,51 @@ +// +// TUCachingNinePatch.h +// NinePatch +// +// Copyright 2010 Tortuga 22, Inc. All rights reserved. +// + +#import +#import +#import "TUNinePatchProtocols.h" + +@interface TUCachingNinePatch : NSObject < NSCoding, NSCopying > { + id < TUNinePatch > _ninePatch; + NSMutableDictionary *_ninePatchImageCache; +} + +// Synthesized Properties +@property(nonatomic, retain, readonly) id < TUNinePatch > ninePatch; +@property(nonatomic, retain, readonly) NSMutableDictionary *ninePatchImageCache; + +// Init + Dealloc +-(id)initWithNinePatchNamed:(NSString *)ninePatchName; +-(id)initWithNinePatch:(id < TUNinePatch >)ninePatch; ++(id)ninePatchCacheWithNinePatchNamed:(NSString *)ninePatchName; ++(id)ninePatchCacheWithNinePatch:(id < TUNinePatch >)ninePatch; +-(void)dealloc; + +// NSCoding +-(id)initWithCoder:(NSCoder *)aDecoder; +-(void)encodeWithCoder:(NSCoder *)anEncoder; + +// NSCopying +-(id)copyWithZone:(NSZone *)zone; + +// Nib +-(void)awakeFromNib; + +// Cache Management +-(void)flushCachedImages; + +// Image Access - Utility Accessors +-(UIImage *)imageOfSize:(CGSize)size; + +// Cache Access +-(void)cacheImage:(UIImage *)image ofSize:(CGSize)size; +-(UIImage *)cachedImageOfSize:(CGSize)size; + +// Image Construction +-(UIImage *)constructImageOfSize:(CGSize)size; + +@end diff --git a/NinePatch/TUCachingNinePatch.m b/NinePatch/TUCachingNinePatch.m new file mode 100755 index 000000000..34c3bef3d --- /dev/null +++ b/NinePatch/TUCachingNinePatch.m @@ -0,0 +1,125 @@ +// +// TUCachingNinePatch.m +// NinePatch +// +// Copyright 2010 Tortuga 22, Inc. All rights reserved. +// + +#import "TUCachingNinePatch.h" +#import "TUNinePatch.h" +#import "TUNinePatchCachingCategories.h" + +@interface TUCachingNinePatch () + +// Synthesized Properties +@property(nonatomic, retain, readwrite) id < TUNinePatch > ninePatch; +@property(nonatomic, retain, readwrite) NSMutableDictionary *ninePatchImageCache; + +@end + + +@implementation TUCachingNinePatch + +#pragma mark Synthesized Properties +@synthesize ninePatch = _ninePatch; +@synthesize ninePatchImageCache = _ninePatchImageCache; + +#pragma mark Init + Dealloc +-(id)initWithNinePatchNamed:(NSString *)ninePatchName { + return [self initWithNinePatch:[TUNinePatch ninePatchNamed:ninePatchName]]; +} + +-(id)initWithNinePatch:(id < TUNinePatch >)ninePatch { + if (self = [super init]) { + self.ninePatch = ninePatch; + self.ninePatchImageCache = [NSMutableDictionary dictionaryWithCapacity:5]; + } + return self; +} + +#pragma mark - ++(id)ninePatchCacheWithNinePatchNamed:(NSString *)ninePatchName { + return [[[self alloc] initWithNinePatchNamed:ninePatchName] autorelease]; +} + ++(id)ninePatchCacheWithNinePatch:(id < TUNinePatch >)ninePatch { + return [[[self alloc] initWithNinePatch:ninePatch] autorelease]; +} + +#pragma mark - +-(void)dealloc { + self.ninePatch = nil; + self.ninePatchImageCache = nil; + [super dealloc]; +} + +#pragma mark NSCoding +-(id)initWithCoder:(NSCoder *)aDecoder { + NSParameterAssert(aDecoder != nil); + if (self = [super init]) { + self.ninePatch = [aDecoder decodeObjectForKey:@"ninePatch"]; + self.ninePatchImageCache = [aDecoder decodeObjectForKey:@"ninePatchImageCache"]; + } + return self; +} + +-(void)encodeWithCoder:(NSCoder *)anEncoder { + NSParameterAssert(anEncoder != nil); + [anEncoder encodeObject:self.ninePatch + forKey:@"ninePatch"]; + [anEncoder encodeObject:self.ninePatchImageCache + forKey:@"ninePatchImageCache"]; +} + +#pragma mark NSCopying +-(id)copyWithZone:(NSZone *)zone { + return [[[self class] alloc] initWithNinePatch:self.ninePatch]; +} + +#pragma mark Nib +-(void)awakeFromNib { + [super awakeFromNib]; + if (!self.ninePatchImageCache) { + self.ninePatchImageCache = [NSMutableDictionary dictionaryWithCapacity:5]; + }; +} + +#pragma mark Cache Management +-(void)flushCachedImages { + NPAssertPropertyNonNil(ninePatchImageCache); + [self.ninePatchImageCache removeAllObjects]; +} + +#pragma mark Image Access - Utility Accessors +-(UIImage *)imageOfSize:(CGSize)size { + UIImage *imageOfSize = [self cachedImageOfSize:size]; + if (!imageOfSize) { + imageOfSize = [self constructImageOfSize:size]; + if (imageOfSize) { + [self cacheImage:imageOfSize + ofSize:size]; + } + } + return imageOfSize; +} + +#pragma mark Cache Access +-(void)cacheImage:(UIImage *)image ofSize:(CGSize)size { + NPParameterAssertNotNilIsKindOfClass(image,UIImage); + NPAssertPropertyNonNil(self.ninePatchImageCache); + return [self.ninePatchImageCache setObject:image + forSize:size]; +} + +-(UIImage *)cachedImageOfSize:(CGSize)size { + NPAssertPropertyNonNil(ninePatchImageCache); + return [self.ninePatchImageCache objectForSize:size]; +} + +#pragma mark Image Construction +-(UIImage *)constructImageOfSize:(CGSize)size { + NPAssertPropertyNonNil(ninePatch); + return (!self.ninePatch)?(nil):([self.ninePatch imageOfSize:size]); +} + +@end diff --git a/NinePatch/TUDebugLoggingAssistant.h b/NinePatch/TUDebugLoggingAssistant.h new file mode 100755 index 000000000..7c6f04f68 --- /dev/null +++ b/NinePatch/TUDebugLoggingAssistant.h @@ -0,0 +1,56 @@ +// +// TUDebugLoggingAssistant.h +// NinePatch +// +// Copyright 2010 Tortuga 22, Inc. All rights reserved. +// + +#import +#import + + +@interface TUDebugLoggingAssistant : NSObject { + NSString *_sessionIdentifier; + NSMutableSet *_activatedLoggingTags; +} + +// Synthesized Properties +@property(nonatomic, retain, readonly) NSString *sessionIdentifier; +@property(nonatomic, retain, readonly) NSMutableSet *activatedLoggingTags; + +// Init + Dealloc +-(id)init; +-(void)dealloc; + +// Managing Logging +-(void)startLoggingTag:(NSString *)loggingTag; +-(void)stopLoggingTag:(NSString *)loggingTag; +-(void)startLoggingTags:(NSString *)loggingTag,...; +-(void)stopLoggingTags:(NSString *)loggingTag,...; +-(void)startLoggingTagsFromArray:(NSArray *)loggingTags; +-(void)stopLoggingTagsFromArray:(NSArray *)loggingTags; + +// Formatting +-(NSString *)formattedImageLogFilenameForTimestamp:(NSTimeInterval)timestamp specifiedFileName:(NSString *)specifiedFileName; + +// Log-Checking +-(BOOL)shouldLogLineWithTag:(NSString *)tag; + +// Singleton Access ++(id)shared; + +// Managing Logging ++(void)startLoggingTag:(NSString *)loggingTag; ++(void)stopLoggingTag:(NSString *)loggingTag; ++(void)startLoggingTags:(NSString *)loggingTag,...; ++(void)stopLoggingTags:(NSString *)loggingTag,...; ++(void)startLoggingTagsFromArray:(NSArray *)loggingTags; ++(void)stopLoggingTagsFromArray:(NSArray *)loggingTags; + +// Formatting ++(NSString *)formattedImageLogFilenameForTimestamp:(NSTimeInterval)timestamp specifiedFileName:(NSString *)specifiedFileName; + +// Log-Checking ++(BOOL)shouldLogLineWithTag:(NSString *)tag; + +@end diff --git a/NinePatch/TUDebugLoggingAssistant.m b/NinePatch/TUDebugLoggingAssistant.m new file mode 100755 index 000000000..94e12afcc --- /dev/null +++ b/NinePatch/TUDebugLoggingAssistant.m @@ -0,0 +1,362 @@ +// +// TUDebugLoggingAssistant.m +// NinePatch +// +// Copyright 2010 Tortuga 22, Inc. All rights reserved. +// + +#import "TUDebugLoggingAssistant.h" + +@interface TUDebugLoggingAssistant () + +@property(nonatomic, retain, readwrite) NSString *sessionIdentifier; +@property(nonatomic, retain, readwrite) NSMutableSet *activatedLoggingTags; + +@end + + +@implementation TUDebugLoggingAssistant + +#pragma mark Synthesized Properties +@synthesize sessionIdentifier = _sessionIdentifier; +@synthesize activatedLoggingTags = _activatedLoggingTags; + +#pragma mark Init + Dealloc +-(id)init { + if (self = [super init]) { + self.activatedLoggingTags = [NSMutableSet set]; + NSString *uuidString = @"UUID_ERROR_ENCOUNTERED"; + @try { + CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); + if (uuid) { + @try { + uuidString = ((NSString *) CFUUIDCreateString(kCFAllocatorDefault, uuid)); + } + @catch (NSException * e) { + NPLogException(e); + if (uuidString) { + @try { + CFRelease(uuidString); + } + @catch (NSException * ee) { + NPLogException(ee); + } + @finally { + uuidString = @"UUID_ERROR_ENCOUNTERED"; + } + } + } + @finally { + @try { + CFRelease(uuid); + } + @catch (NSException * e) { + NPLogException(e); + } + } + } + } + @catch (NSException * e) { + NPLogException(e); + } + @finally { + if (uuidString) { + self.sessionIdentifier = uuidString; + [self.sessionIdentifier release]; // drops retain count back to 1 + } + } + } + return self; +} + +#pragma mark - +-(void)dealloc { + self.sessionIdentifier = nil; + self.activatedLoggingTags = nil; + [super dealloc]; +} + +#pragma mark Managing Logging +-(void)startLoggingTag:(NSString *)loggingTag { + NPAOInputLog(loggingTag); + NPParameterAssertNotNilIsKindOfClass(loggingTag,NSString); + NPAssertPropertyNonNil(activatedLoggingTags); + if (loggingTag) { + @try { + [self.activatedLoggingTags addObject:loggingTag]; + } + @catch (NSException * e) { + NPLogException(e); + } + } +} + +-(void)stopLoggingTag:(NSString *)loggingTag { + NPAOInputLog(loggingTag); + NPParameterAssertNotNilIsKindOfClass(loggingTag,NSString); + NPAssertPropertyNonNil(activatedLoggingTags); + if (loggingTag) { + @try { + [self.activatedLoggingTags removeObject:loggingTag]; + } + @catch (NSException * e) { + NPLogException(e); + } + } +} + +-(void)startLoggingTags:(NSString *)loggingTag,... { + id eachObject; + va_list argumentList; + if (loggingTag) { + NSMutableArray *workingArray = [NSMutableArray arrayWithObject:loggingTag]; + va_start(argumentList, loggingTag); + @try { + while ((eachObject = va_arg(argumentList, id))) { + @try { + [workingArray addObject:eachObject]; + } + @catch (NSException * e) { + NPLogException(e); + } + } + } + @catch (NSException * e) { + NPLogException(e); + } + @finally { + @try { + va_end(argumentList); + } + @catch (NSException * e) { + NPLogException(e); + } + @finally { + if (workingArray) { + [self startLoggingTagsFromArray:workingArray]; + } + } + } + } +} + +-(void)stopLoggingTags:(NSString *)loggingTag,... { + id eachObject; + va_list argumentList; + if (loggingTag) { + NSMutableArray *workingArray = [NSMutableArray arrayWithObject:loggingTag]; + va_start(argumentList, loggingTag); + @try { + while ((eachObject = va_arg(argumentList, id))) { + @try { + [workingArray addObject:eachObject]; + } + @catch (NSException * e) { + NPLogException(e); + } + } + } + @catch (NSException * e) { + NPLogException(e); + } + @finally { + @try { + va_end(argumentList); + } + @catch (NSException * e) { + NPLogException(e); + } + @finally { + if (workingArray) { + [self stopLoggingTagsFromArray:workingArray]; + } + } + } + } +} + +-(void)startLoggingTagsFromArray:(NSArray *)loggingTags { + NPAOInputLog(loggingTags); + NPParameterAssertNotNilIsKindOfClass(loggingTags,NSArray); + NPAssertPropertyNonNil(activatedLoggingTags); + if (loggingTags) { + // N.B.: we're not shooting for speed here + // b/c if you're even instantiating an instance of this + // class it means you're in a debug frenzy and have other + // issues. + // + // So here we break out the for loop and insert objects + // individually so that the asserts in the [self startLoggingTag:tag] + // will get noticed if you're smuggling non-string logging tags in + // by passing them in as part of an array. + // + // No point having type-induced errors in your debugging assistant. + for (NSString *loggingTag in loggingTags) { + [self startLoggingTag:loggingTag]; + } + } +} + +-(void)stopLoggingTagsFromArray:(NSArray *)loggingTags { + NPAOInputLog(loggingTags); + NPParameterAssertNotNilIsKindOfClass(loggingTags,NSArray); + NPAssertPropertyNonNil(activatedLoggingTags); + if (loggingTags) { + // CF comment in startLoggingTagsFromArray + // for some context here + for (NSString *loggingTag in loggingTags) { + [self stopLoggingTag:loggingTag]; + } + } +} + +#pragma mark Formatting +-(NSString *)formattedImageLogFilenameForTimestamp:(NSTimeInterval)timestamp specifiedFileName:(NSString *)specifiedFileName { + NPAInputLog(@"formattedImageLogFilenameForTimestamp:'%f' specifiedFileName:'%@'",timestamp,specifiedFileName); + NPParameterAssertNotNilIsKindOfClass(specifiedFileName,NSString); + NPAssertPropertyNonNil(sessionIdentifier); + NSString *outString = nil; + @try { + if (specifiedFileName) { + outString = [NSString stringWithFormat:@"%@_%@_%.0f.png",self.sessionIdentifier,specifiedFileName,timestamp]; + } else { + outString = [NSString stringWithFormat:@"%@_%.0f.png",self.sessionIdentifier,timestamp]; + } + } + @catch (NSException * e) { + NPLogException(e); + } + NPOOutputLog(outString); + return outString; +} + +#pragma mark Log-Checking +-(BOOL)shouldLogLineWithTag:(NSString *)tag { + NPAInputLog(@"shouldLogLineWithTag:'%@'",tag); + NPParameterAssertNotNilIsKindOfClass(tag,NSString); + NPAssertPropertyNonNil(activatedLoggingTags); + BOOL shouldLogLineWithTag = NO; + if (tag) { + @try { + shouldLogLineWithTag = [self.activatedLoggingTags containsObject:tag]; + } + @catch (NSException * e) { + NPLogException(e); + shouldLogLineWithTag = NO; + } + } + NPBOutputLog(shouldLogLineWithTag); + return shouldLogLineWithTag; +} + +#pragma mark Singleton Access ++(id)shared { + static id shared; + if (!shared) { + shared = [[[self class] alloc] init]; + } + NPAssertNilOrIsKindOfClass(shared,TUDebugLoggingAssistant); + return shared; +} + +#pragma mark Managing Logging ++(void)startLoggingTag:(NSString *)loggingTag { + [[self shared] startLoggingTag:loggingTag]; +} + ++(void)stopLoggingTag:(NSString *)loggingTag { + [[self shared] stopLoggingTag:loggingTag]; +} + +#pragma mark - ++(void)startLoggingTags:(NSString *)loggingTag,... { + id eachObject; + va_list argumentList; + if (loggingTag) { + NSMutableArray *workingArray = [NSMutableArray arrayWithObject:loggingTag]; + va_start(argumentList, loggingTag); + @try { + while ((eachObject = va_arg(argumentList, id))) { + @try { + [workingArray addObject:eachObject]; + } + @catch (NSException * e) { + NPLogException(e); + } + } + } + @catch (NSException * e) { + NPLogException(e); + } + @finally { + @try { + va_end(argumentList); + } + @catch (NSException * e) { + NPLogException(e); + } + @finally { + if (workingArray) { + [self startLoggingTagsFromArray:workingArray]; + } + } + } + } +} + ++(void)stopLoggingTags:(NSString *)loggingTag,... { + id eachObject; + va_list argumentList; + if (loggingTag) { + NSMutableArray *workingArray = [NSMutableArray arrayWithObject:loggingTag]; + va_start(argumentList, loggingTag); + @try { + while ((eachObject = va_arg(argumentList, id))) { + @try { + [workingArray addObject:eachObject]; + } + @catch (NSException * e) { + NPLogException(e); + } + } + } + @catch (NSException * e) { + NPLogException(e); + } + @finally { + @try { + va_end(argumentList); + } + @catch (NSException * e) { + NPLogException(e); + } + @finally { + if (workingArray) { + [self stopLoggingTagsFromArray:workingArray]; + } + } + } + } +} + +#pragma mark - ++(void)startLoggingTagsFromArray:(NSArray *)loggingTags { + [[self shared] startLoggingTagsFromArray:loggingTags]; +} + ++(void)stopLoggingTagsFromArray:(NSArray *)loggingTags { + [[self shared] stopLoggingTagsFromArray:loggingTags]; +} + +#pragma mark Formatting ++(NSString *)formattedImageLogFilenameForTimestamp:(NSTimeInterval)timestamp specifiedFileName:(NSString *)specifiedFileName { + return [[self shared] formattedImageLogFilenameForTimestamp:timestamp + specifiedFileName:specifiedFileName]; +} + +#pragma mark Log-Checking ++(BOOL)shouldLogLineWithTag:(NSString *)tag { + return [[self shared] shouldLogLineWithTag:tag]; +} + +@end diff --git a/NinePatch/TUFullNinePatch.h b/NinePatch/TUFullNinePatch.h new file mode 100755 index 000000000..261b51ec2 --- /dev/null +++ b/NinePatch/TUFullNinePatch.h @@ -0,0 +1,56 @@ +// +// TUFullNinePatch.h +// NinePatch +// +// Copyright 2009 Tortuga 22, Inc. All rights reserved. +// + +#import +#import + +#import "TUNinePatch.h" +#import "TUNinePatchProtocols.h" + +/** + Concrete TUNinePatch instance. Handles NinePatches that stretch horizontally and vertically. Only instantiate directly if you know what you're doing. + */ +@interface TUFullNinePatch : TUNinePatch < TUNinePatch > { + UIImage *_upperEdge; + UIImage *_lowerEdge; + UIImage *_leftEdge; + UIImage *_rightEdge; + UIImage *_upperLeftCorner; + UIImage *_lowerLeftCorner; + UIImage *_upperRightCorner; + UIImage *_lowerRightCorner; +} + +// Synthesized Properties +@property(nonatomic, retain, readonly) UIImage *upperEdge; +@property(nonatomic, retain, readonly) UIImage *lowerEdge; +@property(nonatomic, retain, readonly) UIImage *leftEdge; +@property(nonatomic, retain, readonly) UIImage *rightEdge; + +@property(nonatomic, retain, readonly) UIImage *upperLeftCorner; +@property(nonatomic, retain, readonly) UIImage *lowerLeftCorner; +@property(nonatomic, retain, readonly) UIImage *upperRightCorner; +@property(nonatomic, retain, readonly) UIImage *lowerRightCorner; + +// Init + Dealloc +-(id)initWithCenter:(UIImage *)center contentRegion:(CGRect)contentRegion tileCenterVertically:(BOOL)tileCenterVertically tileCenterHorizontally:(BOOL)tileCenterHorizontally upperLeftCorner:(UIImage *)upperLeftCorner upperRightCorner:(UIImage *)upperRightCorner lowerLeftCorner:(UIImage *)lowerLeftCorner lowerRightCorner:(UIImage *)lowerRightCorner leftEdge:(UIImage *)leftEdge rightEdge:(UIImage *)rightEdge upperEdge:(UIImage *)upperEdge lowerEdge:(UIImage *)lowerEdge; +-(void)dealloc; + +// Sanity-Checking Tools +-(BOOL)checkSizeSanityAgainstOriginalImage:(UIImage *)originalImage; + +// TUNinePatch Overrides +-(void)drawInRect:(CGRect)rect; +-(CGFloat)leftEdgeWidth; +-(CGFloat)rightEdgeWidth; +-(CGFloat)upperEdgeHeight; +-(CGFloat)lowerEdgeHeight; + +// Image Logging +-(void)logExplodedImage; + +@end \ No newline at end of file diff --git a/NinePatch/TUFullNinePatch.m b/NinePatch/TUFullNinePatch.m new file mode 100755 index 000000000..92abc32a6 --- /dev/null +++ b/NinePatch/TUFullNinePatch.m @@ -0,0 +1,318 @@ +// +// TUFullNinePatch.m +// NinePatch +// +// Copyright 2009 Tortuga 22, Inc. All rights reserved. +// + +#import "TUFullNinePatch.h" +#import "TUNinePatchProtocols.h" +#import "UIImage-TUNinePatch.h" + +@interface TUFullNinePatch () + +// Synthesized Properties +@property(nonatomic, retain, readwrite) UIImage *upperEdge; +@property(nonatomic, retain, readwrite) UIImage *lowerEdge; +@property(nonatomic, retain, readwrite) UIImage *leftEdge; +@property(nonatomic, retain, readwrite) UIImage *rightEdge; + +@property(nonatomic, retain, readwrite) UIImage *upperLeftCorner; +@property(nonatomic, retain, readwrite) UIImage *lowerLeftCorner; +@property(nonatomic, retain, readwrite) UIImage *upperRightCorner; +@property(nonatomic, retain, readwrite) UIImage *lowerRightCorner; + +@end + + +@implementation TUFullNinePatch + +#pragma mark Synthesized Properties +@synthesize upperEdge = _upperEdge; +@synthesize lowerEdge = _lowerEdge; +@synthesize leftEdge = _leftEdge; +@synthesize rightEdge = _rightEdge; + +@synthesize upperLeftCorner = _upperLeftCorner; +@synthesize lowerLeftCorner = _lowerLeftCorner; +@synthesize upperRightCorner = _upperRightCorner; +@synthesize lowerRightCorner = _lowerRightCorner; + +#pragma mark NSCoding +-(id)initWithCoder:(NSCoder *)coder { + if (self = [super initWithCoder:coder]) { + self.upperEdge = (UIImage *) [coder decodeObjectForKey:@"upperEdge"]; + self.lowerEdge = (UIImage *) [coder decodeObjectForKey:@"lowerEdge"]; + self.leftEdge = (UIImage *) [coder decodeObjectForKey:@"leftEdge"]; + self.rightEdge = (UIImage *) [coder decodeObjectForKey:@"rightEdge"]; + self.upperLeftCorner = (UIImage *) [coder decodeObjectForKey:@"upperLeftCorner"]; + self.lowerLeftCorner = (UIImage *) [coder decodeObjectForKey:@"lowerLeftCorner"]; + self.upperRightCorner = (UIImage *) [coder decodeObjectForKey:@"upperRightCorner"]; + self.lowerRightCorner = (UIImage *) [coder decodeObjectForKey:@"lowerRightCorner"]; + } + return self; +} + +-(void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + + [coder encodeObject:self.upperEdge + forKey:@"upperEdge"]; + + [coder encodeObject:self.lowerEdge + forKey:@"lowerEdge"]; + + [coder encodeObject:self.leftEdge + forKey:@"leftEdge"]; + + [coder encodeObject:self.rightEdge + forKey:@"rightEdge"]; + + [coder encodeObject:self.upperLeftCorner + forKey:@"upperLeftCorner"]; + + [coder encodeObject:self.lowerLeftCorner + forKey:@"lowerLeftCorner"]; + + [coder encodeObject:self.upperRightCorner + forKey:@"upperRightCorner"]; + + [coder encodeObject:self.lowerRightCorner + forKey:@"lowerRightCorner"]; +} + +#pragma mark NSCopying +-(id)copyWithZone:(NSZone *)zone { + return [[[self class] allocWithZone:zone] initWithCenter:self.center + contentRegion:self.contentRegion + tileCenterVertically:self.tileCenterVertically + tileCenterHorizontally:self.tileCenterHorizontally + upperLeftCorner:self.upperLeftCorner + upperRightCorner:self.upperRightCorner + lowerLeftCorner:self.lowerLeftCorner + lowerRightCorner:self.lowerRightCorner + leftEdge:self.leftEdge + rightEdge:self.rightEdge + upperEdge:self.upperEdge + lowerEdge:self.lowerEdge]; +} + +#pragma mark Init + Dealloc +-(id)initWithCenter:(UIImage *)center contentRegion:(CGRect)contentRegion tileCenterVertically:(BOOL)tileCenterVertically tileCenterHorizontally:(BOOL)tileCenterHorizontally upperLeftCorner:(UIImage *)upperLeftCorner upperRightCorner:(UIImage *)upperRightCorner lowerLeftCorner:(UIImage *)lowerLeftCorner lowerRightCorner:(UIImage *)lowerRightCorner leftEdge:(UIImage *)leftEdge rightEdge:(UIImage *)rightEdge upperEdge:(UIImage *)upperEdge lowerEdge:(UIImage *)lowerEdge { + NPParameterAssertNotNilIsKindOfClass(upperLeftCorner,UIImage); + NPParameterAssertNotNilIsKindOfClass(lowerLeftCorner,UIImage); + NPParameterAssertNotNilIsKindOfClass(upperRightCorner,UIImage); + NPParameterAssertNotNilIsKindOfClass(lowerRightCorner,UIImage); + NPParameterAssertNotNilIsKindOfClass(leftEdge,UIImage); + NPParameterAssertNotNilIsKindOfClass(rightEdge,UIImage); + NPParameterAssertNotNilIsKindOfClass(upperEdge,UIImage); + NPParameterAssertNotNilIsKindOfClass(lowerEdge,UIImage); + if (self = [super initWithCenter:center + contentRegion:contentRegion + tileCenterVertically:tileCenterVertically + tileCenterHorizontally:tileCenterHorizontally]) { + self.upperEdge = upperEdge; + self.lowerEdge = lowerEdge; + self.leftEdge = leftEdge; + self.rightEdge = rightEdge; + self.upperLeftCorner = upperLeftCorner; + self.lowerLeftCorner = lowerLeftCorner; + self.upperRightCorner = upperRightCorner; + self.lowerRightCorner = lowerRightCorner; + } + return self; +} + +#pragma mark +-(void)dealloc { + self.upperEdge = nil; + self.lowerEdge = nil; + self.leftEdge = nil; + self.rightEdge = nil; + self.upperLeftCorner = nil; + self.lowerLeftCorner = nil; + self.upperRightCorner = nil; + self.lowerRightCorner = nil; + [super dealloc]; +} + +#pragma mark Sanity-Checking Tools +-(BOOL)checkSizeSanityAgainstOriginalImage:(UIImage *)originalImage { + CGSize os = [originalImage size]; + CGFloat ow = os.width; + CGFloat oh = os.height; + + CGFloat tr = ([[self upperEdge] size].width + [[self upperLeftCorner] size].width + [[self upperRightCorner] size].width); + CGFloat mr = ([[self center] size].width + [[self leftEdge] size].width + [[self rightEdge] size].width); + CGFloat lr = ([[self lowerEdge] size].width + [[self lowerLeftCorner] size].width + [[self lowerRightCorner] size].width); + + + BOOL topRow = TUWithinEpsilon(1.0f,ow,tr); + BOOL midRow = TUWithinEpsilon(1.0f,ow,mr); + BOOL lowRow = TUWithinEpsilon(1.0f,ow,lr); + + CGFloat lc = ([[self upperLeftCorner] size].height + [[self lowerLeftCorner] size].height + [[self leftEdge] size].height); + CGFloat mc = ([[self center] size].height + [[self upperEdge] size].height + [[self lowerEdge] size].height); + CGFloat rc = ([[self upperRightCorner] size].height + [[self lowerRightCorner] size].height + [[self rightEdge] size].height); + + BOOL lCol = TUWithinEpsilon(1.0f,oh,lc); + BOOL mCol = TUWithinEpsilon(1.0f,oh,mc); + BOOL rCol = TUWithinEpsilon(1.0f,oh,rc); + + BOOL sizesMatch = TUForceYesOrNo(midRow && topRow && lowRow && mCol && lCol && rCol); + DLog(@"SANITY sizesMatch: '%@.'",TUYesOrNoString(sizesMatch)); + DLog(@"SANITY topRow: '%@', midRow:'%@, lowRow:'%@'.", TUYesOrNoString(topRow),TUYesOrNoString(midRow),TUYesOrNoString(lowRow)); + DLog(@"SANITY lCol: '%@', mCol:'%@, rCol:'%@'.", TUYesOrNoString(lCol),TUYesOrNoString(mCol),TUYesOrNoString(rCol)); + DLog(@"SANITY ",ow,tr,mr,lr); + DLog(@"SANITY ",oh,lc,mc,rc); + return sizesMatch; +} + +#pragma mark TUNinePatch Overrides +-(void)drawInRect:(CGRect)rect { + NPSelfProperty(center); + NPSelfProperty(leftEdge); + NPSelfProperty(rightEdge); + NPSelfProperty(lowerEdge); + NPSelfProperty(upperEdge); + NPSelfProperty(upperLeftCorner); + NPSelfProperty(upperRightCorner); + NPSelfProperty(lowerLeftCorner); + NPSelfProperty(lowerRightCorner); + + CGFloat leftEdgeWidth = [self leftEdgeWidth]; + CGFloat rightEdgeWidth = [self rightEdgeWidth]; + BOOL hasLeftEdge = (leftEdgeWidth > 0.0f)?(YES):(NO); + BOOL hasRightEdge = (rightEdgeWidth > 0.0f)?(YES):(NO); + + CGFloat upperEdgeHeight = [self upperEdgeHeight]; + CGFloat lowerEdgeHeight = [self lowerEdgeHeight]; + BOOL hasUpperEdge = (upperEdgeHeight > 0.0f)?(YES):(NO); + BOOL hasLowerEdge = (lowerEdgeHeight > 0.0f)?(YES):(NO); + + BOOL hasUpperLeftCorner = (hasLeftEdge && hasUpperEdge)?(YES):(NO); + BOOL hasUpperRightCorner = (hasRightEdge && hasUpperEdge)?(YES):(NO); + BOOL hasLowerLeftCorner = (hasLeftEdge && hasLowerEdge)?(YES):(NO); + BOOL hasLowerRightCorner = (hasRightEdge && hasLowerEdge)?(YES):(NO); + + + + CGFloat contentWidth = TUTruncateAtZero(rect.size.width - (leftEdgeWidth + rightEdgeWidth)); + CGFloat contentHeight = TUTruncateAtZero(rect.size.height - (upperEdgeHeight + lowerEdgeHeight)); + BOOL hasContentWidth = (contentWidth > 0.0f)?(YES):(NO); + BOOL hasContentHeight = (contentWidth > 0.0f)?(YES):(NO); + if (hasContentWidth && hasContentHeight) { + [self.center drawInRect:CGRectMake(CGRectGetMinX(rect) + leftEdgeWidth, CGRectGetMinY(rect) + upperEdgeHeight, contentWidth, contentHeight)]; + } + if (hasContentWidth) { + if (hasUpperEdge) { + [self.upperEdge drawInRect:CGRectMake( + CGRectGetMinX(rect) + leftEdgeWidth, + CGRectGetMinY(rect), + contentWidth, + upperEdgeHeight)]; + } + if (hasLowerEdge) { + [self.lowerEdge drawInRect:CGRectMake( + CGRectGetMinX(rect) + leftEdgeWidth, + CGRectGetMaxY(rect) - lowerEdgeHeight, + contentWidth, + lowerEdgeHeight)]; + } + } + if (hasContentHeight) { + if (hasLeftEdge) { + [self.leftEdge drawInRect:CGRectMake( + CGRectGetMinX(rect), + CGRectGetMinY(rect) + upperEdgeHeight, + leftEdgeWidth, + contentHeight)]; + } + if (hasRightEdge) { + [self.rightEdge drawInRect:CGRectMake( + CGRectGetMaxX(rect) - rightEdgeWidth, + CGRectGetMinY(rect) + upperEdgeHeight, + rightEdgeWidth, + contentHeight)]; + } + } + if (hasUpperLeftCorner && self.upperLeftCorner) { + [self.upperLeftCorner drawAtPoint:CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect))]; + } + if (hasUpperRightCorner && self.upperRightCorner) { + [self.upperRightCorner drawAtPoint:CGPointMake(CGRectGetMaxX(rect) - rightEdgeWidth, CGRectGetMinY(rect))]; + } + if (hasLowerLeftCorner && self.lowerLeftCorner) { + [self.lowerLeftCorner drawAtPoint:CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect) - lowerEdgeHeight)]; + } + if (hasLowerRightCorner && self.lowerRightCorner) { + [self.lowerRightCorner drawAtPoint:CGPointMake(CGRectGetMaxX(rect) - rightEdgeWidth, CGRectGetMaxY(rect) - lowerEdgeHeight)]; + } +} + +#pragma mark - +-(CGFloat)leftEdgeWidth { + return ((self.upperLeftCorner)? + ([self.upperLeftCorner size].width): + (((self.lowerLeftCorner)? + ([self.lowerLeftCorner size].width):(0.0f)))); +} + +-(CGFloat)rightEdgeWidth { + return ((self.upperRightCorner)? + ([self.upperRightCorner size].width) + :(((self.lowerRightCorner)? + ([self.lowerRightCorner size].width):(0.0f)))); +} + +-(CGFloat)upperEdgeHeight { + return ((self.upperLeftCorner)? + ([self.upperLeftCorner size].height) + :(((self.upperRightCorner)? + ([self.upperRightCorner size].height):(0.0f)))); +} + +-(CGFloat)lowerEdgeHeight { + return ((self.lowerLeftCorner)? + ([self.lowerLeftCorner size].height) + :(((self.lowerRightCorner)? + ([self.lowerRightCorner size].height):(0.0f)))); +} + +#pragma mark Customized Description Overrides +-(NSString *)descriptionPostfix { + return [NSString stringWithFormat:@"%@, self.upperEdge:<'%@'>, self.lowerEdge:<'%@'>, self.leftEdge:<'%@'>, self.rightEdge:<'%@'>, self.upperLeftCorner:<'%@'>, self.lowerLeftCorner:<'%@'>, self.upperRightCorner:<'%@'>, self.lowerRightCorner:<'%@'>", + [super descriptionPostfix], + self.upperEdge, + self.lowerEdge, + self.leftEdge, + self.rightEdge, + self.upperLeftCorner, + self.lowerLeftCorner, + self.upperRightCorner, + self.lowerRightCorner]; +} + +#pragma mark Image Logging +-(void)logExplodedImage { + CGFloat centerWidth = ((self.center)?(self.center.size.width):(0.0f)); + CGFloat centerHeight = ((self.center)?(self.center.size.height):(0.0f)); + CGSize mySize = CGSizeMake( + 4.0f + (centerWidth + ([self leftEdgeWidth]) + ([self rightEdgeWidth])), + 4.0f + (centerHeight + ([self upperEdgeHeight]) + ([self lowerEdgeHeight])) + ); + UIGraphicsBeginImageContext(mySize); + [self.center drawAtPoint:CGPointMake(2.0f + [self leftEdgeWidth], 2.0f + [self upperEdgeHeight])]; + [self.upperLeftCorner drawAtPoint:CGPointMake(0.0f, 0.0f)]; + [self.leftEdge drawAtPoint:CGPointMake(0.0f, [self upperEdgeHeight] + 2.0f)]; + [self.lowerLeftCorner drawAtPoint:CGPointMake(0.0f, [self upperEdgeHeight] + 4.0f + centerHeight)]; + [self.upperEdge drawAtPoint:CGPointMake(2.0f + [self leftEdgeWidth], 0.0f)]; + [self.upperRightCorner drawAtPoint:CGPointMake(4.0f + [self leftEdgeWidth] + centerWidth, 0.0f)]; + [self.rightEdge drawAtPoint:CGPointMake(4.0f + [self leftEdgeWidth] + centerWidth, 2.0f + [self upperEdgeHeight])]; + [self.lowerRightCorner drawAtPoint:CGPointMake(4.0f + [self leftEdgeWidth] + centerWidth, 4.0f + [self upperEdgeHeight] + centerHeight)]; + [self.lowerEdge drawAtPoint:CGPointMake(2.0f + [self leftEdgeWidth], 4.0f + [self upperEdgeHeight] + centerHeight)]; + IMLog(UIGraphicsGetImageFromCurrentImageContext(), @"explodedNinePatchImage"); + UIGraphicsEndImageContext(); +} + +@end \ No newline at end of file diff --git a/NinePatch/TUHorizontalNinePatch.h b/NinePatch/TUHorizontalNinePatch.h new file mode 100755 index 000000000..10ce83479 --- /dev/null +++ b/NinePatch/TUHorizontalNinePatch.h @@ -0,0 +1,37 @@ +// +// TUHorizontalNinePatch.h +// NinePatch +// +// Copyright 2009 Tortuga 22, Inc. All rights reserved. +// + +#import +#import + +#import "TUNinePatch.h" +#import "TUNinePatchProtocols.h" + +/** + Concrete TUNinePatch instance. Handles NinePatches that stretch horizontally but not vertically. Only instantiate directly if you know what you're doing. + */ +@interface TUHorizontalNinePatch : TUNinePatch < TUNinePatch > { + UIImage *_leftEdge; + UIImage *_rightEdge; +} + +// Synthesized Properties +@property(nonatomic, retain, readonly) UIImage *leftEdge; +@property(nonatomic, retain, readonly) UIImage *rightEdge; + +// Init + Dealloc +-(id)initWithCenter:(UIImage *)center contentRegion:(CGRect)contentRegion tileCenterVertically:(BOOL)tileCenterVertically tileCenterHorizontally:(BOOL)tileCenterHorizontally leftEdge:(UIImage *)leftEdge rightEdge:(UIImage *)rightEdge; +-(void)dealloc; + +// TUNinePatch Overrides +-(void)drawInRect:(CGRect)rect; +-(BOOL)stretchesVertically; +-(CGSize)sizeForContentOfSize:(CGSize)contentSize; +-(CGFloat)leftEdgeWidth; +-(CGFloat)rightEdgeWidth; + +@end diff --git a/NinePatch/TUHorizontalNinePatch.m b/NinePatch/TUHorizontalNinePatch.m new file mode 100755 index 000000000..b570cb2dc --- /dev/null +++ b/NinePatch/TUHorizontalNinePatch.m @@ -0,0 +1,124 @@ +// +// TUHorizontalNinePatch.m +// NinePatch +// +// Copyright 2009 Tortuga 22, Inc. All rights reserved. +// + +#import "TUHorizontalNinePatch.h" + +@interface TUHorizontalNinePatch () + +// Synthesized Properties +@property(nonatomic, retain, readwrite) UIImage *leftEdge; +@property(nonatomic, retain, readwrite) UIImage *rightEdge; + +@end + + +@implementation TUHorizontalNinePatch + +#pragma mark Synthesized Properties +@synthesize leftEdge = _leftEdge; +@synthesize rightEdge = _rightEdge; + +#pragma mark NSCoding +-(id)initWithCoder:(NSCoder *)coder { + if (self = [super initWithCoder:coder]) { + self.leftEdge = (UIImage *)[coder decodeObjectForKey:@"leftEdge"]; + self.rightEdge = (UIImage *)[coder decodeObjectForKey:@"rightEdge"]; + } + return self; +} + +-(void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + + [coder encodeObject:self.leftEdge + forKey:@"leftEdge"]; + + [coder encodeObject:self.rightEdge + forKey:@"rightEdge"]; +} + +#pragma mark NSCopying +-(id)copyWithZone:(NSZone *)zone { + return [[[self class] allocWithZone:zone] initWithCenter:self.center + contentRegion:self.contentRegion + tileCenterVertically:self.tileCenterVertically + tileCenterHorizontally:self.tileCenterHorizontally + leftEdge:self.leftEdge + rightEdge:self.rightEdge]; +} + +#pragma mark Init + Dealloc +-(id)initWithCenter:(UIImage *)center contentRegion:(CGRect)contentRegion tileCenterVertically:(BOOL)tileCenterVertically tileCenterHorizontally:(BOOL)tileCenterHorizontally leftEdge:(UIImage *)leftEdge rightEdge:(UIImage *)rightEdge { + NPParameterAssertNotNilIsKindOfClass(leftEdge,UIImage); + NPParameterAssertNotNilIsKindOfClass(rightEdge,UIImage); + if (self = [super initWithCenter:center + contentRegion:contentRegion + tileCenterVertically:tileCenterVertically + tileCenterHorizontally:tileCenterHorizontally]) { + self.leftEdge = leftEdge; + self.rightEdge = rightEdge; + } + return self; +} + +#pragma mark - +-(void)dealloc { + self.leftEdge = nil; + self.rightEdge = nil; + [super dealloc]; +} + +#pragma mark TUNinePatch Overrides +-(void)drawInRect:(CGRect)rect { + CGFloat height = [self minimumHeight]; + [self.center drawInRect:CGRectMake(CGRectGetMinX(rect) + [self leftEdgeWidth], CGRectGetMinY(rect), CGRectGetWidth(rect) - ([self leftEdgeWidth] + [self rightEdgeWidth]), height)]; + if (self.leftEdge) { + [self.leftEdge drawAtPoint:CGPointMake(CGRectGetMinX(rect),CGRectGetMinY(rect))]; + } + if (self.rightEdge) { + [self.rightEdge drawAtPoint:CGPointMake(CGRectGetMaxX(rect) - [self rightEdgeWidth], CGRectGetMinY(rect))]; + } +} + +#pragma mark - +-(BOOL)stretchesVertically { + return NO; +} + +#pragma mark - +-(CGSize)sizeForContentOfSize:(CGSize)contentSize { + CGSize outSize = [super sizeForContentOfSize:contentSize]; + outSize.height = [self minimumHeight]; + return outSize; +} + +#pragma mark - +-(CGFloat)leftEdgeWidth { + CGFloat leftEdgeWidth = 0.0f; + if (self.leftEdge) { + leftEdgeWidth = [self.leftEdge size].width; + } + return leftEdgeWidth; +} + +-(CGFloat)rightEdgeWidth { + CGFloat rightEdgeWidth = 0.0f; + if (self.leftEdge) { + rightEdgeWidth = [self.rightEdge size].width; + } + return rightEdgeWidth; +} + +#pragma mark Customized Description Overrides +-(NSString *)descriptionPostfix { + return [NSString stringWithFormat:@"%@, self.leftEdge:<'%@'>, self.rightEdge:<'%@'>", + [super descriptionPostfix], + self.leftEdge, + self.rightEdge]; +} + +@end \ No newline at end of file diff --git a/NinePatch/TUNinePatch.h b/NinePatch/TUNinePatch.h new file mode 100755 index 000000000..175854c37 --- /dev/null +++ b/NinePatch/TUNinePatch.h @@ -0,0 +1,125 @@ +// +// TUNinePatch.h +// NinePatch +// +// Copyright 2009 Tortuga 22, Inc. All rights reserved. +// + +#import +#import +#import +#import "TUNinePatchProtocols.h" + +/** + Abstract base class for concrete NinePatches; this is the public interface into the TUNinePatch class cluster. Note particularly that TUNinePatch itself doesn't actually implement the TUNinePatch protocol, but its convenience methods promise to supply objects that do implement TUNinePatch. You should really only be using the classlevel convenience methods on this class unless you know what you're doing. If a method isn't documented it's probably not really for public use yet. + */ +@interface TUNinePatch : NSObject < NSCoding, NSCopying > { + UIImage *_center; + CGRect _contentRegion; + BOOL _tileCenterVertically; + BOOL _tileCenterHorizontally; +} + +// Synthesized Properties +@property(nonatomic, retain, readonly) UIImage *center; +@property(nonatomic, assign, readonly) CGRect contentRegion; +@property(nonatomic, assign, readonly) BOOL tileCenterVertically; +@property(nonatomic, assign, readonly) BOOL tileCenterHorizontally; + +// NSCoding +-(id)initWithCoder:(NSCoder *)coder; +-(void)encodeWithCoder:(NSCoder *)coder; + +// NSCopying +-(id)copyWithZone:(NSZone *)zone; + +// Init + Dealloc +-(id)initWithCenter:(UIImage *)center contentRegion:(CGRect)contentRegion; +-(id)initWithCenter:(UIImage *)center contentRegion:(CGRect)contentRegion tileCenterVertically:(BOOL)tileCenterVertically tileCenterHorizontally:(BOOL)tileCenterHorizontally; +-(void)dealloc; + +// Convenience Constructors +/** + This parses ninePatchImage and instantiates an instance of the appropriate TUNinePatch subclass. + + @param ninePatchImage A non-nil UIImage object containing the contents of a .9.png file (eg: it still contains the 1px border containing the scaling information). + @returns An autoreleased object implementing the TUNinePatch protocol, loaded with the contents of ninePatchImage. Can be nil if errors encountered. + */ ++(id < TUNinePatch >)ninePatchWithNinePatchImage:(UIImage *)ninePatchImage; + +/** + Calls through to ninePatchWithImage:stretchableRegion:contentRegion:tileCenterVertically:tileCenterHorizontally with contentRegion=CGRectZero and NO on the tiling params. Will probably get made private or protected soon. + */ ++(id < TUNinePatch >)ninePatchWithImage:(UIImage *)image stretchableRegion:(CGRect)stretchableRegion; + +/** + Creates a NinePatch using the passed-in image and the passed-in scaling information. This method may go protected soon, leaving only the ninePatchWithNinePatchImage: as a public convenience (possibly with the addition of ninePatchNamed: method as well). The argument for goign protected or private is that if this library winds up expanding discontinuous stretchable regions (as is done on Android) then there would be a separate interface for passing in 4 stretchable regions, making the public interface cmoplicated and "multiple ways in". + + @param image A non-nil UIImage object that contains the displayable content. This is contents of .9.png file AFTER removing the 1px border. + @param stretchableRegion Rect specifying the bounds of the central stretchable region. In the .9.png this is specified on the left and top margins. + @param contentRegion Rect specifying the bounds of the content region, EG the box into which associated content might fit. In the .9.png this is specified on the bottom and right margins. + @param tileCenterVertically (Currently unsupported) is intended to specify whether or not the center scales by resizing or scales by tiling. Not fully supported at this time, only use if you know what you're doing. + @param tileCenterHorizontally (Currently unsupported) is intended to specify whether or not the center scales by resizing or scales by tiling. Not fully supported at this time, only use if you know what you're doing. + + @returns An object implementing the TUNinePatch protocol. Can be nil if problems were encountered. + */ ++(id < TUNinePatch >)ninePatchWithImage:(UIImage *)image stretchableRegion:(CGRect)stretchableRegion contentRegion:(CGRect)contentRegion tileCenterVertically:(BOOL)tileCenterVertically tileCenterHorizontally:(BOOL)tileCenterHorizontally; + +// Bundle Loading +/** + Creates a ninepatch in two steps: it takes filename, and tries to load @"filename.9.png" from the main bundle. If that loads it then attempts to construct a NinePatch from that image file's contents. Differs from UIImage's analogous method in that no caching is done. + + @param filename A non-nil NSString containing the filename of the source image but NOT including ".9.png". + @returns An object implementing the TUNinePatch protocol. Can be nil if problems encountered. + */ ++(id < TUNinePatch >)ninePatchNamed:(NSString *)filename; + +// Nine Patch Image Manipulation - High Level ++(CGRect)rectFromHorizontalRange:(NSRange)horizontalRange verticalRange:(NSRange)verticalRange; ++(CGRect)stretchableRegionOfNinePatchImage:(UIImage *)ninePatchImage; ++(CGRect)contentRegionOfNinePatchImage:(UIImage *)ninePatchImage; ++(BOOL)shouldTileCenterHorizontallyForNinePatchImage:(UIImage *)ninePatchImage; ++(BOOL)shouldTileCenterVerticallyForNinePatchImage:(UIImage *)ninePatchImage; + +// Drawing Utility +-(void)drawInRect:(CGRect)rect; + +// Diagnostic Utilities +-(UIImage *)upperEdge; +-(UIImage *)lowerEdge; +-(UIImage *)leftEdge; +-(UIImage *)rightEdge; + +-(UIImage *)upperLeftCorner; +-(UIImage *)lowerLeftCorner; +-(UIImage *)upperRightCorner; +-(UIImage *)lowerRightCorner; + +// TUNinePatch Protocol Methods - Drawing +-(void)inContext:(CGContextRef)context drawAtPoint:(CGPoint)point forContentOfSize:(CGSize)contentSize; +-(void)inContext:(CGContextRef)context drawCenteredInRect:(CGRect)rect forContentOfSize:(CGSize)contentSize; +-(void)inContext:(CGContextRef)context drawInRect:(CGRect)rect; + +// TUNinePatch Protocol Methods - Image Construction +-(UIImage *)imageOfSize:(CGSize)size; + +// TUNinePatch Protocol Methods - Sizing +-(BOOL)stretchesHorizontally; +-(BOOL)stretchesVertically; +-(CGFloat)minimumWidth; +-(CGFloat)minimumHeight; +-(CGSize)minimumSize; +-(CGSize)sizeForContentOfSize:(CGSize)contentSize; +-(CGPoint)upperLeftCornerForContentWhenDrawnAtPoint:(CGPoint)point; + +// TUNinePatch Protocol Methods - Geometry +-(CGFloat)leftEdgeWidth; +-(CGFloat)rightEdgeWidth; +-(CGFloat)upperEdgeHeight; +-(CGFloat)lowerEdgeHeight; + +// Customized Description +-(NSString *)description; +-(NSString *)descriptionPostfix; + +@end \ No newline at end of file diff --git a/NinePatch/TUNinePatch.m b/NinePatch/TUNinePatch.m new file mode 100755 index 000000000..8c3303fb0 --- /dev/null +++ b/NinePatch/TUNinePatch.m @@ -0,0 +1,514 @@ +// +// TUNinePatch.m +// NinePatch +// +// Copyright 2009 Tortuga 22, Inc. All rights reserved. +// + +#import "TUNinePatch.h" +#import "TUVerticalNinePatch.h" +#import "TUHorizontalNinePatch.h" +#import "TUFullNinePatch.h" +#import "UIImage-TUNinePatch.h" + +@interface TUNinePatch () + +@property(nonatomic, retain, readwrite) UIImage *center; +@property(nonatomic, assign, readwrite) CGRect contentRegion; +@property(nonatomic, assign, readwrite) BOOL tileCenterVertically; +@property(nonatomic, assign, readwrite) BOOL tileCenterHorizontally; + +@end + + +@implementation TUNinePatch + +#pragma mark Synthesized Properties +@synthesize center = _center; +@synthesize contentRegion = _contentRegion; +@synthesize tileCenterVertically = _tileCenterVertically; +@synthesize tileCenterHorizontally = _tileCenterHorizontally; + +#pragma mark NSCoding +-(id)initWithCoder:(NSCoder *)coder { + NPAOInputLog(coder); + if (self = [super init]) { + self.center = (UIImage *)[coder decodeObjectForKey:@"center"]; + self.contentRegion = [coder decodeCGRectForKey:@"contentRegion"]; + self.tileCenterVertically = [coder decodeBoolForKey:@"tileCenterVertically"]; + self.tileCenterHorizontally = [coder decodeBoolForKey:@"tileCenterHorizontally"]; + } + return self; +} + +-(void)encodeWithCoder:(NSCoder *)coder { + NPAOInputLog(coder); + [coder encodeObject:self.center + forKey:@"center"]; + + [coder encodeCGRect:self.contentRegion + forKey:@"contentRegion"]; + + [coder encodeBool:self.tileCenterVertically + forKey:@"tileCenterVertically"]; + + [coder encodeBool:self.tileCenterHorizontally + forKey:@"tileCenterHorizontally"]; +} + +#pragma mark NSCopying +-(id)copyWithZone:(NSZone *)zone { + return [[[self class] allocWithZone:zone] initWithCenter:self.center + contentRegion:self.contentRegion + tileCenterVertically:self.tileCenterVertically + tileCenterHorizontally:self.tileCenterHorizontally]; +} + +#pragma mark Init + Dealloc +-(id)initWithCenter:(UIImage *)center contentRegion:(CGRect)contentRegion { + return [self initWithCenter:center + contentRegion:contentRegion + tileCenterVertically:NO + tileCenterHorizontally:NO]; +} + +-(id)initWithCenter:(UIImage *)center contentRegion:(CGRect)contentRegion tileCenterVertically:(BOOL)tileCenterVertically tileCenterHorizontally:(BOOL)tileCenterHorizontally { + NPAInputLog(@"[%@:<0x%x> initWithCenter:%@ contentRegion:%@ tileCenterVertically:%d tileCenterHorizontally:%d]", [self class], ((NSUInteger) self), center, NSStringFromCGRect(contentRegion), tileCenterVertically, tileCenterHorizontally); + NPParameterAssertNotNilIsKindOfClass(center, UIImage); + if (self = [super init]) { + self.center = center; + self.contentRegion = contentRegion; + self.tileCenterVertically = tileCenterVertically; + self.tileCenterHorizontally = tileCenterHorizontally; + } + return self; +} + +#pragma mark - +-(void)dealloc { + self.center = nil; + [super dealloc]; +} + +#pragma mark Convenience Constructors ++(id < TUNinePatch >)ninePatchWithNinePatchImage:(UIImage *)ninePatchImage { + NPAInputLog(@"ninePatchWithNinePatchImage:'%@'",ninePatchImage); + id < TUNinePatch > outPatch = nil; + if (ninePatchImage) { + @try { + outPatch = [self ninePatchWithImage:[ninePatchImage imageAsNinePatchImage] + stretchableRegion:[self stretchableRegionOfNinePatchImage:ninePatchImage] + contentRegion:[self contentRegionOfNinePatchImage:ninePatchImage] + tileCenterVertically:[self shouldTileCenterVerticallyForNinePatchImage:ninePatchImage] + tileCenterHorizontally:[self shouldTileCenterHorizontallyForNinePatchImage:ninePatchImage]]; + } + @catch (NSException * e) { + NPLogException(e); + outPatch = nil; + } + } + NPAssertNilOrConformsToProtocol(outPatch,TUNinePatch); + NPOOutputLog(outPatch); + return outPatch; +} + ++(id < TUNinePatch >)ninePatchWithImage:(UIImage *)image stretchableRegion:(CGRect)stretchableRegion { + NPAInputLog(@"ninePatchWithImage:'%@' stretchableRegion:'%@'",image,NSStringFromCGRect(stretchableRegion)); + NPParameterAssertNotNilIsKindOfClass(image,UIImage); + return [self ninePatchWithImage:image + stretchableRegion:stretchableRegion + contentRegion:CGRectZero + tileCenterVertically:NO + tileCenterHorizontally:NO]; +} + ++(id < TUNinePatch >)ninePatchWithImage:(UIImage *)image stretchableRegion:(CGRect)stretchableRegion contentRegion:(CGRect)contentRegion tileCenterVertically:(BOOL)tileCenterVertically tileCenterHorizontally:(BOOL)tileCenterHorizontally { + NPAInputLog(@"ninePatchWithImage:'%@' stretchableRegion:'%@' contentRegion:'%@' tileCenterVertically:'%@' tileCenterHorizontally:'%@'",image,NSStringFromCGRect(stretchableRegion),NSStringFromCGRect(contentRegion),TUYesOrNoString(tileCenterVertically),TUYesOrNoString(tileCenterHorizontally)); + NPParameterAssertNotNilIsKindOfClass(image,UIImage); + NPParameterAssert(stretchableRegion.origin.x >= 0.0f); + NPParameterAssert(stretchableRegion.origin.y >= 0.0f); + NPParameterAssert([image size].width >= stretchableRegion.origin.x + stretchableRegion.size.width); + NPParameterAssert([image size].height >= stretchableRegion.origin.y + stretchableRegion.size.height); + id < TUNinePatch > ninePatch = nil; + if (image) { + CGFloat imageWidth = [image size].width; + CGFloat imageHeight = [image size].height; + CGRect fixedStretchableRegion = stretchableRegion; + CGFloat stretchableRegionMinX = CGRectGetMinX(fixedStretchableRegion); + CGFloat stretchableRegionMinY = CGRectGetMinY(fixedStretchableRegion); + CGFloat stretchableRegionMaxX = CGRectGetMaxX(fixedStretchableRegion); + CGFloat stretchableRegionMaxY = CGRectGetMaxY(fixedStretchableRegion); + BOOL stretchesOnLeft = (stretchableRegionMinX > 0.0f)?(YES):(NO); + BOOL stretchesOnRight = (stretchableRegionMaxX < imageWidth)?(YES):(NO); + BOOL stretchesOnTop = (stretchableRegionMinY > 0.0f)?(YES):(NO); + BOOL stretchesOnBottom = (stretchableRegionMaxY < imageHeight)?(YES):(NO); + BOOL stretchesHorizontally = (stretchesOnLeft || stretchesOnRight)?(YES):(NO); + BOOL stretchesVertically = (stretchesOnTop || stretchesOnBottom)?(YES):(NO); + if (stretchesVertically && stretchesHorizontally) { + LLog(@"...the specified stretchable region stretches horizontally and vertically."); + UIImage *center = [image extractCenterForStretchableRegion:fixedStretchableRegion]; + UIImage *upperLeftCorner = [image extractUpperLeftCornerForStretchableRegion:fixedStretchableRegion]; + UIImage *upperRightCorner = [image extractUpperRightCornerForStretchableRegion:fixedStretchableRegion]; + UIImage *lowerLeftCorner = [image extractLowerLeftCornerForStretchableRegion:fixedStretchableRegion]; + UIImage *lowerRightCorner = [image extractLowerRightCornerForStretchableRegion:fixedStretchableRegion]; + UIImage *leftEdge = [image extractLeftEdgeForStretchableRegion:fixedStretchableRegion]; + UIImage *rightEdge = [image extractRightEdgeForStretchableRegion:fixedStretchableRegion]; + UIImage *lowerEdge = [image extractLowerEdgeForStretchableRegion:fixedStretchableRegion]; + UIImage *upperEdge = [image extractUpperEdgeForStretchableRegion:fixedStretchableRegion]; + + // Mega-Block of size sanity checking + + // Given that the only major bug encountered while developing this library + // proved to be a difficult-to-track-down source of off-by-one errors in + // the sizes of the slices, you can understand the paranoia here. + // + // Just remember to build with the assertion-checking off. + + NPAssertCorrectSubimageWidthDecomposition(image, upperLeftCorner, upperEdge, upperRightCorner); + NPAssertCorrectSubimageWidthDecomposition(image, leftEdge, upperEdge, rightEdge); + NPAssertCorrectSubimageWidthDecomposition(image, lowerLeftCorner, lowerEdge, lowerRightCorner); + + NPAssertCorrectSubimageHeightDecomposition(image, upperLeftCorner, leftEdge, lowerLeftCorner); + NPAssertCorrectSubimageHeightDecomposition(image, upperEdge, center, lowerEdge); + NPAssertCorrectSubimageHeightDecomposition(image, upperRightCorner, rightEdge, lowerRightCorner); + + NPAssertWithinOne(([upperLeftCorner size].height), ([upperRightCorner size].height)); + NPAssertWithinOne(([upperLeftCorner size].height), ([upperEdge size].height)); + NPAssertWithinOne(([upperEdge size].height), ([upperRightCorner size].height)); + + NPAssertWithinOne(([leftEdge size].height), ([center size].height)); + NPAssertWithinOne(([center size].height), ([rightEdge size].height)); + NPAssertWithinOne(([rightEdge size].height), ([leftEdge size].height)); + + NPAssertWithinOne(([lowerLeftCorner size].height), ([lowerRightCorner size].height)); + NPAssertWithinOne(([lowerRightCorner size].height), ([lowerEdge size].height)); + NPAssertWithinOne(([lowerEdge size].height), ([lowerLeftCorner size].height)); + + NPAssertWithinOne(([upperLeftCorner size].width), ([leftEdge size].width)); + NPAssertWithinOne(([leftEdge size].width), ([lowerLeftCorner size].width)); + NPAssertWithinOne(([upperLeftCorner size].width), ([lowerLeftCorner size].width)); + + NPAssertWithinOne(([upperEdge size].width), ([center size].width)); + NPAssertWithinOne(([center size].width), ([lowerEdge size].width)); + NPAssertWithinOne(([upperEdge size].width), ([lowerEdge size].width)); + + NPAssertWithinOne(([upperRightCorner size].width), ([rightEdge size].width)); + NPAssertWithinOne(([rightEdge size].width), ([lowerRightCorner size].width)); + NPAssertWithinOne(([lowerRightCorner size].width), ([upperRightCorner size].width)); + + ninePatch = [[[TUFullNinePatch alloc] initWithCenter:center + contentRegion:contentRegion + tileCenterVertically:tileCenterVertically + tileCenterHorizontally:tileCenterHorizontally + upperLeftCorner:upperLeftCorner + upperRightCorner:upperRightCorner + lowerLeftCorner:lowerLeftCorner + lowerRightCorner:lowerRightCorner + leftEdge:leftEdge + rightEdge:rightEdge + upperEdge:upperEdge + lowerEdge:lowerEdge] autorelease]; + } else if (stretchesVertically) { + UIImage *center = [image extractCenterForStretchableRegion:fixedStretchableRegion]; + UIImage *upperEdge = [image extractUpperEdgeForStretchableRegion:fixedStretchableRegion]; + UIImage *lowerEdge = [image extractLowerEdgeForStretchableRegion:fixedStretchableRegion]; + + NPAssertCorrectSubimageHeightDecomposition(image,center,upperEdge,lowerEdge); + NPAssertWithinOne(([center size].width),([upperEdge size].width)); + NPAssertWithinOne(([lowerEdge size].width),([upperEdge size].width)); + NPAssertWithinOne(([center size].width),([lowerEdge size].width)); + + ninePatch = [[[TUVerticalNinePatch alloc] initWithCenter:center + contentRegion:contentRegion + tileCenterVertically:tileCenterVertically + tileCenterHorizontally:tileCenterHorizontally + upperEdge:upperEdge + lowerEdge:lowerEdge] autorelease]; + } else if (stretchesHorizontally) { + UIImage *center = [image extractCenterForStretchableRegion:fixedStretchableRegion]; + UIImage *leftEdge = [image extractLeftEdgeForStretchableRegion:fixedStretchableRegion]; + UIImage *rightEdge = [image extractRightEdgeForStretchableRegion:fixedStretchableRegion]; + + NPAssertCorrectSubimageWidthDecomposition(image, leftEdge, center, rightEdge); + NPAssertWithinOne(([center size].height),([leftEdge size].height)); + NPAssertWithinOne(([center size].height),([rightEdge size].height)); + NPAssertWithinOne(([leftEdge size].height),([rightEdge size].height)); + + ninePatch = [[[TUHorizontalNinePatch alloc] initWithCenter:center + contentRegion:contentRegion + tileCenterVertically:tileCenterVertically + tileCenterHorizontally:tileCenterHorizontally + leftEdge:leftEdge + rightEdge:rightEdge] autorelease]; + } else { + ninePatch = [[[self alloc] initWithCenter:image + contentRegion:contentRegion + tileCenterVertically:tileCenterVertically + tileCenterHorizontally:tileCenterHorizontally] autorelease]; + } + } + NPAssertNilOrConformsToProtocol(ninePatch,TUNinePatch); + NPOOutputLog(ninePatch); + return ninePatch; +} + +#pragma mark Bundle Loading ++(id < TUNinePatch >)ninePatchNamed:(NSString *)filename { + NPParameterAssertNotNilIsKindOfClass(filename,NSString); + id < TUNinePatch > outPatch = nil; + if (filename) { + NSBundle *mainBundle = [NSBundle mainBundle]; + if (mainBundle) { + NSString *filePath = [mainBundle pathForResource:[NSString stringWithFormat:@"%@.9",filename] + ofType:@"png"]; + if (filePath) { + UIImage *ninepatch = [[UIImage alloc] initWithContentsOfFile:filePath]; + if (ninepatch) { + @try { + outPatch = [self ninePatchWithNinePatchImage:ninepatch]; + } + @catch (NSException * e) { + NPLogException(e); + outPatch = nil; + } + @finally { + [ninepatch release]; + } + } + } + } + } + NPAssertNilOrConformsToProtocol(outPatch,TUNinePatch); + NPOOutputLog(outPatch); + return outPatch; +} + +#pragma mark Nine Patch Image Manipulation - High Level ++(CGRect)rectFromHorizontalRange:(NSRange)horizontalRange verticalRange:(NSRange)verticalRange { + NPAInputLog(@"rectFromHorizontalRange:'%@' verticalRange:'%@'",NSStringFromRange(horizontalRange),NSStringFromRange(verticalRange)); + CGFloat minX = (TUIsNotFoundRange(horizontalRange))?(0.0f):((CGFloat) horizontalRange.location); + CGFloat width = (TUIsNotFoundRange(horizontalRange))?(0.0f):((CGFloat) horizontalRange.length); + CGFloat minY = (TUIsNotFoundRange(verticalRange)?(0.0f):((CGFloat) verticalRange.location)); + CGFloat height = (TUIsNotFoundRange(verticalRange)?(0.0f):((CGFloat) verticalRange.length)); + CGRect outRect = CGRectMake(minX,minY,width,height); + NPCGROutputLog(outRect); + return outRect; +} + ++(CGRect)stretchableRegionOfNinePatchImage:(UIImage *)ninePatchImage { + NPAInputLog(@"stretchableRegionOfNinePatchImage:'%@'",ninePatchImage); + NPParameterAssertNotNilIsKindOfClass(ninePatchImage,UIImage); + CGRect outRect = CGRectZero; + if (ninePatchImage) { + outRect = [self rectFromHorizontalRange:[ninePatchImage blackPixelRangeInUpperStrip] + verticalRange:[ninePatchImage blackPixelRangeInLeftStrip]]; + } + NPCGROutputLog(outRect); + return outRect; +} + ++(CGRect)contentRegionOfNinePatchImage:(UIImage *)ninePatchImage { + NPAInputLog(@"contentRegionOfNinePatchImage:'%@'",ninePatchImage); + NPParameterAssertNotNilIsKindOfClass(ninePatchImage,UIImage); + CGRect outRect = CGRectZero; + if (ninePatchImage) { + outRect = [self rectFromHorizontalRange:[ninePatchImage blackPixelRangeInLowerStrip] + verticalRange:[ninePatchImage blackPixelRangeInRightStrip]]; + } + NPCGROutputLog(outRect); + return outRect; +} + ++(BOOL)shouldTileCenterHorizontallyForNinePatchImage:(UIImage *)ninePatchImage { + NPAInputLog(@"shouldTileCenterHorizontallyForNinePatchImage:'%@'",ninePatchImage); + NPParameterAssertNotNilIsKindOfClass(ninePatchImage,UIImage); + BOOL shouldTileCenterHorizontallyForNinePatchImage = NO; + if (ninePatchImage) { + shouldTileCenterHorizontallyForNinePatchImage = [ninePatchImage upperLeftCornerIsBlackPixel]; + } + NPBOutputLog(shouldTileCenterHorizontallyForNinePatchImage); + return shouldTileCenterHorizontallyForNinePatchImage; +} + ++(BOOL)shouldTileCenterVerticallyForNinePatchImage:(UIImage *)ninePatchImage { + NPAInputLog(@"shouldTileCenterHorizontallyForNinePatchImage:'%@'",ninePatchImage); + NPParameterAssertNotNilIsKindOfClass(ninePatchImage,UIImage); + BOOL shouldTileCenterVerticallyForNinePatchImage = NO; + if (ninePatchImage) { + shouldTileCenterVerticallyForNinePatchImage = [ninePatchImage lowerLeftCornerIsBlackPixel]; + } + NPBOutputLog(shouldTileCenterVerticallyForNinePatchImage); + return shouldTileCenterVerticallyForNinePatchImage; +} + +#pragma mark Drawing Utility +-(void)drawInRect:(CGRect)rect { + if (self.center) { + if (self.tileCenterHorizontally && self.tileCenterVertically) { + [self.center drawAsPatternInRect:rect]; + } else { + // NB: this behavior is not 100% accurate + // in that it only works right for tiling on and off + // half-tiling has to wait + [self.center drawInRect:rect]; + } + } +} + +#pragma mark Diagnostic Utilities +-(UIImage *)upperEdge { + return nil; +} + +-(UIImage *)lowerEdge { + return nil; +} + +-(UIImage *)leftEdge { + return nil; +} + +-(UIImage *)rightEdge { + return nil; +} + +-(UIImage *)upperLeftCorner { + return nil; +} + +-(UIImage *)lowerLeftCorner { + return nil; +} + +-(UIImage *)upperRightCorner { + return nil; +} + +-(UIImage *)lowerRightCorner { + return nil; +} + +#pragma mark TUNinePatch Protocol Methods - Drawing +-(void)inContext:(CGContextRef)context drawAtPoint:(CGPoint)point forContentOfSize:(CGSize)contentSize { + NPParameterAssert(context != nil); + NPAInputLog(@"inContext:'%@' drawAtPoint:'%@' forContentOfSize:'%@'",context,NSStringFromCGPoint(point),NSStringFromCGSize(contentSize)); + CGSize size = [self sizeForContentOfSize:contentSize]; + [self inContext:context + drawInRect:CGRectMake(point.x, point.y, size.width, size.height)]; +} + +-(void)inContext:(CGContextRef)context drawCenteredInRect:(CGRect)rect forContentOfSize:(CGSize)contentSize { + NPParameterAssert(context != nil); + NPAInputLog(@"inContext:'%@' drawCenteredInRect:'%@' forContentOfSize:'%@'",context,NSStringFromCGRect(rect),NSStringFromCGSize(contentSize)); + CGSize size = [self sizeForContentOfSize:contentSize]; + CGFloat xStart = floorf((CGRectGetWidth(rect) - size.width) * 0.5f); + CGFloat yStart = floorf((CGRectGetHeight(rect) - size.height) * 0.5f); + [self inContext:context + drawInRect:CGRectMake(xStart, yStart, size.width, size.height)]; +} + +-(void)inContext:(CGContextRef)context drawInRect:(CGRect)rect { + if (context) { + CGContextSaveGState(context); + CGContextBeginTransparencyLayer(context, nil); + @try { + [self drawInRect:rect]; + } + @catch (NSException * e) { + NPLogException(e); + } + @finally { + CGContextEndTransparencyLayer(context); + CGContextRestoreGState(context); + } + } +} + +#pragma mark TUNinePatch Protocol Methods - Image Construction +-(UIImage *)imageOfSize:(CGSize)size { + UIImage *image = nil; + UIGraphicsBeginImageContext(size); + [self drawInRect:CGRectMake(0.0f,0.0f,size.width,size.height)]; + image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +#pragma mark TUNinePatch Protocol Methods - Sizing +-(BOOL)stretchesHorizontally { + return YES; +} + +-(BOOL)stretchesVertically { + return YES; +} + +#pragma mark - +-(CGFloat)minimumWidth { + CGFloat minimumWidth = 0.0f; + if (self.center) { + minimumWidth = [self.center size].width + self.rightEdgeWidth + self.leftEdgeWidth; + } + return minimumWidth; +} + +-(CGFloat)minimumHeight { + CGFloat minimumHeight = 0.0f; + if (self.center) { + minimumHeight = [self.center size].height + self.upperEdgeHeight + self.lowerEdgeHeight; + } + return minimumHeight; +} + +-(CGSize)minimumSize { + return CGSizeMake([self minimumWidth], [self minimumHeight]); +} + +-(CGSize)sizeForContentOfSize:(CGSize)contentSize { + CGSize outSize = [self minimumSize]; + CGFloat contentRegionWidth = CGRectGetWidth(self.contentRegion); + CGFloat contentRegionHeight = CGRectGetHeight(self.contentRegion); + if ((contentRegionWidth > 0.0f) || (contentRegionHeight > 0.0f)) { + // WE HAVE CONTENT REGION + outSize.width += (contentSize.width > contentRegionWidth)?(contentSize.width - contentRegionWidth):(0.0f); + outSize.height += (contentSize.height > contentRegionHeight)?(contentSize.height - contentRegionHeight):(0.0f); + } else { + // WE ONLY NEED A SIMPLE "WHICH IS BIGGER?" CHECK + outSize.width = (contentSize.width > outSize.width)?(contentSize.width):(outSize.width); + outSize.height = (contentSize.height > outSize.height)?(contentSize.height):(outSize.height); + } + return outSize; +} + +-(CGPoint)upperLeftCornerForContentWhenDrawnAtPoint:(CGPoint)point { + return CGPointMake(point.x + CGRectGetMinX(self.contentRegion), point.y + CGRectGetMinY(self.contentRegion)); +} + +#pragma mark TUNinePatch Protocol Methods - Geometry +-(CGFloat)leftEdgeWidth { + return 0.0f; +} + +-(CGFloat)rightEdgeWidth { + return 0.0f; +} + +-(CGFloat)upperEdgeHeight { + return 0.0f; +} + +-(CGFloat)lowerEdgeHeight { + return 0.0f; +} + +#pragma mark Customized Description +-(NSString *)description { + return [NSString stringWithFormat:@"<%@>:( %@ )",[super description],[self descriptionPostfix]]; +} + +-(NSString *)descriptionPostfix { + return [NSString stringWithFormat:@"center:<'%@'>, contentRegion:<'%@'>", self.center, NSStringFromCGRect(self.contentRegion)]; +} + + +@end \ No newline at end of file diff --git a/NinePatch/TUNinePatchCache.h b/NinePatch/TUNinePatchCache.h new file mode 100755 index 000000000..e42d519d3 --- /dev/null +++ b/NinePatch/TUNinePatchCache.h @@ -0,0 +1,99 @@ +// +// TUNinePatchCache.h +// NinePatch +// +// Copyright 2010 Tortuga 22, Inc. All rights reserved. +// + +#import +#import +#import "TUNinePatchProtocols.h" + +@class TUCachingNinePatch; +/** + + This class is included to make it easy to work with NinePatches if (1) all you want are static images (you don't care much about drawing into CGContextRefs) and (2) . Its semantics are probably non-optimal but are very straightforward: it caches every single request you make to it (both NinePatches and the rendered images). If you're only generating a handful of images and/or you're not super memory-constrained you should probably use this class. It has functionality for flushing the cache with various levels of granularity if you need such functionality. + + One thing that's maybe not so obvious is that the methods on this class span two levels of abstraction and caching. Briefly: + - an instance of TUCachingNinePatch has a ninePatch property (that is a TUNinePatch-implementing object) + - TUCachingNinePatch caches all images it generates + - TUNinePatchCache generates and caches instance of TUCachingNinePatch (which in turn cache images) + + Where the danger zone emerges is ninePatchNamed: this constructs a TUCachingNinePatch (which as part of its construction constructs an object implementing TUNinePatch), caches the TUCachingNinePatch instance, and returns that instance's ninePatch property (which is what actually implements the TUNinePatch protocol). This is in fact the behavior we wanted when we made this library, but it is a little subtle. + + */ +@interface TUNinePatchCache : NSObject { + NSMutableDictionary *_ninePatchCache; +} + +// Synthesized Properties +/** + This is where the NinePatches get cached. You should pretty much never look at or manipulate this directly. If I believed in private instance variables this's be private. Maybe I'll make it private soon. + */ +@property(nonatomic, retain, readonly) NSMutableDictionary *ninePatchCache; + +-(id)init; +/** + Gets at the application's shared instance, creating it if it doesn't exist. + */ ++(id)shared; + +// Getting Ninepatches Directly +/** + Use this method to get at the actual NinePatch you want to interact with (if eg you're using this cache but need finer-grained control than just generating images). Will load the NinePatch (from the app's main bundle) if it doesn't exist yet. + + @param ninePatchName The name of the NinePatch you're trying to get at. + @returns The NinePatch object you wanted. Can return nil if problems were encountered. + */ +-(id < TUNinePatch >)ninePatchNamed:(NSString *)ninePachName; + +// These methods should be private, if I believed in private methods +-(TUCachingNinePatch *)cachingNinePatchNamed:(NSString *)ninePatchName; +-(void)cacheCachingNinePatch:(TUCachingNinePatch *)cachingNinePatch named:(NSString *)ninePatchName; +-(TUCachingNinePatch *)cachedCachingNinePatchNamed:(NSString *)ninePatchName; +-(TUCachingNinePatch *)constructCachingNinePatchNamed:(NSString *)ninePatchName; + +// Getting Images Directly +/** + This method renders the image at the requested size using the NinePatch with the passed-in name. Tries to use a cached image and/or NinePatch as possible, otherwise loading from scratch. Any NinePatch or image it loads is subsequently cached. + + @param size The size the output image should be rendered at. + @param ninePatchName the name of the NinePatch you want to use to render the image. Don't include @".9.png" in the name. + @returns An image rendered from the specified ninePatchName at the requested size. Can return nil if difficulties were encountered. Image should be retained if it is important it be held onto by the recipient, but should not be released by the recipient. + */ +-(UIImage *)imageOfSize:(CGSize)size forNinePatchNamed:(NSString *)ninePatchName; + +// Getting Ninepatches - Convenience +/** + Semantics same as instance-level method of same name, but calls through to the singleton instance. + */ ++(id < TUNinePatch >)ninePatchNamed:(NSString *)ninePatchName; + +// Getting Images - Convenience +/** + This is a convenience method; calls instance method of the same name on the singleton. Easiest way to use this in your code. + */ ++(UIImage *)imageOfSize:(CGSize)size forNinePatchNamed:(NSString *)ninePatchName; + +// Cache Management - Direct +/** + Flushes all cached content (NinePatches AND their cached rendered images, if any). + */ +-(void)flushCache; +/** + Flushes only the content for the NinePatch with the passed-in name. Won't complain if there's no cached NinePatch with the passed-in name. + */ +-(void)flushCacheForNinePatchNamed:(NSString *)name; + +// Cache Management - Convenience +/** + Flushes all cached content from the singleton. + */ ++(void)flushCache; + +/** + Flushes the NinePatch with the passed-in name from the singleton (which also flushes any cached images). + */ ++(void)flushCacheForNinePatchNamed:(NSString *)name; + +@end diff --git a/NinePatch/TUNinePatchCache.m b/NinePatch/TUNinePatchCache.m new file mode 100755 index 000000000..bd6cb9c8b --- /dev/null +++ b/NinePatch/TUNinePatchCache.m @@ -0,0 +1,133 @@ +// +// TUNinePatchCache.m +// NinePatch +// +// Copyright 2010 Tortuga 22, Inc. All rights reserved. +// + +#import "TUNinePatchCache.h" +#import "TUCachingNinePatch.h" +#import "TUNinePatch.h" + +@interface TUNinePatchCache () + +@property(nonatomic, retain, readwrite) NSMutableDictionary *ninePatchCache; + +@end + + +@implementation TUNinePatchCache + +#pragma mark Synthesized Properties +@synthesize ninePatchCache = _ninePatchCache; + +#pragma mark Init + Dealloc +-(id)init { + if (self = [super init]) { + self.ninePatchCache = [NSMutableDictionary dictionary]; + } + return self; +} + +#pragma mark - ++(id)shared { + static TUNinePatchCache *shared; + if (!shared) { + shared = [[self alloc] init]; + } + return shared; +} + +#pragma mark - +-(void)dealloc { + self.ninePatchCache = nil; + [super dealloc]; +} + +#pragma mark Getting Ninepatches Directly +// Getting Ninepatches Directly +-(id < TUNinePatch >)ninePatchNamed:(NSString *)ninePatchName { + TUCachingNinePatch *cachingNinePatch = [self cachingNinePatchNamed:ninePatchName]; + NPAssertNilOrIsKindOfClass(cachingNinePatch,TUCachingNinePatch); + return (!cachingNinePatch)?(nil):([cachingNinePatch ninePatch]); +} + + +-(TUCachingNinePatch *)cachingNinePatchNamed:(NSString *)ninePatchName { + TUCachingNinePatch *cachingNinePatch = [self cachedCachingNinePatchNamed:ninePatchName]; + NPAssertNilOrIsKindOfClass(cachingNinePatch,TUCachingNinePatch); + if (!cachingNinePatch) { + cachingNinePatch = [self constructCachingNinePatchNamed:ninePatchName]; + NPAssertNilOrIsKindOfClass(cachingNinePatch,TUCachingNinePatch); + if (cachingNinePatch) { + [self cacheCachingNinePatch:cachingNinePatch + named:ninePatchName]; + } + } + return cachingNinePatch; +} + +-(void)cacheCachingNinePatch:(TUCachingNinePatch *)cachingNinePatch named:(NSString *)ninePatchName { + NPAssertPropertyNonNil(ninePatchCache); + if (cachingNinePatch && ninePatchName) { + [self.ninePatchCache setObject:cachingNinePatch + forKey:ninePatchName]; + } +} + +-(TUCachingNinePatch *)cachedCachingNinePatchNamed:(NSString *)ninePatchName { + return (!ninePatchName)?(nil):([self.ninePatchCache objectForKey:ninePatchName]); +} + +-(TUCachingNinePatch *)constructCachingNinePatchNamed:(NSString *)ninePatchName { + return (!ninePatchName)?(nil):([TUCachingNinePatch ninePatchCacheWithNinePatchNamed:ninePatchName]); +} + +#pragma mark Getting Images Directly +-(UIImage *)imageOfSize:(CGSize)size forNinePatchNamed:(NSString *)ninePatchName { + NPParameterAssertNotNilIsKindOfClass(ninePatchName,NSString); + UIImage *image = nil; + TUCachingNinePatch *cachingNinePatch = [self cachingNinePatchNamed:ninePatchName]; + if (cachingNinePatch) { + image = [cachingNinePatch imageOfSize:size]; + } + return image; +} + +#pragma mark Getting Ninepatches - Convenience ++(TUCachingNinePatch *)cachingNinePatchNamed:(NSString *)ninePatchName { + return [[self shared] ninePatchNamed:ninePatchName]; +} + ++(id < TUNinePatch >)ninePatchNamed:(NSString *)ninePatchName { + TUCachingNinePatch *cachingNinePatch = [[self shared] cachingNinePatchNamed:ninePatchName]; + NPAssertNilOrIsKindOfClass(cachingNinePatch,TUCachingNinePatch); + return (!cachingNinePatch)?(nil):([cachingNinePatch ninePatch]); +} + +#pragma mark Getting Images - Convenience ++(UIImage *)imageOfSize:(CGSize)size forNinePatchNamed:(NSString *)ninePatchName { + return [[self shared] imageOfSize:size + forNinePatchNamed:ninePatchName]; +} + +#pragma mark Cache Management - Direct +-(void)flushCache { + [self.ninePatchCache removeAllObjects]; +} +-(void)flushCacheForNinePatchNamed:(NSString *)name { + if (name) { + [self.ninePatchCache removeObjectForKey:name]; + } +} + +#pragma mark Cache Management - Convenience ++(void)flushCache { + [[self shared] flushCache]; +} + ++(void)flushCacheForNinePatchNamed:(NSString *)name { + [[self shared] flushCacheForNinePatchNamed:name]; +} + +@end \ No newline at end of file diff --git a/NinePatch/TUNinePatchCachingCategories.h b/NinePatch/TUNinePatchCachingCategories.h new file mode 100755 index 000000000..7f897495b --- /dev/null +++ b/NinePatch/TUNinePatchCachingCategories.h @@ -0,0 +1,28 @@ +// +// TUNinePatchCachingCategories.h +// NinePatch +// +// Copyright 2010 Tortuga 22, Inc. All rights reserved. +// + +#import +#import +#import + +@interface NSString (NinePatchCaching) + ++(NSString *)ninePatchKeyStringForSize:(CGSize)size; + +@end + +@interface NSDictionary (NinePatchCaching) + +-(id)objectForSize:(CGSize)size; + +@end + +@interface NSMutableDictionary (NinePatchCaching) + +-(void)setObject:(id)object forSize:(CGSize)size; + +@end diff --git a/NinePatch/TUNinePatchCachingCategories.m b/NinePatch/TUNinePatchCachingCategories.m new file mode 100755 index 000000000..7ec6d81af --- /dev/null +++ b/NinePatch/TUNinePatchCachingCategories.m @@ -0,0 +1,52 @@ +// +// TUNinePatchCachingCategories.m +// NinePatch +// +// Copyright 2010 Tortuga 22, Inc. All rights reserved. +// + +#import "TUNinePatchCachingCategories.h" + +@implementation NSString (NinePatchCaching) + +/** + It's not clear we can't just use NSStringFromCGSize. This might get cut in a future revision. + */ ++(NSString *)ninePatchKeyStringForSize:(CGSize)size { + return [NSString stringWithFormat:@"ninePatchKeyString.%#.0f.%#.0f",size.width,size.height]; +} + +@end + +@implementation NSDictionary (NinePatchCaching) + +/** + Convenience method to make it a little less annoying to pull objects out of the caches keyed by their size. + */ +-(id)objectForSize:(CGSize)size { + id object = nil; + NSString *key = [NSString ninePatchKeyStringForSize:size]; + if (key) { + object = [self objectForKey:key]; + } + return object; +} + +@end + +@implementation NSMutableDictionary (NinePatchCaching) + +/** + Convenience method to make it a little less annoying to put objects in the caches keyed by their size. + */ +-(void)setObject:(id)object forSize:(CGSize)size { + if (object) { + NSString *key = [NSString ninePatchKeyStringForSize:size]; + if (key) { + [self setObject:object + forKey:key]; + } + } +} + +@end diff --git a/NinePatch/TUNinePatchProtocols.h b/NinePatch/TUNinePatchProtocols.h new file mode 100755 index 000000000..f3889b1ad --- /dev/null +++ b/NinePatch/TUNinePatchProtocols.h @@ -0,0 +1,121 @@ +// +// TUNinePatch.h +// NinePatch +// +// Copyright 2009 Tortuga 22, Inc. All rights reserved. +// + +#import +#import + +/** + + Defines the methods shared by all concrete NinePatch classes. Expect many of these methods to be removed from the + protocol as the library is improved. + + */ +@protocol TUNinePatch < NSObject, NSCoding, NSCopying > + +// TUNinePatch Protocol Methods - Drawing +/** + This draws the NinePatch with its upper-left corner at a specified location in a specified context, scaled to fit content of the size. This is a good method to use when drawing NinePatches that use the content bounds. This method will probably be improved in the future to return the upper-left corner for the content to be drawn. + + @param context A non-nil CGContextRef into which NinePatch will be drawn. + @param point The upper left corner the NinePatch will be drawn at. + @param size The size of the content the NinePatch will be sized to contain. + */ +-(void)inContext:(CGContextRef)context drawAtPoint:(CGPoint)point forContentOfSize:(CGSize)size; + +/** + This draws the NinePatch centered (horizontally and vertically) inside the containmentRect in the specified context, sized to fit content of the passed-in size. This is essentially a convenience method wrapping inContext:drawAtPoint:forContentOfSize:. Like its cousin it'll probably be modified to return information about where the content should be drawn. Will let the NinePatch overflow the containmentRect if the necessary size is sufficiently large; it'll be centered, just too big. + + @param context A non-nil CGContextRef into which NinePatch will be drawn. + @param containmentRect the rect in which the NinePatch will be centered. + @param size The size of the content the NinePatch will be sized to contain. + */ +-(void)inContext:(CGContextRef)context drawCenteredInRect:(CGRect)containmentRect forContentOfSize:(CGSize)size; + +/** + This method draws the NinePatch into the passed-in context filling the passed-in rect. In all current implementations of TUNinePatch the other drawing utilities eventually call through this method to do their actual drawing. + + @param context A non-nil CGContextRef into which NinePatch will be drawn. + @param rect The rect into which the NinePatch will be drawn. NinePatch is scaled to fill the rect. + */ +-(void)inContext:(CGContextRef)context drawInRect:(CGRect)rect; + +// TUNinePatch Protocol Methods - Image Construction +/** + Renders the NinePatch into an image of the requested size. Implementations vary in their memory management here -- to guarantee the returned image hangs around you should retain it. + + @param size The size the output UIImage should be. + @returns A UIImage rendering of the NinePatch scaled like the passed-in size. + */ +-(UIImage *)imageOfSize:(CGSize)size; + +// TUNinePatch Protocol Methods - Sizing +/** + Returns YES if the NinePatch scales horizontally, NO otherwise. May be removed from protocol in future. + */ +-(BOOL)stretchesHorizontally; +/** + Returns YES if the NinePatch scales vertically, NO otherwise. May be removed from protocol in future. + */ +-(BOOL)stretchesVertically; + +/** + Returns smallest horizontal size you scan scale NinePatch down to. This is usually equal to the leftEdge + the rightEdge (sending central column width -> 0). May be removed from protocol in future. + + @returns minimumWidth the smallest width NinePatch will render at. + */ +-(CGFloat)minimumWidth; + +/** + Returns smallest vertical size you scan scale NinePatch down to. This is usually equal to the topEdge + bottomEdge (sending central row height -> 0). May be removed from protocol in future. + + @returns minimumHeight the smallest height NinePatch will render at. + */ +-(CGFloat)minimumHeight; + +/** + Returns CGSize created from minimumWidth and minimumHeight in the obvious way. + */ +-(CGSize)minimumSize; + +/** + This is used for layout. Recall that NinePatch allows you to specify a "content region" (into which the content must fit) as a way of standardizing the amount of padding you do when using a particular NinePatch. This method returns the size the NinePatch needs to be drawn at to correctly accommodate content of the passed-in size. Note that for NinePatches that don't specify anything wrt content size this is the identity function. + + @param contentSize The size of the content you're using the NinePatch to display. + @returns The size you need to draw the NinePatch at to accommodate the content. + */ +-(CGSize)sizeForContentOfSize:(CGSize)contentSize; + +/** + This is used for layout. This is basically a convenience function to calculate the offset for where "content" starts. It's the identity function when the NinePatch doesn't specify anything for the content region. + + @param point The point at which you're planning to place the upper left corner of the NinePatch when you draw it. + @returns The point at which you need to place the upper left corner of the content you're going to draw. + */ +-(CGPoint)upperLeftCornerForContentWhenDrawnAtPoint:(CGPoint)point; + +// TUNinePatch Protocol Methods - Geometry +/** + The width of the left column. This geometric property lookup is probably going to get removed from the protocol. + */ +-(CGFloat)leftEdgeWidth; + +/** + The width of the right column. This geometric property lookup is probably going to get removed from the protocol. + */ +-(CGFloat)rightEdgeWidth; + +/** + The width of the upper row. This geometric property lookup is probably going to get removed from the protocol. + */ +-(CGFloat)upperEdgeHeight; + +/** + The width of the lower column. This geometric property lookup is probably going to get removed from the protocol. + */ +-(CGFloat)lowerEdgeHeight; + +@end \ No newline at end of file diff --git a/NinePatch/TUVerticalNinePatch.h b/NinePatch/TUVerticalNinePatch.h new file mode 100755 index 000000000..c9c266f28 --- /dev/null +++ b/NinePatch/TUVerticalNinePatch.h @@ -0,0 +1,36 @@ +// +// TUVerticalNinePatch.h +// NinePatch +// +// Copyright 2009 Tortuga 22, Inc. All rights reserved. +// + +#import +#import +#import "TUNinePatch.h" +#import "TUNinePatchProtocols.h" + +/** + Concrete TUNinePatch instance. Handles NinePatches that only stretch vertically. Only instantiate directly if you know what you're doing. + */ +@interface TUVerticalNinePatch : TUNinePatch < TUNinePatch > { + UIImage *_upperEdge; + UIImage *_lowerEdge; +} + +// Synthesized Properties +@property(nonatomic, retain, readonly) UIImage *upperEdge; +@property(nonatomic, retain, readonly) UIImage *lowerEdge; + +// Init + Dealloc +-(id)initWithCenter:(UIImage *)center contentRegion:(CGRect)contentRegion tileCenterVertically:(BOOL)tileCenterVertically tileCenterHorizontally:(BOOL)tileCenterHorizontally upperEdge:(UIImage *)upperEdge lowerEdge:(UIImage *)lowerEdge; +-(void)dealloc; + +// TUNinePatch Overrides +-(void)drawInRect:(CGRect)rect; +-(BOOL)stretchesHorizontally; +-(CGSize)sizeForContentOfSize:(CGSize)contentSize; +-(CGFloat)upperEdgeHeight; +-(CGFloat)lowerEdgeHeight; + +@end \ No newline at end of file diff --git a/NinePatch/TUVerticalNinePatch.m b/NinePatch/TUVerticalNinePatch.m new file mode 100755 index 000000000..f2363050c --- /dev/null +++ b/NinePatch/TUVerticalNinePatch.m @@ -0,0 +1,132 @@ +// +// TUVerticalNinePatch.m +// NinePatch +// +// Copyright 2009 Tortuga 22, Inc. All rights reserved. +// + +#import "TUVerticalNinePatch.h" + +@interface TUVerticalNinePatch () + +// Synthesized Properties +@property(nonatomic, retain, readwrite) UIImage *upperEdge; +@property(nonatomic, retain, readwrite) UIImage *lowerEdge; + +@end + + +@implementation TUVerticalNinePatch + +#pragma mark Synthesized Properties +@synthesize upperEdge = _upperEdge; +@synthesize lowerEdge = _lowerEdge; + +#pragma mark NSCoding +-(id)initWithCoder:(NSCoder *)coder { + if (self = [super initWithCoder:coder]) { + self.upperEdge = (UIImage *)[coder decodeObjectForKey:@"upperEdge"]; + self.lowerEdge = (UIImage *)[coder decodeObjectForKey:@"lowerEdge"]; + } + return self; +} + +-(void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + + [coder encodeObject:self.upperEdge + forKey:@"upperEdge"]; + + [coder encodeObject:self.lowerEdge + forKey:@"lowerEdge"]; +} + +#pragma mark NSCopying +-(id)copyWithZone:(NSZone *)zone { + return [[[self class] allocWithZone:zone] initWithCenter:self.center + contentRegion:self.contentRegion + tileCenterVertically:self.tileCenterVertically + tileCenterHorizontally:self.tileCenterHorizontally + upperEdge:self.upperEdge + lowerEdge:self.lowerEdge]; +} + +#pragma mark Init + Dealloc +-(id)initWithCenter:(UIImage *)center contentRegion:(CGRect)contentRegion tileCenterVertically:(BOOL)tileCenterVertically tileCenterHorizontally:(BOOL)tileCenterHorizontally upperEdge:(UIImage *)upperEdge lowerEdge:(UIImage *)lowerEdge { + NPParameterAssertNotNilIsKindOfClass(upperEdge,UIImage); + NPParameterAssertNotNilIsKindOfClass(lowerEdge,UIImage); + if (self = [super initWithCenter:center + contentRegion:contentRegion + tileCenterVertically:tileCenterVertically + tileCenterHorizontally:tileCenterHorizontally]) { + self.upperEdge = upperEdge; + self.lowerEdge = lowerEdge; + } + return self; +} + +#pragma mark - +-(void)dealloc { + self.upperEdge = nil; + self.lowerEdge = nil; + [super dealloc]; +} + +#pragma mark TUNinePatch Overrides +-(void)drawInRect:(CGRect)rect { + NPSelfProperty(center); + NPSelfProperty(upperEdge); + NPSelfProperty(lowerEdge); + CGFloat width = [self minimumWidth]; + [self.center drawInRect:CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect) + [self upperEdgeHeight], width, CGRectGetHeight(rect) - ([self upperEdgeHeight] + [self lowerEdgeHeight]))]; + if (self.upperEdge) { + [self.upperEdge drawAtPoint:CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect))]; + } + if (self.lowerEdge) { + [self.lowerEdge drawAtPoint:CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect) - [self lowerEdgeHeight])]; + } +} + +#pragma mark - +-(BOOL)stretchesHorizontally { + return NO; +} + +-(CGSize)sizeForContentOfSize:(CGSize)contentSize { + NPAInputLog(@"sizeForContentOfSize:'%@'",NSStringFromCGSize(contentSize)); + CGSize outSize = [super sizeForContentOfSize:contentSize]; + outSize.width = [self minimumWidth]; + NPCGSOutputLog(outSize); + return outSize; +} + +#pragma mark - +-(CGFloat)upperEdgeHeight { + NPSelfProperty(upperEdge); + CGFloat upperEdgeHeight = 0.0f; + if (self.upperEdge) { + upperEdgeHeight = [self.upperEdge size].height; + } + NPFOutputLog(upperEdgeHeight); + return upperEdgeHeight; +} + +-(CGFloat)lowerEdgeHeight { + NPSelfProperty(lowerEdge); + CGFloat lowerEdgeHeight = 0.0f; + if (self.lowerEdge) { + lowerEdgeHeight = [self.lowerEdge size].height; + } + NPFOutputLog(lowerEdgeHeight); + return lowerEdgeHeight; +} + +#pragma mark Customized Description Overrides +-(NSString *)descriptionPostfix { + return [NSString stringWithFormat:@"%@, self.upperEdge:<'%@'>, self.lowerEdge:<'%@'>", + [super descriptionPostfix], + self.upperEdge, + self.lowerEdge]; +} + +@end \ No newline at end of file diff --git a/NinePatch/UIImage-TUNinePatch.h b/NinePatch/UIImage-TUNinePatch.h new file mode 100755 index 000000000..78ba6fe67 --- /dev/null +++ b/NinePatch/UIImage-TUNinePatch.h @@ -0,0 +1,77 @@ +// +// UIImage-TUNinePatch.h +// NinePatch +// +// Copyright 2009 Tortuga 22, Inc. All rights reserved. +// + +#import +#import +#import "TUNinePatchProtocols.h" + +void TUImageLog(UIImage *image, NSString *imageName); +/** + This category implements all the image-slicing, pixel-tasting, and similar image-manipulation and image-analysis functions. These are only used in methods that'll probably become private real soon now, so maybe a "not much to see here" sign is called for. + */ +@interface UIImage (TUNinePatch) + +// Black Pixel Searching - Corners +-(BOOL)upperLeftCornerIsBlackPixel; +-(BOOL)upperRightCornerIsBlackPixel; +-(BOOL)lowerLeftCornerIsBlackPixel; +-(BOOL)lowerRightCornerIsBlackPixel; + +// Pixel Tasting - Single Pixel +-(BOOL)isBlackPixel; + +// Black Pixel Searching - Strips +-(NSRange)blackPixelRangeInUpperStrip; +-(NSRange)blackPixelRangeInLowerStrip; +-(NSRange)blackPixelRangeInLeftStrip; +-(NSRange)blackPixelRangeInRightStrip; + +// Pixel Tasting - Strips +-(NSRange)blackPixelRangeAsVerticalStrip; +-(NSRange)blackPixelRangeAsHorizontalStrip; + +// Corners - Rects +-(CGRect)upperLeftCornerRect; +-(CGRect)lowerLeftCornerRect; +-(CGRect)upperRightCornerRect; +-(CGRect)lowerRightCornerRect; + +// Corners - Slicing +-(UIImage *)upperLeftCorner; +-(UIImage *)lowerLeftCorner; +-(UIImage *)upperRightCorner; +-(UIImage *)lowerRightCorner; + +// Strips - Sizing +-(CGRect)upperStripRect; +-(CGRect)lowerStripRect; +-(CGRect)leftStripRect; +-(CGRect)rightStripRect; + +// Strips - Slicing +-(UIImage *)upperStrip; +-(UIImage *)lowerStrip; +-(UIImage *)leftStrip; +-(UIImage *)rightStrip; + +// Subimage Slicing +-(UIImage *)subImageInRect:(CGRect)rect; + +// Nine-Patch Content Extraction +-(UIImage *)imageAsNinePatchImage; + +-(UIImage *)extractUpperLeftCornerForStretchableRegion:(CGRect)stretchableRegion; +-(UIImage *)extractUpperRightCornerForStretchableRegion:(CGRect)stretchableRegion; +-(UIImage *)extractLowerLeftCornerForStretchableRegion:(CGRect)stretchableRegion; +-(UIImage *)extractLowerRightCornerForStretchableRegion:(CGRect)stretchableRegion; +-(UIImage *)extractLeftEdgeForStretchableRegion:(CGRect)stretchableRegion; +-(UIImage *)extractRightEdgeForStretchableRegion:(CGRect)stretchableRegion; +-(UIImage *)extractUpperEdgeForStretchableRegion:(CGRect)stretchableRegion; +-(UIImage *)extractLowerEdgeForStretchableRegion:(CGRect)stretchableRegion; +-(UIImage *)extractCenterForStretchableRegion:(CGRect)stretchableRegion; + +@end diff --git a/NinePatch/UIImage-TUNinePatch.m b/NinePatch/UIImage-TUNinePatch.m new file mode 100755 index 000000000..4f9d16095 --- /dev/null +++ b/NinePatch/UIImage-TUNinePatch.m @@ -0,0 +1,539 @@ +// +// UIImage-TUNinePatch.m +// NinePatch +// +// Copyright 2009 Tortuga 22, Inc. All rights reserved. +// + +#import "UIImage-TUNinePatch.h" +#import "TUNinePatchProtocols.h" + +void TUImageLog(UIImage *image, NSString *imageName) { + if (image && imageName) { + NSString *fullFileName = [imageName stringByAppendingString:@".png"]; + if (fullFileName) { + NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; + if (documentsDirectory) { + NSString *fullFilePath = [documentsDirectory stringByAppendingPathComponent:fullFileName]; + if (fullFilePath) { + NSData *pngData = UIImagePNGRepresentation(image); + if (pngData) { + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (fileManager) { + BOOL succeeded = [fileManager createFileAtPath:fullFilePath contents:pngData attributes:nil]; + if (succeeded) { + DLog(@"Seemingly successfully wrote image to file at: '%@'.",fullFilePath); + } else { + DLog(@"Seemingly failed to write image to file at: '%@'.",fullFilePath); + } + } else { + LLog(@"Couldn't get default fileManager, aborting imagelog."); + } + } else { + LLog(@"Couldn't get PNGRepresentation, aborting imagelog."); + } + } else { + LLog(@"Couldn't get fullFilePath, aborting imagelog."); + } + } else { + LLog(@"Couldn't get fullFilePath, aborting imagelog."); + } + } else { + DLog(@"Could't get fullFileName, aborting imageLog."); + } + } else { + DLog(@"Can't log image: '%@', imageName: '%@', as one or both are nil.",image, imageName); + } +} + +@implementation UIImage (TUNinePatch) + +#pragma mark Black Pixel Searching - Corners +-(BOOL)upperLeftCornerIsBlackPixel { + BOOL upperLeftCornerIsBlackPixel = NO; + UIImage *upperLeftCorner = [self upperLeftCorner]; + if (upperLeftCorner) { + upperLeftCornerIsBlackPixel = [upperLeftCorner isBlackPixel]; + } + NPBOutputLog(upperLeftCornerIsBlackPixel); + return upperLeftCornerIsBlackPixel; +} + +-(BOOL)upperRightCornerIsBlackPixel { + BOOL upperRightCornerIsBlackPixel = NO; + UIImage *upperRightCorner = [self upperRightCorner]; + if (upperRightCorner) { + upperRightCornerIsBlackPixel = [upperRightCorner isBlackPixel]; + } + NPBOutputLog(upperRightCornerIsBlackPixel); + return upperRightCornerIsBlackPixel; +} + +-(BOOL)lowerLeftCornerIsBlackPixel { + BOOL lowerLeftCornerIsBlackPixel = NO; + UIImage *lowerLeftCorner = [self lowerLeftCorner]; + if (lowerLeftCorner) { + lowerLeftCornerIsBlackPixel = [lowerLeftCorner isBlackPixel]; + } + NPBOutputLog(lowerLeftCornerIsBlackPixel); + return lowerLeftCornerIsBlackPixel; +} + +-(BOOL)lowerRightCornerIsBlackPixel { + BOOL lowerRightCornerIsBlackPixel = NO; + UIImage *lowerRightCorner = [self lowerRightCorner]; + if (lowerRightCorner) { + lowerRightCornerIsBlackPixel = [lowerRightCorner isBlackPixel]; + } + NPBOutputLog(lowerRightCornerIsBlackPixel); + return lowerRightCornerIsBlackPixel; +} + +#pragma mark Pixel Tasting - Single Pixel +-(BOOL)isBlackPixel { + NPAssert(([self size].width > 0.0f), @"Should have width > 0.0f"); + NPAssert(([self size].height > 0.0f), @"Should have height > 0.0f"); + BOOL isBlackPixel = NO; + if (([self size].width > 0.0f) && ([self size].height > 0.0f)) { + CGImageRef cgImage = [self CGImage]; + NSUInteger width = CGImageGetWidth(cgImage); + NSUInteger height = CGImageGetHeight(cgImage); + NSUInteger bytesPerRow = width * TURGBABytesPerPixel; + NSUInteger bitsPerComponent = 8; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + UInt8 *pixelByteData = malloc(width * height * TURGBABytesPerPixel); + + + CGContextRef context = CGBitmapContextCreate( + (void *)pixelByteData, + width, + height, + bitsPerComponent, + bytesPerRow, + colorSpace, + kCGImageAlphaPremultipliedLast); + + CGContextDrawImage(context, CGRectMake(0.0f,0.0f,1.0f,1.0f), cgImage); + TURGBAPixel *pixelData = (TURGBAPixel *) CGBitmapContextGetData(context); + if (pixelData) { + isBlackPixel = TURGBAPixelIsBlack(pixelData[0]); + } + CGContextRelease(context); + CGColorSpaceRelease(colorSpace); + free(pixelByteData); + } + NPBOutputLog(isBlackPixel); + return isBlackPixel; +} + +#pragma mark Black Pixel Searching - Strips +-(NSRange)blackPixelRangeInUpperStrip { + NSRange blackPixelRangeInUpperStrip = TUNotFoundRange; + UIImage *upperStrip = [self upperStrip]; + if (upperStrip) { + blackPixelRangeInUpperStrip = [upperStrip blackPixelRangeAsHorizontalStrip]; + } + NPNSROutputLog(blackPixelRangeInUpperStrip); + return blackPixelRangeInUpperStrip; +} + +-(NSRange)blackPixelRangeInLowerStrip { + NSRange blackPixelRangeInLowerStrip = TUNotFoundRange; + UIImage *lowerStrip = [self lowerStrip]; + if (lowerStrip) { + blackPixelRangeInLowerStrip = [lowerStrip blackPixelRangeAsHorizontalStrip]; + } + NPNSROutputLog(blackPixelRangeInLowerStrip); + return blackPixelRangeInLowerStrip; +} + +-(NSRange)blackPixelRangeInLeftStrip { + NSRange blackPixelRangeInLeftStrip = TUNotFoundRange; + UIImage *leftStrip = [self leftStrip]; + if (leftStrip) { + blackPixelRangeInLeftStrip = [leftStrip blackPixelRangeAsVerticalStrip]; + } + NPNSROutputLog(blackPixelRangeInLeftStrip); + return blackPixelRangeInLeftStrip; +} + +-(NSRange)blackPixelRangeInRightStrip { + NSRange blackPixelRangeInRightStrip = TUNotFoundRange; + UIImage *rightStrip = [self rightStrip]; + if (rightStrip) { + blackPixelRangeInRightStrip = [rightStrip blackPixelRangeAsVerticalStrip]; + } + NPNSROutputLog(blackPixelRangeInRightStrip); + return blackPixelRangeInRightStrip; +} + +#pragma mark Pixel Tasting - Strips +-(NSRange)blackPixelRangeAsVerticalStrip { + NPAssert([self size].width == 1.0f, @"This method assumes the image has width == 1.0f"); + NSRange blackPixelRangeAsVerticalStrip = TUNotFoundRange; + NSUInteger firstBlackPixel = NSNotFound; + NSUInteger lastBlackPixel = NSNotFound; + if ([self size].height > 0.0f) { + CGImageRef cgImage = [self CGImage]; + + NSUInteger width = CGImageGetWidth(cgImage); + NSUInteger height = CGImageGetHeight(cgImage); + NSUInteger bytesPerRow = width * TURGBABytesPerPixel; + NSUInteger bitsPerComponent = 8; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + UInt8 *pixelByteData = malloc(width * height * TURGBABytesPerPixel); + + CGContextRef context = CGBitmapContextCreate( + (void *)pixelByteData, + width, + height, + bitsPerComponent, + bytesPerRow, + colorSpace, + kCGImageAlphaPremultipliedLast); + + // NEW: seeing nondetermnistic errors where sometimes the image is parsed right + // and sometimes not parsed right. The followthing three lines paint the context + // to solid white, then paste the image over it, so this ought to normalize the + // outcome a bit more. + CGRect contextBounds = CGRectMake(0.0f, 0.0f, width, height); + CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]); + CGContextFillRect(context, contextBounds); + + // Having normalized the context we now paint the image + CGContextDrawImage(context, contextBounds, cgImage); + TURGBAPixel *pixelData = (TURGBAPixel *) CGBitmapContextGetData(context); + if (pixelData) { + // CF note in the AsHorizontal method below + for (NSUInteger i = 0; i < height; i++) { + if (TURGBAPixelIsBlack(pixelData[((height - 1) - i)])) { + firstBlackPixel = ((height - 1) - i); + } + if (TURGBAPixelIsBlack(pixelData[i])) { + lastBlackPixel = i; + } + } + + if ((firstBlackPixel != NSNotFound) && (lastBlackPixel != NSNotFound)) { + NPAssert(lastBlackPixel >= firstBlackPixel, ([NSString stringWithFormat:@"Got firstBlackPixel:'%d' and lastBlackPixel:'%d'.",firstBlackPixel,lastBlackPixel])); + blackPixelRangeAsVerticalStrip.location = TUTruncateWithin(firstBlackPixel, 0, height - 1) / self.scale; + // We can't just use TUTruncateAtZero on lastBlackPixel - firstBlackPixel here. + // The semantics of pixel coordinates are such that a zero difference between lastBlackPixel and firstBlackPixel is ok + // but < 0 is obv. very bad. + // Thus 1 + TUTruncateAtZero(lastBlackPixel - firstBlackPixel) won't work. + // and fixing the expression s.t. it does work is more complicated than + // just breaking it down like so. + NSInteger length = lastBlackPixel - firstBlackPixel; + if (length >= 0) { + length += 1; + } else { + length = 0; + } + blackPixelRangeAsVerticalStrip.length = length/self.scale; + } + } + CGContextRelease(context); + CGColorSpaceRelease(colorSpace); + free(pixelByteData); + } + NPNSROutputLog(blackPixelRangeAsVerticalStrip); + return blackPixelRangeAsVerticalStrip; +} + +-(NSRange)blackPixelRangeAsHorizontalStrip { + NPAssert([self size].height == 1.0f, @"This method assumes the image has height == 1.0f"); + NSRange blackPixelRangeAsHorizontalStrip = TUNotFoundRange; + NSUInteger firstBlackPixel = NSNotFound; + NSUInteger lastBlackPixel = NSNotFound; + if ([self size].width > 0.0f) { + CGImageRef cgImage = [self CGImage]; + + NSUInteger width = CGImageGetWidth(cgImage); + NSUInteger height = CGImageGetHeight(cgImage); + NSUInteger bytesPerRow = width * TURGBABytesPerPixel; + NSUInteger bitsPerComponent = 8; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + UInt8 *pixelByteData = malloc(width * height * TURGBABytesPerPixel); + + CGContextRef context = CGBitmapContextCreate( + (void *)pixelByteData, + width, + height, + bitsPerComponent, + bytesPerRow, + colorSpace, + kCGImageAlphaPremultipliedLast); + + // NEW: seeing nondetermnistic errors where sometimes the image is parsed right + // and sometimes not parsed right. The followthing three lines paint the context + // to solid white, then paste the image over it, so this ought to normalize the + // outcome a bit more. + CGRect contextBounds = CGRectMake(0.0f, 0.0f, width, height); + CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]); + CGContextFillRect(context, contextBounds); + + // Having normalized the context we now paint the image + CGContextDrawImage(context, contextBounds, cgImage); + TURGBAPixel *pixelData = (TURGBAPixel *) CGBitmapContextGetData(context); + if (pixelData) { + // The for loop below is walking the strip from both ends. + // Basically you could do this check a bunch of ways, with a + // bunch of trade-offs in terms of how fast it is and how robust it + // is and how any "format errors" in your nine patch manifest. + // + // What I have found is that ninepatch is a fussy format, with a + // common failure mode being that you painted a pixel "black" but + // either got the alpha wrong, or it wasn't quite black, or it + // didn't composite to black, etc., and thus get invalid ninepatches. + // + // What I do here is just look for the highest and lowest black pixels, + // and treat anything in between as also black. EG: + // + // - if X == black and O == not-black + // - then these square brackes - [ and ] - enclose the "black" region + // + // - then: OOOOXXXXXOOOOO -> OOOO[XXXXX]OOOOO + // - but also: OOOXXOOXXOOO -> OOO[XXOOXX]OOO + // - and even: OXOOOOOOOXO -> O[XOOOOOOOX]O + // + // This is a judgement call on my part, in that the approach I can take to + // accomplish this is straightforward without any complicated state tracking, + // and the behavior it has in the face of "invalid" nine-patches is generally + // what I meant, anyways. + // + // The actual implementation is straightforward but suboptimal. + // I look through the array once, iterating i from 0->(width -1). + // On each iteration I taste the pixel @ i and at (width - 1) -1, + // and if the pixel @ i is black I set the "lastBlackPixel" == i + // and if the pixel @ (width - 1) - i is black I set the "firstBlackPixel" + // to (width - 1) - i. + // + // Once the loop is done the values in the lastBlackPixel and firstBlackPixel + // contain what they ought to have. + // + // Given the continual risk of hard-to-spot off-by-one errors throughout this + // library I've opted to keep it dumb and suboptimal in places like this one, + // so that I can be more comfortable that what problems there are are elsewhere. + // + // If you subseqently do add an improved loop please wrap it in ifdefs like + // #ifdef CLEVERNESS YOUR-CODE #else DUMB-CODE #endif + // + for (NSUInteger i = 0; i < width; i++) { + if (TURGBAPixelIsBlack(pixelData[((width - 1) - i)])) { + firstBlackPixel = ((width - 1) - i); + } + if (TURGBAPixelIsBlack(pixelData[i])) { + lastBlackPixel = i; + } + } + + if ((firstBlackPixel != NSNotFound) && (lastBlackPixel != NSNotFound)) { + NPAssert(lastBlackPixel >= firstBlackPixel, ([NSString stringWithFormat:@"Got firstBlackPixel:'%d' and lastBlackPixel:'%d'.",firstBlackPixel,lastBlackPixel])); + blackPixelRangeAsHorizontalStrip.location = TUTruncateWithin(firstBlackPixel, 0, width - 1) / self.scale; + // We can't just use TUTruncateAtZero on lastBlackPixel - firstBlackPixel here. + // The semantics of pixel coordinates are such that a zero difference between lastBlackPixel and firstBlackPixel is ok + // but < 0 is obv. very bad. + // Thus 1 + TUTruncateAtZero(lastBlackPixel - firstBlackPixel) won't work. + // and fixing the expression s.t. it does work is more complicated than + // just breaking it down like so. + NSInteger length = lastBlackPixel - firstBlackPixel; + if (length >= 0) { + length += 1; + } else { + length = 0; + } + blackPixelRangeAsHorizontalStrip.length = length / self.scale; + } + } + CGContextRelease(context); + CGColorSpaceRelease(colorSpace); + free(pixelByteData); + } + NPNSROutputLog(blackPixelRangeAsHorizontalStrip); + return blackPixelRangeAsHorizontalStrip; +} + +#pragma mark Corners - Rects +-(CGRect)upperLeftCornerRect { + return CGRectMake(0.0f, 0.0f, 1.0f/self.scale, 1.0f/self.scale); +} + +-(CGRect)lowerLeftCornerRect { + return CGRectMake(0.0f, [self size].height - (1.0f/self.scale), 1.0f/self.scale, 1.0f/self.scale); +} + +-(CGRect)upperRightCornerRect { + return CGRectMake([self size].width - (1.0f/self.scale), 0.0f, 1.0f/self.scale, 1.0f/self.scale); +} + +-(CGRect)lowerRightCornerRect { + return CGRectMake([self size].width - 1.0f, [self size].height - (1.0f/self.scale), 1.0f/self.scale, 1.0f/self.scale); +} + +#pragma mark Corners - Slicing +-(UIImage *)upperLeftCorner { + return [self subImageInRect:[self upperLeftCornerRect]]; +} + +-(UIImage *)lowerLeftCorner { + return [self subImageInRect:[self lowerLeftCornerRect]]; +} + +-(UIImage *)upperRightCorner { + return [self subImageInRect:[self upperRightCornerRect]]; +} + +-(UIImage *)lowerRightCorner { + return [self subImageInRect:[self lowerRightCornerRect]]; +} + +#pragma mark Strips - Sizing +-(CGRect)upperStripRect { + CGSize selfSize = [self size]; + CGFloat stripWidth = TUTruncateAtZero(selfSize.width - (2.0f/self.scale)); + return CGRectMake((1.0f/self.scale), 0.0f, stripWidth, 1.0f/self.scale); +} + +-(CGRect)lowerStripRect { + CGSize selfSize = [self size]; + CGFloat stripWidth = TUTruncateAtZero(selfSize.width - (2.0f/self.scale)); + return CGRectMake(1.0f/self.scale, selfSize.height - (1.0f/self.scale), stripWidth, 1.0f/self.scale); +} + +-(CGRect)leftStripRect { + CGSize selfSize = [self size]; + CGFloat stripHeight = TUTruncateAtZero(selfSize.height - (2.0f/self.scale)); + return CGRectMake(0.0f, 1.0f/self.scale, 1.0f/self.scale, stripHeight); +} + +-(CGRect)rightStripRect { + CGSize selfSize = [self size]; + CGFloat stripHeight = TUTruncateAtZero(selfSize.height - (2.0f/self.scale)); + return CGRectMake(selfSize.width - (1.0f/self.scale), 1.0f/self.scale, 1.0f/self.scale, stripHeight); +} + +#pragma mark Strips - Slicing +-(UIImage *)upperStrip { + return [self subImageInRect:[self upperStripRect]]; +} + +-(UIImage *)lowerStrip { + return [self subImageInRect:[self lowerStripRect]]; +} + +-(UIImage *)leftStrip { + return [self subImageInRect:[self leftStripRect]]; +} + +-(UIImage *)rightStrip { + return [self subImageInRect:[self rightStripRect]]; +} + +-(UIImage *)subImageInRect:(CGRect)rect { + NPAInputLog(@"subImageInRect:'%@'",NSStringFromCGRect(rect)); + UIImage *subImage = nil; + CGImageRef cir = [self CGImage]; + if (cir) { + rect.origin.x *= self.scale; + rect.origin.y *= self.scale; + rect.size.width *= self.scale; + rect.size.height *= self.scale; + CGImageRef subCGImage = CGImageCreateWithImageInRect(cir, rect); + if (subCGImage) { + subImage = [UIImage imageWithCGImage:subCGImage scale:self.scale orientation:self.imageOrientation]; + CGImageRelease(subCGImage); + NPAssertNilOrIsKindOfClass(subImage,UIImage); + NPAssert((CGSizeEqualToSize([subImage size], rect.size)), @"Shouldn't get unequal subimage and requested sizes."); + } else { + DLog(@"Couldn't create subImage in rect: '%@'.", NSStringFromCGRect(rect)); + } + } else { + LLog(@"self.CGImage is somehow nil."); + } + //DLog(@"[%@:<0x%x> subImageInRect:%@] yielded subImage: '%@' of size: '%@'", [self class], ((NSUInteger) self), NSStringFromCGRect(rect), subImage, NSStringFromCGSize([subImage size])); + //IMLog(self, @"subImageInRectSourceImage"); + //IMLog(subImage, @"subImageInRect"); + NPOOutputLog(subImage); + return subImage; +} + +#pragma mark Nine-Patch Content Extraction +-(UIImage *)imageAsNinePatchImage { + UIImage *imageOfNinePatchImage = nil; + CGFloat width = [self size].width - (2.0f/self.scale); + CGFloat height = [self size].height - (2.0f/self.scale); + if (width > 0.0f && height > 0.0f) { + imageOfNinePatchImage = [self subImageInRect:CGRectMake((1.0f/self.scale), (1.0f/self.scale), width, height)]; + } + NPOOutputLog(imageOfNinePatchImage); + return imageOfNinePatchImage; +} + +#pragma mark - +-(UIImage *)extractUpperLeftCornerForStretchableRegion:(CGRect)stretchableRegion { + NPAInputLog(@"extractUpperLeftCornerForStretchableRegion:'%@'",NSStringFromCGRect(stretchableRegion)); + UIImage *upperLeftCorner = [self subImageInRect:CGRectMake(0.0f, 0.0f, CGRectGetMinX(stretchableRegion), CGRectGetMinY(stretchableRegion))]; + NPOOutputLog(upperLeftCorner); + return upperLeftCorner; +} + +-(UIImage *)extractUpperRightCornerForStretchableRegion:(CGRect)stretchableRegion { + NPAInputLog(@"extractUpperRightCornerForStretchableRegion:'%@'",NSStringFromCGRect(stretchableRegion)); + UIImage *upperRightCorner = [self subImageInRect:CGRectMake(CGRectGetMaxX(stretchableRegion), 0.0f, [self size].width - CGRectGetMaxX(stretchableRegion), CGRectGetMinY(stretchableRegion))]; + NPOOutputLog(upperRightCorner); + return upperRightCorner; +} + +-(UIImage *)extractLowerLeftCornerForStretchableRegion:(CGRect)stretchableRegion { + NPAInputLog(@"extractUpperRightCornerForStretchableRegion:'%@'",NSStringFromCGRect(stretchableRegion)); + UIImage *lowerLeftCorner = [self subImageInRect:CGRectMake(0.0f, CGRectGetMaxY(stretchableRegion), CGRectGetMinX(stretchableRegion), [self size].height - CGRectGetMaxY(stretchableRegion))]; + NPOOutputLog(lowerLeftCorner); + return lowerLeftCorner; +} + +-(UIImage *)extractLowerRightCornerForStretchableRegion:(CGRect)stretchableRegion { + NPAInputLog(@"extractLowerRightCornerForStretchableRegion:'%@'",NSStringFromCGRect(stretchableRegion)); + UIImage *lowerRightCorner = [self subImageInRect:CGRectMake(CGRectGetMaxX(stretchableRegion), CGRectGetMaxY(stretchableRegion), [self size].width - CGRectGetMaxX(stretchableRegion), [self size].height - CGRectGetMaxY(stretchableRegion))]; + NPOOutputLog(lowerRightCorner); + return lowerRightCorner; +} + +#pragma mark - +-(UIImage *)extractLeftEdgeForStretchableRegion:(CGRect)stretchableRegion { + NPAInputLog(@"extractLeftEdgeForStretchableRegion:'%@'",NSStringFromCGRect(stretchableRegion)); + UIImage *leftEdge = [self subImageInRect:CGRectMake(0.0f, CGRectGetMinY(stretchableRegion), CGRectGetMinX(stretchableRegion), CGRectGetHeight(stretchableRegion))]; + NPOOutputLog(leftEdge); + return leftEdge; +} + +-(UIImage *)extractRightEdgeForStretchableRegion:(CGRect)stretchableRegion { + NPAInputLog(@"extractRightEdgeForStretchableRegion:'%@'",NSStringFromCGRect(stretchableRegion)); + UIImage *rightEdge = [self subImageInRect:CGRectMake(CGRectGetMaxX(stretchableRegion), CGRectGetMinY(stretchableRegion), [self size].width - CGRectGetMaxX(stretchableRegion), CGRectGetHeight(stretchableRegion))]; + NPOOutputLog(rightEdge); + return rightEdge; +} + +-(UIImage *)extractUpperEdgeForStretchableRegion:(CGRect)stretchableRegion { + NPAInputLog(@"extractUpperEdgeForStretchableRegion:'%@'",NSStringFromCGRect(stretchableRegion)); + UIImage *upperEdge = [self subImageInRect:CGRectMake(CGRectGetMinX(stretchableRegion), 0.0f, CGRectGetWidth(stretchableRegion), CGRectGetMinY(stretchableRegion))]; + NPOOutputLog(upperEdge); + return upperEdge; +} + +-(UIImage *)extractLowerEdgeForStretchableRegion:(CGRect)stretchableRegion { + NPAInputLog(@"extractLowerEdgeForStretchableRegion:'%@'",NSStringFromCGRect(stretchableRegion)); + UIImage *lowerEdge = [self subImageInRect:CGRectMake(CGRectGetMinX(stretchableRegion), CGRectGetMaxY(stretchableRegion), CGRectGetWidth(stretchableRegion), [self size].height - CGRectGetMaxY(stretchableRegion))]; + NPOOutputLog(center); + return lowerEdge; +} + +#pragma mark - +-(UIImage *)extractCenterForStretchableRegion:(CGRect)stretchableRegion { + NPAInputLog(@"extractCenterForStretchableRegion:'%@'",NSStringFromCGRect(stretchableRegion)); + UIImage *center = [self subImageInRect:stretchableRegion]; + NPOOutputLog(center); + return center; +} + + +@end diff --git a/Resources/20-gear2.png b/Resources/20-gear2.png deleted file mode 100644 index b8180ded27c10b0e80bbcf5bb5e598da968b65d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 375 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 literal 0 HcmV?d00001 diff --git a/Resources/chat_bubble_incoming.png b/Resources/chat_bubble_incoming.png new file mode 100644 index 0000000000000000000000000000000000000000..a731fbd1d42c1cbb4b16b0b319c43ebba4b0e4ed GIT binary patch literal 2538 zcmbVOYgAI{8s12P7Zge@Z;e~*Vw%7P6eUd%#56S`E0?7~fC|b*K!wI?BaNDxVr5!Q z#dNV&GqtLdlcgn<)7X?{j$O^uW%M+zY_!a@GC5e;oFA?EarRn!ec$`;_j%s?T6;4$ zF~-Tk(*Xbgr`Sbop7o5ez63iI03e*=sa{sWLld2>S*A$WA!ubvD;fa8BehC_FiWff*NQVF@(BDuV>2F< zh$8SSgA+&zN|rcNvS^)3ylh=!l5ky?FieDxTnL71=~e=nSR(+nvTV7Uu8qKdlM=usRJheCy@!DKQiBou-|nIUXWG>JlDg+Nd+Ie3!eYgiVCOd^v) zsZlIi6a+;>OzX)ZQA4Oy7C98+giT^&2qp3fd7?d^vSo`^l0319qf*GgkM*TXzT!WO#R&-xC9x@# zPkDzyq_C)HCM$%@ra;jl9QMr1(HabKVmSV1SSD5037oio8GWnpWqHJM>&B?8 zt0AAd0tNt-XDpkUqg$w|CdNrp^AG*V+KaqTSm(f);+k*51i(Cf;l+4ECb>u$CPN=1? z8b?RZg?dq#{)JkHy%n{5&vqwY6g+M4{__#@4(LLGfW0@ZNx*&65 z|9TAlhW7Q?u*K|}P&Lo=s-*A6hq>jhsMm2?x{ZoabpF)8NVwi-fB60en2?N})iY|N zFTkS!qGi#MJQlInt@q$~U#36waQ)!+Cl8LjcdhY0(NtQ+DghLF4~E?p-Oyi1WDr0= zpG3SAfzBddgNEMv@ZP4$bz2YzT;x-@aCe(&S)1J5uk zm+bK@}I*%3P(No!!Q0oYI5hCw7&l@s_AhQrdcY_=n59ZW? zpm2F>g#nzH3|ii?ZCmf3wQN?J61o>OH!e=)8f=TEB3 z>zw-BUAPR{CdoW@^=G{jQ+cj*%)=fMG?<5K6pT*ibzxV$N^ZhZxts0Io6@*b{#(6` z-Xr_s3sBO97t=XqMWqGx0lRwLWpNwQxO3r$0C1>2=*&PwdPB}LL7*|!P*tzfHH+NG`WemSc{vOn!I_ zBfn{F2XCdf>vCG_%yQzt*T2KcV66Y>@N*i{$*mKK51UR8g8t+_}JaJz?|!niL3nnB~7Zoc6We$Z)c|A+g%v!H<2~EH&5H( z&?zEf`vzT4>8rl`i)lSf!omC!xZgJ9H=iHQWPF22ZMAFJa;b1EH%01zu`%a4)?X~H z+~;w;d!2dk&4(Cn%CVTgW&%^(!&2RNz!CNf;Au81H8_uh;uNWC6onmzFx(?Aj0bxC zcBeJhw(T-G)-GLqixMznMsaJ^wiz{uozpP}?{>U$wO+gDe6=su2TLpJ`Ib*c6}n(p z0GFaz7ye9{lBGz^Hw8Z0h-(f!W6URGtkwDUPswh4H1W+pbY_{i+dWk_WEY zi=g*&YIz$UUw&x+r9#Zp{ZSa3f^dupLFBDEQAC%5O}N(8i*DvwMtmYY76M%=^9sky z&(91nw_KN-I@w&eElY^UAH+G9s9{@aT~TQ`vO2vBJ+*+Iihgh8=&nWA%ffjxDBC=lAZQW3KbGAEyEyKKQ%?cRr{m P{=QOom#QRV zu9KNuCgNq|14zV`8(o$4H`8P1W_My7rn~CEfn*VK{*P0qPE{R6`8Q*S9aETM*s7Xcf^T! z5jWz84(Nfd1!WGor`eQcO53d3{TK=;ehwYb16^F--TC3@y?v1{0I4ZM+iW!LoalzdhwhuB&41w88qq}TP15*&;nlF$V|m7=whq0o$U;fLsR$@EBh zqK^U(aQTx(2DzEaRV5&gg9o?_?P%HRu7u-p@EF?Z_*te=YV{=?kAnxecp(jBJsPh| z;Bs&$#J~Ki?|qVRJPsb<;@)RDcP{F zNwS#?)J)1_iT|-AZWL~%@-+#^d%)#9z{PV_(pYw`N_w&`IrXULUX?VKM=ssSgY|Mt zLgEe%;4yYH`BL9JlT6F&Tj)>PMfP0Jot6&)^E?E2ce>4rm^&SG`#vebv~=m>+IpGM zW=|+U&xU?Qrm0d%Xz`W?rOgCl%3* zXF{sb==zS&@MbW(uzAQZ8IB1&lfG8F(~nI#UUT@;`l1=Rvx;7sQ?25Su*A(mNkO>A00000NkvXXu0mjfJp;02 literal 0 HcmV?d00001 diff --git a/Resources/chat_bubble_outgoing.png b/Resources/chat_bubble_outgoing.png new file mode 100644 index 0000000000000000000000000000000000000000..8a597fc3860f267cae03deb4f7c0a4f2002c7569 GIT binary patch literal 2485 zcmbVOX;c&E8XgD<0RjfF?8_JsQ6ZTLAtW(TAd7;4fK*VlO-Kfak_3`q0{4m_h!iQJ zUcsdYsfxIx(gH3Q0t6L{f>%VST6e{bqN1Qg(m}<0e|Y-i&N*}Dd%t;}=Uu;Zz8|8a z=3863SONfG9kzfMi+<;$FBy*m02Av7nk#BJtN97)#foILMx;c55Q!oQ0mEdX6eJcA zNwP9J5HV(J$mRKPpizYB+tt=g70|0xTHeDoMg{Z+KBtftdBa~P~rN~r@(S9U*O_I^t{k@|JAQ}y8ky45_MU=)!wT+{J-lq)054PuLUf`IIdWQA$@M5`j`713%W6CH;zjCRacYWI()tfKPcdVTc*Z z58=|Oya1R_7mz-4CI3&&C}=R0iQ)L4VVT@QCval6{qbJfo?B4Xtkpw&XZD?GRd~n+S=9lqAp;y45r$nLZa= zp6yZsnCOdU4+eX@8^1rJZEH=fvqif<`>BrL@W@#FYKw0%gV~pHj)^ynnU^OOIO{Fq z3_s=(bb3ABrmEREo_D{%k6UIEU~k_C3c{_kU%&P8F3CM%N-des+it9@0w^&%*Nz+7 zhXy(hU)SjzFfUEeKo^xJ67(ku2nFpfy#@NN^RGRfOki&;+u_8Eq&=qI8|>NcGiQez z&G|Fnhd9E`wWDs8rQ3xr-PY4^xpqTu+fu-TuZG?oHikUOwF62r$v)xMdFwXB!B{-+ zAkv-T6a8xcRKULao2z3}c011`3~U=xn_XH}`#_@S_4oG|QGo#af!_SHy_nL!y zZl8R-*C@FYA6?gv|F%Czcc5>ti$PF0Xsq4m+YXi8`6EAt za3v?Iq$+(ky}Q)JWIA?cA^+J(1cvEbAR5XgmgSF{Z_XbLv9WaF*;tw~Q)eG9IO?2Q zk$V&9lqOzT_*AM}#I#BvpHs#B~QLx`!hxyW^q@VdbFcHFP*UIIPtc# zhdioLS5bRAq{k+kdCs+{D|cm*(EL$d8R)rktYF8Eo6A1T>shWi%fa}jniPfxo_;ux zo1bspV*v&M!BW|8XGV5A{n0emV=1{Vl*s7xUgN8i_tcbOkGVM=#JjStBDct^xYlVP zaMCeuV_T>IpYW=wI=*9L4b4GpTJDN@N*pM{W4hgIxAXY8`5R3i*!XzJkA>WIDx0(2 z31=%zO7?IYG&z<3#Ar0;x5A(X`e1nXk7x!C1xBA>I{_EmBff9`km^DxH93V@9+ZcB zdBJ(@UI*=)_n7?VI&5Q2$I#2Gjyt-(Z?Z1p2ER3ZQ8E9R@9rk;yC{zbq1hYo7rqnj zHP~j~e`?#kzC>5J`s&t{TIU>tfp2e~+lk#V<)pd9vlESk$M-mjs{AoJ1^)v z_1;GY91?wR?C*{unz{ZutiY3>)5CR9q{3S(Lw;EuGCSrupXlnEc;(`X_J)}xpEJA} zqid4)#B_3h931F0Hy?yFxWP?hjK`I){}ufZ-XhHC6Z%$NJX?kRVKKYmb!;oa>hZXL z;>@nPo$m+V^iIJVG>(TJsNZWwN>i>pC;D7psC!a)sexwx45=8tLo4jnyG94zg{tda z(uUW>PVLHfSFd<{^uF!Ry-V`i4E=;X-QP7Y`MrT|H?@s@{I=CkK7H{#2kszisg|9& zXGu9x#%LFF`h>yNk$agRmSK9{__hAB(C@L;=I{^yz8t0NFyzJ0%i>kMukVw-aej-f zT*SLNHKsak*FpW+)uar;hGff4_KaWY7h9y6VV5I)_T3y@CileaWpBznzq9^(|}hieMaD*Hhsy*E2oHt<0YtrO%GV zN0JKyG?n2uc$Si(?Os1AX4Hg|dL8*o#5ciVM_Pmg!V;=7mb%rKLz ztLog6_s_w~P_Uy;)jLuby6Q;6ZDqxZ3g0Ohm* literal 0 HcmV?d00001 diff --git a/Resources/setup_back_disabled.png b/Resources/setup_back_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..805084589d7b2972ab31a6f62f28f933f97a8068 GIT binary patch literal 5050 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 T+-C6S?=&NQQ@v^(=eYj^NClVC literal 0 HcmV?d00001 diff --git a/Resources/setup_cancel_disabled.png b/Resources/setup_cancel_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..30b9978fa9ab797a42c625ddae0aafea307d3c83 GIT binary patch literal 5924 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 Cbx;2Q literal 0 HcmV?d00001 diff --git a/Resources/setup_start_disabled.png b/Resources/setup_start_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..1c66451b3017e5ad8142fe7adc908ff95c086a45 GIT binary patch literal 5620 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