Crazy cool selection behavior.

This commit is contained in:
Derek Hammer
2011-02-25 23:52:41 +01:00
committed by Klaas Pieter Annema
parent 5c6fce4e8c
commit 2b498feb67
+154 -227
View File
@@ -136,21 +136,20 @@ CPTableViewFirstColumnOnlyAutoresizingStyle = 5;
CPTableView object displays record-oriented data in a table and
allows the user to edit values and resize and rearrange columns.
A CPTableView requires you to either set a data source which implements
@ref numberofrows "numberOfRowsInTableView:" and @ref objectValueForTable "tableView:objectValueForTableColumn:row:",
numberOfRowsInTableView: and tableView:objectValueForTableColumn:row:,
or alternatively to provide data through Key Value Bindings.
To use a table view with Key Value Bindings, bind each column's
\c CPValueBinding to an \c array.field path - typically this would be to a path
in an array controller like \c arrangedObjects.X, where \c X is the name of a
field. Optionally also bind the table's \c selectionIndexes and
\c sortDescriptors to the array controller.
CPValueBinding to an array.field path - typically this would be to a path
in an array controller like "arrangedObjects.X", where X is the name of a
field. Optionally also bind the table's "selectionIndexes" and
"sortDescriptors" to the array controller.
All delegate and data source methods are documented in the @ref setdatasource "setDataSource:" and @ref setdelegate "setDelegate:" methods.
All delegate and data source methods are documented in the setDataSource: and setDelegate: methods.
If you want to display something other than just text in the table you should call @link CPTableColumn::setDataView: setDataView:@endlink on a CPTableColumn object. More documentation in that class including theme states.
@note CPTableView does not contain its own scrollview. You should be sure you place the tableview in a CPScrollView on your own.
If you want to display something other than just text in the table you should call setDataView: on a CPTableColumn object. More documentation in that class including theme states.
Note: CPTableView does not contain its own scrollview. You should be sure you place the tableview in a CPScrollView on your own.
*/
@implementation CPTableView : CPControl
{
@@ -229,7 +228,6 @@ CPTableViewFirstColumnOnlyAutoresizingStyle = 5;
unsigned _destinationDragStyle;
BOOL _isSelectingSession;
CPIndexSet _draggedRowIndexes;
BOOL _wasSelectionBroken;
_CPDropOperationDrawingView _dropOperationFeedbackView;
CPDragOperation _dragOperationDefaultMask;
@@ -239,6 +237,7 @@ CPTableViewFirstColumnOnlyAutoresizingStyle = 5;
BOOL _disableAutomaticResizing @accessors(property=disableAutomaticResizing);
BOOL _lastColumnShouldSnap;
BOOL _implementsCustomDrawRow;
BOOL _wasBroken;
CPTableColumn _draggedColumn;
CPArray _differedColumnDataToRemove;
@@ -382,69 +381,47 @@ CPTableViewFirstColumnOnlyAutoresizingStyle = 5;
}
/*!
@anchor setdatasource
<pre>
Sets the receiver's data source to a given object.
The data source implements various methods for handling the tableview's data when bindings are not used.
@param anObject The data source for the receiver. This object must implement numberOfRowsInTableView: and tableView:objectValueForTableColumn:row:
These methods are outlined below.
@section overview Overview
CPTableView generally requires a datasource to run. This data source can be thought of just like a delegate, but specifically for the tableview data.
Methods include:
- (int)numberOfRowsInTableView:(CPTableView)aTableView;
Returns the number of rows in the tableview
@section required Required Methods
@anchor numberofrows
Returns the number of rows in the tableview
@code
- (int)numberOfRowsInTableView:(CPTableView)aTableView;
@endcode
- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aColumn row:(int)aRowIndex;
Returns the object value for each dataview. Each dataview will be sent a setObjectValue: method which will contain
the object you return from this datasource method.
Returns the object value for each dataview. Each dataview will be sent a setObjectValue: method which will contain the object you return from this datasource method.
@anchor objectValueForTable
@code
- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aColumn row:(int)aRowIndex;
@endcode
Editing:
- (void)tableView:(CPTableView)aTableView setObjectValue:(id)anObject forTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex;
Sets the data object for an item in a given row and column. This needs to be implemented if you want inline editing support
Sorting:
- (void)tableView:(CPTableView)aTableView sortDescriptorsDidChange:(CPArray)oldDescriptors;
The tableview will call this method if you click the tableheader. You should sort the datasource based off of the new sort descriptors and reload the data
@section editing Editing:
Sets the data object for an item in a given row and column. This needs to be implemented if you want inline editing support
@code
- (void)tableView:(CPTableView)aTableView setObjectValue:(id)anObject forTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex;
@endcode
Drag and Drop:
In order for the tableview to receive drops don't forget to first register the tableview for drag types like you do with every other view
- (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(CPDraggingInfo)info proposedRow:(int)row proposedDropOperation:(CPTableViewDropOperation)operation;
Return the drag operation (move, copy, etc) that should be performed if a registered drag type is over the tableview
The data source can retarget a drop if you want by calling -(void)setDropRow:(int)aRow dropOperation:(CPTableViewDropOperation)anOperation;
@section sorting Sorting:
The tableview will call this method if you click the tableheader. You should sort the datasource based off of the new sort descriptors and reload the data
@code
- (void)tableView:(CPTableView)aTableView sortDescriptorsDidChange:(CPArray)oldDescriptors;
@endcode
- (BOOL)tableView:(CPTableView)aTableView writeRowsWithIndexes:(CPIndexSet)rowIndexes toPasteboard:(CPPasteboard)pboard;
Returns YES if the drop operation is allowed otherwise NO.
This method is invoked by the tableview after a drag should begin, but before it is started. If you don't want the drag to being return NO.
If you want the drag to begin you should return YES and place the drag data on the pboard.
@section draganddrop Drag and Drop:
@note In order for the tableview to receive drops don't forget to first register the tableview for drag types like you do with every other view.
- (BOOL)tableView:(CPTableView)aTableView acceptDrop:(CPDraggingInfo)info row:(int)row dropOperation:(CPTableViewDropOperation)operation;
Return YES if the operation was successful otherwise return NO.
The data source should incorporate the data from the dragging pasteboard in this method implementation.
To get this data use the draggingPasteboard method on the CPDraggingInfo object.
Return the drag operation (move, copy, etc) that should be performed if a registered drag type is over the tableview
The data source can retarget a drop if you want by calling <pre>-(void)setDropRow:(int)aRow dropOperation:(CPTableViewDropOperation)anOperation;</pre>
@code
- (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(CPDraggingInfo)info proposedRow:(int)row proposedDropOperation:(CPTableViewDropOperation)operation;
@endcode
Returns YES if the drop operation is allowed otherwise NO. This method is invoked by the tableview after a drag should begin, but before it is started. If you don't want the drag to being return NO. If you want the drag to begin you should return YES and place the drag data on the pboard.
@code
- (BOOL)tableView:(CPTableView)aTableView writeRowsWithIndexes:(CPIndexSet)rowIndexes toPasteboard:(CPPasteboard)pboard;
@endcode
Return YES if the operation was successful otherwise return NO. The data source should incorporate the data from the dragging pasteboard in this method implementation. To get this data use the draggingPasteboard method on the CPDraggingInfo object.
@code
- (BOOL)tableView:(CPTableView)aTableView acceptDrop:(CPDraggingInfo)info row:(int)row dropOperation:(CPTableViewDropOperation)operation;
@endcode
NOT YET IMPLEMENTED
@code
- (CPArray)tableView:(CPTableView)aTableView namesOfPromisedFilesDroppedAtDestination:(CPURL)dropDestination forDraggedRowsWithIndexes:(CPIndexSet)indexSet;
@endcode
- (CPArray)tableView:(CPTableView)aTableView namesOfPromisedFilesDroppedAtDestination:(CPURL)dropDestination forDraggedRowsWithIndexes:(CPIndexSet)indexSet;
NOT YET IMPLEMENTED
</pre>
@param anObject The data source for the receiver. This object must implement numberOfRowsInTableView: and tableView:objectValueForTableColumn:row:
*/
- (void)setDataSource:(id)aDataSource
{
@@ -579,7 +556,7 @@ NOT YET IMPLEMENTED
/*!
Passing YES will allow the user to resize columns. Passing NO will keep the table columns unmovable by the user.
@note The this does not affect autoresizing behavior.
Note the this does not affect autoresizing behavior.
*/
- (void)setAllowsColumnResizing:(BOOL)shouldAllowColumnResizing
{
@@ -690,7 +667,7 @@ NOT YET IMPLEMENTED
/*!
Sets the height of each row.
@note This may still used even if variable row height is being used.
NOTE: This may still used even if variable row height is being used.
@param aRowHeight the height of each row
*/
@@ -1375,7 +1352,7 @@ NOT YET IMPLEMENTED
[self selectColumnIndexes:[CPIndexSet indexSet] byExtendingSelection:NO];
}
/*!
/*
Returns the number of columns in the table
*/
- (int)numberOfColumns
@@ -1383,7 +1360,7 @@ NOT YET IMPLEMENTED
return NUMBER_OF_COLUMNS();
}
/*!
/*
Returns the number of rows in the receiver.
*/
- (int)numberOfRows
@@ -1585,9 +1562,8 @@ NOT YET IMPLEMENTED
UPDATE_COLUMN_RANGES_IF_NECESSARY();
var range = _tableColumnRanges[aColumnIndex];
var spacing = _intercellSpacing.width || 1;
return _CGRectMake(range.location + spacing, 0.0, range.length - (2 * spacing), _CGRectGetHeight([self bounds]));
return _CGRectMake(range.location, 0.0, range.length, _CGRectGetHeight([self bounds]));
}
// Complexity:
@@ -1924,7 +1900,7 @@ NOT YET IMPLEMENTED
/*!
@ignore
FIX ME: this can be a lot faster
FIX ME: this is broken
*/
- (void)_resizeAllColumnUniformlyWithOldSize:(CGSize)oldSize
{
@@ -1984,12 +1960,7 @@ NOT YET IMPLEMENTED
// now that we've reached the end we know there are likely rounding errors
// so we should size the last resized to fit
// find the last visisble column
while (count-- && [_tableColumns[count] isHidden]) ;
// find the max x, but subtract a single pixel since the spacing isn't applicable here.
var delta = superviewWidth - _CGRectGetMaxX([self rectOfColumn:count]) - ([self intercellSpacing].width || 1),
var delta = superviewWidth - _CGRectGetMaxX([self rectOfColumn:[self numberOfColumns] - 1]),
newSize = [item width] + delta;
[item _tryToResizeToWidth:newSize];
@@ -2194,8 +2165,8 @@ NOT YET IMPLEMENTED
/*!
Set the name under which the table information is automatically saved to theAutosaveName.
The table information is saved separately for each user and for each application that user uses.
@note Even though a table view has an autosave name, it may not be saving table information automatically.
To set whether table information is being saved automatically, use \c setAutosaveTableColumns:
Note that even though a table view has an autosave name, it may not be saving table information automatically.
To set whether table information is being saved automatically, use setAutosaveTableColumns:
*/
- (void)setAutosaveName:(CPString)theAutosaveName
{
@@ -2220,7 +2191,7 @@ NOT YET IMPLEMENTED
Sets whether the order and width of this table view's columns are automatically saved.
The table information is saved separately for each user and for each application that user uses.
@note that if autosaveName returns nil, this setting is ignored and table information isn't saved.
Note that if autosaveName returns nil, this setting is ignored and table information isn't saved.
*/
- (void)setAutosaveTableColumns:(BOOL)shouldAutosave
{
@@ -2308,141 +2279,80 @@ NOT YET IMPLEMENTED
/*!
@anchor setdelegate
Sets the delegate of the receiver.
<pre>
Sets the delegate of the receiver. The delegate can implement the following methods:
Displaying Cells
- (void)tableView:(CPTableView)aTableView willDisplayView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex;
Called when the tableview is about to display a dataview
- (BOOL)tableView:(CPTableView)tableView isGroupRow:(int)row;
Group rows are a way to separate a groups of data in a tableview. Return YES if the given row is a group row, otherwise NO.
Editing Cells
- (BOOL)tableView:(CPTableView)aTableView shouldEditTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex;
Return YES if the dataview at a given index and column should be edited, otherwise NO.
Setting Row and Column Size
- (float)tableView:(CPTableView)tableView heightOfRow:(int)row;
Return the height of the row at a given index. Only implement this if you want variable row heights. Otherwise use setRowHeight: on the tableview.
Selecting in the Tableview
- (BOOL)selectionShouldChangeInTableView:(CPTableView)aTableView;
Return YES if the selection of the tableview should change, otherwise NO to keep the current selection.
NOTE: this is only called via user interaction
- (BOOL)tableView:(CPTableView)aTableView shouldSelectRow:(int)rowIndex;
Return YES if the row at a given index should be selected, other NO to deny the selection.
NOTE: this is only called via user interaction
- (BOOL)tableView:(CPTableView)aTableView shouldSelectTableColumn:(CPTableColumn)aTableColumn;
Return YES if the table column given should be selected, otherwise NO to deny the selection.
NOTE: this is only called via user interaction
- (void)tableViewSelectionIsChanging:(CPNotification)aNotification
Inform the delegate that the tableview is in the process of chaining the selection.
This usually happens when the user is dragging their mouse across rows.
NOTE: this is only called via user interaction
- (void)tableViewSelectionDidChange:(CPNotification)aNotification;
Informs the delegate that the tableview selection has changed.
NOTE: this is only called via user interaction
Moving and Resizing Columns
- (BOOL)tableView:(CPTableView)tableView shouldReorderColumn:(int)columnIndex toColumn:(int)newColumnIndex;
Return YES if the column at a given index should move to a new column index, otherwise NO.
- (void)tableView:(CPTableView)tableView didDragTableColumn:(CPTableColumn)tableColumn;
Notifies the delegate that the tableview drag occurred. This is send on mouse up.
- (void)tableViewColumnDidMove:(CPNotification)aNotification;
Notifies the delegate that a tablecolumn was moved by the user.
- (void)tableViewColumnDidResize:(CPNotification)aNotification
Notifies the delegate that the user resized the table column
Responding to Mouse Events
- (void)tableView:(CPTableView)tableView didClickTableColumn:(CPTableColumn)tableColumn
Sent when the user clicks a table column but doesn't drag.
- (void)tableView:(CPTableView)tableView mouseDownInHeaderOfTableColumn:(CPTableColumn)tableColumn;
Notify the delegate that the user click the table header of a column.
Contextual Menus
- (CPMenu)tableView:(CPTableView)aTableView menuForTableColumn:(CPTableColumn)aColumn row:(int)aRow
Called when the user right clicks on the tableview. -1 is passed for the row or column if the user doesn't right click on a real row or column
Return a CPMenu that should be displayed if the user right clicks. If you do not implement this the tableview will just call super on menuForEvent
Delete Key
- (void)tableViewDeleteKeyPressed:(CPTableView)aTableView;
Called when the user presses the delete key. Many times you will want to delete data (or prompt for deletion) when the user hids the delete key.
Your delegate can implement this method to avoid subclassing the tableview to add this behaviour.
</pre>
@param aDelegate the delegate object for the tableview.
The delegate can provide easy notification for user interaction, display behaviour, contextual menus, and more.
@section displayingcells Displaying Cells:
Called when the tableview is about to display a dataview
@code
- (void)tableView:(CPTableView)aTableView willDisplayView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex;
@endcode
Group rows are a way to separate a groups of data in a tableview. Return YES if the given row is a group row, otherwise NO.
@code
- (BOOL)tableView:(CPTableView)tableView isGroupRow:(int)row;
@endcode
@section editingcells Editing Cells:
Return YES if the dataview at a given index and column should be edited, otherwise NO.
@code
- (BOOL)tableView:(CPTableView)aTableView shouldEditTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex;
@endcode
@section sizes Setting Row and Column Size:
Return the height of the row at a given index. Only implement this if you want variable row heights. Otherwise use setRowHeight: on the tableview.
@code
- (float)tableView:(CPTableView)tableView heightOfRow:(int)row;
@endcode
@section selection Selecting in the TableView:
@note These methods are only called when the user does something.@endnote
Return YES if the selection of the tableview should change, otherwise NO to keep the current selection.
@code
- (BOOL)selectionShouldChangeInTableView:(CPTableView)aTableView;
@endcode
Return YES if the row at a given index should be selected, other NO to deny the selection.
@code
- (BOOL)tableView:(CPTableView)aTableView shouldSelectRow:(int)rowIndex;
@endcode
Return YES if the table column given should be selected, otherwise NO to deny the selection.
@code
- (BOOL)tableView:(CPTableView)aTableView shouldSelectTableColumn:(CPTableColumn)aTableColumn;
@endcode
Inform the delegate that the tableview is in the process of chaining the selection.
This usually happens when the user is dragging their mouse across rows.
@code
- (void)tableViewSelectionIsChanging:(CPNotification)aNotification
@endcode
Informs the delegate that the tableview selection has changed.
@code
- (void)tableViewSelectionDidChange:(CPNotification)aNotification;
@endcode
@section movingandresizingcolumsn Moving and Resizing Columns:
Return YES if the column at a given index should move to a new column index, otherwise NO.
@code
- (BOOL)tableView:(CPTableView)tableView shouldReorderColumn:(int)columnIndex toColumn:(int)newColumnIndex;
@endcode
Notifies the delegate that the tableview drag occurred. This is send on mouse up.
@code
- (void)tableView:(CPTableView)tableView didDragTableColumn:(CPTableColumn)tableColumn;
@endcode
Notifies the delegate that a tablecolumn was moved by the user.
@code
- (void)tableViewColumnDidMove:(CPNotification)aNotification;
@endcode
Notifies the delegate that the user resized the table column
@code
- (void)tableViewColumnDidResize:(CPNotification)aNotification;
@endcode
@section mousevents Responding to Mouse Events:
Sent when the user clicks a table column but doesn't drag.
@code
- (void)tableView:(CPTableView)tableView didClickTableColumn:(CPTableColumn)tableColumn;
@endcode
Notify the delegate that the user click the table header of a column.
@code
- (void)tableView:(CPTableView)tableView mouseDownInHeaderOfTableColumn:(CPTableColumn)tableColumn;
@endcode
@section contextualmenus Contextual Menus:
Called when the user right clicks on the tableview. -1 is passed for the row or column if the user doesn't right click on a real row or column
Return a CPMenu that should be displayed if the user right clicks. If you do not implement this the tableview will just call super on menuForEvent
@code
- (CPMenu)tableView:(CPTableView)aTableView menuForTableColumn:(CPTableColumn)aColumn row:(int)aRow;
@endcode
@section deletekey Delete Key
Called when the user presses the delete key. Many times you will want to delete data (or prompt for deletion) when the user hids the delete key.
Your delegate can implement this method to avoid subclassing the tableview to add this behaviour.
@code
- (void)tableViewDeleteKeyPressed:(CPTableView)aTableView;
@endcode
*/
- (void)setDelegate:(id)aDelegate
{
@@ -2892,7 +2802,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
/*!
Sets the default operation mask for the drag behavior of the table view.
@note isLocal is not implemented.
NOTE: isLocal is not implemented.
*/
- (void)setDraggingSourceOperationMask:(CPDragOperation)mask forLocal:(BOOL)isLocal
{
@@ -3517,7 +3427,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
/*!
Draws the background in a given clip rect.
This method should only be overridden if you want something other than a solid color or alternating row colors.
@note this method should not be called directly, instead use \c setNeedsDisplay:
NOTE: this method should not be called directly, instead use setNeedsDisplay:
*/
- (void)drawBackgroundInClipRect:(CGRect)aRect
{
@@ -3571,7 +3481,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
/*!
Draws the grid for the tableview based on the set grid mask in a given clip rect.
@note this method should not be called directly, instead use setNeedsDisplay:
NOTE: this method should not be called directly, instead use setNeedsDisplay:
*/
- (void)drawGridInClipRect:(CGRect)aRect
{
@@ -3648,7 +3558,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
/*!
Draws the selection with the set selection highlight style in a given clip rect.
You can change the highlight style to a source list style gradient in setSelectionHighlightStyle:
@note this method should not be called directly, instead use \c setNeedsDisplay:
NOTE: this method should not be called directly, instead use setNeedsDisplay:
*/
- (void)highlightSelectionInClipRect:(CGRect)aRect
{
@@ -3879,7 +3789,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
While this method doesn't do anything in Cappuccino, subclasses can override it to customize the
appearance of a row.
@note \c tableView:willDisplayView:forTableColumn:row is sent to the delegate before drawing
NOTE: tableView:willDisplayView:forTableColumn:row is sent to the delegate before drawing
*/
- (void)drawRow:(CPInteger)row clipRect:(CGRect)rect
{
@@ -4567,19 +4477,14 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
[super keyDown:anEvent];
}
/*!
@ignore
Determines if the selection is broken. A broken selection
is a non-continuous selection of rows.
*/
- (BOOL)_selectionIsBroken
- (BOOL)selectionIsBroken
{
return [self selectedRowIndexes]._ranges.length !== 1;
}
/*!
@ignore
Selection behaviour depends on two things:
Selection behavior depends on two things:
_lastSelectedRow and the anchored selection (the last row selected by itself)
*/
- (void)_moveSelectionWithEvent:(CPEvent)theEvent upward:(BOOL)shouldGoUpward
@@ -4593,18 +4498,18 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
var extend = (([theEvent modifierFlags] & CPShiftKeyMask) && _allowsMultipleSelection),
i = [self selectedRow];
if([self _selectionIsBroken])
if([self selectionIsBroken])
{
while ([selectedIndexes containsIndex:i])
{
shouldGoUpward ? i-- : i++;
}
_wasSelectionBroken = true;
_wasBroken = true;
}
else if (_wasSelectionBroken && ((shouldGoUpward && i !== [selectedIndexes firstIndex]) || (!shouldGoUpward && i !== [selectedIndexes lastindex])))
else if (_wasBroken && ((shouldGoUpward && i !== [selectedIndexes leastIndex]) || (!shouldGoUpward && i !== [selectedIndexes mostIndex])))
{
shouldGoUpward ? i = [selectedIndexes firstIndex] - 1 : i = [selectedIndexes lastIndex];
_wasSelectionBroken = false;
shouldGoUpward ? i = [selectedIndexes leastIndex] - 1 : i = [selectedIndexes mostIndex];
_wasBroken = false;
}
else
{
@@ -4676,6 +4581,28 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
@end
@implementation CPIndexSet (LeastAndMost)
- (int)leastIndex
{
var least = _ranges[0].location;
for(var i = 1; i < _ranges.length; i++)
if (_ranges[i].location < least)
least = _ranges[i].location;
return least;
}
- (int)mostIndex
{
var most = _ranges[0].location + _ranges[0].length;
for(var i = 1; i < _ranges.length; i++)
if (_ranges[i].location + _ranges[i].length > most)
most = _ranges[i].location + _ranges[i].length;
return most;
}
@end
@implementation CPTableView (Bindings)
/*!