ctlIME // Tear ctlInputMethod into parts.

This commit is contained in:
ShikiSuen 2022-07-06 11:42:17 +08:00
parent 38b0478d39
commit 42cc923425
5 changed files with 564 additions and 474 deletions

View File

@ -41,7 +41,7 @@ class ctlInputMethod: IMKInputController {
static var areWeDeleting = false
///
private var ctlCandidateCurrent = ctlCandidateUniversal.init(.horizontal)
var ctlCandidateCurrent = ctlCandidateUniversal.init(.horizontal)
///
static let tooltipController = TooltipController()
@ -49,9 +49,9 @@ class ctlInputMethod: IMKInputController {
// MARK: -
/// 調
private var keyHandler: KeyHandler = .init()
var keyHandler: KeyHandler = .init()
///
private var state: InputStateProtocol = InputState.Empty()
var state: InputStateProtocol = InputState.Empty()
// MARK: -
@ -233,474 +233,3 @@ class ctlInputMethod: IMKInputController {
resetKeyHandler()
}
}
// MARK: - 調 (State Handling)
extension ctlInputMethod {
/// 調
///
///
///
/// - Parameter newState:
private func handle(state newState: InputStateProtocol) {
let prevState = state
state = newState
switch newState {
case let newState as InputState.Deactivated:
handle(state: newState, previous: prevState)
case let newState as InputState.Empty:
handle(state: newState, previous: prevState)
case let newState as InputState.EmptyIgnoringPreviousState:
handle(state: newState, previous: prevState)
case let newState as InputState.Committing:
handle(state: newState, previous: prevState)
case let newState as InputState.Inputting:
handle(state: newState, previous: prevState)
case let newState as InputState.Marking:
handle(state: newState, previous: prevState)
case let newState as InputState.ChoosingCandidate:
handle(state: newState, previous: prevState)
case let newState as InputState.AssociatedPhrases:
handle(state: newState, previous: prevState)
case let newState as InputState.SymbolTable:
handle(state: newState, previous: prevState)
default: break
}
}
/// .NotEmpty()
private func setInlineDisplayWithCursor() {
guard let state = state as? InputState.NotEmpty else {
clearInlineDisplay()
return
}
var identifier: AnyObject {
switch IME.currentInputMode {
case InputMode.imeModeCHS:
if #available(macOS 12.0, *) {
return "zh-Hans" as AnyObject
}
case InputMode.imeModeCHT:
if #available(macOS 12.0, *) {
return (mgrPrefs.shiftJISShinjitaiOutputEnabled || mgrPrefs.chineseConversionEnabled)
? "ja" as AnyObject : "zh-Hant" as AnyObject
}
default:
break
}
return "" as AnyObject
}
// [Shiki's Note] This might needs to be bug-reported to Apple:
// The LanguageIdentifier attribute of an NSAttributeString designated to
// IMK Client().SetMarkedText won't let the actual font respect your languageIdentifier
// settings. Still, this might behaves as Apple's current expectation, I'm afraid.
if #available(macOS 12.0, *) {
state.attributedString.setAttributes(
[.languageIdentifier: identifier],
range: NSRange(
location: 0,
length: state.composingBuffer.utf16.count
)
)
}
/// selectionRange
/// 0 replacementRangeNSNotFound
///
client().setMarkedText(
state.attributedString, selectionRange: NSRange(location: state.cursorIndex, length: 0),
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
)
}
/// .NotEmpty()
/// setInlineDisplayWithCursor()
private func clearInlineDisplay() {
client().setMarkedText(
"", selectionRange: NSRange(location: 0, length: 0),
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
)
}
///
/// IMK commitComposition
private func commit(text: String) {
let buffer = IME.kanjiConversionIfRequired(text)
if buffer.isEmpty {
return
}
client().insertText(
buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
)
}
private func handle(state: InputState.Deactivated, previous: InputStateProtocol) {
_ = state //
ctlCandidateCurrent.delegate = nil
ctlCandidateCurrent.visible = false
hideTooltip()
if let previous = previous as? InputState.NotEmpty {
commit(text: previous.composingBuffer)
}
clearInlineDisplay()
}
private func handle(state: InputState.Empty, previous: InputStateProtocol) {
_ = state //
ctlCandidateCurrent.visible = false
hideTooltip()
// .EmptyIgnoringPreviousState
if let previous = previous as? InputState.NotEmpty,
!(state is InputState.EmptyIgnoringPreviousState)
{
commit(text: previous.composingBuffer)
}
clearInlineDisplay()
}
private func handle(
state: InputState.EmptyIgnoringPreviousState, previous: InputStateProtocol
) {
_ = state //
_ = previous //
// previous state 使西 commit
handle(state: InputState.Empty())
}
private func handle(state: InputState.Committing, previous: InputStateProtocol) {
_ = previous //
ctlCandidateCurrent.visible = false
hideTooltip()
let textToCommit = state.textToCommit
if !textToCommit.isEmpty {
commit(text: textToCommit)
}
clearInlineDisplay()
}
private func handle(state: InputState.Inputting, previous: InputStateProtocol) {
_ = previous //
ctlCandidateCurrent.visible = false
hideTooltip()
let textToCommit = state.textToCommit
if !textToCommit.isEmpty {
commit(text: textToCommit)
}
setInlineDisplayWithCursor()
if !state.tooltip.isEmpty {
show(
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
cursorIndex: state.cursorIndex
)
}
}
private func handle(state: InputState.Marking, previous: InputStateProtocol) {
_ = previous //
ctlCandidateCurrent.visible = false
setInlineDisplayWithCursor()
if state.tooltip.isEmpty {
hideTooltip()
} else {
show(
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
cursorIndex: state.markerIndex
)
}
}
private func handle(state: InputState.ChoosingCandidate, previous: InputStateProtocol) {
_ = previous //
hideTooltip()
setInlineDisplayWithCursor()
show(candidateWindowWith: state)
}
private func handle(state: InputState.SymbolTable, previous: InputStateProtocol) {
_ = previous //
hideTooltip()
setInlineDisplayWithCursor()
show(candidateWindowWith: state)
}
private func handle(state: InputState.AssociatedPhrases, previous: InputStateProtocol) {
_ = previous //
hideTooltip()
clearInlineDisplay()
show(candidateWindowWith: state)
}
}
// MARK: -
extension ctlInputMethod {
private func show(candidateWindowWith state: InputStateProtocol) {
var isTypingVertical: Bool {
if let state = state as? InputState.ChoosingCandidate {
return state.isTypingVertical
} else if let state = state as? InputState.AssociatedPhrases {
return state.isTypingVertical
}
return false
}
var isCandidateWindowVertical: Bool {
var candidates: [String] = []
if let state = state as? InputState.ChoosingCandidate {
candidates = state.candidates
} else if let state = state as? InputState.AssociatedPhrases {
candidates = state.candidates
}
if isTypingVertical { return true }
// 使
candidates.sort {
$0.count > $1.count
}
// 使
// Beer emoji
let maxCandidatesPerPage = mgrPrefs.candidateKeys.count
let firstPageCandidates = candidates[0..<min(maxCandidatesPerPage, candidates.count)]
return firstPageCandidates.joined().count > Int(round(Double(maxCandidatesPerPage) * 1.8))
// true
}
ctlCandidateCurrent.delegate = nil
/// currentLayout
/// ctlCandidate() SymbolTable
///
/// layoutCandidateView
/// macOS 10.x SwiftUI
if isCandidateWindowVertical { // 使
ctlCandidateCurrent = .init(.vertical)
} else if mgrPrefs.useHorizontalCandidateList {
ctlCandidateCurrent = .init(.horizontal)
} else {
ctlCandidateCurrent = .init(.vertical)
}
// set the attributes for the candidate panel (which uses NSAttributedString)
let textSize = mgrPrefs.candidateListTextSize
let keyLabelSize = max(textSize / 2, mgrPrefs.minKeyLabelSize)
func labelFont(name: String?, size: CGFloat) -> NSFont {
if let name = name {
return NSFont(name: name, size: size) ?? NSFont.systemFont(ofSize: size)
}
return NSFont.systemFont(ofSize: size)
}
func candidateFont(name: String?, size: CGFloat) -> NSFont {
let currentMUIFont =
(keyHandler.inputMode == InputMode.imeModeCHS)
? "Sarasa Term Slab SC" : "Sarasa Term Slab TC"
var finalReturnFont =
NSFont(name: currentMUIFont, size: size) ?? NSFont.systemFont(ofSize: size)
// macOS 11 Big Sur macOS 12 Monterey 使
if #available(macOS 12.0, *) { finalReturnFont = NSFont.systemFont(ofSize: size) }
if let name = name {
return NSFont(name: name, size: size) ?? finalReturnFont
}
return finalReturnFont
}
ctlCandidateCurrent.keyLabelFont = labelFont(
name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize
)
ctlCandidateCurrent.candidateFont = candidateFont(
name: mgrPrefs.candidateTextFontName, size: textSize
)
let candidateKeys = mgrPrefs.candidateKeys
let keyLabels =
candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys)
let keyLabelSuffix = state is InputState.AssociatedPhrases ? "^" : ""
ctlCandidateCurrent.keyLabels = keyLabels.map {
CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix)
}
ctlCandidateCurrent.delegate = self
ctlCandidateCurrent.reloadData()
ctlCandidateCurrent.visible = true
var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0)
var cursor = 0
if let state = state as? InputState.ChoosingCandidate {
cursor = state.cursorIndex
if cursor == state.composingBuffer.count, cursor != 0 {
cursor -= 1
}
}
while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, cursor >= 0 {
client().attributes(
forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect
)
cursor -= 1
}
if isTypingVertical {
ctlCandidateCurrent.set(
windowTopLeftPoint: NSPoint(
x: lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, y: lineHeightRect.origin.y - 4.0
),
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0
)
} else {
ctlCandidateCurrent.set(
windowTopLeftPoint: NSPoint(x: lineHeightRect.origin.x, y: lineHeightRect.origin.y - 4.0),
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0
)
}
}
private func show(tooltip: String, composingBuffer: String, cursorIndex: Int) {
var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0)
var cursor = cursorIndex
if cursor == composingBuffer.count, cursor != 0 {
cursor -= 1
}
while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, cursor >= 0 {
client().attributes(
forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect
)
cursor -= 1
}
ctlInputMethod.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin)
}
private func hideTooltip() {
ctlInputMethod.tooltipController.hide()
}
}
// MARK: -
extension ctlInputMethod: KeyHandlerDelegate {
func ctlCandidate() -> ctlCandidate { ctlCandidateCurrent }
func keyHandler(
_: KeyHandler, didSelectCandidateAt index: Int,
ctlCandidate controller: ctlCandidate
) {
ctlCandidate(controller, didSelectCandidateAtIndex: index)
}
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputStateProtocol)
-> Bool
{
guard let state = state as? InputState.Marking else {
return false
}
if !state.validToWrite {
return false
}
let refInputModeReversed: InputMode =
(keyHandler.inputMode == InputMode.imeModeCHT)
? InputMode.imeModeCHS : InputMode.imeModeCHT
if !mgrLangModel.writeUserPhrase(
state.userPhrase, inputMode: keyHandler.inputMode,
areWeDuplicating: state.chkIfUserPhraseExists,
areWeDeleting: ctlInputMethod.areWeDeleting
)
|| !mgrLangModel.writeUserPhrase(
state.userPhraseConverted, inputMode: refInputModeReversed,
areWeDuplicating: false,
areWeDeleting: ctlInputMethod.areWeDeleting
)
{
return false
}
return true
}
}
// MARK: -
extension ctlInputMethod: ctlCandidateDelegate {
func candidateCountForController(_ controller: ctlCandidate) -> Int {
_ = controller //
if let state = state as? InputState.ChoosingCandidate {
return state.candidates.count
} else if let state = state as? InputState.AssociatedPhrases {
return state.candidates.count
}
return 0
}
func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: Int)
-> String
{
_ = controller //
if let state = state as? InputState.ChoosingCandidate {
return state.candidates[index]
} else if let state = state as? InputState.AssociatedPhrases {
return state.candidates[index]
}
return ""
}
func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: Int) {
_ = controller //
if let state = state as? InputState.SymbolTable,
let node = state.node.children?[index]
{
if let children = node.children, !children.isEmpty {
handle(state: InputState.Empty()) //
handle(
state: InputState.SymbolTable(node: node, isTypingVertical: state.isTypingVertical)
)
} else {
handle(state: InputState.Committing(textToCommit: node.title))
handle(state: InputState.Empty())
}
return
}
if let state = state as? InputState.ChoosingCandidate {
let selectedValue = state.candidates[index]
keyHandler.fixNode(value: selectedValue, respectCursorPushing: true)
let inputting = keyHandler.buildInputtingState
if mgrPrefs.useSCPCTypingMode {
keyHandler.clear()
let composingBuffer = inputting.composingBuffer
handle(state: InputState.Committing(textToCommit: composingBuffer))
if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState(
withKey: composingBuffer, isTypingVertical: state.isTypingVertical
), !associatePhrases.candidates.isEmpty
{
handle(state: associatePhrases)
} else {
handle(state: InputState.Empty())
}
} else {
handle(state: inputting)
}
return
}
if let state = state as? InputState.AssociatedPhrases {
let selectedValue = state.candidates[index]
handle(state: InputState.Committing(textToCommit: selectedValue))
if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState(
withKey: selectedValue, isTypingVertical: state.isTypingVertical
), !associatePhrases.candidates.isEmpty
{
handle(state: associatePhrases)
} else {
handle(state: InputState.Empty())
}
}
}
}

