Import the Voltaire (commit 152775fc) project to replace IMK candidate UI.

This commit is contained in:
Lukhnos D. Liu 2012-03-28 23:25:12 -07:00
parent adf6713aa7
commit 7d5cd8173c
12 changed files with 1466 additions and 0 deletions

View File

@ -0,0 +1,67 @@
//
// VTCandidateController.h
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <Cocoa/Cocoa.h>
@class VTCandidateController;
@protocol VTCandidateControllerDelegate
- (NSUInteger)candidateCountForController:(VTCandidateController *)controller;
- (NSString *)candidateController:(VTCandidateController *)controller candidateAtIndex:(NSUInteger)index;
- (void)candidateController:(VTCandidateController *)controller didSelectCandidateAtIndex:(NSUInteger)index;
@end
@interface VTCandidateController : NSWindowController
{
@protected
__weak id<VTCandidateControllerDelegate> _delegate;
NSArray *_keyLabels;
NSFont *_keyLabelFont;
NSFont *_candidateFont;
NSFont *_CJKCandidateFont;
}
- (void)reloadData;
- (BOOL)showNextPage;
- (BOOL)showPreviousPage;
- (BOOL)highlightNextCandidate;
- (BOOL)highlightPreviousCandidate;
- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index;
@property (assign, weak, nonatomic) id<VTCandidateControllerDelegate> delegate;
@property (assign, nonatomic) NSUInteger selectedCandidateIndex;
@property (assign, nonatomic) BOOL visible;
@property (assign, nonatomic) NSPoint windowTopLeftPoint;
@property (copy, nonatomic) NSArray *keyLabels;
@property (copy, nonatomic) NSFont *keyLabelFont;
@property (copy, nonatomic) NSFont *candidateFont;
@property (copy, nonatomic) NSFont *CJKCandidateFont;
@end

View File

@ -0,0 +1,124 @@
//
// VTCandidateController.m
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTCandidateController.h"
@implementation VTCandidateController
@synthesize delegate = _delegate;
@synthesize keyLabels = _keyLabels;
@synthesize keyLabelFont = _keyLabelFont;
@synthesize candidateFont = _candidateFont;
@synthesize CJKCandidateFont = CJKCandidateFont;
- (void)dealloc
{
[_keyLabels release];
[_keyLabelFont release];
[_candidateFont release];
[_CJKCandidateFont release];
[super dealloc];
}
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if (self) {
// populate the default values
_keyLabels = [[NSArray arrayWithObjects:@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", nil] retain];
_keyLabelFont = [[NSFont systemFontOfSize:14.0] retain];
_candidateFont = [[NSFont systemFontOfSize:18.0] retain];
_CJKCandidateFont = [_candidateFont retain];
}
return self;
}
- (void)reloadData
{
}
- (BOOL)showNextPage
{
return NO;
}
- (BOOL)showPreviousPage
{
return NO;
}
- (BOOL)highlightNextCandidate
{
return NO;
}
- (BOOL)highlightPreviousCandidate
{
return NO;
}
- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index
{
return NSUIntegerMax;
}
- (BOOL)visible
{
return [[self window] isVisible];
}
- (void)setVisible:(BOOL)visible
{
if (visible) {
[[self window] orderFront:self];
}
else {
[[self window] orderOut:self];
}
}
- (NSPoint)windowTopLeftPoint
{
NSRect frameRect = [[self window] frame];
return NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height);
}
- (void)setWindowTopLeftPoint:(NSPoint)windowTopLeftPoint
{
[[self window] setFrameTopLeftPoint:windowTopLeftPoint];
}
- (NSUInteger)selectedCandidateIndex
{
return NSUIntegerMax;
}
- (void)setSelectedCandidateIndex:(NSUInteger)newIndex
{
}
@end

View File

@ -0,0 +1,40 @@
//
// VTHorizontalCandidateController.h
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTCandidateController.h"
@class VTHorizontalCandidateView;
@interface VTHorizontalCandidateController : VTCandidateController
{
@protected
VTHorizontalCandidateView *_candidateView;
NSButton *_prevPageButton;
NSButton *_nextPageButton;
NSUInteger _currentPage;
}
@end

View File

