/** GormOutlineView The NSOutlineView subclass in gorm which handles outlet/action editing Copyright (C) 2001 Free Software Foundation, Inc. Author: Gregory John Casamento Date: July 2002 This file is part of the GNUstep GUI Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "GormOutlineView.h" static NSNotificationCenter *nc = nil; static const NSInteger current_version = 1; // Cache the arrow images... static NSImage *collapsed = nil; static NSImage *expanded = nil; static NSImage *unexpandable = nil; static NSImage *action = nil; static NSImage *outlet = nil; static NSImage *actionSelected = nil; static NSImage *outletSelected = nil; // some common colors which will be used to indicate state in the outline // view. static NSColor *salmonColor = nil; static NSColor *darkSalmonColor = nil; static NSColor *lightGreyBlueColor = nil; static NSColor *darkGreyBlueColor = nil; @implementation GormOutletActionHolder - init { [super init]; _name = nil; return self; } - initWithName: (NSString *)name { [self init]; ASSIGN(_name,name); return self; } - (NSString *)getName { return _name; } - (void)setName: (NSString *)name { ASSIGN(_name,name); } @end @implementation GormOutlineView // Initialize the class when it is loaded + (void) initialize { if (self == [GormOutlineView class]) { NSBundle *bundle = [NSBundle bundleForClass: self]; NSString *path = nil; // initialize images [self setVersion: current_version]; nc = [NSNotificationCenter defaultCenter]; collapsed = [NSImage imageNamed: @"common_outlineCollapsed"]; expanded = [NSImage imageNamed: @"common_outlineExpanded"]; unexpandable = [NSImage imageNamed: @"common_outlineUnexpandable"]; path = [bundle pathForImageResource: @"GormAction"]; action = [[NSImage alloc] initWithContentsOfFile: path]; path = [bundle pathForImageResource: @"GormOutlet"]; outlet = [[NSImage alloc] initWithContentsOfFile: path]; path = [bundle pathForImageResource: @"GormActionSelected"]; actionSelected = [[NSImage alloc] initWithContentsOfFile: path]; path = [bundle pathForImageResource: @"GormOutletSelected"]; outletSelected = [[NSImage alloc] initWithContentsOfFile: path]; // initialize colors salmonColor = RETAIN([NSColor colorWithCalibratedRed: 0.850980 green: 0.737255 blue: 0.576471 alpha: 1.0 ]); darkSalmonColor = RETAIN([NSColor colorWithCalibratedRed: 0.568627 green: 0.494118 blue: 0.384314 alpha: 1.0 ]); lightGreyBlueColor = RETAIN([NSColor colorWithCalibratedRed: 0.450980 green: 0.450980 blue: 0.521569 alpha: 1.0 ]); darkGreyBlueColor = RETAIN([NSColor colorWithCalibratedRed: 0.333333 green: 0.333333 blue: 0.384314 alpha: 1.0 ]); } } - (void) _handleDoubleClick: (id)sender { NSDebugLog(@"Double clicked"); } - init { if((self = [super init]) != nil) { _actionColumn = nil; _outletColumn = nil; _isEditing = NO; _attributeOffset = 0.0; _edittype = None; _menuItem = nil; [self setDoubleAction: @selector(_handleDoubleClick:)]; [self setTarget: self]; } return self; } - (void) collapseItem: (id)item collapseChildren: (BOOL)collapseChildren; { if (!_isEditing) { // [self deselectAll: self]; [super collapseItem: item collapseChildren: collapseChildren]; } } - (void) expandItem: (id)item expandChildren: (BOOL)expandChildren { if (!_isEditing) { // [self deselectAll: self]; [super expandItem: item expandChildren: expandChildren]; } } - (BOOL) _isOutletOrActionOfItemBeingEdited: (NSString *)name { NSArray *array = nil; array = [_dataSource outlineView: self actionsForItem: _itemBeingEdited]; if ([array containsObject: name]) return YES; array = [_dataSource outlineView: self outletsForItem: _itemBeingEdited]; if ([array containsObject: name]) return YES; return NO; } - (void) _addNewActionToObject: (id)item { NSUInteger insertionPoint = 0; NSString *name = nil; GormOutletActionHolder *holder = [[GormOutletActionHolder alloc] init]; name = [_dataSource outlineView: self addNewActionForClass: _itemBeingEdited]; if (name != nil) { _numberOfRows += 1; [holder setName: name]; insertionPoint = [_items indexOfObject: item]; [_items insertObject: holder atIndex: insertionPoint + 1]; [self setNeedsDisplay: YES]; [self noteNumberOfRowsChanged]; } } - (void) _addNewOutletToObject: (id)item { NSUInteger insertionPoint = 0; GormOutletActionHolder *holder = [[GormOutletActionHolder alloc] init]; NSString *name = nil; _numberOfRows += 1; name = [_dataSource outlineView: self addNewOutletForClass: _itemBeingEdited]; if (name != nil) { [holder setName: name]; insertionPoint = [_items indexOfObject: item]; [_items insertObject: holder atIndex: insertionPoint + 1]; [self setNeedsDisplay: YES]; [self noteNumberOfRowsChanged]; } } - (void) removeItemAtRow: (int)row { [_items removeObjectAtIndex: row]; _numberOfRows -= 1; [self setNeedsDisplay: YES]; [self noteNumberOfRowsChanged]; } - (void)_openActions: (id)item { NSInteger numchildren = 0; NSInteger i = 0; NSUInteger insertionPoint = 0; id object = nil; id sitem = (item == nil)?((id)[NSNull null]):((id)item); object = [_dataSource outlineView: self actionsForItem: sitem]; numchildren = [object count]; _numberOfRows += numchildren; // open the item... if (item != nil) { [self setItemBeingEdited: item]; [self setIsEditing: YES]; } insertionPoint = [_items indexOfObject: item]; if (insertionPoint == NSNotFound) { insertionPoint = 0; } else { insertionPoint++; } [self setNeedsDisplay: YES]; for (i = numchildren - 1; i >= 0; i--) { id child = [object objectAtIndex: i]; // Add all of the children... GormOutletActionHolder *holder; holder = [[GormOutletActionHolder alloc] initWithName: child]; [_items insertObject: holder atIndex: insertionPoint]; } [self noteNumberOfRowsChanged]; } - (void) _openOutlets: (id)item { NSInteger numchildren = 0; NSInteger i = 0; NSInteger insertionPoint = 0; id object = nil; id sitem = (item == nil)?((id)[NSNull null]):((id)item); object = [_dataSource outlineView: self outletsForItem: sitem]; numchildren = [object count]; _numberOfRows += numchildren; // open the item... if (item != nil) { [self setItemBeingEdited: item]; [self setIsEditing: YES]; } insertionPoint = [_items indexOfObject: item]; if (insertionPoint == NSNotFound) { insertionPoint = 0; } else { insertionPoint++; } [self setNeedsDisplay: YES]; for (i = numchildren - 1; i >= 0; i--) { id child = [object objectAtIndex: i]; // Add all of the children... GormOutletActionHolder *holder; holder = [[GormOutletActionHolder alloc] initWithName: child]; [_items insertObject: holder atIndex: insertionPoint]; } [self noteNumberOfRowsChanged]; } - (void) drawRow: (NSInteger)rowIndex clipRect: (NSRect)aRect { NSInteger startingColumn; NSInteger endingColumn; NSTableColumn *tb; NSRect drawingRect; NSCell *cell; NSCell *imageCell = nil; NSRect imageRect; NSInteger i; float x_pos; if (_dataSource == nil) { return; } /* Using columnAtPoint: here would make it called twice per row per drawn rect - so we avoid it and do it natively */ if (rowIndex >= _numberOfRows) { return; } /* Determine starting column as fast as possible */ x_pos = NSMinX (aRect); i = 0; while ((x_pos > _columnOrigins[i]) && (i < _numberOfColumns)) { i++; } startingColumn = (i - 1); if (startingColumn == -1) startingColumn = 0; /* Determine ending column as fast as possible */ x_pos = NSMaxX (aRect); // Nota Bene: we do *not* reset i while ((x_pos > _columnOrigins[i]) && (i < _numberOfColumns)) { i++; } endingColumn = (i - 1); if (endingColumn == -1) endingColumn = _numberOfColumns - 1; /* Draw the row between startingColumn and endingColumn */ for (i = startingColumn; i <= endingColumn; i++) { if (i != _editedColumn || rowIndex != _editedRow) { id item = [self itemAtRow: rowIndex]; id value = nil, valueforcell = nil; BOOL isOutletAction = NO; tb = [_tableColumns objectAtIndex: i]; cell = [tb dataCellForRow: rowIndex]; value = [_dataSource outlineView: self objectValueForTableColumn: tb byItem: item]; if ([value isKindOfClass: [GormOutletActionHolder class]]) { valueforcell = [value getName]; isOutletAction = YES; } else { valueforcell = value; isOutletAction = NO; } if ([_delegate respondsToSelector: @selector(outlineView:willDisplayCell:forTableColumn:item:)]) { [_delegate outlineView: self willDisplayCell: cell forTableColumn: tb item: item]; } [cell setObjectValue: valueforcell]; drawingRect = [self frameOfCellAtColumn: i row: rowIndex]; if (isOutletAction) { drawingRect.origin.x += _attributeOffset; drawingRect.size.width -= _attributeOffset; } if (tb == _outlineTableColumn && !isOutletAction) { NSImage *image = nil; NSInteger level = 0; float indentationFactor = 0.0; // display the correct arrow... if ([self isItemExpanded: item]) { image = expanded; } else { image = collapsed; } if (![self isExpandable: item]) { image = unexpandable; } level = [self levelForItem: item]; indentationFactor = _indentationPerLevel * level; imageCell = [[NSCell alloc] initImageCell: image]; if (_indentationMarkerFollowsCell) { imageRect.origin.x = drawingRect.origin.x + indentationFactor; imageRect.origin.y = drawingRect.origin.y; } else { imageRect.origin.x = drawingRect.origin.x; imageRect.origin.y = drawingRect.origin.y; } imageRect.size.width = [image size].width; imageRect.size.height = [image size].height; [imageCell drawWithFrame: imageRect inView: self]; drawingRect.origin.x += indentationFactor + [image size].width + 5; drawingRect.size.width -= indentationFactor + [image size].width + 5; // [cell drawWithFrame: drawingRect inView: self]; } else if ((tb == _actionColumn || tb == _outletColumn) && isOutletAction == NO) { NSImage *image = nil; if (item == _itemBeingEdited && tb == _actionColumn && _edittype == Actions) image = actionSelected; else if (item == _itemBeingEdited && tb == _outletColumn && _edittype == Outlets) image = outletSelected; else image = (tb == _actionColumn)?action:outlet; // Prepare image cell... imageCell = [[NSCell alloc] initImageCell: image]; imageRect.origin.x = drawingRect.origin.x; imageRect.origin.y = drawingRect.origin.y; imageRect.size.width = [image size].width; imageRect.size.height = [image size].height; [imageCell drawWithFrame: imageRect inView: self]; // Adjust drawing rect of cell being displayed... drawingRect.origin.x += [image size].width + 5; drawingRect.size.width -= [image size].width + 5; // [cell drawWithFrame: drawingRect inView: self]; } if (((tb != _outletColumn || tb != _actionColumn) && !isOutletAction) || (tb == _outlineTableColumn)) { [cell drawWithFrame: drawingRect inView: self]; } } } } - (void) reset { [self setItemBeingEdited: nil]; [self setIsEditing: NO]; [self setBackgroundColor: salmonColor]; [self reloadData]; } - (void) mouseDown: (NSEvent *)theEvent { NSPoint location = [theEvent locationInWindow]; NSTableColumn *tb; NSImage *image = nil; id _clickedItem = nil; BOOL isActionOrOutlet = NO; location = [self convertPoint: location fromView: nil]; _clickedRow = [self rowAtPoint: location]; _clickedColumn = [self columnAtPoint: location]; _clickedItem = [self itemAtRow: _clickedRow]; isActionOrOutlet = [_clickedItem isKindOfClass: [GormOutletActionHolder class]]; tb = [_tableColumns objectAtIndex: _clickedColumn]; if (tb == _actionColumn) { image = action; } else if (tb == _outletColumn) { image = outlet; } if ((tb == _actionColumn || tb == _outletColumn) && !_isEditing) { NSInteger position = 0; position += _columnOrigins[_clickedColumn] + 5; if (location.x >= position && location.x <= position + [image size].width + 5) { [self setItemBeingEdited: _clickedItem]; [self setIsEditing: YES]; // [self setBackgroundColor: darkSalmonColor]; // for later if (tb == _actionColumn) { _edittype = Actions; [self _openActions: _clickedItem]; } else if (tb == _outletColumn) { _edittype = Outlets; [self _openOutlets: _clickedItem]; } } [super mouseDown: theEvent]; } else if (_isEditing && !isActionOrOutlet) { if (_clickedItem != [self itemBeingEdited] && !isActionOrOutlet) { [self reset]; } else if (tb == _actionColumn) { if (_edittype != Actions) { [self reset]; _edittype = Actions; [self _openActions: _clickedItem]; } } else /* tb == _outletColumn */ { if (_edittype != Outlets) { [self reset]; _edittype = Outlets; [self _openOutlets: _clickedItem]; } } } else { [super mouseDown: theEvent]; } } // additional methods for subclass - (void) setAttributeOffset: (float)offset { _attributeOffset = offset; } - (float) attributeOffset { return _attributeOffset; } - (void) setItemBeingEdited: (id)item { _itemBeingEdited = item; } - (id) itemBeingEdited { return _itemBeingEdited; } - (void) setIsEditing: (BOOL)flag { _isEditing = flag; } - (BOOL) isEditing { return _isEditing; } - (void)setActionColumn: (NSTableColumn *)ac { ASSIGN(_actionColumn,ac); } - (NSTableColumn *)actionColumn { return _actionColumn; } - (void)setOutletColumn: (NSTableColumn *)oc { ASSIGN(_outletColumn,oc); } - (NSTableColumn *)outletColumn { return _outletColumn; } - (void)setMenuItem: (NSMenuItem *)item { ASSIGN(_menuItem, item); } - (NSMenuItem *)menuItem { return _menuItem; } - (GSAttributeType)editType { return _edittype; } - (void) editColumn: (NSInteger) columnIndex row: (NSInteger) rowIndex withEvent: (NSEvent *) theEvent select: (BOOL) flag { NSText *t; NSTableColumn *tb; NSRect drawingRect, imageRect; unsigned length = 0; id item = nil; NSInteger level = 0; float indentationFactor = 0.0; NSImage *image = nil; NSCell *imageCell = nil; id value = nil; BOOL isOutletOrAction = NO; // We refuse to edit cells if the delegate can not accept results // of editing. if (_dataSource_editable == NO) { return; } [self scrollRowToVisible: rowIndex]; [self scrollColumnToVisible: columnIndex]; if (rowIndex < 0 || rowIndex >= _numberOfRows || columnIndex < 0 || columnIndex >= _numberOfColumns) { [NSException raise: NSInvalidArgumentException format: @"Row/column out of index in edit"]; } if (_textObject != nil) { [self validateEditing]; [self abortEditing]; } // Now (_textObject == nil) t = [_window fieldEditor: YES forObject: self]; if ([t superview] != nil) { if ([t resignFirstResponder] == NO) { return; } } _editedRow = rowIndex; _editedColumn = columnIndex; item = [self itemAtRow: _editedRow]; // Prepare the cell tb = [_tableColumns objectAtIndex: columnIndex]; // NB: need to be released when no longer used _editedCell = [[tb dataCellForRow: rowIndex] copy]; value = [_dataSource outlineView: self objectValueForTableColumn: tb byItem: item]; if ([value isKindOfClass: [GormOutletActionHolder class]]) { isOutletOrAction = YES; value = [value getName]; } [_editedCell setEditable: YES]; [_editedCell setObjectValue: value]; // We really want the correct background color! if ([_editedCell respondsToSelector: @selector(setBackgroundColor:)]) { [(NSTextFieldCell *)_editedCell setBackgroundColor: _backgroundColor]; } else { [t setBackgroundColor: _backgroundColor]; } // But of course the delegate can mess it up if it wants if (_del_responds) { [_delegate outlineView: self willDisplayCell: _editedCell forTableColumn: tb item: [self itemAtRow: rowIndex]]; } /* Please note the important point - calling stringValue normally causes the _editedCell to call the validateEditing method of its control view ... which happens to be this object :-) but we don't want any spurious validateEditing to be performed before the actual editing is started (otherwise you easily end up with the table view picking up the string stored in the field editor, which is likely to be the string resulting from the last edit somewhere else ... getting into the bug that when you TAB from one cell to another one, the string is copied!), so we must call stringValue when _textObject is still nil. */ if (flag) { length = [[_editedCell stringValue] length]; } _textObject = [_editedCell setUpFieldEditorAttributes: t]; // determine which image to use... if ([self isItemExpanded: item]) { image = expanded; } else { image = collapsed; } if (![self isExpandable: item]) { image = unexpandable; } // move the drawing rect over like in the drawRow routine... level = [self levelForItem: item]; indentationFactor = _indentationPerLevel * level; drawingRect = [self frameOfCellAtColumn: columnIndex row: rowIndex]; if (isOutletOrAction) { drawingRect.origin.x += _attributeOffset; drawingRect.size.width -= _attributeOffset; } else { drawingRect.origin.x += indentationFactor + 5 + [image size].width; drawingRect.size.width -= indentationFactor + 5 + [image size].width; } // create the image cell.. imageCell = [[NSCell alloc] initImageCell: image]; if (_indentationMarkerFollowsCell) { imageRect.origin.x = drawingRect.origin.x + indentationFactor; imageRect.origin.y = drawingRect.origin.y; } else { imageRect.origin.x = drawingRect.origin.x; imageRect.origin.y = drawingRect.origin.y; } // draw... imageRect.size.width = [image size].width; imageRect.size.height = [image size].height; [imageCell drawWithFrame: imageRect inView: self]; if (flag) { [_editedCell selectWithFrame: drawingRect inView: self editor: _textObject delegate: self start: 0 length: length]; } else { [_editedCell editWithFrame: drawingRect inView: self editor: _textObject delegate: self event: theEvent]; } return; } - (void) selectRow: (int)rowIndex { [self setNeedsDisplayInRect: [self rectOfRow: rowIndex]]; [_selectedRows addIndex: rowIndex]; _selectedRow = rowIndex; } @end /* implementation of GormOutlineView */