Import the Voltaire (commit 152775fc) project to replace IMK candidate UI.
This commit is contained in:
parent
adf6713aa7
commit
7d5cd8173c
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue