Source

OPML / OPML / OPMLOutline+ViewRelated.m

Full commit
//
//  OPMLOutline+ViewRelated.m
//  OPML
//
//  Created by Ivan Vučica on 07.01.2012..
//  Copyright (c) 2012 Ivan Vučica. All rights reserved.
//

#import "OPMLOutline+ViewRelated.h"
#import "OPMLOutline+Private.h"
#import "OPMLOutlineXMLElement.h"
#import "OPMLOutlineView.h"

NSString *OPMLOutline_NodeDragAndDropPboardType = @"OPMLOutline_NodeDragAndDropPboardType";

#if GNUSTEP
#warning This is probably defined somewhere.
#define MAXFLOAT 10000000
#endif

@implementation OPMLOutline (ViewRelated)

// MARK: -
// MARK: Outline view data source

-(NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
    if(!item)
    {
        // own child count
        return [[[self bodyNode] nodesForXPath:@"./outline" error:NULL] count];
    }
    
    if([item isKindOfClass:[OPMLOutlineXMLElement class]])
    {
        OPMLOutlineXMLElement * xmlItem = (OPMLOutlineXMLElement*)item;
        
        NSString * xmlItemType = [[xmlItem attributeForName:@"type"] stringValue];

        if([xmlItemType isEqualToString:@"include"])
        {
            if(![[xmlItem attributeForName:@"url"] stringValue])
            {
                // no url? it's not an inclusion.
                // just return the number of locally-stored children.
                return [[xmlItem nodesForXPath:@"./outline" error:NULL] count];
            }
            // otherwise load the outline, assign it to the
            // node, and return the number of children in included outline.
            
            OPMLOutline * includedOutline = [xmlItem childOutline];
            if(!includedOutline)
            {
                // not loaded? load and cache inside the xml element
                includedOutline = [self includedOutlineForURL:[NSURL URLWithString:[[xmlItem attributeForName:@"url"] stringValue]]];
                [xmlItem setChildOutline:includedOutline];
            }
            if(includedOutline)
            {
                // we have loaded the outline ok, ask this outline for its root node's children
                return [includedOutline outlineView:outlineView numberOfChildrenOfItem:nil];
            }
            else
            {
                // we have not loaded the outline ok, so return local children
                return [[xmlItem nodesForXPath:@"./outline" error:NULL] count];
            }
        }
        else
        {
        #if !GNUSTEP
            return [xmlItem childCount];
        #else
            return [[xmlItem nodesForXPath:@"./outline" error:NULL] count];
        #endif
        }
    }
    
    return 0;
}

