Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for Full Keyboard Access for checkboxes, radios and popups #1697

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions AppKit/AppKit.j
Expand Up @@ -68,6 +68,7 @@
@import "CPImageView.j"
@import "CPKeyBinding.j"
@import "CPLevelIndicator.j"
@import "CPMatrix.j"
@import "CPMenu.j"
@import "CPMenuItem.j"
@import "CPOpenPanel.j"
Expand Down
7 changes: 7 additions & 0 deletions AppKit/CPApplication.j
Expand Up @@ -109,6 +109,8 @@ CPRunContinuesResponse = -1002;
CPPanel _aboutPanel;

CPThemeBlend _themeBlend @accessors(property=themeBlend);

BOOL _fullKeyboardAccess @accessors(property=fullKeyboardAccess);
}

/*!
Expand Down Expand Up @@ -1169,6 +1171,11 @@ CPRunContinuesResponse = -1002;
userInfo:nil];
}

- (BOOL)fullKeyboardAccess
{
return _fullKeyboardAccess !== nil ? _fullKeyboardAccess : [[[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPFullKeyboardAccess"] uppercaseString] === @"YES";
}

+ (CPString)defaultThemeName
{
return ([[CPBundle mainBundle] objectForInfoDictionaryKey:"CPDefaultTheme"] || @"Aristo");
Expand Down
43 changes: 43 additions & 0 deletions AppKit/CPControl.j
Expand Up @@ -189,6 +189,49 @@ var CPControlBlackColor = [CPColor blackColor];
return self;
}

- (BOOL)acceptsFirstResponder
{
return [CPApp fullKeyboardAccess];
}

- (BOOL)becomeFirstResponder
{
[self setThemeState:CPThemeStateFocused];
[self scrollRectToVisible:[self bounds]];
[self setNeedsLayout];
return YES;
}

- (BOOL)resignFirstResponder
{
[self unsetThemeState:CPThemeStateFocused];
[self setNeedsLayout];
return YES;
}

- (void)keyDown:(CPEvent)anEvent
{
if ([anEvent keyCode] === CPSpaceKeyCode)
{
[self setThemeState:CPThemeStateHighlighted];
[self setNeedsLayout];
}
else
[super keyUp:anEvent];
}

- (void)keyUp:(CPEvent)anEvent
{
if ([anEvent keyCode] === CPSpaceKeyCode)
{
[self unsetThemeState:CPThemeStateHighlighted];
[self setNeedsLayout];
[self performClick:nil];
}
else
[super keyUp:anEvent];
}

/*!
Sets the receiver's target action.

Expand Down
129 changes: 129 additions & 0 deletions AppKit/CPMatrix.j
@@ -0,0 +1,129 @@
/*
* CPMatrix.j
* AppKit
*
* Created by Martin Carlberg.
* Copyright 2012, Martin Carlberg.
*
* 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.1 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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

@import <Foundation/CPObject.j>

@import "CPControl.j"


/*!
@ingroup appkit

This class only implements a very simple Full Keyboard Access for
radio buttons. All other radio group functionality is implemented
in the CPRadioGroup class
*/

@implementation CPMatrix : CPControl
{
}

- (BOOL)becomeFirstResponder
{
[self setupNextAndPreviousKeyView];
var becomeFirst = [super becomeFirstResponder];

if (becomeFirst)
{
var subviews = [self subviews];

if ([subviews count])
{
var firstCell = [subviews objectAtIndex:0],
radioGroup = [firstCell radioGroup];

if (radioGroup)
{
firstCell = [radioGroup selectedRadio];
}
window.setTimeout(function()
{
[[firstCell window] makeFirstResponder:firstCell];
}, 0);
}
}

return becomeFirst;
}

- (void)setupNextAndPreviousKeyView
{
var subviews = [self subviews],
subviewSize = [subviews count];

for (var i = 0; i < subviewSize; i++)
{
var subview = [subviews objectAtIndex:i];
subview._nextKeyView = [self nextKeyView];
subview._previousKeyView = [self previousKeyView];
}
}

- (void)selectNextCell:(CPView)currentCell
{
var cells = [self subviews],
indexOfCell = [cells indexOfObject:currentCell];

if (++indexOfCell === [cells count])
indexOfCell = 0;

[[self window] makeFirstResponder:[cells objectAtIndex:indexOfCell]];
}

- (void)selectPreviousCell:(CPView)currentCell
{
var cells = [self subviews],
indexOfCell = [cells indexOfObject:currentCell];

if (!indexOfCell)
indexOfCell = [cells count];

[[self window] makeFirstResponder:[cells objectAtIndex:--indexOfCell]];
}

- (CPRadio)selectedRadio
{
var cells = [self subviews];
return [cells count] ? [[[cells objectAtIndex:0] radioGroup] selectedRadio] : nil;
}

