#import "PithosPreferencesController.h"
#import "PithosBrowserController.h"
#import "PithosAccountNode.h"
+#import "PithosContainerNode.h"
+#import "PithosSubdirNode.h"
+#import "PithosObjectNode.h"
+#import "PithosEmptyNode.h"
#import "PithosAccount.h"
#import "pithos_macosAppDelegate.h"
+#import "ImageAndTextCell.h"
+@interface PithosPreferencesSyncOutlineViewCell : ImageAndTextCell {}
+@end
+
+@implementation PithosPreferencesSyncOutlineViewCell
+
+- (void)setObjectValue:(id)object {
+ if ([object isKindOfClass:[PithosNode class]]) {
+ PithosNode *node = (PithosNode *)object;
+ [self setStringValue:node.displayName];
+ [self setImage:node.icon];
+ [self setEditable:NO];
+ } else {
+ [super setObjectValue:object];
+ }
+}
+
+@end
+
@implementation PithosPreferencesController
@synthesize selectedPithosAccount;
@synthesize accountsArrayController;
@synthesize accountRemoveEnable;
@synthesize serverURL, authUser, authToken, manual, loginEnable, loginCancelEnable;
+@synthesize syncActive, syncDirectoryPath, syncContainersDictionary, syncApplyEnable, syncCancelEnable,
+ syncContainersOutlineView, syncContainersRootFilesNodes;
@synthesize groupsDictionaryController, selectedGroupMembersDictionaryController;
-@synthesize syncActive, syncDirectoryPath, syncApplyEnable, syncCancelEnable;
#pragma mark -
#pragma mark Object Lifecycle
// // Select the first tab when the window is loaded for the first time.
// [[window valueForKeyPath:@"toolbar"] setSelectedItemIdentifier:@"0"];
-
+
+ [[[syncContainersOutlineView tableColumns] objectAtIndex:1] setDataCell:[[[PithosPreferencesSyncOutlineViewCell alloc] init] autorelease]];
+
[groupsDictionaryController setInitialKey:@"group"];
[groupsDictionaryController setInitialValue:@"user"];
[selectedGroupMembersDictionaryController setInitialKey:@"user"];
self.selectedPithosAccount = [[accountsArrayController selectedObjects] objectAtIndex:0];
[accountsArrayController addObserver:self forKeyPath:@"selection" options:NSKeyValueObservingOptionNew context:NULL];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(selectedPithosAccountNodeChildrenUpdated:)
+ name:@"SelectedPithosAccountNodeChildrenUpdated"
+ object:nil];
}
- (BOOL)windowShouldClose:(id)sender {
}
}
+- (void)selectedPithosAccountNodeChildrenUpdated:(NSNotification *)notification {
+ [syncContainersOutlineView reloadData];
+ [syncContainersOutlineView expandItem:nil expandChildren:YES];
+}
+
#pragma mark -
#pragma Update
((selectedPithosAccount.syncActive != syncActive) ||
(![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] &&
(![[NSFileManager defaultManager] fileExistsAtPath:syncDirectoryPath isDirectory:&isDirectory] ||
- isDirectory))));
+ isDirectory)) ||
+ ![selectedPithosAccount.syncContainersDictionary isEqualToDictionary:syncContainersDictionary]));
self.syncCancelEnable = (selectedPithosAccount.active &&
((selectedPithosAccount.syncActive != syncActive) ||
- ![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath]));
+ ![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] ||
+ ![selectedPithosAccount.syncContainersDictionary isEqualToDictionary:syncContainersDictionary]));
}
#pragma mark -
- (void)setSelectedPithosAccount:(PithosAccount *)aSelectedPithosAccount {
if (aSelectedPithosAccount && ![aSelectedPithosAccount isEqualTo:selectedPithosAccount]) {
+ selectedPithosAccount.accountNode.childrenUpdatedNotificationName = nil;
[selectedPithosAccount release];
selectedPithosAccount = [aSelectedPithosAccount retain];
+ selectedPithosAccount.accountNode.childrenUpdatedNotificationName = [NSString stringWithString:@"SelectedPithosAccountNodeChildrenUpdated"];
[self updateAccounts];
[self loginCancel:self];
manual = aManual;
[self updateLogin];
if (!manual) {
- self.authUser = [[selectedPithosAccount.authUser copy] autorelease];
- self.authToken = [[selectedPithosAccount.authToken copy] autorelease];
+ self.authUser = selectedPithosAccount.authUser;
+ self.authToken = selectedPithosAccount.authToken;
}
}
[self updateSync];
}
+- (void)setSyncContainersDictionary:(NSMutableDictionary *)aSyncContainersDictionary {
+ [syncContainersDictionary release];
+ syncContainersDictionary = [[NSMutableDictionary alloc] initWithCapacity:[aSyncContainersDictionary count]];
+ for (NSString *containerName in aSyncContainersDictionary) {
+ [syncContainersDictionary setObject:[NSMutableArray arrayWithArray:[aSyncContainersDictionary objectForKey:containerName]]
+ forKey:containerName];
+ }
+ [self updateSync];
+}
+
#pragma mark -
#pragma Actions
}
- (IBAction)loginCancel:(id)server {
- self.serverURL = [[selectedPithosAccount.serverURL copy] autorelease];
- self.authUser = [[selectedPithosAccount.authUser copy] autorelease];
- self.authToken = [[selectedPithosAccount.authToken copy] autorelease];
+ self.serverURL = selectedPithosAccount.serverURL;
+ self.authUser = selectedPithosAccount.authUser;
+ self.authToken = selectedPithosAccount.authToken;
}
#pragma Sync Actions
- (IBAction)syncApply:(id)sender {
- [selectedPithosAccount updateSyncWithSyncActive:syncActive syncDirectoryPath:syncDirectoryPath];
+ [selectedPithosAccount updateSyncWithSyncActive:syncActive
+ syncDirectoryPath:syncDirectoryPath
+ syncContainersDictionary:syncContainersDictionary];
[self updateSync];
pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
[delegate savePithosAccounts:self];
+ [delegate sync];
}
- (IBAction)syncCancel:(id)sender {
self.syncActive = selectedPithosAccount.syncActive;
- self.syncDirectoryPath = [[selectedPithosAccount.syncDirectoryPath copy] autorelease];
+ self.syncDirectoryPath = selectedPithosAccount.syncDirectoryPath;
+ self.syncContainersDictionary = selectedPithosAccount.syncContainersDictionary;
+ self.syncContainersRootFilesNodes = [NSMutableDictionary dictionary];
+ [self selectedPithosAccountNodeChildrenUpdated:nil];
+}
+
+- (IBAction)syncRefresh:(id)sender {
+ selectedPithosAccount.accountNode.forcedRefresh = YES;
+ [selectedPithosAccount.accountNode invalidateChildrenRecursive];
+ if (selectedPithosAccount.accountNode.children) {
+ }
}
#pragma mark Groups Actions
[selectedPithosAccount.accountNode refreshInfo];
}
+#pragma mark -
+#pragma mark NSOutlineViewDataSource
+
+- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
+ if (outlineView == syncContainersOutlineView) {
+ if (item == nil)
+ return selectedPithosAccount.accountNode.children.count;
+ if ([item class] == [PithosContainerNode class]) {
+ // We add 1 for the root files node
+ NSInteger subdirCount = 1;
+ for (PithosNode *node in [(PithosContainerNode *)item children]) {
+ if ([node class] == [PithosSubdirNode class])
+ subdirCount++;
+ }
+ return subdirCount;
+ }
+ }
+ return 0;
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
+ if (outlineView == syncContainersOutlineView) {
+ if (item == nil)
+ return [selectedPithosAccount.accountNode.children objectAtIndex:index];
+ if ([item class] == [PithosContainerNode class]) {
+ PithosContainerNode *containerNode = (PithosContainerNode *)item;
+ NSInteger currentSubdirIndex = -1;
+ for (PithosNode *node in containerNode.children) {
+ if ([node class] == [PithosSubdirNode class]) {
+ currentSubdirIndex++;
+ if (currentSubdirIndex == index)
+ return node;
+ }
+ }
+ if (++currentSubdirIndex == index) {
+ PithosEmptyNode *rootFilesNode = [syncContainersRootFilesNodes objectForKey:containerNode.displayName];
+ if (!rootFilesNode) {
+ rootFilesNode = [[[PithosEmptyNode alloc] initWithDisplayName:@"<root files>"
+ icon:[[NSWorkspace sharedWorkspace] iconForFileType:@""]]
+ autorelease];
+ rootFilesNode.parent = containerNode;
+ [syncContainersRootFilesNodes setObject:rootFilesNode forKey:containerNode.displayName];
+ }
+ return rootFilesNode;
+ }
+ }
+ }
+ return nil;
+}
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
+ if (outlineView == syncContainersOutlineView) {
+ if ([item class] == [PithosContainerNode class])
+ return YES;
+ }
+ return NO;
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
+ if (outlineView == syncContainersOutlineView) {
+ if ([[tableColumn identifier] isEqualToString:@"sync"]) {
+ if ([item class] == [PithosContainerNode class]) {
+ // A container is
+ // off if not in dictionary
+ // mixed if in dictionary with exclusions
+ // on if in dictionary without exclusions
+ PithosContainerNode *node = (PithosContainerNode *)item;
+ NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.displayName];
+ if (containerExcludedDirectories) {
+ if ([containerExcludedDirectories count])
+ return [NSNumber numberWithUnsignedInteger:NSMixedState];
+ else
+ return [NSNumber numberWithUnsignedInteger:NSOnState];
+ }
+ return [NSNumber numberWithUnsignedInteger:NSOffState];
+ } else if ([item class] == [PithosSubdirNode class]) {
+ // Root files are off if parent container not in dictionary or if excluded
+ // displayName should be localized and lowercased
+ PithosSubdirNode *node = (PithosSubdirNode *)item;
+ NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
+ NSString *directoryName = [[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"];
+ if (!containerExcludedDirectories || [containerExcludedDirectories containsObject:directoryName])
+ return [NSNumber numberWithUnsignedInteger:NSOffState];
+ else
+ return [NSNumber numberWithUnsignedInteger:NSOnState];
+ } else if ([item class] == [PithosEmptyNode class]) {
+ // Root files are off if parent container not in dictionary or if excluded
+ PithosEmptyNode *node = (PithosEmptyNode *)item;
+ NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
+ if (!containerExcludedDirectories || [containerExcludedDirectories containsObject:@""])
+ return [NSNumber numberWithUnsignedInteger:NSOffState];
+ else
+ return [NSNumber numberWithUnsignedInteger:NSOnState];
+ }
+ return [NSNumber numberWithUnsignedInteger:NSOffState];
+ } else if ([[tableColumn identifier] isEqualToString:@"path"]) {
+ return (PithosNode *)item;
+ }
+ }
+ return nil;
+}
+
+- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
+ if (outlineView == syncContainersOutlineView) {
+ if ([[tableColumn identifier] isEqualToString:@"sync"]) {
+ NSCellStateValue newState = [object unsignedIntegerValue];
+ if ([item class] == [PithosContainerNode class]) {
+ // If new state is
+ // mixed/on include container with no excluded directories
+ // off exclude container
+ PithosContainerNode *node = (PithosContainerNode *)item;
+ if ((newState == NSOnState) || (newState == NSMixedState)) {
+ [syncContainersDictionary setObject:[NSMutableArray array] forKey:node.displayName];
+ } else {
+ [syncContainersDictionary removeObjectForKey:node.displayName];
+ }
+ [outlineView reloadItem:item reloadChildren:YES];
+ } else if ([item class] == [PithosSubdirNode class]) {
+ // If new state is
+ // mixed/on include directory (if container not included, include and exclude all others)
+ // off exclude directory
+ PithosSubdirNode *node = (PithosSubdirNode *)item;
+ NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
+ NSString *directoryName = [[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"];
+ if ((newState == NSOnState) || (newState == NSMixedState)) {
+ if (containerExcludedDirectories) {
+ [containerExcludedDirectories removeObject:directoryName];
+ } else {
+ NSMutableArray *newContainerExcludeDirectories = [NSMutableArray arrayWithObject:@""];
+ for (PithosNode *siblingNode in node.parent.children) {
+ if ([siblingNode class] == [PithosSubdirNode class]) {
+ NSString *siblingDirectoryName = [[siblingNode.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"];
+ if (![siblingDirectoryName isEqualToString:directoryName] &&
+ ![newContainerExcludeDirectories containsObject:siblingDirectoryName])
+ [newContainerExcludeDirectories addObject:siblingDirectoryName];
+ }
+ }
+ [syncContainersDictionary setObject:newContainerExcludeDirectories forKey:node.parent.displayName];
+ }
+ } else if (![containerExcludedDirectories containsObject:directoryName]) {
+ [containerExcludedDirectories addObject:directoryName];
+ }
+ [outlineView reloadItem:[outlineView parentForItem:item]];
+ } else if ([item class] == [PithosEmptyNode class]) {
+ // If new state is
+ // mixed/on include root files (if container not included, include and exclude all others)
+ // off exclude root files
+ PithosEmptyNode *node = (PithosEmptyNode *)item;
+ NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
+ if ((newState == NSOnState) || (newState == NSMixedState)) {
+ if (containerExcludedDirectories) {
+ [containerExcludedDirectories removeObject:@""];
+ } else {
+ NSMutableArray *newContainerExcludeDirectories = [NSMutableArray array];
+ for (PithosNode *siblingNode in node.parent.children) {
+ if ([siblingNode class] == [PithosSubdirNode class]) {
+ NSString *siblingDirectoryName = [[siblingNode.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"];
+ if (![newContainerExcludeDirectories containsObject:siblingDirectoryName])
+ [newContainerExcludeDirectories addObject:siblingDirectoryName];
+ }
+ }
+ [syncContainersDictionary setObject:newContainerExcludeDirectories forKey:node.parent.displayName];
+ }
+ } else if (![containerExcludedDirectories containsObject:@""]) {
+ [containerExcludedDirectories addObject:@""];
+ }
+ [outlineView reloadItem:[outlineView parentForItem:item]];
+ }
+ [self updateSync];
+ }
+ }
+}
+
@end