View File

@ -0,0 +1,152 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
// All possible vChewing-specific modifications are of:
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
/*
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:
1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. No trademark license is granted to use the trade names, trademarks, service
marks, or product names of Contributor, except as required to fulfill notice
requirements above.
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 Foundation
// MARK: - KeyHandler Delegate
extension ctlInputMethod: KeyHandlerDelegate {
func ctlCandidate() -> ctlCandidate { ctlCandidateCurrent }
func keyHandler(
_: KeyHandler, didSelectCandidateAt index: Int,
ctlCandidate controller: ctlCandidate
) {
ctlCandidate(controller, didSelectCandidateAtIndex: index)
}
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputStateProtocol)
-> Bool
{
guard let state = state as? InputState.Marking else {
return false
}
if !state.validToWrite {
return false
}
let refInputModeReversed: InputMode =
(keyHandler.inputMode == InputMode.imeModeCHT)
? InputMode.imeModeCHS : InputMode.imeModeCHT
if !mgrLangModel.writeUserPhrase(
state.userPhrase, inputMode: keyHandler.inputMode,
areWeDuplicating: state.chkIfUserPhraseExists,
areWeDeleting: ctlInputMethod.areWeDeleting
)
|| !mgrLangModel.writeUserPhrase(
state.userPhraseConverted, inputMode: refInputModeReversed,
areWeDuplicating: false,
areWeDeleting: ctlInputMethod.areWeDeleting
)
{
return false
}
return true
}
}
// MARK: - Candidate Controller Delegate
extension ctlInputMethod: ctlCandidateDelegate {
func candidateCountForController(_ controller: ctlCandidate) -> Int {
_ = controller //
if let state = state as? InputState.ChoosingCandidate {
return state.candidates.count
} else if let state = state as? InputState.AssociatedPhrases {
return state.candidates.count
}
return 0
}
func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: Int)
-> String
{
_ = controller //
if let state = state as? InputState.ChoosingCandidate {
return state.candidates[index]
} else if let state = state as? InputState.AssociatedPhrases {
return state.candidates[index]
}
return ""
}
func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: Int) {
_ = controller //
if let state = state as? InputState.SymbolTable,
let node = state.node.children?[index]
{
if let children = node.children, !children.isEmpty {
handle(state: InputState.Empty()) //
handle(
state: InputState.SymbolTable(node: node, isTypingVertical: state.isTypingVertical)
)
} else {
handle(state: InputState.Committing(textToCommit: node.title))
handle(state: InputState.Empty())
}
return
}
if let state = state as? InputState.ChoosingCandidate {
let selectedValue = state.candidates[index]
keyHandler.fixNode(value: selectedValue, respectCursorPushing: true)
let inputting = keyHandler.buildInputtingState
if mgrPrefs.useSCPCTypingMode {
keyHandler.clear()
let composingBuffer = inputting.composingBuffer
handle(state: InputState.Committing(textToCommit: composingBuffer))
if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState(
withKey: composingBuffer, isTypingVertical: state.isTypingVertical
), !associatePhrases.candidates.isEmpty
{
handle(state: associatePhrases)
} else {
handle(state: InputState.Empty())
}
} else {
handle(state: inputting)
}
return
}
if let state = state as? InputState.AssociatedPhrases {
let selectedValue = state.candidates[index]
handle(state: InputState.Committing(textToCommit: selectedValue))
if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState(
withKey: selectedValue, isTypingVertical: state.isTypingVertical
), !associatePhrases.candidates.isEmpty
{
handle(state: associatePhrases)
} else {
handle(state: InputState.Empty())
}
}
}
}

View File

@ -0,0 +1,169 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
// All possible vChewing-specific modifications are of:
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
/*
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:
1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. No trademark license is granted to use the trade names, trademarks, service
marks, or product names of Contributor, except as required to fulfill notice
requirements above.
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
import Foundation
// MARK: - Tooltip Display and Candidate Display Methods
extension ctlInputMethod {
func show(tooltip: String, composingBuffer: String, cursorIndex: Int) {
var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0)
var cursor = cursorIndex
if cursor == composingBuffer.count, cursor != 0 {
cursor -= 1
}
while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, cursor >= 0 {
client().attributes(
forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect
)
cursor -= 1
}
ctlInputMethod.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin)
}
func show(candidateWindowWith state: InputStateProtocol) {
var isTypingVertical: Bool {
if let state = state as? InputState.ChoosingCandidate {
return state.isTypingVertical
} else if let state = state as? InputState.AssociatedPhrases {
return state.isTypingVertical
}
return false
}
var isCandidateWindowVertical: Bool {
var candidates: [String] = []
if let state = state as? InputState.ChoosingCandidate {
candidates = state.candidates
} else if let state = state as? InputState.AssociatedPhrases {
candidates = state.candidates
}
if isTypingVertical { return true }
// 使
candidates.sort {
$0.count > $1.count
}
// 使
// Beer emoji
let maxCandidatesPerPage = mgrPrefs.candidateKeys.count
let firstPageCandidates = candidates[0..<min(maxCandidatesPerPage, candidates.count)]
return firstPageCandidates.joined().count > Int(round(Double(maxCandidatesPerPage) * 1.8))
// true
}
ctlCandidateCurrent.delegate = nil
/// currentLayout
/// ctlCandidate() SymbolTable
///
/// layoutCandidateView
/// macOS 10.x SwiftUI
if isCandidateWindowVertical { // 使
ctlCandidateCurrent = .init(.vertical)
} else if mgrPrefs.useHorizontalCandidateList {
ctlCandidateCurrent = .init(.horizontal)
} else {
ctlCandidateCurrent = .init(.vertical)
}
// set the attributes for the candidate panel (which uses NSAttributedString)
let textSize = mgrPrefs.candidateListTextSize
let keyLabelSize = max(textSize / 2, mgrPrefs.minKeyLabelSize)
func labelFont(name: String?, size: CGFloat) -> NSFont {
if let name = name {
return NSFont(name: name, size: size) ?? NSFont.systemFont(ofSize: size)
}
return NSFont.systemFont(ofSize: size)
}
func candidateFont(name: String?, size: CGFloat) -> NSFont {
let currentMUIFont =
(keyHandler.inputMode == InputMode.imeModeCHS)
? "Sarasa Term Slab SC" : "Sarasa Term Slab TC"
var finalReturnFont =
NSFont(name: currentMUIFont, size: size) ?? NSFont.systemFont(ofSize: size)
// macOS 11 Big Sur macOS 12 Monterey 使
if #available(macOS 12.0, *) { finalReturnFont = NSFont.systemFont(ofSize: size) }
if let name = name {
return NSFont(name: name, size: size) ?? finalReturnFont
}
return finalReturnFont
}
ctlCandidateCurrent.keyLabelFont = labelFont(
name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize
)
ctlCandidateCurrent.candidateFont = candidateFont(
name: mgrPrefs.candidateTextFontName, size: textSize
)
let candidateKeys = mgrPrefs.candidateKeys
let keyLabels =
candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys)
let keyLabelSuffix = state is InputState.AssociatedPhrases ? "^" : ""
ctlCandidateCurrent.keyLabels = keyLabels.map {
CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix)
}
ctlCandidateCurrent.delegate = self
ctlCandidateCurrent.reloadData()
ctlCandidateCurrent.visible = true
var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0)
var cursor = 0
if let state = state as? InputState.ChoosingCandidate {
cursor = state.cursorIndex
if cursor == state.composingBuffer.count, cursor != 0 {
cursor -= 1
}
}
while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, cursor >= 0 {
client().attributes(
forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect
)
cursor -= 1
}
if isTypingVertical {
ctlCandidateCurrent.set(
windowTopLeftPoint: NSPoint(
x: lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, y: lineHeightRect.origin.y - 4.0
),
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0
)
} else {
ctlCandidateCurrent.set(
windowTopLeftPoint: NSPoint(x: lineHeightRect.origin.x, y: lineHeightRect.origin.y - 4.0),
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0
)
}
}
}

View File

@ -0,0 +1,228 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
// All possible vChewing-specific modifications are of:
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
/*
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:
1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. No trademark license is granted to use the trade names, trademarks, service
marks, or product names of Contributor, except as required to fulfill notice
requirements above.
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 Foundation
// MARK: - 調 (State Handling)
extension ctlInputMethod {
/// 調
///
///
///
/// - Parameter newState:
func handle(state newState: InputStateProtocol) {
let prevState = state
state = newState
switch newState {
case let newState as InputState.Deactivated:
handle(state: newState, previous: prevState)
case let newState as InputState.Empty:
handle(state: newState, previous: prevState)
case let newState as InputState.EmptyIgnoringPreviousState:
handle(state: newState, previous: prevState)
case let newState as InputState.Committing:
handle(state: newState, previous: prevState)
case let newState as InputState.Inputting:
handle(state: newState, previous: prevState)
case let newState as InputState.Marking:
handle(state: newState, previous: prevState)
case let newState as InputState.ChoosingCandidate:
handle(state: newState, previous: prevState)
case let newState as InputState.AssociatedPhrases:
handle(state: newState, previous: prevState)
case let newState as InputState.SymbolTable:
handle(state: newState, previous: prevState)
default: break
}
}
/// .NotEmpty()
private func setInlineDisplayWithCursor() {
guard let state = state as? InputState.NotEmpty else {
clearInlineDisplay()
return
}
var identifier: AnyObject {
switch IME.currentInputMode {
case InputMode.imeModeCHS:
if #available(macOS 12.0, *) {
return "zh-Hans" as AnyObject
}
case InputMode.imeModeCHT:
if #available(macOS 12.0, *) {
return (mgrPrefs.shiftJISShinjitaiOutputEnabled || mgrPrefs.chineseConversionEnabled)
? "ja" as AnyObject : "zh-Hant" as AnyObject
}
default:
break
}
return "" as AnyObject
}
// [Shiki's Note] This might needs to be bug-reported to Apple:
// The LanguageIdentifier attribute of an NSAttributeString designated to
// IMK Client().SetMarkedText won't let the actual font respect your languageIdentifier
// settings. Still, this might behaves as Apple's current expectation, I'm afraid.
if #available(macOS 12.0, *) {
state.attributedString.setAttributes(
[.languageIdentifier: identifier],
range: NSRange(
location: 0,
length: state.composingBuffer.utf16.count
)
)
}
/// selectionRange
/// 0 replacementRangeNSNotFound
///
client().setMarkedText(
state.attributedString, selectionRange: NSRange(location: state.cursorIndex, length: 0),
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
)
}
/// .NotEmpty()
/// setInlineDisplayWithCursor()
private func clearInlineDisplay() {
client().setMarkedText(
"", selectionRange: NSRange(location: 0, length: 0),
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
)
}
///
/// IMK commitComposition
private func commit(text: String) {
let buffer = IME.kanjiConversionIfRequired(text)
if buffer.isEmpty {
return
}
client().insertText(
buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
)
}
private func handle(state: InputState.Deactivated, previous: InputStateProtocol) {
_ = state //
ctlCandidateCurrent.delegate = nil
ctlCandidateCurrent.visible = false
ctlInputMethod.tooltipController.hide()
if let previous = previous as? InputState.NotEmpty {
commit(text: previous.composingBuffer)
}
clearInlineDisplay()
}
private func handle(state: InputState.Empty, previous: InputStateProtocol) {
_ = state //
ctlCandidateCurrent.visible = false
ctlInputMethod.tooltipController.hide()
// .EmptyIgnoringPreviousState
if let previous = previous as? InputState.NotEmpty,
!(state is InputState.EmptyIgnoringPreviousState)
{
commit(text: previous.composingBuffer)
}
clearInlineDisplay()
}
private func handle(
state: InputState.EmptyIgnoringPreviousState, previous: InputStateProtocol
) {
_ = state //
_ = previous //
// previous state 使西 commit
handle(state: InputState.Empty())
}
private func handle(state: InputState.Committing, previous: InputStateProtocol) {
_ = previous //
ctlCandidateCurrent.visible = false
ctlInputMethod.tooltipController.hide()
let textToCommit = state.textToCommit
if !textToCommit.isEmpty {
commit(text: textToCommit)
}
clearInlineDisplay()
}
private func handle(state: InputState.Inputting, previous: InputStateProtocol) {
_ = previous //
ctlCandidateCurrent.visible = false
ctlInputMethod.tooltipController.hide()
let textToCommit = state.textToCommit
if !textToCommit.isEmpty {
commit(text: textToCommit)
}
setInlineDisplayWithCursor()
if !state.tooltip.isEmpty {
show(
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
cursorIndex: state.cursorIndex
)
}
}
private func handle(state: InputState.Marking, previous: InputStateProtocol) {
_ = previous //
ctlCandidateCurrent.visible = false
setInlineDisplayWithCursor()
if state.tooltip.isEmpty {
ctlInputMethod.tooltipController.hide()
} else {
show(
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
cursorIndex: state.markerIndex
)
}
}
private func handle(state: InputState.ChoosingCandidate, previous: InputStateProtocol) {
_ = previous //
ctlInputMethod.tooltipController.hide()
setInlineDisplayWithCursor()
show(candidateWindowWith: state)
}
private func handle(state: InputState.SymbolTable, previous: InputStateProtocol) {
_ = previous //
ctlInputMethod.tooltipController.hide()
setInlineDisplayWithCursor()
show(candidateWindowWith: state)
}
private func handle(state: InputState.AssociatedPhrases, previous: InputStateProtocol) {
_ = previous //
ctlInputMethod.tooltipController.hide()
clearInlineDisplay()
show(candidateWindowWith: state)
}
}

View File

@ -9,6 +9,9 @@
/* Begin PBXBuildFile section */
5B0AF8B527B2C8290096FE54 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0AF8B427B2C8290096FE54 /* StringExtension.swift */; };
5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B11328827B94CFB00E58451 /* AppleKeyboardConverter.swift */; };
5B21176C287539BB000443A9 /* ctlInputMethod_HandleStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */; };
5B21176E28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */; };
5B21177028753B9D000443A9 /* ctlInputMethod_Delegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21176F28753B9D000443A9 /* ctlInputMethod_Delegates.swift */; };
5B242403284B0D6500520FE4 /* ctlCandidateUniversal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B242402284B0D6500520FE4 /* ctlCandidateUniversal.swift */; };
5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */; };
5B38F59A281E2E49007D5F5D /* 6_Unigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */; };
@ -199,6 +202,9 @@
5B18BA7227C7BD8B0056EB19 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.txt; sourceTree = "<group>"; };
5B18BA7327C7BD8C0056EB19 /* LICENSE-JPN.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "LICENSE-JPN.txt"; sourceTree = "<group>"; };
5B18BA7427C7BD8C0056EB19 /* LICENSE-CHT.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "LICENSE-CHT.txt"; sourceTree = "<group>"; };
5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_HandleStates.swift; sourceTree = "<group>"; };
5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_HandleDisplay.swift; sourceTree = "<group>"; };
5B21176F28753B9D000443A9 /* ctlInputMethod_Delegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Delegates.swift; sourceTree = "<group>"; };
5B242402284B0D6500520FE4 /* ctlCandidateUniversal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlCandidateUniversal.swift; sourceTree = "<group>"; };
5B2DB17127AF8771006D874E /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = Data/Makefile; sourceTree = "<group>"; };
5B2F2BB3286216A500B8557B /* vChewingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = vChewingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@ -446,6 +452,9 @@
children = (
5B11328827B94CFB00E58451 /* AppleKeyboardConverter.swift */,
D4A13D5927A59D5C003BE359 /* ctlInputMethod_Core.swift */,
5B21176F28753B9D000443A9 /* ctlInputMethod_Delegates.swift */,
5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */,
5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */,
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */,
D4E569DA27A34CC100AC2CEF /* CTools.h */,
D4E569DB27A34CC100AC2CEF /* CTools.m */,
@ -1141,6 +1150,8 @@
5BA9FD4827FEF3C9002DE248 /* PreferencesWindowController.swift in Sources */,
5BD0113B28180D6100609769 /* LMInstantiator.swift in Sources */,
D4E569DC27A34D0E00AC2CEF /* CTools.m in Sources */,
5B21177028753B9D000443A9 /* ctlInputMethod_Delegates.swift in Sources */,
5B21176E28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift in Sources */,
5B84579F2871AD2200C93B01 /* HotenkaChineseConverter.swift in Sources */,
5B887F302826AEA400B6651E /* lmCoreEX.swift in Sources */,
5BA9FD4627FEF3C9002DE248 /* Container.swift in Sources */,
@ -1163,6 +1174,7 @@
5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */,
5B54E743283A7D89001ECBDC /* lmCoreNS.swift in Sources */,
5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */,
5B21176C287539BB000443A9 /* ctlInputMethod_HandleStates.swift in Sources */,
5B38F59B281E2E49007D5F5D /* 7_KeyValuePaired.swift in Sources */,
5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */,
5B38F5A4281E2E49007D5F5D /* 5_LanguageModel.swift in Sources */,