@ -0,0 +1,244 @@
//
// VTHorizontalCandidateController.m
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTHorizontalCandidateController.h"
#import "VTHorizontalCandidateView.h"
@interface VTHorizontalCandidateController (Private)
- (NSUInteger)pageCount;
- (void)layoutCandidateView;
- (void)pageButtonAction:(id)sender;
- (void)candidateViewMouseDidClick:(id)sender;
@end
@implementation VTHorizontalCandidateController
- (void)dealloc
{
[_candidateView release];
[_prevPageButton release];
[_nextPageButton release];
[super dealloc];
}
- (id)init
{
NSRect contentRect = NSMakeRect(128.0, 128.0, 0.0, 0.0);
NSUInteger styleMask = NSBorderlessWindowMask | NSNonactivatingPanelMask;
NSPanel *panel = [[[NSPanel alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO] autorelease];
[panel setLevel:CGShieldingWindowLevel() + 1];
[panel setHasShadow:YES];
self = [self initWithWindow:panel];
if (self) {
contentRect.origin = NSMakePoint(0.0, 0.0);
_candidateView = [[VTHorizontalCandidateView alloc] initWithFrame:contentRect];
_candidateView.target = self;
_candidateView.action = @selector(candidateViewMouseDidClick:);
[[panel contentView] addSubview:_candidateView];
contentRect.size = NSMakeSize(36.0, 20.0);
_nextPageButton = [[NSButton alloc] initWithFrame:contentRect];
_prevPageButton = [[NSButton alloc] initWithFrame:contentRect];
[_nextPageButton setButtonType:NSMomentaryLightButton];
[_nextPageButton setBezelStyle:NSSmallSquareBezelStyle];
[_nextPageButton setTitle:@"»"];
[_nextPageButton setTarget:self];
[_nextPageButton setAction:@selector(pageButtonAction:)];
[_prevPageButton setButtonType:NSMomentaryLightButton];
[_prevPageButton setBezelStyle:NSSmallSquareBezelStyle];
[_prevPageButton setTitle:@"«"];
[_prevPageButton setTarget:self];
[_prevPageButton setAction:@selector(pageButtonAction:)];
[[panel contentView] addSubview:_nextPageButton];
[[panel contentView] addSubview:_prevPageButton];
}
return self;
}
- (void)reloadData
{
_candidateView.highlightedIndex = 0;
_currentPage = 0;
[self layoutCandidateView];
}
- (BOOL)showNextPage
{
if (_currentPage + 1 >= [self pageCount]) {
return NO;
}
_currentPage++;
_candidateView.highlightedIndex = 0;
[self layoutCandidateView];
return YES;
}
- (BOOL)showPreviousPage
{
if (_currentPage == 0) {
return NO;
}
_currentPage--;
_candidateView.highlightedIndex = 0;
[self layoutCandidateView];
return YES;
}
- (BOOL)highlightNextCandidate
{
NSUInteger currentIndex = self.selectedCandidateIndex;
if (currentIndex + 1 >= [_delegate candidateCountForController:self]) {
return NO;
}
self.selectedCandidateIndex = currentIndex + 1;
return YES;
}
- (BOOL)highlightPreviousCandidate
{
NSUInteger currentIndex = self.selectedCandidateIndex;
if (currentIndex == 0) {
return NO;
}
self.selectedCandidateIndex = currentIndex - 1;
return YES;
}
- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index
{
NSUInteger result = _currentPage * [_keyLabels count] + index;
return result < [_delegate candidateCountForController:self] ? result : NSUIntegerMax;
}
- (NSUInteger)selectedCandidateIndex
{
return _currentPage * [_keyLabels count] + _candidateView.highlightedIndex;
}
- (void)setSelectedCandidateIndex:(NSUInteger)newIndex
{
NSUInteger keyLabelCount = [_keyLabels count];
if (newIndex < [_delegate candidateCountForController:self]) {
_currentPage = newIndex / keyLabelCount;
_candidateView.highlightedIndex = newIndex % keyLabelCount;
[self layoutCandidateView];
}
}
@end
@implementation VTHorizontalCandidateController (Private)
- (NSUInteger)pageCount
{
NSUInteger totalCount = [_delegate candidateCountForController:self];
NSUInteger keyLabelCount = [_keyLabels count];
return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0);
}
- (void)layoutCandidateView
{
[_candidateView setKeyLabelFont:_keyLabelFont candidateFont:_candidateFont CJKCandidateFont:_CJKCandidateFont];
NSMutableArray *candidates = [NSMutableArray array];
NSUInteger count = [_delegate candidateCountForController:self];
NSUInteger keyLabelCount = [_keyLabels count];
for (NSUInteger index = _currentPage * keyLabelCount, j = 0; index < count && j < keyLabelCount; index++, j++) {
[candidates addObject:[_delegate candidateController:self candidateAtIndex:index]];
}
[_candidateView setKeyLabels:_keyLabels displayedCandidates:candidates];
NSSize newSize = _candidateView.sizeForView;
NSRect frameRect = [_candidateView frame];
frameRect.size = newSize;
[_candidateView setFrame:frameRect];
if ([self pageCount] > 1) {
NSRect buttonRect = [_nextPageButton frame];
CGFloat spacing = 0.0;
if (newSize.height >= 60.0) {
spacing = ceil(newSize.height * 0.1);
}
CGFloat buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0;
buttonRect.origin = NSMakePoint(newSize.width + 8.0, buttonOriginY);
[_nextPageButton setFrame:buttonRect];
buttonRect.origin = NSMakePoint(newSize.width + 8.0, buttonOriginY + buttonRect.size.height + spacing);
[_prevPageButton setFrame:buttonRect];
[_nextPageButton setEnabled:(_currentPage + 1 < [self pageCount])];
[_prevPageButton setEnabled:(_currentPage != 0)];
newSize.width += 52.0;
[_nextPageButton setHidden:NO];
[_prevPageButton setHidden:NO];
}
else {
[_nextPageButton setHidden:YES];
[_prevPageButton setHidden:YES];
}
frameRect = [[self window] frame];
NSPoint topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height);
frameRect.size = newSize;
frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height);
[[self window] setFrame:frameRect display:YES];
[_candidateView setNeedsDisplay:YES];
}
- (void)pageButtonAction:(id)sender
{
if (sender == _nextPageButton) {
[self showNextPage];
}
else if (sender == _prevPageButton) {
[self showPreviousPage];
}
}
- (void)candidateViewMouseDidClick:(id)sender
{
[_delegate candidateController:self didSelectCandidateAtIndex:self.selectedCandidateIndex];
}
@end

