255 lines
11 KiB
Objective-C
255 lines
11 KiB
Objective-C
//
|
|
// AppDelegate.m
|
|
// ViewBasedSourceList
|
|
//
|
|
// Created by Alex Rozanski on 28/12/2013.
|
|
// Copyright 2009-14 Alex Rozanski http://alexrozanski.com and other contributors.
|
|
// This software is licensed under the New BSD License. Full details can be found in the README.
|
|
//
|
|
|
|
#import "AppDelegate.h"
|
|
#import "PhotoCollection.h"
|
|
|
|
@interface AppDelegate ()
|
|
@property (strong, nonatomic) NSMutableArray *libraryCollections;
|
|
@property (strong, nonatomic) NSMutableArray *albums;
|
|
@property (strong, nonatomic) NSMutableArray *sourceListItems;
|
|
@end
|
|
|
|
static NSString * const draggingType = @"SourceListExampleDraggingType";
|
|
|
|
@implementation AppDelegate
|
|
|
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
|
{
|
|
// Used to support drag and drop in the source list.
|
|
[self.sourceList registerForDraggedTypes:@[draggingType]];
|
|
|
|
// Set up our data model. We could set an identifier on the PXSourceListItem instances, but it makes more sense
|
|
// to put our identifying information on the underlying model object in this case.
|
|
PhotoCollection *photosCollection = [PhotoCollection collectionWithTitle:@"Photos" identifier:@"photos" numberOfItems:264];
|
|
PhotoCollection *eventsCollection = [PhotoCollection collectionWithTitle:@"Events" identifier:@"events" numberOfItems:689];
|
|
PhotoCollection *peopleCollection = [PhotoCollection collectionWithTitle:@"People" identifier:@"people" numberOfItems:135];
|
|
PhotoCollection *placesCollection = [PhotoCollection collectionWithTitle:@"Places" identifier:@"places" numberOfItems:28];
|
|
|
|
self.libraryCollections = [@[photosCollection, eventsCollection, peopleCollection, placesCollection] mutableCopy];
|
|
self.albums = [@[[PhotoCollection collectionWithTitle:@"Holiday Snaps" identifier:nil numberOfItems:200],
|
|
[PhotoCollection collectionWithTitle:@"Graduation" identifier: nil numberOfItems:1050]] mutableCopy];
|
|
|
|
self.sourceListItems = [[NSMutableArray alloc] init];
|
|
|
|
// Icon images we're going to use in the Source List.
|
|
NSImage *photosImage = [NSImage imageNamed:@"photos"];
|
|
[photosImage setTemplate:YES];
|
|
NSImage *eventsImage = [NSImage imageNamed:@"events"];
|
|
[eventsImage setTemplate:YES];
|
|
NSImage *peopleImage = [NSImage imageNamed:@"people"];
|
|
[peopleImage setTemplate:YES];
|
|
NSImage *placesImage = [NSImage imageNamed:@"places"];
|
|
[placesImage setTemplate:YES];
|
|
NSImage *albumImage = [NSImage imageNamed:@"album"];
|
|
[albumImage setTemplate:YES];
|
|
|
|
// Set up our Source List data model used in the Source List data source methods.
|
|
PXSourceListItem *libraryItem = [PXSourceListItem itemWithTitle:@"LIBRARY" identifier:nil];
|
|
libraryItem.children = @[[PXSourceListItem itemWithRepresentedObject:photosCollection icon:photosImage],
|
|
[PXSourceListItem itemWithRepresentedObject:eventsCollection icon:eventsImage],
|
|
[PXSourceListItem itemWithRepresentedObject:peopleCollection icon:peopleImage],
|
|
[PXSourceListItem itemWithRepresentedObject:placesCollection icon:placesImage]];
|
|
|
|
PXSourceListItem *albumsItem = [PXSourceListItem itemWithTitle:@"ALBUMS" identifier:nil];
|
|
for (PhotoCollection *collection in self.albums) {
|
|
[albumsItem addChildItem:[PXSourceListItem itemWithRepresentedObject:collection icon:albumImage]];
|
|
}
|
|
|
|
[self.sourceListItems addObject:libraryItem];
|
|
[self.sourceListItems addObject:albumsItem];
|
|
|
|
[self.sourceList reloadData];
|
|
}
|
|
|
|
#pragma mark - Actions
|
|
|
|
- (IBAction)addButtonAction:(id)sender
|
|
{
|
|
NSImage *albumImage = [NSImage imageNamed:@"album"];
|
|
[albumImage setTemplate:YES];
|
|
|
|
PXSourceListItem *newItem = [PXSourceListItem itemWithTitle:@"New Album" identifier:nil icon:albumImage];
|
|
[self.sourceListItems[1] addChildItem:newItem];
|
|
|
|
NSUInteger childIndex = [[self.sourceListItems[1] children] indexOfObject:newItem];
|
|
[self.sourceList insertItemsAtIndexes:[NSIndexSet indexSetWithIndex:childIndex]
|
|
inParent:self.sourceListItems[1]
|
|
withAnimation:NSTableViewAnimationEffectNone];
|
|
|
|
[self.sourceList editColumn:0 row:[self.sourceList rowForItem:newItem] withEvent:nil select:YES];
|
|
}
|
|
|
|
- (IBAction)removeButtonAction:(id)sender
|
|
{
|
|
PXSourceListItem *selectedItem = [self.sourceList itemAtRow:self.sourceList.selectedRow];
|
|
PXSourceListItem *parentItem = self.sourceListItems[1];
|
|
|
|
|
|
[self.sourceList removeItemsAtIndexes:[NSIndexSet indexSetWithIndex:[parentItem.children indexOfObject:selectedItem]]
|
|
inParent:parentItem
|
|
withAnimation:NSTableViewAnimationSlideUp];
|
|
|
|
// Only 'album' items can be deleted.
|
|
[parentItem removeChildItem:selectedItem];
|
|
}
|
|
|
|
#pragma mark - PXSourceList Data Source
|
|
|
|
- (NSUInteger)sourceList:(PXSourceList*)sourceList numberOfChildrenOfItem:(id)item
|
|
{
|
|
if (!item)
|
|
return self.sourceListItems.count;
|
|
|
|
return [[item children] count];
|
|
}
|
|
|
|
- (id)sourceList:(PXSourceList*)aSourceList child:(NSUInteger)index ofItem:(id)item
|
|
{
|
|
if (!item)
|
|
return self.sourceListItems[index];
|
|
|
|
return [[item children] objectAtIndex:index];
|
|
}
|
|
|
|
- (BOOL)sourceList:(PXSourceList*)aSourceList isItemExpandable:(id)item
|
|
{
|
|
return [item hasChildren];
|
|
}
|
|
|
|
#pragma mark - PXSourceList Delegate
|
|
|
|
- (BOOL)sourceList:(PXSourceList *)aSourceList isGroupAlwaysExpanded:(id)group
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (NSView *)sourceList:(PXSourceList *)aSourceList viewForItem:(id)item
|
|
{
|
|
PXSourceListTableCellView *cellView = nil;
|
|
if ([aSourceList levelForItem:item] == 0)
|
|
cellView = [aSourceList makeViewWithIdentifier:@"HeaderCell" owner:nil];
|
|
else
|
|
cellView = [aSourceList makeViewWithIdentifier:@"MainCell" owner:nil];
|
|
PXSourceListItem *sourceListItem = item;
|
|
|
|
// Don't allow us to double-click to edit the title for items in the "Library" group.
|
|
BOOL isTitleEditable = ![[self.sourceListItems[0] children] containsObject:item];
|
|
cellView.textField.editable = isTitleEditable;
|
|
cellView.textField.selectable = isTitleEditable;
|
|
|
|
PhotoCollection *collection = sourceListItem.representedObject;
|
|
|
|
cellView.textField.stringValue = sourceListItem.title ? sourceListItem.title : [sourceListItem.representedObject title];
|
|
cellView.imageView.image = [item icon];
|
|
cellView.badgeView.hidden = !collection;
|
|
cellView.badgeView.badgeValue = collection.numberOfItems;
|
|
|
|
return cellView;
|
|
}
|
|
|
|
- (void)sourceListSelectionDidChange:(NSNotification *)notification
|
|
{
|
|
PXSourceListItem *selectedItem = [self.sourceList itemAtRow:self.sourceList.selectedRow];
|
|
BOOL removeButtonEnabled = NO;
|
|
NSString *newLabel = @"";
|
|
if (selectedItem) {
|
|
// Only allow us to remove items in the 'albums' group.
|
|
removeButtonEnabled = [[self.sourceListItems[1] children] containsObject:selectedItem];
|
|
|
|
// We can use the underlying model object to do something based on the selection.
|
|
PhotoCollection *collection = selectedItem.representedObject;
|
|
|
|
if (collection.identifier)
|
|
newLabel = [NSString stringWithFormat:@"'%@' collection selected.", collection.identifier];
|
|
else
|
|
newLabel = @"User-created collection selected.";
|
|
}
|
|
|
|
self.selectedItemLabel.stringValue = newLabel;
|
|
self.removeButton.enabled = removeButtonEnabled;
|
|
}
|
|
|
|
#pragma mark - Drag and Drop
|
|
|
|
- (BOOL)sourceList:(PXSourceList*)aSourceList writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
|
|
{
|
|
// Only allow the items in the 'Albums' group to be moved around.
|
|
for (PXSourceListItem *item in items) {
|
|
if (![[self.sourceListItems[1] children] containsObject:item])
|
|
return NO;
|
|
}
|
|
|
|
// We're dragging from and to the 'Albums' group.
|
|
PXSourceListItem *parentItem = self.sourceListItems[1];
|
|
|
|
// For simplicity in this example, put the dragged indexes on the pasteboard. Since we use the representedObject
|
|
// on SourceListItem, we cannot reliably archive it directly.
|
|
NSMutableIndexSet *draggedChildIndexes = [NSMutableIndexSet indexSet];
|
|
for (PXSourceListItem *item in items)
|
|
[draggedChildIndexes addIndex:[[parentItem children] indexOfObject:item]];
|
|
|
|
[pboard declareTypes:@[draggingType] owner:self];
|
|
[pboard setData:[NSKeyedArchiver archivedDataWithRootObject:draggedChildIndexes] forType:draggingType];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (NSDragOperation)sourceList:(PXSourceList*)sourceList validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
|
|
{
|
|
PXSourceListItem *albumsItem = self.sourceListItems[1];
|
|
|
|
// Only allow the items in the 'albums' group to be moved around. It can either be dropped on the group header, or inserted between other child items.
|
|
// It can't be made the child of another item in this group, so the only valid case is when the proposedItem is the 'Albums' group item.
|
|
if (![item isEqual:albumsItem])
|
|
return NSDragOperationNone;
|
|
|
|
return NSDragOperationMove;
|
|
}
|
|
|
|
- (BOOL)sourceList:(PXSourceList*)aSourceList acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
|
|
{
|
|
NSPasteboard *draggingPasteboard = info.draggingPasteboard;
|
|
NSMutableIndexSet *draggedChildIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:[draggingPasteboard dataForType:draggingType]];
|
|
|
|
PXSourceListItem *parentItem = self.sourceListItems[1];
|
|
NSMutableArray *draggedItems = [NSMutableArray array];
|
|
[draggedChildIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
|
[draggedItems addObject:[[parentItem children] objectAtIndex:idx]];
|
|
}];
|
|
|
|
// An index of -1 means it's been dropped on the group header itself, so insert at the end of the group.
|
|
if (index == -1)
|
|
index = parentItem.children.count;
|
|
|
|
// Perform the Source List and model updates.
|
|
[aSourceList beginUpdates];
|
|
[aSourceList removeItemsAtIndexes:draggedChildIndexes
|
|
inParent:parentItem
|
|
withAnimation:NSTableViewAnimationEffectNone];
|
|
[parentItem removeChildItems:draggedItems];
|
|
|
|
// We have to calculate the new child index which we have to perform the drop at, since we've just removed items from the parent item which
|
|
// may have come before the drop index.
|
|
NSUInteger adjustedDropIndex = index - [draggedChildIndexes countOfIndexesInRange:NSMakeRange(0, index)];
|
|
|
|
// The insertion indexes are now simply from the adjusted drop index.
|
|
NSIndexSet *insertionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(adjustedDropIndex, draggedChildIndexes.count)];
|
|
[parentItem insertChildItems:draggedItems atIndexes:insertionIndexes];
|
|
|
|
[aSourceList insertItemsAtIndexes:insertionIndexes
|
|
inParent:parentItem
|
|
withAnimation:NSTableViewAnimationEffectNone];
|
|
[aSourceList endUpdates];
|
|
|
|
return YES;
|
|
}
|
|
|
|
@end
|