1#import "FavoritesDrawerController.h"
2#import "CelestiaFavorite.h"
3#import "CelestiaFavorites.h"
4#define SAFENODE(node) ((MyTree*)((node == nil) ? [CelestiaFavorites sharedFavorites] : node))
5#define DragDropSimplePboardType @"CelestiaFavoriteOutlineViewPboardType"
6/*
7@implementation NSMenuItem(DebuggingAPI)
8-(NSString*)description
9{
10    return [NSString stringWithFormat:@"<MenuItem: 0x%x %@ enabled:%@ target:%@ representedObject:%@>",self,[self title],[self isEnabled]?@"YES":@"NO",[self target],[self representedObject]];
11}
12@end
13*/
14@implementation CelestiaFavorite(ViewAPI)
15-(NSMenuItem*)favoriteMenuItem
16{
17    NSMenuItem* menuItem = [[[NSMenuItem alloc] initWithTitle:[self name] action:([self isFolder] ? nil : @selector(activate)) keyEquivalent:@""] autorelease];
18    return [self setupFavoriteMenuItem:menuItem];
19}
20-(NSMenuItem*)setupFavoriteMenuItem:(NSMenuItem*)menuItem
21{
22    [menuItem setTarget:self];
23    [menuItem setAction:([self isFolder] ? nil : @selector(activate))];
24    [menuItem setTitle:[self name]];
25    [menuItem setRepresentedObject:self];
26    [menuItem setKeyEquivalent:@""];
27    [menuItem setEnabled:YES];
28    return menuItem;
29}
30@end
31
32@implementation CelestiaFavorites(ViewAPI)
33-(NSArray*)favoriteMenuItems
34{
35    NSEnumerator *enumerator = [[self children] objectEnumerator];
36    MyTree* node = nil;
37    NSMutableArray* menuItems = [NSMutableArray arrayWithCapacity:[[self children] count]];
38    //NSLog(@"[CelestiaFavorites favoriteMenuItems]");
39    while ((node = [enumerator nextObject]) != nil)
40        [menuItems addObject:[node favoriteMenuItem]];
41    return (NSArray*)menuItems;
42}
43@end
44@implementation MyTree(ViewAPI)
45-(void)activate
46{
47    [(CelestiaFavorite*)[self nodeValue] activate];
48}
49-(NSMenuItem*)favoriteMenuItem
50{
51    NSEnumerator* enumerator = nil;
52    NSMenu* subMenu = nil;
53    NSMenuItem* menuItem = [[self nodeValue] favoriteMenuItem];
54    MyTree* node = nil;
55    //NSLog(@"[MyTree favoriteMenuItem]");
56    if ([self isLeaf])
57        return menuItem;
58    enumerator = [[self children] objectEnumerator];
59    subMenu = [[[NSMenu alloc] initWithTitle:[[self nodeValue] name]] autorelease];
60    while ((node = [enumerator nextObject]) != nil)
61        [subMenu addItem:[node favoriteMenuItem]];
62    [menuItem setSubmenu:subMenu];
63    [menuItem setTarget:menuItem];
64    [menuItem setAction:@selector(submenuAction:)];
65    return menuItem;
66}
67@end
68
69@implementation FavoritesDrawerController
70-(NSArray*)selectedNodes
71{
72    return [outlineView allSelectedItems];
73}
74-(NSArray*)draggedNodes
75{
76    return draggedNodes;
77}
78-(void)activateFavorite:(CelestiaFavorite*)fav
79{
80    id menuItem = fav;
81    if ([menuItem isKindOfClass:[NSMenuItem class]])
82        fav = [(NSMenuItem*)menuItem representedObject];
83    [fav activate];
84    [outlineView deselectAll:self];
85}
86-(void)awakeFromNib
87{
88    draggedNodes = nil;
89    [outlineView setVerticalMotionCanBeginDrag: YES];
90    [outlineView setTarget:self];
91    [outlineView setDoubleAction:@selector(doubleClick:)];
92    [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:DragDropSimplePboardType, nil]];
93    [favoritesMenu setAutoenablesItems:NO];
94}
95-(IBAction)addNewFavorite:(id)sender
96{
97    CelestiaFavorites* favs = [CelestiaFavorites sharedFavorites];
98    MyTree* node = nil;
99    //NSLog(@"[FavoritesDrawerController addNewFavorite:%@]",sender);
100    node = [favs addNewFavorite:nil];
101
102    // kludge to fix name
103    {
104    CelestiaAppCore* appCore = [CelestiaAppCore sharedAppCore];
105    NSString* newName  = [ [ [appCore simulation] selection ] briefName ];
106    [((CelestiaFavorite*)[node nodeValue]) setName: newName];
107    }
108
109    [[CelestiaFavorites sharedFavorites] synchronize];
110//    [self outlineView:outlineView editItem:node];
111}
112-(IBAction)addNewFolder:(id)sender
113{
114    CelestiaFavorites* favs = [CelestiaFavorites sharedFavorites];
115    MyTree* node = nil;
116    //NSLog(@"[FavoritesDrawerController addNewFavorite:%@]",sender);
117    node = [favs addNewFolder:NSLocalizedString(@"untitled folder",@"")];
118    [[CelestiaFavorites sharedFavorites] synchronize];
119    [self outlineView:outlineView editItem:node];
120}
121-(IBAction)doubleClick:(id)sender
122{
123    //NSLog(@"[FavoritesDrawerController doubleClick:%@]",sender);
124    if ([outlineView numberOfSelectedRows]==1)
125    {
126        // Make sure item is not a folder
127        id theItem = [outlineView itemAtRow:[outlineView selectedRow]];
128        if (![outlineView isExpandable: theItem])
129            [self activateFavorite:[theItem nodeValue]];
130    }
131}
132-(void)synchronizeFavoritesMenu
133{
134    NSEnumerator *enumerator = [[[CelestiaFavorites sharedFavorites] favoriteMenuItems] objectEnumerator];
135    NSMenuItem *menuItem = nil;
136    // remove old menu items
137    while ([favoritesMenu numberOfItems]>3)
138        [favoritesMenu removeItemAtIndex:[favoritesMenu numberOfItems]-1];
139    while ((menuItem = [enumerator nextObject]) != nil)
140        [favoritesMenu addItem:menuItem];
141    [outlineView reloadData];
142}
143-(id)outlineView:(NSOutlineView*)olv child:(int)index ofItem:(id)item
144{
145    id rval;
146    //NSLog(@"[FavoritesDrawerController outlineview:%@ child:%d ofItem:%@]",olv,index,item);
147    rval = [SAFENODE(item) childAtIndex:index];
148    //NSLog(@"rval = %@",rval);
149    return rval;
150}
151-(BOOL)outlineView:(NSOutlineView*)olv isItemExpandable:(id)item
152{
153    //NSLog(@"[FavoritesDrawerController outlineview:%@ itemIsExpandable:%@]",olv,item);
154    return ![SAFENODE(item) isLeaf];
155}
156-(int)outlineView:(NSOutlineView *)olv numberOfChildrenOfItem:(id)item
157{
158    int rval;
159    //NSLog(@"[FavoritesDrawerController outlineview:%@ numberOfChildrenOfItem:%@]",olv,item);
160    rval = [SAFENODE(item) numberOfChildren];
161    //NSLog(@"rval = %d",rval);
162    return rval;
163}
164-(id)outlineView:(NSOutlineView *)olv objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
165{
166    id objectValue = nil;
167    //NSLog(@"[FavoritesDrawerController outlineview:%@ objectValueForTableColumn:%@ byItem:%@]",olv,tableColumn,item);
168    if([[tableColumn identifier] isEqualToString: @"NAME"]) {
169	objectValue = [(CelestiaFavorite*)[SAFENODE(item) nodeValue] name];
170    } else if([[tableColumn identifier] isEqualToString: @"SELECTION"] && [SAFENODE(item) isLeaf]) {
171	objectValue = [(CelestiaFavorite*)[SAFENODE(item) nodeValue] selectionName];
172    }
173    //NSLog(@"rval = %@",objectValue);
174    return objectValue;
175}
176// Optional method: needed to allow editing.
177-(void)outlineView:(NSOutlineView *)olv setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
178{
179    //NSLog(@"[FavoritesDrawerController outlineview:%@ setObjectValue:%@ forTableColumn:%@ byItem:%@]",olv,object,tableColumn,item);
180    if([[tableColumn identifier] isEqualToString: @"NAME"]) {
181        [(CelestiaFavorite*)[SAFENODE(item) nodeValue] setName: object];
182        [[CelestiaFavorites sharedFavorites] synchronize];
183        [(FavoriteInfoWindowController*)favoriteInfoWindowController updateFavorite:(CelestiaFavorite*)[SAFENODE(item) nodeValue]];
184
185    }
186}
187
188// ================================================================
189//  NSOutlineView delegate methods.
190// ================================================================
191- (NSMenu *)outlineView:(NSOutlineView *)olv
192contextMenuForItem:(id)item
193{
194    MyTree* node = SAFENODE(item);
195    NSMenu* contextMenu = [favoritesContextMenu copy];
196    NSInvocation* editInv = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:@selector(outlineView:editItem:)]];
197    NSInvocation* delInv = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:@selector(outlineView:deleteItem:)]];
198    NSMenuItem* editItem = [contextMenu itemAtIndex:3];
199    NSMenuItem* delItem = [contextMenu itemAtIndex:4];
200    NSMutableArray* arr = nil;
201
202    BOOL multipleItems = NO;
203    //NSLog(@"[FavoritesDrawerController outlineView:%@ contextMenuForItem:%@]",olv,item);
204    //NSLog(@"row = %d",[olv rowForItem:item]);
205    //[olv selectRow:[olv rowForItem:item] byExtendingSelection:NO];
206    if ([olv numberOfSelectedRows]>1 || [olv rowForItem:item]!=[olv selectedRow]) {
207        id nextItem = nil;
208        NSEnumerator* rowEnum = [olv selectedRowEnumerator];
209        arr = [NSMutableArray arrayWithCapacity:[olv numberOfSelectedRows]];
210        while ((nextItem = [rowEnum nextObject]) != nil) {
211            nextItem = [olv itemAtRow:[nextItem intValue]];
212            if (nextItem == nil)
213                continue;
214            if ([item isEqual:nextItem])
215                multipleItems = YES;
216            [arr addObject:nextItem];
217        }
218    }
219    if (multipleItems)
220        item = arr;
221    else
222        [olv selectRow:[olv rowForItem:item] byExtendingSelection:NO];
223    if (![node isLeaf] || multipleItems) {
224        [contextMenu removeItemAtIndex:0];
225        [contextMenu removeItemAtIndex:0];
226        [contextMenu removeItemAtIndex:0];
227    } else {
228        NSMenuItem* navItem = [contextMenu itemAtIndex:0];
229        NSString* title = [navItem title];
230        NSMenuItem* showItem = [contextMenu itemAtIndex:1];
231        [showItem setTarget:favoriteInfoWindowController];
232        [showItem setAction:@selector(showWindow:)];
233        [(CelestiaFavorite*)[node nodeValue] setupFavoriteMenuItem:navItem];
234        [navItem setTarget:self];
235        [navItem setTitle:title];
236        [navItem setAction:@selector(activateFavorite:)];
237    }
238    if (multipleItems) {
239        [contextMenu removeItemAtIndex:0];
240    } else {
241        [editInv setTarget:self];
242        [editInv setSelector:@selector(outlineView:editItem:)];
243        [editInv setArgument:&olv atIndex:2];
244        [editInv setArgument:&item atIndex:3];
245        [editItem setTarget:editInv];
246        [editItem setAction:@selector(invoke)];
247    }
248    [delInv setTarget:self];
249    [delInv setSelector:@selector(outlineView:deleteItem:)];
250    [delInv setArgument:&olv atIndex:2];
251    [delInv setArgument:&item atIndex:3];
252    [delItem setTarget:delInv];
253    [delItem setAction:@selector(invoke)];
254    return contextMenu;
255}
256- (void)outlineView:(NSOutlineView*)olv editItem:(id)item
257{
258    int row = [olv rowForItem:item];
259    //NSLog(@"[FavoritesDrawerController outlineView:%@ editItem:%@]",olv,item);
260    //NSLog(@"row = %d",row); // -1 ??
261    if (row<0) {
262        NSLog(@"row is -1");
263        return;
264    }
265    [olv selectRow:row byExtendingSelection:NO];
266    [olv editColumn:[olv columnWithIdentifier:@"NAME"] row:row withEvent:nil select:YES];
267}
268
269- (void)outlineView:(NSOutlineView*)olv deleteItem:(id)item
270{
271    //NSLog(@"[FavoritesDrawerController outlineView:%@ deleteItem:%@]",olv,item);
272    if ([item isKindOfClass:[MyTree class]])
273        [item removeFromParent];
274    else
275        [(NSArray*)item makeObjectsPerformSelector:@selector(removeFromParent)];
276    [[CelestiaFavorites sharedFavorites] synchronize];
277    [olv deselectAll:self];
278    [(FavoriteInfoWindowController*)favoriteInfoWindowController updateFavorite:nil];
279}
280
281-(void)outlineViewSelectionDidChange:(NSNotification*)notification
282{
283    NSOutlineView* olv = [notification object];
284    if ([olv numberOfSelectedRows]==1) {
285        [(FavoriteInfoWindowController*)favoriteInfoWindowController updateFavorite:[[olv itemAtRow:[olv selectedRow]] nodeValue]];
286    } else {
287        [(FavoriteInfoWindowController*)favoriteInfoWindowController updateFavorite:nil];
288    }
289}
290/*
291- (BOOL)outlineView:(NSOutlineView *)olv shouldExpandItem:(id)item
292{
293    BOOL rval;
294    NSLog(@"[FavoritesDrawerController outlineview:%@ shouldExpandItem:%@]",olv,item);
295    rval = ![SAFENODE(item) isLeaf];
296    NSLog(@"rval = %@",((rval)?@"YES":@"NO"));
297    return rval;
298}
299*/
300/*
301- (void)outlineView:(NSOutlineView *)olv willDisplayCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
302{
303    // NSLog(@"[FavoritesDrawerController outlineview:%@ willDisplayCell:%@ forTableColumn:%@ item:0x%X]",olv,cell,tableColumn,item);
304    if ([[tableColumn identifier] isEqualToString: @"NAME"]) {
305	[cell setStringValue: [(CelestiaFavorite*)item name]];
306    } else if ([[tableColumn identifier] isEqualToString: @"SELECTION"]) {
307	if ([(CelestiaFavorite*)item isFolder])
308            [cell setStringValue:@""];
309        else
310            [cell setStringValue:[(CelestiaFavorite*)item selectionName]];
311    }
312}
313*/
314
315// ================================================================
316//  NSOutlineView data source methods. (dragging related)
317// ================================================================
318
319- (BOOL)outlineView:(NSOutlineView *)olv writeItems:(NSArray*)items toPasteboard:(NSPasteboard*)pboard
320{
321    //NSLog(@"[FavoritesDrawerController outlineView:%@ writeItems:%@ toPasteboard:%@]",olv,items,pboard);
322    draggedNodes = items; // Don't retain since this is just holding temporaral drag information, and it is only used during a drag!  We could put this in the pboard actually.
323
324    // Provide data for our custom type, and simple NSStrings.
325    [pboard declareTypes:[NSArray arrayWithObjects: DragDropSimplePboardType, NSStringPboardType, nil] owner:self];
326
327    // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!.
328    [pboard setData:[NSData data] forType:DragDropSimplePboardType];
329
330    // Put string data on the pboard... notice you candrag into TextEdit!
331    [pboard setString: [draggedNodes description] forType: NSStringPboardType];
332
333    return YES;
334}
335
336- (unsigned int)outlineView:(NSOutlineView*)olv validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)childIndex {
337    // This method validates whether or not the proposal is a valid one. Returns NO if the drop should not be allowed.
338    MyTree *targetNode = item;
339    BOOL targetNodeIsValid = YES;
340
341    BOOL isOnDropTypeProposal = childIndex==NSOutlineViewDropOnItemIndex;
342    //NSLog(@"[FavoritesDrawerController outlineView:%@ validateDrop:%@ proposedItem:%@ proposedChildIndex:%d]",olv,info,item,childIndex);
343    // Refuse if: dropping "on" the view itself unless we have no data in the view.
344    /*
345     if (targetNode==nil && isOnDropTypeProposal==YES && [self outlineView:olv numberOfChildrenOfItem:nil]!=0)
346        targetNodeIsValid = NO;
347    */
348    if (targetNode==nil && isOnDropTypeProposal==YES)// && [self allowOnDropOnLeaf]==NO)
349        targetNodeIsValid = NO;
350
351    if (targetNodeIsValid && [targetNode isLeaf])
352        targetNodeIsValid = NO;
353
354    if (isOnDropTypeProposal)
355        targetNodeIsValid = NO;
356
357    // Check to make sure we don't allow a node to be inserted into one of its descendants!
358    if (targetNodeIsValid && ([info draggingSource]==outlineView) && [[info draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObject: DragDropSimplePboardType]] != nil) {
359        NSArray* _draggedNodes = [[[info draggingSource] dataSource] draggedNodes];
360        //NSEnumerator* enumerator = [_draggedNodes objectEnumerator];
361        //MyTree* node = nil;
362        targetNodeIsValid = ![targetNode isDescendantOfNodeInArray: _draggedNodes];
363        /*
364         while ((node = [enumerator nextObject]) != nil)
365            if ([[node parent] isEqualTo:SAFENODE(item)] && !isOnDropTypeProposal && childIndex >= [SAFENODE(item) numberOfChildren]) {
366                targetNodeIsValid = NO;
367                break;
368            }
369         */
370    }
371
372
373    // Set the item and child index in case we computed a retargeted one.
374    [outlineView setDropItem:targetNode dropChildIndex:childIndex];
375
376    return targetNodeIsValid ? NSDragOperationGeneric : NSDragOperationNone;
377}
378
379- (void)_performDropOperation:(id <NSDraggingInfo>)info onNode:(id)pnode atIndex:(int)childIndex {
380    // Helper method to insert dropped data into the model.
381    NSPasteboard* pboard = [info draggingPasteboard];
382    NSMutableArray* itemsToSelect = nil;
383    MyTree* parentNode = SAFENODE(pnode);
384    //NSLog(@"[FavoritesDrawerController _performDropOperation:%@ onNode:%@ atIndex:%d]",info,pnode,childIndex);
385    //NSLog(@"parentNode = %@",parentNode);
386    if ([pboard availableTypeFromArray:[NSArray arrayWithObjects:DragDropSimplePboardType, nil]] != nil) {
387        FavoritesDrawerController *dragDataSource = [[info draggingSource] dataSource];
388        NSArray *_draggedNodes = [MyTree minimumNodeCoverFromNodesInArray: [dragDataSource draggedNodes]];
389        NSEnumerator *draggedNodesEnum = [_draggedNodes objectEnumerator];
390        MyTree *_draggedNode = nil, *_draggedNodeParent = nil;
391        //MyTree* favs = [CelestiaFavorites sharedFavorites];
392	itemsToSelect = [NSMutableArray arrayWithArray:[self selectedNodes]];
393        /*
394        NSLog(@"dragDataSource = %@",dragDataSource);
395        NSLog(@"_draggedNodes = %@",_draggedNodes);
396        NSLog(@"itemsToSelect = %@",itemsToSelect);
397         */
398        while ((_draggedNode = [draggedNodesEnum nextObject])) {
399            _draggedNodeParent = [_draggedNode parent];
400            if (parentNode==_draggedNodeParent && [parentNode indexOfChild: _draggedNode]<childIndex) childIndex--;
401            [_draggedNodeParent removeChild: _draggedNode];
402        }
403        [parentNode insertChildren: _draggedNodes atIndex: childIndex];
404    }
405
406    [[CelestiaFavorites sharedFavorites] synchronize];
407    [outlineView selectItems: itemsToSelect byExtendingSelection: NO];
408}
409
410- (BOOL)outlineView:(NSOutlineView*)olv acceptDrop:(id <NSDraggingInfo>)info item:(id)targetItem childIndex:(int)childIndex {
411    MyTree * 		parentNode = nil;
412    //NSLog(@"[FavoritesDrawerController outlineView:%@ acceptDrop:%@ item:%@ childIndex:%d]",olv,info,targetItem,childIndex);
413    // Determine the parent to insert into and the child index to insert at.
414    parentNode = (MyTree*)(targetItem ? targetItem : [CelestiaFavorites sharedFavorites]);//SAFENODE(targetItem);
415    childIndex = (childIndex==NSOutlineViewDropOnItemIndex ? 0 : childIndex);
416    [self _performDropOperation:info onNode:parentNode atIndex:childIndex];
417    return YES;
418}
419
420@end
421