From 82d40abd709a38ec7162e6bae9159f5c8d9f2fef Mon Sep 17 00:00:00 2001
From: CaS
Date: Mon, 21 Oct 2002 13:03:23 +0000
Subject: [PATCH] Rewrite key view handling
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@14805 72102866-910b-0410-8b05-ffd578937521
---
ChangeLog | 7 +
Headers/gnustep/gui/NSView.h | 4 +-
Source/NSResponder.m | 5 +-
Source/NSTextView.m | 12 +-
Source/NSView.m | 474 +++++++++++++++++++++++++++--------
5 files changed, 388 insertions(+), 114 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 0d40bc0ab..9a5547ff3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2002-10-21 Richard Frith-Macdonald
+
+ * Source/NSView.m: Key view code ... complete rewrite to match
+ MacOS-X implementation.
+
Mon Oct 21 01:17:41 2002 Nicola Pero
* Source/NSView.m ([-setNextKeyView:], [-setPreviousKeyView:]):
@@ -9,6 +14,8 @@ Mon Oct 21 01:17:41 2002 Nicola Pero
* Headers/gnustep/gui/NSComboBox.h: Add APPKIT_EXPORT and make layout
a bit more consistent.
+ * Source/NSTextView.m: ([-dealloc]) remove unneeded RETAIN()
+ Fixes for beugs reported by Caba Conti.
2002-10-18 Adam Fedor
diff --git a/Headers/gnustep/gui/NSView.h b/Headers/gnustep/gui/NSView.h
index a0b548df6..d439084c6 100644
--- a/Headers/gnustep/gui/NSView.h
+++ b/Headers/gnustep/gui/NSView.h
@@ -106,8 +106,8 @@ enum {
BOOL _allocate_gstate;
BOOL _renew_gstate;
- NSView *_nextKeyView;
- NSView *_previousKeyView;
+ void *_nextKeyView;
+ void *_previousKeyView;
}
/*
diff --git a/Source/NSResponder.m b/Source/NSResponder.m
index 0170d1a4b..04a48351d 100644
--- a/Source/NSResponder.m
+++ b/Source/NSResponder.m
@@ -69,8 +69,9 @@
_next_responder = aResponder;
}
-/*
- * Determining the first responder
+/**
+ * Returns YES if the receiver is able to become the first responder,
+ * NO otherwise.
*/
- (BOOL) acceptsFirstResponder
{
diff --git a/Source/NSTextView.m b/Source/NSTextView.m
index af3f1196d..19f48c875 100644
--- a/Source/NSTextView.m
+++ b/Source/NSTextView.m
@@ -352,19 +352,15 @@ static NSNotificationCenter *nc;
return self;
}
-- (void)dealloc
+- (void) dealloc
{
if (_tvf.owns_text_network == YES)
{
if (_textStorage != nil)
{
- /* Balance the RELEASE we sent to us to break the retain cycle
- in initWithFrame: or initWithCoder: (otherwise releasing the
- _textStorage will make our retain count go below zero ;-) */
- RETAIN (self);
-
- /* This releases all the text objects (us included) in
- * fall. */
+ /* This releases all the text objects (us included) which means
+ * this method will be called again ... so this time we just return.
+ */
DESTROY (_textStorage);
/* When the rest of the text network is released, we'll be
diff --git a/Source/NSView.m b/Source/NSView.m
index f49a4a3e1..4ea8a0cff 100644
--- a/Source/NSView.m
+++ b/Source/NSView.m
@@ -68,6 +68,21 @@
#include
#include
+/*
+ * We need a fast array that can store objects without retain/release ...
+ */
+#define GSI_ARRAY_TYPES GSUNION_OBJ
+#define GSI_ARRAY_NO_RELEASE 1
+#define GSI_ARRAY_NO_RETAIN 1
+
+#ifdef GSIArray
+#undef GSIArray
+#endif
+#include
+
+#define nKV(O) ((GSIArray)(O->_nextKeyView))
+#define pKV(O) ((GSIArray)(O->_previousKeyView))
+
/* Variable tells this view and subviews that we're printing. Not really
a class variable because we want it visible to subviews also
*/
@@ -91,7 +106,7 @@ struct NSWindow_struct
hierarchy of NSViews, headed by the window's content view. Every
other view in a window is a descendant of this view.
- Subclasses can override drawRect:
in order to
+
Subclasses can override -drawRect: in order to
implement their appearance. Other methods of NSView and NSResponder
can also be overridden to handle user generated events.
@@ -280,18 +295,106 @@ GSSetDragTypes(NSView* obj, NSArray *types)
- (void) dealloc
{
+ NSView *tmp;
+ unsigned count;
+
while ([_sub_views count] > 0)
{
[[_sub_views lastObject] removeFromSuperviewWithoutNeedingDisplay];
}
- if (_nextKeyView != nil)
+
+ /*
+ * Remove self from view chain. Try to mimic MacOS-X behavior ...
+ * We send setNextKeyView: messages to all view for which we are the
+ * next key view, setting their next key view to nil.
+ *
+ * First we do the obvious stuff using the standard methods.
+ */
+ [self setNextKeyView: nil];
+ [[self previousKeyView] setNextKeyView: nil];
+
+ /*
+ * Now, we locate any remaining cases where a view has us as its next
+ * view, and ask the view to change that.
+ */
+ if (pKV(self) != 0)
{
- [_nextKeyView setPreviousKeyView: nil];
+ count = GSIArrayCount(pKV(self));
+ while (count-- > 0)
+ {
+ tmp = GSIArrayItemAtIndex(pKV(self), count).obj;
+ if ([tmp nextKeyView] == self)
+ {
+ [tmp setNextKeyView: nil];
+ }
+ }
}
- if (_previousKeyView != nil)
+
+ /*
+ * Now we clean up the previous view array, in case subclasses have
+ * overridden the default -setNextkeyView: method and broken things.
+ * We also relase the memory we used.
+ */
+ if (pKV(self) != 0)
{
- [_previousKeyView setNextKeyView: nil];
+ count = GSIArrayCount(pKV(self));
+ while (count-- > 0)
+ {
+ tmp = GSIArrayItemAtIndex(pKV(self), count).obj;
+ if (tmp != nil && nKV(tmp) != 0)
+ {
+ unsigned otherCount = GSIArrayCount(nKV(tmp));
+
+ while (otherCount-- > 1)
+ {
+ if (GSIArrayItemAtIndex(nKV(tmp), otherCount).obj == self)
+ {
+ GSIArrayRemoveItemAtIndex(nKV(tmp), otherCount);
+ }
+ }
+ if (GSIArrayItemAtIndex(nKV(tmp), 0).obj == self)
+ {
+ GSIArraySetItemAtIndex(nKV(tmp), (GSIArrayItem)nil, 0);
+ }
+ }
+ }
+ GSIArrayClear(pKV(self));
+ NSZoneFree(NSDefaultMallocZone(), pKV(self));
+ pKV(self) = 0;
}
+
+ /*
+ * Now we clean up all views which have us as their previous view.
+ * We also relase the memory we used.
+ */
+ if (nKV(self) != 0)
+ {
+ count = GSIArrayCount(nKV(self));
+ while (count-- > 0)
+ {
+ tmp = GSIArrayItemAtIndex(nKV(self), count).obj;
+ if (tmp != nil && pKV(tmp) != 0)
+ {
+ unsigned otherCount = GSIArrayCount(pKV(tmp));
+
+ while (otherCount-- > 1)
+ {
+ if (GSIArrayItemAtIndex(pKV(tmp), otherCount).obj == self)
+ {
+ GSIArrayRemoveItemAtIndex(pKV(tmp), otherCount);
+ }
+ }
+ if (GSIArrayItemAtIndex(pKV(tmp), 0).obj == self)
+ {
+ GSIArraySetItemAtIndex(pKV(tmp), (GSIArrayItem)nil, 0);
+ }
+ }
+ }
+ GSIArrayClear(nKV(self));
+ NSZoneFree(NSDefaultMallocZone(), nKV(self));
+ nKV(self) = 0;
+ }
+
RELEASE(_matrixToWindow);
RELEASE(_matrixFromWindow);
RELEASE(_frameMatrix);
@@ -305,7 +408,9 @@ GSSetDragTypes(NSView* obj, NSArray *types)
[super dealloc];
}
-/** Adds aView as a subview of the receiver. */
+/**
+ * Adds aView as a subview of the receiver.
+ */
- (void) addSubview: (NSView*)aView
{
if (aView == nil)
@@ -316,7 +421,7 @@ GSSetDragTypes(NSView* obj, NSArray *types)
if ([self isDescendantOf: aView])
{
[NSException raise: NSInvalidArgumentException
- format: @"addSubview: creates a loop in the views tree!\n"];
+ format: @"addSubview: creates a loop in the views tree!"];
}
RETAIN(aView);
@@ -352,7 +457,8 @@ GSSetDragTypes(NSView* obj, NSArray *types)
if ([self isDescendantOf: aView])
{
[NSException raise: NSInvalidArgumentException
- format: @"addSubview: positioned: relativeTo: creates a loop in the views tree!\n"];
+ format: @"addSubview:positioned:relativeTo: creates a "
+ @"loop in the views tree!"];
}
if (aView == otherView)
@@ -389,11 +495,10 @@ GSSetDragTypes(NSView* obj, NSArray *types)
}
/**
- Returns self
if aView is the receiver or
- aView is a subview of the receiver, the ancestor view
- shared by aView and the receiver, if any,
- aView if it is an ancestor of the receiver, otherwise
- returns nil
. */
+ * Returns self if aView is the receiver or aView is a subview of the receiver,
+ * the ancestor view shared by aView and the receiver if any, or
+ * aView if it is an ancestor of the receiver, otherwise returns nil.
+ */
- (NSView*) ancestorSharedWithView: (NSView*)aView
{
if (self == aView)
@@ -420,8 +525,8 @@ GSSetDragTypes(NSView* obj, NSArray *types)
}
/**
- Returns YES
if aView is an ancestor of the receiver.
-*/
+ * Returns YES if aView is an ancestor of the receiver.
+ */
- (BOOL) isDescendantOf: (NSView*)aView
{
if (aView == self)
@@ -454,8 +559,9 @@ GSSetDragTypes(NSView* obj, NSArray *types)
}
/**
- Removes the receiver from its superviews list of subviews, by
- invoking the superviews [-removeSubview:] method. */
+ * Removes the receiver from its superviews list of subviews, by
+ * invoking the superviews [-removeSubview:] method.
+ */
- (void) removeFromSuperviewWithoutNeedingDisplay
{
if (_super_view != nil)
@@ -526,8 +632,8 @@ GSSetDragTypes(NSView* obj, NSArray *types)
}
/**
- Removes oldView from the receiver and places
- newView in its place. */
+ * Removes oldView from the receiver and places newView in its place.
+ */
- (void) replaceSubview: (NSView*)oldView with: (NSView*)newView
{
if (newView == oldView)
@@ -616,18 +722,18 @@ GSSetDragTypes(NSView* obj, NSArray *types)
}
/**
- Notifies the receiver that its superview is being changed to
- newSuperview. */
+ * Notifies the receiver that its superview is being changed to newSuper.
+ */
- (void) viewWillMoveToSuperview: (NSView*)newSuper
{
_super_view = newSuper;
}
/**
- Notifies the receiver that it will now be a view of newWindow.
- Note, this method is also used when removing a view from a window
- (in which case, newWindow is nil) to let all the subviews know
- that they have also been removed from the window.
+ * Notifies the receiver that it will now be a view of newWindow.
+ * Note, this method is also used when removing a view from a window
+ * (in which case, newWindow is nil ) to let all the subviews know
+ * that they have also been removed from the window.
*/
- (void) viewWillMoveToWindow: (NSWindow*)newWindow
{
@@ -2307,18 +2413,21 @@ static NSView* findByTag(NSView *view, int aTag, unsigned *level)
/*
* Aiding Event Handling
*/
+
/**
- Returns YES
if the view object will accept the first
- click received when in an inactive window, and NO
- otherwise. */
+ * Returns YES if the view object will accept the first
+ * click received when in an inactive window, and NO
+ * otherwise.
+ */
- (BOOL) acceptsFirstMouse: (NSEvent*)theEvent
{
return NO;
}
/**
- Returns the subview, lowest in the receiver's hierarchy, which
- contains aPoint */
+ * Returns the subview, lowest in the receiver's hierarchy, which
+ * contains aPoint, or nil if there is no such view.
+ */
- (NSView*) hitTest: (NSPoint)aPoint
{
NSPoint p;
@@ -2359,8 +2468,8 @@ static NSView* findByTag(NSView *view, int aTag, unsigned *level)
}
/**
- Returns whether or not aPoint lies within aRect
-*/
+ * Returns whether or not aPoint lies within aRect.
+ */
- (BOOL) mouse: (NSPoint)aPoint inRect: (NSRect)aRect
{
return NSMouseInRect (aPoint, aRect, _rFlags.flipped_view);
@@ -2446,114 +2555,274 @@ static NSView* findByTag(NSView *view, int aTag, unsigned *level)
return NO;
}
-- (void) setNextKeyView: (NSView *)aView
-{
- /* As an exception, we do not retain aView, to avoid retain loops
- * (the simplest being a view retaining and being retained by
- * another view), which prevents objects from being ever
- * deallocated. To understand how we manage without retaining
- * _nextKeyView, see [NSView -dealloc]. */
- /* A safety measure against recursion. */
- if (aView == _nextKeyView)
+/**
+ * The effect of the -setNextkeyView: method is to set aView to be the
+ * value returned by subsequent calls to the receivers -nextKeyView method.
+ * This also has the effect of setting the previous key view of aView,
+ * so that subsequent calls to its -previousKeyView method will return
+ * the receiver.
+ *
+ * As a special case, if you pass nil as aView then the -previousKeyView
+ * of the receivers current -nextKeyView is set to nil as well as the
+ * receivers -nextKeyView: being set to nil.
+ * This behavior provides MacOS-X compatibility.
+ *
+ * If you pass a non-view object other than nil, an
+ * NSInternaInconsistencyException is raised.
+ *
+ * NB This method does NOT cause aView to be
+ * retained, and if aView is deallocated, the [NSView-dealloc] method will
+ * automatically remove it from the key view chain it is in.
+ *
+ * For keyboard navigation, views are linked together in a chain, so that
+ * the current first responder view can be changed by stepping backward
+ * and forward in that chain. This is the method for building and modifying
+ * that chain.
+ *
+ * The MacOS-X documentation refers to this chain as a loop, but
+ * the actual implementation is not a loop at all (except as a special case
+ * when you make the chain into a loop). In fact, while each view may have
+ * only zero or one next view, and zero or one previous
+ * view, several views may have their next view set to a single
+ * view and/or their previous views set to a single view. So the
+ * actual setup is a directed graph rather than a loop.
+ *
+ * While a directed graph is a very powerful and flexible way of managing
+ * the way views get keyboard focus in response to tabs etc, it can be
+ * confusing if misused. It is probably best therefore, to set your views
+ * up as a single loop within each window.
+ *
+ *
+ * [a setNextKeyView: b];
+ * [b setNextKeyView: c];
+ * [c setNextKeyView: d];
+ * [d setNextKeyView: a];
+ *
+ */
+- (voidi) setNextKeyView: (NSView *)aView
+{
+ NSView *tmp;
+ unsigned count;
+
+ if (aView != nil && [aView isKindOfClass: viewClass] == NO)
{
+ [NSException raise: NSInternalInconsistencyException
+ format: @"[NSView -setNextKeyView:] passed non-view object %@", aView];
+ }
+
+ if (aView == nil)
+ {
+ if (nKV(self) != 0)
+ {
+ tmp = GSIArrayItemAtIndex(nKV(self), 0).obj;
+ if (tmp != nil)
+ {
+ /*
+ * Remove all reference to self from our next key view.
+ */
+ if (pKV(tmp) != 0)
+ {
+ count = GSIArrayCount(pKV(tmp));
+ while (count-- > 1)
+ {
+ if (GSIArrayItemAtIndex(pKV(tmp), count).obj == self)
+ {
+ GSIArrayRemoveItemAtIndex(pKV(tmp), count);
+ }
+ }
+ if (GSIArrayItemAtIndex(pKV(tmp), 0).obj == self)
+ {
+ GSIArraySetItemAtIndex(pKV(tmp), (GSIArrayItem)nil, 0);
+ }
+ }
+ /*
+ * Clear link to the next key view.
+ */
+ GSIArraySetItemAtIndex(nKV(self), (GSIArrayItem)nil, 0);
+ }
+ }
return;
}
- if (aView == nil || [aView isKindOfClass: viewClass])
+ if (nKV(self) == 0)
{
- if (_nextKeyView != nil)
- {
- NSView *oldNextKeyView = _nextKeyView;
-
- /* Discard the pointer to us in the old next key view. To
- * prevent [oldNextKeyView setPreviousKeyView: nil] from
- * calling us recursively, set _nextKeyView to nil for
- * the time of the call. */
- _nextKeyView = nil;
-
- if ([oldNextKeyView previousKeyView] != nil)
- {
- [oldNextKeyView setPreviousKeyView: nil];
- }
- }
-
- /* Assign the _nextKeyView first to prevent recursion. */
- _nextKeyView = aView;
-
- if (aView != nil)
+ /*
+ * Create array and ensure that it has a nil item at index 0 ...
+ * so we always have room for the pointer to the next view.
+ */
+ nKV(self) = NSZoneMalloc(NSDefaultMallocZone(), sizeof(GSIArray_t));
+ GSIArrayInitWithZoneAndCapacity(nKV(self), NSDefaultMallocZone(), 1);
+ GSIArrayAddItem(nKV(self), (GSIArrayItem)nil);
+ }
+ else
+ {
+ /* A safety measure against recursion. */
+ tmp = GSIArrayItemAtIndex(nKV(self), 0).obj;
+ if (tmp == aView)
{
- if ([aView previousKeyView] != self)
- {
- [aView setPreviousKeyView: self];
- }
+ return;
}
}
+
+ if (pKV(aView) == 0)
+ {
+ /*
+ * Create array and ensure that it has a nil item at index 0 ...
+ * so we always have room for the pointer to the previous view.
+ */
+ pKV(aView) = NSZoneMalloc(NSDefaultMallocZone(), sizeof(GSIArray_t));
+ GSIArrayInitWithZoneAndCapacity(pKV(aView), NSDefaultMallocZone(), 1);
+ GSIArrayAddItem(pKV(aView), (GSIArrayItem)nil);
+ }
+
+ /*
+ * Tell the old previous view of aView that aView no longer points to it.
+ */
+ tmp = GSIArrayItemAtIndex(pKV(aView), 0).obj;
+ if (tmp != nil)
+ {
+ count = GSIArrayCount(nKV(tmp));
+ while (count-- > 1)
+ {
+ if (GSIArrayItemAtIndex(nKV(tmp), count).obj == aView)
+ {
+ GSIArrayRemoveItemAtIndex(nKV(tmp), count);
+ }
+ }
+ /*
+ * If the view still points to aView, make a note of it in the
+ * 'previous' array of aView while making space for the new link.
+ */
+ if (GSIArrayItemAtIndex(nKV(tmp), 0).obj == aView)
+ {
+ GSIArrayInsertItem(pKV(aView), (GSIArrayItem)nil, 0);
+ }
+ }
+
+ /*
+ * Set up 'previous' link in aView to point to us.
+ */
+ GSIArraySetItemAtIndex(pKV(aView), (GSIArrayItem)self, 0);
+
+ /*
+ * Tell our current 'next' view that we are no longer pointing to it.
+ */
+ tmp = GSIArrayItemAtIndex(nKV(self), 0).obj;
+ if (tmp != nil)
+ {
+ count = GSIArrayCount(pKV(tmp));
+ while (count-- > 1)
+ {
+ if (GSIArrayItemAtIndex(pKV(tmp), count).obj == self)
+ {
+ GSIArrayRemoveItemAtIndex(pKV(tmp), count);
+ }
+ }
+ /*
+ * If the view still points to us, make a note of it in the
+ * 'next' array while making space for the new link to aView.
+ */
+ if (GSIArrayItemAtIndex(pKV(tmp), 0).obj == self)
+ {
+ GSIArrayInsertItem(nKV(self), (GSIArrayItem)nil, 0);
+ }
+ }
+
+ /*
+ * Set up 'next' link to point to aView.
+ */
+ GSIArraySetItemAtIndex(nKV(self), (GSIArrayItem)aView, 0);
}
+
+/**
+ * Returns the next view after the receiver in the key view chain.
+ * Returns nil if there is no view after the receiver.
+ * The next view is set up using the -setNextKeyView: method.
+ * The key view chain is used to determine the order in which views become
+ * first responder when using keyboard navigation.
+ */
- (NSView *) nextKeyView
{
- return _nextKeyView;
+ if (nKV(self) == 0)
+ {
+ return nil;
+ }
+ return GSIArrayItemAtIndex(nKV(self), 0).obj;
}
+
+/**
+ * Returns the first available view after the receiver which is
+ * actually able to become first responder. See -nextKeyView and
+ * [NSResponder-acceptsFirstResponder]
+ */
- (NSView *) nextValidKeyView
{
NSView *theView;
- theView = _nextKeyView;
+ theView = [self nextKeyView];
while (1)
{
if ([theView acceptsFirstResponder] || (theView == nil)
- || (theView == self))
- return theView;
-
+ || (theView == self))
+ {
+ return theView;
+ }
theView = [theView nextKeyView];
}
}
+
+/**
+ * GNUstep addition ... a conveninece method to insert a view in the
+ * key view chain before the receiver, using the -previousKeyView and
+ * -setNextKeyView: methods.
+ */
- (void) setPreviousKeyView: (NSView *)aView
{
- if (aView == _previousKeyView)
+ NSView *p = [self previousKeyView];
+
+ if (aView == p || aView == self)
{
return;
}
-
- if (aView == nil || [aView isKindOfClass: viewClass])
- {
- if (_previousKeyView != nil)
- {
- NSView *oldPreviousKeyView = _previousKeyView;
-
- _previousKeyView = nil;
- if ([oldPreviousKeyView nextKeyView] != nil)
- {
- [oldPreviousKeyView setNextKeyView: nil];
- }
- }
-
- _previousKeyView = aView;
-
- if (aView != nil)
- {
- if ([aView nextKeyView] != self)
- {
- [aView setNextKeyView: self];
- }
- }
- }
+ [p setNextKeyView: aView];
+ [aView setNextKeyView: self];
}
+
+/**
+ * Returns the view before the receiver in the key view chain.
+ * Returns nil if there is no view before the receiver in the chain.
+ * The previous view of the receiver was set up by passing it as the
+ * argument to a call of -setNextKeyView: on that view.
+ * The key view chain is used to determine the order in which views become
+ * first responder when using keyboard navigation.
+ */
- (NSView *) previousKeyView
{
- return _previousKeyView;
+ if (pKV(self) == 0)
+ {
+ return nil;
+ }
+ return GSIArrayItemAtIndex(pKV(self), 0).obj;
}
+
+/**
+ * Returns the first available view before the receiver which is
+ * actually able to become first responder. See -nextKeyView and
+ * [NSResponder-acceptsFirstResponder]
+ */
- (NSView *) previousValidKeyView
{
NSView *theView;
- theView = _previousKeyView;
+ theView = [self previousKeyView];
while (1)
{
if ([theView acceptsFirstResponder] || (theView == nil)
- || (theView == self))
- return theView;
-
+ || (theView == self))
+ {
+ return theView;
+ }
theView = [theView previousKeyView];
}
}
@@ -3278,7 +3547,8 @@ static NSView* findByTag(NSView *view, int aTag, unsigned *level)
at: &_is_rotated_or_scaled_from_base];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_post_frame_changes];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_autoresizes_subviews];
- [aDecoder decodeValueOfObjCType: @encode(unsigned int) at: &_autoresizingMask];
+ [aDecoder decodeValueOfObjCType: @encode(unsigned int)
+ at: &_autoresizingMask];
_nextKeyView = [aDecoder decodeObject];
_previousKeyView = [aDecoder decodeObject];