View File

@ -0,0 +1,55 @@
//
// VTHorizontalCandidateView.h
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <Cocoa/Cocoa.h>
@interface VTHorizontalCandidateView : NSView
{
@protected
NSArray *_keyLabels;
NSArray *_displayedCandidates;
CGFloat _keyLabelHeight;
CGFloat _candidateTextHeight;
CGFloat _cellPadding;
NSDictionary *_keyLabelAttrDict;
NSDictionary *_candidateAttrDict;
NSDictionary *_CJKCandidateAttrDict;
NSArray *_elementWidths;
NSUInteger _highlightedIndex;
NSUInteger _trackingHighlightedIndex;
SEL _action;
__weak id _target;
}
- (void)setKeyLabels:(NSArray *)labels displayedCandidates:(NSArray *)candidates;
- (void)setKeyLabelFont:(NSFont *)labelFont candidateFont:(NSFont *)candidateFont CJKCandidateFont:(NSFont *)candidateFontCJK;
@property (readonly, nonatomic) NSSize sizeForView;
@property (assign, nonatomic) NSUInteger highlightedIndex;
@property (assign, nonatomic) SEL action;
@property (weak, assign, nonatomic) id target;
@end

View File

@ -0,0 +1,283 @@
//
// VTHorizontalCandidateView.m
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTHorizontalCandidateView.h"
@implementation VTHorizontalCandidateView
@synthesize highlightedIndex = _highlightedIndex;
@synthesize action = _action;
@synthesize target = _target;
- (void)dealloc
{
[_keyLabels release];
[_displayedCandidates release];
[_keyLabelAttrDict release];
[_candidateAttrDict release];
[_CJKCandidateAttrDict release];
[_elementWidths release];
[super dealloc];
}
- (void)setKeyLabels:(NSArray *)labels displayedCandidates:(NSArray *)candidates
{
NSUInteger count = MIN([labels count], [candidates count]);
id tmp;
tmp = _keyLabels;
_keyLabels = [[labels subarrayWithRange:NSMakeRange(0, count)] retain];
[tmp release];
tmp = _displayedCandidates;
_displayedCandidates = [[candidates subarrayWithRange:NSMakeRange(0, count)] retain];
[tmp release];
NSMutableArray *newWidths = [NSMutableArray array];
NSSize baseSize = NSMakeSize(10240.0, 10240.0);
for (NSUInteger index = 0; index < count; index++) {
NSRect labelRect = [[_keyLabels objectAtIndex:index] boundingRectWithSize:baseSize options:NSStringDrawingUsesLineFragmentOrigin attributes:_keyLabelAttrDict];
// TODO: Handle CJK text drawing
NSRect candidateRect = [[_displayedCandidates objectAtIndex:index] boundingRectWithSize:baseSize options:NSStringDrawingUsesLineFragmentOrigin attributes:_candidateAttrDict];
CGFloat width = MAX(labelRect.size.width, candidateRect.size.width) + _cellPadding;
[newWidths addObject:[NSNumber numberWithDouble:width]];
}
tmp = _elementWidths;
_elementWidths = [newWidths retain];
[tmp release];
}
- (void)setKeyLabelFont:(NSFont *)labelFont candidateFont:(NSFont *)candidateFont CJKCandidateFont:(NSFont *)candidateFontCJK
{
NSMutableParagraphStyle *paraStyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
[paraStyle setAlignment:NSCenterTextAlignment];
id tmp;
tmp = _keyLabelAttrDict;
_keyLabelAttrDict = [[NSDictionary dictionaryWithObjectsAndKeys:
labelFont, NSFontAttributeName,
paraStyle, NSParagraphStyleAttributeName,
[NSColor textColor], NSForegroundColorAttributeName,
nil] retain];
[tmp release];
tmp = _candidateAttrDict;
_candidateAttrDict = [[NSDictionary dictionaryWithObjectsAndKeys:
candidateFont, NSFontAttributeName,
paraStyle, NSParagraphStyleAttributeName,
[NSColor textColor], NSForegroundColorAttributeName,
nil] retain];
[tmp release];
tmp = _CJKCandidateAttrDict;
_CJKCandidateAttrDict = [[NSDictionary dictionaryWithObjectsAndKeys:
candidateFontCJK, NSFontAttributeName,
paraStyle, NSParagraphStyleAttributeName,
[NSColor textColor], NSForegroundColorAttributeName,
nil] retain];
[tmp release];
CGFloat labelFontSize = [labelFont pointSize];
CGFloat candidateFontSize = MAX([candidateFont pointSize], [candidateFontCJK pointSize]);
CGFloat biggestSize = MAX(labelFontSize, candidateFontSize);
_keyLabelHeight = labelFontSize;
_candidateTextHeight = candidateFontSize;
if (labelFontSize <= 16.0) {
_keyLabelHeight += 4.0;
}
else if (labelFontSize <= 24.0) {
_keyLabelHeight += 8.0;
}
else if (labelFontSize <= 64.0) {
_keyLabelHeight += 16.0;
}
else {
_keyLabelHeight += 24.0;
}
if (candidateFontSize <= 16.0) {
_candidateTextHeight += 4.0;
}
else if (candidateFontSize <= 24.0) {
_candidateTextHeight += 8.0;
}
else if (candidateFontSize <= 64.0) {
_candidateTextHeight += 16.0;
}
else {
_candidateTextHeight += 24.0;
}
if (biggestSize <= 16.0) {
_cellPadding = 8.0;
}
else if (biggestSize <= 24.0) {
_cellPadding = 16.0;
}
else if (biggestSize <= 64.0) {
_cellPadding = 24.0;
}
else {
_cellPadding = 32.0;
}
}
- (NSSize)sizeForView
{
NSSize result = NSMakeSize(0.0, 0.0);
if ([_elementWidths count]) {
for (NSNumber *w in _elementWidths) {
result.width += [w doubleValue];
}
result.width += [_elementWidths count];
result.height = _keyLabelHeight + _candidateTextHeight + 1.0;
}
return result;
}
- (BOOL)isFlipped
{
return YES;
}
- (void)drawRect:(NSRect)dirtyRect
{
NSColor *white = [NSColor whiteColor];
NSColor *darkGray = [NSColor colorWithDeviceWhite:0.7 alpha:1.0];
NSColor *lightGray = [NSColor colorWithDeviceWhite:0.8 alpha:1.0];
NSRect bounds = [self bounds];
[white setFill];
[NSBezierPath fillRect:bounds];
[[NSColor darkGrayColor] setStroke];
[NSBezierPath strokeLineFromPoint:NSMakePoint(bounds.size.width, 0.0) toPoint:NSMakePoint(bounds.size.width, bounds.size.height)];
NSUInteger count = [_elementWidths count];
CGFloat accuWidth = 0.0;
for (NSUInteger index = 0; index < count; index++) {
NSDictionary *activeCandidateAttr = _candidateAttrDict;
CGFloat currentWidth = [[_elementWidths objectAtIndex:index] doubleValue];
NSRect labelRect = NSMakeRect(accuWidth, 0.0, currentWidth, _keyLabelHeight);
NSRect candidateRect = NSMakeRect(accuWidth, _keyLabelHeight + 1.0, currentWidth, _candidateTextHeight);
if (index == _highlightedIndex) {
[darkGray setFill];
}
else {
[lightGray setFill];
}
[NSBezierPath fillRect:labelRect];
[[_keyLabels objectAtIndex:index] drawInRect:labelRect withAttributes:_keyLabelAttrDict];
if (index == _highlightedIndex) {
[[NSColor selectedTextBackgroundColor] setFill];
activeCandidateAttr = [[_candidateAttrDict mutableCopy] autorelease];
[(NSMutableDictionary *)activeCandidateAttr setObject:[NSColor selectedTextColor] forKey:NSForegroundColorAttributeName];
}
else {
[white setFill];
}
[NSBezierPath fillRect:candidateRect];
[[_displayedCandidates objectAtIndex:index] drawInRect:candidateRect withAttributes:activeCandidateAttr];
accuWidth += currentWidth + 1.0;
}
}
- (NSUInteger)findHitIndex:(NSEvent *)theEvent
{
NSUInteger result = NSUIntegerMax;
NSPoint location = [self convertPoint:[theEvent locationInWindow] toView:nil];
if (!NSPointInRect(location, [self bounds])) {
return result;
}
NSUInteger count = [_elementWidths count];
CGFloat accuWidth = 0.0;
for (NSUInteger index = 0; index < count; index++) {
CGFloat currentWidth = [[_elementWidths objectAtIndex:index] doubleValue];
if (location.x >= accuWidth && location.x <= accuWidth + currentWidth) {
result = index;
break;
}
accuWidth += currentWidth + 1.0;
}
return result;
}
- (void)mouseDown:(NSEvent *)theEvent
{
NSUInteger newIndex = [self findHitIndex:theEvent];
_trackingHighlightedIndex = _highlightedIndex;
if (newIndex != NSUIntegerMax) {
_highlightedIndex = newIndex;
[self setNeedsDisplay:YES];
}
}
- (void)mouseUp:(NSEvent *)theEvent
{
NSUInteger newIndex = [self findHitIndex:theEvent];
BOOL triggerAction = NO;
if (newIndex == _highlightedIndex) {
triggerAction = YES;
}
else {
_highlightedIndex = _trackingHighlightedIndex;
}
_trackingHighlightedIndex = 0;
[self setNeedsDisplay:YES];
if (triggerAction && _target && _action) {
[_target performSelector:_action withObject:self];
}
}
@end

