11#import "BookmarkListController.h"
13#import "BookmarkEditorController.h"
14#import "RDPSessionViewController.h"
15#import "Toast+UIView.h"
16#import "Reachability.h"
17#import "GlobalDefaults.h"
18#import "BlockAlertView.h"
20#define SECTION_SESSIONS 0
21#define SECTION_BOOKMARKS 1
24@interface BookmarkListController (Private)
25#pragma mark misc functions
26- (UIButton *)disclosureButtonWithImage:(UIImage *)image;
27- (void)performSearch:(NSString *)searchText;
28#pragma mark Persisting bookmarks
29- (void)scheduleWriteBookmarksToDataStore;
30- (void)writeBookmarksToDataStore;
31- (void)scheduleWriteManualBookmarksToDataStore;
32- (void)writeManualBookmarksToDataStore;
33- (void)readManualBookmarksFromDataStore;
34- (void)writeArray:(NSArray *)bookmarks toDataStoreURL:(NSURL *)url;
35- (NSMutableArray *)arrayFromDataStoreURL:(NSURL *)url;
36- (NSURL *)manualBookmarksDataStoreURL;
37- (NSURL *)connectionHistoryDataStoreURL;
42@synthesize searchBar = _searchBar, tableView = _tableView, bmTableCell = _bmTableCell,
43 sessTableCell = _sessTableCell;
47- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
49 if ((
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]))
52 [
self readManualBookmarksFromDataStore];
55 [
self readConnectionHistoryFromDataStore];
58 _manual_search_result = nil;
61 [[NSNotificationCenter defaultCenter] addObserver:self
62 selector:@selector(sessionDisconnected:)
63 name:TSXSessionDidDisconnectNotification
65 [[NSNotificationCenter defaultCenter] addObserver:self
66 selector:@selector(sessionFailedToConnect:)
67 name:TSXSessionDidFailToConnectNotification
71 [
self setTitle:NSLocalizedString(@"Connections",
72 @"'Connections': bookmark controller title")];
73 [
self setTabBarItem:[[[UITabBarItem alloc]
74 initWithTabBarSystemItem:UITabBarSystemItemBookmarks
78 _star_on_img = [[UIImage
79 imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"icon_accessory_star_on"
80 ofType:@"png"]] retain];
82 [[UIImage imageWithContentsOfFile:[[NSBundle mainBundle]
83 pathForResource:@"icon_accessory_star_off"
84 ofType:@"png"]] retain];
87 [[NSNotificationCenter defaultCenter] addObserver:self
88 selector:@selector(reachabilityChanged:)
89 name:kReachabilityChangedNotification
93 _active_sessions = [[NSMutableArray alloc] init];
94 _temporary_bookmark = nil;
110 [[
self navigationItem] setRightBarButtonItem:[
self editButtonItem]];
113- (void)viewWillAppear:(BOOL)animated
115 [
super viewWillAppear:animated];
118 if ([[_searchBar text] length] > 0)
119 [
self performSearch:[_searchBar text]];
122 [_tableView reloadData];
126- (void)viewSafeAreaInsetsDidChange
128 [
super viewSafeAreaInsetsDidChange];
129 [[
self view] setNeedsLayout];
132- (void)viewDidLayoutSubviews
134 [
super viewDidLayoutSubviews];
136 const CGRect bounds = [[
self view] bounds];
137 const UIEdgeInsets safe = [[
self view] safeAreaInsets];
138 const CGFloat x = safe.left;
139 const CGFloat w = bounds.size.width - safe.left - safe.right;
140 CGFloat searchH = CGRectGetHeight([_searchBar frame]);
142 [_searchBar setFrame:CGRectMake(x, safe.top, w, searchH)];
144 setFrame:CGRectMake(x, safe.top + searchH, w, bounds.size.height - safe.top - searchH)];
147- (void)viewWillDisappear:(BOOL)animated
149 [
super viewWillDisappear:animated];
152 [_searchBar setText:@""];
153 [_searchBar resignFirstResponder];
154 [
self performSearch:@""];
158- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
164- (void)didReceiveMemoryWarning
167 [
super didReceiveMemoryWarning];
174 [
super viewDidUnload];
181 [[NSNotificationCenter defaultCenter] removeObserver:self];
183 [_temporary_bookmark release];
184 [_connection_history release];
185 [_active_sessions release];
186 [_manual_search_result release];
187 [_manual_bookmarks release];
189 [_star_on_img release];
190 [_star_off_img release];
196#pragma mark Table view data source
198- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
204- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
209 case SECTION_SESSIONS:
213 case SECTION_BOOKMARKS:
216 if (_manual_search_result != nil)
217 return ([_manual_search_result count] + [_history_search_result count] + 1);
218 return ([_manual_bookmarks count] + 1);
228- (UITableViewCell *)cellForGenericListEntry
230 static NSString *CellIdentifier =
@"BookmarkListCell";
231 UITableViewCell *cell = [[
self tableView] dequeueReusableCellWithIdentifier:CellIdentifier];
234 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
235 reuseIdentifier:CellIdentifier];
236 [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
237 [cell setAccessoryView:[
self disclosureButtonWithImage:_star_off_img]];
245 static NSString *BookmarkCellIdentifier =
@"BookmarkCell";
247 dequeueReusableCellWithIdentifier:BookmarkCellIdentifier];
250 [[NSBundle mainBundle] loadNibNamed:@"BookmarkTableViewCell" owner:self options:nil];
251 [_bmTableCell setAccessoryView:[
self disclosureButtonWithImage:_star_on_img]];
260- (UITableViewCell *)tableView:(UITableView *)tableView
261 cellForRowAtIndexPath:(NSIndexPath *)indexPath
264 switch ([indexPath section])
266 case SECTION_SESSIONS:
269 static NSString *SessionCellIdentifier =
@"SessionCell";
271 dequeueReusableCellWithIdentifier:SessionCellIdentifier];
274 [[NSBundle mainBundle] loadNibNamed:@"SessionTableViewCell" owner:self options:nil];
275 cell = _sessTableCell;
276 _sessTableCell = nil;
280 RDPSession *session = [_active_sessions objectAtIndex:[indexPath row]];
281 [[cell title] setText:[session sessionName]];
282 [[cell server] setText:[[session params] StringForKey:@"hostname"]];
283 [[cell username] setText:[[session params] StringForKey:@"username"]];
285 setImage:[session getScreenshotWithSize:[[cell screenshot] bounds].size]];
286 [[cell disconnectButton] setTag:[indexPath row]];
290 case SECTION_BOOKMARKS:
293 if ([indexPath row] == 0)
297 UITableViewCell *cell = [
self cellForGenericListEntry];
298 if ([[_searchBar text] length] == 0)
301 setText:[@" " stringByAppendingString:
302 NSLocalizedString(@"Add Connection",
303 @"'Add Connection': button label")]];
304 [((UIButton *)[cell accessoryView]) setHidden:YES];
308 [[cell textLabel] setText:[@" " stringByAppendingString:[_searchBar text]]];
309 [((UIButton *)[cell accessoryView]) setHidden:NO];
317 if ([
self isIndexPathToHistoryItem:indexPath])
319 UITableViewCell *cell = [
self cellForGenericListEntry];
321 setText:[@" " stringByAppendingString:
322 [_history_search_result
324 [
self historyIndexFromIndexPath:indexPath]]]];
325 [((UIButton *)[cell accessoryView]) setHidden:NO];
333 if (_manual_search_result == nil)
334 entry = [_manual_bookmarks
335 objectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]];
337 entry = [[_manual_search_result
338 objectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]]
339 valueForKey:@"bookmark"];
341 [[cell title] setText:[entry label]];
342 [[cell subTitle] setText:[[entry params] StringForKey:@"hostname"]];
352 NSAssert(0,
@"Failed to create cell");
357- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
360 if ([indexPath section] == SECTION_SESSIONS)
362 if ([indexPath section] == SECTION_BOOKMARKS && [indexPath row] == 0)
368- (void)tableView:(UITableView *)tableView
369 commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
370 forRowAtIndexPath:(NSIndexPath *)indexPath
372 if (editingStyle == UITableViewCellEditingStyleDelete)
375 switch ([indexPath section])
377 case SECTION_BOOKMARKS:
379 if (_manual_search_result == nil)
381 removeObjectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]];
385 if ([
self isIndexPathToHistoryItem:indexPath])
389 [_history_search_result
390 objectAtIndex:[
self historyIndexFromIndexPath:indexPath]]];
391 [_history_search_result
392 removeObjectAtIndex:[
self historyIndexFromIndexPath:indexPath]];
398 [[_manual_search_result
399 objectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]]
400 valueForKey:@"bookmark"]];
401 [_manual_search_result
402 removeObjectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]];
405 [
self scheduleWriteManualBookmarksToDataStore];
410 [tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]]
411 withRowAnimation:UITableViewRowAnimationNone];
416- (void)tableView:(UITableView *)tableView
417 moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
418 toIndexPath:(NSIndexPath *)toIndexPath
420 if ([fromIndexPath compare:toIndexPath] != NSOrderedSame)
422 switch ([fromIndexPath section])
424 case SECTION_BOOKMARKS:
426 int fromIdx = [
self bookmarkIndexFromIndexPath:fromIndexPath];
427 int toIdx = [
self bookmarkIndexFromIndexPath:toIndexPath];
429 [[_manual_bookmarks objectAtIndex:fromIdx] retain];
430 [_manual_bookmarks removeObjectAtIndex:fromIdx];
431 if (toIdx >= [_manual_bookmarks count])
432 [_manual_bookmarks addObject:temp_bookmark];
434 [_manual_bookmarks insertObject:temp_bookmark atIndex:toIdx];
435 [temp_bookmark release];
437 [
self scheduleWriteManualBookmarksToDataStore];
445- (NSIndexPath *)tableView:(UITableView *)tableView
446 targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
447 toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
453 if ([proposedDestinationIndexPath row] == 0 ||
454 ([sourceIndexPath section] != [proposedDestinationIndexPath section]) ||
455 _manual_search_result != nil)
457 return sourceIndexPath;
461 return proposedDestinationIndexPath;
466- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
469 if ([indexPath section] == SECTION_BOOKMARKS && [indexPath row] == 0)
474- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
476 if (section == SECTION_SESSIONS && [_active_sessions count] > 0)
477 return NSLocalizedString(
@"My Sessions",
@"'My Session': section sessions header");
478 if (section == SECTION_BOOKMARKS)
479 return NSLocalizedString(
@"Manual Connections",
480 @"'Manual Connections': section manual bookmarks header");
484- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
489- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
491 if ([indexPath section] == SECTION_SESSIONS)
493 return [tableView rowHeight];
497#pragma mark Table view delegate
499- (void)setEditing:(BOOL)editing animated:(BOOL)animated
501 [
super setEditing:editing animated:animated];
502 [[
self tableView] setEditing:editing animated:animated];
505- (void)accessoryButtonTapped:(UIControl *)button withEvent:(UIEvent *)event
508 NSIndexPath *indexPath =
509 [[
self tableView] indexPathForRowAtPoint:[[[event touchesForView:button] anyObject]
510 locationInView:[
self tableView]]];
511 if (indexPath == nil)
514 [[[
self tableView] delegate] tableView:[
self tableView]
515 accessoryButtonTappedForRowWithIndexPath:indexPath];
518- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
520 if ([indexPath section] == SECTION_SESSIONS)
523 RDPSession *session = [_active_sessions objectAtIndex:[indexPath row]];
524 UIViewController *ctrl =
527 session:session] autorelease];
528 [ctrl setHidesBottomBarWhenPushed:YES];
529 [[
self navigationController] pushViewController:ctrl animated:YES];
534 if ([indexPath section] == SECTION_BOOKMARKS)
537 if ([indexPath row] == 0)
539 if ([[_searchBar text] length] == 0)
546 [bookmarkEditorController
547 setTitle:NSLocalizedString(@"Add Connection", @"Add Connection title")];
548 [bookmarkEditorController setDelegate:self];
549 [bookmarkEditorController setHidesBottomBarWhenPushed:YES];
550 [[
self navigationController] pushViewController:bookmarkEditorController
557 bookmark = [
self bookmarkForQuickConnectTo:[_searchBar text]];
558 if (![_connection_history containsObject:[_searchBar text]])
560 [_connection_history addObject:[_searchBar text]];
561 [
self scheduleWriteConnectionHistoryToDataStore];
567 if (_manual_search_result != nil)
569 if ([
self isIndexPathToHistoryItem:indexPath])
572 NSString *item = [_history_search_result
573 objectAtIndex:[
self historyIndexFromIndexPath:indexPath]];
574 bookmark = [
self bookmarkForQuickConnectTo:item];
577 bookmark = [[_manual_search_result
578 objectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]]
579 valueForKey:@"bookmark"];
582 bookmark = [_manual_bookmarks
583 objectAtIndex:[
self bookmarkIndexFromIndexPath:
590 reachabilityWithHostName:[[bookmark params]
591 StringForKey:@"hostname"]]
592 currentReachabilityStatus] == ReachableViaWiFi];
599 UIViewController *ctrl =
602 session:session] autorelease];
603 [ctrl setHidesBottomBarWhenPushed:YES];
604 [[
self navigationController] pushViewController:ctrl animated:YES];
605 [_active_sessions addObject:session];
610- (void)tableView:(UITableView *)tableView
611 accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
614 NSString *bookmark_editor_title =
615 NSLocalizedString(
@"Edit Connection",
@"Edit Connection title");
617 if ([indexPath section] == SECTION_BOOKMARKS)
619 if ([indexPath row] == 0)
622 bookmark = [
self bookmarkForQuickConnectTo:[_searchBar text]];
623 bookmark_editor_title = NSLocalizedString(
@"Add Connection",
@"Add Connection title");
627 if (_manual_search_result != nil)
629 if ([
self isIndexPathToHistoryItem:indexPath])
632 NSString *item = [_history_search_result
633 objectAtIndex:[
self historyIndexFromIndexPath:indexPath]];
634 bookmark = [
self bookmarkForQuickConnectTo:item];
635 bookmark_editor_title =
636 NSLocalizedString(
@"Add Connection",
@"Add Connection title");
639 bookmark = [[_manual_search_result
640 objectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]]
641 valueForKey:@"bookmark"];
644 bookmark = [_manual_bookmarks
645 objectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]];
655 [editBookmarkController setHidesBottomBarWhenPushed:YES];
656 [editBookmarkController setTitle:bookmark_editor_title];
657 [editBookmarkController setDelegate:self];
658 [[
self navigationController] pushViewController:editBookmarkController animated:YES];
663#pragma mark Search Bar Delegates
665- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
668 [searchBar setShowsCancelButton:YES animated:YES];
672- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
675 [_manual_search_result release];
676 _manual_search_result = nil;
679 [searchBar setText:@""];
680 [searchBar resignFirstResponder];
683- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar
685 [searchBar setShowsCancelButton:NO animated:YES];
688 [_tableView setAllowsSelection:YES];
693- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
695 [_searchBar resignFirstResponder];
698- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
700 [
self performSearch:searchText];
701 [_tableView reloadData];
704#pragma mark - Session handling
707- (void)sessionDisconnected:(NSNotification *)notification
711 [_active_sessions removeObject:session];
714 if ([[
self navigationController] visibleViewController] ==
self)
715 [_tableView reloadSections:[NSIndexSet indexSetWithIndex:SECTION_SESSIONS]
716 withRowAnimation:UITableViewRowAnimationNone];
720 if (![_manual_bookmarks containsObject:[session bookmark]])
723 _temporary_bookmark = [[session bookmark] retain];
727 NSLocalizedString(
@"Save Connection Settings?",
@"Save connection settings title");
728 NSString *message = NSLocalizedString(
729 @"Your Connection Settings have not been saved. Do you want to save them?",
730 @"Save connection settings message");
732 [alert setCancelButtonWithTitle:NSLocalizedString(@"No", @"No Button") block:nil];
733 [alert addButtonWithTitle:NSLocalizedString(@"Yes", @"Yes Button")
735 if (_temporary_bookmark)
737 [_manual_bookmarks addObject:_temporary_bookmark];
739 reloadSections:[NSIndexSet
740 indexSetWithIndex:SECTION_BOOKMARKS]
741 withRowAnimation:UITableViewRowAnimationNone];
742 [_temporary_bookmark autorelease];
743 _temporary_bookmark = nil;
750- (void)sessionFailedToConnect:(NSNotification *)notification
754 [_active_sessions removeObject:session];
757 [[
self view] makeToast:NSLocalizedString(@"Failed to connect to session!",
758 @"Failed to connect error message")
759 duration:ToastDurationNormal
763#pragma mark - Reachability notification
764- (void)reachabilityChanged:(NSNotification *)notification
768 if ([_active_sessions count] > 0)
770 RDPSession *session = [_active_sessions objectAtIndex:0];
771 [session disconnect];
775#pragma mark - BookmarkEditorController delegate
781 for (
int idx = 0; idx < [_manual_bookmarks count]; ++idx)
783 if ([[bookmark uuid] isEqualToString:[[_manual_bookmarks objectAtIndex:idx] uuid]])
785 [_manual_bookmarks replaceObjectAtIndex:idx withObject:bookmark];
791 [_manual_bookmarks addObject:bookmark];
794 NSString *hostname = [[bookmark params] StringForKey:@"hostname"];
795 if ([_connection_history containsObject:hostname])
797 [_connection_history removeObject:hostname];
798 [
self scheduleWriteConnectionHistoryToDataStore];
801 [
self scheduleWriteManualBookmarksToDataStore];
804- (IBAction)disconnectButtonPressed:(
id)sender
807 RDPSession *session = [_active_sessions objectAtIndex:[sender tag]];
808 [session disconnect];
811#pragma mark - Misc functions
813- (BOOL)hasNoBookmarks
815 return ([_manual_bookmarks count] == 0);
818- (UIButton *)disclosureButtonWithImage:(UIImage *)image
822 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
823 [button setFrame:CGRectMake(0, 0, [image size].width * 2, [image size].height + 10)];
824 [button setImage:image forState:UIControlStateNormal];
825 [button addTarget:self
826 action:@selector(accessoryButtonTapped:withEvent:)
827 forControlEvents:UIControlEventTouchUpInside];
828 [button setUserInteractionEnabled:YES];
832- (void)performSearch:(NSString *)searchText
834 [_manual_search_result autorelease];
836 if ([searchText length] > 0)
838 _manual_search_result = [FilterBookmarks(
840 [searchText componentsSeparatedByCharactersInSet:[NSCharacterSet
841 whitespaceAndNewlineCharacterSet]])
843 _history_search_result = [FilterHistory(_connection_history, searchText) retain];
847 _history_search_result = nil;
848 _manual_search_result = nil;
852- (int)bookmarkIndexFromIndexPath:(NSIndexPath *)indexPath
854 return [indexPath row] -
855 ((_history_search_result != nil) ? [_history_search_result count] : 0) - 1;
858- (int)historyIndexFromIndexPath:(NSIndexPath *)indexPath
860 return [indexPath row] - 1;
863- (BOOL)isIndexPathToHistoryItem:(NSIndexPath *)indexPath
865 return (([indexPath row] - 1) < [_history_search_result count]);
872 [bookmark setLabel:host];
873 [[bookmark params] setValue:host forKey:@"hostname"];
877#pragma mark - Persisting bookmarks
879- (void)scheduleWriteBookmarksToDataStore
881 [[NSOperationQueue mainQueue] addOperationWithBlock:^{
882 [
self writeBookmarksToDataStore];
886- (void)writeBookmarksToDataStore
888 [
self writeManualBookmarksToDataStore];
891- (void)scheduleWriteManualBookmarksToDataStore
893 [[NSOperationQueue mainQueue]
894 addOperation:[[[NSInvocationOperation alloc]
896 selector:@selector(writeManualBookmarksToDataStore)
897 object:nil] autorelease]];
900- (void)writeManualBookmarksToDataStore
902 [
self writeArray:_manual_bookmarks toDataStoreURL:[
self manualBookmarksDataStoreURL]];
905- (void)scheduleWriteConnectionHistoryToDataStore
907 [[NSOperationQueue mainQueue]
908 addOperation:[[[NSInvocationOperation alloc]
910 selector:@selector(writeConnectionHistoryToDataStore)
911 object:nil] autorelease]];
914- (void)writeConnectionHistoryToDataStore
916 [
self writeArray:_connection_history toDataStoreURL:[
self connectionHistoryDataStoreURL]];
919- (void)writeArray:(NSArray *)bookmarks toDataStoreURL:(NSURL *)url
921 NSData *archived_data = [NSKeyedArchiver archivedDataWithRootObject:bookmarks];
922 [archived_data writeToURL:url atomically:YES];
925- (void)readManualBookmarksFromDataStore
927 [_manual_bookmarks autorelease];
928 _manual_bookmarks = [
self arrayFromDataStoreURL:[
self manualBookmarksDataStoreURL]];
930 if (_manual_bookmarks == nil)
932 _manual_bookmarks = [[NSMutableArray alloc] init];
936- (void)readConnectionHistoryFromDataStore
938 [_connection_history autorelease];
939 _connection_history = [
self arrayFromDataStoreURL:[
self connectionHistoryDataStoreURL]];
941 if (_connection_history == nil)
942 _connection_history = [[NSMutableArray alloc] init];
945- (NSMutableArray *)arrayFromDataStoreURL:(NSURL *)url
947 NSData *archived_data = [NSData dataWithContentsOfURL:url];
952 return [[NSKeyedUnarchiver unarchiveObjectWithData:archived_data] retain];
955- (NSURL *)manualBookmarksDataStoreURL
958 fileURLWithPath:[NSString stringWithFormat:@"%@/%@",
959 [NSSearchPathForDirectoriesInDomains(
960 NSDocumentDirectory, NSUserDomainMask, YES)
962 @"com.freerdp.ifreerdp.bookmarks.plist"]];
965- (NSURL *)connectionHistoryDataStoreURL
968 fileURLWithPath:[NSString
969 stringWithFormat:@"%@/%@",
970 [NSSearchPathForDirectoriesInDomains(
971 NSDocumentDirectory, NSUserDomainMask, YES)
973 @"com.freerdp.ifreerdp.connection_history.plist"]];