Converts VerticalCandidateController to Swift.
This commit is contained in:
parent
5aafe64751
commit
a97cc5ca6c
|
@ -44,6 +44,7 @@
|
||||||
6AE210B315FC63CC003659FE /* PlainBopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6AE210B115FC63CC003659FE /* PlainBopomofo@2x.tiff */; };
|
6AE210B315FC63CC003659FE /* PlainBopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6AE210B115FC63CC003659FE /* PlainBopomofo@2x.tiff */; };
|
||||||
6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */; };
|
6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */; };
|
||||||
D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */; };
|
D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */; };
|
||||||
|
D427F75F278C74B7004A2160 /* VerticalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427F75E278C74B7004A2160 /* VerticalCandidateController.swift */; };
|
||||||
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; };
|
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; };
|
||||||
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; };
|
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; };
|
||||||
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; };
|
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; };
|
||||||
|
@ -178,6 +179,7 @@
|
||||||
6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NonModalAlertWindowController.xib; sourceTree = "<group>"; };
|
6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NonModalAlertWindowController.xib; sourceTree = "<group>"; };
|
||||||
D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "McBopomofo-Bridging-Header.h"; sourceTree = "<group>"; };
|
D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "McBopomofo-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenCCBridge.swift; sourceTree = "<group>"; };
|
D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenCCBridge.swift; sourceTree = "<group>"; };
|
||||||
|
D427F75E278C74B7004A2160 /* VerticalCandidateController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalCandidateController.swift; sourceTree = "<group>"; };
|
||||||
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
|
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
|
||||||
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; };
|
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; };
|
||||||
D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = "<group>"; };
|
D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = "<group>"; };
|
||||||
|
@ -248,7 +250,6 @@
|
||||||
6A0D4F4715FC0EB900ABF4B3 /* Resources */,
|
6A0D4F4715FC0EB900ABF4B3 /* Resources */,
|
||||||
6A0D4EC315FC0D6400ABF4B3 /* AppDelegate.h */,
|
6A0D4EC315FC0D6400ABF4B3 /* AppDelegate.h */,
|
||||||
6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */,
|
6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */,
|
||||||
6A0D4EF615FC0DA600ABF4B3 /* McBopomofo-Prefix.pch */,
|
|
||||||
6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */,
|
6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */,
|
||||||
6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */,
|
6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */,
|
||||||
6A0D4EC815FC0D6400ABF4B3 /* main.m */,
|
6A0D4EC815FC0D6400ABF4B3 /* main.m */,
|
||||||
|
@ -256,6 +257,7 @@
|
||||||
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */,
|
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */,
|
||||||
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */,
|
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */,
|
||||||
D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */,
|
D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */,
|
||||||
|
6A0D4EF615FC0DA600ABF4B3 /* McBopomofo-Prefix.pch */,
|
||||||
D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */,
|
D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */,
|
||||||
);
|
);
|
||||||
path = Source;
|
path = Source;
|
||||||
|
@ -266,6 +268,7 @@
|
||||||
children = (
|
children = (
|
||||||
D47F7DD9278C32CD002F9DD7 /* CandidateController.swift */,
|
D47F7DD9278C32CD002F9DD7 /* CandidateController.swift */,
|
||||||
D47F7DDB278C39EC002F9DD7 /* HorizontalCandidateController.swift */,
|
D47F7DDB278C39EC002F9DD7 /* HorizontalCandidateController.swift */,
|
||||||
|
D427F75E278C74B7004A2160 /* VerticalCandidateController.swift */,
|
||||||
6A0D4ED915FC0DA600ABF4B3 /* VTCandidateController.h */,
|
6A0D4ED915FC0DA600ABF4B3 /* VTCandidateController.h */,
|
||||||
6A0D4EDA15FC0DA600ABF4B3 /* VTCandidateController.m */,
|
6A0D4EDA15FC0DA600ABF4B3 /* VTCandidateController.m */,
|
||||||
6A0D4EDB15FC0DA600ABF4B3 /* VTHorizontalCandidateController.h */,
|
6A0D4EDB15FC0DA600ABF4B3 /* VTHorizontalCandidateController.h */,
|
||||||
|
@ -583,6 +586,7 @@
|
||||||
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */,
|
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */,
|
||||||
D47F7DDC278C39EC002F9DD7 /* HorizontalCandidateController.swift in Sources */,
|
D47F7DDC278C39EC002F9DD7 /* HorizontalCandidateController.swift in Sources */,
|
||||||
6A0D4F0015FC0DA600ABF4B3 /* VTHorizontalCandidateView.m in Sources */,
|
6A0D4F0015FC0DA600ABF4B3 /* VTHorizontalCandidateView.m in Sources */,
|
||||||
|
D427F75F278C74B7004A2160 /* VerticalCandidateController.swift in Sources */,
|
||||||
6A0D4F0115FC0DA600ABF4B3 /* VTVerticalCandidateController.m in Sources */,
|
6A0D4F0115FC0DA600ABF4B3 /* VTVerticalCandidateController.m in Sources */,
|
||||||
6A0D4F0215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m in Sources */,
|
6A0D4F0215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m in Sources */,
|
||||||
6A0D4F0315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m in Sources */,
|
6A0D4F0315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m in Sources */,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class HorizontalCandidateView: NSView {
|
fileprivate class HorizontalCandidateView: NSView {
|
||||||
var highlightedIndex: UInt = 0
|
var highlightedIndex: UInt = 0
|
||||||
var action: Selector?
|
var action: Selector?
|
||||||
weak var target: AnyObject?
|
weak var target: AnyObject?
|
||||||
|
@ -159,6 +159,7 @@ class HorizontalCandidateView: NSView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc(HorizontalCandidateController)
|
||||||
public class HorizontalCandidateController : CandidateController {
|
public class HorizontalCandidateController : CandidateController {
|
||||||
private var candidateView: HorizontalCandidateView
|
private var candidateView: HorizontalCandidateView
|
||||||
private var prevPageButton: NSButton
|
private var prevPageButton: NSButton
|
||||||
|
@ -295,7 +296,7 @@ public class HorizontalCandidateController : CandidateController {
|
||||||
|
|
||||||
extension HorizontalCandidateController {
|
extension HorizontalCandidateController {
|
||||||
|
|
||||||
var pageCount: UInt {
|
private var pageCount: UInt {
|
||||||
guard let delegate = delegate else {
|
guard let delegate = delegate else {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -304,7 +305,7 @@ extension HorizontalCandidateController {
|
||||||
return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0)
|
return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func layoutCandidateView() {
|
private func layoutCandidateView() {
|
||||||
guard let delegate = delegate else {
|
guard let delegate = delegate else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -363,7 +364,7 @@ extension HorizontalCandidateController {
|
||||||
self.candidateView.setNeedsDisplay(candidateView.bounds)
|
self.candidateView.setNeedsDisplay(candidateView.bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func pageButtonAction(_ sender: Any) {
|
@objc fileprivate func pageButtonAction(_ sender: Any) {
|
||||||
guard let sender = sender as? NSButton else {
|
guard let sender = sender as? NSButton else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -374,7 +375,7 @@ extension HorizontalCandidateController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func candidateViewMouseDidClick(_ sender: Any) {
|
@objc fileprivate func candidateViewMouseDidClick(_ sender: Any) {
|
||||||
delegate?.candidateController(self, didSelectCandidateAtIndex: self.selectedCandidateIndex)
|
delegate?.candidateController(self, didSelectCandidateAtIndex: self.selectedCandidateIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,423 @@
|
||||||
|
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 {
|
||||||
|
let textRect = NSRect(x: 0.0, y: CGFloat(index) * cellHeight + labelOffsetY, width: bounds.size.width, height: cellHeight - labelOffsetY)
|
||||||
|
var cellRect = NSRect(x: 0.0, y: CGFloat(index) * cellHeight, width: bounds.size.width, height: cellHeight - 1);
|
||||||
|
|
||||||
|
if index + 1 >= 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(VerticalCandidateController)
|
||||||
|
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.self
|
||||||
|
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
|
||||||
|
|
||||||
|
scrollView.documentView = tableView
|
||||||
|
panel.contentView?.addSubview(scrollView)
|
||||||
|
|
||||||
|
|
||||||
|
let paraStyle = NSMutableParagraphStyle()
|
||||||
|
paraStyle.setParagraphStyle(NSParagraphStyle.default)
|
||||||
|
paraStyle.firstLineHeadIndent = candidateTextLeftMargin
|
||||||
|
paraStyle.lineBreakMode = .byClipping
|
||||||
|
|
||||||
|
candidateTextParagraphStyle = paraStyle
|
||||||
|
|
||||||
|
|
||||||
|
if #available(macOS 10.16, *) {
|
||||||
|
tableView.style = .fullWidth
|
||||||
|
candidateTextPadding = kCandidateTextPaddingWithMandatedTableViewPadding
|
||||||
|
candidateTextLeftMargin = kCandidateTextLeftMarginWithMandatedTableViewPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
self.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public 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
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc 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
|
||||||
|
self.doLayoutCanaditeView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func doLayoutCanaditeView() {
|
||||||
|
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..<Int(keyLabelCount)])
|
||||||
|
keyLabelStripView.labelOffsetY = (keyLabelFontSize >= 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,7 +39,6 @@
|
||||||
#import "OVStringHelper.h"
|
#import "OVStringHelper.h"
|
||||||
#import "OVUTF8Helper.h"
|
#import "OVUTF8Helper.h"
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
#import "OVNonModalAlertWindowController.h"
|
|
||||||
#import "VTHorizontalCandidateController.h"
|
#import "VTHorizontalCandidateController.h"
|
||||||
#import "VTVerticalCandidateController.h"
|
#import "VTVerticalCandidateController.h"
|
||||||
#import "McBopomofo-Swift.h"
|
#import "McBopomofo-Swift.h"
|
||||||
|
@ -1589,7 +1588,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
||||||
NSLog(@"openUserPhrases called");
|
NSLog(@"openUserPhrases called");
|
||||||
if (!LTCheckIfUserLanguageModelFileExists()) {
|
if (!LTCheckIfUserLanguageModelFileExists()) {
|
||||||
NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), LTUserDataFolderPath()];
|
NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), LTUserDataFolderPath()];
|
||||||
[[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil];
|
[[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue