+#pragma mark NSOutlineViewDataSource
+
+// <my account> [PithosEmptyNode]
+// - <container>+ [PithosContainerNode]
+// -- <subdir>+ [PithosSubdirNode]
+// -- <root files> [PithosEmptyNode]
+// <sharing account>+ [PithosSharingAccountNode]
+// - <container>+ [PithosContainerNode]
+// -- <subdir>+ [PithosSubdirNode]
+// -- <root files> [PithosEmptyNode]
+
+- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
+ if (!selectedPithosAccount.active)
+ return 0;
+ if (outlineView == syncAccountsOutlineView) {
+ if (item == nil) {
+ // root: <my account> + #<sharing account>
+ NSInteger accountsCount = 0;
+ if ([selectedPithosAccount.accountNode.children count])
+ accountsCount = 1;
+ if (selectedPithosAccount.sharingAccountsNode.children)
+ accountsCount += selectedPithosAccount.sharingAccountsNode.children.count;
+ return accountsCount;
+ } else if (item == syncAccountsMyAccountNode) {
+ // root/<my account>: #<container>
+ if (selectedPithosAccount.accountNode.children) {
+ NSInteger containersCount = 0;
+ for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
+ if (![[node.displayName lowercaseString] isEqualToString:@"shared to me"])
+ containersCount++;
+ }
+ return containersCount;
+ }
+ } else if ([item class] == [PithosAccountNode class]) {
+ // root/<sharing account>: #<container>
+ PithosAccountNode *accountNode = (PithosAccountNode *)item;
+ if (accountNode.children)
+ return accountNode.children.count;
+ } else if ([item class] == [PithosContainerNode class]) {
+ // root/{<my account>, <sharing account>}/<container>: #<subdir> + <root files>
+ PithosContainerNode *containerNode = (PithosContainerNode *)item;
+ if (containerNode.children) {
+ // We add 1 for the root files node
+ NSInteger subdirCount = 1;
+ for (PithosNode *node in containerNode.children) {
+ if (([node class] == [PithosSubdirNode class]) && (!syncSkipHidden || ![node.displayName hasPrefix:@"."]))
+ subdirCount++;
+ }
+ return subdirCount;
+ }
+ }
+ }
+ return 0;
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
+ if (outlineView == syncAccountsOutlineView) {
+ if (item == nil) {
+ // root: [ <my account>, <sharing account>+ ]
+ if ([selectedPithosAccount.accountNode.children count]) {
+ if (index == 0)
+ return syncAccountsMyAccountNode;
+ else
+ return [selectedPithosAccount.sharingAccountsNode.children objectAtIndex:(index - 1)];
+ } else {
+ return [selectedPithosAccount.sharingAccountsNode.children objectAtIndex:index];
+ }
+ } else if (item == syncAccountsMyAccountNode) {
+ // root/<my account>: [ <container>+ ]
+ NSInteger currentContainerIndex = -1;
+ for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
+ if (![[node.displayName lowercaseString] isEqualToString:@"shared to me"]) {
+ currentContainerIndex++;
+ if (currentContainerIndex == index)
+ return node;
+ }
+ }
+ } else if ([item class] == [PithosAccountNode class]) {
+ // root/<sharing account>: [ <container>+ ]
+ return [((PithosAccountNode *)item).children objectAtIndex:index];
+ } else if ([item class] == [PithosContainerNode class]) {
+ // root/{<my account>, <sharing account>}/<container>: [ <subdir>+, <root files> ]
+ PithosContainerNode *containerNode = (PithosContainerNode *)item;
+ NSInteger currentSubdirIndex = -1;
+ for (PithosNode *node in containerNode.children) {
+ if (([node class] == [PithosSubdirNode class]) && (!syncSkipHidden || ![node.displayName hasPrefix:@"."])) {
+ currentSubdirIndex++;
+ if (currentSubdirIndex == index)
+ return node;
+ }
+ }
+ if (++currentSubdirIndex == index) {
+ NSString *accountName = containerNode.sharingAccount;
+ if (!accountName)
+ accountName = @"";
+ PithosEmptyNode *rootFilesNode = [[syncAccountsRootFilesNodes objectForKey:accountName]
+ objectForKey:containerNode.displayName];
+ if (!rootFilesNode) {
+ if (![syncAccountsRootFilesNodes objectForKey:accountName])
+ [syncAccountsRootFilesNodes setObject:[NSMutableDictionary dictionary] forKey:accountName];
+ rootFilesNode = [[PithosEmptyNode alloc] initWithDisplayName:@"<root files>"
+ icon:[[NSWorkspace sharedWorkspace] iconForFileType:@""]];
+ rootFilesNode.parent = containerNode;
+ [[syncAccountsRootFilesNodes objectForKey:accountName] setObject:rootFilesNode forKey:containerNode.displayName];
+ }
+ return rootFilesNode;
+ }
+ }
+ }
+ return nil;
+}
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
+ if (outlineView == syncAccountsOutlineView) {
+ if ((item == syncAccountsMyAccountNode) ||
+ ([item class] == [PithosAccountNode class]) ||
+ ([item class] == [PithosContainerNode class]))
+ return YES;
+ }
+ return NO;
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
+ if (outlineView == syncAccountsOutlineView) {
+ if ([[tableColumn identifier] isEqualToString:@"sync"]) {
+ if (item == syncAccountsMyAccountNode) {
+ // root/<my account>
+ // My account is
+ // off if not in dictionary
+ // mixed if in dictionary with exclusions
+ // on if in dictionary without exclusions
+ NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:@""];
+ if (syncContainersDictionary) {
+ for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
+ if (![[node.displayName lowercaseString] isEqualToString:@"shared to me"]) {
+ NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.displayName];
+ if (!containerExcludedDirectories || [containerExcludedDirectories count])
+ return [NSNumber numberWithUnsignedInteger:NSMixedState];
+ }
+ }
+ return [NSNumber numberWithUnsignedInteger:NSOnState];
+ }
+ return [NSNumber numberWithUnsignedInteger:NSOffState];
+ } else if ([item class] == [PithosAccountNode class]) {
+ // root/<sharing account>
+ // A sharing account is
+ // off if not in dictionary
+ // mixed if in dictionary with exclusions
+ // on if in dictionary without exclusions
+ PithosAccountNode *accountNode = (PithosAccountNode *)item;
+ NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountNode.displayName];
+ if (syncContainersDictionary) {
+ for (PithosContainerNode *node in accountNode.children) {
+ NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.displayName];
+ if (!containerExcludedDirectories || [containerExcludedDirectories count])
+ return [NSNumber numberWithUnsignedInteger:NSMixedState];
+ }
+ return [NSNumber numberWithUnsignedInteger:NSOnState];
+ }
+ return [NSNumber numberWithUnsignedInteger:NSOffState];
+ } else if ([item class] == [PithosContainerNode class]) {
+ // root/{<my account>, <sharing account>}/<container>
+ // A container is
+ // off if not in dictionary
+ // mixed if in dictionary with exclusions
+ // on if in dictionary without exclusions
+ PithosContainerNode *node = (PithosContainerNode *)item;
+ NSString *accountName = node.sharingAccount;
+ if (!accountName)
+ accountName = @"";
+ NSMutableSet *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName]
+ 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/{<my account>, <sharing account>}/<container>/<subdir>
+ // Directory is off if parent container not in dictionary or if excluded
+ // displayName should be localized and lowercased
+ PithosSubdirNode *node = (PithosSubdirNode *)item;
+ NSString *accountName = node.sharingAccount;
+ if (!accountName)
+ accountName = @"";
+ NSMutableSet *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName]
+ objectForKey:node.parent.displayName];
+ if (!containerExcludedDirectories ||
+ [containerExcludedDirectories
+ containsObject:[[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"]])
+ return [NSNumber numberWithUnsignedInteger:NSOffState];
+ else
+ return [NSNumber numberWithUnsignedInteger:NSOnState];
+ } else if ([item class] == [PithosEmptyNode class]) {
+ // root/{<my account>, <sharing account>}/<container>/<root files>
+ // Root files is off if parent container not in dictionary or if excluded
+ PithosEmptyNode *node = (PithosEmptyNode *)item;
+ NSString *accountName = node.parent.sharingAccount;
+ if (!accountName)
+ accountName = @"";
+ NSMutableSet *containerExcludedDirectories = [[syncAccountsDictionary objectForKey:accountName]
+ 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 == syncAccountsOutlineView) {
+ if ([[tableColumn identifier] isEqualToString:@"sync"]) {
+ NSCellStateValue newState = [object unsignedIntegerValue];
+ if (item == syncAccountsMyAccountNode) {
+ // root/<my account>
+ // If new state is
+ // mixed/on include my account with no exclusions
+ // off exclude my account
+ if ((newState == NSOnState) || (newState == NSMixedState)) {
+ NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary];
+ for (PithosContainerNode *node in selectedPithosAccount.accountNode.children) {
+ if (![[node.displayName lowercaseString] isEqualToString:@"shared to me"])
+ [syncContainersDictionary setObject:[NSMutableSet set] forKey:node.displayName];
+ }
+ [syncAccountsDictionary setObject:syncContainersDictionary forKey:@""];
+ } else {
+ [syncAccountsDictionary removeObjectForKey:@""];
+ }
+ [outlineView reloadItem:item reloadChildren:YES];
+ } else if ([item class] == [PithosAccountNode class]) {
+ // root/<sharing account>
+ // If new state is
+ // mixed/on include sharing account with no exclusions
+ // off exclude sharing account
+ PithosAccountNode *accountNode = (PithosAccountNode *)item;
+ if ((newState == NSOnState) || (newState == NSMixedState)) {
+ NSMutableDictionary *syncContainersDictionary = [NSMutableDictionary dictionary];
+ for (PithosContainerNode *node in accountNode.children) {
+ [syncContainersDictionary setObject:[NSMutableSet set] forKey:node.displayName];
+ }
+ [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountNode.displayName];
+ } else {
+ [syncAccountsDictionary removeObjectForKey:accountNode.displayName];
+ }
+ [outlineView reloadItem:item reloadChildren:YES];
+ } else if ([item class] == [PithosContainerNode class]) {
+ // root/{<my account>, <sharing account>}/<container>
+ // If new state is
+ // mixed/on include container with no excluded directories
+ // off exclude container
+ PithosContainerNode *node = (PithosContainerNode *)item;
+ NSString *accountName = node.sharingAccount;
+ PithosNode *accountNode = node.parent;
+ if (!accountName) {
+ accountName = @"";
+ accountNode = syncAccountsMyAccountNode;
+ }
+ NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName];
+ if ((newState == NSOnState) || (newState == NSMixedState)) {
+ if (!syncContainersDictionary) {
+ syncContainersDictionary = [NSMutableDictionary dictionary];
+ [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
+ }
+ [syncContainersDictionary setObject:[NSMutableSet set] forKey:node.displayName];
+ } else if (syncContainersDictionary) {
+ [syncContainersDictionary removeObjectForKey:node.displayName];
+ if (![syncContainersDictionary count])
+ [syncAccountsDictionary removeObjectForKey:accountName];
+ }
+ [outlineView reloadItem:accountNode reloadChildren:YES];
+ } else if ([item class] == [PithosSubdirNode class]) {
+ // root/{<my account>, <sharing account>}/<container>/<subdir>
+ // If new state is
+ // mixed/on include directory (if container not included, include and exclude all others)
+ // off exclude directory
+ PithosSubdirNode *node = (PithosSubdirNode *)item;
+ NSString *accountName = node.sharingAccount;
+ PithosNode *accountNode = node.parent.parent;
+ if (!accountName) {
+ accountName = @"";
+ accountNode = syncAccountsMyAccountNode;
+ }
+ NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName];
+ NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
+ NSString *directoryName = [[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"];
+ if ((newState == NSOnState) || (newState == NSMixedState)) {
+ if (containerExcludedDirectories) {
+ [containerExcludedDirectories removeObject:directoryName];
+ } else {
+ if (!syncContainersDictionary) {
+ syncContainersDictionary = [NSMutableDictionary dictionary];
+ [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
+ }
+ NSMutableSet *newContainerExcludeDirectories = [NSMutableSet setWithObject:@""];
+ for (PithosNode *siblingNode in node.parent.children) {
+ if (([siblingNode class] == [PithosSubdirNode class]) &&
+ (!syncSkipHidden || ![siblingNode.displayName hasPrefix:@"."])) {
+ 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 (syncContainersDictionary &&
+ containerExcludedDirectories &&
+ ![containerExcludedDirectories containsObject:directoryName]) {
+ [containerExcludedDirectories addObject:directoryName];
+ }
+ [outlineView reloadItem:accountNode reloadChildren:YES];
+ } 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;
+ NSString *accountName = node.parent.sharingAccount;
+ PithosNode *accountNode = node.parent.parent;
+ if (!accountName) {
+ accountName = @"";
+ accountNode = syncAccountsMyAccountNode;
+ }
+ NSMutableDictionary *syncContainersDictionary = [syncAccountsDictionary objectForKey:accountName];
+ NSMutableSet *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
+ if ((newState == NSOnState) || (newState == NSMixedState)) {
+ if (containerExcludedDirectories) {
+ [containerExcludedDirectories removeObject:@""];
+ } else {
+ if (!syncContainersDictionary) {
+ syncContainersDictionary = [NSMutableDictionary dictionary];
+ [syncAccountsDictionary setObject:syncContainersDictionary forKey:accountName];
+ }
+ NSMutableSet *newContainerExcludeDirectories = [NSMutableSet set];
+ for (PithosNode *siblingNode in node.parent.children) {
+ if (([siblingNode class] == [PithosSubdirNode class]) &&
+ (!syncSkipHidden || ![siblingNode.displayName hasPrefix:@"."])) {
+ NSString *siblingDirectoryName = [[siblingNode.displayName lowercaseString]
+ stringByReplacingOccurrencesOfString:@"/" withString:@":"];
+ if (![newContainerExcludeDirectories containsObject:siblingDirectoryName])
+ [newContainerExcludeDirectories addObject:siblingDirectoryName];
+ }
+ }
+ [syncContainersDictionary setObject:newContainerExcludeDirectories forKey:node.parent.displayName];
+ }
+ } else if (syncContainersDictionary &&
+ containerExcludedDirectories &&
+ ![containerExcludedDirectories containsObject:@""]) {
+ [containerExcludedDirectories addObject:@""];
+ }
+ [outlineView reloadItem:accountNode reloadChildren:YES];
+ }
+ [self updateSync];
+ }
+ }