Commit f24504e4 authored by David's avatar David
Browse files

macosx: Move drag and drop support to new PLModel and rewrite

This simplifies the methods, also using a simpler storage for
dragged items.
parent 42d2a27a
......@@ -1182,7 +1182,7 @@ static VLCMainWindow *_o_sharedInstance = nil;
{
if ([[item identifier] isEqualToString:@"playlist"] || [[item identifier] isEqualToString:@"medialibrary"]) {
NSPasteboard *o_pasteboard = [info draggingPasteboard];
if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] || [[o_pasteboard types] containsObject: NSFilenamesPboardType])
if ([[o_pasteboard types] containsObject: VLCPLItemPasteboadType] || [[o_pasteboard types] containsObject: NSFilenamesPboardType])
return NSDragOperationGeneric;
}
return NSDragOperationNone;
......@@ -1225,11 +1225,10 @@ static VLCMainWindow *_o_sharedInstance = nil;
NSArray * array = [[[VLCMain sharedInstance] playlist] draggedItems];
NSUInteger count = [array count];
playlist_item_t * p_item = NULL;
PL_LOCK;
for(NSUInteger i = 0; i < count; i++) {
p_item = [[array objectAtIndex:i] pointerValue];
playlist_item_t *p_item = playlist_ItemGetById(p_playlist, [[array objectAtIndex:i] plItemId]);
if (!p_item) continue;
playlist_NodeAddCopy(p_playlist, p_item, p_node, PLAYLIST_END);
}
......
......@@ -36,9 +36,9 @@
@property(readonly, copy) NSMutableArray *children;
@property(readonly) int plItemId;
@property(readonly) input_item_t *input;
@property(readonly) PLItem *parent;
@property(readwrite, retain) PLItem *parent;
- (id)initWithPlaylistItem:(playlist_item_t *)p_item parent:(PLItem *)parent;
- (id)initWithPlaylistItem:(playlist_item_t *)p_item;
- (BOOL)isLeaf;
......
......@@ -33,7 +33,7 @@
@synthesize input=p_input;
@synthesize parent=_parent;
- (id)initWithPlaylistItem:(playlist_item_t *)p_item parent:(PLItem *)parent;
- (id)initWithPlaylistItem:(playlist_item_t *)p_item;
{
self = [super init];
if(self) {
......@@ -42,8 +42,6 @@
p_input = p_item->p_input;
input_item_Hold(p_input);
_children = [[NSMutableArray alloc] init];
[parent retain];
_parent = parent;
}
return self;
......@@ -58,6 +56,21 @@
[super dealloc];
}
// own hash and isEqual methods are important to retain expandable state
// when items are moved / recreated
- (NSUInteger)hash
{
return (NSUInteger)[self plItemId];
}
- (BOOL)isEqual:(id)other
{
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return [self plItemId] == [other plItemId];
}
- (BOOL)isLeaf
{
......@@ -71,15 +84,14 @@
- (void)addChild:(PLItem *)item atPos:(int)pos
{
// if ([o_children count] > pos) {
// NSLog(@"invalid position %d", pos);
// }
[_children insertObject:item atIndex:pos];
[item setParent: self];
}
- (void)deleteChild:(PLItem *)child
{
[child setParent:nil];
[_children removeObject:child];
}
......
......@@ -25,17 +25,29 @@
#include <vlc_common.h>
#define VLCPLItemPasteboadType @"VLCPlaylistItemPboardType"
@class VLCPlaylist;
@interface PLModel : NSObject<NSOutlineViewDataSource>
{
PLItem *_rootItem;
playlist_t *p_playlist;
NSOutlineView *_outlineView;
// TODO: write these objects to the pastboard properly?
NSMutableArray *_draggedItems;
// TODO: for transition
VLCPlaylist *_playlist;
}
@property(readonly) PLItem *rootItem;
@property(readonly, copy) NSArray *draggedItems;
- (id)initWithOutlineView:(NSOutlineView *)outlineView playlist:(playlist_t *)pl rootItem:(playlist_item_t *)root;
- (id)initWithOutlineView:(NSOutlineView *)outlineView playlist:(playlist_t *)pl rootItem:(playlist_item_t *)root playlistObject:(id)plObj;
- (void)changeRootItem:(playlist_item_t *)p_root;
......
......@@ -23,8 +23,11 @@
#import "misc.h"
#import "playlist.h"
#include <vlc_playlist.h>
#include <vlc_input_item.h>
#import <vlc_input.h>
#include <vlc_url.h>
#define TRACKNUM_COLUMN @"tracknumber"
......@@ -44,16 +47,19 @@
@implementation PLModel
@synthesize rootItem=_rootItem;
@synthesize draggedItems=_draggedItems;
- (id)initWithOutlineView:(NSOutlineView *)outlineView playlist:(playlist_t *)pl rootItem:(playlist_item_t *)root;
- (id)initWithOutlineView:(NSOutlineView *)outlineView playlist:(playlist_t *)pl rootItem:(playlist_item_t *)root playlistObject:(id)plObj;
{
self = [super init];
if(self) {
p_playlist = pl;
_outlineView = [outlineView retain];
_playlist = plObj;
PL_LOCK;
_rootItem = [[PLItem alloc] initWithPlaylistItem:root parent:nil];
_rootItem = [[PLItem alloc] initWithPlaylistItem:root];
[self rebuildPLItem:_rootItem];
PL_UNLOCK;
......@@ -67,7 +73,7 @@
NSLog(@"change root item to %p", p_root);
PL_ASSERT_LOCKED;
[_rootItem release];
_rootItem = [[PLItem alloc] initWithPlaylistItem:p_root parent:nil];
_rootItem = [[PLItem alloc] initWithPlaylistItem:p_root];
[self rebuildPLItem:_rootItem];
[_outlineView reloadData];
}
......@@ -106,7 +112,7 @@
if (p_child->i_flags & PLAYLIST_DBL_FLAG)
continue;
PLItem *o_child = [[[PLItem alloc] initWithPlaylistItem:p_child parent:o_item] autorelease];
PLItem *o_child = [[[PLItem alloc] initWithPlaylistItem:p_child] autorelease];
[o_item addChild:o_child atPos:currPos++];
if (p_child->i_children >= 0) {
......@@ -159,7 +165,8 @@
playlist_item_t *p_item = playlist_ItemGetById(p_playlist, i_item);
if (!p_item || p_item->i_flags & PLAYLIST_DBL_FLAG)
{
PL_UNLOCK; return;
PL_UNLOCK;
return;
}
int pos;
......@@ -167,7 +174,7 @@
if(p_item->p_parent->pp_children[pos] == p_item)
break;
PLItem *o_new_item = [[[PLItem alloc] initWithPlaylistItem:p_item parent:o_parent] autorelease];
PLItem *o_new_item = [[[PLItem alloc] initWithPlaylistItem:p_item] autorelease];
PL_UNLOCK;
if (pos < 0)
return;
......@@ -375,4 +382,197 @@
return o_value;
}
#pragma mark -
#pragma mark Drag and Drop support
- (BOOL)isItem: (PLItem *)p_item inNode: (PLItem *)p_node
{
while(p_item) {
if ([p_item plItemId] == [p_node plItemId]) {
return YES;
}
p_item = [p_item parent];
}
return NO;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
{
NSUInteger itemCount = [items count];
[_draggedItems release];
_draggedItems = [[NSMutableArray alloc] initWithArray:items];
/* Add the data to the pasteboard object. */
[pboard declareTypes: [NSArray arrayWithObject:VLCPLItemPasteboadType] owner: self];
[pboard setData:[NSData data] forType:VLCPLItemPasteboadType];
return YES;
}
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
{
NSPasteboard *o_pasteboard = [info draggingPasteboard];
/* Dropping ON items is not allowed if item is not a node */
if (item) {
if (index == NSOutlineViewDropOnItemIndex && [item isLeaf]) {
return NSDragOperationNone;
}
}
if (![self editAllowed])
return NSDragOperationNone;
/* Drop from the Playlist */
if ([[o_pasteboard types] containsObject:VLCPLItemPasteboadType]) {
NSUInteger count = [_draggedItems count];
for (NSUInteger i = 0 ; i < count ; i++) {
/* We refuse to Drop in a child of an item we are moving */
if ([self isItem: item inNode: [_draggedItems objectAtIndex:i]]) {
return NSDragOperationNone;
}
}
return NSDragOperationMove;
}
/* Drop from the Finder */
else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
return NSDragOperationGeneric;
}
return NSDragOperationNone;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)targetItem childIndex:(NSInteger)index
{
NSPasteboard *o_pasteboard = [info draggingPasteboard];
/* Drag & Drop inside the playlist */
if ([[o_pasteboard types] containsObject:VLCPLItemPasteboadType]) {
if (index == -1) // this is no valid target, sanitize to top of table
index = 0;
if (targetItem == nil) {
targetItem = _rootItem;
}
NSMutableArray *o_filteredItems = [NSMutableArray arrayWithArray:_draggedItems];
const NSUInteger draggedItemsCount = [_draggedItems count];
for (NSInteger i = 0; i < [o_filteredItems count]; i++) {
for (NSUInteger j = 0; j < draggedItemsCount; j++) {
PLItem *itemToCheck = [o_filteredItems objectAtIndex:i];
PLItem *nodeToTest = [_draggedItems objectAtIndex:j];
if ([itemToCheck plItemId] == [nodeToTest plItemId])
continue;
if ([self isItem:itemToCheck inNode:nodeToTest]) {
[o_filteredItems removeObjectAtIndex:i];
--i;
break;
}
}
}
NSUInteger count = [o_filteredItems count];
if (count == 0)
return NO;
playlist_item_t **pp_items = (playlist_item_t **)calloc(count, sizeof(playlist_item_t*));
if (!pp_items)
return NO;
PL_LOCK;
playlist_item_t *p_new_parent = playlist_ItemGetById(p_playlist, [targetItem plItemId]);
if (!p_new_parent) {
PL_UNLOCK;
return NO;
}
NSUInteger j = 0;
for (NSUInteger i = 0; i < count; i++) {
playlist_item_t *p_item = playlist_ItemGetById(p_playlist, [[o_filteredItems objectAtIndex:i] plItemId]);
if (p_item)
pp_items[j++] = p_item;
}
if (playlist_TreeMoveMany(p_playlist, j, pp_items, p_new_parent, index) != VLC_SUCCESS) {
PL_UNLOCK;
free(pp_items);
return NO;
}
PL_UNLOCK;
free(pp_items);
// rebuild our model
NSUInteger filteredItemsCount = [o_filteredItems count];
for(NSUInteger i = 0; i < filteredItemsCount; ++i) {
PLItem *o_item = [o_filteredItems objectAtIndex:i];
NSLog(@"delete child from parent %p", [o_item parent]);
[[o_item parent] deleteChild:o_item];
[targetItem addChild:o_item atPos:index + i];
}
[_outlineView reloadData];
NSMutableIndexSet *selectedIndexes = [[NSMutableIndexSet alloc] init];
for(NSUInteger i = 0; i < draggedItemsCount; ++i) {
NSInteger row = [_outlineView rowForItem:[_draggedItems objectAtIndex:i]];
if (row < 0)
continue;
[selectedIndexes addIndex:row];
}
if ([selectedIndexes count] == 0)
[selectedIndexes addIndex:[_outlineView rowForItem:targetItem]];
[_outlineView selectRowIndexes:selectedIndexes byExtendingSelection:NO];
[selectedIndexes release];
return YES;
}
else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType]
sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
NSUInteger count = [o_values count];
NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
input_thread_t *p_input = playlist_CurrentInput(p_playlist);
if (count == 1 && p_input) {
int i_result = input_AddSubtitleOSD(p_input, vlc_path2uri([[o_values objectAtIndex:0] UTF8String], NULL), true, true);
vlc_object_release(p_input);
if (i_result == VLC_SUCCESS)
return YES;
}
else if (p_input)
vlc_object_release(p_input);
for (NSUInteger i = 0; i < count; i++) {
NSDictionary *o_dic;
char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
if (!psz_uri)
continue;
o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
free(psz_uri);
[o_array addObject: o_dic];
}
// if (item == nil)
[_playlist appendArray:o_array atPos:index enqueue: YES];
// TODO support for drop on sub nodes
// else {
// assert(p_node->i_children != -1);
// [_playlist appendNodeArray:o_array inNode: p_node atPos:index enqueue:YES];
// }
return YES;
}
return NO;
}
@end
......@@ -164,9 +164,6 @@
NSImage *o_descendingSortingImage;
NSImage *o_ascendingSortingImage;
NSMutableArray *o_nodes_array;
NSMutableArray *o_items_array;
BOOL b_selected_item_met;
BOOL b_isSortDescending;
id o_tc_sortColumn;
......@@ -238,9 +235,6 @@
playlist_t * p_playlist = pl_Get(VLCIntf);
p_current_root_item = p_playlist->p_local_category;
o_outline_dict = [[NSMutableDictionary alloc] init];
o_nodes_array = [[NSMutableArray alloc] init];
o_items_array = [[NSMutableArray alloc] init];
}
return self;
}
......@@ -248,8 +242,6 @@
- (void)dealloc
{
[o_outline_dict release];
[o_nodes_array release];
[o_items_array release];
[super dealloc];
}
......@@ -267,7 +259,7 @@
[self reloadStyles];
[self initStrings];
o_model = [[PLModel alloc] initWithOutlineView:o_outline_view playlist:p_playlist rootItem:p_current_root_item];
o_model = [[PLModel alloc] initWithOutlineView:o_outline_view playlist:p_playlist rootItem:p_current_root_item playlistObject:self];
[o_outline_view setDataSource:o_model];
[o_outline_view reloadData];
......@@ -450,30 +442,6 @@
return NO;
}
/* This method is useful for instance to remove the selected children of an
already selected node */
- (void)removeItemsFrom:(id)o_items ifChildrenOf:(id)o_nodes
{
NSUInteger itemCount = [o_items count];
NSUInteger nodeCount = [o_nodes count];
for (NSUInteger i = 0 ; i < itemCount ; i++) {
for (NSUInteger j = 0 ; j < nodeCount ; j++) {
if (o_items == o_nodes) {
if (j == i) continue;
}
if ([self isItem: [o_items objectAtIndex:i]
inNode: [o_nodes objectAtIndex:j]
checkItemExistence: NO locked:NO]) {
[o_items removeObjectAtIndex:i];
/* We need to execute the next iteration with the same index
since the current item has been deleted */
i--;
break;
}
}
}
}
- (IBAction)savePlaylist:(id)sender
{
playlist_t * p_playlist = pl_Get(VLCIntf);
......@@ -1048,9 +1016,10 @@
return o_playing_item;
}
// TODO remove method
- (NSArray *)draggedItems
{
return [[o_nodes_array arrayByAddingObjectsFromArray: o_items_array] retain];
return [[self model] draggedItems];
}
- (void)setColumn: (NSString *)o_column state: (NSInteger)i_state translationDict:(NSDictionary *)o_dict
......@@ -1466,203 +1435,4 @@
return o_value;
}
/* Required for drag & drop and reordering */
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
{
playlist_t *p_playlist = pl_Get(VLCIntf);
/* First remove the items that were moved during the last drag & drop
operation */
[o_items_array removeAllObjects];
[o_nodes_array removeAllObjects];
NSUInteger itemCount = [items count];
for (NSUInteger i = 0 ; i < itemCount ; i++) {
id o_item = [items objectAtIndex:i];
/* Fill the items and nodes to move in 2 different arrays */
if (![o_item isLeaf])
[o_nodes_array addObject: o_item];
else
[o_items_array addObject: o_item];
}
/* Now we need to check if there are selected items that are in already
selected nodes. In that case, we only want to move the nodes */
[self removeItemsFrom: o_nodes_array ifChildrenOf: o_nodes_array];
[self removeItemsFrom: o_items_array ifChildrenOf: o_nodes_array];
/* We add the "VLCPlaylistItemPboardType" type to be able to recognize
a Drop operation coming from the playlist. */
[pboard declareTypes: [NSArray arrayWithObject:@"VLCPlaylistItemPboardType"] owner: self];
[pboard setData:[NSData data] forType:@"VLCPlaylistItemPboardType"];
return YES;
}
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
{
playlist_t *p_playlist = pl_Get(VLCIntf);
NSPasteboard *o_pasteboard = [info draggingPasteboard];
if (!p_playlist) return NSDragOperationNone;
/* Dropping ON items is not allowed if item is not a node */
if (item) {
if (index == NSOutlineViewDropOnItemIndex &&
((playlist_item_t *)[item pointerValue])->i_children == -1) {
return NSDragOperationNone;
}
}
/* We refuse to drop an item in anything else than a child of the General
Node. We still accept items that would be root nodes of the outlineview
however, to allow drop in an empty playlist. */
/// todo
// if (!(([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO locked: NO] ||
// (var_CreateGetBool(p_playlist, "media-library") && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO locked: NO])) || item == nil)) {
// return NSDragOperationNone;
// }
/* Drop from the Playlist */
if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
NSUInteger count = [o_nodes_array count];
for (NSUInteger i = 0 ; i < count ; i++) {
/* We refuse to Drop in a child of an item we are moving */
if ([self isItem: item inNode: [o_nodes_array objectAtIndex:i] checkItemExistence: NO locked:NO]) {
return NSDragOperationNone;
}
}
return NSDragOperationMove;
}
/* Drop from the Finder */
else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
return NSDragOperationGeneric;
}
return NSDragOperationNone;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
{
playlist_t * p_playlist = pl_Get(VLCIntf);
NSPasteboard *o_pasteboard = [info draggingPasteboard];
/* Drag & Drop inside the playlist */
if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
if (index == -1) // this is no valid target, sanitize to top of table
index = 0;
int i_row = 0;
playlist_item_t *p_new_parent, *p_item = NULL;
NSArray *o_all_items = [o_nodes_array arrayByAddingObjectsFromArray: o_items_array];
/* If the item is to be dropped as root item of the outline, make it a
child of the respective general node, if is either the pl or the ml
Else, choose the proposed parent as parent. */
if (item == nil) {
// TODO edit allowed / no drop in other types
if ([[self model] currentRootType] == ROOT_TYPE_PLAYLIST ||
[[self model] currentRootType] == ROOT_TYPE_MEDIALIBRARY)
item = [[self model] rootItem];
else
return NO;
}
/* Make sure the proposed parent is a node.
(This should never be true) */
if (p_new_parent->i_children < 0)
return NO;
NSUInteger count = [o_all_items count];
if (count == 0)
return NO;
playlist_item_t **pp_items = (playlist_item_t **)calloc(count, sizeof(playlist_item_t*));
if (!pp_items)
return NO;
PL_LOCK;
p_new_parent = playlist_ItemGetById(p_playlist, [item plItemId]);
if (!p_new_parent) {
PL_UNLOCK;
return NO;
}
NSUInteger j = 0;
for (NSUInteger i = 0; i < count; i++) {
p_item = playlist_ItemGetById(p_playlist, [[o_all_items objectAtIndex:i] plItemId]);
if (p_item)
pp_items[j++] = p_item;
}
if (j == 0 || playlist_TreeMoveMany(p_playlist, j, pp_items, p_new_parent, index) != VLC_SUCCESS) {