View File

@ -0,0 +1,41 @@
//
// VTVerticalCandidateController.h
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTCandidateController.h"
@class VTVerticalKeyLabelStripView;
@interface VTVerticalCandidateController : VTCandidateController
{
@protected
VTVerticalKeyLabelStripView *_keyLabelStripView;
NSScrollView *_scrollView;
NSTableView *_tableView;
NSMutableParagraphStyle *_candidateTextParagraphStyle;
CGFloat _maxCandidateAttrStringWidth;
}
@end

View File

@ -0,0 +1,399 @@
//
// VTVerticalCandidateController.m
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTVerticalCandidateController.h"
#import "VTVerticalKeyLabelStripView.h"
#import "VTVerticalCandidateTableView.h"
static const CGFloat kCandidateTextPadding = 24.0;
static const CGFloat kCandidateTextLeftMargin = 8.0;
@interface VTVerticalCandidateController (Private) <NSTableViewDataSource, NSTableViewDelegate>
- (void)rowDoubleClicked:(id)sender;
- (BOOL)scrollPageByOne:(BOOL)forward;
- (BOOL)moveSelectionByOne:(BOOL)forward;
- (void)layoutCandidateView;
@end
@implementation VTVerticalCandidateController
- (void)dealloc
{
[_candidateTextParagraphStyle release];
[_keyLabelStripView release];
[_scrollView release];
[_tableView release];
[super dealloc];
}
- (id)init
{
NSRect contentRect = NSMakeRect(128.0, 128.0, 0.0, 0.0);
NSUInteger styleMask = NSBorderlessWindowMask | NSNonactivatingPanelMask;
NSPanel *panel = [[[NSPanel alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO] autorelease];
[panel setLevel:CGShieldingWindowLevel() + 1];
[panel setHasShadow:YES];
self = [self initWithWindow:panel];
if (self) {
_candidateTextParagraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[_candidateTextParagraphStyle setFirstLineHeadIndent:kCandidateTextLeftMargin];
contentRect.origin = NSMakePoint(0.0, 0.0);
NSRect stripRect = contentRect;
stripRect.size.width = 10.0;
_keyLabelStripView = [[VTVerticalKeyLabelStripView alloc] initWithFrame:stripRect];
[[panel contentView] addSubview:_keyLabelStripView];
NSRect scrollViewRect = contentRect;
scrollViewRect.origin.x = stripRect.size.width;
scrollViewRect.size.width -= stripRect.size.width;
_scrollView = [[NSScrollView alloc] initWithFrame:scrollViewRect];
// >=10.7 only, elastic scroll causes some drawing issues with visible scroller, so we disable it
if ([_scrollView respondsToSelector:@selector(setVerticalScrollElasticity:)]) {
[_scrollView setVerticalScrollElasticity:NSScrollElasticityNone];
}
_tableView = [[VTVerticalCandidateTableView alloc] initWithFrame:contentRect];
[_tableView setDataSource:self];
[_tableView setDelegate:self];
NSTableColumn *column = [[[NSTableColumn alloc] initWithIdentifier:@"candidate"] autorelease];
[column setEditable:NO];
[_tableView addTableColumn:column];
[_tableView setIntercellSpacing:NSMakeSize(0.0, 1.0)];
[_tableView setHeaderView:nil];
[_tableView setAllowsMultipleSelection:NO];
[_tableView setAllowsEmptySelection:YES];
[_tableView setDoubleAction:@selector(rowDoubleClicked:)];
[_tableView setTarget:self];
[_scrollView setDocumentView:_tableView];
[[panel contentView] addSubview:_scrollView];
}
return self;
}
- (void)reloadData
{
_maxCandidateAttrStringWidth = ceil(MAX([_candidateFont pointSize], [_CJKCandidateFont pointSize])) * 2.0 + kCandidateTextPadding;
[_tableView reloadData];
[self layoutCandidateView];
if ([_delegate candidateCountForController:self]) {
self.selectedCandidateIndex = 0;
}
}
- (BOOL)showNextPage
{
return [self scrollPageByOne:YES];
}
- (BOOL)showPreviousPage
{
return [self scrollPageByOne:NO];
}
- (BOOL)highlightNextCandidate
{
return [self moveSelectionByOne:YES];
}
- (BOOL)highlightPreviousCandidate
{
return [self moveSelectionByOne:NO];
}
- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index
{
NSInteger firstVisibleRow = [_tableView rowAtPoint:[_scrollView documentVisibleRect].origin];
if (firstVisibleRow != -1) {
NSUInteger result = firstVisibleRow + index;
if (result < [_delegate candidateCountForController:self]) {
return result;
}
}
return NSUIntegerMax;
}
- (NSUInteger)selectedCandidateIndex
{
NSInteger selectedRow = [_tableView selectedRow];
return (selectedRow == -1) ? NSUIntegerMax : selectedRow;
}
- (void)setSelectedCandidateIndex:(NSUInteger)newIndex
{
NSInteger selectedRow = [_tableView selectedRow];
NSUInteger labelCount = [_keyLabels count];
NSUInteger itemCount = [_delegate candidateCountForController:self];
NSUInteger lastVisibleRow = newIndex;
if (selectedRow != -1 && itemCount > 0 && itemCount > labelCount) {
if (newIndex > selectedRow && (newIndex - selectedRow) > 1) {
lastVisibleRow = MIN(newIndex + labelCount - 1, itemCount - 1);
}
// no need to handle the backward case: (newIndex < selectedRow && selectedRow - newIndex > 1)
}
if (itemCount > labelCount) {
[_tableView scrollRowToVisible:lastVisibleRow];
}
[_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:newIndex] byExtendingSelection:NO];
}
@end
@implementation VTVerticalCandidateController (Private)
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [_delegate candidateCountForController:self];
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSString *candidate = [_delegate candidateController:self candidateAtIndex:row];
// TODO: Handle CJK font fallback
NSAttributedString *attrString = [[[NSAttributedString alloc] initWithString:candidate attributes:[NSDictionary dictionaryWithObjectsAndKeys:_candidateFont, NSFontAttributeName, _candidateTextParagraphStyle, NSParagraphStyleAttributeName, nil]] autorelease];
// expand the window width if text overflows
NSRect boundingRect = [attrString boundingRectWithSize:NSMakeSize(10240.0, 10240.0) options:NSStringDrawingUsesLineFragmentOrigin];
CGFloat textWidth = boundingRect.size.width + kCandidateTextPadding;
if (textWidth > _maxCandidateAttrStringWidth) {
_maxCandidateAttrStringWidth = textWidth;
[self layoutCandidateView];
}
// keep track of the highlighted index in the key label strip
NSUInteger count = [_keyLabels count];
NSInteger selectedRow = [_tableView selectedRow];
if (selectedRow != -1) {
// cast this into signed integer to make our life easier
NSInteger newHilightIndex;
if (_keyLabelStripView.highlightedIndex != -1 && (row >= selectedRow + count || (selectedRow > count && row <= selectedRow - count))) {
newHilightIndex = -1;
}
else {
NSInteger firstVisibleRow = [_tableView rowAtPoint:[_scrollView documentVisibleRect].origin];
newHilightIndex = selectedRow - firstVisibleRow;
if (newHilightIndex < -1) {
newHilightIndex = -1;
}
}
if (newHilightIndex != _keyLabelStripView.highlightedIndex) {
_keyLabelStripView.highlightedIndex = newHilightIndex;
[_keyLabelStripView setNeedsDisplay:YES];
}
}
return attrString;
}
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
{
NSInteger selectedRow = [_tableView selectedRow];
if (selectedRow != -1) {
// keep track of the highlighted index in the key label strip
NSInteger firstVisibleRow = [_tableView rowAtPoint:[_scrollView documentVisibleRect].origin];
_keyLabelStripView.highlightedIndex = selectedRow - firstVisibleRow;
[_keyLabelStripView setNeedsDisplay:YES];
}
}
- (void)rowDoubleClicked:(id)sender
{
NSInteger clickedRow = [_tableView clickedRow];
if (clickedRow != -1) {
[_delegate candidateController:self didSelectCandidateAtIndex:clickedRow];
}
}
- (BOOL)scrollPageByOne:(BOOL)forward
{
NSUInteger labelCount = [_keyLabels count];
NSUInteger itemCount = [_delegate candidateCountForController:self];
if (0 == itemCount) {
return NO;
}
if (itemCount <= labelCount) {
return NO;
}
NSUInteger newIndex = self.selectedCandidateIndex;
if (forward) {
if (newIndex == itemCount - 1) {
return NO;
}
newIndex = MIN(newIndex + labelCount, itemCount - 1);
}
else {
if (newIndex == 0) {
return NO;
}
if (newIndex < labelCount) {
newIndex = 0;
}
else {
newIndex -= labelCount;
}
}
self.selectedCandidateIndex = newIndex;
return YES;
}
- (BOOL)moveSelectionByOne:(BOOL)forward
{
NSUInteger itemCount = [_delegate candidateCountForController:self];
if (0 == itemCount) {
return NO;
}
NSUInteger newIndex = self.selectedCandidateIndex;
if (forward) {
if (newIndex == itemCount - 1) {
return NO;
}
newIndex++;
}
else {
if (0 == newIndex) {
return NO;
}
newIndex--;
}
self.selectedCandidateIndex = newIndex;
return YES;
}
- (void)layoutCandidateView
{
NSUInteger count = [_delegate candidateCountForController:self];
if (!count) {
return;
}
CGFloat candidateFontSize = ceil(MAX([_candidateFont pointSize], [_CJKCandidateFont pointSize]));
CGFloat keyLabelFontSize = ceil([_keyLabelFont pointSize]);
CGFloat fontSize = MAX(candidateFontSize, keyLabelFontSize);
NSControlSize controlSize = (fontSize > 36.0) ? NSRegularControlSize : NSSmallControlSize;
NSUInteger keyLabelCount = [_keyLabels count];
CGFloat scrollerWidth = 0.0;
if (count <= keyLabelCount) {
keyLabelCount = count;
[_scrollView setHasVerticalScroller:NO];
}
else {
[_scrollView setHasVerticalScroller:YES];
NSScroller *verticalScroller = [_scrollView verticalScroller];
[verticalScroller setControlSize:controlSize];
// calling >=10.7 only API
if ([verticalScroller respondsToSelector:@selector(setScrollerStyle:)]) {
[verticalScroller setScrollerStyle:NSScrollerStyleLegacy];
scrollerWidth = [NSScroller scrollerWidthForControlSize:controlSize scrollerStyle:NSScrollerStyleLegacy];
}
else {
// not on >=10.7
scrollerWidth = [NSScroller scrollerWidthForControlSize:controlSize];
}
}
_keyLabelStripView.keyLabelFont = _keyLabelFont;
_keyLabelStripView.keyLabels = [_keyLabels subarrayWithRange:NSMakeRange(0, keyLabelCount)];
_keyLabelStripView.labelOffsetY = (keyLabelFontSize >= candidateFontSize) ? 0.0 : floor((candidateFontSize - keyLabelFontSize) / 2.0);
CGFloat rowHeight = fontSize;
if (fontSize <= 16.0) {
rowHeight += 4.0;
}
else if (fontSize <= 24.0) {
rowHeight += 8.0;
}
else if (fontSize <= 64.0) {
rowHeight += 16.0;
}
else {
rowHeight += 24.0;
}
[_tableView setRowHeight:rowHeight];
CGFloat rowSpacing = [_tableView intercellSpacing].height;
CGFloat stripWidth = ceil(keyLabelFontSize * 1.20);
CGFloat tableViewStartWidth = ceil(_maxCandidateAttrStringWidth + scrollerWidth);;
CGFloat windowWidth = stripWidth + 1.0 + tableViewStartWidth;
CGFloat windowHeight = keyLabelCount * (rowHeight + rowSpacing);
NSRect frameRect = [[self window] frame];
frameRect = [[self window] frame];
NSPoint topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height);
frameRect.size = NSMakeSize(windowWidth, windowHeight);
frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height);
[_keyLabelStripView setFrame:NSMakeRect(0.0, 0.0, stripWidth, windowHeight)];
[_scrollView setFrame:NSMakeRect(stripWidth + 1.0, 0.0, tableViewStartWidth, windowHeight)];
[[self window] setFrame:frameRect display:YES];
}
@end