-(id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
    if(!item)
    {
        // root body node
        return [[[self bodyNode] nodesForXPath:@"./outline" error:NULL] objectAtIndex:index];
    }
    
    if([item isKindOfClass:[OPMLOutlineXMLElement class]])
    {
        OPMLOutlineXMLElement * xmlItem = (OPMLOutlineXMLElement*)item;
        
        OPMLOutline * includedOutline = [xmlItem childOutline];
        if(includedOutline) // ask outline about its root node's child
            return [includedOutline outlineView:outlineView child:index ofItem:nil];
        
        return [[xmlItem nodesForXPath:@"./outline" error:NULL] objectAtIndex:index];
    }
    
    return nil;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item;
{
    return YES;
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
{
    if ([item isKindOfClass:[OPMLOutlineXMLElement class]]) 
    {
        OPMLOutlineXMLElement * element = (OPMLOutlineXMLElement*)item;
        
        return [[element attributeForName:@"text"] stringValue];
    }
    return nil;
}

- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
    if ([item isKindOfClass:[OPMLOutlineXMLElement class]]) {
        OPMLOutlineXMLElement * element = (OPMLOutlineXMLElement*)item;
#if !GNUSTEP
        if([element rootDocument] == _xmlDocument)
#else
#warning GNUstep: Not checking whether current node is member of the associated document
#endif
        {
            NSXMLNode * textAttribute = [element attributeForName:@"text"];
            
            [[[self undoManager] prepareWithInvocationTarget:self] outlineView:outlineView setObjectValue:[textAttribute stringValue] forTableColumn:tableColumn byItem:item];
            
            [textAttribute setStringValue:object];
        }
    }
    [outlineView reloadItem:item reloadChildren:YES];
}

// MARK: -
// MARK: Outline view delegate

- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
{
    NSSize size = NSZeroSize;
    
    NSInteger level = [outlineView levelForItem:item];
    CGFloat indentation = (level+1) * [outlineView indentationPerLevel];
    NSString * text = [self outlineView:outlineView objectValueForTableColumn:nil byItem:item];
    
    NSInteger rowId = [outlineView rowForItem:item];
    NSTableColumn * column = [[outlineView tableColumns] objectAtIndex:0];
    NSTextFieldCell * cell = (NSTextFieldCell*)[column dataCellForRow:rowId];
    if(![cell isKindOfClass:[NSTextFieldCell class]])
    {
        return 17;
    }

    CGFloat extraPixels = 10;
    
    CGFloat availableWidth = [[outlineView superview] frame].size.width - indentation - cell.image.size.width - extraPixels;
    NSRect rect = NSMakeRect(0, 0, availableWidth, MAXFLOAT);
    
    NSDictionary * attributes = nil;
    if([[cell attributedStringValue] length] > 0)
    {
        attributes = [[cell attributedStringValue] attributesAtIndex:0 effectiveRange:NULL];
    }
    
    /////////////////////
    
    size = [text boundingRectWithSize:rect.size 
                              options:NSStringDrawingUsesLineFragmentOrigin 
                           attributes:attributes].size;
    
    if(size.height < 17)
        return 17;
    if(size.height > 400)
        return 400;
    if(isnan(size.height))
        return 17;
    if(isinf(size.height))
        return 17;
    return size.height;
}

-(BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
    if([item isKindOfClass:[OPMLOutlineXMLElement class]])
    {
#if !GNUSTEP
        if ([item rootDocument] == _xmlDocument)
#endif
        {
            return YES;
        }
        return NO;
    }
    return NO;
}

-(void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
    
    //[cell setImage:[NSImage imageNamed:@"NSAddTemplate"]];
    
}
- (NSArray *)writablePasteboardTypes {
    return [NSArray arrayWithObjects:
            NSPasteboardTypeString,
            OPMLOutline_NodeDragAndDropPboardType, 
            
            nil];
}

- (BOOL)outlineView:(NSOutlineView *)outlineView
         writeItems:(NSArray *)items
       toPasteboard:(NSPasteboard *)pasteboard
{
    if(items.count > 1)
        return NO;
    OPMLOutlineXMLElement * element = [items objectAtIndex:0];
    NSString * xpath = [element XPath];
    NSString * xml = [element XMLString];
    NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:xpath, @"xpath", xml, @"xml", nil];
    
    [pasteboard declareTypes:[self writablePasteboardTypes]
                       owner:self];
    
    [pasteboard setData:[NSKeyedArchiver archivedDataWithRootObject:dict]
                forType:OPMLOutline_NodeDragAndDropPboardType];
    [pasteboard setString:xml
                  forType:NSPasteboardTypeString];
    return YES;
}
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView
                  validateDrop:(id<NSDraggingInfo>)info
                  proposedItem:(id)item
            proposedChildIndex:(NSInteger)index
{
    /*
    NSLog(@"validating drop into %p", outlineView);
    NSLog(@"Proposed item %p", item);
     */
    
    NSPasteboard* pboard = [info draggingPasteboard];
    OPMLOutlineView* sourceOutline = [info draggingSource];
    NSData * incomingData = [pboard dataForType:OPMLOutline_NodeDragAndDropPboardType];
    NSDictionary * incomingDict = [NSKeyedUnarchiver unarchiveObjectWithData:incomingData];
    NSString * incomingXPath = [incomingDict objectForKey:@"xpath"];
    NSString * incomingXML = [incomingDict objectForKey:@"xml"];
#pragma unused(incomingXML)
    
    if(outlineView != sourceOutline)
        return NSDragOperationCopy & [info draggingSourceOperationMask];
    
    id draggedItem = [[self nodesForXPath:incomingXPath error:nil] objectAtIndex:0];
    
    if(!item)
        item = [self bodyNode];
    
    NSMutableArray * queue = [NSMutableArray arrayWithObject:draggedItem];
    while([queue count])
    {
        OPMLOutlineXMLElement * frontItem = [queue objectAtIndex:0];
        if(frontItem == item)
            return NSDragOperationNone;
        [queue addObjectsFromArray:[frontItem children]];
        [queue removeObjectAtIndex:0];
    }
    
    return NSDragOperationMove & [info draggingSourceOperationMask];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView
         acceptDrop:(id<NSDraggingInfo>)info
               item:(id)item
         childIndex:(NSInteger)index
{
    NSPasteboard* pboard = [info draggingPasteboard];
    OPMLOutlineView* sourceOutline = [info draggingSource];
    NSData * incomingData = [pboard dataForType:OPMLOutline_NodeDragAndDropPboardType];
    NSDictionary * incomingDict = [NSKeyedUnarchiver unarchiveObjectWithData:incomingData];
    NSString * incomingXPath = [incomingDict objectForKey:@"xpath"];
    NSString * incomingXML = [incomingDict objectForKey:@"xml"];

    // Move the specified row to its new location...
    
    return YES;
}



@end