diff --git a/Source/CandidateUI/HorizontalCandidateController.swift b/Source/CandidateUI/HorizontalCandidateController.swift new file mode 100644 index 00000000..74bd033d --- /dev/null +++ b/Source/CandidateUI/HorizontalCandidateController.swift @@ -0,0 +1,418 @@ +// +// HorizontalCandidateController.swift +// +// Copyright (c) 2011 The McBopomofo Project. +// +// Contributors: +// Mengjuei Hsieh (@mjhsieh) +// Weizhong Yang (@zonble) +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// 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 + +fileprivate class HorizontalCandidateView: NSView { + var highlightedIndex: UInt = 0 + var action: Selector? + weak var target: AnyObject? + + private var keyLabels: [String] = [] + private var displayedCandidates: [String] = [] + private var keyLabelHeight: CGFloat = 0 + private var candidateTextHeight: CGFloat = 0 + private var cellPadding: CGFloat = 0 + private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var elementWidths: [CGFloat] = [] + private var trackingHighlightedIndex: UInt = UInt.max + + override var isFlipped: Bool { + true + } + + var sizeForView: NSSize { + var result = NSSize.zero + + if !elementWidths.isEmpty { + result.width = elementWidths.reduce(0, +) + result.width += CGFloat(elementWidths.count) + result.height = keyLabelHeight + candidateTextHeight + 1.0 + } + return result + } + + @objc (setKeyLabels:displayedCandidates:) + func set(keyLabels labels: [String], displayedCandidates candidates: [String]) { + let count = min(labels.count, candidates.count) + keyLabels = Array(labels[0.. UInt? { + let location = convert(event.locationInWindow, to: nil) + if !NSPointInRect(location, self.bounds) { + return nil + } + var accuWidth: CGFloat = 0.0 + for index in 0..= accuWidth && location.x <= accuWidth + currentWidth { + return UInt(index) + } + accuWidth += currentWidth + 1.0 + } + return nil + + } + + override func mouseUp(with event: NSEvent) { + trackingHighlightedIndex = highlightedIndex + guard let newIndex = findHitIndex(event: event) else { + return + } + highlightedIndex = newIndex + self.setNeedsDisplay(self.bounds) + } + + override func mouseDown(with event: NSEvent) { + guard let newIndex = findHitIndex(event: event) else { + return + } + var triggerAction = false + if newIndex == highlightedIndex { + triggerAction = true + } else { + highlightedIndex = trackingHighlightedIndex + } + + trackingHighlightedIndex = 0 + self.setNeedsDisplay(self.bounds) + if triggerAction { + if let target = target as? NSObject, let action = action { + target.perform(action, with: self) + } + } + } +} + +@objc (VTHorizontalCandidateController) +public class HorizontalCandidateController: CandidateController { + private var candidateView: HorizontalCandidateView + private var prevPageButton: NSButton + private var nextPageButton: NSButton + private var currentPage: UInt = 0 + + public init() { + var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) + let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] + let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel)) + panel.hasShadow = true + + contentRect.origin = NSPoint.zero + candidateView = HorizontalCandidateView(frame: contentRect) + panel.contentView?.addSubview(candidateView) + + contentRect.size = NSSize(width: 36.0, height: 20.0) + nextPageButton = NSButton(frame: contentRect) + nextPageButton.setButtonType(.momentaryLight) + nextPageButton.bezelStyle = .smallSquare + nextPageButton.title = "»" + + prevPageButton = NSButton(frame: contentRect) + prevPageButton.setButtonType(.momentaryLight) + prevPageButton.bezelStyle = .smallSquare + prevPageButton.title = "«" + + panel.contentView?.addSubview(nextPageButton) + panel.contentView?.addSubview(prevPageButton) + + super.init(window: panel) + + candidateView.target = self + candidateView.action = #selector(candidateViewMouseDidClick(_:)) + + nextPageButton.target = self + nextPageButton.action = #selector(pageButtonAction(_:)) + + prevPageButton.target = self + prevPageButton.action = #selector(pageButtonAction(_:)) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func reloadData() { + candidateView.highlightedIndex = 0 + currentPage = 0 + layoutCandidateView() + } + + public override func showNextPage() -> Bool { + guard delegate != nil else { + return false + } + + if currentPage + 1 >= pageCount { + return false + } + + currentPage += 1 + candidateView.highlightedIndex = 0 + layoutCandidateView() + return true + } + + public override func showPreviousPage() -> Bool { + guard delegate != nil else { + return false + } + + if currentPage == 0 { + return false + } + + currentPage -= 1 + candidateView.highlightedIndex = 0 + layoutCandidateView() + return true + } + + public override func highlightNextCandidate() -> Bool { + guard let delegate = delegate else { + return false + } + + let currentIndex = selectedCandidateIndex + if currentIndex + 1 >= delegate.candidateCountForController(self) { + return false + } + selectedCandidateIndex = currentIndex + 1 + return true + } + + public override func highlightPreviousCandidate() -> Bool { + guard delegate != nil else { + return false + } + + let currentIndex = selectedCandidateIndex + if currentIndex == 0 { + return false + } + + selectedCandidateIndex = currentIndex - 1 + return true + } + + public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { + guard let delegate = delegate else { + return UInt.max + } + + let result = currentPage * UInt(keyLabels.count) + index + return result < delegate.candidateCountForController(self) ? result : UInt.max + } + + public override var selectedCandidateIndex: UInt { + get { + currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex + } + set { + guard let delegate = delegate else { + return + } + let keyLabelCount = UInt(keyLabels.count) + if newValue < delegate.candidateCountForController(self) { + currentPage = newValue / keyLabelCount + candidateView.highlightedIndex = newValue % keyLabelCount + layoutCandidateView() + } + } + } +} + +extension HorizontalCandidateController { + + private var pageCount: UInt { + guard let delegate = delegate else { + return 0 + } + let totalCount = delegate.candidateCountForController(self) + let keyLabelCount = UInt(keyLabels.count) + return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0) + } + + private func layoutCandidateView() { + guard let delegate = delegate else { + return + } + + candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont) + var candidates = [String]() + let count = delegate.candidateCountForController(self) + let keyLabelCount = UInt(keyLabels.count) + + let begin = currentPage * keyLabelCount + for index in begin.. 1 { + var buttonRect = nextPageButton.frame + var spacing = 0.0 + + if newSize.height < 40.0 { + buttonRect.size.height = floor(newSize.height / 2) + } else { + buttonRect.size.height = 20.0 + } + + if newSize.height >= 60.0 { + spacing = ceil(newSize.height * 0.1) + } + + let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0 + buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY) + nextPageButton.frame = buttonRect + + buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY + buttonRect.size.height + spacing) + prevPageButton.frame = buttonRect + + newSize.width += 52.0 + nextPageButton.isHidden = false + prevPageButton.isHidden = false + } else { + nextPageButton.isHidden = true + prevPageButton.isHidden = true + } + + frameRect = window?.frame ?? NSRect.zero + + let 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: false) + candidateView.setNeedsDisplay(candidateView.bounds) + } + + @objc fileprivate func pageButtonAction(_ sender: Any) { + guard let sender = sender as? NSButton else { + return + } + if sender == nextPageButton { + _ = showNextPage() + } else if sender == prevPageButton { + _ = showPreviousPage() + } + } + + @objc fileprivate func candidateViewMouseDidClick(_ sender: Any) { + delegate?.candidateController(self, didSelectCandidateAtIndex: selectedCandidateIndex) + } + +} diff --git a/Source/CandidateUI/VTCandidateController.h b/Source/CandidateUI/VTCandidateController.h deleted file mode 100644 index 41e36a5c..00000000 --- a/Source/CandidateUI/VTCandidateController.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// 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; - BOOL _visible; -} - -- (void)reloadData; - -- (BOOL)showNextPage; -- (BOOL)showPreviousPage; -- (BOOL)highlightNextCandidate; -- (BOOL)highlightPreviousCandidate; - -- (void)setWindowTopLeftPoint:(NSPoint)topLeftPoint bottomOutOfScreenAdjustmentHeight:(CGFloat)height; - -- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index; - -@property (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; -@end diff --git a/Source/CandidateUI/VTCandidateController.m b/Source/CandidateUI/VTCandidateController.m deleted file mode 100644 index 3b5f700a..00000000 --- a/Source/CandidateUI/VTCandidateController.m +++ /dev/null @@ -1,178 +0,0 @@ -// -// 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; - -- (void)dealloc -{ - _keyLabels = nil; - _keyLabelFont = nil; - _candidateFont = nil; -} - -- (id)initWithWindow:(NSWindow *)window -{ - self = [super initWithWindow:window]; - if (self) { - // populate the default values - _keyLabels = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"]; - _keyLabelFont = [NSFont monospacedDigitSystemFontOfSize:14.0 weight:NSFontWeightMedium]; - _candidateFont = [NSFont systemFontOfSize:18.0 weight:NSFontWeightRegular]; - } - return self; -} - -- (void)reloadData -{ -} - -- (BOOL)showNextPage -{ - return NO; -} - -- (BOOL)showPreviousPage -{ - return NO; -} - -- (BOOL)highlightNextCandidate -{ - return NO; -} - -- (BOOL)highlightPreviousCandidate -{ - return NO; -} - -- (void)setWindowTopLeftPoint:(NSPoint)topLeftPoint bottomOutOfScreenAdjustmentHeight:(CGFloat)height -{ - // Since layout is now deferred, the origin setting should also be deferred so that - // the correct visible frame dimensions are used. - NSArray *params = [NSArray arrayWithObjects:[NSValue valueWithPoint:topLeftPoint], [NSNumber numberWithDouble:height], nil]; - [self performSelector:@selector(deferredSetWindowTopLeftPoint:) withObject:params afterDelay:0.0]; -} - -- (void)deferredSetWindowTopLeftPoint:(NSArray *)params -{ - NSPoint topLeftPoint = [[params objectAtIndex:0] pointValue]; - CGFloat height = [[params objectAtIndex:1] doubleValue]; - - NSPoint adjustedPoint = topLeftPoint; - CGFloat adjustedHeight = height; - - // first, locate the screen the point is in - NSRect screenFrame = [[NSScreen mainScreen] visibleFrame]; - - for (NSScreen *screen in [NSScreen screens]) { - NSRect frame = [screen visibleFrame]; - if (topLeftPoint.x >= NSMinX(frame) && topLeftPoint.x <= NSMaxX(frame) && topLeftPoint.y >= NSMinY(frame) && topLeftPoint.y <= NSMaxY(frame)) { - screenFrame = frame; - break; - } - } - - // make sure we don't have any erratic value - if (adjustedHeight > screenFrame.size.height / 2.0) { - adjustedHeight = 0.0; - } - - NSSize windowSize = [[self window] frame].size; - - // bottom beneath the screen? - if (adjustedPoint.y - windowSize.height < NSMinY(screenFrame)) { - adjustedPoint.y = topLeftPoint.y + adjustedHeight + windowSize.height; - } - - // top over the screen? - if (adjustedPoint.y >= NSMaxY(screenFrame)) { - adjustedPoint.y = NSMaxY(screenFrame) - 1.0; - } - - // right - if (adjustedPoint.x + windowSize.width >= NSMaxX(screenFrame)) { - adjustedPoint.x = NSMaxX(screenFrame) - windowSize.width; - } - - // left - if (adjustedPoint.x < NSMinX(screenFrame)) { - adjustedPoint.x = NSMinX(screenFrame); - } - - [[self window] setFrameTopLeftPoint:adjustedPoint]; -} - -- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index -{ - return NSUIntegerMax; -} - -- (BOOL)visible -{ - // Because setVisible: defers its action, we need to use our own visible. Do not use [[self window] isVisible]. - return _visible; -} - -- (void)setVisible:(BOOL)visible -{ - _visible = visible; - if (visible) { - [[self window] performSelector:@selector(orderFront:) withObject:self afterDelay:0.0]; - } - else { - [[self window] performSelector:@selector(orderOut:) withObject:self afterDelay:0.0]; - } -} - -- (NSPoint)windowTopLeftPoint -{ - NSRect frameRect = [[self window] frame]; - return NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height); -} - -- (void)setWindowTopLeftPoint:(NSPoint)topLeftPoint -{ - [self setWindowTopLeftPoint:topLeftPoint bottomOutOfScreenAdjustmentHeight:0.0]; -} - -- (NSUInteger)selectedCandidateIndex -{ - return NSUIntegerMax; -} - -- (void)setSelectedCandidateIndex:(NSUInteger)newIndex -{ -} -@end diff --git a/Source/CandidateUI/VTCandidateController.swift b/Source/CandidateUI/VTCandidateController.swift new file mode 100644 index 00000000..42a575f6 --- /dev/null +++ b/Source/CandidateUI/VTCandidateController.swift @@ -0,0 +1,152 @@ +// +// VTCandidateController.swift +// +// Voltaire IME Candidate Controller Module +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla // Original Developer +// Weizhong Yang (@zonble) @ OpenVanilla // Rewriter to Swift +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// 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 + +@objc (VTCandidateControllerDelegate) +public protocol CandidateControllerDelegate: AnyObject { + func candidateCountForController(_ controller: CandidateController) -> UInt + func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String + func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) +} + +@objc (VTCandidateController) +public class CandidateController: NSWindowController { + @objc public weak var delegate: CandidateControllerDelegate? + @objc public var selectedCandidateIndex: UInt = UInt.max + @objc public var visible: Bool = false { + didSet { + if visible { + window?.perform(#selector(NSWindow.orderFront(_:)), with: self, afterDelay: 0.0) + } else { + window?.perform(#selector(NSWindow.orderOut(_:)), with: self, afterDelay: 0.0) + } + } + } + @objc public var windowTopLeftPoint: NSPoint { + get { + guard let frameRect = window?.frame else { + return NSPoint.zero + } + return NSPoint(x: frameRect.minX, y: frameRect.maxY) + } + set { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { + self.set(windowTopLeftPoint: newValue, bottomOutOfScreenAdjustmentHeight: 0) + } + } + } + + @objc public var keyLabels: [String] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] + @objc public var keyLabelFont: NSFont = NSFont.systemFont(ofSize: 14) + @objc public var candidateFont: NSFont = NSFont.systemFont(ofSize: 18) + + @objc public func reloadData() { + } + + @objc public func showNextPage() -> Bool { + false + } + + @objc public func showPreviousPage() -> Bool { + false + } + + @objc public func highlightNextCandidate() -> Bool { + false + } + + @objc public func highlightPreviousCandidate() -> Bool { + false + } + + @objc public func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { + UInt.max + } + + @objc (setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:) + public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { + self.doSet(windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height) + } + } + + func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { + var adjustedPoint = windowTopLeftPoint + var adjustedHeight = height + + var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero + for screen in NSScreen.screens { + let frame = screen.visibleFrame + if windowTopLeftPoint.x >= frame.minX && + windowTopLeftPoint.x <= frame.maxX && + windowTopLeftPoint.y >= frame.minY && + windowTopLeftPoint.y <= frame.maxY { + screenFrame = frame + break + } + } + + if adjustedHeight > screenFrame.size.height / 2.0 { + adjustedHeight = 0.0 + } + + let windowSize = window?.frame.size ?? NSSize.zero + + // bottom beneath the screen? + if adjustedPoint.y - windowSize.height < screenFrame.minY { + adjustedPoint.y = windowTopLeftPoint.y + adjustedHeight + windowSize.height + } + + // top over the screen? + if adjustedPoint.y >= screenFrame.maxY { + adjustedPoint.y = screenFrame.maxY - 1.0 + } + + // right + if adjustedPoint.x + windowSize.width >= screenFrame.maxX { + adjustedPoint.x = screenFrame.maxX - windowSize.width + } + + // left + if adjustedPoint.x < screenFrame.minX { + adjustedPoint.x = screenFrame.minX + } + + window?.setFrameTopLeftPoint(adjustedPoint) + } + +} diff --git a/Source/CandidateUI/VTHorizontalCandidateController.h b/Source/CandidateUI/VTHorizontalCandidateController.h deleted file mode 100644 index cd396755..00000000 --- a/Source/CandidateUI/VTHorizontalCandidateController.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// VTHorizontalCandidateController.h -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (@ShikiSuen) @ vChewing -// -// 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 deleted file mode 100644 index 61cf9a1d..00000000 --- a/Source/CandidateUI/VTHorizontalCandidateController.m +++ /dev/null @@ -1,267 +0,0 @@ -// -// VTHorizontalCandidateController.m -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (@ShikiSuen) @ vChewing -// -// 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 = nil; - _prevPageButton = nil; - _nextPageButton = nil; -} - -- (id)init -{ - NSRect contentRect = NSMakeRect(128.0, 128.0, 0.0, 0.0); - NSUInteger styleMask = NSBorderlessWindowMask | NSNonactivatingPanelMask; - - NSWindow *panel = [[NSWindow alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; - [panel setLevel:kCGPopUpMenuWindowLevel]; - [panel setHasShadow:YES]; - [panel setOpaque:NO]; - [panel setBackgroundColor:[NSColor clearColor]]; - - 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(16.0, 20.0); - _nextPageButton = [[NSButton alloc] initWithFrame:contentRect]; - _prevPageButton = [[NSButton alloc] initWithFrame:contentRect]; - [_nextPageButton setButtonType:NSMomentaryLightButton]; - [_nextPageButton setBezelStyle:NSBezelStyleSmallSquare]; - [_nextPageButton setTitle:@"↓"]; - [_nextPageButton setTarget:self]; - [_nextPageButton setAction:@selector(pageButtonAction:)]; - [_nextPageButton setWantsLayer: YES]; - [_nextPageButton.layer setCornerRadius: 3]; - [_nextPageButton.layer setBorderColor: [NSColor clearColor].CGColor]; - [_nextPageButton.layer setBorderWidth: 3]; - [_nextPageButton.layer setBackgroundColor: [NSColor windowBackgroundColor].CGColor]; - - [_prevPageButton setButtonType:NSMomentaryLightButton]; - [_prevPageButton setBezelStyle:NSBezelStyleSmallSquare]; - [_prevPageButton setTitle:@"↑"]; - [_prevPageButton setTarget:self]; - [_prevPageButton setAction:@selector(pageButtonAction:)]; - [_prevPageButton setWantsLayer: YES]; - [_prevPageButton.layer setCornerRadius: 3]; - [_prevPageButton.layer setBorderColor: [NSColor clearColor].CGColor]; - [_prevPageButton.layer setBorderWidth: 3]; - [_prevPageButton.layer setBackgroundColor: [NSColor windowBackgroundColor].CGColor]; - - [[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]; - - 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 < 40.0) { - buttonRect.size.height = floor(newSize.height / 2); - } - else { - buttonRect.size.height = 20.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:NO]; - [_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 deleted file mode 100644 index f0d35fa8..00000000 --- a/Source/CandidateUI/VTHorizontalCandidateView.h +++ /dev/null @@ -1,59 +0,0 @@ -// -// VTHorizontalCandidateView.h -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (@ShikiSuen) @ vChewing -// -// 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; - 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; - -@property (readonly, nonatomic) NSSize sizeForView; -@property (assign, nonatomic) NSUInteger highlightedIndex; -@property (assign, nonatomic) SEL action; -@property (weak, nonatomic) id target; -@end diff --git a/Source/CandidateUI/VTHorizontalCandidateView.m b/Source/CandidateUI/VTHorizontalCandidateView.m deleted file mode 100644 index a26752d1..00000000 --- a/Source/CandidateUI/VTHorizontalCandidateView.m +++ /dev/null @@ -1,239 +0,0 @@ -// -// VTHorizontalCandidateView.m -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (@ShikiSuen) @ vChewing -// -// 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" - -// use these instead of MIN/MAX macro to keep compilers happy with pedantic warnings on -NS_INLINE CGFloat min(CGFloat a, CGFloat b) { return a < b ? a : b; } -NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } - -@implementation VTHorizontalCandidateView - -@synthesize highlightedIndex = _highlightedIndex; -@synthesize action = _action; -@synthesize target = _target; - -static NSColor *colorFromRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - return [NSColor colorWithDeviceRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:(a/255.0f)]; -} - -- (void)dealloc -{ - _keyLabels = nil; - _displayedCandidates = nil; - _keyLabelAttrDict = nil; - _candidateAttrDict = nil; - _elementWidths = nil; -} - -- (void)setKeyLabels:(NSArray *)labels displayedCandidates:(NSArray *)candidates -{ - NSUInteger count = min([labels count], [candidates count]); - _keyLabels = [labels subarrayWithRange:NSMakeRange(0, count)]; - _displayedCandidates = [candidates subarrayWithRange:NSMakeRange(0, count)]; - - 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]; - - 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]]; - } - - _elementWidths = newWidths; -} - -- (void)setKeyLabelFont:(NSFont *)labelFont candidateFont:(NSFont *)candidateFont -{ - NSColor *clrCandidateText = colorFromRGBA(233,233,233,255); - NSColor *clrCandidateTextIndex = colorFromRGBA(233,233,233,213); - NSMutableParagraphStyle *paraStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; - [paraStyle setAlignment:NSCenterTextAlignment]; - - _keyLabelAttrDict = [NSDictionary dictionaryWithObjectsAndKeys: - labelFont, NSFontAttributeName, - paraStyle, NSParagraphStyleAttributeName, - clrCandidateTextIndex, NSForegroundColorAttributeName, - nil]; - _candidateAttrDict = [NSDictionary dictionaryWithObjectsAndKeys: - candidateFont, NSFontAttributeName, - paraStyle, NSParagraphStyleAttributeName, - clrCandidateText, NSForegroundColorAttributeName, - nil]; - - CGFloat labelFontSize = [labelFont pointSize]; - CGFloat candidateFontSize = [candidateFont pointSize]; - CGFloat biggestSize = max(labelFontSize, candidateFontSize); - - _keyLabelHeight = ceil(labelFontSize * 1.20); - _candidateTextHeight = ceil(candidateFontSize * 1.20); - _cellPadding = ceil(biggestSize / 2.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 *clrCandidateSelectedBG = [NSColor alternateSelectedControlColor]; - NSColor *clrCandidateSelectedText = colorFromRGBA(233,233,233,255); - NSColor *clrCandidateWindowBorder = colorFromRGBA(255,255,255,75); - NSColor *clrCandidateWindowBG = colorFromRGBA(28,28,28,255); - NSColor *clrCandidateBG = colorFromRGBA(28,28,28,255); - - [self setWantsLayer: YES]; - [self.layer setBorderColor: [clrCandidateWindowBorder CGColor]]; - [self.layer setBorderWidth: 1]; - [self.layer setCornerRadius: 6]; - - NSRect bounds = [self bounds]; - - [clrCandidateWindowBG setFill]; - [NSBezierPath fillRect:bounds]; - - 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 + 1.0, _keyLabelHeight + 1.0); - NSRect candidateRect = NSMakeRect(accuWidth, _keyLabelHeight + 1.0, currentWidth + 1.0, _candidateTextHeight); - - if (index == _highlightedIndex) { - [clrCandidateSelectedBG setFill]; - } - else { - [clrCandidateBG setFill]; - } - - [NSBezierPath fillRect:labelRect]; - [[_keyLabels objectAtIndex:index] drawInRect:labelRect withAttributes:_keyLabelAttrDict]; - - if (index == _highlightedIndex) { - [clrCandidateSelectedBG setFill]; - activeCandidateAttr = [_candidateAttrDict mutableCopy]; - [(NSMutableDictionary *)activeCandidateAttr setObject:clrCandidateSelectedText forKey:NSForegroundColorAttributeName]; - } - else { - [clrCandidateBG 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; - } - - 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]; - -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Warc-performSelector-leaks" - if (triggerAction && _target && _action) { - [_target performSelector:_action withObject:self]; - } -# pragma clang diagnostic pop -} - -@end diff --git a/Source/CandidateUI/VTVerticalCandidateController.h b/Source/CandidateUI/VTVerticalCandidateController.h deleted file mode 100644 index 04bdb596..00000000 --- a/Source/CandidateUI/VTVerticalCandidateController.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// 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 deleted file mode 100644 index ba4c3239..00000000 --- a/Source/CandidateUI/VTVerticalCandidateController.m +++ /dev/null @@ -1,469 +0,0 @@ -// -// VTVerticalCandidateController.m -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (ShikiSuen) @ vChewing -// -// 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" - -// use these instead of MIN/MAX macro to keep compilers happy with pedantic warnings on -NS_INLINE CGFloat min(CGFloat a, CGFloat b) { return a < b ? a : b; } -NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } - -static const CGFloat kCandidateTextPadding = 24.0; -static const CGFloat kCandidateTextLeftMargin = 8.0; - -static NSColor *colorFromRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - return [NSColor colorWithDeviceRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:(a/255.0f)]; -} - -#if defined(__MAC_11_0) -static const CGFloat kCandidateTextPaddingWithMandatedTableViewPadding = 18.0; -static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; -#endif - -@interface VTVerticalCandidateController (Private) -- (void)rowDoubleClicked:(id)sender; -- (BOOL)scrollPageByOne:(BOOL)forward; -- (BOOL)moveSelectionByOne:(BOOL)forward; -- (void)layoutCandidateView; -@end - -@implementation VTVerticalCandidateController -{ - // Total padding added to the left and the right of the table view cell text. - CGFloat _candidateTextPadding; - - // The indent of the table view cell text from the left. - CGFloat _candidateTextLeftMargin; -} - - -- (id)init -{ - // NSColor *clrCandidateSelectedBG = [NSColor systemBlueColor]; - NSColor *clrCandidateSelectedText = [[NSColor whiteColor] colorWithAlphaComponent: 0.8]; - NSColor *clrCandidateWindowBorder = colorFromRGBA(255,255,255,75); - NSColor *clrCandidateWindowBG = colorFromRGBA(28,28,28,255); - // NSColor *clrCandidateBG = colorFromRGBA(28,28,28,255); - - NSRect contentRect = NSMakeRect(128.0, 128.0, 0.0, 0.0); - NSUInteger styleMask = NSBorderlessWindowMask | NSNonactivatingPanelMask; - NSView *panelView = [[NSView alloc] initWithFrame:contentRect]; - NSWindow *panel = [[NSWindow alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; - [panel setLevel:kCGPopUpMenuWindowLevel]; - [panel setContentView: panelView]; - [panel setHasShadow:YES]; - [panel setOpaque:NO]; - [panel setBackgroundColor: [NSColor clearColor]]; - [panel setOpaque:false]; - [panelView setWantsLayer: YES]; - [panelView.layer setBorderColor: [clrCandidateWindowBorder CGColor]]; - [panelView.layer setBorderWidth: 1]; - [panelView.layer setCornerRadius: 6]; - [panelView.layer setBackgroundColor: [clrCandidateWindowBG CGColor]]; - - self = [self initWithWindow:panel]; - if (self) { - contentRect.origin = NSMakePoint(0.0, 0.0); - - NSRect stripRect = contentRect; - stripRect.size.width = 10.0; - _keyLabelStripView = [[VTVerticalKeyLabelStripView alloc] initWithFrame:stripRect]; - [_keyLabelStripView setWantsLayer: YES]; - [_keyLabelStripView.layer setBorderWidth: 0]; - - [[panel contentView] addSubview:_keyLabelStripView]; - - NSRect scrollViewRect = contentRect; - scrollViewRect.origin.x = stripRect.size.width; - scrollViewRect.size.width -= stripRect.size.width; - - _scrollView = [[NSScrollView alloc] initWithFrame:scrollViewRect]; - [_scrollView setAutohidesScrollers: YES]; - [_scrollView setWantsLayer: YES]; - [_scrollView.layer setBorderWidth: 0]; - [_scrollView setDrawsBackground:NO]; - - // >=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"]; - [column setDataCell:[[NSTextFieldCell alloc] init]]; - [column setEditable:NO]; - [column.dataCell setTextColor: clrCandidateSelectedText]; - // [column.dataCell setSelectionColor: clrCandidateSelectedBG]; - - _candidateTextPadding = kCandidateTextPadding; - _candidateTextLeftMargin = kCandidateTextLeftMargin; - - [_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]; - [_tableView setBackgroundColor:[NSColor clearColor]]; - [_tableView setGridColor:[NSColor clearColor]]; - - #if defined(__MAC_11_0) - if (@available(macOS 11.0, *)) { - [_tableView setStyle:NSTableViewStyleFullWidth]; - _candidateTextPadding = kCandidateTextPaddingWithMandatedTableViewPadding; - _candidateTextLeftMargin = kCandidateTextLeftMarginWithMandatedTableViewPadding; - } - #endif - - [_scrollView setDocumentView:_tableView]; - [[panel contentView] addSubview:_scrollView]; - - _candidateTextParagraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; - [_candidateTextParagraphStyle setFirstLineHeadIndent:_candidateTextLeftMargin]; - [_candidateTextParagraphStyle setLineBreakMode:NSLineBreakByClipping]; - } - - return self; -} - -- (void)reloadData -{ - _maxCandidateAttrStringWidth = ceil([_candidateFont pointSize] * 2.0 + _candidateTextPadding); - - [_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)aNewIndex -{ - NSUInteger newIndex = aNewIndex; - - NSInteger selectedRow = [_tableView selectedRow]; - - NSUInteger labelCount = [_keyLabels count]; - NSUInteger itemCount = [_delegate candidateCountForController:self]; - - if (newIndex == NSUIntegerMax) { - if (itemCount == 0) { - [_tableView deselectAll:self]; - return; - } - newIndex = 0; - } - - 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 = @""; - - // rendering can occur when the delegate is already gone or data goes stale; in that case we ignore it - - if (row < [_delegate candidateCountForController:self]) { - candidate = [_delegate candidateController:self candidateAtIndex:row]; - } - - NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:candidate attributes:[NSDictionary dictionaryWithObjectsAndKeys:_candidateFont, NSFontAttributeName, _candidateTextParagraphStyle, NSParagraphStyleAttributeName, nil]]; - - // we do more work than what this method is expected to; normally not a good practice, but for the amount of data (9 to 10 rows max), we can afford the overhead - - // expand the window width if text overflows - NSRect boundingRect = [attrString boundingRectWithSize:NSMakeSize(10240.0, 10240.0) options:NSStringDrawingUsesLineFragmentOrigin]; - CGFloat textWidth = boundingRect.size.width + _candidateTextPadding; - 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 && newHilightIndex >= 0) { - _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]; - - // fix a subtle OS X "bug" that, since we force the scroller to appear, - // scrolling sometimes shows a temporarily "broken" scroll bar - // (but quickly disappears) - if ([_scrollView hasVerticalScroller]) { - [[_scrollView verticalScroller] setNeedsDisplay]; - } - } -} - -- (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 -{ - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(doLayoutCanaditeView) object:nil]; - [self performSelector:@selector(doLayoutCanaditeView) withObject:nil afterDelay:0.0]; -} - -- (void)doLayoutCanaditeView -{ - NSUInteger count = [_delegate candidateCountForController:self]; - if (!count) { - return; - } - - CGFloat candidateFontSize = ceil([_candidateFont 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]; - [verticalScroller setScrollerStyle:NSScrollerStyleOverlay]; - scrollerWidth = [NSScroller scrollerWidthForControlSize:controlSize scrollerStyle:NSScrollerStyleOverlay]; - } - - _keyLabelStripView.keyLabelFont = _keyLabelFont; - _keyLabelStripView.keyLabels = [_keyLabels subarrayWithRange:NSMakeRange(0, keyLabelCount)]; - _keyLabelStripView.labelOffsetY = (keyLabelFontSize >= candidateFontSize) ? 0.0 : floor((candidateFontSize - keyLabelFontSize) / 2.0); - - CGFloat rowHeight = ceil(fontSize * 1.25); - [_tableView setRowHeight:rowHeight]; - - CGFloat maxKeyLabelWidth = keyLabelFontSize; - NSDictionary *textAttr = [NSDictionary dictionaryWithObjectsAndKeys: - _keyLabelFont, NSFontAttributeName, - nil]; - NSSize boundingBox = NSMakeSize(1600.0, 1600.0); - for (NSString *label in _keyLabels) { - NSRect rect = [label boundingRectWithSize:boundingBox options:NSStringDrawingUsesLineFragmentOrigin attributes:textAttr]; - maxKeyLabelWidth = max(rect.size.width, maxKeyLabelWidth); - } - - CGFloat rowSpacing = [_tableView intercellSpacing].height; - CGFloat stripWidth = ceil(maxKeyLabelWidth); - CGFloat tableViewStartWidth = ceil(_maxCandidateAttrStringWidth + scrollerWidth);; - CGFloat windowWidth = stripWidth + tableViewStartWidth; - CGFloat windowHeight = keyLabelCount * (rowHeight + rowSpacing); - - NSRect 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, 0.0, tableViewStartWidth, windowHeight)]; - [[self window] setFrame:frameRect display:NO]; -} -@end diff --git a/Source/CandidateUI/VTVerticalCandidateTableView.h b/Source/CandidateUI/VTVerticalCandidateTableView.h deleted file mode 100644 index 47bb37ac..00000000 --- a/Source/CandidateUI/VTVerticalCandidateTableView.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// 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 deleted file mode 100644 index 990f134d..00000000 --- a/Source/CandidateUI/VTVerticalCandidateTableView.m +++ /dev/null @@ -1,38 +0,0 @@ -// -// 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 deleted file mode 100644 index 8439ba1a..00000000 --- a/Source/CandidateUI/VTVerticalKeyLabelStripView.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// 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 deleted file mode 100644 index 7469dc54..00000000 --- a/Source/CandidateUI/VTVerticalKeyLabelStripView.m +++ /dev/null @@ -1,114 +0,0 @@ -// -// VTVerticalKeyLabelStripView.h -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (ShikiSuen) @ vChewing -// -// 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; - -static NSColor *colorFromRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - return [NSColor colorWithDeviceRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:(a/255.0f)]; -} - -- (void)dealloc -{ - _keyLabelFont = nil; - _keyLabels = nil; -} - -- (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 clearColor] setFill]; - [NSBezierPath fillRect:bounds]; - - NSUInteger count = [_keyLabels count]; - if (!count) { - return; - } - - CGFloat cellHeight = bounds.size.height / count; - NSColor *clrCandidateBG = colorFromRGBA(28,28,28,255); - NSColor *clrCandidateTextIndex = colorFromRGBA(233,233,233,213); - NSColor *clrCandidateSelectedBG = [NSColor alternateSelectedControlColor]; - - NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; - [style setAlignment:NSCenterTextAlignment]; - - NSDictionary *textAttr = [NSDictionary dictionaryWithObjectsAndKeys: - _keyLabelFont, NSFontAttributeName, - clrCandidateTextIndex, 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); - - // fill in the last cell - if (index + 1 >= count) { - cellRect.size.height += 1.0; - } - - if (index == _highlightedIndex) { - [clrCandidateSelectedBG setFill]; - } - else { - [clrCandidateBG setFill]; - } - - [NSBezierPath fillRect:cellRect]; - - NSString *text = [_keyLabels objectAtIndex:index]; - [text drawInRect:textRect withAttributes:textAttr]; - } -} -@end diff --git a/Source/CandidateUI/VerticalCandidateController.swift b/Source/CandidateUI/VerticalCandidateController.swift new file mode 100644 index 00000000..1333831f --- /dev/null +++ b/Source/CandidateUI/VerticalCandidateController.swift @@ -0,0 +1,455 @@ +// +// VerticalCandidateController.swift +// +// Copyright (c) 2011 The McBopomofo Project. +// +// Contributors: +// Mengjuei Hsieh (@mjhsieh) +// Weizhong Yang (@zonble) +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// 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 + +fileprivate class VerticalKeyLabelStripView: NSView { + var keyLabelFont: NSFont = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize) + var labelOffsetY: CGFloat = 0 + var keyLabels: [String] = [] + var highlightedIndex: UInt = UInt.max + + override var isFlipped: Bool { + true + } + + override func draw(_ dirtyRect: NSRect) { + let bounds = self.bounds + NSColor.white.setFill() + NSBezierPath.fill(bounds) + + let count = UInt(keyLabels.count) + if count == 0 { + return + } + let cellHeight: CGFloat = bounds.size.height / CGFloat(count) + let black = NSColor.black + let darkGray = NSColor(deviceWhite: 0.7, alpha: 1.0) + let lightGray = NSColor(deviceWhite: 0.8, alpha: 1.0) + + let paraStyle = NSMutableParagraphStyle() + paraStyle.setParagraphStyle(NSParagraphStyle.default) + paraStyle.alignment = .center + + let textAttr: [NSAttributedString.Key: AnyObject] = [ + .font: keyLabelFont, + .foregroundColor: black, + .paragraphStyle: paraStyle] + for index in 0..= count { + cellRect.size.height += 1.0 + } + + (index == highlightedIndex ? darkGray : lightGray).setFill() + NSBezierPath.fill(cellRect) + let text = keyLabels[Int(index)] + (text as NSString).draw(in: textRect, withAttributes: textAttr) + } + } +} + +fileprivate class VerticalCandidateTableView: NSTableView { + override func adjustScroll(_ newVisible: NSRect) -> NSRect { + var scrollRect = newVisible + let rowHeightPlusSpacing = rowHeight + intercellSpacing.height + scrollRect.origin.y = (scrollRect.origin.y / rowHeightPlusSpacing) * rowHeightPlusSpacing + return scrollRect + } +} + +private let kCandidateTextPadding = 24.0 +private let kCandidateTextLeftMargin = 8.0 +private let kCandidateTextPaddingWithMandatedTableViewPadding = 18.0 +private let kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0 + + +@objc (VTVerticalCandidateController) +public class VerticalCandidateController: CandidateController { + private var keyLabelStripView: VerticalKeyLabelStripView + private var scrollView: NSScrollView + private var tableView: NSTableView + private var candidateTextParagraphStyle: NSMutableParagraphStyle + private var candidateTextPadding: CGFloat = kCandidateTextPadding + private var candidateTextLeftMargin: CGFloat = kCandidateTextLeftMargin + private var maxCandidateAttrStringWidth: CGFloat = 0 + + public init() { + var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) + let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] + let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) + panel.hasShadow = true + + contentRect.origin = NSPoint.zero + var stripRect = contentRect + stripRect.size.width = 10.0 + keyLabelStripView = VerticalKeyLabelStripView(frame: stripRect) + panel.contentView?.addSubview(keyLabelStripView) + + var scrollViewRect = contentRect + scrollViewRect.origin.x = stripRect.size.width + scrollViewRect.size.width -= stripRect.size.width + scrollView = NSScrollView(frame: scrollViewRect) + scrollView.verticalScrollElasticity = .none + + tableView = NSTableView(frame: contentRect) + let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "candidate")) + column.dataCell = NSTextFieldCell() + column.isEditable = false + + candidateTextPadding = kCandidateTextPadding + candidateTextLeftMargin = kCandidateTextLeftMargin + + tableView.addTableColumn(column) + tableView.intercellSpacing = NSSize(width: 0.0, height: 1.0) + tableView.headerView = nil + tableView.allowsMultipleSelection = false + tableView.allowsEmptySelection = false + + if #available(macOS 10.16, *) { + tableView.style = .fullWidth + candidateTextPadding = kCandidateTextPaddingWithMandatedTableViewPadding + candidateTextLeftMargin = kCandidateTextLeftMarginWithMandatedTableViewPadding + } + + scrollView.documentView = tableView + panel.contentView?.addSubview(scrollView) + + let paraStyle = NSMutableParagraphStyle() + paraStyle.setParagraphStyle(NSParagraphStyle.default) + paraStyle.firstLineHeadIndent = candidateTextLeftMargin + paraStyle.lineBreakMode = .byClipping + + candidateTextParagraphStyle = paraStyle + + super.init(window: panel) + tableView.dataSource = self + tableView.delegate = self + tableView.doubleAction = #selector(rowDoubleClicked(_:)) + tableView.target = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func reloadData() { + maxCandidateAttrStringWidth = ceil(candidateFont.pointSize * 2.0 + candidateTextPadding) + tableView.reloadData() + layoutCandidateView() + if delegate?.candidateCountForController(self) ?? 0 > 0 { + selectedCandidateIndex = 0 + } + } + + public override func showNextPage() -> Bool { + scrollPageByOne(true) + } + + public override func showPreviousPage() -> Bool { + scrollPageByOne(false) + } + + public override func highlightNextCandidate() -> Bool { + moveSelectionByOne(true) + } + + public override func highlightPreviousCandidate() -> Bool { + moveSelectionByOne(false) + } + + public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { + guard let delegate = delegate else { + return UInt.max + } + + let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin) + if firstVisibleRow != -1 { + let result = UInt(firstVisibleRow) + index + if result < delegate.candidateCountForController(self) { + return result + } + } + + return UInt.max + } + + public override var selectedCandidateIndex: UInt { + get { + let selectedRow = tableView.selectedRow + return selectedRow == -1 ? UInt.max : UInt(selectedRow) + + } + set { + guard let delegate = delegate else { + return + } + var newIndex = newValue + let selectedRow = tableView.selectedRow + let labelCount = keyLabels.count + let itemCount = delegate.candidateCountForController(self) + + if newIndex == UInt.max { + if itemCount == 0 { + tableView.deselectAll(self) + return + } + newIndex = 0 + } + + var lastVisibleRow = newValue + + if selectedRow != -1 && itemCount > 0 && itemCount > labelCount { + if newIndex > selectedRow && (Int(newIndex) - selectedRow) > 1 { + lastVisibleRow = min(newIndex + UInt(labelCount) - 1, itemCount - 1) + } + // no need to handle the backward case: (newIndex < selectedRow && selectedRow - newIndex > 1) + } + + if itemCount > labelCount { + tableView.scrollRowToVisible(Int(lastVisibleRow)) + } + tableView.selectRowIndexes(IndexSet(integer: Int(newIndex)), byExtendingSelection: false) + } + } +} + +extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegate { + + public func numberOfRows(in tableView: NSTableView) -> Int { + Int(delegate?.candidateCountForController(self) ?? 0) + } + + public func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { + guard let delegate = delegate else { + return nil + } + var candidate = "" + if row < delegate.candidateCountForController(self) { + candidate = delegate.candidateController(self, candidateAtIndex: UInt(row)) + } + let attrString = NSAttributedString(string: candidate, attributes: [ + .font: candidateFont, + .paragraphStyle: candidateTextParagraphStyle + ]) + + // we do more work than what this method is expected to; normally not a good practice, but for the amount of data (9 to 10 rows max), we can afford the overhead + + // expand the window width if text overflows + let boundingRect = attrString.boundingRect(with: NSSize(width: 10240.0, height: 10240.0), options: .usesLineFragmentOrigin) + let textWidth = boundingRect.size.width + candidateTextPadding + if textWidth > maxCandidateAttrStringWidth { + maxCandidateAttrStringWidth = textWidth + layoutCandidateView() + } + + // keep track of the highlighted index in the key label strip + let count = UInt(keyLabels.count) + let selectedRow = tableView.selectedRow + + if selectedRow != -1 { + var newHilightIndex = 0 + + if keyLabelStripView.highlightedIndex != -1 && + (row >= selectedRow + Int(count) || (selectedRow > count && row <= selectedRow - Int(count))) { + newHilightIndex = -1 + } else { + let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin) + newHilightIndex = selectedRow - firstVisibleRow + if newHilightIndex < -1 { + newHilightIndex = -1 + } + } + + if newHilightIndex != keyLabelStripView.highlightedIndex && newHilightIndex >= 0 { + keyLabelStripView.highlightedIndex = UInt(newHilightIndex) + keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame) + } + + } + return attrString + } + + public func tableViewSelectionDidChange(_ notification: Notification) { + let selectedRow = tableView.selectedRow + if selectedRow != -1 { + // keep track of the highlighted index in the key label strip + let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin) + keyLabelStripView.highlightedIndex = UInt(selectedRow - firstVisibleRow) + keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame) + + // fix a subtle OS X "bug" that, since we force the scroller to appear, + // scrolling sometimes shows a temporarily "broken" scroll bar + // (but quickly disappears) + if scrollView.hasVerticalScroller { + scrollView.verticalScroller?.setNeedsDisplay() + } + } + } + + @objc func rowDoubleClicked(_ sender: Any) { + let clickedRow = tableView.clickedRow + if clickedRow != -1 { + delegate?.candidateController(self, didSelectCandidateAtIndex: UInt(clickedRow)) + } + } + + func scrollPageByOne(_ forward: Bool) -> Bool { + guard let delegate = delegate else { + return false + } + let labelCount = UInt(keyLabels.count) + let itemCount = delegate.candidateCountForController(self) + if 0 == itemCount { + return false + } + if itemCount <= labelCount { + return false + } + + var newIndex = selectedCandidateIndex + if forward { + if newIndex == itemCount - 1 { + return false + } + newIndex = min(newIndex + labelCount, itemCount - 1) + } else { + if newIndex == 0 { + return false + } + + if newIndex < labelCount { + newIndex = 0 + } else { + newIndex -= labelCount + } + } + selectedCandidateIndex = newIndex + return true + } + + private func moveSelectionByOne(_ forward: Bool) -> Bool { + guard let delegate = delegate else { + return false + } + let itemCount = delegate.candidateCountForController(self) + if 0 == itemCount { + return false + } + var newIndex = selectedCandidateIndex + if forward { + if newIndex == itemCount - 1 { + return false + } + newIndex += 1 + } else { + if 0 == newIndex { + return false + } + newIndex -= 1 + } + selectedCandidateIndex = newIndex + return true + } + + private func layoutCandidateView() { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { [self] in + doLayoutCandidateView() + } + } + + private func doLayoutCandidateView() { + guard let delegate = delegate else { + return + } + let count = delegate.candidateCountForController(self) + if 0 == count { + return + } + + let candidateFontSize = ceil(candidateFont.pointSize) + let keyLabelFontSize = ceil(keyLabelFont.pointSize) + let fontSize = max(candidateFontSize, keyLabelFontSize) + + let controlSize: NSControl.ControlSize = fontSize > 36.0 ? .regular : .small + + var keyLabelCount = UInt(keyLabels.count) + var scrollerWidth: CGFloat = 0.0 + if count <= keyLabelCount { + keyLabelCount = count + scrollView.hasVerticalScroller = false + } else { + scrollView.hasVerticalScroller = true + let verticalScroller = scrollView.verticalScroller + verticalScroller?.controlSize = controlSize + verticalScroller?.scrollerStyle = .legacy + scrollerWidth = NSScroller.scrollerWidth(for: controlSize, scrollerStyle: .legacy) + } + + keyLabelStripView.keyLabelFont = keyLabelFont + keyLabelStripView.keyLabels = Array(keyLabels[0..= candidateFontSize) ? 0.0 : floor((candidateFontSize - keyLabelFontSize) / 2.0) + + let rowHeight = ceil(fontSize * 1.25) + tableView.rowHeight = rowHeight + + var maxKeyLabelWidth = keyLabelFontSize + let textAttr: [NSAttributedString.Key: AnyObject] = [.font: keyLabelFont] + let boundingBox = NSSize(width: 1600.0, height: 1600.0) + + for label in keyLabels { + let rect = (label as NSString).boundingRect(with: boundingBox, options: .usesLineFragmentOrigin, attributes: textAttr) + maxKeyLabelWidth = max(rect.size.width, maxKeyLabelWidth) + } + + let rowSpacing = tableView.intercellSpacing.height + let stripWidth = ceil(maxKeyLabelWidth * 1.20) + let tableViewStartWidth = ceil(maxCandidateAttrStringWidth + scrollerWidth) + let windowWidth = stripWidth + 1.0 + tableViewStartWidth + let windowHeight = CGFloat(keyLabelCount) * (rowHeight + rowSpacing) + + var frameRect = self.window?.frame ?? NSRect.zero + let 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.frame = NSRect(x: 0.0, y: 0.0, width: stripWidth, height: windowHeight) + scrollView.frame = NSRect(x: stripWidth + 1.0, y: 0.0, width: tableViewStartWidth, height: windowHeight) + self.window?.setFrame(frameRect, display: false) + } +} diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 1e398bdb..6692bfa7 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -43,8 +43,6 @@ #import "OVStringHelper.h" #import "OVUTF8Helper.h" #import "AppDelegate.h" -#import "VTHorizontalCandidateController.h" -#import "VTVerticalCandidateController.h" #import "OVNonModalAlertWindowController.h" #import "vChewing-Swift.h" diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index ae4bbaef..6e83dec4 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -12,6 +12,9 @@ 5B1958522788A2BF00FAEB14 /* MITLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* MITLicense.txt */; }; 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */; }; 5B58E87F278413E7003EA2AD /* MITLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* MITLicense.txt */; }; + 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */; }; + 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */; }; + 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */; }; 5BC3FB83278492DE0022E99A /* data-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC3FB82278492DE0022E99A /* data-chs.txt */; }; 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */; }; 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */; }; @@ -22,12 +25,6 @@ 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC815FC0D6400ABF4B3 /* main.m */; }; 6A0D4ED415FC0D6400ABF4B3 /* OVInputSourceHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */; }; 6A0D4ED515FC0D6400ABF4B3 /* PreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */; }; - 6A0D4EFE15FC0DA600ABF4B3 /* VTCandidateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EDA15FC0DA600ABF4B3 /* VTCandidateController.m */; }; - 6A0D4EFF15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EDC15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m */; }; - 6A0D4F0015FC0DA600ABF4B3 /* VTHorizontalCandidateView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EDE15FC0DA600ABF4B3 /* VTHorizontalCandidateView.m */; }; - 6A0D4F0115FC0DA600ABF4B3 /* VTVerticalCandidateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EE015FC0DA600ABF4B3 /* VTVerticalCandidateController.m */; }; - 6A0D4F0215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EE215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m */; }; - 6A0D4F0315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EE415FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m */; }; 6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */; }; 6A0D4F0915FC0DA600ABF4B3 /* Bopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */; }; 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */; }; @@ -91,6 +88,9 @@ 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D82763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D92763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.xib"; sourceTree = ""; }; + 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalCandidateController.swift; sourceTree = ""; }; + 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VTCandidateController.swift; sourceTree = ""; }; + 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalCandidateController.swift; sourceTree = ""; }; 5BC3FB82278492DE0022E99A /* data-chs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-chs.txt"; sourceTree = ""; }; 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = frmAboutWindow.h; sourceTree = ""; }; 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = frmAboutWindow.m; sourceTree = ""; }; @@ -113,18 +113,6 @@ 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OVInputSourceHelper.m; sourceTree = ""; }; 6A0D4ECB15FC0D6400ABF4B3 /* PreferencesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesWindowController.h; sourceTree = ""; }; 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesWindowController.m; sourceTree = ""; }; - 6A0D4ED915FC0DA600ABF4B3 /* VTCandidateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTCandidateController.h; sourceTree = ""; }; - 6A0D4EDA15FC0DA600ABF4B3 /* VTCandidateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTCandidateController.m; sourceTree = ""; }; - 6A0D4EDB15FC0DA600ABF4B3 /* VTHorizontalCandidateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTHorizontalCandidateController.h; sourceTree = ""; }; - 6A0D4EDC15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTHorizontalCandidateController.m; sourceTree = ""; }; - 6A0D4EDD15FC0DA600ABF4B3 /* VTHorizontalCandidateView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTHorizontalCandidateView.h; sourceTree = ""; }; - 6A0D4EDE15FC0DA600ABF4B3 /* VTHorizontalCandidateView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTHorizontalCandidateView.m; sourceTree = ""; }; - 6A0D4EDF15FC0DA600ABF4B3 /* VTVerticalCandidateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTVerticalCandidateController.h; sourceTree = ""; }; - 6A0D4EE015FC0DA600ABF4B3 /* VTVerticalCandidateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTVerticalCandidateController.m; sourceTree = ""; }; - 6A0D4EE115FC0DA600ABF4B3 /* VTVerticalCandidateTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTVerticalCandidateTableView.h; sourceTree = ""; }; - 6A0D4EE215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTVerticalCandidateTableView.m; sourceTree = ""; }; - 6A0D4EE315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTVerticalKeyLabelStripView.h; sourceTree = ""; }; - 6A0D4EE415FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTVerticalKeyLabelStripView.m; sourceTree = ""; }; 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Bopomofo.tiff; sourceTree = ""; }; 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "Bopomofo@2x.tiff"; sourceTree = ""; }; 6A0D4EF515FC0DA600ABF4B3 /* vChewing-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "vChewing-Info.plist"; sourceTree = ""; }; @@ -307,18 +295,9 @@ 6A0D4ED815FC0DA600ABF4B3 /* CandidateUI */ = { isa = PBXGroup; children = ( - 6A0D4ED915FC0DA600ABF4B3 /* VTCandidateController.h */, - 6A0D4EDA15FC0DA600ABF4B3 /* VTCandidateController.m */, - 6A0D4EDB15FC0DA600ABF4B3 /* VTHorizontalCandidateController.h */, - 6A0D4EDC15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m */, - 6A0D4EDD15FC0DA600ABF4B3 /* VTHorizontalCandidateView.h */, - 6A0D4EDE15FC0DA600ABF4B3 /* VTHorizontalCandidateView.m */, - 6A0D4EDF15FC0DA600ABF4B3 /* VTVerticalCandidateController.h */, - 6A0D4EE015FC0DA600ABF4B3 /* VTVerticalCandidateController.m */, - 6A0D4EE115FC0DA600ABF4B3 /* VTVerticalCandidateTableView.h */, - 6A0D4EE215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m */, - 6A0D4EE315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.h */, - 6A0D4EE415FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m */, + 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */, + 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */, + 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */, ); path = CandidateUI; sourceTree = ""; @@ -646,18 +625,15 @@ 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */, 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */, 6A0D4ED415FC0D6400ABF4B3 /* OVInputSourceHelper.m in Sources */, + 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */, 6A0D4ED515FC0D6400ABF4B3 /* PreferencesWindowController.m in Sources */, - 6A0D4EFE15FC0DA600ABF4B3 /* VTCandidateController.m in Sources */, - 6A0D4EFF15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m in Sources */, - 6A0D4F0015FC0DA600ABF4B3 /* VTHorizontalCandidateView.m in Sources */, 6AFF97F3253B299E007F1C49 /* OVNonModalAlertWindowController.m in Sources */, - 6A0D4F0115FC0DA600ABF4B3 /* VTVerticalCandidateController.m in Sources */, - 6A0D4F0215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m in Sources */, - 6A0D4F0315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m in Sources */, D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */, + 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */, + 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };