Minor UI changes.
[pithos-macos] / pithos-macos / PithosBrowserController.m
1     //
2 //  PithosBrowserController.m
3 //  pithos-macos
4 //
5 // Copyright 2011 GRNET S.A. All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
9 // conditions are met:
10 // 
11 //   1. Redistributions of source code must retain the above
12 //      copyright notice, this list of conditions and the following
13 //      disclaimer.
14 // 
15 //   2. Redistributions in binary form must reproduce the above
16 //      copyright notice, this list of conditions and the following
17 //      disclaimer in the documentation and/or other materials
18 //      provided with the distribution.
19 // 
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 // 
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
37
38 #import "PithosBrowserController.h"
39 #import "ASIPithosRequest.h"
40 #import "PithosAccountNode.h"
41 #import "PithosContainerNode.h"
42 #import "PithosEmptyNode.h"
43 #import "ImageAndTextCell.h"
44 #import "FileSystemBrowserCell.h"
45
46 //@interface PithosBrowserCell : NSBrowserCell {}
47 @interface PithosBrowserCell : FileSystemBrowserCell {}
48 @end
49
50 @implementation PithosBrowserCell
51
52 - (id)init {
53     if ((self = [super init])) {
54         [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
55     }
56     return self;
57 }
58
59 - (void)setObjectValue:(id)object {
60     if ([object isKindOfClass:[PithosNode class]]) {
61         PithosNode *node = (PithosNode *)object;
62         [self setStringValue:node.displayName];
63         [self setImage:node.icon];
64 //        // All cells are set as leafs because a branchingImage is already set!
65 //        // Maybe this cell is already inside an NSBrowserCell
66 //        [self setLeaf:YES];
67     } else {
68         [super setObjectValue:object];
69     }
70 }
71
72 @end
73
74 @interface PithosOutlineViewCell : ImageAndTextCell {}
75 @end
76
77 @implementation PithosOutlineViewCell
78
79 - (void)setObjectValue:(id)object {
80     if ([object isKindOfClass:[PithosNode class]]) {
81         PithosNode *node = (PithosNode *)object;
82         [self setStringValue:node.displayName];
83         [self setImage:node.icon];
84         [self setEditable:NO];
85     } else {
86         [super setObjectValue:object];
87     }
88 }
89
90 @end
91
92 @interface PithosBrowserController (Private) {}
93 - (void)authenticateWithAuthUser:(NSString *)authUser authToken:(NSString *)authToken;
94 - (void)resetContainers;
95 @end
96
97 @implementation PithosBrowserController
98 @synthesize userDefaultsController, outlineViewDataSourceArray, splitView, outlineView, browser;
99 @synthesize authenticationPanel, authenticationUserTextField, authenticationTokenTextField, authenticationRenewCheckBox, 
100             authenticationCancelPushButton, authenticationManualPushButton, authenticationLoginPushButton;
101
102 #pragma mark -
103 #pragma Object Lifecycle
104
105 - (id)init {
106     return [super initWithWindowNibName:@"PithosBrowserController"];
107 }
108
109 - (void)dealloc {
110     [[NSNotificationCenter defaultCenter] removeObserver:self];
111     [sharedPreviewController release];
112     [outlineViewDataSourceArray release];
113     [rootNode release];
114     [authenticationLoginPushButton release];
115     [authenticationManualPushButton release];
116     [authenticationCancelPushButton release];
117     [authenticationRenewCheckBox release];
118     [authenticationTokenTextField release];
119     [authenticationUserTextField release];
120     [authenticationPanel release];
121     [browser release];
122     [splitView release];
123     [outlineView release];
124     [userDefaultsController release];
125     [super dealloc];
126 }
127
128 - (void)awakeFromNib {
129     [browser setCellClass:[PithosBrowserCell class]];
130 }
131
132 - (void)resetContainers {
133     rootNode = nil;
134     [browser loadColumnZero];
135     self.outlineViewDataSourceArray = nil;
136     
137     // Create the outlineView tree
138     // CONTAINERS
139         NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
140                             [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
141     // CONTAINERS/pithos
142         [[containersTreeNode mutableChildNodes] addObject:
143      [NSTreeNode treeNodeWithRepresentedObject:
144       [[[PithosContainerNode alloc] initWithContainerName:@"pithos" 
145                                                      icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)]
146         ] autorelease]]];
147     // CONTAINERS/trash
148         [[containersTreeNode mutableChildNodes] addObject:
149      [NSTreeNode treeNodeWithRepresentedObject:
150       [[[PithosContainerNode alloc] initWithContainerName:@"trash"
151                                                      icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)]
152         ] autorelease]]];
153     // SHARED
154         NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
155                                       [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
156     // SHARED/my shared
157         [[sharedTreeNode mutableChildNodes] addObject:
158      [NSTreeNode treeNodeWithRepresentedObject:
159       [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared" 
160                                                icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
161         ] autorelease]]];
162     // SHARED/others shared
163         [[sharedTreeNode mutableChildNodes] addObject:
164      [NSTreeNode treeNodeWithRepresentedObject:
165       [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
166                                                icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGuestUserIcon)]
167         ] autorelease]]];
168     
169     self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
170     
171         // Expand the folder outline view
172     [outlineView expandItem:nil expandChildren:YES];
173         [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
174 }
175
176 - (void)windowDidLoad {
177 //    [userDefaultsController setAppliesImmediately:NO];
178     
179     [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
180     
181     // Register for updates
182     [[NSNotificationCenter defaultCenter] addObserver:self 
183                                              selector:@selector(pithosNodeChildrenUpdated:) 
184                                                  name:@"PithosNodeChildrenUpdated" 
185                                                object:nil];
186
187     [self authenticateWithAuthUser:[authenticationUserTextField stringValue] authToken:[authenticationTokenTextField stringValue]];
188 }
189
190 #pragma mark -
191 #pragma Observers
192
193 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
194     if ([[browser parentForItemsInColumn:[browser lastColumn]] isEqualTo:[notification object]]) 
195         [browser reloadColumn:[browser lastColumn]];
196 }
197
198 #pragma mark -
199 #pragma Actions
200
201 - (IBAction)refresh:(id)sender {
202     [[browser parentForItemsInColumn:[browser lastColumn]] invalidateChildren];
203     [browser reloadColumn:[browser lastColumn]];
204 }
205
206 #pragma mark -
207 #pragma Authentication
208
209 - (void)authenticateFromURLWithAuthUser:(NSString *)authUser authToken:(NSString *)authToken {
210     if ([authUser length] && [authToken length]) {
211         [authenticationUserTextField setStringValue:authUser];
212         [authenticationTokenTextField setStringValue:authToken];
213         [userDefaultsController save:self];
214         [self authenticateWithAuthUser:authUser authToken:authToken];
215     }
216     // XXX else maybe an error message?
217 }
218
219 - (void)authenticateWithAuthUser:(NSString *)authUser authToken:(NSString *)authToken {
220     // XXX hardcoded for now
221     NSString *storageURLPrefix = @"https://pithos.dev.grnet.gr/v1/";
222     
223     NSLog(@"Authentication - storageURLPrefix:%@, authUser:%@, authToken:%@", storageURLPrefix, authUser, authToken);
224     if ([authUser length] && [authToken length]) {
225         [ASIPithosRequest setStorageURL:[storageURLPrefix stringByAppendingString:[authUser stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
226         [ASIPithosRequest setAuthToken:authToken];
227         [self resetContainers];
228     } else {
229         [self authenticationSelect:nil];
230     }
231 }
232
233 - (IBAction)authenticationSelect:(id)sender {
234         [self.window makeFirstResponder:nil];
235         [NSApp beginSheet:authenticationPanel
236            modalForWindow:self.window
237         modalDelegate:self
238            didEndSelector:NULL
239                   contextInfo:nil];
240 }
241
242 - (IBAction)authenticationLogin:(id)sender {
243         [NSApp endSheet:authenticationPanel];
244         [authenticationPanel orderOut:self];
245     // XXX hardcoded for now
246     NSProcessInfo *processInfo = [NSProcessInfo processInfo];
247     NSString *loginURL = [NSString stringWithFormat:@"https://pithos.dev.grnet.gr/login?next=pithos://%@_%d",
248                           [processInfo processName], [processInfo processIdentifier]];
249     if ([authenticationRenewCheckBox state] == NSOnState)
250         loginURL = [loginURL stringByAppendingString:@"&renew="];
251     NSLog(@"loginURL: %@", loginURL);
252     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:loginURL]];
253     // XXX Should we wait for results or do something else?
254     // XXX check the case where this happens for the first time
255     // XXX maybe don't remove the Panel, and let the handler do it
256 }
257
258 - (IBAction)authenticationCancel:(id)sender {
259         [NSApp endSheet:authenticationPanel];
260         [authenticationPanel orderOut:self];
261     [userDefaultsController revert:sender];
262 }
263
264 - (IBAction)authenticationManual:(id)sender {
265         [NSApp endSheet:authenticationPanel];
266         [authenticationPanel orderOut:self];
267     [userDefaultsController save:sender];
268     // Because of delayed saves of the userDefaultsController, we use the TextField values directly, instead of 
269     //NSString *authUser = [[userDefaultsController values] valueForKey:@"authUser"];
270     //NSString *authToken = [[userDefaultsController values] valueForKey:@"authToken"];    
271     [self authenticateWithAuthUser:[authenticationUserTextField stringValue] authToken:[authenticationTokenTextField stringValue]];
272 }
273
274 #pragma mark -
275 #pragma NSBrowserDelegate
276
277 - (id)rootItemForBrowser:(NSBrowser *)browser {
278     return rootNode;    
279 }
280
281 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
282     PithosNode *node = (PithosNode *)item;
283     return node.children.count;
284 }
285
286 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
287     PithosNode *node = (PithosNode *)item;
288     return [node.children objectAtIndex:index];
289 }
290
291 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
292     PithosNode *node = (PithosNode *)item;
293     return node.isLeafItem;
294 }
295
296 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
297     PithosNode *node = (PithosNode *)item;
298     return node;
299 }
300
301 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
302     if (sharedPreviewController == nil)
303         sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
304     return sharedPreviewController;
305 }
306
307 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth  {
308 //    if (!forUserResize) {
309 //        id item = [browser parentForItemsInColumn:columnIndex]; 
310 //        if ([self browser:browser isLeafItem:item]) {
311 //            suggestedWidth = 200; 
312 //        }
313 //    }
314 //    return suggestedWidth;
315 //}
316
317 #pragma mark -
318 #pragma NSSplitViewDelegate
319
320 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
321     return 100;
322 }
323
324 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
325     return 260;
326 }
327
328 #pragma mark -
329 #pragma mark NSOutlineViewDelegate
330
331 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
332     return ([[item representedObject] isLeaf]);
333 }
334
335 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
336         return (![[item representedObject] isLeaf]);
337 }
338
339 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
340     PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
341     if (node) {
342         rootNode = node;
343         [browser loadColumnZero];
344     }
345 }
346
347 @end