Make outline view DnD fully functioual.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@29083 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
rfm 2009-11-30 18:56:51 +00:00
parent b70463270b
commit 8d0b8c6fbc
2 changed files with 258 additions and 263 deletions

View file

@ -1,3 +1,10 @@
2009-11-30 Richard Frith-Macdonald <rfm@gnu.org>
* Source/NSOutlineView.m: Improve DnD allowing drop on items as well
as inside them. Attempt to mimix OSX behavior. Simplify code.
Use triangular images similar to OSX appearance.
Doubtless needs more polishing.
2009-11-29 Wolfgang Lux <wolfgang.lux@gmail.com> 2009-11-29 Wolfgang Lux <wolfgang.lux@gmail.com>
* Source/NSView.m (-dealloc): Fix bug where -dealloc could break * Source/NSView.m (-dealloc): Fix bug where -dealloc could break

View file

@ -66,11 +66,11 @@ static int lastVerticalQuarterPosition;
static int lastHorizontalHalfPosition; static int lastHorizontalHalfPosition;
static NSRect oldDraggingRect; static NSRect oldDraggingRect;
static int oldDropRow; static id oldDropItem;
static int oldProposedDropRow; static id currentDropItem;
static int currentDropRow; static int oldDropIndex;
static int oldDropLevel; static int currentDropIndex;
static int currentDropLevel;
static NSMutableSet *autoExpanded = nil; static NSMutableSet *autoExpanded = nil;
static NSDate *lastDragUpdate = nil; static NSDate *lastDragUpdate = nil;
static NSDate *lastDragChange = nil; static NSDate *lastDragChange = nil;
@ -134,9 +134,18 @@ static NSImage *unexpandable = nil;
{ {
[self setVersion: current_version]; [self setVersion: current_version];
nc = [NSNotificationCenter defaultCenter]; nc = [NSNotificationCenter defaultCenter];
#if 0
/* Old Interface Builder style. */
collapsed = [NSImage imageNamed: @"common_outlineCollapsed"]; collapsed = [NSImage imageNamed: @"common_outlineCollapsed"];
expanded = [NSImage imageNamed: @"common_outlineExpanded"]; expanded = [NSImage imageNamed: @"common_outlineExpanded"];
unexpandable = [NSImage imageNamed: @"common_outlineUnexpandable"]; unexpandable = [NSImage imageNamed: @"common_outlineUnexpandable"];
#else
/* Current OSX style images. */
// FIXME ... better ones?
collapsed = [NSImage imageNamed: @"common_ArrowRightH"];
expanded = [NSImage imageNamed: @"common_ArrowDownH"];
unexpandable = [[NSImage alloc] initWithSize: [expanded size]];
#endif
autoExpanded = [NSMutableSet new]; autoExpanded = [NSMutableSet new];
} }
} }
@ -279,7 +288,7 @@ static NSImage *unexpandable = nil;
* Expands the given item only. This is the equivalent of calling * Expands the given item only. This is the equivalent of calling
* [NSOutlineView-expandItem:expandChildren:] with NO. * [NSOutlineView-expandItem:expandChildren:] with NO.
*/ */
- (void)expandItem: (id)item - (void) expandItem: (id)item
{ {
[self expandItem: item expandChildren: NO]; [self expandItem: item expandChildren: NO];
} }
@ -796,7 +805,7 @@ static NSImage *unexpandable = nil;
/* /*
* Drawing * Drawing
*/ */
- (void)drawRow: (int)rowIndex clipRect: (NSRect)aRect - (void) drawRow: (int)rowIndex clipRect: (NSRect)aRect
{ {
int startingColumn; int startingColumn;
int endingColumn; int endingColumn;
@ -955,30 +964,21 @@ static NSImage *unexpandable = nil;
[super drawRect: aRect]; [super drawRect: aRect];
} }
- (void) setDropItem: (id) item - (void) setDropItem: (id)item
dropChildIndex: (int) childIndex dropChildIndex: (int)childIndex
{ {
int row = [_items indexOfObject: item];
id itemAfter;
if (row == NSNotFound) if (item != nil && [_items indexOfObject: item] == NSNotFound)
{ {
/* FIXME raise an exception, or perhaps we should support
* setting an item which is not visible (inside a collapsed
* item presumably), or perhaps we should treat this as
* cancelling the drop?
*/
return; return;
} }
currentDropItem = item;
if (childIndex == NSOutlineViewDropOnItemIndex) currentDropIndex = childIndex;
{
currentDropRow = row;
currentDropLevel = NSOutlineViewDropOnItemIndex;
}
else
{
itemAfter = [_dataSource outlineView: self
child: childIndex
ofItem: item];
currentDropRow = [_items indexOfObject: itemAfter];
currentDropLevel = [self levelForItem: itemAfter];
}
} }
/* /*
@ -988,9 +988,8 @@ static NSImage *unexpandable = nil;
- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) sender - (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) sender
{ {
//NSLog(@"draggingEntered"); //NSLog(@"draggingEntered");
currentDropRow = -1; oldDropItem = currentDropItem = nil;
// currentDropOperation = -1; oldDropIndex = currentDropIndex = -1;
oldDropRow = -1;
lastVerticalQuarterPosition = -1; lastVerticalQuarterPosition = -1;
oldDraggingRect = NSMakeRect(0.,0., 0., 0.); oldDraggingRect = NSMakeRect(0.,0., 0., 0.);
return NSDragOperationCopy; return NSDragOperationCopy;
@ -1013,10 +1012,10 @@ static NSImage *unexpandable = nil;
int row; int row;
int verticalQuarterPosition; int verticalQuarterPosition;
int horizontalHalfPosition; int horizontalHalfPosition;
int positionInRow;
int levelBefore; int levelBefore;
int levelAfter; int levelAfter;
int level; int level;
BOOL dropOn = NO;
NSDragOperation dragOperation = [sender draggingSourceOperationMask]; NSDragOperation dragOperation = [sender draggingSourceOperationMask];
ASSIGN(lastDragUpdate, [NSDate date]); ASSIGN(lastDragUpdate, [NSDate date]);
@ -1028,23 +1027,13 @@ static NSImage *unexpandable = nil;
horizontalHalfPosition = horizontalHalfPosition =
((p.x - _bounds.origin.y) / _indentationPerLevel) * 2.; ((p.x - _bounds.origin.y) / _indentationPerLevel) * 2.;
row = (verticalQuarterPosition + 1) / 4;
row = verticalQuarterPosition; positionInRow = verticalQuarterPosition % 4;
row = row % 4;
if (row == 1 || row == 2) dropOn = YES;
if ((verticalQuarterPosition - oldProposedDropRow * 4 <= 2)
&& (verticalQuarterPosition - oldProposedDropRow * 4 >= -3))
{
row = oldProposedDropRow;
}
else
{
row = (verticalQuarterPosition + 2) / 4;
}
if (row > _numberOfRows) if (row > _numberOfRows)
row = _numberOfRows; {
row = _numberOfRows; // beyond the last real row
positionInRow = 1; // inside the root item
}
//NSLog(@"horizontalHalfPosition = %d", horizontalHalfPosition); //NSLog(@"horizontalHalfPosition = %d", horizontalHalfPosition);
@ -1079,22 +1068,30 @@ static NSImage *unexpandable = nil;
{ {
int childIndex; int childIndex;
/* Save positions to avoid executing this code when the general
* position of the mouse is unchanged.
*/
lastVerticalQuarterPosition = verticalQuarterPosition;
lastHorizontalHalfPosition = horizontalHalfPosition;
if (horizontalHalfPosition / 2 < levelAfter) if (horizontalHalfPosition / 2 < levelAfter)
horizontalHalfPosition = levelAfter * 2; horizontalHalfPosition = levelAfter * 2;
else if (horizontalHalfPosition / 2 > levelBefore) else if (horizontalHalfPosition / 2 > levelBefore)
horizontalHalfPosition = levelBefore * 2 + 1; horizontalHalfPosition = levelBefore * 2 + 1;
level = horizontalHalfPosition / 2; level = horizontalHalfPosition / 2;
lastVerticalQuarterPosition = verticalQuarterPosition;
lastHorizontalHalfPosition = horizontalHalfPosition;
//NSLog(@"horizontalHalfPosition = %d", horizontalHalfPosition); //NSLog(@"horizontalHalfPosition = %d", horizontalHalfPosition);
//NSLog(@"verticalQuarterPosition = %d", verticalQuarterPosition); //NSLog(@"verticalQuarterPosition = %d", verticalQuarterPosition);
currentDropRow = row; if (positionInRow > 0 && positionInRow < 3)
currentDropLevel = level; {
/* We are directly over the middle of a row ... so the drop
* should be directory on the item in that row.
*/
item = [self itemAtRow: row];
childIndex = NSOutlineViewDropOnItemIndex;
}
else
{ {
int i; int i;
int j = 0; int j = 0;
@ -1120,12 +1117,9 @@ static NSImage *unexpandable = nil;
childIndex = j; childIndex = j;
} }
if (YES == dropOn) currentDropItem = item;
{ currentDropIndex = childIndex;
childIndex = NSOutlineViewDropOnItemIndex;
}
oldProposedDropRow = currentDropRow;
if ([_dataSource respondsToSelector: if ([_dataSource respondsToSelector:
@selector(outlineView:validateDrop:proposedItem:proposedChildIndex:)]) @selector(outlineView:validateDrop:proposedItem:proposedChildIndex:)])
{ {
@ -1135,10 +1129,15 @@ static NSImage *unexpandable = nil;
proposedChildIndex: childIndex]; proposedChildIndex: childIndex];
} }
if ((currentDropRow != oldDropRow) || (currentDropLevel != oldDropLevel)) //NSLog(@"Drop on %@ %d", currentDropItem, currentDropIndex);
if ((currentDropItem != oldDropItem)
|| (currentDropIndex != oldDropIndex))
{ {
NSBezierPath *path; NSBezierPath *path;
oldDropItem = currentDropItem;
oldDropIndex = currentDropIndex;
ASSIGN(lastDragChange, lastDragUpdate); ASSIGN(lastDragChange, lastDragUpdate);
[self lockFocus]; [self lockFocus];
@ -1147,34 +1146,69 @@ static NSImage *unexpandable = nil;
[[NSColor darkGrayColor] set]; [[NSColor darkGrayColor] set];
//NSLog(@"currentDropLevel %d, currentDropRow %d", if (currentDropIndex != NSOutlineViewDropOnItemIndex)
//currentDropLevel, currentDropRow);
if (currentDropLevel != NSOutlineViewDropOnItemIndex)
{ {
if (currentDropRow == 0) int numberOfChildren;
numberOfChildren = [_dataSource outlineView: self
numberOfChildrenOfItem: currentDropItem];
if (currentDropIndex >= numberOfChildren)
{
/* The index lies beyond the last item,
* so we get the last but one item and we
* use the row after it. If there are no
* children at all, we use the parent item row.
*/
if (numberOfChildren == 0)
{
row = [self rowForItem: currentDropItem];
}
else
{
item = [_dataSource outlineView: self
child: numberOfChildren - 1
ofItem: currentDropItem];
row = [self rowForItem: item] + 1;
}
}
else
{
/* Find the row for the item containing the child
* we will be dropping on.
*/
item = [_dataSource outlineView: self
child: currentDropIndex
ofItem: currentDropItem];
row = [self rowForItem: item];
}
level = [self levelForItem: item];
if (currentDropItem == nil && currentDropIndex == 0)
{ {
newRect = NSMakeRect([self visibleRect].origin.x, newRect = NSMakeRect([self visibleRect].origin.x,
currentDropRow * _rowHeight, 0,
[self visibleRect].size.width, [self visibleRect].size.width,
2); 2);
} }
else if (currentDropRow == _numberOfRows) else if (row == _numberOfRows)
{ {
newRect = NSMakeRect([self visibleRect].origin.x, newRect = NSMakeRect([self visibleRect].origin.x,
currentDropRow * _rowHeight - 2, row * _rowHeight - 2,
[self visibleRect].size.width, [self visibleRect].size.width,
2); 2);
} }
else else
{ {
newRect = NSMakeRect([self visibleRect].origin.x, newRect = NSMakeRect([self visibleRect].origin.x,
currentDropRow * _rowHeight - 1, row * _rowHeight - 1,
[self visibleRect].size.width, [self visibleRect].size.width,
2); 2);
} }
newRect.origin.x += currentDropLevel * _indentationPerLevel; level++;
newRect.size.width -= currentDropLevel * _indentationPerLevel; newRect.origin.x += level * _indentationPerLevel;
newRect.size.width -= level * _indentationPerLevel;
/* The rectangle is a line across the cell indicating the /* The rectangle is a line across the cell indicating the
* insertion position. We adjust by enough pixels to allow for * insertion position. We adjust by enough pixels to allow for
* a ring drawn on the left end. * a ring drawn on the left end.
@ -1207,8 +1241,10 @@ static NSImage *unexpandable = nil;
} }
else else
{ {
row = [_items indexOfObject: currentDropItem];
level = [self levelForItem: currentDropItem];
newRect = [self frameOfCellAtColumn: 0 newRect = [self frameOfCellAtColumn: 0
row: currentDropRow]; row: row];
newRect.origin.x = _bounds.origin.x; newRect.origin.x = _bounds.origin.x;
newRect.size.width = _bounds.size.width + 2; newRect.size.width = _bounds.size.width + 2;
newRect.origin.x -= _intercellSpacing.height / 2; newRect.origin.x -= _intercellSpacing.height / 2;
@ -1233,8 +1269,8 @@ static NSImage *unexpandable = nil;
{ {
} }
newRect.origin.x += currentDropLevel * _indentationPerLevel; newRect.origin.x += level * _indentationPerLevel;
newRect.size.width -= currentDropLevel * _indentationPerLevel; newRect.size.width -= level * _indentationPerLevel;
NSFrameRectWithWidth(newRect, 2.0); NSFrameRectWithWidth(newRect, 2.0);
// NSRectFill(newRect); // NSRectFill(newRect);
@ -1244,23 +1280,6 @@ static NSImage *unexpandable = nil;
[self unlockFocus]; [self unlockFocus];
oldDropRow = currentDropRow;
oldDropLevel = currentDropLevel;
}
else
{
if (YES == dropOn)
{
item = [_items objectAtIndex: currentDropRow];
if ([self isExpandable: item] && ![self isItemExpanded: item])
{
[self expandItem: item expandChildren: NO];
if ([self isItemExpanded: item])
{
[autoExpanded addObject: item];
}
}
}
} }
} }
else else
@ -1268,10 +1287,10 @@ static NSImage *unexpandable = nil;
/* If we have been hovering over an item for more than half a second, /* If we have been hovering over an item for more than half a second,
* we should expand it. * we should expand it.
*/ */
if (YES == dropOn if (positionInRow > 0 && positionInRow < 3
&& [lastDragUpdate timeIntervalSinceDate: lastDragChange] >= 0.5) && [lastDragUpdate timeIntervalSinceDate: lastDragChange] >= 0.5)
{ {
item = [_items objectAtIndex: currentDropRow]; item = [_items objectAtIndex: row];
if ([self isExpandable: item] && ![self isItemExpanded: item]) if ([self isExpandable: item] && ![self isItemExpanded: item])
{ {
[self expandItem: item expandChildren: NO]; [self expandItem: item expandChildren: NO];
@ -1298,42 +1317,10 @@ static NSImage *unexpandable = nil;
respondsToSelector: respondsToSelector:
@selector(outlineView:acceptDrop:item:childIndex:)]) @selector(outlineView:acceptDrop:item:childIndex:)])
{ {
id item;
int childIndex;
if (currentDropLevel == NSOutlineViewDropOnItemIndex)
{
item = [self itemAtRow: currentDropRow];
childIndex = currentDropLevel;
}
else
{
int lvl, i, j = 0;
for (i = currentDropRow - 1; i >= 0; i--)
{
lvl = [self levelForRow: i];
if (lvl == currentDropLevel - 1)
{
break;
}
else if (lvl == currentDropLevel)
{
j++;
}
}
if (i == -1)
item = nil;
else
item = [self itemAtRow: i];
childIndex = j;
}
result = [_dataSource outlineView: self result = [_dataSource outlineView: self
acceptDrop: sender acceptDrop: sender
item: item item: currentDropItem
childIndex: childIndex]; childIndex: currentDropIndex];
} }
[self _autoCollapse]; [self _autoCollapse];
@ -1469,7 +1456,8 @@ static NSImage *unexpandable = nil;
if (![self isExpandable: item]) if (![self isExpandable: item])
{ {
image = unexpandable; // image = unexpandable;
image = nil;
} }
level = [self levelForItem: item]; level = [self levelForItem: item];