From 4bc8763f12b5813166065ac65029aeac526dec9f Mon Sep 17 00:00:00 2001 From: wlux Date: Thu, 4 Oct 2012 09:20:08 +0000 Subject: [PATCH] Improve keyboard navigation by automatically computing a key view loop for a window (and tab view item). This implicit key view is created when a window is made key and does not have an explicit key view loop, which is detected by checking the initial first responder of the window. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@35632 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 23 +++++++++++ Headers/AppKit/NSTabView.h | 1 + Source/NSControl.m | 12 ++++++ Source/NSTabView.m | 85 +++++++++++++++++++++++++++++++++++++- Source/NSView.m | 60 +++++++++++++++++++++++++++ Source/NSViewPrivate.h | 37 +++++++++++++++++ Source/NSWindow.m | 15 +++++-- 7 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 Source/NSViewPrivate.h diff --git a/ChangeLog b/ChangeLog index 9179b2d08..9869fa9b1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,26 @@ +2012-10-04 Wolfgang Lux + + Improve keyboard navigation by automatically computing a key view + loop for a window (and tab view item). This implicit key view is + created when a window is made key and does not have an explicit + key view loop, which is detected by checking the initial first + responder of the window. + + * Source/NSViewPrivate.h: New header declaring auxiliary key view + loop methods. + + * Source/NSWindow.m (-becomeKeyWindow, -recalculateKeyViewLoop): + * Source/NSView.m (-_setUpKeyViewLooopWithNextKeyView:, + -_recursiveSetUpKeyViewLoopWithNextKeyView:, -_viewWillMoveToWindow:, + cmpFrame()): + * Source/NSControl.m (-_setUpKeyViewLoopWithNextKeyView:): + * Source/NSTabView.m (-dealloc, -selectTabViewItem:, -setNextKeyView:, + _setUpKeyViewLoopWithNextKeyView:): Implement code to + automatically recalculate the key view loop of a window. + + * Headers/AppKit/NSTabView.h: Add new member to maintain the + original next key view of a tab view. + 2012-10-04 Wolfgang Lux * Source/NSClipView.m (-setDocumentView:, -setNextKeyView:): diff --git a/Headers/AppKit/NSTabView.h b/Headers/AppKit/NSTabView.h index 17b3fb7c8..7a5903b6d 100644 --- a/Headers/AppKit/NSTabView.h +++ b/Headers/AppKit/NSTabView.h @@ -55,6 +55,7 @@ typedef enum { BOOL _truncated_label; id _delegate; NSUInteger _selected_item; + NSView *_original_nextKeyView; } - (void)addTabViewItem:(NSTabViewItem *)tabViewItem; - (void)insertTabViewItem:(NSTabViewItem *)tabViewItem diff --git a/Source/NSControl.m b/Source/NSControl.m index ba61e928f..82158c075 100644 --- a/Source/NSControl.m +++ b/Source/NSControl.m @@ -49,6 +49,7 @@ #import "AppKit/NSTextView.h" #import "AppKit/NSWindow.h" #import "GSBindingHelpers.h" +#import "NSViewPrivate.h" /* * Class variables @@ -1120,3 +1121,14 @@ static NSNotificationCenter *nc; } @end + +@implementation NSControl(KeyViewLoop) + +- (void) _setUpKeyViewLoopWithNextKeyView: (NSView *)nextKeyView +{ + // Controls are expected to have no subviews + //NSLog(@"%@@%p -_setUpKeyViewLoopWithKeyKeyView:%@@%p", [self class], self, [nextKeyView class], nextKeyView); + [self setNextKeyView: nextKeyView]; +} + +@end diff --git a/Source/NSTabView.m b/Source/NSTabView.m index 6f0c57e28..a424fe8cf 100644 --- a/Source/NSTabView.m +++ b/Source/NSTabView.m @@ -42,6 +42,12 @@ #import "AppKit/NSWindow.h" #import "GNUstepGUI/GSTheme.h" #import "GSBindingHelpers.h" +#import "NSViewPrivate.h" + +@interface NSTabViewItem (KeyViewLoop) +- (void) _setUpKeyViewLoopWithNextKeyView: (NSView *)nextKeyView; +- (NSView *) _lastKeyView; +@end @implementation NSTabView @@ -82,6 +88,9 @@ { RELEASE(_items); RELEASE(_font); + // Reset the _selected attribute to prevent crash when -dealloc calls + // -setNextKeyView: + _selected = nil; [super dealloc]; } @@ -262,11 +271,23 @@ if (selectedView != nil) { + NSView *firstResponder; + [self addSubview: selectedView]; // FIXME: We should not change this mask - [selectedView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable]; + [selectedView setAutoresizingMask: + NSViewWidthSizable | NSViewHeightSizable]; [selectedView setFrame: [self contentRect]]; - [_window makeFirstResponder: [_selected initialFirstResponder]]; + firstResponder = [_selected initialFirstResponder]; + if (firstResponder == nil) + { + firstResponder = [_selected view]; + [_selected setInitialFirstResponder: firstResponder]; + [firstResponder _setUpKeyViewLoopWithNextKeyView: + _original_nextKeyView]; + } + [self setNextKeyView: firstResponder]; + [_window makeFirstResponder: firstResponder]; } /* Will need to redraw tabs and content area. */ @@ -691,3 +712,63 @@ } } @end + +@implementation NSTabViewItem (KeyViewLoop) + +- (void) _setUpKeyViewLoopWithNextKeyView: (NSView *)nextKeyView +{ + [self setInitialFirstResponder: [self view]]; + [[self view] _setUpKeyViewLoopWithNextKeyView: nextKeyView]; +} + +- (NSView *) _lastKeyView +{ + NSView *keyView = [self initialFirstResponder]; + NSView *itemView = [self view]; + NSView *lastKeyView = nil; + NSMutableArray *views = // cycle protection + [[NSMutableArray alloc] initWithCapacity: 1 + [[itemView subviews] count]]; + + if (keyView == nil && itemView != nil) + { + [self _setUpKeyViewLoopWithNextKeyView: itemView]; + } + while ([keyView isDescendantOf: itemView] && ![views containsObject: keyView]) + { + [views addObject: keyView]; + lastKeyView = keyView; + keyView = [keyView nextKeyView]; + } + [views release]; + return lastKeyView; +} + +@end + +@implementation NSTabView (KeyViewLoop) + +- (void) _setUpKeyViewLoopWithNextKeyView: (NSView *)nextKeyView +{ + [_items makeObjectsPerform: @selector(_setUpKeyViewLoopWithNextKeyView:) + withObject: nextKeyView]; + if (_selected) + { + [super setNextKeyView: [_selected initialFirstResponder]]; + } + [self setNextKeyView: nextKeyView]; +} + +- (void) setNextKeyView: (NSView *)nextKeyView +{ + _original_nextKeyView = nextKeyView; + if (_selected) + { + [[_selected _lastKeyView] setNextKeyView: nextKeyView]; + } + else + { + [super setNextKeyView: nextKeyView]; + } +} + +@end diff --git a/Source/NSView.m b/Source/NSView.m index 64b8031e0..dda2eb8f5 100644 --- a/Source/NSView.m +++ b/Source/NSView.m @@ -77,6 +77,7 @@ #import "GSToolTips.h" #import "GSBindingHelpers.h" #import "GSGuiPrivate.h" +#import "NSViewPrivate.h" /* * We need a fast array that can store objects without retain/release ... @@ -422,10 +423,18 @@ GSSetDragTypes(NSView* obj, NSArray *types) if (_window != nil) { [GSDisplayServer removeDragTypes: t fromWindow: _window]; + if ([_window autorecalculatesKeyViewLoop]) + { + [_window recalculateKeyViewLoop]; + } } if (newWindow != nil) { [GSDisplayServer addDragTypes: t toWindow: newWindow]; + if ([newWindow autorecalculatesKeyViewLoop]) + { + [newWindow recalculateKeyViewLoop]; + } } } @@ -5057,3 +5066,54 @@ static NSView* findByTag(NSView *view, int aTag, unsigned *level) @end +@implementation NSView(KeyViewLoop) + +static int +cmpFrame(id view1, id view2, void *context) +{ + BOOL flippedSuperView = [(NSView *)context isFlipped]; + NSRect frame1 = [view1 frame]; + NSRect frame2 = [view2 frame]; + + if (NSMinY(frame1) < NSMinY(frame2)) + return flippedSuperView ? NSOrderedAscending : NSOrderedDescending; + if (NSMaxY(frame1) > NSMaxY(frame2)) + return flippedSuperView ? NSOrderedDescending : NSOrderedAscending; + + // FIXME Should use NSMaxX in a Hebrew or Arabic locale + if (NSMinX(frame1) < NSMinX(frame2)) + return NSOrderedAscending; + if (NSMinX(frame1) > NSMinX(frame2)) + return NSOrderedDescending; + return NSOrderedSame; +} + +- (void) _setUpKeyViewLoopWithNextKeyView: (NSView *)nextKeyView +{ + if (_rFlags.has_subviews) + { + [self _recursiveSetUpKeyViewLoopWithNextKeyView: nextKeyView]; + } + else + { + [self setNextKeyView: nextKeyView]; + } +} + +- (void) _recursiveSetUpKeyViewLoopWithNextKeyView: (NSView *)nextKeyView +{ + NSArray *sortedViews; + NSView *aView; + NSEnumerator *e; + + sortedViews = [_sub_views sortedArrayUsingFunction: cmpFrame context: self]; + e = [sortedViews reverseObjectEnumerator]; + while ((aView = [e nextObject]) != nil) + { + [aView _setUpKeyViewLoopWithNextKeyView: nextKeyView]; + nextKeyView = aView; + } + [self setNextKeyView: nextKeyView]; +} + +@end diff --git a/Source/NSViewPrivate.h b/Source/NSViewPrivate.h new file mode 100644 index 000000000..417dabd69 --- /dev/null +++ b/Source/NSViewPrivate.h @@ -0,0 +1,37 @@ +/* + NSViewPrivate.h + + The private methods of the NSView classes + + Copyright (C) 2010 Free Software Foundation, Inc. + + 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 Lesser General Public + License as published by the Free Software Foundation; either + version 2 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 + Lesser General Public License for more details. + + 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 or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _GNUstep_H_NSViewPrivate +#define _GNUstep_H_NSViewPrivate + +#import "AppKit/NSView.h" + +@interface NSView (KeyViewLoop) +- (void) _setUpKeyViewLoopWithNextKeyView: (NSView *)nextKeyView; +- (void) _recursiveSetUpKeyViewLoopWithNextKeyView: (NSView *)nextKeyView; +@end + +#endif // _GNUstep_H_NSViewPrivate diff --git a/Source/NSWindow.m b/Source/NSWindow.m index 01911c8e0..39741895b 100644 --- a/Source/NSWindow.m +++ b/Source/NSWindow.m @@ -89,6 +89,7 @@ #import "GSToolTips.h" #import "GSIconManager.h" #import "NSToolbarFrameworkPrivate.h" +#import "NSViewPrivate.h" #define GSI_ARRAY_TYPES 0 #define GSI_ARRAY_TYPE NSWindow * @@ -1551,6 +1552,10 @@ titleWithRepresentedFilename(NSString *representedFilename) if ((!_firstResponder) || (_firstResponder == self)) { + if (!_initialFirstResponder) + { + [self recalculateKeyViewLoop]; + } if (_initialFirstResponder) { [self makeFirstResponder: _initialFirstResponder]; @@ -4367,7 +4372,7 @@ resetCursorRectsForView(NSView *theView) _f.selectionDirection = NSSelectingNext; [(id)theView selectText: self]; _f.selectionDirection = NSDirectSelection; - } + } } } @@ -4464,7 +4469,7 @@ resetCursorRectsForView(NSView *theView) { return; } - if ([theView respondsToSelector:@selector(selectText:)]) + if ([theView respondsToSelector:@selector(selectText:)]) { _f.selectionDirection = NSSelectingPrevious; [(id)theView selectText: self]; @@ -4504,8 +4509,10 @@ current key view.
- (void) recalculateKeyViewLoop { -// FIXME -// Should be called from NSView viewWillMoveToWindow +// Should be called from NSView viewWillMoveToWindow (but only if +// -autorecalculatesKeyViewLoop returns YES) + [_contentView _setUpKeyViewLoopWithNextKeyView: _contentView]; + [self setInitialFirstResponder: [_contentView nextValidKeyView]]; }