View File

@ -0,0 +1,31 @@
//
// VTVerticalCandidateTableView.h
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <Cocoa/Cocoa.h>
@interface VTVerticalCandidateTableView : NSTableView
@end

View File

@ -0,0 +1,38 @@
//
// VTVerticalCandidateTableView.m
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTVerticalCandidateTableView.h"
@implementation VTVerticalCandidateTableView
- (NSRect)adjustScroll:(NSRect)newVisible
{
NSRect scrollRect = newVisible;
CGFloat rowHeightPlusSpacing = [self rowHeight] + [self intercellSpacing].height;
scrollRect.origin.y = (NSInteger)(scrollRect.origin.y / rowHeightPlusSpacing) * rowHeightPlusSpacing;
return scrollRect;
}
@end

View File

@ -0,0 +1,43 @@
//
// VTVerticalKeyLabelStripView.m
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <Cocoa/Cocoa.h>
@interface VTVerticalKeyLabelStripView : NSView
{
@protected
NSFont *_keyLabelFont;
CGFloat _labelOffsetY;
NSArray *_keyLabels;
NSInteger _highlightedIndex;
}
@property (retain, nonatomic) NSFont *keyLabelFont;
@property (assign, nonatomic) CGFloat labelOffsetY;
@property (retain, nonatomic) NSArray *keyLabels;
@property (assign, nonatomic) NSInteger highlightedIndex;
@end