/*!
An CPMatrix object adds to the target-action paradigm implemented by its subviews by maintaining its own target and action in addition to the targets and actions of its subviews. A matrix's target and action are used if one of its subviews doesn't have a target or action set. This design allows for common usage patterns, including the following:

If none of the subviews of the CPMatrix object has either target or action set, the target and action of the CPMatrix object is always used.
If only the actions of each of the subviews is set, they share the target specified by their CPMatrix object, but send different messages to it.
If only the targets of each of the subviews is set, they all send the action message specified by the NSMatrix object, but to different targets.

This is the exact same behaviour as Cocoa has on Mac OS X.
*/
- (void)sendAction:(SEL)theAction to:(id)theTarget
{
if (theAction)
if (theTarget)
[super sendAction:theAction to:theTarget];
else
[super sendAction:theAction to:_target];
else
[super sendAction:_action to:_target];
}

@end
22 changes: 22 additions & 0 deletions AppKit/CPPopUpButton.j
Expand Up @@ -663,6 +663,28 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
// [super observeValueForKeyPath:aKeyPath ofObject:anObject change:changes context:aContext];
}

- (void)keyUp:(CPEvent)anEvent
{
if ([anEvent keyCode] === CPSpaceKeyCode)
{
var bounds = [self bounds];

CPApp._currentEvent = [CPEvent mouseEventWithType:CPLeftMouseDown
location:[self convertPointToBase:CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds))]
modifierFlags:[anEvent modifierFlags]
timestamp:[anEvent timestamp]
windowNumber:[self window]
context:nil
eventNumber:0
clickCount:1
pressure:0];
[self mouseDown:CPApp._currentEvent];
}
else
[super keyUp:anEvent];
}


- (void)mouseDown:(CPEvent)anEvent
{
if (![self isEnabled] || ![self numberOfItems])
Expand Down
56 changes: 55 additions & 1 deletion AppKit/CPRadio.j
Expand Up @@ -152,12 +152,66 @@ CPRadioImageOffset = 4.0;

- (void)sendAction:(SEL)anAction to:(id)anObject
{
[super sendAction:anAction to:anObject];
var superview = [self superview];
if ([superview isKindOfClass:[CPMatrix class]])
[superview sendAction:anAction to:anObject];
else
[super sendAction:anAction to:anObject];

if (_radioGroup)
[CPApp sendAction:[_radioGroup action] to:[_radioGroup target] from:_radioGroup];
}

- (void)keyUp:(CPEvent)anEvent
{
switch ([anEvent keyCode])
{
case CPDownArrowKeyCode:
case CPUpArrowKeyCode:
break;

default:
[super keyUp:anEvent];
}
}

- (void)keyUp:(CPEvent)anEvent
{
switch ([anEvent keyCode])
{
case CPDownArrowKeyCode:
[self selectNextRadio];
break;

case CPUpArrowKeyCode:
[self selectPreviousRadio];
break;

default:
[super keyUp:anEvent];
}
}

- (void)selectNextRadio
{
var superview = [self superview];

if (superview && [superview isKindOfClass:[CPMatrix class]])
{
[superview selectNextCell:self];
}
}

- (void)selectPreviousRadio
{
var superview = [self superview];

if (superview && [superview isKindOfClass:[CPMatrix class]])
{
[superview selectPreviousCell:self];
}
}

@end

var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
Expand Down
1 change: 1 addition & 0 deletions AppKit/CPTheme.j
Expand Up @@ -421,6 +421,7 @@ CPThemeStateCircular = CPThemeState("circular");
CPThemeStateAutocompleting = CPThemeState("autocompleting");
CPThemeStateMainWindow = CPThemeState("mainWindow");
CPThemeStateKeyWindow = CPThemeState("keyWindow");
CPThemeStateFocused = CPThemeState("focused");

@implementation _CPThemeAttribute : CPObject
{
Expand Down
7 changes: 3 additions & 4 deletions Tools/nib2cib/NSMatrix.j
Expand Up @@ -21,7 +21,7 @@
*/

@import <Foundation/CPObject.j>
@import <AppKit/CPView.j>
@import <AppKit/CPMatrix.j>

@import "NSView.j"

Expand All @@ -30,7 +30,7 @@ var NSMatrixRadioModeMask = 0x40000000,
NSMatrixDrawsBackgroundMask = 0x01000000;


@implementation NSMatrix : CPView
@implementation NSMatrix : CPMatrix

- (id)initWithCoder:(CPCoder)aCoder
{
Expand Down Expand Up @@ -81,8 +81,7 @@ var NSMatrixRadioModeMask = 0x40000000,
if (drawsBackground)
[self setBackgroundColor:backgroundColor];

self.isa = [CPView class];
NIB_CONNECTION_EQUIVALENCY_TABLE[[self UID]] = radioGroup;
self.isa = [CPMatrix class];
}
else
{
Expand Down