Merge pull request #284 from gnustep/NSBrowser_bindings_branch

This commit is contained in:
Gregory Casamento 2025-01-26 08:46:43 -05:00 committed by GitHub
commit c28695dd4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 320 additions and 110 deletions

View file

@ -1,3 +1,11 @@
2024-11-08 Gregory John Casamento <greg.casamento@gmail.com>
* Headers/AppKit/NSBrowser.h: Add dictionary to hold relationships.
* Source/NSBrowser.m: Add logic in methods to support binfings,
expose bindings in +initialize.
* Source/NSTextFieldCell.m: Minor fix to NSTextField for displaying
binding values.
2024-10-31 Richard Frith-Macdonald <rfm@gnu.org>
* Source/GSServicesManager.m: fix -laterDate: conditionals to be true

View file

@ -37,6 +37,7 @@
@class NSArray;
@class NSIndexPath;
@class NSIndexSet;
@class NSMutableDictionary;
@class NSCell;
@class NSEvent;
@ -100,6 +101,7 @@ APPKIT_EXPORT_CLASS
NSBrowserColumnResizingType _columnResizing;
BOOL _itemBasedDelegate;
NSMutableDictionary *_columnDictionary;
}
//

View file

@ -16,7 +16,7 @@
Date: September 2002
Author: Gregory Casamento <greg.casamento@gmail.com>
Date: July 2024
Note: Added support for 10.6+ delegate methods.
Note: Added support for 10.6+ delegate methods. Added bindings support
This file is part of the GNUstep GUI Library.
@ -56,10 +56,13 @@
#import "AppKit/NSColor.h"
#import "AppKit/NSFont.h"
#import "AppKit/NSGraphics.h"
#import "AppKit/NSKeyValueBinding.h"
#import "AppKit/NSMatrix.h"
#import "AppKit/NSScroller.h"
#import "AppKit/NSScrollView.h"
#import "AppKit/NSTableHeaderCell.h"
#import "AppKit/NSTreeController.h"
#import "AppKit/NSTreeNode.h"
#import "AppKit/NSEvent.h"
#import "AppKit/NSViewController.h"
#import "AppKit/NSWindow.h"
@ -260,6 +263,10 @@ static BOOL browserUseBezels;
- (void) _themeDidActivate: (NSNotification*)notification;
@end
// Category to handle bindings
@interface NSBrowser (GSBindingsPrivate)
@end
//
// NSBrowser implementation
//
@ -2268,7 +2275,7 @@ static BOOL browserUseBezels;
_itemBasedDelegate = NO;
if ([anObject respondsToSelector:
@selector(browser:numberOfChildrenOfItem:)]
@selector(browser:numberOfChildrenOfItem:)]
&& [anObject respondsToSelector:
@selector(browser:child:ofItem:)]
&& [anObject respondsToSelector:
@ -2486,6 +2493,10 @@ static BOOL browserUseBezels;
}
[self _themeDidActivate: nil];
// Bindings...
[self exposeBinding: NSContentBinding];
[self exposeBinding: NSContentValuesBinding];
}
}
@ -2558,6 +2569,7 @@ static BOOL browserUseBezels;
// Item based delegate, 10.6+
_itemBasedDelegate = NO;
_columnDictionary = [[NSMutableDictionary alloc] init];
[[NSNotificationCenter defaultCenter]
addObserver: self
@ -2583,6 +2595,7 @@ static BOOL browserUseBezels;
RELEASE(_pathSeparator);
RELEASE(_horizontalScroller);
RELEASE(_browserColumns);
RELEASE(_columnDictionary);
TEST_RELEASE(_charBuffer);
[super dealloc];
@ -3009,6 +3022,7 @@ static BOOL browserUseBezels;
// Item based delegate, 10.6+
_itemBasedDelegate = NO;
_columnDictionary = [[NSMutableDictionary alloc] init];
// Horizontal scroller
_scrollerRect.origin.x = bs.width;
@ -3296,10 +3310,19 @@ static BOOL browserUseBezels;
- (id) _itemForColumn: (NSInteger)column
{
id item = nil;
GSKeyValueBinding *theBinding;
theBinding = [GSKeyValueBinding getBinding: NSContentBinding
forObject: self];
if (column == 0)
{
item = [_browserDelegate rootItemForBrowser: self];
if (theBinding == nil)
{
item = [_browserDelegate rootItemForBrowser: self];
}
else
{
item = nil; // [NSNull null];
}
}
else
{
@ -3319,7 +3342,20 @@ static BOOL browserUseBezels;
{
id cell = [selectedCells objectAtIndex: 0];
item = [cell objectValue];
if (theBinding != nil)
{
NSNumber *colNum = [NSNumber numberWithInteger: col];
NSArray *array = [_columnDictionary objectForKey: colNum];
if ([array count] > 0)
{
NSInteger row = [self selectedRowInColumn: col];
item = [array objectAtIndex: row];
}
}
else
{
item = [cell objectValue];
}
}
}
}
@ -3328,22 +3364,104 @@ static BOOL browserUseBezels;
return item;
}
- (NSString *) _keyPathForValueBinding
{
NSString *keyPath = nil;
NSDictionary *info = [GSKeyValueBinding infoForBinding: NSContentValuesBinding
forObject: self];
if (info != nil)
{
NSString *ikp = [info objectForKey: NSObservedKeyPathKey];
NSUInteger location = [ikp rangeOfString: @"."].location;
keyPath = (location == NSNotFound ? ikp : [ikp substringFromIndex: location + 1]);
}
return keyPath;
}
/* Loads column 'column' (asking the delegate). */
- (void) _performLoadOfColumn: (NSInteger)column
{
NSBrowserColumn *bc;
NSScrollView *sc;
NSMatrix *matrix;
NSInteger i, rows, cols;
NSBrowserColumn *bc = nil;
NSScrollView *sc = nil;
NSMatrix *matrix = nil;
NSInteger i = 0, rows = 0, cols = 0;
id child = nil;
id item = nil;
NSNumber *colNum = nil;
NSTreeController *tc = nil;
NSArray *children = nil;
if (_itemBasedDelegate)
{
item = [self _itemForColumn: column];
GSKeyValueBinding *theBinding;
// Ask the delegate for the number of rows for a given item...
rows = [_browserDelegate browser: self numberOfChildrenOfItem: item];
theBinding = [GSKeyValueBinding getBinding: NSContentBinding
forObject: self];
item = [self _itemForColumn: column];
if (theBinding != nil)
{
id observedObject = [theBinding observedObject];
rows = 0;
colNum = [NSNumber numberWithInteger: column];
if ([observedObject isKindOfClass: [NSTreeController class]])
{
tc = (NSTreeController *)observedObject;
if (item == nil)
{
NSTreeNode *node = (NSTreeNode *)[theBinding destinationValue];
if (node != nil)
{
/* Per the documentation 10.4/5+ uses NSTreeNode as the return value for
* the contents of this tree node consists of a dictionary with a single
* key of "children". This is per the tests for this at
* https://github.com/gcasa/NSTreeController_test. Specifically it returns
* _NSControllerTreeProxy. The equivalent of that class in GNUstep is
* GSControllerTreeProxy.
*/
children = [node mutableChildNodes];
rows = [children count];
item = node;
}
}
else
{
NSString *childrenKeyPath = [tc childrenKeyPathForNode: item];
if (childrenKeyPath != nil)
{
NSString *countKeyPath = [tc countKeyPathForNode: item];
children = [item valueForKeyPath: childrenKeyPath];
if (countKeyPath == nil)
{
rows = [children count]; // get the count directly...
}
else
{
NSNumber *countValue = [item valueForKeyPath: countKeyPath];
rows = [countValue integerValue];
}
}
}
// If the node has children, add them to the column...
if (children != nil)
{
[_columnDictionary setObject: children forKey: colNum];
}
}
}
else
{
// Ask the delegate for the number of rows for a given item...
rows = [_browserDelegate browser: self numberOfChildrenOfItem: item];
}
cols = 1;
}
else if (_passiveDelegate)
@ -3410,23 +3528,64 @@ static BOOL browserUseBezels;
[sc setDocumentView: matrix];
// Loading is different based upon item/passive/active delegate
if (_itemBasedDelegate)
if (_itemBasedDelegate == YES) // && item != nil && tc != nil)
{
// Iterate over the children for the item....
for (i = 0; i < rows; i++)
{
id aCell = [matrix cellAtRow: i column: 0];
if (![aCell isLoaded])
{
BOOL leaf = YES;
id val = nil;
NSString *childrenKeyPath = [tc childrenKeyPathForNode: item];
child = [_browserDelegate browser: self child: i ofItem: item];
leaf = [_browserDelegate browser: self isLeafItem: child];
val = [_browserDelegate browser: self objectValueForItem: child];
[aCell setLeaf: leaf];
[aCell setObjectValue: val];
[aCell setLoaded: YES];
if (childrenKeyPath != nil)
{
NSString *leafKeyPath = [tc leafKeyPathForNode: item];
NSString *valueKeyPath = [self _keyPathForValueBinding];
// Iterate over the children for the item....
for (i = 0; i < rows; i++)
{
id aCell = [matrix cellAtRow: i column: 0];
if (![aCell isLoaded])
{
BOOL leaf = YES;
id val = nil;
NSNumber *leafBool = nil;
child = [children objectAtIndex: i];
leafBool = [child valueForKeyPath: leafKeyPath];
leaf = [leafBool boolValue];
// If a content values binding is present, it uses that key path,
// but if one isn't it uses the description... per documentation.
if (valueKeyPath != nil)
{
val = [child valueForKeyPath: valueKeyPath];
}
else
{
val = [child description]; // per documentation.
}
[aCell setLeaf: leaf];
[aCell setObjectValue: val];
[aCell setLoaded: YES];
}
}
}
else
{
// Iterate over the children for the item....
for (i = 0; i < rows; i++)
{
id aCell = [matrix cellAtRow: i column: 0];
if (![aCell isLoaded])
{
BOOL leaf = YES;
id val = nil;
child = [_browserDelegate browser: self child: i ofItem: item];
leaf = [_browserDelegate browser: self isLeafItem: child];
val = [_browserDelegate browser: self objectValueForItem: child];
[aCell setLeaf: leaf];
[aCell setObjectValue: val];
[aCell setLoaded: YES];
}
}
}
}
@ -3592,3 +3751,40 @@ static BOOL browserUseBezels;
}
@end
@implementation NSBrowser (GSBindingsPrivate)
/* Private methods to handle bindings */
- (void) setValue: (id)anObject forKey: (NSString*)aKey
{
if ([aKey isEqual: NSContentBinding]
|| [aKey isEqual: NSContentValuesBinding])
{
// Reload data
_passiveDelegate = NO;
_itemBasedDelegate = YES;
[self loadColumnZero];
NSDebugLLog(@"NSBinding", @"Setting browser view content/values to %@", anObject);
}
else
{
[super setValue: anObject forKey: aKey];
}
}
- (id) valueForKey: (NSString*)aKey
{
if ([aKey isEqual: NSContentBinding]
|| [aKey isEqual: NSContentValuesBinding])
{
return nil;
}
else
{
return [super valueForKey: aKey];
}
}
@end

View file

@ -8,7 +8,7 @@
Date: 1996
Author: Nicola Pero <n.pero@mi.flashnet.it>
Date: November 1999
This file is part of the GNUstep GUI Library.
This library is free software; you can redistribute it and/or
@ -23,10 +23,10 @@
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
*/
#import "config.h"
#import <Foundation/NSNotification.h>
@ -36,17 +36,21 @@
#import "AppKit/NSEvent.h"
#import "AppKit/NSFont.h"
#import "AppKit/NSGraphics.h"
#import "AppKit/NSKeyValueBinding.h"
#import "AppKit/NSStringDrawing.h"
#import "AppKit/NSTextField.h"
#import "AppKit/NSTextFieldCell.h"
#import "AppKit/NSText.h"
#import "GSBindingHelpers.h"
@implementation NSTextFieldCell
+ (void) initialize
{
if (self == [NSTextFieldCell class])
{
[self exposeBinding: NSValueBinding];
[self setVersion: 2];
}
}
@ -87,7 +91,7 @@
}
//
// Modifying Graphic Attributes
// Modifying Graphic Attributes
//
- (void) setBackgroundColor: (NSColor *)aColor
{
@ -199,25 +203,25 @@
return textObject;
}
- (void) _drawBackgroundWithFrame: (NSRect)cellFrame
inView: (NSView*)controlView
- (void) _drawBackgroundWithFrame: (NSRect)cellFrame
inView: (NSView*)controlView
{
if (_textfieldcell_draws_background)
{
if ([self isEnabled])
{
[_background_color set];
}
{
[_background_color set];
}
else
{
[[NSColor controlBackgroundColor] set];
}
{
[[NSColor controlBackgroundColor] set];
}
NSRectFill([self drawingRectForBounds: cellFrame]);
}
}
}
- (void) _drawBorderAndBackgroundWithFrame: (NSRect)cellFrame
inView: (NSView*)controlView
- (void) _drawBorderAndBackgroundWithFrame: (NSRect)cellFrame
inView: (NSView*)controlView
{
// FIXME: Should use the bezel style if set.
[super _drawBorderAndBackgroundWithFrame: cellFrame inView: controlView];
@ -233,16 +237,16 @@
NSRect titleRect;
/* Make sure we are a text cell; titleRect might return an incorrect
rectangle otherwise. Note that the type could be different if the
user has set an image on us, which we just ignore (OS X does so as
well). */
rectangle otherwise. Note that the type could be different if the
user has set an image on us, which we just ignore (OS X does so as
well). */
_cell.type = NSTextCellType;
titleRect = [self titleRectForBounds: cellFrame];
[[self _drawAttributedString] drawInRect: titleRect];
}
}
/*
/*
Attributed string that will be displayed.
*/
- (NSAttributedString*) _drawAttributedString
@ -254,31 +258,31 @@
{
attrStr = [self placeholderAttributedString];
if ((attrStr == nil) || ([[attrStr string] length] == 0))
{
NSString *string;
NSDictionary *attributes;
NSMutableDictionary *newAttribs;
string = [self placeholderString];
if (string == nil)
{
return nil;
}
{
NSString *string;
NSDictionary *attributes;
NSMutableDictionary *newAttribs;
attributes = [self _nonAutoreleasedTypingAttributes];
newAttribs = [NSMutableDictionary
dictionaryWithDictionary: attributes];
[newAttribs setObject: [NSColor disabledControlTextColor]
forKey: NSForegroundColorAttributeName];
return AUTORELEASE([[NSAttributedString alloc]
initWithString: string
attributes: newAttribs]);
}
string = [self placeholderString];
if (string == nil)
{
return nil;
}
attributes = [self _nonAutoreleasedTypingAttributes];
newAttribs = [NSMutableDictionary
dictionaryWithDictionary: attributes];
[newAttribs setObject: [NSColor disabledControlTextColor]
forKey: NSForegroundColorAttributeName];
return AUTORELEASE([[NSAttributedString alloc]
initWithString: string
attributes: newAttribs]);
}
else
{
return attrStr;
}
{
return attrStr;
}
}
else
{
@ -288,12 +292,12 @@
- (BOOL) isOpaque
{
if (_textfieldcell_draws_background == NO
|| _background_color == nil
if (_textfieldcell_draws_background == NO
|| _background_color == nil
|| [_background_color alphaComponent] < 1.0)
return NO;
else
return YES;
return YES;
}
//
@ -311,9 +315,9 @@
[aCoder encodeObject: [self textColor] forKey: @"NSTextColor"];
[aCoder encodeBool: [self drawsBackground] forKey: @"NSDrawsBackground"];
if ([self isBezeled])
{
[aCoder encodeInt: [self bezelStyle] forKey: @"NSTextBezelStyle"];
}
{
[aCoder encodeInt: [self bezelStyle] forKey: @"NSTextBezelStyle"];
}
}
else
{
@ -329,53 +333,53 @@
self = [super initWithCoder: aDecoder];
if (self == nil)
return self;
if ([aDecoder allowsKeyedCoding])
{
if ([aDecoder containsValueForKey: @"NSBackgroundColor"])
{
[self setBackgroundColor: [aDecoder decodeObjectForKey:
@"NSBackgroundColor"]];
}
{
[self setBackgroundColor: [aDecoder decodeObjectForKey:
@"NSBackgroundColor"]];
}
if ([aDecoder containsValueForKey: @"NSTextColor"])
{
[self setTextColor: [aDecoder decodeObjectForKey: @"NSTextColor"]];
}
{
[self setTextColor: [aDecoder decodeObjectForKey: @"NSTextColor"]];
}
if ([aDecoder containsValueForKey: @"NSDrawsBackground"])
{
[self setDrawsBackground: [aDecoder decodeBoolForKey:
@"NSDrawsBackground"]];
}
{
[self setDrawsBackground: [aDecoder decodeBoolForKey:
@"NSDrawsBackground"]];
}
if ([aDecoder containsValueForKey: @"NSTextBezelStyle"])
{
[self setBezelStyle: [aDecoder decodeIntForKey:
@"NSTextBezelStyle"]];
}
{
[self setBezelStyle: [aDecoder decodeIntForKey:
@"NSTextBezelStyle"]];
}
if ([aDecoder containsValueForKey: @"NSPlaceholderString"])
{
[self setPlaceholderString: [aDecoder decodeObjectForKey:
@"NSPlaceholderString"]];
}
{
[self setPlaceholderString: [aDecoder decodeObjectForKey:
@"NSPlaceholderString"]];
}
}
else
{
BOOL tmp;
if ([aDecoder versionForClassName:@"NSTextFieldCell"] < 2)
{
/* Replace the old default _action_mask with the new default one
if it's set. There isn't really a way to modify this value
on an NSTextFieldCell encoded in a .gorm file. The old default value
causes problems with newer NSTableViews which uses this to discern
whether it should trackMouse:inRect:ofView:untilMouseUp: or not.
This also disables the action from being sent on an uneditable and
unselectable text fields.
*/
if (_action_mask == NSLeftMouseUpMask)
{
_action_mask = NSKeyUpMask | NSKeyDownMask;
}
}
{
/* Replace the old default _action_mask with the new default one
if it's set. There isn't really a way to modify this value
on an NSTextFieldCell encoded in a .gorm file. The old default value
causes problems with newer NSTableViews which uses this to discern
whether it should trackMouse:inRect:ofView:untilMouseUp: or not.
This also disables the action from being sent on an uneditable and
unselectable text fields.
*/
if (_action_mask == NSLeftMouseUpMask)
{
_action_mask = NSKeyUpMask | NSKeyDownMask;
}
}
[aDecoder decodeValueOfObjCType: @encode(id) at: &_background_color];
[aDecoder decodeValueOfObjCType: @encode(id) at: &_text_color];