View File

@ -0,0 +1,101 @@
//
// VTVerticalKeyLabelStripView.h
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTVerticalKeyLabelStripView.h"
@implementation VTVerticalKeyLabelStripView
@synthesize keyLabelFont = _keyLabelFont;
@synthesize labelOffsetY = _labelOffsetY;
@synthesize keyLabels = _keyLabels;
@synthesize highlightedIndex = _highlightedIndex;
- (void)dealloc
{
[_keyLabelFont release];
[_keyLabels release];
[super dealloc];
}
- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if (self) {
_keyLabelFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
}
return self;
}
- (BOOL)isFlipped
{
return YES;
}
- (void)drawRect:(NSRect)dirtyRect
{
NSRect bounds = [self bounds];
[[NSColor whiteColor] setFill];
[NSBezierPath fillRect:bounds];
NSUInteger count = [_keyLabels count];
if (!count) {
return;
}
CGFloat cellHeight = bounds.size.height / count;
NSColor *black = [NSColor blackColor];
NSColor *darkGray = [NSColor colorWithDeviceWhite:0.7 alpha:1.0];
NSColor *lightGray = [NSColor colorWithDeviceWhite:0.8 alpha:1.0];
NSMutableParagraphStyle *style = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
[style setAlignment:NSCenterTextAlignment];
NSDictionary *textAttr = [NSDictionary dictionaryWithObjectsAndKeys:
_keyLabelFont, NSFontAttributeName,
black, NSForegroundColorAttributeName,
style, NSParagraphStyleAttributeName,
nil];
for (NSUInteger index = 0; index < count; index++) {
NSRect textRect = NSMakeRect(0.0, index * cellHeight + _labelOffsetY, bounds.size.width, cellHeight - _labelOffsetY);
NSRect cellRect = NSMakeRect(0.0, index * cellHeight, bounds.size.width, cellHeight - 1);
if (index == _highlightedIndex) {
[darkGray setFill];
}
else {
[lightGray setFill];
}
[NSBezierPath fillRect:cellRect];
NSString *text = [_keyLabels objectAtIndex:index];
[text drawInRect:textRect withAttributes:textAttr];
}
}
@end