From 7d5cd8173c0d0564084fbd4ac187f77a8ec9a3b0 Mon Sep 17 00:00:00 2001 From: "Lukhnos D. Liu" Date: Wed, 28 Mar 2012 23:25:12 -0700 Subject: [PATCH] Import the Voltaire (commit 152775fc) project to replace IMK candidate UI. --- Source/CandidateUI/VTCandidateController.h | 67 +++ Source/CandidateUI/VTCandidateController.m | 124 ++++++ .../VTHorizontalCandidateController.h | 40 ++ .../VTHorizontalCandidateController.m | 244 +++++++++++ .../CandidateUI/VTHorizontalCandidateView.h | 55 +++ .../CandidateUI/VTHorizontalCandidateView.m | 283 +++++++++++++ .../VTVerticalCandidateController.h | 41 ++ .../VTVerticalCandidateController.m | 399 ++++++++++++++++++ .../VTVerticalCandidateTableView.h | 31 ++ .../VTVerticalCandidateTableView.m | 38 ++ .../CandidateUI/VTVerticalKeyLabelStripView.h | 43 ++ .../CandidateUI/VTVerticalKeyLabelStripView.m | 101 +++++ 12 files changed, 1466 insertions(+) create mode 100644 Source/CandidateUI/VTCandidateController.h create mode 100644 Source/CandidateUI/VTCandidateController.m create mode 100644 Source/CandidateUI/VTHorizontalCandidateController.h create mode 100644 Source/CandidateUI/VTHorizontalCandidateController.m create mode 100644 Source/CandidateUI/VTHorizontalCandidateView.h create mode 100644 Source/CandidateUI/VTHorizontalCandidateView.m create mode 100644 Source/CandidateUI/VTVerticalCandidateController.h create mode 100644 Source/CandidateUI/VTVerticalCandidateController.m create mode 100644 Source/CandidateUI/VTVerticalCandidateTableView.h create mode 100644 Source/CandidateUI/VTVerticalCandidateTableView.m create mode 100644 Source/CandidateUI/VTVerticalKeyLabelStripView.h create mode 100644 Source/CandidateUI/VTVerticalKeyLabelStripView.m diff --git a/Source/CandidateUI/VTCandidateController.h b/Source/CandidateUI/VTCandidateController.h new file mode 100644 index 00000000..1dfd72c0 --- /dev/null +++ b/Source/CandidateUI/VTCandidateController.h @@ -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 + +@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 _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 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 diff --git a/Source/CandidateUI/VTCandidateController.m b/Source/CandidateUI/VTCandidateController.m new file mode 100644 index 00000000..9e8984a9 --- /dev/null +++ b/Source/CandidateUI/VTCandidateController.m @@ -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 diff --git a/Source/CandidateUI/VTHorizontalCandidateController.h b/Source/CandidateUI/VTHorizontalCandidateController.h new file mode 100644 index 00000000..23c9da4e --- /dev/null +++ b/Source/CandidateUI/VTHorizontalCandidateController.h @@ -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 diff --git a/Source/CandidateUI/VTHorizontalCandidateController.m b/Source/CandidateUI/VTHorizontalCandidateController.m new file mode 100644 index 00000000..ec198700 --- /dev/null +++ b/Source/CandidateUI/VTHorizontalCandidateController.m @@ -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 diff --git a/Source/CandidateUI/VTHorizontalCandidateView.h b/Source/CandidateUI/VTHorizontalCandidateView.h new file mode 100644 index 00000000..cddf1a30 --- /dev/null +++ b/Source/CandidateUI/VTHorizontalCandidateView.h @@ -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 + +@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 diff --git a/Source/CandidateUI/VTHorizontalCandidateView.m b/Source/CandidateUI/VTHorizontalCandidateView.m new file mode 100644 index 00000000..0cde17e4 --- /dev/null +++ b/Source/CandidateUI/VTHorizontalCandidateView.m @@ -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 diff --git a/Source/CandidateUI/VTVerticalCandidateController.h b/Source/CandidateUI/VTVerticalCandidateController.h new file mode 100644 index 00000000..04bdb596 --- /dev/null +++ b/Source/CandidateUI/VTVerticalCandidateController.h @@ -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 diff --git a/Source/CandidateUI/VTVerticalCandidateController.m b/Source/CandidateUI/VTVerticalCandidateController.m new file mode 100644 index 00000000..693f1331 --- /dev/null +++ b/Source/CandidateUI/VTVerticalCandidateController.m @@ -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) +- (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 diff --git a/Source/CandidateUI/VTVerticalCandidateTableView.h b/Source/CandidateUI/VTVerticalCandidateTableView.h new file mode 100644 index 00000000..47bb37ac --- /dev/null +++ b/Source/CandidateUI/VTVerticalCandidateTableView.h @@ -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 + +@interface VTVerticalCandidateTableView : NSTableView +@end diff --git a/Source/CandidateUI/VTVerticalCandidateTableView.m b/Source/CandidateUI/VTVerticalCandidateTableView.m new file mode 100644 index 00000000..bf326ac5 --- /dev/null +++ b/Source/CandidateUI/VTVerticalCandidateTableView.m @@ -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 diff --git a/Source/CandidateUI/VTVerticalKeyLabelStripView.h b/Source/CandidateUI/VTVerticalKeyLabelStripView.h new file mode 100644 index 00000000..8439ba1a --- /dev/null +++ b/Source/CandidateUI/VTVerticalKeyLabelStripView.h @@ -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 + +@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 diff --git a/Source/CandidateUI/VTVerticalKeyLabelStripView.m b/Source/CandidateUI/VTVerticalKeyLabelStripView.m new file mode 100644 index 00000000..a7ca4290 --- /dev/null +++ b/Source/CandidateUI/VTVerticalKeyLabelStripView.m @@ -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