diff --git a/AUTHORS b/AUTHORS index 4b267489..b2aec08d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -17,13 +17,9 @@ $ Contributors and volunteers of the upstream repo, having no responsibility in - Zonble Yang: - McBopomofo for macOS 2.x architect. - Voltaire candidate window MK2 (massively modified as MK3 in vChewing by Shiki Suen). - - Notifier window. - App-style installer (only preserved for developer purposes). - - PrefMgr (userdefaults manager). - Mengjuei Hsieh: - McBopomofo for macOS 1.x main developer and architect. - - The original C++ version of the User Override Module. - - Shiki Suen is trying to rewrite this module in Swift (and CSharp) with further development. Although there is no Lukhnos's codes left in the current repository, we still credit him for his previous work: @@ -32,6 +28,8 @@ Although there is no Lukhnos's codes left in the current repository, we still cr - Shiki Suen's Megrez engine (MIT License) is basically a Swift-rewritten version of Gramambular 2 with further development. - Developer of Mandarin syllable composer (removed since vChewing 1.5.7). - Shiki Suen's Tekkon engine is made from scratch and has no relationship to Mandarin syllable composer. + - Developer of the original C++ version of the User Override Module MK2. + - Shiki Suen is trying to rewrite this module in Swift (and CSharp) with further development. $ Special thanks to: diff --git a/Installer/InstallerBg.png b/Installer/InstallerBg.png index ce23aa14..4d9ab6fd 100644 Binary files a/Installer/InstallerBg.png and b/Installer/InstallerBg.png differ diff --git a/Installer/InstallerBg@2x.png b/Installer/InstallerBg@2x.png index e3b0f8ec..28bf1a8f 100644 Binary files a/Installer/InstallerBg@2x.png and b/Installer/InstallerBg@2x.png differ diff --git a/Packages/Fuziki_NSAttributedTextView/Sources/NSAttributedTextView/NSAttributedTextView.swift b/Packages/Fuziki_NSAttributedTextView/Sources/NSAttributedTextView/NSAttributedTextView.swift index b11cec88..7978874c 100644 --- a/Packages/Fuziki_NSAttributedTextView/Sources/NSAttributedTextView/NSAttributedTextView.swift +++ b/Packages/Fuziki_NSAttributedTextView/Sources/NSAttributedTextView/NSAttributedTextView.swift @@ -48,7 +48,7 @@ public class NSAttributedTextView: NSView { } public var direction: writingDirection = .horizontal - public var fontSize: CGFloat = NSFont.systemFontSize { + public var fontSize: Double = NSFont.systemFontSize { didSet { attributes[.font] = NSFont.systemFont(ofSize: fontSize) } diff --git a/Packages/OpenVanilla_Voltaire/.gitignore b/Packages/OpenVanilla_Voltaire/.gitignore new file mode 100644 index 00000000..3b298120 --- /dev/null +++ b/Packages/OpenVanilla_Voltaire/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/OpenVanilla_Voltaire/Package.swift b/Packages/OpenVanilla_Voltaire/Package.swift new file mode 100644 index 00000000..2bb82029 --- /dev/null +++ b/Packages/OpenVanilla_Voltaire/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "Voltaire", + platforms: [ + .macOS(.v10_11) + ], + products: [ + .library( + name: "Voltaire", + targets: ["Voltaire"] + ) + ], + dependencies: [ + .package(path: "../vChewing_CandidateWindow"), + .package(path: "../vChewing_Shared"), + ], + targets: [ + .target( + name: "Voltaire", + dependencies: [ + .product(name: "Shared", package: "vChewing_Shared"), + .product(name: "CandidateWindow", package: "vChewing_CandidateWindow"), + ] + ) + ] +) diff --git a/Packages/OpenVanilla_Voltaire/README.md b/Packages/OpenVanilla_Voltaire/README.md new file mode 100644 index 00000000..933d23a4 --- /dev/null +++ b/Packages/OpenVanilla_Voltaire/README.md @@ -0,0 +1,48 @@ +# Voltaire + +Voltaire is a UI component replacement for Apple's InputMethodKit (IMK). The +built-in candidate UI has a limited interaction model and is not very +extensible nor customizable. + +The project also comes with a test app that demonstrates the features of the +UI component. + +Voltaire MK3 provides following new features comparing to MK1 and MK2: + +1. A brand-new candidate window design conforming to the latest macOS UI design style, plus a floating label indicating the current page number of candidates (a frequently-asked feature by vChewing users). +2. One class for both vertical and horizontal display purposes. This can be specified as a parameter on init(). + +3. Can specify whether default candidate fonts conform to MOE glyphs standard or continental glyphs standard, requiring macOS 12 Monterey or later. +4. Can specify whether page buttons are shown. + +Regarding the horizontal and vertical layout: + +1. It is recommended to use init() in lieu of directly changing the layout variable since the latter doesn't redraw page buttons correctly. +2. Vertical candidate mode doesn't support scrolling. This is a deliberated design. + +--- + +Copyrights: +``` +- (c) 2022 Shiki Suen for all modifications introduced to Voltaire MK3. +- (c) 2021 Zonble Yang for rewriting Voltaire MK2 in Swift. +- (c) 2012 Lukhnos Liu for Voltaire MK1 development in Objective-C. + +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. +``` diff --git a/Source/Modules/UIModules/CandidateUI/ctlCandidateUniversal.swift b/Packages/OpenVanilla_Voltaire/Sources/Voltaire/CtlCandidateUniversal.swift similarity index 82% rename from Source/Modules/UIModules/CandidateUI/ctlCandidateUniversal.swift rename to Packages/OpenVanilla_Voltaire/Sources/Voltaire/CtlCandidateUniversal.swift index 330110df..858d215f 100644 --- a/Source/Modules/UIModules/CandidateUI/ctlCandidateUniversal.swift +++ b/Packages/OpenVanilla_Voltaire/Sources/Voltaire/CtlCandidateUniversal.swift @@ -10,6 +10,10 @@ // 將之前 Zonble 重寫的 Voltaire 選字窗隔的橫向版本與縱向版本合併到同一個型別實體內。 +import CandidateWindow +import Cocoa +import Shared + private class vwrCandidateUniversal: NSView { var highlightedIndex: Int = 0 { didSet { highlightedIndex = min(max(highlightedIndex, 0), dispCandidatesWithLabels.count - 1) } @@ -17,22 +21,23 @@ private class vwrCandidateUniversal: NSView { var action: Selector? weak var target: AnyObject? + weak var controller: AnyObject? var isVerticalLayout = false - var fractionFontSize: CGFloat = 12.0 + var fractionFontSize: Double = 12.0 private var keyLabels: [String] = [] private var displayedCandidates: [String] = [] private var dispCandidatesWithLabels: [String] = [] - private var keyLabelHeight: CGFloat = 0 - private var keyLabelWidth: CGFloat = 0 - private var candidateTextHeight: CGFloat = 0 - private var cellPadding: CGFloat = 0 + private var keyLabelHeight: Double = 0 + private var keyLabelWidth: Double = 0 + private var candidateTextHeight: Double = 0 + private var cellPadding: Double = 0 private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:] private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] - private var windowWidth: CGFloat = 0 // 縱排專用 - private var elementWidths: [CGFloat] = [] - private var elementHeights: [CGFloat] = [] // 縱排專用 + private var windowWidth: Double = 0 // 縱排專用 + private var elementWidths: [Double] = [] + private var elementHeights: [Double] = [] // 縱排專用 private var trackingHighlightedIndex: Int = .max { didSet { trackingHighlightedIndex = max(trackingHighlightedIndex, 0) } } @@ -50,17 +55,17 @@ private class vwrCandidateUniversal: NSView { result.width = windowWidth result.height = elementHeights.reduce(0, +) case false: - result.width = elementWidths.reduce(0, +) + CGFloat(elementWidths.count) + result.width = elementWidths.reduce(0, +) + Double(elementWidths.count) result.height = candidateTextHeight + cellPadding } } return result } - @objc(setKeyLabels:displayedCandidates:) func set(keyLabels labels: [String], displayedCandidates candidates: [String]) { + guard let delegate = (controller as? CtlCandidateUniversal)?.delegate else { return } let candidates = candidates.map { theCandidate -> String in - let theConverted = ChineseConverter.kanjiConversionIfRequired(theCandidate) + let theConverted = delegate.kanjiConversionIfRequired(theCandidate) return (theCandidate == theConverted) ? theCandidate : "\(theConverted)(\(theCandidate))" } @@ -69,9 +74,9 @@ private class vwrCandidateUniversal: NSView { displayedCandidates = Array(candidates[0.. Bool { - guard delegate != nil else { return false } + guard let delegate = delegate else { return false } if pageCount == 1 { return highlightNextCandidate() } - if currentPageIndex + 1 >= pageCount { IMEApp.buzz() } + if currentPageIndex + 1 >= pageCount { delegate.buzz() } currentPageIndex = (currentPageIndex + 1 >= pageCount) ? 0 : currentPageIndex + 1 if currentPageIndex == pageCount - 1 { candidateView.highlightedIndex = min(lastPageContentCount - 1, candidateView.highlightedIndex) @@ -444,9 +418,9 @@ public class ctlCandidateUniversal: ctlCandidate { } @discardableResult override public func showPreviousPage() -> Bool { - guard delegate != nil else { return false } + guard let delegate = delegate else { return false } if pageCount == 1 { return highlightPreviousCandidate() } - if currentPageIndex == 0 { IMEApp.buzz() } + if currentPageIndex == 0 { delegate.buzz() } currentPageIndex = (currentPageIndex == 0) ? pageCount - 1 : currentPageIndex - 1 if currentPageIndex == pageCount - 1 { candidateView.highlightedIndex = min(lastPageContentCount - 1, candidateView.highlightedIndex) @@ -459,7 +433,7 @@ public class ctlCandidateUniversal: ctlCandidate { @discardableResult override public func highlightNextCandidate() -> Bool { guard let delegate = delegate else { return false } selectedCandidateIndex = - (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) + (selectedCandidateIndex + 1 >= delegate.candidatePairs().count) ? 0 : selectedCandidateIndex + 1 return true } @@ -468,7 +442,7 @@ public class ctlCandidateUniversal: ctlCandidate { guard let delegate = delegate else { return false } selectedCandidateIndex = (selectedCandidateIndex == 0) - ? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1 + ? delegate.candidatePairs().count - 1 : selectedCandidateIndex - 1 return true } @@ -478,7 +452,7 @@ public class ctlCandidateUniversal: ctlCandidate { } let result = currentPageIndex * keyLabels.count + index - return result < delegate.candidateCountForController(self) ? result : Int.max + return result < delegate.candidatePairs().count ? result : Int.max } override public var selectedCandidateIndex: Int { @@ -490,7 +464,7 @@ public class ctlCandidateUniversal: ctlCandidate { return } let keyLabelCount = keyLabels.count - if newValue < delegate.candidateCountForController(self) { + if newValue < delegate.candidatePairs().count { currentPageIndex = newValue / keyLabelCount candidateView.highlightedIndex = newValue % keyLabelCount layoutCandidateView() @@ -499,12 +473,12 @@ public class ctlCandidateUniversal: ctlCandidate { } } -extension ctlCandidateUniversal { +extension CtlCandidateUniversal { private var pageCount: Int { guard let delegate = delegate else { return 0 } - let totalCount = delegate.candidateCountForController(self) + let totalCount = delegate.candidatePairs().count let keyLabelCount = keyLabels.count return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0) } @@ -513,24 +487,22 @@ extension ctlCandidateUniversal { guard let delegate = delegate else { return 0 } - let totalCount = delegate.candidateCountForController(self) + let totalCount = delegate.candidatePairs().count let keyLabelCount = keyLabels.count return totalCount % keyLabelCount } private func layoutCandidateView() { - guard let delegate = delegate else { - return - } + guard let delegate = delegate, let window = window else { return } candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont) var candidates = [(String, String)]() - let count = delegate.candidateCountForController(self) + let count = delegate.candidatePairs().count let keyLabelCount = keyLabels.count let begin = currentPageIndex * keyLabelCount for index in begin.. 1, PrefMgr.shared.showPageButtonsInCandidateWindow { + if pageCount > 1, showPageButtons { var buttonRect = nextPageButton.frame - let spacing: CGFloat = 0.0 + let spacing = 0.0 if currentLayout == .horizontal { buttonRect.size.height = floor(newSize.height / 2) } - let buttonOriginY: CGFloat = { + let buttonOriginY: Double = { if currentLayout == .vertical { return counterHeight } @@ -582,12 +554,12 @@ extension ctlCandidateUniversal { rect.size.height += 3 rect.size.width += 4 - let rectOriginY: CGFloat = + let rectOriginY: Double = (currentLayout == .horizontal) ? (newSize.height - rect.height) / 2 : counterHeight - let rectOriginX: CGFloat = - PrefMgr.shared.showPageButtonsInCandidateWindow + let rectOriginX: Double = + showPageButtons ? newSize.width : newSize.width + 4 rect.origin = NSPoint(x: rectOriginX, y: rectOriginY) @@ -598,12 +570,12 @@ extension ctlCandidateUniversal { pageCounterLabel.isHidden = true } - frameRect = window?.frame ?? NSRect.seniorTheBeast + frameRect = window.frame let topLeftPoint = NSPoint(x: frameRect.origin.x, y: frameRect.origin.y + frameRect.size.height) frameRect.size = newSize frameRect.origin = NSPoint(x: topLeftPoint.x, y: topLeftPoint.y - frameRect.size.height) - window?.setFrame(frameRect, display: false) + window.setFrame(frameRect, display: false) candidateView.setNeedsDisplay(candidateView.bounds) } @@ -619,6 +591,6 @@ extension ctlCandidateUniversal { } @objc private func candidateViewMouseDidClick(_: Any) { - delegate?.candidateSelected(at: selectedCandidateIndex) + delegate?.candidatePairSelected(at: selectedCandidateIndex) } } diff --git a/Packages/Sindresorhus_Preferences/Source/Preferences/Container.swift b/Packages/Sindresorhus_Preferences/Source/Preferences/Container.swift index 39bcd60f..c99bce24 100755 --- a/Packages/Sindresorhus_Preferences/Source/Preferences/Container.swift +++ b/Packages/Sindresorhus_Preferences/Source/Preferences/Container.swift @@ -54,7 +54,7 @@ extension Preferences { } } .modifier(Section.LabelWidthModifier(maximumWidth: $maximumLabelWidth)) - .frame(width: CGFloat(contentWidth), alignment: .leading) + .frame(width: Double(contentWidth), alignment: .leading) .padding(.vertical, 20) .padding(.horizontal, 30) } @@ -65,9 +65,9 @@ extension Preferences { if index != sections.count - 1, sections[index].bottomDivider { Divider() // Strangely doesn't work without width being specified. Probably because of custom alignment. - .frame(width: CGFloat(contentWidth), height: 20) + .frame(width: Double(contentWidth), height: 20) .alignmentGuide(.preferenceSectionLabel) { - $0[.leading] + CGFloat(max(minimumLabelWidth, maximumLabelWidth)) + $0[.leading] + Double(max(minimumLabelWidth, maximumLabelWidth)) } } } diff --git a/Packages/Sindresorhus_Preferences/Source/Preferences/SegmentedControlStyleViewController.swift b/Packages/Sindresorhus_Preferences/Source/Preferences/SegmentedControlStyleViewController.swift index 0aebd1d1..13119ad4 100755 --- a/Packages/Sindresorhus_Preferences/Source/Preferences/SegmentedControlStyleViewController.swift +++ b/Packages/Sindresorhus_Preferences/Source/Preferences/SegmentedControlStyleViewController.swift @@ -77,8 +77,8 @@ final class SegmentedControlStyleViewController: NSViewController, PreferencesSt ) }() - let segmentBorderWidth = CGFloat(preferencePanes.count) + 1 - let segmentWidth = segmentSize.width * CGFloat(preferencePanes.count) + segmentBorderWidth + let segmentBorderWidth = Double(preferencePanes.count) + 1 + let segmentWidth = segmentSize.width * Double(preferencePanes.count) + segmentBorderWidth let segmentHeight = segmentSize.height segmentedControl.frame = CGRect(x: 0, y: 0, width: segmentWidth, height: segmentHeight) diff --git a/Packages/vChewing_CandidateWindow/.gitignore b/Packages/vChewing_CandidateWindow/.gitignore new file mode 100644 index 00000000..3b298120 --- /dev/null +++ b/Packages/vChewing_CandidateWindow/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/vChewing_CandidateWindow/Package.swift b/Packages/vChewing_CandidateWindow/Package.swift new file mode 100644 index 00000000..0ca9b8be --- /dev/null +++ b/Packages/vChewing_CandidateWindow/Package.swift @@ -0,0 +1,26 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "CandidateWindow", + platforms: [ + .macOS(.v10_11) + ], + products: [ + .library( + name: "CandidateWindow", + targets: ["CandidateWindow"] + ) + ], + dependencies: [ + .package(path: "../vChewing_Shared") + ], + targets: [ + .target( + name: "CandidateWindow", + dependencies: [ + .product(name: "Shared", package: "vChewing_Shared") + ] + ) + ] +) diff --git a/Packages/vChewing_CandidateWindow/README.md b/Packages/vChewing_CandidateWindow/README.md new file mode 100644 index 00000000..628d5b76 --- /dev/null +++ b/Packages/vChewing_CandidateWindow/README.md @@ -0,0 +1,13 @@ +# CandidateWindow + +用以定義與威注音的選字窗有關的基礎內容,目前尚未完工。 + +``` +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// 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 defined in MIT License. +``` diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CtlCandidate.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CtlCandidate.swift new file mode 100644 index 00000000..ef5509c9 --- /dev/null +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CtlCandidate.swift @@ -0,0 +1,147 @@ +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// 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 defined in MIT License. + +import Cocoa +import Shared + +open class CtlCandidate: NSWindowController, CtlCandidateProtocol { + open var showPageButtons: Bool = false + open var currentLayout: CandidateLayout = .horizontal + open var locale: String = "" + open var useLangIdentifier: Bool = false + + open func highlightedColor() -> NSColor { + var result = NSColor.alternateSelectedControlColor + var colorBlendAmount: Double = NSApplication.isDarkMode ? 0.3 : 0.0 + if #available(macOS 10.14, *), !NSApplication.isDarkMode, locale == "zh-Hant" { + colorBlendAmount = 0.15 + } + // The background color of the highlightened candidate + switch locale { + case "zh-Hans": + result = NSColor.systemRed + case "zh-Hant": + result = NSColor.systemBlue + case "ja": + result = NSColor.systemBrown + default: break + } + var blendingAgainstTarget: NSColor = NSApplication.isDarkMode ? NSColor.black : NSColor.white + if #unavailable(macOS 10.14) { + colorBlendAmount = 0.3 + blendingAgainstTarget = NSColor.white + } + return result.blended(withFraction: colorBlendAmount, of: blendingAgainstTarget)! + } + + open weak var delegate: CtlCandidateDelegate? { + didSet { + reloadData() + } + } + + open var windowTopLeftPoint: NSPoint { + get { + guard let frameRect = window?.frame else { return NSPoint.zero } + return NSPoint(x: frameRect.minX, y: frameRect.maxY) + } + set { + DispatchQueue.main.async { + self.set(windowTopLeftPoint: newValue, bottomOutOfScreenAdjustmentHeight: 0) + } + } + } + + open var selectedCandidateIndex: Int = .max + open var visible = false { + didSet { + NSObject.cancelPreviousPerformRequests(withTarget: self) + DispatchQueue.main.async { [self] in + _ = visible ? window?.orderFront(self) : window?.orderOut(self) + } + } + } + + public required init(_: CandidateLayout = .horizontal) { + super.init(window: .init()) + visible = false + } + + /// Sets the location of the candidate window. + /// + /// Please note that the method has side effects that modifies + /// `windowTopLeftPoint` to make the candidate window to stay in at least + /// in a screen. + /// + /// - Parameters: + /// - windowTopLeftPoint: The given location. + /// - height: The height that helps the window not to be out of the bottom + /// of a screen. + public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: Double) { + DispatchQueue.main.async { [self] in + guard let window = window, var screenFrame = NSScreen.main?.visibleFrame else { return } + let windowSize = window.frame.size + + var adjustedPoint = windowTopLeftPoint + var delta = heightDelta + for frame in NSScreen.screens.map(\.visibleFrame).filter({ $0.contains(windowTopLeftPoint) }) { + screenFrame = frame + break + } + + if delta > screenFrame.size.height / 2.0 { delta = 0.0 } + + if adjustedPoint.y < screenFrame.minY + windowSize.height { + adjustedPoint.y = windowTopLeftPoint.y + windowSize.height + delta + } + adjustedPoint.y = min(adjustedPoint.y, screenFrame.maxY - 1.0) + adjustedPoint.x = min(max(adjustedPoint.x, screenFrame.minX), screenFrame.maxX - windowSize.width - 1.0) + + window.setFrameTopLeftPoint(adjustedPoint) + } + } + + // MARK: - Contents that are not needed to be implemented here. + + @available(*, unavailable) + public required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open var keyLabels: [CandidateKeyLabel] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] + .map { + CandidateKeyLabel(key: $0, displayedText: $0) + } + + open var candidateFont = NSFont.systemFont(ofSize: 18) + open var keyLabelFont = NSFont.monospacedDigitSystemFont(ofSize: 14, weight: .medium) + + open var tooltip: String = "" + + @discardableResult open func highlightNextCandidate() -> Bool { + false + } + + @discardableResult open func highlightPreviousCandidate() -> Bool { + false + } + + @discardableResult open func showNextPage() -> Bool { + false + } + + @discardableResult open func showPreviousPage() -> Bool { + false + } + + open func candidateIndexAtKeyLabelIndex(_: Int) -> Int { + Int.max + } + + open func reloadData() {} +} diff --git a/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator.swift b/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator.swift index 90a5c0f7..0efc45ae 100644 --- a/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator.swift +++ b/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMInstantiator.swift @@ -221,6 +221,15 @@ extension vChewingLM { return !unigramsFor(key: key).isEmpty } + /// 根據給定的索引鍵和資料值,確認是否有該具體的資料值在庫。 + /// - Parameters: + /// - key: 索引鍵。 + /// - value: 資料值。 + /// - Returns: 是否在庫。 + public func hasKeyValuePairFor(key: String, value: String) -> Bool { + unigramsFor(key: key).map(\.value).contains(value) + } + /// 給定讀音字串,讓 LMI 給出對應的經過處理的單元圖陣列。 /// - Parameter key: 給定的讀音字串。 /// - Returns: 對應的經過處理的單元圖陣列。 diff --git a/Packages/vChewing_Megrez/README.md b/Packages/vChewing_Megrez/README.md index 1bbe1572..34fd4f5c 100644 --- a/Packages/vChewing_Megrez/README.md +++ b/Packages/vChewing_Megrez/README.md @@ -13,7 +13,7 @@ Megrez Engine is a module made for processing lingual data of an input method. T ### §1. 初期化 -在你的 ctlInputMethod (InputMethodController) 或者 KeyHandler 內初期化一份 Megrez.Compositor 組字器副本(這裡將該副本命名為「`compositor`」)。由於 Megrez.Compositor 的型別是 Struct 型別(為了讓 Compositor 可以 deep copy),所以其副本可以用 var 來宣告。 +在你的 IMKInputController 或者 KeyHandler 內初期化一份 Megrez.Compositor 組字器副本(這裡將該副本命名為「`compositor`」)。由於 Megrez.Compositor 的型別是 Struct 型別(為了讓 Compositor 可以 deep copy),所以其副本可以用 var 來宣告。 以 KeyHandler 為例: ```swift @@ -24,17 +24,17 @@ class KeyHandler { } ``` -以 ctlInputMethod 為例: +以 IMKInputController 為例: ```swift -@objc(ctlInputMethod) // 根據 info.plist 內的情況來確定型別的命名 -class ctlInputMethod: IMKInputController { +@objc(IMKMyInputController) // 根據 info.plist 內的情況來確定型別的命名 +class IMKMyInputController: IMKInputController { // 先設定好變數 var compositor: Megrez.Compositor = .init() ... } ``` -由於 Swift 會在某個大副本(KeyHandler 或者 ctlInputMethod 副本)被銷毀的時候自動銷毀其中的全部副本,所以 Megrez.Compositor 的副本初期化沒必要寫在 init() 當中。但你很可能會想在 init() 時指定 Tekkon.Composer 所對接的語言模組型別、以及其可以允許的最大詞長。 +由於 Swift 會在某個大副本(KeyHandler 或者 IMKInputController 副本)被銷毀的時候自動銷毀其中的全部副本,所以 Megrez.Compositor 的副本初期化沒必要寫在 init() 當中。但你很可能會想在 init() 時指定 Tekkon.Composer 所對接的語言模組型別、以及其可以允許的最大詞長。 這裡就需要在 init() 時使用參數: ```swift diff --git a/Packages/vChewing_NotifierUI/.gitignore b/Packages/vChewing_NotifierUI/.gitignore new file mode 100644 index 00000000..3b298120 --- /dev/null +++ b/Packages/vChewing_NotifierUI/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/vChewing_NotifierUI/Package.swift b/Packages/vChewing_NotifierUI/Package.swift new file mode 100644 index 00000000..45cda7d5 --- /dev/null +++ b/Packages/vChewing_NotifierUI/Package.swift @@ -0,0 +1,22 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "NotifierUI", + platforms: [ + .macOS(.v10_11) + ], + products: [ + .library( + name: "NotifierUI", + targets: ["NotifierUI"] + ) + ], + dependencies: [], + targets: [ + .target( + name: "NotifierUI", + dependencies: [] + ) + ] +) diff --git a/Packages/vChewing_NotifierUI/README.md b/Packages/vChewing_NotifierUI/README.md new file mode 100644 index 00000000..eb361d73 --- /dev/null +++ b/Packages/vChewing_NotifierUI/README.md @@ -0,0 +1,13 @@ +# NotifierUI + +威注音的飄雲通知視窗。 + +``` +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// 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 defined in MIT License. +``` diff --git a/Packages/vChewing_NotifierUI/Sources/NotifierUI/NotifierUI.swift b/Packages/vChewing_NotifierUI/Sources/NotifierUI/NotifierUI.swift new file mode 100644 index 00000000..7fe0fc21 --- /dev/null +++ b/Packages/vChewing_NotifierUI/Sources/NotifierUI/NotifierUI.swift @@ -0,0 +1,155 @@ +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// 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 defined in MIT License. + +import Cocoa + +public class Notifier: NSWindowController { + public static func notify(message: String) { + Self.message = message + } + + static var message: String = "" { + didSet { + if !Self.message.isEmpty { + Self.message = Notifier(message).blankValue + } + } + } + + // MARK: - Private Declarations + + private static var instanceStack: [Notifier] = [] + private let blankValue = "" + + @discardableResult private init(_ message: String) { + let rawMessage = message.replacingOccurrences(of: "\n", with: "") + guard let screenRect = NSScreen.main?.visibleFrame, !rawMessage.isEmpty else { + super.init(window: nil) + return + } + let kLargeFontSize: Double = 17 + let kSmallFontSize: Double = 15 + let messageArray = message.components(separatedBy: "\n") + + let paraStyle = NSMutableParagraphStyle() + paraStyle.setParagraphStyle(NSParagraphStyle.default) + paraStyle.alignment = .center + let attrTitle: [NSAttributedString.Key: AnyObject] = [ + .foregroundColor: NSColor.controlTextColor, + .font: NSFont.boldSystemFont(ofSize: kLargeFontSize), + .paragraphStyle: paraStyle, + ] + let attrString = NSMutableAttributedString(string: messageArray[0], attributes: attrTitle) + let attrAlt: [NSAttributedString.Key: AnyObject] = [ + .foregroundColor: NSColor.secondaryLabelColor, + .font: NSFont.systemFont(ofSize: kSmallFontSize), + .paragraphStyle: paraStyle, + ] + let additionalString = messageArray.count > 1 ? "\n\(messageArray[1])" : "" + let attrStringAlt = NSMutableAttributedString(string: additionalString, attributes: attrAlt) + attrString.insert(attrStringAlt, at: attrString.length) + + let textRect: NSRect = attrString.boundingRect( + with: NSSize(width: 1600.0, height: 1600.0), options: [.usesLineFragmentOrigin] + ) + let windowWidth = Double(4) * kLargeFontSize + textRect.width + let contentRect = NSRect(x: 0, y: 0, width: windowWidth, height: 60.0) + var windowRect = contentRect + windowRect.origin.x = screenRect.maxX - windowRect.width - 10 + windowRect.origin.y = screenRect.maxY - windowRect.height - 10 + let styleMask: NSWindow.StyleMask = [.borderless] + + let transparentVisualEffect = NSVisualEffectView() + transparentVisualEffect.blendingMode = .behindWindow + transparentVisualEffect.state = .active + + let theWindow = NSWindow( + contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false + ) + theWindow.contentView = transparentVisualEffect + theWindow.isMovableByWindowBackground = true + theWindow.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel)) + theWindow.hasShadow = true + theWindow.backgroundColor = .textBackgroundColor + theWindow.title = "" + theWindow.titlebarAppearsTransparent = true + theWindow.titleVisibility = .hidden + theWindow.showsToolbarButton = false + theWindow.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true + theWindow.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isHidden = true + theWindow.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true + theWindow.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true + theWindow.isReleasedWhenClosed = true + theWindow.isMovable = false + + let lblMessage = NSTextField() + lblMessage.attributedStringValue = attrString + lblMessage.drawsBackground = false + lblMessage.font = .boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)) + lblMessage.frame = contentRect + lblMessage.isBezeled = false + lblMessage.isEditable = false + lblMessage.isSelectable = false + lblMessage.textColor = .controlTextColor + theWindow.contentView?.addSubview(lblMessage) + + let x = lblMessage.frame.origin.x + let y = ((theWindow.frame.height) - textRect.height) / 1.9 + let newFrame = NSRect(x: x, y: y, width: theWindow.frame.width, height: textRect.height) + lblMessage.frame = newFrame + + super.init(window: theWindow) + display() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func close() { + super.close() + } +} + +// MARK: - Private Functions + +extension Notifier { + private func shiftExistingWindowPositions() { + guard let window = window, !Self.instanceStack.isEmpty else { return } + for theInstanceWindow in Self.instanceStack.compactMap(\.window) { + var theOrigin = theInstanceWindow.frame + theOrigin.origin.y -= (10 + window.frame.height) + theInstanceWindow.setFrame(theOrigin, display: true) + } + } + + private func fadeIn() { + guard let window = window else { return } + let afterRect = window.frame + var beforeRect = afterRect + beforeRect.origin.x -= 20 + window.setFrame(beforeRect, display: true) + window.orderFront(self) + window.setFrame(afterRect, display: true, animate: true) + } + + private func display() { + let existingInstanceArray = Self.instanceStack.compactMap(\.window) + if !existingInstanceArray.isEmpty { + existingInstanceArray.forEach { $0.alphaValue *= 0.5 } + } + shiftExistingWindowPositions() + fadeIn() + Self.instanceStack.insert(self, at: 0) + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + self.close() + Self.instanceStack.removeAll(where: { $0.window == nil }) + } + } +} diff --git a/Packages/vChewing_PopupCompositionBuffer/.gitignore b/Packages/vChewing_PopupCompositionBuffer/.gitignore new file mode 100644 index 00000000..3b298120 --- /dev/null +++ b/Packages/vChewing_PopupCompositionBuffer/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/vChewing_PopupCompositionBuffer/Package.swift b/Packages/vChewing_PopupCompositionBuffer/Package.swift new file mode 100644 index 00000000..e8ac4343 --- /dev/null +++ b/Packages/vChewing_PopupCompositionBuffer/Package.swift @@ -0,0 +1,26 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "PopupCompositionBuffer", + platforms: [ + .macOS(.v10_11) + ], + products: [ + .library( + name: "PopupCompositionBuffer", + targets: ["PopupCompositionBuffer"] + ) + ], + dependencies: [ + .package(path: "../vChewing_Shared") + ], + targets: [ + .target( + name: "PopupCompositionBuffer", + dependencies: [ + .product(name: "Shared", package: "vChewing_Shared") + ] + ) + ] +) diff --git a/Packages/vChewing_PopupCompositionBuffer/README.md b/Packages/vChewing_PopupCompositionBuffer/README.md new file mode 100644 index 00000000..eb32ca75 --- /dev/null +++ b/Packages/vChewing_PopupCompositionBuffer/README.md @@ -0,0 +1,13 @@ +# NotifierUI + +威注音的浮動組字窗。 + +``` +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// 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 defined in MIT License. +``` diff --git a/Source/Modules/UIModules/PopupCompositionBufferUI/ctlPopupCompositionBuffer.swift b/Packages/vChewing_PopupCompositionBuffer/Sources/PopupCompositionBuffer/PopupCompositionBuffer.swift similarity index 92% rename from Source/Modules/UIModules/PopupCompositionBufferUI/ctlPopupCompositionBuffer.swift rename to Packages/vChewing_PopupCompositionBuffer/Sources/PopupCompositionBuffer/PopupCompositionBuffer.swift index 16824bc6..0cb5fb85 100644 --- a/Source/Modules/UIModules/PopupCompositionBufferUI/ctlPopupCompositionBuffer.swift +++ b/Packages/vChewing_PopupCompositionBuffer/Sources/PopupCompositionBuffer/PopupCompositionBuffer.swift @@ -6,7 +6,10 @@ // marks, or product names of Contributor, except as required to fulfill notice // requirements defined in MIT License. -public class ctlPopupCompositionBuffer: NSWindowController { +import Cocoa +import Shared + +public class PopupCompositionBuffer: NSWindowController { public var isTypingDirectionVertical = false { didSet { if #unavailable(macOS 10.14) { @@ -33,7 +36,7 @@ public class ctlPopupCompositionBuffer: NSWindowController { panel.hasShadow = true panel.backgroundColor = NSColor.controlBackgroundColor panel.styleMask = .fullSizeContentView - + panel.isMovable = false messageTextField = NSTextField() messageTextField.isEditable = false messageTextField.isSelectable = false @@ -58,7 +61,7 @@ public class ctlPopupCompositionBuffer: NSWindowController { return } - let attrString: NSMutableAttributedString = .init(string: state.data.displayedTextConverted) + let attrString: NSMutableAttributedString = .init(string: state.displayedTextConverted) let verticalAttributes: [NSAttributedString.Key: Any] = [ .verticalGlyphForm: true, .paragraphStyle: { @@ -75,7 +78,7 @@ public class ctlPopupCompositionBuffer: NSWindowController { if isTypingDirectionVertical { attrString.setAttributes( - verticalAttributes, range: NSRange(location: 0, length: attrString.string.utf16.count) + verticalAttributes, range: NSRange(location: 0, length: attrString.length) ) } @@ -95,8 +98,8 @@ public class ctlPopupCompositionBuffer: NSWindowController { attrString.setAttributes( markerAttributes, range: NSRange( - location: state.data.u16MarkedRange.lowerBound, - length: state.data.u16MarkedRange.upperBound - state.data.u16MarkedRange.lowerBound + location: state.u16MarkedRange.lowerBound, + length: state.u16MarkedRange.upperBound - state.u16MarkedRange.lowerBound ) ) @@ -123,12 +126,12 @@ public class ctlPopupCompositionBuffer: NSWindowController { isTypingDirectionVertical ? NSMutableAttributedString(string: "▔", attributes: cursorAttributes) : NSMutableAttributedString(string: "_", attributes: cursorAttributes) - attrString.insert(attrCursor, at: state.data.u16Cursor) + attrString.insert(attrCursor, at: state.u16Cursor) textShown = attrString messageTextField.maximumNumberOfLines = 1 if let editor = messageTextField.currentEditor() { - editor.selectedRange = NSRange(state.data.u16MarkedRange) + editor.selectedRange = NSRange(state.u16MarkedRange) } window?.orderFront(nil) set(windowOrigin: point) @@ -165,7 +168,7 @@ public class ctlPopupCompositionBuffer: NSWindowController { with: NSSize(width: 1600.0, height: 1600.0), options: [.usesLineFragmentOrigin, .usesFontLeading] ) - rect.size.width = max(rect.size.width, 20 * CGFloat(attrString.string.count)) + 2 + rect.size.width = max(rect.size.width, 20 * Double(attrString.string.count)) + 2 rect.size.height *= 1.2 rect.size.height = max(22, rect.size.height) if isTypingDirectionVertical { diff --git a/Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMCandidateNode.swift b/Packages/vChewing_Shared/Sources/Shared/CandidateNode.swift similarity index 100% rename from Packages/vChewing_LangModelAssembly/Sources/LangModelAssembly/LMCandidateNode.swift rename to Packages/vChewing_Shared/Sources/Shared/CandidateNode.swift diff --git a/Packages/vChewing_Shared/Sources/Shared/Protocols/CtlCandidateProtocol.swift b/Packages/vChewing_Shared/Sources/Shared/Protocols/CtlCandidateProtocol.swift new file mode 100644 index 00000000..f7ab9800 --- /dev/null +++ b/Packages/vChewing_Shared/Sources/Shared/Protocols/CtlCandidateProtocol.swift @@ -0,0 +1,41 @@ +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// 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 defined in MIT License. + +import Cocoa + +public protocol CtlCandidateDelegate: AnyObject { + func candidatePairs() -> [(String, String)] + func candidatePairAt(_ index: Int) -> (String, String) + func candidatePairSelected(at index: Int) + func buzz() + func kanjiConversionIfRequired(_ target: String) -> String +} + +public protocol CtlCandidateProtocol { + var locale: String { get set } + var currentLayout: CandidateLayout { get set } + var delegate: CtlCandidateDelegate? { get set } + var selectedCandidateIndex: Int { get set } + var visible: Bool { get set } + var windowTopLeftPoint: NSPoint { get set } + var keyLabels: [CandidateKeyLabel] { get set } + var keyLabelFont: NSFont { get set } + var candidateFont: NSFont { get set } + var tooltip: String { get set } + var useLangIdentifier: Bool { get set } + var showPageButtons: Bool { get set } + + init(_ layout: CandidateLayout) + func reloadData() + func showNextPage() -> Bool + func showPreviousPage() -> Bool + func highlightNextCandidate() -> Bool + func highlightPreviousCandidate() -> Bool + func candidateIndexAtKeyLabelIndex(_: Int) -> Int + func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: Double) +} diff --git a/Packages/vChewing_Shared/Sources/Shared/Protocols/IMEStateProtocol.swift b/Packages/vChewing_Shared/Sources/Shared/Protocols/IMEStateProtocol.swift new file mode 100644 index 00000000..173b86eb --- /dev/null +++ b/Packages/vChewing_Shared/Sources/Shared/Protocols/IMEStateProtocol.swift @@ -0,0 +1,64 @@ +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// 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 defined in MIT License. + +import Cocoa + +// 所有 IMEState 均遵守該協定: +public protocol IMEStateProtocol { + var type: StateType { get } + var data: StateDataProtocol { get } + var isASCIIMode: Bool { get set } + var isVerticalTyping: Bool { get set } + var isVerticalCandidateWindow: Bool { get set } + var candidates: [(String, String)] { get set } + var hasComposition: Bool { get } + var isCandidateContainer: Bool { get } + var displayedText: String { get } + var displayedTextConverted: String { get } + var textToCommit: String { get set } + var tooltip: String { get set } + var attributedString: NSAttributedString { get } + var convertedToInputting: IMEStateProtocol { get } + var isFilterable: Bool { get } + var isMarkedLengthValid: Bool { get } + var node: CandidateNode { get set } + var displayTextSegments: [String] { get } + var tooltipBackupForInputting: String { get set } + var markedRange: Range { get } + var u16MarkedRange: Range { get } + var u16Cursor: Int { get } + var cursor: Int { get set } + var marker: Int { get set } +} + +public protocol StateDataProtocol { + var cursor: Int { get set } + var marker: Int { get set } + var markedRange: Range { get } + var u16MarkedRange: Range { get } + var u16Cursor: Int { get } + var textToCommit: String { get set } + var markedReadings: [String] { get set } + var displayTextSegments: [String] { get set } + var isFilterable: Bool { get } + var isVerticalTyping: Bool { get set } + var isMarkedLengthValid: Bool { get } + var candidates: [(String, String)] { get set } + var displayedText: String { get set } + var displayedTextConverted: String { get } + var tooltipBackupForInputting: String { get set } + var tooltip: String { get set } + var attributedStringNormal: NSAttributedString { get } + var attributedStringMarking: NSAttributedString { get } + var attributedStringPlaceholder: NSAttributedString { get } + var userPhraseDumped: String { get } + var userPhraseDumpedConverted: String { get } + var doesUserPhraseExist: Bool { get } + var tooltipColorState: TooltipColorState { get set } + mutating func updateTooltipForMarking() +} diff --git a/Packages/vChewing_Shared/Sources/Shared/Shared.swift b/Packages/vChewing_Shared/Sources/Shared/Shared.swift index f0c5da3c..78c63280 100644 --- a/Packages/vChewing_Shared/Sources/Shared/Shared.swift +++ b/Packages/vChewing_Shared/Sources/Shared/Shared.swift @@ -103,6 +103,50 @@ public enum UserDef: String, CaseIterable { } } +// MARK: - Enums and Structs used by Candidate Window + +public enum CandidateLayout { + case horizontal + case vertical +} + +public struct CandidateKeyLabel { + public private(set) var key: String + public private(set) var displayedText: String + + public init(key: String, displayedText: String) { + self.key = key + self.displayedText = displayedText + } +} + +// MARK: - Tooltip Color States + +public enum TooltipColorState { + case normal + case redAlert + case warning + case denialOverflow + case denialInsufficiency + case prompt +} + +// MARK: - IMEState types. + +// 用以讓每個狀態自描述的 enum。 +public enum StateType: String { + case ofDeactivated = "Deactivated" + case ofEmpty = "Empty" + case ofAbortion = "Abortion" // 該狀態會自動轉為 Empty + case ofCommitting = "Committing" + case ofAssociates = "Associates" + case ofNotEmpty = "NotEmpty" + case ofInputting = "Inputting" + case ofMarking = "Marking" + case ofCandidates = "Candidates" + case ofSymbolTable = "SymbolTable" +} + // MARK: - Parser for Syllable composer public enum KeyboardParser: Int, CaseIterable { diff --git a/Packages/vChewing_Tekkon/README.md b/Packages/vChewing_Tekkon/README.md index c5727822..30c05096 100644 --- a/Packages/vChewing_Tekkon/README.md +++ b/Packages/vChewing_Tekkon/README.md @@ -23,7 +23,7 @@ Regarding pinyin input support, we only support: Hanyu Pinyin, Secondary Pinyin, ### §1. 初期化 -在你的 ctlInputMethod (InputMethodController) 或者 KeyHandler 內初期化一份 Tekkon.Composer 注拼槽副本(這裡將該副本命名為「`_composer`」)。由於 Tekkon.Composer 的型別是 Struct 型別,所以其副本必須為變數(var),否則無法自我 mutate。 +在你的 IMKInputController (InputMethodController) 或者 KeyHandler 內初期化一份 Tekkon.Composer 注拼槽副本(這裡將該副本命名為「`_composer`」)。由於 Tekkon.Composer 的型別是 Struct 型別,所以其副本必須為變數(var),否則無法自我 mutate。 以 KeyHandler 為例: ```swift @@ -34,10 +34,10 @@ class KeyHandler: NSObject { } ``` -以 ctlInputMethod 為例: +以 IMKInputController 為例: ```swift -@objc(ctlInputMethod) // 根據 info.plist 內的情況來確定型別的命名 -class ctlInputMethod: IMKInputController { +@objc(IMKMyInputController) // 根據 info.plist 內的情況來確定型別的命名 +class IMKMyInputController: IMKInputController { // 先設定好變數 var _composer: Tekkon.Composer = .init() ... @@ -45,7 +45,7 @@ class ctlInputMethod: IMKInputController { ``` -由於 Swift 會在某個大副本(KeyHandler 或者 ctlInputMethod 副本)被銷毀的時候自動銷毀其中的全部副本,所以 Tekkon.Composer 的副本初期化沒必要寫在 init() 當中。但你很可能會想在 init() 時指定 Tekkon.Composer 所使用的注音排列(是大千?還是倚天傳統?還是神通?等)。 +由於 Swift 會在某個大副本(KeyHandler 或者 IMKInputController 副本)被銷毀的時候自動銷毀其中的全部副本,所以 Tekkon.Composer 的副本初期化沒必要寫在 init() 當中。但你很可能會想在 init() 時指定 Tekkon.Composer 所使用的注音排列(是大千?還是倚天傳統?還是神通?等)。 這裡就需要在 _composer 這個副本所在的型別當中額外寫一個過程函式。 @@ -159,9 +159,9 @@ final class TekkonTests: XCTestCase { #### // 2. 訊號處理 -無論是 KeyHandler 還是 ctlInputMethod 都得要處理被傳入的 NSEvent 當中的 charCode 訊號。 +無論是 KeyHandler 還是 IMKInputController 都得要處理被傳入的 NSEvent 當中的 charCode 訊號。 -比如 ctlInputMethod 內: +比如 IMKInputController 內: ```swift func handleInputText(_ inputText: String?, key keyCode: Int, modifiers flags: Int, client: Any?) -> Bool { ... diff --git a/Packages/vChewing_TooltipUI/.gitignore b/Packages/vChewing_TooltipUI/.gitignore new file mode 100644 index 00000000..3b298120 --- /dev/null +++ b/Packages/vChewing_TooltipUI/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/vChewing_TooltipUI/Package.swift b/Packages/vChewing_TooltipUI/Package.swift new file mode 100644 index 00000000..6dd58c26 --- /dev/null +++ b/Packages/vChewing_TooltipUI/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "TooltipUI", + platforms: [ + .macOS(.v10_11) + ], + products: [ + .library( + name: "TooltipUI", + targets: ["TooltipUI"] + ) + ], + dependencies: [ + .package(path: "../Fuziki_NSAttributedTextView"), + .package(path: "../vChewing_CocoaExtension"), + .package(path: "../vChewing_Shared"), + ], + targets: [ + .target( + name: "TooltipUI", + dependencies: [ + .product(name: "NSAttributedTextView", package: "Fuziki_NSAttributedTextView"), + .product(name: "CocoaExtension", package: "vChewing_CocoaExtension"), + .product(name: "Shared", package: "vChewing_Shared"), + ] + ) + ] +) diff --git a/Packages/vChewing_TooltipUI/README.md b/Packages/vChewing_TooltipUI/README.md new file mode 100644 index 00000000..b6692f65 --- /dev/null +++ b/Packages/vChewing_TooltipUI/README.md @@ -0,0 +1,13 @@ +# NotifierUI + +威注音的工具提示視窗。 + +``` +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// 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 defined in MIT License. +``` diff --git a/Source/Modules/UIModules/TooltipUI/ctlTooltip.swift b/Packages/vChewing_TooltipUI/Sources/TooltipUI/TooltipUI.swift similarity index 93% rename from Source/Modules/UIModules/TooltipUI/ctlTooltip.swift rename to Packages/vChewing_TooltipUI/Sources/TooltipUI/TooltipUI.swift index 9c58a041..af044ce6 100644 --- a/Source/Modules/UIModules/TooltipUI/ctlTooltip.swift +++ b/Packages/vChewing_TooltipUI/Sources/TooltipUI/TooltipUI.swift @@ -6,18 +6,12 @@ // marks, or product names of Contributor, except as required to fulfill notice // requirements defined in MIT License. +import Cocoa +import CocoaExtension import NSAttributedTextView +import Shared -public class ctlTooltip: NSWindowController { - public enum ColorStates { - case normal - case redAlert - case warning - case denialOverflow - case denialInsufficiency - case prompt - } - +public class TooltipUI: NSWindowController { private var messageText: NSAttributedTooltipTextView private var tooltip: String = "" { didSet { @@ -43,6 +37,7 @@ public class ctlTooltip: NSWindowController { panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 2) panel.hasShadow = true panel.backgroundColor = NSColor.controlBackgroundColor + panel.isMovable = false messageText = NSAttributedTooltipTextView() messageText.backgroundColor = NSColor.controlBackgroundColor messageText.textColor = NSColor.textColor @@ -57,7 +52,7 @@ public class ctlTooltip: NSWindowController { public func show( tooltip: String = "", at point: NSPoint, - bottomOutOfScreenAdjustmentHeight heightDelta: CGFloat, + bottomOutOfScreenAdjustmentHeight heightDelta: Double, direction: NSAttributedTooltipTextView.writingDirection = .horizontal ) { self.direction = direction @@ -66,7 +61,7 @@ public class ctlTooltip: NSWindowController { set(windowTopLeftPoint: point, bottomOutOfScreenAdjustmentHeight: heightDelta) } - public func setColor(state: ColorStates) { + public func setColor(state: TooltipColorState) { var backgroundColor = NSColor.controlBackgroundColor var textColor = NSColor.textColor switch state { @@ -123,7 +118,7 @@ public class ctlTooltip: NSWindowController { window?.orderOut(nil) } - private func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: CGFloat) { + private func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: Double) { guard let window = window else { return } let windowSize = window.frame.size diff --git a/README-CHS.md b/README-CHS.md index 3309cb05..444e4ea4 100644 --- a/README-CHS.md +++ b/README-CHS.md @@ -91,7 +91,7 @@ P.S.: 威注音输入法的 Shift 按键监测功能仅借由对 NSEvent 讯号 威注音专案目前仅用到小麦注音的下述程式组件(MIT License): - 仅供研发人员调试方便而使用的 App 版安装程式 (by Zonble Yang),不对公众使用。 -- Voltaire MK2 选字窗、飘云通知视窗 (by Zonble Yang),有大幅度修改。 +- Voltaire MK2 选字窗 (by Zonble Yang),有大幅度修改。 威注音专案目前还用到如下的来自 Lukhnos Liu 的算法: diff --git a/README.md b/README.md index 4259b4c8..dd86fa38 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ P.S.: 威注音輸入法的 Shift 按鍵監測功能僅藉由對 NSEvent 訊號 威注音專案目前僅用到小麥注音的下述程式組件(MIT License): - 僅供研發人員調試方便而使用的 App 版安裝程式 (by Zonble Yang),不對公眾使用。 -- Voltaire MK2 選字窗、飄雲通知視窗 (by Zonble Yang),有大幅度修改。 +- Voltaire MK2 選字窗 (by Zonble Yang),有大幅度修改。 威注音專案目前還用到如下的來自 Lukhnos Liu 的算法: diff --git a/Source/Data b/Source/Data index 33487f55..6f71610f 160000 --- a/Source/Data +++ b/Source/Data @@ -1 +1 @@ -Subproject commit 33487f554d2306f43d100c9c5b8ce99c2109ca52 +Subproject commit 6f71610f126eee8efcce4a4ff832bec03af7c79b diff --git a/Source/Modules/ChineseConverterBridge.swift b/Source/Modules/ChineseConverterBridge.swift index 6952ffff..a0223b77 100644 --- a/Source/Modules/ChineseConverterBridge.swift +++ b/Source/Modules/ChineseConverterBridge.swift @@ -73,7 +73,7 @@ public enum ChineseConverter { /// /// - Parameter string: Text in Original Script. /// - Returns: Text converted to Different Script. - public static func crossConvert(_ string: String) -> String? { + public static func crossConvert(_ string: String) -> String { switch IMEApp.currentInputMode { case .imeModeCHS: return shared.convert(string, to: .zhHantTW) diff --git a/Source/Modules/IMEState.swift b/Source/Modules/IMEState.swift index aee24f70..04c06935 100644 --- a/Source/Modules/IMEState.swift +++ b/Source/Modules/IMEState.swift @@ -7,46 +7,9 @@ // requirements defined in MIT License. import LangModelAssembly +import Shared -// 用以讓每個狀態自描述的 enum。 -public enum StateType: String { - case ofDeactivated = "Deactivated" - case ofEmpty = "Empty" - case ofAbortion = "Abortion" // 該狀態會自動轉為 Empty - case ofCommitting = "Committing" - case ofAssociates = "Associates" - case ofNotEmpty = "NotEmpty" - case ofInputting = "Inputting" - case ofMarking = "Marking" - case ofCandidates = "Candidates" - case ofSymbolTable = "SymbolTable" -} - -// 所有 IMEState 均遵守該協定: -public protocol IMEStateProtocol { - var type: StateType { get } - var data: StateData { get } - var isASCIIMode: Bool { get set } - var isVerticalTyping: Bool { get set } - var isVerticalCandidateWindow: Bool { get set } - var candidates: [(String, String)] { get } - var hasComposition: Bool { get } - var isCandidateContainer: Bool { get } - var displayedText: String { get } - var textToCommit: String { get set } - var tooltip: String { get set } - var attributedString: NSAttributedString { get } - var convertedToInputting: IMEState { get } - var isFilterable: Bool { get } - var isMarkedLengthValid: Bool { get } - var node: CandidateNode { get set } - var cursor: Int { get } - var displayTextSegments: [String] { get } - var tooltipBackupForInputting: String { get set } - var markedRange: Range { get } -} - -/// 用以呈現輸入法控制器(ctlInputMethod)的各種狀態。 +/// 用以呈現輸入法控制器(SessionCtl)的各種狀態。 /// /// 從實際角度來看,輸入法屬於有限態械(Finite State Machine)。其藉由滑鼠/鍵盤 /// 等輸入裝置接收輸入訊號,據此切換至對應的狀態,再根據狀態更新使用者介面內容, @@ -79,17 +42,17 @@ public protocol IMEStateProtocol { /// - .SymbolTable: 波浪鍵符號選單專用的狀態,有自身的特殊處理。 public struct IMEState: IMEStateProtocol { public var type: StateType = .ofEmpty - public var data: StateData = .init() + public var data: StateDataProtocol = StateData() as StateDataProtocol public var node: CandidateNode = .init(name: "") public var isASCIIMode = false public var isVerticalCandidateWindow = false - init(_ data: StateData = .init(), type: StateType = .ofEmpty) { + init(_ data: StateDataProtocol = StateData() as StateDataProtocol, type: StateType = .ofEmpty) { self.data = data self.type = type - isVerticalTyping = ctlInputMethod.isVerticalTyping + isVerticalTyping = SessionCtl.isVerticalTyping } - init(_ data: StateData = .init(), type: StateType = .ofEmpty, node: CandidateNode) { + init(_ data: StateDataProtocol = StateData() as StateDataProtocol, type: StateType = .ofEmpty, node: CandidateNode) { self.data = data self.type = type self.node = node @@ -105,14 +68,14 @@ extension IMEState { public static func ofAbortion() -> IMEState { .init(type: .ofAbortion) } public static func ofCommitting(textToCommit: String) -> IMEState { var result = IMEState(type: .ofCommitting) - result.data.textToCommit = textToCommit + result.textToCommit = textToCommit ChineseConverter.ensureCurrencyNumerals(target: &result.data.textToCommit) return result } public static func ofAssociates(candidates: [(String, String)]) -> IMEState { var result = IMEState(type: .ofAssociates) - result.data.candidates = candidates + result.candidates = candidates return result } @@ -120,10 +83,10 @@ extension IMEState { var result = IMEState(type: .ofNotEmpty) // 注意資料的設定順序,一定得先設定 displayTextSegments。 result.data.displayTextSegments = displayTextSegments.map { - if !ctlInputMethod.isVerticalTyping { return $0 } + if !SessionCtl.isVerticalTyping { return $0 } guard PrefMgr.shared.hardenVerticalPunctuations else { return $0 } var neta = $0 - ChineseConverter.hardenVerticalPunctuations(target: &neta, convert: ctlInputMethod.isVerticalTyping) + ChineseConverter.hardenVerticalPunctuations(target: &neta, convert: SessionCtl.isVerticalTyping) return neta } @@ -133,7 +96,7 @@ extension IMEState { } public static func ofInputting(displayTextSegments: [String], cursor: Int) -> IMEState { - var result = IMEState.ofNotEmpty(displayTextSegments: displayTextSegments, cursor: cursor) + var result = Self.ofNotEmpty(displayTextSegments: displayTextSegments, cursor: cursor) result.type = .ofInputting return result } @@ -143,7 +106,7 @@ extension IMEState { ) -> IMEState { - var result = IMEState.ofNotEmpty(displayTextSegments: displayTextSegments, cursor: cursor) + var result = Self.ofNotEmpty(displayTextSegments: displayTextSegments, cursor: cursor) result.type = .ofMarking result.data.marker = marker result.data.markedReadings = markedReadings @@ -154,7 +117,7 @@ extension IMEState { public static func ofCandidates(candidates: [(String, String)], displayTextSegments: [String], cursor: Int) -> IMEState { - var result = IMEState.ofNotEmpty(displayTextSegments: displayTextSegments, cursor: cursor) + var result = Self.ofNotEmpty(displayTextSegments: displayTextSegments, cursor: cursor) result.type = .ofCandidates result.data.candidates = candidates return result @@ -172,35 +135,44 @@ extension IMEState { extension IMEState { public var isFilterable: Bool { data.isFilterable } public var isMarkedLengthValid: Bool { data.isMarkedLengthValid } - public var candidates: [(String, String)] { data.candidates } public var displayedText: String { data.displayedText } - public var cursor: Int { data.cursor } + public var displayedTextConverted: String { data.displayedTextConverted } public var displayTextSegments: [String] { data.displayTextSegments } public var markedRange: Range { data.markedRange } - public var convertedToInputting: IMEState { + public var u16MarkedRange: Range { data.u16MarkedRange } + public var u16Cursor: Int { data.u16Cursor } + + public var cursor: Int { + get { data.cursor } + set { data.cursor = newValue } + } + + public var marker: Int { + get { data.marker } + set { data.marker = newValue } + } + + public var convertedToInputting: IMEStateProtocol { if type == .ofInputting { return self } - var result = IMEState.ofInputting(displayTextSegments: data.displayTextSegments, cursor: data.cursor) + var result = Self.ofInputting(displayTextSegments: data.displayTextSegments, cursor: data.cursor) result.tooltip = data.tooltipBackupForInputting result.isVerticalTyping = isVerticalTyping return result } + public var candidates: [(String, String)] { + get { data.candidates } + set { data.candidates = newValue } + } + public var textToCommit: String { - get { - data.textToCommit - } - set { - data.textToCommit = newValue - } + get { data.textToCommit } + set { data.textToCommit = newValue } } public var tooltip: String { - get { - data.tooltip - } - set { - data.tooltip = newValue - } + get { data.tooltip } + set { data.tooltip = newValue } } public var attributedString: NSAttributedString { @@ -231,11 +203,7 @@ extension IMEState { } public var tooltipBackupForInputting: String { - get { - data.tooltipBackupForInputting - } - set { - data.tooltipBackupForInputting = newValue - } + get { data.tooltipBackupForInputting } + set { data.tooltipBackupForInputting = newValue } } } diff --git a/Source/Modules/IMEStateData.swift b/Source/Modules/IMEStateData.swift index 08abfbbf..72225d2c 100644 --- a/Source/Modules/IMEStateData.swift +++ b/Source/Modules/IMEStateData.swift @@ -6,9 +6,11 @@ // marks, or product names of Contributor, except as required to fulfill notice // requirements defined in MIT License. +import Shared import Tekkon +import TooltipUI -public struct StateData { +public struct StateData: StateDataProtocol { private static var minCandidateLength: Int { PrefMgr.shared.allowBoostingSingleKanjiAsUserPhrase ? 1 : 2 } @@ -17,8 +19,8 @@ public struct StateData { Self.minCandidateLength...PrefMgr.shared.maxCandidateLength } - var displayedText: String = "" - var displayedTextConverted: String { + public var displayedText: String = "" + public var displayedTextConverted: String { /// 先做繁簡轉換 var result = ChineseConverter.kanjiConversionIfRequired(displayedText) if result.utf16.count != displayedText.utf16.count @@ -31,19 +33,19 @@ public struct StateData { // MARK: Cursor & Marker & Range for UTF8 - var cursor: Int = 0 { + public var cursor: Int = 0 { didSet { cursor = min(max(cursor, 0), displayedText.count) } } - var marker: Int = 0 { + public var marker: Int = 0 { didSet { marker = min(max(marker, 0), displayedText.count) } } - var markedRange: Range { + public var markedRange: Range { min(cursor, marker).. { + public var u16MarkedRange: Range { min(u16Cursor, u16Marker).. ctlCandidateProtocol + func candidateController() -> CtlCandidateProtocol func candidateSelectionCalledByKeyHandler(at index: Int) func performUserPhraseOperation(with state: IMEStateProtocol, addToFilter: Bool) -> Bool @@ -30,7 +30,7 @@ public protocol KeyHandlerDelegate { /// KeyHandler 按鍵調度模組。 public class KeyHandler { - /// 委任物件 (ctlInputMethod),以便呼叫其中的函式。 + /// 委任物件 (SessionCtl),以便呼叫其中的函式。 public var delegate: KeyHandlerDelegate? public var prefs: PrefMgrProtocol diff --git a/Source/Modules/KeyHandler_HandleCandidate.swift b/Source/Modules/KeyHandler_HandleCandidate.swift index 86671c68..b449d2bc 100644 --- a/Source/Modules/KeyHandler_HandleCandidate.swift +++ b/Source/Modules/KeyHandler_HandleCandidate.swift @@ -26,7 +26,7 @@ extension KeyHandler { stateCallback: @escaping (IMEStateProtocol) -> Void, errorCallback: @escaping (String) -> Void ) -> Bool { - guard var ctlCandidate = delegate?.ctlCandidate() else { + guard var ctlCandidate = delegate?.candidateController() else { errorCallback("06661F6E") return true } diff --git a/Source/Modules/KeyHandler_HandleComposition.swift b/Source/Modules/KeyHandler_HandleComposition.swift index c48af66a..ff54915f 100644 --- a/Source/Modules/KeyHandler_HandleComposition.swift +++ b/Source/Modules/KeyHandler_HandleComposition.swift @@ -145,7 +145,7 @@ extension KeyHandler { stateCallback(candidateState) } } - // 將「這個按鍵訊號已經被輸入法攔截處理了」的結果藉由 ctlInputMethod 回報給 IMK。 + // 將「這個按鍵訊號已經被輸入法攔截處理了」的結果藉由 SessionCtl 回報給 IMK。 return true } diff --git a/Source/Modules/KeyHandler_HandleInput.swift b/Source/Modules/KeyHandler_HandleInput.swift index e49406de..bdda6aa1 100644 --- a/Source/Modules/KeyHandler_HandleInput.swift +++ b/Source/Modules/KeyHandler_HandleInput.swift @@ -400,7 +400,7 @@ extension KeyHandler { // MARK: - 終末處理 (Still Nothing) - /// 對剩下的漏網之魚做攔截處理、直接將當前狀態繼續回呼給 ctlInputMethod。 + /// 對剩下的漏網之魚做攔截處理、直接將當前狀態繼續回呼給 SessionCtl。 /// 否則的話,可能會導致輸入法行為異常:部分應用會阻止輸入法完全攔截某些按鍵訊號。 /// 砍掉這一段會導致「F1-F12 按鍵干擾組字區」的問題。 /// 暫時只能先恢復這段,且補上偵錯彙報機制,方便今後排查故障。 diff --git a/Source/Modules/KeyHandler_States.swift b/Source/Modules/KeyHandler_States.swift index dc93a5af..15db2a02 100644 --- a/Source/Modules/KeyHandler_States.swift +++ b/Source/Modules/KeyHandler_States.swift @@ -114,7 +114,6 @@ extension KeyHandler { func buildAssociatePhraseState( withPair pair: Megrez.Compositor.KeyValuePaired ) -> IMEState { - // 上一行必須要用驚嘆號,否則 Xcode 會誤導你砍掉某些實際上必需的語句。 IMEState.ofAssociates( candidates: buildAssociatePhraseArray(withPair: pair)) } @@ -127,7 +126,7 @@ extension KeyHandler { /// - input: 輸入按鍵訊號。 /// - stateCallback: 狀態回呼。 /// - errorCallback: 錯誤回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handleMarkingState( _ state: IMEStateProtocol, input: InputSignalProtocol, @@ -236,7 +235,7 @@ extension KeyHandler { /// - isTypingVertical: 是否縱排輸入? /// - stateCallback: 狀態回呼。 /// - errorCallback: 錯誤回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handlePunctuation( _ customPunctuation: String, state: IMEStateProtocol, @@ -286,7 +285,7 @@ extension KeyHandler { /// - Parameters: /// - state: 當前狀態。 /// - stateCallback: 狀態回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handleEnter( state: IMEStateProtocol, stateCallback: @escaping (IMEStateProtocol) -> Void @@ -304,7 +303,7 @@ extension KeyHandler { /// - Parameters: /// - state: 當前狀態。 /// - stateCallback: 狀態回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handleCtrlCommandEnter( state: IMEStateProtocol, stateCallback: @escaping (IMEStateProtocol) -> Void @@ -332,7 +331,7 @@ extension KeyHandler { /// - Parameters: /// - state: 當前狀態。 /// - stateCallback: 狀態回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handleCtrlOptionCommandEnter( state: IMEStateProtocol, stateCallback: @escaping (IMEStateProtocol) -> Void @@ -370,7 +369,7 @@ extension KeyHandler { /// - input: 輸入按鍵訊號。 /// - stateCallback: 狀態回呼。 /// - errorCallback: 錯誤回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handleBackSpace( state: IMEStateProtocol, input: InputSignalProtocol, @@ -432,7 +431,7 @@ extension KeyHandler { /// - input: 輸入按鍵訊號。 /// - stateCallback: 狀態回呼。 /// - errorCallback: 錯誤回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handleDelete( state: IMEStateProtocol, input: InputSignalProtocol, @@ -476,7 +475,7 @@ extension KeyHandler { /// - state: 當前狀態。 /// - stateCallback: 狀態回呼。 /// - errorCallback: 錯誤回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handleClockKey( state: IMEStateProtocol, stateCallback: @escaping (IMEStateProtocol) -> Void, @@ -497,7 +496,7 @@ extension KeyHandler { /// - state: 當前狀態。 /// - stateCallback: 狀態回呼。 /// - errorCallback: 錯誤回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handleHome( state: IMEStateProtocol, stateCallback: @escaping (IMEStateProtocol) -> Void, @@ -529,7 +528,7 @@ extension KeyHandler { /// - state: 當前狀態。 /// - stateCallback: 狀態回呼。 /// - errorCallback: 錯誤回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handleEnd( state: IMEStateProtocol, stateCallback: @escaping (IMEStateProtocol) -> Void, @@ -560,7 +559,7 @@ extension KeyHandler { /// - Parameters: /// - state: 當前狀態。 /// - stateCallback: 狀態回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handleEsc( state: IMEStateProtocol, stateCallback: @escaping (IMEStateProtocol) -> Void @@ -592,7 +591,7 @@ extension KeyHandler { /// - input: 輸入按鍵訊號。 /// - stateCallback: 狀態回呼。 /// - errorCallback: 錯誤回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handleForward( state: IMEStateProtocol, input: InputSignalProtocol, @@ -661,7 +660,7 @@ extension KeyHandler { /// - input: 輸入按鍵訊號。 /// - stateCallback: 狀態回呼。 /// - errorCallback: 錯誤回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handleBackward( state: IMEStateProtocol, input: InputSignalProtocol, @@ -730,7 +729,7 @@ extension KeyHandler { /// - reverseModifier: 是否有控制輪替方向的修飾鍵輸入。 /// - stateCallback: 狀態回呼。 /// - errorCallback: 錯誤回呼。 - /// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。 + /// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。 func handleInlineCandidateRotation( state: IMEStateProtocol, reverseModifier: Bool, diff --git a/Source/Modules/LMMgr.swift b/Source/Modules/LMMgr.swift index 4a3209eb..426818c2 100644 --- a/Source/Modules/LMMgr.swift +++ b/Source/Modules/LMMgr.swift @@ -8,7 +8,7 @@ import BookmarkManager import LangModelAssembly -import Megrez +import NotifierUI import Shared /// 使用者辭典資料預設範例檔案名稱。 @@ -98,7 +98,7 @@ public enum LMMgr { } if !Self.lmCHT.isLanguageModelLoaded { showFinishNotification = true - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("Loading CHT Core Dict...", comment: "") ) group.enter() @@ -109,7 +109,7 @@ public enum LMMgr { } if !Self.lmCHS.isLanguageModelLoaded { showFinishNotification = true - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("Loading CHS Core Dict...", comment: "") ) group.enter() @@ -120,7 +120,7 @@ public enum LMMgr { } group.notify(queue: DispatchQueue.main) { if showFinishNotification { - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("Core Dict loading complete.", comment: "") ) } @@ -162,7 +162,7 @@ public enum LMMgr { case .imeModeCHS: if !Self.lmCHS.isLanguageModelLoaded { showFinishNotification = true - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("Loading CHS Core Dict...", comment: "") ) group.enter() @@ -174,7 +174,7 @@ public enum LMMgr { case .imeModeCHT: if !Self.lmCHT.isLanguageModelLoaded { showFinishNotification = true - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("Loading CHT Core Dict...", comment: "") ) group.enter() @@ -187,7 +187,7 @@ public enum LMMgr { } group.notify(queue: DispatchQueue.main) { if showFinishNotification { - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("Core Dict loading complete.", comment: "") ) } @@ -244,15 +244,11 @@ public enum LMMgr { mode: Shared.InputMode, key unigramKey: String ) -> Bool { - let unigrams: [Megrez.Unigram] = - (mode == .imeModeCHT) - ? Self.lmCHT.unigramsFor(key: unigramKey) : Self.lmCHS.unigramsFor(key: unigramKey) - for unigram in unigrams { - if unigram.value == userPhrase { - return true - } + switch mode { + case .imeModeCHS: return lmCHS.hasKeyValuePairFor(key: unigramKey, value: userPhrase) + case .imeModeCHT: return lmCHT.hasKeyValuePairFor(key: unigramKey, value: userPhrase) + case .imeModeNULL: return false } - return false } public static func setPhraseReplacementEnabled(_ state: Bool) { diff --git a/Source/Modules/PrefMgr_Extension.swift b/Source/Modules/PrefMgr_Extension.swift index 3dfb16f1..1e3dedd8 100644 --- a/Source/Modules/PrefMgr_Extension.swift +++ b/Source/Modules/PrefMgr_Extension.swift @@ -8,7 +8,7 @@ import Shared -// MARK: Auto parameter fix procedures, executed everytime on ctlInputMethod.activateServer(). +// MARK: Auto parameter fix procedures, executed everytime on SessionCtl.activateServer(). extension PrefMgr { public func fixOddPreferences() { diff --git a/Source/Modules/ctlInputMethod_Core.swift b/Source/Modules/SessionCtl_Core.swift similarity index 53% rename from Source/Modules/ctlInputMethod_Core.swift rename to Source/Modules/SessionCtl_Core.swift index f6865e35..763c8729 100644 --- a/Source/Modules/ctlInputMethod_Core.swift +++ b/Source/Modules/SessionCtl_Core.swift @@ -9,9 +9,11 @@ // requirements defined in MIT License. import IMKUtils +import PopupCompositionBuffer import Shared import ShiftKeyUpChecker -import Tekkon +import TooltipUI +import Voltaire /// 輸入法控制模組,乃在輸入法端用以控制輸入行為的基礎型別。 /// @@ -21,24 +23,24 @@ import Tekkon /// 檢查委任物件是否實現了方法:若存在的話,就調用委任物件內的版本。 /// - Remark: 在輸入法的主函式中分配的 IMKServer 型別為客體應用程式創建的每個 /// 輸入會話創建一個控制器型別。因此,對於每個輸入會話,都有一個對應的 IMKInputController。 -@objc(ctlInputMethod) // 必須加上 ObjC,因為 IMK 是用 ObjC 寫的。 -class ctlInputMethod: IMKInputController { +@objc(SessionCtl) // 必須加上 ObjC,因為 IMK 是用 ObjC 寫的。 +class SessionCtl: IMKInputController { /// 標記狀態來聲明目前新增的詞彙是否需要賦以非常低的權重。 static var areWeNerfing = false /// 目前在用的的選字窗副本。 - static var ctlCandidateCurrent: ctlCandidateProtocol = - PrefMgr.shared.useIMKCandidateWindow ? ctlCandidateIMK.init(.horizontal) : ctlCandidateUniversal.init(.horizontal) + static var ctlCandidateCurrent: CtlCandidateProtocol = + PrefMgr.shared.useIMKCandidateWindow ? CtlCandidateIMK(.horizontal) : CtlCandidateUniversal(.horizontal) /// 工具提示視窗的共用副本。 - static var tooltipInstance = ctlTooltip() + static var tooltipInstance = TooltipUI() /// 浮動組字窗的共用副本。 - static var popupCompositionBuffer = ctlPopupCompositionBuffer() + static var popupCompositionBuffer = PopupCompositionBuffer() // MARK: - - /// 當前這個 ctlInputMethod 副本是否處於英數輸入模式。 + /// 當前這個 SessionCtl 副本是否處於英數輸入模式。 var isASCIIMode = false { didSet { resetKeyHandler() @@ -75,8 +77,46 @@ class ctlInputMethod: IMKInputController { return result } - // MARK: - 工具函式 + /// InputMode 需要在每次出現內容變更的時候都連帶重設組字器與各項語言模組, + /// 順帶更新 IME 模組及 UserPrefs 當中對於當前語言模式的記載。 + var inputMode: Shared.InputMode = IMEApp.currentInputMode { + willSet { + /// 將新的簡繁輸入模式提報給 Prefs 與 IME 模組。 + IMEApp.currentInputMode = newValue + PrefMgr.shared.mostRecentInputMode = IMEApp.currentInputMode.rawValue + } + didSet { + /// 重設所有語言模組。這裡不需要做按需重設,因為對運算量沒有影響。 + keyHandler.currentLM = LMMgr.currentLM() // 會自動更新組字引擎內的模組。 + keyHandler.currentUOM = LMMgr.currentUOM() + /// 清空注拼槽+同步最新的注拼槽排列設定。 + keyHandler.ensureKeyboardParser() + /// 將輸入法偏好設定同步至語言模組內。 + syncBaseLMPrefs() + } + } + /// 對用以設定委任物件的控制器型別進行初期化處理。 + /// + /// inputClient 參數是客體應用側存在的用以藉由 IMKServer 伺服器向輸入法傳訊的物件。該物件始終遵守 IMKTextInput 協定。 + /// - Remark: 所有由委任物件實裝的「被協定要求實裝的方法」都會有一個用來接受客體物件的參數。在 IMKInputController 內部的型別不需要接受這個參數,因為已經有「client()」這個參數存在了。 + /// - Parameters: + /// - server: IMKServer + /// - delegate: 客體物件 + /// - inputClient: 用以接受輸入的客體應用物件 + override init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) { + super.init(server: server, delegate: delegate, client: inputClient) + keyHandler.delegate = self + syncBaseLMPrefs() + // 下述兩行很有必要,否則輸入法會在手動重啟之後無法立刻生效。 + resetKeyHandler() + activateServer(inputClient) + } +} + +// MARK: - 工具函式 + +extension SessionCtl { /// 指定鍵盤佈局。 func setKeyLayout() { guard let client = client() else { return } @@ -101,28 +141,11 @@ class ctlInputMethod: IMKInputController { } handle(state: isSecureMode ? IMEState.ofAbortion() : IMEState.ofEmpty()) } +} - // MARK: - IMKInputController 方法 - - /// 對用以設定委任物件的控制器型別進行初期化處理。 - /// - /// inputClient 參數是客體應用側存在的用以藉由 IMKServer 伺服器向輸入法傳訊的物件。該物件始終遵守 IMKTextInput 協定。 - /// - Remark: 所有由委任物件實裝的「被協定要求實裝的方法」都會有一個用來接受客體物件的參數。在 IMKInputController 內部的型別不需要接受這個參數,因為已經有「client()」這個參數存在了。 - /// - Parameters: - /// - server: IMKServer - /// - delegate: 客體物件 - /// - inputClient: 用以接受輸入的客體應用物件 - override init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) { - super.init(server: server, delegate: delegate, client: inputClient) - keyHandler.delegate = self - syncBaseLMPrefs() - // 下述兩行很有必要,否則輸入法會在手動重啟之後無法立刻生效。 - resetKeyHandler() - activateServer(inputClient) - } - - // MARK: - IMKStateSetting 協定規定的方法 +// MARK: - IMKStateSetting 協定規定的方法 +extension SessionCtl { /// 啟用輸入法時,會觸發該函式。 /// - Parameter sender: 呼叫了該函式的客體(無須使用)。 override func activateServer(_ sender: Any!) { @@ -192,25 +215,6 @@ class ctlInputMethod: IMKInputController { } } - /// InputMode 需要在每次出現內容變更的時候都連帶重設組字器與各項語言模組, - /// 順帶更新 IME 模組及 UserPrefs 當中對於當前語言模式的記載。 - var inputMode: Shared.InputMode = IMEApp.currentInputMode { - willSet { - /// 將新的簡繁輸入模式提報給 Prefs 與 IME 模組。 - IMEApp.currentInputMode = newValue - PrefMgr.shared.mostRecentInputMode = IMEApp.currentInputMode.rawValue - } - didSet { - /// 重設所有語言模組。這裡不需要做按需重設,因為對運算量沒有影響。 - keyHandler.currentLM = LMMgr.currentLM() // 會自動更新組字引擎內的模組。 - keyHandler.currentUOM = LMMgr.currentUOM() - /// 清空注拼槽+同步最新的注拼槽排列設定。 - keyHandler.ensureKeyboardParser() - /// 將輸入法偏好設定同步至語言模組內。 - syncBaseLMPrefs() - } - } - /// 將輸入法偏好設定同步至語言模組內。 func syncBaseLMPrefs() { LMMgr.currentLM().isPhraseReplacementEnabled = PrefMgr.shared.phraseReplacementEnabled @@ -219,9 +223,13 @@ class ctlInputMethod: IMKInputController { LMMgr.currentLM().isSCPCEnabled = PrefMgr.shared.useSCPCTypingMode LMMgr.currentLM().deltaOfCalendarYears = PrefMgr.shared.deltaOfCalendarYears } +} - // MARK: - IMKServerInput 協定規定的方法 +// MARK: - IMKServerInput 協定規定的方法(僅部分) +// 註:handle(_ event:) 位於 SessionCtl_HandleEvent.swift。 + +extension SessionCtl { /// 該函式的回饋結果決定了輸入法會攔截且捕捉哪些類型的輸入裝置操作事件。 /// /// 一個客體應用會與輸入法共同確認某個輸入裝置操作事件是否可以觸發輸入法內的某個方法。預設情況下, @@ -237,103 +245,6 @@ class ctlInputMethod: IMKInputController { return Int(events.rawValue) } - /// 接受所有鍵鼠事件為 NSEvent,讓輸入法判斷是否要處理、該怎樣處理。 - /// - Parameters: - /// - event: 裝置操作輸入事件,可能會是 nil。 - /// - sender: 呼叫了該函式的客體(無須使用)。 - /// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。 - @objc(handleEvent:client:) override func handle(_ event: NSEvent!, client sender: Any!) -> Bool { - _ = sender // 防止格式整理工具毀掉與此對應的參數。 - - // MARK: 前置處理 - - // 更新此時的靜態狀態標記。 - state.isASCIIMode = isASCIIMode - state.isVerticalTyping = isVerticalTyping - - // 就這傳入的 NSEvent 都還有可能是 nil,Apple InputMethodKit 團隊到底在搞三小。 - // 只針對特定類型的 client() 進行處理。 - guard let event = event, sender is IMKTextInput else { - resetKeyHandler() - return false - } - - // 用 Shift 開關半形英數模式,僅對 macOS 10.15 及之後的 macOS 有效。 - let shouldUseShiftToggleHandle: Bool = { - switch PrefMgr.shared.shiftKeyAccommodationBehavior { - case 0: return false - case 1: return Shared.arrClientShiftHandlingExceptionList.contains(clientBundleIdentifier) - case 2: return true - default: return false - } - }() - - /// 警告:這裡的 event 必須是原始 event 且不能被 var,否則會影響 Shift 中英模式判定。 - if #available(macOS 10.15, *) { - if Self.theShiftKeyDetector.check(event), !PrefMgr.shared.disableShiftTogglingAlphanumericalMode { - if !shouldUseShiftToggleHandle || (!rencentKeyHandledByKeyHandlerEtc && shouldUseShiftToggleHandle) { - NotifierController.notify( - message: isASCIIMode.toggled() - ? NSLocalizedString("Alphanumerical Input Mode", comment: "") - : NSLocalizedString("Chinese Input Mode", comment: "") - ) - } - if shouldUseShiftToggleHandle { - rencentKeyHandledByKeyHandlerEtc = false - } - return false - } - } - - // MARK: 針對客體的具體處理 - - // 不再讓威注音處理由 Shift 切換到的英文模式的按鍵輸入。 - if isASCIIMode { return false } - - /// 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。 - /// 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。 - /// 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false, - /// 否則,每次處理這種判斷時都會觸發 NSInternalInconsistencyException。 - if event.type == .flagsChanged { return false } - - /// 沒有文字輸入客體的話,就不要再往下處理了。 - guard client() != nil else { return false } - - var eventToDeal = event - - // 如果是方向鍵輸入的話,就想辦法帶上標記資訊、來說明當前是縱排還是橫排。 - if event.isUp || event.isDown || event.isLeft || event.isRight { - eventToDeal = event.reinitiate(charactersIgnoringModifiers: isVerticalTyping ? "Vertical" : "Horizontal") ?? event - } - - // 使 NSEvent 自翻譯,這樣可以讓 Emacs NSEvent 變成標準 NSEvent。 - if eventToDeal.isEmacsKey { - let verticalProcessing = - (state.isCandidateContainer) - ? state.isVerticalCandidateWindow : state.isVerticalTyping - eventToDeal = eventToDeal.convertFromEmacKeyEvent(isVerticalContext: verticalProcessing) - } - - // 準備修飾鍵,用來判定要新增的詞彙是否需要賦以非常低的權重。 - Self.areWeNerfing = eventToDeal.modifierFlags.contains([.shift, .command]) - - // IMK 選字窗處理,當且僅當啟用了 IMK 選字窗的時候才會生效。 - if let result = imkCandidatesEventHandler(event: eventToDeal) { - if shouldUseShiftToggleHandle { - rencentKeyHandledByKeyHandlerEtc = result - } - return result - } - - /// 剩下的 NSEvent 直接交給 commonEventHandler 來處理。 - /// 這樣可以與 IMK 選字窗共用按鍵處理資源,維護起來也比較方便。 - let result = commonEventHandler(eventToDeal) - if shouldUseShiftToggleHandle { - rencentKeyHandledByKeyHandlerEtc = result - } - return result - } - /// 有時會出現某些 App 攔截輸入法的 Ctrl+Enter / Shift+Enter 熱鍵的情況。 /// 也就是說 handle(event:) 完全抓不到這個 Event。 /// 這時需要在 commitComposition 這一關做一些收尾處理。 @@ -344,13 +255,13 @@ class ctlInputMethod: IMKInputController { // super.commitComposition(sender) // 這句不要引入,否則每次切出輸入法時都會死當。 } - /// 指定輸入法要遞交出去的內容(雖然威注音可能並未用到這個函式)。 + /// 指定輸入法要遞交出去的內容(雖然 InputMethodKit 可能並不會真的用到這個函式)。 /// - Parameter sender: 呼叫了該函式的客體(無須使用)。 /// - Returns: 字串內容,或者 nil。 override func composedString(_ sender: Any!) -> Any! { _ = sender // 防止格式整理工具毀掉與此對應的參數。 guard state.hasComposition else { return "" } - return state.displayedText + return state.displayedTextConverted } /// 輸入法要被換掉或關掉的時候,要做的事情。 @@ -360,121 +271,4 @@ class ctlInputMethod: IMKInputController { resetKeyHandler() super.inputControllerWillClose() } - - // MARK: - IMKCandidates 功能擴充 - - /// 生成 IMK 選字窗專用的候選字串陣列。 - /// - Parameter sender: 呼叫了該函式的客體(無須使用)。 - /// - Returns: IMK 選字窗專用的候選字串陣列。 - override func candidates(_ sender: Any!) -> [Any]! { - _ = sender // 防止格式整理工具毀掉與此對應的參數。 - var arrResult = [String]() - - // 注意:下文中的不可列印字元是用來方便在 IMEState 當中用來分割資料的。 - func handleIMKCandidatesPrepared(_ candidates: [(String, String)], prefix: String = "") { - for theCandidate in candidates { - let theConverted = ChineseConverter.kanjiConversionIfRequired(theCandidate.1) - var result = (theCandidate.1 == theConverted) ? theCandidate.1 : "\(theConverted)\u{1A}(\(theCandidate.1))" - if arrResult.contains(result) { - let reading: String = - PrefMgr.shared.showHanyuPinyinInCompositionBuffer - ? Tekkon.cnvPhonaToHanyuPinyin(target: Tekkon.restoreToneOneInZhuyinKey(target: theCandidate.0)) - : theCandidate.0 - result = "\(result)\u{17}(\(reading))" - } - arrResult.append(prefix + result) - } - } - - if state.type == .ofAssociates { - handleIMKCandidatesPrepared(state.candidates, prefix: "⇧") - } else if state.type == .ofSymbolTable { - // 分類符號選單不會出現同符異音項、不需要康熙 / JIS 轉換,所以使用簡化過的處理方式。 - arrResult = state.candidates.map(\.1) - } else if state.type == .ofCandidates { - guard !state.candidates.isEmpty else { return .init() } - if state.candidates[0].0.contains("_punctuation") { - arrResult = state.candidates.map(\.1) // 標點符號選單處理。 - } else { - handleIMKCandidatesPrepared(state.candidates) - } - } - - return arrResult - } - - /// IMK 選字窗限定函式,只要選字窗內的高亮內容選擇出現變化了、就會呼叫這個函式。 - /// - Parameter _: 已經高亮選中的候選字詞內容。 - override open func candidateSelectionChanged(_: NSAttributedString!) { - // 警告:不要考慮用實作這個函式的方式來更新內文組字區的顯示。 - // 因為這樣會導致 IMKServer.commitCompositionWithReply() 呼叫你本來不想呼叫的 commitComposition(), - // 然後 keyHandler 會被重設,屆時輸入法會在狀態處理等方面崩潰掉。 - - // 這個函式的實作其實很容易誘發各種崩潰,所以最好不要輕易實作。 - - // 有些幹話還是要講的: - // 在這個函式當中試圖(無論是否拿著傳入的參數)從 ctlCandidateIMK 找 identifier 的話, - // 只會找出 NSNotFound。你想 NSLog 列印看 identifier 是多少,輸入法直接崩潰。 - // 而且會他媽的崩得連 console 內的 ips 錯誤報告都沒有。 - // 在下文的 candidateSelected() 試圖看每個候選字的 identifier 的話,永遠都只能拿到 NSNotFound。 - // 衰洨 IMK 真的看上去就像是沒有做過單元測試的東西,賈伯斯有檢查過的話會被氣得從棺材裡爬出來。 - } - - /// IMK 選字窗限定函式,只要選字窗確認了某個候選字詞的選擇、就會呼叫這個函式。 - /// - Parameter candidateString: 已經確認的候選字詞內容。 - override open func candidateSelected(_ candidateString: NSAttributedString!) { - let candidateString: String = candidateString?.string ?? "" - if state.type == .ofAssociates { - if !PrefMgr.shared.alsoConfirmAssociatedCandidatesByEnter { - handle(state: IMEState.ofAbortion()) - return - } - } - - var indexDeducted = 0 - - // 注意:下文中的不可列印字元是用來方便在 IMEState 當中用來分割資料的。 - func handleIMKCandidatesSelected(_ candidates: [(String, String)], prefix: String = "") { - for (i, neta) in candidates.enumerated() { - let theConverted = ChineseConverter.kanjiConversionIfRequired(neta.1) - let netaShown = (neta.1 == theConverted) ? neta.1 : "\(theConverted)\u{1A}(\(neta.1))" - let reading: String = - PrefMgr.shared.showHanyuPinyinInCompositionBuffer - ? Tekkon.cnvPhonaToHanyuPinyin(target: Tekkon.restoreToneOneInZhuyinKey(target: neta.0)) : neta.0 - let netaShownWithPronunciation = "\(netaShown)\u{17}(\(reading))" - if candidateString == prefix + netaShownWithPronunciation { - indexDeducted = i - break - } - if candidateString == prefix + netaShown { - indexDeducted = i - break - } - } - } - - // 分類符號選單不會出現同符異音項、不需要康熙 / JIS 轉換,所以使用簡化過的處理方式。 - func handleSymbolCandidatesSelected(_ candidates: [(String, String)]) { - for (i, neta) in candidates.enumerated() { - if candidateString == neta.1 { - indexDeducted = i - break - } - } - } - - if state.type == .ofAssociates { - handleIMKCandidatesSelected(state.candidates, prefix: "⇧") - } else if state.type == .ofSymbolTable { - handleSymbolCandidatesSelected(state.candidates) - } else if state.type == .ofCandidates { - guard !state.candidates.isEmpty else { return } - if state.candidates[0].0.contains("_punctuation") { - handleSymbolCandidatesSelected(state.candidates) // 標點符號選單處理。 - } else { - handleIMKCandidatesSelected(state.candidates) - } - } - candidateSelected(at: indexDeducted) - } } diff --git a/Source/Modules/ctlInputMethod_Delegates.swift b/Source/Modules/SessionCtl_Delegates.swift similarity index 63% rename from Source/Modules/ctlInputMethod_Delegates.swift rename to Source/Modules/SessionCtl_Delegates.swift index 98de8525..66ad9022 100644 --- a/Source/Modules/ctlInputMethod_Delegates.swift +++ b/Source/Modules/SessionCtl_Delegates.swift @@ -12,31 +12,29 @@ import Shared // MARK: - KeyHandler Delegate -extension ctlInputMethod: KeyHandlerDelegate { +extension SessionCtl: KeyHandlerDelegate { var clientBundleIdentifier: String { guard let client = client() else { return "" } return client.bundleIdentifier() ?? "" } - func ctlCandidate() -> ctlCandidateProtocol { ctlInputMethod.ctlCandidateCurrent } + func candidateController() -> CtlCandidateProtocol { Self.ctlCandidateCurrent } func candidateSelectionCalledByKeyHandler(at index: Int) { - candidateSelected(at: index) + candidatePairSelected(at: index) } func performUserPhraseOperation(with state: IMEStateProtocol, addToFilter: Bool) -> Bool { guard state.type == .ofMarking else { return false } - let refInputModeReversed: Shared.InputMode = - (inputMode == .imeModeCHT) ? .imeModeCHS : .imeModeCHT if !LMMgr.writeUserPhrase( state.data.userPhraseDumped, inputMode: inputMode, - areWeDuplicating: state.data.chkIfUserPhraseExists, + areWeDuplicating: state.data.doesUserPhraseExist, areWeDeleting: addToFilter ) || !LMMgr.writeUserPhrase( - state.data.userPhraseDumpedConverted, inputMode: refInputModeReversed, + state.data.userPhraseDumpedConverted, inputMode: inputMode.reversed, areWeDuplicating: false, areWeDeleting: addToFilter ) @@ -49,48 +47,27 @@ extension ctlInputMethod: KeyHandlerDelegate { // MARK: - Candidate Controller Delegate -extension ctlInputMethod: ctlCandidateDelegate { - var isAssociatedPhrasesState: Bool { state.type == .ofAssociates } - - /// 完成 handle() 函式本該完成的內容,但去掉了與 IMK 選字窗有關的判斷語句。 - /// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。 - /// 該函式僅由 IMK 選字窗來存取,且對接給 commonEventHandler()。 - /// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。 - /// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。 - @discardableResult func sharedEventHandler(_ event: NSEvent) -> Bool { - commonEventHandler(event) +extension SessionCtl: CtlCandidateDelegate { + func buzz() { + IMEApp.buzz() } - func candidateCountForController(_ controller: ctlCandidateProtocol) -> Int { - _ = controller // 防止格式整理工具毀掉與此對應的參數。 - if state.isCandidateContainer { - return state.candidates.count - } - return 0 + func kanjiConversionIfRequired(_ target: String) -> String { + ChineseConverter.kanjiConversionIfRequired(target) } - /// 直接給出全部的候選字詞的字音配對陣列 - /// - Parameter controller: 對應的控制器。因為有唯一解,所以填錯了也不會有影響。 - /// - Returns: 候選字詞陣列(字音配對)。 - func candidatesForController(_ controller: ctlCandidateProtocol) -> [(String, String)] { - _ = controller // 防止格式整理工具毀掉與此對應的參數。 - if state.isCandidateContainer { - return state.candidates - } - return .init() + func candidatePairs() -> [(String, String)] { + state.isCandidateContainer ? state.candidates : [] } - func ctlCandidate(_ controller: ctlCandidateProtocol, candidateAtIndex index: Int) - -> (String, String) - { - _ = controller // 防止格式整理工具毀掉與此對應的參數。 - if state.isCandidateContainer { + func candidatePairAt(_ index: Int) -> (String, String) { + if state.isCandidateContainer, state.candidates.count > index { return state.candidates[index] } return ("", "") } - func candidateSelected(at index: Int) { + func candidatePairSelected(at index: Int) { if state.type == .ofSymbolTable, (0.. NSRect { @@ -28,14 +29,10 @@ extension ctlInputMethod { guard let client = client() else { return lineHeightRect } - var u16Cursor: Int = { - // iMessage 在 cursor == 0 時的計算會有一些偏差,所以例外處理。 - if clientBundleIdentifier == "com.apple.MobileSMS" { return state.data.u16Cursor } - if state.data.marker >= state.data.cursor { return state.data.u16Cursor } - return state.data.u16Marker // 這樣可以讓工具提示視窗始終盡量往書寫方向的後方顯示。 - }() - u16Cursor = max(min(state.data.displayedTextConverted.utf16.count, u16Cursor), 0) + var u16Cursor: Int = state.u16MarkedRange.lowerBound + u16Cursor = max(min(state.displayedTextConverted.utf16.count, u16Cursor), 0) if zeroCursor { u16Cursor = 0 } + // iMessage 的話,據此算出來的 lineHeightRect 結果的橫向座標起始點不準確。目前無解。 while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, u16Cursor >= 0 { client.attributes( forCharacterIndex: u16Cursor, lineHeightRectangle: &lineHeightRect @@ -49,7 +46,7 @@ extension ctlInputMethod { guard client() != nil else { return } let lineHeightRect = lineHeightRect() var finalOrigin: NSPoint = lineHeightRect.origin - let delta: CGFloat = lineHeightRect.size.height + 4.0 // bottomOutOfScreenAdjustmentHeight + let delta: Double = lineHeightRect.size.height + 4.0 // bottomOutOfScreenAdjustmentHeight if isVerticalTyping { finalOrigin = NSPoint( x: lineHeightRect.origin.x + lineHeightRect.size.width + 5, y: lineHeightRect.origin.y @@ -61,14 +58,14 @@ extension ctlInputMethod { }() // 強制重新初期化,因為 NSAttributedTextView 有顯示滯後性。 do { - ctlInputMethod.tooltipInstance.hide() - ctlInputMethod.tooltipInstance = .init() + Self.tooltipInstance.hide() + Self.tooltipInstance = .init() if state.type == .ofMarking { - ctlInputMethod.tooltipInstance.setColor(state: state.data.tooltipColorState) + Self.tooltipInstance.setColor(state: state.data.tooltipColorState) } } // 再設定其文字顯示內容並顯示。 - ctlInputMethod.tooltipInstance.show( + Self.tooltipInstance.show( tooltip: tooltip, at: finalOrigin, bottomOutOfScreenAdjustmentHeight: delta, direction: tooltipContentDirection ) @@ -83,7 +80,7 @@ extension ctlInputMethod { } if isVerticalTyping { return true } // 接下來的判斷並非適用於 IMK 選字窗,所以先插入排除語句。 - guard ctlInputMethod.ctlCandidateCurrent is ctlCandidateUniversal else { return false } + guard Self.ctlCandidateCurrent is CtlCandidateUniversal else { return false } // 以上是通用情形。接下來決定橫排輸入時是否使用縱排選字窗。 // 因為在拿候選字陣列時已經排序過了,所以這裡不用再多排序。 // 測量每頁顯示候選字的累計總長度。如果太長的話就強制使用縱排候選字窗。 @@ -96,7 +93,7 @@ extension ctlInputMethod { state.isVerticalCandidateWindow = (isCandidateWindowVertical || !PrefMgr.shared.useHorizontalCandidateList) - ctlInputMethod.ctlCandidateCurrent.delegate = nil + Self.ctlCandidateCurrent.delegate = nil /// 下面這一段本可直接指定 currentLayout,但這樣的話翻頁按鈕位置無法精準地重新繪製。 /// 所以只能重新初期化。壞處就是得在 ctlCandidate() 當中與 SymbolTable 控制有關的地方 @@ -109,26 +106,26 @@ extension ctlInputMethod { ? CandidateLayout.vertical : CandidateLayout.horizontal) - ctlInputMethod.ctlCandidateCurrent = + Self.ctlCandidateCurrent = PrefMgr.shared.useIMKCandidateWindow - ? ctlCandidateIMK.init(candidateLayout) : ctlCandidateUniversal.init(candidateLayout) + ? CtlCandidateIMK(candidateLayout) : CtlCandidateUniversal(candidateLayout) // set the attributes for the candidate panel (which uses NSAttributedString) let textSize = PrefMgr.shared.candidateListTextSize let minimumKeyLabelSize: Double = 10 let keyLabelSize = max(textSize / 2, minimumKeyLabelSize) - func labelFont(name: String?, size: CGFloat) -> NSFont { + func labelFont(name: String?, size: Double) -> NSFont { if let name = name { return NSFont(name: name, size: size) ?? NSFont.systemFont(ofSize: size) } return NSFont.systemFont(ofSize: size) } - ctlInputMethod.ctlCandidateCurrent.keyLabelFont = labelFont( + Self.ctlCandidateCurrent.keyLabelFont = labelFont( name: PrefMgr.shared.candidateKeyLabelFontName, size: keyLabelSize ) - ctlInputMethod.ctlCandidateCurrent.candidateFont = ctlInputMethod.candidateFont( + Self.ctlCandidateCurrent.candidateFont = Self.candidateFont( name: PrefMgr.shared.candidateTextFontName, size: textSize ) @@ -136,33 +133,46 @@ extension ctlInputMethod { let keyLabels = candidateKeys.count > 4 ? Array(candidateKeys) : Array(CandidateKey.defaultKeys) let keyLabelSuffix = state.type == .ofAssociates ? "^" : "" - ctlInputMethod.ctlCandidateCurrent.keyLabels = keyLabels.map { + Self.ctlCandidateCurrent.keyLabels = keyLabels.map { CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix) } - ctlInputMethod.ctlCandidateCurrent.delegate = self - ctlInputMethod.ctlCandidateCurrent.reloadData() + Self.ctlCandidateCurrent.delegate = self + Self.ctlCandidateCurrent.showPageButtons = PrefMgr.shared.showPageButtonsInCandidateWindow + Self.ctlCandidateCurrent.useLangIdentifier = PrefMgr.shared.handleDefaultCandidateFontsByLangIdentifier + Self.ctlCandidateCurrent.locale = { + switch inputMode { + case .imeModeCHS: return "zh-Hans" + case .imeModeCHT: + if !PrefMgr.shared.shiftJISShinjitaiOutputEnabled, !PrefMgr.shared.chineseConversionEnabled { + return "zh-Hant" + } + return "ja" + default: return "" + } + }() + Self.ctlCandidateCurrent.reloadData() if #available(macOS 10.14, *) { // Spotlight 視窗會擋住 IMK 選字窗,所以需要特殊處理。 - if let ctlCandidateCurrent = ctlInputMethod.ctlCandidateCurrent as? ctlCandidateIMK { + if let ctlCandidateCurrent = Self.ctlCandidateCurrent as? CtlCandidateIMK { while ctlCandidateCurrent.windowLevel() <= client.windowLevel() { ctlCandidateCurrent.setWindowLevel(UInt64(max(0, client.windowLevel() + 1000))) } } } - ctlInputMethod.ctlCandidateCurrent.visible = true + Self.ctlCandidateCurrent.visible = true if isVerticalTyping { - ctlInputMethod.ctlCandidateCurrent.set( + Self.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 { - ctlInputMethod.ctlCandidateCurrent.set( + Self.ctlCandidateCurrent.set( windowTopLeftPoint: NSPoint(x: lineHeightRect().origin.x, y: lineHeightRect().origin.y - 4.0), bottomOutOfScreenAdjustmentHeight: lineHeightRect().size.height + 4.0 ) @@ -178,7 +188,7 @@ extension ctlInputMethod { /// **REASON**: IMKCandidates has bug that it does not respect font attributes attached to the /// results generated from `candidiates() -> [Any]!` function. IMKCandidates is plagued with /// bugs which are not dealt in the recent decade, regardless Radar complaints from input method developers. - /// 1) Remove the usage of ".languageIdentifier" from ctlCandidateUniversal.swift (already done). + /// 1) Make sure the usage of ".languageIdentifier" is disabled in the Dev Zone of the vChewing Preferences. /// 2) Run "make update" in the project folder to download the latest git-submodule of dictionary file. /// 3) Compile the target "vChewingInstaller", run it. It will install the input method into /// "~/Library/Input Methods/" folder. Remember to ENABLE BOTH "vChewing-CHS" @@ -188,7 +198,7 @@ extension ctlInputMethod { /// 5) Do NOT enable either KangXi conversion mode nor JIS conversion mode. They are disabled by default. /// 6) Expecting the glyph differences of the candidate "骨" between PingFang SC and PingFang TC when rendering /// the candidate window in different "vChewing-CHS" and "vChewing-CHT" input modes. - static func candidateFont(name: String? = nil, size: CGFloat) -> NSFont { + static func candidateFont(name: String? = nil, size: Double) -> NSFont { let finalReturnFont: NSFont = { switch IMEApp.currentInputMode { diff --git a/Source/Modules/SessionCtl_HandleEvent.swift b/Source/Modules/SessionCtl_HandleEvent.swift new file mode 100644 index 00000000..f3eae536 --- /dev/null +++ b/Source/Modules/SessionCtl_HandleEvent.swift @@ -0,0 +1,237 @@ +// (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). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// 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 defined in MIT License. + +import InputMethodKit +import NotifierUI +import Shared + +// MARK: - Facade + +extension SessionCtl { + /// 接受所有鍵鼠事件為 NSEvent,讓輸入法判斷是否要處理、該怎樣處理。 + /// - Parameters: + /// - event: 裝置操作輸入事件,可能會是 nil。 + /// - sender: 呼叫了該函式的客體(無須使用)。 + /// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。 + @objc(handleEvent:client:) override func handle(_ event: NSEvent!, client sender: Any!) -> Bool { + _ = sender // 防止格式整理工具毀掉與此對應的參數。 + + // MARK: 前置處理 + + // 更新此時的靜態狀態標記。 + state.isASCIIMode = isASCIIMode + state.isVerticalTyping = isVerticalTyping + + // 就這傳入的 NSEvent 都還有可能是 nil,Apple InputMethodKit 團隊到底在搞三小。 + // 只針對特定類型的 client() 進行處理。 + guard let event = event, sender is IMKTextInput else { + resetKeyHandler() + return false + } + + // 用 Shift 開關半形英數模式,僅對 macOS 10.15 及之後的 macOS 有效。 + let shouldUseShiftToggleHandle: Bool = { + switch PrefMgr.shared.shiftKeyAccommodationBehavior { + case 0: return false + case 1: return Shared.arrClientShiftHandlingExceptionList.contains(clientBundleIdentifier) + case 2: return true + default: return false + } + }() + + /// 警告:這裡的 event 必須是原始 event 且不能被 var,否則會影響 Shift 中英模式判定。 + if #available(macOS 10.15, *) { + if Self.theShiftKeyDetector.check(event), !PrefMgr.shared.disableShiftTogglingAlphanumericalMode { + if !shouldUseShiftToggleHandle || (!rencentKeyHandledByKeyHandlerEtc && shouldUseShiftToggleHandle) { + let status = NSLocalizedString("NotificationSwitchShift", comment: "") + Notifier.notify( + message: isASCIIMode.toggled() + ? NSLocalizedString("Alphanumerical Input Mode", comment: "") + "\n" + status + : NSLocalizedString("Chinese Input Mode", comment: "") + "\n" + status + ) + } + if shouldUseShiftToggleHandle { + rencentKeyHandledByKeyHandlerEtc = false + } + return false + } + } + + // MARK: 針對客體的具體處理 + + // 不再讓威注音處理由 Shift 切換到的英文模式的按鍵輸入。 + if isASCIIMode { return false } + + /// 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。 + /// 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。 + /// 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false, + /// 否則,每次處理這種判斷時都會觸發 NSInternalInconsistencyException。 + if event.type == .flagsChanged { return false } + + /// 沒有文字輸入客體的話,就不要再往下處理了。 + guard client() != nil else { return false } + + var eventToDeal = event + + // 如果是方向鍵輸入的話,就想辦法帶上標記資訊、來說明當前是縱排還是橫排。 + if event.isUp || event.isDown || event.isLeft || event.isRight { + eventToDeal = event.reinitiate(charactersIgnoringModifiers: isVerticalTyping ? "Vertical" : "Horizontal") ?? event + } + + // 使 NSEvent 自翻譯,這樣可以讓 Emacs NSEvent 變成標準 NSEvent。 + if eventToDeal.isEmacsKey { + let verticalProcessing = (state.isCandidateContainer) ? state.isVerticalCandidateWindow : state.isVerticalTyping + eventToDeal = eventToDeal.convertFromEmacKeyEvent(isVerticalContext: verticalProcessing) + } + + // 準備修飾鍵,用來判定要新增的詞彙是否需要賦以非常低的權重。 + Self.areWeNerfing = eventToDeal.modifierFlags.contains([.shift, .command]) + + // IMK 選字窗處理,當且僅當啟用了 IMK 選字窗的時候才會生效。 + if let result = imkCandidatesEventPreHandler(event: eventToDeal) { + if shouldUseShiftToggleHandle { rencentKeyHandledByKeyHandlerEtc = result } + return result + } + + /// 剩下的 NSEvent 直接交給 commonEventHandler 來處理。 + /// 這樣可以與 IMK 選字窗共用按鍵處理資源,維護起來也比較方便。 + let result = commonEventHandler(eventToDeal) + if shouldUseShiftToggleHandle { + rencentKeyHandledByKeyHandlerEtc = result + } + return result + } +} + +// MARK: - Private functions + +extension SessionCtl { + /// 完成 handle() 函式本該完成的內容,但去掉了與 IMK 選字窗有關的判斷語句。 + /// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。 + /// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。 + /// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。 + private func commonEventHandler(_ event: NSEvent) -> Bool { + // 無法列印的訊號輸入,一概不作處理。 + // 這個過程不能放在 KeyHandler 內,否則不會起作用。 + if !event.charCode.isPrintable { return false } + + /// 將按鍵行為與當前輸入法狀態結合起來、交給按鍵調度模組來處理。 + /// 再根據返回的 result bool 數值來告知 IMK「這個按鍵事件是被處理了還是被放行了」。 + /// 這裡不用 keyHandler.handleCandidate() 是因為需要針對聯想詞輸入狀態做額外處理。 + let result = keyHandler.handle(input: event, state: state) { newState in + self.handle(state: newState) + } errorCallback: { errorString in + vCLog(errorString) + IMEApp.buzz() + } + return result + } + + /// 完成 handle() 函式本該完成的內容,但專門處理與 IMK 選字窗有關的判斷語句。 + /// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。 + /// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。 + /// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。 + private func imkCandidatesEventPreHandler(event eventToDeal: NSEvent) -> Bool? { + // IMK 選字窗處理,當且僅當啟用了 IMK 選字窗的時候才會生效。 + // 這樣可以讓 interpretKeyEvents() 函式自行判斷: + // - 是就地交給 imkCandidates.interpretKeyEvents() 處理? + // - 還是藉由 delegate 扔回 SessionCtl 給 KeyHandler 處理? + if let imkCandidates = Self.ctlCandidateCurrent as? CtlCandidateIMK, imkCandidates.visible { + let event: NSEvent = CtlCandidateIMK.replaceNumPadKeyCodes(target: eventToDeal) ?? eventToDeal + + // Shift+Enter 是個特殊情形,不提前攔截處理的話、會有垃圾參數傳給 delegate 的 keyHandler 從而崩潰。 + // 所以這裡直接將 Shift Flags 清空。 + if event.isShiftHold, event.isEnter { + guard let newEvent = event.reinitiate(modifierFlags: []) else { + NSSound.beep() + return true + } + + return imkCandidatesEventSubHandler(event: newEvent) + } + + // 聯想詞選字。 + if let newChar = CtlCandidateIMK.defaultIMKSelectionKey[event.keyCode], + event.isShiftHold, state.type == .ofAssociates, + let newEvent = event.reinitiate(modifierFlags: [], characters: newChar) + { + if #available(macOS 10.14, *) { + imkCandidates.handleKeyboardEvent(newEvent) + } else { + imkCandidates.interpretKeyEvents([newEvent]) + } + return true + } + + return imkCandidatesEventSubHandler(event: event) + } + return nil + } + + private func imkCandidatesEventSubHandler(event: NSEvent) -> Bool { + let eventArray = [event] + guard let imkC = Self.ctlCandidateCurrent as? CtlCandidateIMK else { return false } + if event.isEsc || event.isBackSpace || event.isDelete || (event.isShiftHold && !event.isSpace) { + return commonEventHandler(event) + } else if event.isSymbolMenuPhysicalKey { + // 符號鍵的行為是固定的,不受偏好設定影響。 + switch imkC.currentLayout { + case .horizontal: _ = event.isShiftHold ? imkC.moveUp(self) : imkC.moveDown(self) + case .vertical: _ = event.isShiftHold ? imkC.moveLeft(self) : imkC.moveRight(self) + } + return true + } else if event.isSpace { + switch PrefMgr.shared.specifyShiftSpaceKeyBehavior { + case true: _ = event.isShiftHold ? imkC.highlightNextCandidate() : imkC.showNextPage() + case false: _ = event.isShiftHold ? imkC.showNextPage() : imkC.highlightNextCandidate() + } + return true + } else if event.isTab { + switch PrefMgr.shared.specifyShiftTabKeyBehavior { + case true: _ = event.isShiftHold ? imkC.showPreviousPage() : imkC.showNextPage() + case false: _ = event.isShiftHold ? imkC.highlightPreviousCandidate() : imkC.highlightNextCandidate() + } + return true + } else { + if let newChar = CtlCandidateIMK.defaultIMKSelectionKey[event.keyCode] { + /// 根據 KeyCode 重新換算一下選字鍵的 NSEvent,糾正其 Character 數值。 + /// 反正 IMK 選字窗目前也沒辦法修改選字鍵。 + let newEvent = event.reinitiate(characters: newChar) + if let newEvent = newEvent { + if PrefMgr.shared.useSCPCTypingMode, state.type == .ofAssociates { + // 註:input.isShiftHold 已經在 Self.handle() 內處理,因為在那邊處理才有效。 + return event.isShiftHold ? true : commonEventHandler(event) + } else { + if #available(macOS 10.14, *) { + imkC.handleKeyboardEvent(newEvent) + } else { + imkC.interpretKeyEvents([newEvent]) + } + return true + } + } + } + + if PrefMgr.shared.useSCPCTypingMode, !event.isReservedKey { + return commonEventHandler(event) + } + + if state.type == .ofAssociates, + !event.isPageUp, !event.isPageDown, !event.isCursorForward, !event.isCursorBackward, + !event.isCursorClockLeft, !event.isCursorClockRight, !event.isSpace, + !event.isEnter || !PrefMgr.shared.alsoConfirmAssociatedCandidatesByEnter + { + return commonEventHandler(event) + } + imkC.interpretKeyEvents(eventArray) + return true + } + } +} diff --git a/Source/Modules/ctlInputMethod_HandleStates.swift b/Source/Modules/SessionCtl_HandleStates.swift similarity index 83% rename from Source/Modules/ctlInputMethod_HandleStates.swift rename to Source/Modules/SessionCtl_HandleStates.swift index 2540d20e..2f3c8928 100644 --- a/Source/Modules/ctlInputMethod_HandleStates.swift +++ b/Source/Modules/SessionCtl_HandleStates.swift @@ -8,9 +8,12 @@ // marks, or product names of Contributor, except as required to fulfill notice // requirements defined in MIT License. +import PopupCompositionBuffer +import Shared + // MARK: - 狀態調度 (State Handling) -extension ctlInputMethod { +extension SessionCtl { /// 針對傳入的新狀態進行調度。 /// /// 先將舊狀態單獨記錄起來,再將新舊狀態作為參數, @@ -21,9 +24,9 @@ extension ctlInputMethod { state = newState switch state.type { case .ofDeactivated: - ctlInputMethod.ctlCandidateCurrent.delegate = nil - ctlInputMethod.ctlCandidateCurrent.visible = false - ctlInputMethod.tooltipInstance.hide() + Self.ctlCandidateCurrent.delegate = nil + Self.ctlCandidateCurrent.visible = false + Self.tooltipInstance.hide() if previous.hasComposition { commit(text: previous.displayedText) } @@ -36,29 +39,29 @@ extension ctlInputMethod { state = IMEState.ofEmpty() previous = state } - ctlInputMethod.ctlCandidateCurrent.visible = false - ctlInputMethod.tooltipInstance.hide() + Self.ctlCandidateCurrent.visible = false + Self.tooltipInstance.hide() // 全專案用以判斷「.Abortion」的地方僅此一處。 if previous.hasComposition, state.type != .ofAbortion { commit(text: previous.displayedText) } // 在這裡手動再取消一次選字窗與工具提示的顯示,可謂雙重保險。 - ctlInputMethod.ctlCandidateCurrent.visible = false - ctlInputMethod.tooltipInstance.hide() + Self.ctlCandidateCurrent.visible = false + Self.tooltipInstance.hide() clearInlineDisplay() // 最後一道保險 keyHandler.clear() case .ofCommitting: - ctlInputMethod.ctlCandidateCurrent.visible = false - ctlInputMethod.tooltipInstance.hide() + Self.ctlCandidateCurrent.visible = false + Self.tooltipInstance.hide() let textToCommit = state.textToCommit if !textToCommit.isEmpty { commit(text: textToCommit) } clearInlineDisplay() // 最後一道保險 keyHandler.clear() case .ofInputting: - ctlInputMethod.ctlCandidateCurrent.visible = false - ctlInputMethod.tooltipInstance.hide() + Self.ctlCandidateCurrent.visible = false + Self.tooltipInstance.hide() let textToCommit = state.textToCommit if !textToCommit.isEmpty { commit(text: textToCommit) } setInlineDisplayWithCursor() @@ -66,27 +69,27 @@ extension ctlInputMethod { show(tooltip: state.tooltip) } case .ofMarking: - ctlInputMethod.ctlCandidateCurrent.visible = false + Self.ctlCandidateCurrent.visible = false setInlineDisplayWithCursor() if state.tooltip.isEmpty { - ctlInputMethod.tooltipInstance.hide() + Self.tooltipInstance.hide() } else { show(tooltip: state.tooltip) } case .ofCandidates, .ofAssociates, .ofSymbolTable: - ctlInputMethod.tooltipInstance.hide() + Self.tooltipInstance.hide() setInlineDisplayWithCursor() showCandidates() default: break } // 浮動組字窗的顯示判定 if state.hasComposition, PrefMgr.shared.clientsIMKTextInputIncapable.contains(clientBundleIdentifier) { - ctlInputMethod.popupCompositionBuffer.isTypingDirectionVertical = isVerticalTyping - ctlInputMethod.popupCompositionBuffer.show( + Self.popupCompositionBuffer.isTypingDirectionVertical = isVerticalTyping + Self.popupCompositionBuffer.show( state: state, at: lineHeightRect(zeroCursor: true).origin ) } else { - ctlInputMethod.popupCompositionBuffer.hide() + Self.popupCompositionBuffer.hide() } } diff --git a/Source/Modules/SessionCtl_IMKCandidatesData.swift b/Source/Modules/SessionCtl_IMKCandidatesData.swift new file mode 100644 index 00000000..066847c3 --- /dev/null +++ b/Source/Modules/SessionCtl_IMKCandidatesData.swift @@ -0,0 +1,129 @@ +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// 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 defined in MIT License. + +import Foundation +import Tekkon + +// MARK: - IMKCandidates 功能擴充 + +extension SessionCtl { + /// 生成 IMK 選字窗專用的候選字串陣列。 + /// - Parameter sender: 呼叫了該函式的客體(無須使用)。 + /// - Returns: IMK 選字窗專用的候選字串陣列。 + override func candidates(_ sender: Any!) -> [Any]! { + _ = sender // 防止格式整理工具毀掉與此對應的參數。 + var arrResult = [String]() + + // 注意:下文中的不可列印字元是用來方便在 IMEState 當中用來分割資料的。 + func handleIMKCandidatesPrepared(_ candidates: [(String, String)], prefix: String = "") { + for theCandidate in candidates { + let theConverted = ChineseConverter.kanjiConversionIfRequired(theCandidate.1) + var result = (theCandidate.1 == theConverted) ? theCandidate.1 : "\(theConverted)\u{1A}(\(theCandidate.1))" + if arrResult.contains(result) { + let reading: String = + PrefMgr.shared.showHanyuPinyinInCompositionBuffer + ? Tekkon.cnvPhonaToHanyuPinyin(target: Tekkon.restoreToneOneInZhuyinKey(target: theCandidate.0)) + : theCandidate.0 + result = "\(result)\u{17}(\(reading))" + } + arrResult.append(prefix + result) + } + } + + if state.type == .ofAssociates { + handleIMKCandidatesPrepared(state.candidates, prefix: "⇧") + } else if state.type == .ofSymbolTable { + // 分類符號選單不會出現同符異音項、不需要康熙 / JIS 轉換,所以使用簡化過的處理方式。 + arrResult = state.candidates.map(\.1) + } else if state.type == .ofCandidates { + guard !state.candidates.isEmpty else { return .init() } + if state.candidates[0].0.contains("_punctuation") { + arrResult = state.candidates.map(\.1) // 標點符號選單處理。 + } else { + handleIMKCandidatesPrepared(state.candidates) + } + } + + return arrResult + } + + /// IMK 選字窗限定函式,只要選字窗內的高亮內容選擇出現變化了、就會呼叫這個函式。 + /// - Parameter _: 已經高亮選中的候選字詞內容。 + override open func candidateSelectionChanged(_: NSAttributedString!) { + // 警告:不要考慮用實作這個函式的方式來更新內文組字區的顯示。 + // 因為這樣會導致 IMKServer.commitCompositionWithReply() 呼叫你本來不想呼叫的 commitComposition(), + // 然後 keyHandler 會被重設,屆時輸入法會在狀態處理等方面崩潰掉。 + + // 這個函式的實作其實很容易誘發各種崩潰,所以最好不要輕易實作。 + + // 有些幹話還是要講的: + // 在這個函式當中試圖(無論是否拿著傳入的參數)從 ctlCandidateIMK 找 identifier 的話, + // 只會找出 NSNotFound。你想 NSLog 列印看 identifier 是多少,輸入法直接崩潰。 + // 而且會他媽的崩得連 console 內的 ips 錯誤報告都沒有。 + // 在下文的 candidateSelected() 試圖看每個候選字的 identifier 的話,永遠都只能拿到 NSNotFound。 + // 衰洨 IMK 真的看上去就像是沒有做過單元測試的東西,賈伯斯有檢查過的話會被氣得從棺材裡爬出來。 + } + + /// IMK 選字窗限定函式,只要選字窗確認了某個候選字詞的選擇、就會呼叫這個函式。 + /// - Parameter candidateString: 已經確認的候選字詞內容。 + override open func candidateSelected(_ candidateString: NSAttributedString!) { + let candidateString: String = candidateString?.string ?? "" + if state.type == .ofAssociates { + if !PrefMgr.shared.alsoConfirmAssociatedCandidatesByEnter { + handle(state: IMEState.ofAbortion()) + return + } + } + + var indexDeducted = 0 + + // 注意:下文中的不可列印字元是用來方便在 IMEState 當中用來分割資料的。 + func handleIMKCandidatesSelected(_ candidates: [(String, String)], prefix: String = "") { + for (i, neta) in candidates.enumerated() { + let theConverted = ChineseConverter.kanjiConversionIfRequired(neta.1) + let netaShown = (neta.1 == theConverted) ? neta.1 : "\(theConverted)\u{1A}(\(neta.1))" + let reading: String = + PrefMgr.shared.showHanyuPinyinInCompositionBuffer + ? Tekkon.cnvPhonaToHanyuPinyin(target: Tekkon.restoreToneOneInZhuyinKey(target: neta.0)) : neta.0 + let netaShownWithPronunciation = "\(netaShown)\u{17}(\(reading))" + if candidateString == prefix + netaShownWithPronunciation { + indexDeducted = i + break + } + if candidateString == prefix + netaShown { + indexDeducted = i + break + } + } + } + + // 分類符號選單不會出現同符異音項、不需要康熙 / JIS 轉換,所以使用簡化過的處理方式。 + func handleSymbolCandidatesSelected(_ candidates: [(String, String)]) { + for (i, neta) in candidates.enumerated() { + if candidateString == neta.1 { + indexDeducted = i + break + } + } + } + + if state.type == .ofAssociates { + handleIMKCandidatesSelected(state.candidates, prefix: "⇧") + } else if state.type == .ofSymbolTable { + handleSymbolCandidatesSelected(state.candidates) + } else if state.type == .ofCandidates { + guard !state.candidates.isEmpty else { return } + if state.candidates[0].0.contains("_punctuation") { + handleSymbolCandidatesSelected(state.candidates) // 標點符號選單處理。 + } else { + handleIMKCandidatesSelected(state.candidates) + } + } + candidatePairSelected(at: indexDeducted) + } +} diff --git a/Source/Modules/ctlInputMethod_Menu.swift b/Source/Modules/SessionCtl_Menu.swift similarity index 97% rename from Source/Modules/ctlInputMethod_Menu.swift rename to Source/Modules/SessionCtl_Menu.swift index 651fe196..deada5c3 100644 --- a/Source/Modules/ctlInputMethod_Menu.swift +++ b/Source/Modules/SessionCtl_Menu.swift @@ -8,6 +8,7 @@ // marks, or product names of Contributor, except as required to fulfill notice // requirements defined in MIT License. +import NotifierUI import Preferences extension Bool { @@ -20,7 +21,7 @@ extension Bool { // 因為選單部分的內容又臭又長,所以就單獨拉到一個檔案內管理了。 -extension ctlInputMethod { +extension SessionCtl { override func menu() -> NSMenu! { let optionKeyPressed = NSEvent.modifierFlags.contains(.option) @@ -191,7 +192,7 @@ extension ctlInputMethod { // MARK: - IME Menu Items -extension ctlInputMethod { +extension SessionCtl { @objc override func showPreferences(_: Any?) { if #unavailable(macOS 10.15) { showLegacyPreferences() @@ -223,7 +224,7 @@ extension ctlInputMethod { @objc func toggleSCPCTypingMode(_: Any? = nil) { resetKeyHandler() - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("Per-Char Select Mode", comment: "") + "\n" + (PrefMgr.shared.useSCPCTypingMode.toggled() ? NSLocalizedString("NotificationSwitchON", comment: "") @@ -233,7 +234,7 @@ extension ctlInputMethod { @objc func toggleChineseConverter(_: Any?) { resetKeyHandler() - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("Force KangXi Writing", comment: "") + "\n" + (PrefMgr.shared.chineseConversionEnabled.toggled() ? NSLocalizedString("NotificationSwitchON", comment: "") @@ -243,7 +244,7 @@ extension ctlInputMethod { @objc func toggleShiftJISShinjitaiOutput(_: Any?) { resetKeyHandler() - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("JIS Shinjitai Output", comment: "") + "\n" + (PrefMgr.shared.shiftJISShinjitaiOutputEnabled.toggled() ? NSLocalizedString("NotificationSwitchON", comment: "") @@ -253,7 +254,7 @@ extension ctlInputMethod { @objc func toggleCurrencyNumerals(_: Any?) { resetKeyHandler() - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("Currency Numeral Output", comment: "") + "\n" + (PrefMgr.shared.currencyNumeralsEnabled.toggled() ? NSLocalizedString("NotificationSwitchON", comment: "") @@ -263,7 +264,7 @@ extension ctlInputMethod { @objc func toggleHalfWidthPunctuation(_: Any?) { resetKeyHandler() - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("Half-Width Punctuation Mode", comment: "") + "\n" + (PrefMgr.shared.halfWidthPunctuationEnabled.toggled() ? NSLocalizedString("NotificationSwitchON", comment: "") @@ -273,7 +274,7 @@ extension ctlInputMethod { @objc func toggleCNS11643Enabled(_: Any?) { resetKeyHandler() - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("CNS11643 Mode", comment: "") + "\n" + (PrefMgr.shared.cns11643Enabled.toggled() ? NSLocalizedString("NotificationSwitchON", comment: "") @@ -283,7 +284,7 @@ extension ctlInputMethod { @objc func toggleSymbolEnabled(_: Any?) { resetKeyHandler() - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("Symbol & Emoji Input", comment: "") + "\n" + (PrefMgr.shared.symbolInputEnabled.toggled() ? NSLocalizedString("NotificationSwitchON", comment: "") @@ -293,7 +294,7 @@ extension ctlInputMethod { @objc func toggleAssociatedPhrasesEnabled(_: Any?) { resetKeyHandler() - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("Per-Char Associated Phrases", comment: "") + "\n" + (PrefMgr.shared.associatedPhrasesEnabled.toggled() ? NSLocalizedString("NotificationSwitchON", comment: "") @@ -303,7 +304,7 @@ extension ctlInputMethod { @objc func togglePhraseReplacement(_: Any?) { resetKeyHandler() - NotifierController.notify( + Notifier.notify( message: NSLocalizedString("Use Phrase Replacement", comment: "") + "\n" + (PrefMgr.shared.phraseReplacementEnabled.toggled() ? NSLocalizedString("NotificationSwitchON", comment: "") diff --git a/Source/Modules/UIModules/CandidateUI/ctlCandidateIMK.swift b/Source/Modules/UIModules/CandidateUI/IMKCandidatesImpl.swift similarity index 63% rename from Source/Modules/UIModules/CandidateUI/ctlCandidateIMK.swift rename to Source/Modules/UIModules/CandidateUI/IMKCandidatesImpl.swift index 35d360f3..40dce0b2 100644 --- a/Source/Modules/UIModules/CandidateUI/ctlCandidateIMK.swift +++ b/Source/Modules/UIModules/CandidateUI/IMKCandidatesImpl.swift @@ -6,12 +6,19 @@ // marks, or product names of Contributor, except as required to fulfill notice // requirements defined in MIT License. -public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol { +import CandidateWindow +import Shared + +/// 威注音自用的 IMKCandidates 型別。因為有用到 bridging header,所以無法弄成 Swift Package。 +public class CtlCandidateIMK: IMKCandidates, CtlCandidateProtocol { + public var showPageButtons: Bool = false + public var locale: String = "" + public var useLangIdentifier: Bool = false public var currentLayout: CandidateLayout = .horizontal public static let defaultIMKSelectionKey: [UInt16: String] = [ 18: "1", 19: "2", 20: "3", 21: "4", 23: "5", 22: "6", 26: "7", 28: "8", 25: "9", ] - public weak var delegate: ctlCandidateDelegate? { + public weak var delegate: CtlCandidateDelegate? { didSet { reloadData() } @@ -40,26 +47,15 @@ public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol { ofSize: 14, weight: .medium ) - public var candidateFont = NSFont.systemFont(ofSize: PrefMgr.shared.candidateListTextSize) { + public var candidateFont = NSFont.systemFont(ofSize: 16) { didSet { if #available(macOS 10.14, *) { setFontSize(candidateFont.pointSize) } var attributes = attributes() // FB11300759: Set "NSAttributedString.Key.font" doesn't work. attributes?[NSAttributedString.Key.font] = candidateFont - if PrefMgr.shared.handleDefaultCandidateFontsByLangIdentifier { - switch IMEApp.currentInputMode { - case .imeModeCHS: - if #available(macOS 12.0, *) { - attributes?[NSAttributedString.Key.languageIdentifier] = "zh-Hans" as AnyObject - } - case .imeModeCHT: - if #available(macOS 12.0, *) { - attributes?[NSAttributedString.Key.languageIdentifier] = - (PrefMgr.shared.shiftJISShinjitaiOutputEnabled || PrefMgr.shared.chineseConversionEnabled) - ? "ja" as AnyObject : "zh-Hant" as AnyObject - } - default: - break + if #available(macOS 12.0, *) { + if useLangIdentifier { + attributes?[NSAttributedString.Key.languageIdentifier] = locale as AnyObject } } setAttributes(attributes) @@ -119,7 +115,7 @@ public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol { private var pageCount: Int { guard let delegate = delegate else { return 0 } - let totalCount = delegate.candidateCountForController(self) + let totalCount = delegate.candidatePairs().count let keyLabelCount = keyLabels.count return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0) } @@ -151,7 +147,7 @@ public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol { public func candidateIndexAtKeyLabelIndex(_ index: Int) -> Int { guard let delegate = delegate else { return Int.max } let result = currentPageIndex * keyLabels.count + index - return result < delegate.candidateCountForController(self) ? result : Int.max + return result < delegate.candidatePairs().count ? result : Int.max } public var selectedCandidateIndex: Int { @@ -159,17 +155,17 @@ public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol { set { selectCandidate(withIdentifier: newValue) } } - public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { + public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: Double) { DispatchQueue.main.async { self.doSet(windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height) } } - func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: CGFloat) { + func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: Double) { + guard var screenFrame = NSScreen.main?.visibleFrame else { return } var adjustedPoint = windowTopLeftPoint let windowSize = candidateFrame().size var delta = heightDelta - var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.seniorTheBeast for frame in NSScreen.screens.map(\.visibleFrame).filter({ $0.contains(windowTopLeftPoint) }) { screenFrame = frame break @@ -185,74 +181,6 @@ public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol { setCandidateFrameTopLeft(adjustedPoint) } - - override public func interpretKeyEvents(_ eventArray: [NSEvent]) { - // 鬼知道為什麼這個函式接收的參數是陣列,但經過測試卻發現這個函式收到的陣列往往內容只有一個。 - // 這也可能是 Objective-C 當中允許接收內容為 nil 的一種方式。 - guard !eventArray.isEmpty else { return } - let event = eventArray[0] - guard let delegate = delegate else { return } - if event.isEsc || event.isBackSpace || event.isDelete || (event.isShiftHold && !event.isSpace) { - _ = delegate.sharedEventHandler(event) - } else if event.isSymbolMenuPhysicalKey { - // 符號鍵的行為是固定的,不受偏好設定影響。 - switch currentLayout { - case .horizontal: event.isShiftHold ? moveUp(self) : moveDown(self) - case .vertical: event.isShiftHold ? moveLeft(self) : moveRight(self) - } - } else if event.isSpace { - switch PrefMgr.shared.specifyShiftSpaceKeyBehavior { - case true: _ = event.isShiftHold ? highlightNextCandidate() : showNextPage() - case false: _ = event.isShiftHold ? showNextPage() : highlightNextCandidate() - } - } else if event.isTab { - switch PrefMgr.shared.specifyShiftTabKeyBehavior { - case true: _ = event.isShiftHold ? showPreviousPage() : showNextPage() - case false: _ = event.isShiftHold ? highlightPreviousCandidate() : highlightNextCandidate() - } - } else { - if let newChar = Self.defaultIMKSelectionKey[event.keyCode] { - /// 根據 KeyCode 重新換算一下選字鍵的 NSEvent,糾正其 Character 數值。 - /// 反正 IMK 選字窗目前也沒辦法修改選字鍵。 - let newEvent = event.reinitiate(characters: newChar) - if let newEvent = newEvent { - if PrefMgr.shared.useSCPCTypingMode, delegate.isAssociatedPhrasesState { - // 註:input.isShiftHold 已經在 ctlInputMethod.handle() 內處理,因為在那邊處理才有效。 - if !event.isShiftHold { - _ = delegate.sharedEventHandler(event) - return - } - } else { - if #available(macOS 10.14, *) { - handleKeyboardEvent(newEvent) - } else { - super.interpretKeyEvents([newEvent]) - } - return - } - } - } - - if PrefMgr.shared.useSCPCTypingMode, !event.isReservedKey { - _ = delegate.sharedEventHandler(event) - return - } - - if delegate.isAssociatedPhrasesState, - !event.isPageUp, !event.isPageDown, !event.isCursorForward, !event.isCursorBackward, - !event.isCursorClockLeft, !event.isCursorClockRight, !event.isSpace, - !event.isEnter || !PrefMgr.shared.alsoConfirmAssociatedCandidatesByEnter - { - _ = delegate.sharedEventHandler(event) - return - } - super.interpretKeyEvents(eventArray) - } - } - - public func superInterpretKeyEvents(_ eventArray: [NSEvent]) { - super.interpretKeyEvents(eventArray) - } } // MARK: - Generate TISInputSource Object @@ -277,7 +205,7 @@ var currentTISInputSource: TISInputSource? { // MARK: - Translating NumPad KeyCodes to Default IMK Candidate Selection KeyCodes. -extension ctlCandidateIMK { +extension CtlCandidateIMK { public static func replaceNumPadKeyCodes(target event: NSEvent) -> NSEvent? { let mapNumPadKeyCodeTranslation: [UInt16: UInt16] = [ 83: 18, 84: 19, 85: 20, 86: 21, 87: 23, 88: 22, 89: 26, 91: 28, 92: 25, diff --git a/Source/Modules/UIModules/CandidateUI/ctlCandidate.swift b/Source/Modules/UIModules/CandidateUI/ctlCandidate.swift deleted file mode 100644 index 29434ccc..00000000 --- a/Source/Modules/UIModules/CandidateUI/ctlCandidate.swift +++ /dev/null @@ -1,175 +0,0 @@ -// (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). -// ==================== -// This code is released under the MIT license (SPDX-License-Identifier: MIT) -// ... with NTL restriction stating that: -// 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 defined in MIT License. - -public enum CandidateLayout { - case horizontal - case vertical -} - -public class CandidateKeyLabel: NSObject { - public private(set) var key: String - public private(set) var displayedText: String - - public init(key: String, displayedText: String) { - self.key = key - self.displayedText = displayedText - super.init() - } -} - -public protocol ctlCandidateDelegate: AnyObject { - var isAssociatedPhrasesState: Bool { get } - func sharedEventHandler(_ event: NSEvent) -> Bool - func candidateCountForController(_ controller: ctlCandidateProtocol) -> Int - func candidatesForController(_ controller: ctlCandidateProtocol) -> [(String, String)] - func ctlCandidate(_ controller: ctlCandidateProtocol, candidateAtIndex index: Int) - -> (String, String) - func candidateSelected(at index: Int) -} - -public protocol ctlCandidateProtocol { - var currentLayout: CandidateLayout { get set } - var delegate: ctlCandidateDelegate? { get set } - var selectedCandidateIndex: Int { get set } - var visible: Bool { get set } - var windowTopLeftPoint: NSPoint { get set } - var keyLabels: [CandidateKeyLabel] { get set } - var keyLabelFont: NSFont { get set } - var candidateFont: NSFont { get set } - var tooltip: String { get set } - - init(_ layout: CandidateLayout) - func reloadData() - func showNextPage() -> Bool - func showPreviousPage() -> Bool - func highlightNextCandidate() -> Bool - func highlightPreviousCandidate() -> Bool - func candidateIndexAtKeyLabelIndex(_: Int) -> Int - func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) -} - -public class ctlCandidate: NSWindowController, ctlCandidateProtocol { - public var currentLayout: CandidateLayout = .horizontal - public weak var delegate: ctlCandidateDelegate? { - didSet { - reloadData() - } - } - - public var selectedCandidateIndex: Int = .max - public var visible = false { - didSet { - NSObject.cancelPreviousPerformRequests(withTarget: self) - if visible { - window?.perform(#selector(NSWindow.orderFront(_:)), with: self, afterDelay: 0.0) - } else { - window?.perform(#selector(NSWindow.orderOut(_:)), with: self, afterDelay: 0.0) - } - } - } - - 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.async { - self.set(windowTopLeftPoint: newValue, bottomOutOfScreenAdjustmentHeight: 0) - } - } - } - - public required init(_: CandidateLayout = .horizontal) { - super.init(window: .init()) - visible = false - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public var keyLabels: [CandidateKeyLabel] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] - .map { - CandidateKeyLabel(key: $0, displayedText: $0) - } - - public var keyLabelFont = NSFont.monospacedDigitSystemFont( - ofSize: 14, weight: .medium - ) - public var candidateFont = NSFont.systemFont(ofSize: 18) - public var tooltip: String = "" - - public func reloadData() {} - - @discardableResult public func showNextPage() -> Bool { - false - } - - @discardableResult public func showPreviousPage() -> Bool { - false - } - - @discardableResult public func highlightNextCandidate() -> Bool { - false - } - - @discardableResult public func highlightPreviousCandidate() -> Bool { - false - } - - public func candidateIndexAtKeyLabelIndex(_: Int) -> Int { - Int.max - } - - /// Sets the location of the candidate window. - /// - /// Please note that the method has side effects that modifies - /// `windowTopLeftPoint` to make the candidate window to stay in at least - /// in a screen. - /// - /// - Parameters: - /// - windowTopLeftPoint: The given location. - /// - height: The height that helps the window not to be out of the bottom - /// of a screen. - public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { - DispatchQueue.main.async { - self.doSet( - windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height - ) - } - } - - func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: CGFloat) { - guard let window = window else { return } - let windowSize = window.frame.size - - var adjustedPoint = windowTopLeftPoint - var delta = heightDelta - var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.seniorTheBeast - for frame in NSScreen.screens.map(\.visibleFrame).filter({ $0.contains(windowTopLeftPoint) }) { - screenFrame = frame - break - } - - if delta > screenFrame.size.height / 2.0 { delta = 0.0 } - - if adjustedPoint.y < screenFrame.minY + windowSize.height { - adjustedPoint.y = windowTopLeftPoint.y + windowSize.height + delta - } - adjustedPoint.y = min(adjustedPoint.y, screenFrame.maxY - 1.0) - adjustedPoint.x = min(max(adjustedPoint.x, screenFrame.minX), screenFrame.maxX - windowSize.width - 1.0) - - window.setFrameTopLeftPoint(adjustedPoint) - } -} diff --git a/Source/Modules/UIModules/NotifierUI/NotifierController.swift b/Source/Modules/UIModules/NotifierUI/NotifierController.swift deleted file mode 100644 index f0a7bfcf..00000000 --- a/Source/Modules/UIModules/NotifierUI/NotifierController.swift +++ /dev/null @@ -1,226 +0,0 @@ -// (c) 2021 and onwards Weizhong Yang (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -// ==================== -// This code is released under the MIT license (SPDX-License-Identifier: MIT) -// ... with NTL restriction stating that: -// 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 defined in MIT License. - -private protocol NotifierWindowDelegate: AnyObject { - func windowDidBecomeClicked(_ window: NotifierWindow) -} - -private class NotifierWindow: NSWindow { - weak var clickDelegate: NotifierWindowDelegate? - - override func mouseDown(with _: NSEvent) { - clickDelegate?.windowDidBecomeClicked(self) - } -} - -private let kWindowWidth: CGFloat = 213.0 -private let kWindowHeight: CGFloat = 60.0 - -public class NotifierController: NSWindowController, NotifierWindowDelegate { - static var message: String = "" { - didSet { - if !Self.message.isEmpty { - NotifierController.initiateWithNoStay(message: message) - Self.message = "" - } - } - } - - private var messageTextField: NSTextField - - private var message: String = "" { - didSet { - let paraStyle = NSMutableParagraphStyle() - paraStyle.setParagraphStyle(NSParagraphStyle.default) - paraStyle.alignment = .center - let attr: [NSAttributedString.Key: AnyObject] = [ - .foregroundColor: foregroundColor, - .font: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)), - .paragraphStyle: paraStyle, - ] - let attrString = NSAttributedString(string: message, attributes: attr) - messageTextField.attributedStringValue = attrString - let width = window?.frame.width ?? kWindowWidth - let rect = attrString.boundingRect( - with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin - ) - let height = rect.height - let x = messageTextField.frame.origin.x - let y = ((window?.frame.height ?? kWindowHeight) - height) / 2 - let newFrame = NSRect(x: x, y: y, width: width, height: height) - messageTextField.frame = newFrame - } - } - - private var shouldStay = false - private var backgroundColor: NSColor = .textBackgroundColor { - didSet { - window?.backgroundColor = backgroundColor - } - } - - private var foregroundColor: NSColor = .controlTextColor { - didSet { - messageTextField.textColor = foregroundColor - } - } - - private var waitTimer: Timer? - private var fadeTimer: Timer? - - private static var instanceCount = 0 - private static var lastLocation = NSPoint.zero - - private static func initiateWithNoStay(message: String) { - let controller = NotifierController() - controller.message = message - controller.show() - } - - public static func notify(message: String) { - Self.message = message - } - - public static func notify(message: String, stay: Bool) { - let controller = NotifierController() - controller.message = message - controller.shouldStay = stay - controller.show() - } - - private static func increaseInstanceCount() { - instanceCount += 1 - } - - private static func decreaseInstanceCount() { - instanceCount -= 1 - if instanceCount < 0 { - instanceCount = 0 - } - } - - private init() { - let screenRect = NSScreen.main?.visibleFrame ?? NSRect.seniorTheBeast - let contentRect = NSRect(x: 0, y: 0, width: kWindowWidth, height: kWindowHeight) - var windowRect = contentRect - windowRect.origin.x = screenRect.maxX - windowRect.width - 10 - windowRect.origin.y = screenRect.maxY - windowRect.height - 10 - let styleMask: NSWindow.StyleMask = [.fullSizeContentView, .titled] - - let transparentVisualEffect = NSVisualEffectView() - transparentVisualEffect.blendingMode = .behindWindow - transparentVisualEffect.state = .active - - let panel = NotifierWindow( - contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false - ) - panel.contentView = transparentVisualEffect - panel.isMovableByWindowBackground = true - panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel)) - panel.hasShadow = true - panel.backgroundColor = backgroundColor - panel.title = "" - panel.titlebarAppearsTransparent = true - panel.titleVisibility = .hidden - panel.showsToolbarButton = false - panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true - panel.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isHidden = true - panel.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true - panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true - - messageTextField = NSTextField() - messageTextField.frame = contentRect - messageTextField.isEditable = false - messageTextField.isSelectable = false - messageTextField.isBezeled = false - messageTextField.textColor = foregroundColor - messageTextField.drawsBackground = false - messageTextField.font = .boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)) - panel.contentView?.addSubview(messageTextField) - - super.init(window: panel) - - panel.clickDelegate = self - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func show() { - func setStartLocation() { - if Self.instanceCount == 0 { - return - } - let lastLocation = Self.lastLocation - let screenRect = NSScreen.main?.visibleFrame ?? NSRect.seniorTheBeast - var windowRect = window?.frame ?? NSRect.seniorTheBeast - windowRect.origin.x = lastLocation.x - windowRect.origin.y = lastLocation.y - 10 - windowRect.height - - if windowRect.origin.y < screenRect.minY { - return - } - - window?.setFrame(windowRect, display: true) - } - - func moveIn() { - let afterRect = window?.frame ?? NSRect.seniorTheBeast - Self.lastLocation = afterRect.origin - var beforeRect = afterRect - beforeRect.origin.y += 10 - window?.setFrame(beforeRect, display: true) - window?.orderFront(self) - window?.setFrame(afterRect, display: true, animate: true) - } - - setStartLocation() - moveIn() - Self.increaseInstanceCount() - waitTimer = Timer.scheduledTimer( - timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut), - userInfo: nil, - repeats: false - ) - } - - @objc private func doFadeOut(_: Timer) { - let opacity = window?.alphaValue ?? 0 - if opacity <= 0 { - close() - } else { - window?.alphaValue = opacity - 0.2 - } - } - - @objc private func fadeOut() { - waitTimer?.invalidate() - waitTimer = nil - Self.decreaseInstanceCount() - fadeTimer = Timer.scheduledTimer( - timeInterval: 0.01, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil, - repeats: true - ) - } - - override public func close() { - waitTimer?.invalidate() - waitTimer = nil - fadeTimer?.invalidate() - fadeTimer = nil - super.close() - } - - fileprivate func windowDidBecomeClicked(_: NotifierWindow) { - fadeOut() - } -} diff --git a/Source/Modules/UIModules/PrefUI/suiPrefPaneDevZone.swift b/Source/Modules/UIModules/PrefUI/suiPrefPaneDevZone.swift index 371554ca..454d3716 100644 --- a/Source/Modules/UIModules/PrefUI/suiPrefPaneDevZone.swift +++ b/Source/Modules/UIModules/PrefUI/suiPrefPaneDevZone.swift @@ -19,7 +19,7 @@ struct suiPrefPaneDevZone: View { @State private var selShiftKeyAccommodationBehavior: Int = UserDefaults.standard.integer( forKey: UserDef.kShiftKeyAccommodationBehavior.rawValue) - private let contentMaxHeight: Double = 432 + private let contentMaxHeight: Double = 440 private let contentWidth: Double = { switch PrefMgr.shared.appleLanguages[0] { case "ja": diff --git a/Source/Modules/UIModules/PrefUI/suiPrefPaneDictionary.swift b/Source/Modules/UIModules/PrefUI/suiPrefPaneDictionary.swift index 4e140760..d6864a60 100644 --- a/Source/Modules/UIModules/PrefUI/suiPrefPaneDictionary.swift +++ b/Source/Modules/UIModules/PrefUI/suiPrefPaneDictionary.swift @@ -37,7 +37,7 @@ struct suiPrefPaneDictionary: View { private static let dlgOpenPath = NSOpenPanel() - private let contentMaxHeight: Double = 432 + private let contentMaxHeight: Double = 440 private let contentWidth: Double = { switch PrefMgr.shared.appleLanguages[0] { case "ja": diff --git a/Source/Modules/UIModules/PrefUI/suiPrefPaneExperience.swift b/Source/Modules/UIModules/PrefUI/suiPrefPaneExperience.swift index f1399b73..29c281ea 100644 --- a/Source/Modules/UIModules/PrefUI/suiPrefPaneExperience.swift +++ b/Source/Modules/UIModules/PrefUI/suiPrefPaneExperience.swift @@ -48,7 +48,7 @@ struct suiPrefPaneExperience: View { @State private var selAlwaysShowTooltipTextsHorizontally = UserDefaults.standard.bool( forKey: UserDef.kAlwaysShowTooltipTextsHorizontally.rawValue) - private let contentMaxHeight: Double = 432 + private let contentMaxHeight: Double = 440 private let contentWidth: Double = { switch PrefMgr.shared.appleLanguages[0] { case "ja": diff --git a/Source/Modules/UIModules/PrefUI/suiPrefPaneGeneral.swift b/Source/Modules/UIModules/PrefUI/suiPrefPaneGeneral.swift index 14acbd93..1908f4dd 100644 --- a/Source/Modules/UIModules/PrefUI/suiPrefPaneGeneral.swift +++ b/Source/Modules/UIModules/PrefUI/suiPrefPaneGeneral.swift @@ -39,7 +39,7 @@ struct suiPrefPaneGeneral: View { forKey: UserDef.kCheckUpdateAutomatically.rawValue) @State private var selEnableDebugMode = UserDefaults.standard.bool(forKey: UserDef.kIsDebugModeEnabled.rawValue) - private let contentMaxHeight: Double = 432 + private let contentMaxHeight: Double = 440 private let contentWidth: Double = { switch PrefMgr.shared.appleLanguages[0] { case "ja": @@ -60,7 +60,7 @@ struct suiPrefPaneGeneral: View { Picker( "", selection: $selCandidateUIFontSize.onChange { - PrefMgr.shared.candidateListTextSize = CGFloat(selCandidateUIFontSize) + PrefMgr.shared.candidateListTextSize = Double(selCandidateUIFontSize) } ) { Group { diff --git a/Source/Modules/UIModules/PrefUI/suiPrefPaneKeyboard.swift b/Source/Modules/UIModules/PrefUI/suiPrefPaneKeyboard.swift index be2ef9f9..47b23312 100644 --- a/Source/Modules/UIModules/PrefUI/suiPrefPaneKeyboard.swift +++ b/Source/Modules/UIModules/PrefUI/suiPrefPaneKeyboard.swift @@ -34,7 +34,7 @@ struct suiPrefPaneKeyboard: View { @State private var selUsingHotKeyCurrencyNumerals = UserDefaults.standard.bool( forKey: UserDef.kUsingHotKeyCurrencyNumerals.rawValue) - private let contentMaxHeight: Double = 432 + private let contentMaxHeight: Double = 440 private let contentWidth: Double = { switch PrefMgr.shared.appleLanguages[0] { case "ja": diff --git a/Source/Modules/WindowControllers/ctlClientListMgr.swift b/Source/Modules/WindowControllers/ctlClientListMgr.swift index 787bec77..019e0c71 100644 --- a/Source/Modules/WindowControllers/ctlClientListMgr.swift +++ b/Source/Modules/WindowControllers/ctlClientListMgr.swift @@ -53,7 +53,7 @@ extension ctlClientListMgr { alert.addButton(withTitle: NSLocalizedString("Just Select", comment: "") + "…") alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) - let maxFloat = CGFloat(Float.greatestFiniteMagnitude) + let maxFloat = Double(Float.greatestFiniteMagnitude) let scrollview = NSScrollView(frame: NSRect(x: 0, y: 0, width: 370, height: 380)) let contentSize = scrollview.contentSize scrollview.borderType = .noBorder diff --git a/Source/Modules/WindowControllers/ctlPrefWindow.swift b/Source/Modules/WindowControllers/ctlPrefWindow.swift index ab5d3749..37856bdf 100644 --- a/Source/Modules/WindowControllers/ctlPrefWindow.swift +++ b/Source/Modules/WindowControllers/ctlPrefWindow.swift @@ -12,7 +12,7 @@ import BookmarkManager import IMKUtils import Shared -private let kWindowTitleHeight: CGFloat = 78 +private let kWindowTitleHeight: Double = 78 extension NSToolbarItem.Identifier { fileprivate static let ofGeneral = NSToolbarItem.Identifier(rawValue: "tabGeneral") diff --git a/Source/Modules/ctlInputMethod_HandleEvent.swift b/Source/Modules/ctlInputMethod_HandleEvent.swift deleted file mode 100644 index c4fb131a..00000000 --- a/Source/Modules/ctlInputMethod_HandleEvent.swift +++ /dev/null @@ -1,76 +0,0 @@ -// (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). -// ==================== -// This code is released under the MIT license (SPDX-License-Identifier: MIT) -// ... with NTL restriction stating that: -// 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 defined in MIT License. - -import InputMethodKit -import Shared - -extension ctlInputMethod { - /// 完成 handle() 函式本該完成的內容,但去掉了與 IMK 選字窗有關的判斷語句。 - /// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。 - /// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。 - /// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。 - func commonEventHandler(_ event: NSEvent) -> Bool { - // 無法列印的訊號輸入,一概不作處理。 - // 這個過程不能放在 KeyHandler 內,否則不會起作用。 - if !event.charCode.isPrintable { return false } - - /// 將按鍵行為與當前輸入法狀態結合起來、交給按鍵調度模組來處理。 - /// 再根據返回的 result bool 數值來告知 IMK「這個按鍵事件是被處理了還是被放行了」。 - /// 這裡不用 keyHandler.handleCandidate() 是因為需要針對聯想詞輸入狀態做額外處理。 - let result = keyHandler.handle(input: event, state: state) { newState in - self.handle(state: newState) - } errorCallback: { errorString in - vCLog(errorString) - IMEApp.buzz() - } - return result - } - - /// 完成 handle() 函式本該完成的內容,但專門處理與 IMK 選字窗有關的判斷語句。 - /// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。 - /// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。 - /// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。 - func imkCandidatesEventHandler(event eventToDeal: NSEvent) -> Bool? { - // IMK 選字窗處理,當且僅當啟用了 IMK 選字窗的時候才會生效。 - // 這樣可以讓 interpretKeyEvents() 函式自行判斷: - // - 是就地交給 super.interpretKeyEvents() 處理? - // - 還是藉由 delegate 扔回 ctlInputMethod 給 KeyHandler 處理? - if let imkCandidates = ctlInputMethod.ctlCandidateCurrent as? ctlCandidateIMK, imkCandidates.visible { - let event: NSEvent = ctlCandidateIMK.replaceNumPadKeyCodes(target: eventToDeal) ?? eventToDeal - - // Shift+Enter 是個特殊情形,不提前攔截處理的話、會有垃圾參數傳給 delegate 的 keyHandler 從而崩潰。 - // 所以這裡直接將 Shift Flags 清空。 - if event.isShiftHold, event.isEnter { - guard let newEvent = event.reinitiate(modifierFlags: []) else { - NSSound.beep() - return true - } - imkCandidates.interpretKeyEvents([newEvent]) - return true - } - - // 聯想詞選字。 - if let newChar = ctlCandidateIMK.defaultIMKSelectionKey[event.keyCode], - event.isShiftHold, isAssociatedPhrasesState, - let newEvent = event.reinitiate(modifierFlags: [], characters: newChar) - { - if #available(macOS 10.14, *) { - imkCandidates.handleKeyboardEvent(newEvent) - } else { - imkCandidates.superInterpretKeyEvents([newEvent]) - } - } - - imkCandidates.interpretKeyEvents([event]) - return true - } - return nil - } -} diff --git a/Source/Resources/Base.lproj/Localizable.strings b/Source/Resources/Base.lproj/Localizable.strings index 7c2d6817..f9fbaae2 100644 --- a/Source/Resources/Base.lproj/Localizable.strings +++ b/Source/Resources/Base.lproj/Localizable.strings @@ -38,6 +38,7 @@ "Force KangXi Writing" = "Force KangXi Writing"; "NotificationSwitchON" = "✔ ON"; "NotificationSwitchOFF" = "✘ OFF"; +"NotificationSwitchShift" = "↺ Switched"; "Edit User Phrases…" = "Edit User Phrases…"; "Reload User Phrases" = "Reload User Phrases"; "Unable to create the user phrase file." = "Unable to create the user phrase file."; diff --git a/Source/Resources/IME-Info.plist b/Source/Resources/IME-Info.plist index 99b14976..e4a6b44c 100644 --- a/Source/Resources/IME-Info.plist +++ b/Source/Resources/IME-Info.plist @@ -92,13 +92,13 @@ InputMethodConnectionName org_atelierInmu_inputmethod_vChewing_Connection InputMethodServerControllerClass - ctlInputMethod + SessionCtl InputMethodServerDelegateClass - ctlInputMethod + SessionCtl InputMethodServerPreferencesWindowControllerClass ctlPrefWindow InputMethodSessionController - ctlInputMethod + SessionCtl LSApplicationCategoryType public.app-category.utilities LSHasLocalizedDisplayName diff --git a/Source/Resources/Images/Images.xcassets/AboutBanner.imageset/AboutBanner.png b/Source/Resources/Images/Images.xcassets/AboutBanner.imageset/AboutBanner.png index f1d8d861..0aa34401 100644 Binary files a/Source/Resources/Images/Images.xcassets/AboutBanner.imageset/AboutBanner.png and b/Source/Resources/Images/Images.xcassets/AboutBanner.imageset/AboutBanner.png differ diff --git a/Source/Resources/Images/Images.xcassets/AboutBanner.imageset/AboutBanner@2x.png b/Source/Resources/Images/Images.xcassets/AboutBanner.imageset/AboutBanner@2x.png index ec334668..123a9874 100644 Binary files a/Source/Resources/Images/Images.xcassets/AboutBanner.imageset/AboutBanner@2x.png and b/Source/Resources/Images/Images.xcassets/AboutBanner.imageset/AboutBanner@2x.png differ diff --git a/Source/Resources/Images/Images.xcassets/AboutBanner.imageset/AboutBanner@3x.png b/Source/Resources/Images/Images.xcassets/AboutBanner.imageset/AboutBanner@3x.png index 5b23944f..ee381cc3 100644 Binary files a/Source/Resources/Images/Images.xcassets/AboutBanner.imageset/AboutBanner@3x.png and b/Source/Resources/Images/Images.xcassets/AboutBanner.imageset/AboutBanner@3x.png differ diff --git a/Source/Resources/Images/Images.xcassets/AlertIcon.imageset/128.png b/Source/Resources/Images/Images.xcassets/AlertIcon.imageset/128.png index 5728defa..8488495a 100644 Binary files a/Source/Resources/Images/Images.xcassets/AlertIcon.imageset/128.png and b/Source/Resources/Images/Images.xcassets/AlertIcon.imageset/128.png differ diff --git a/Source/Resources/Images/Images.xcassets/AlertIcon.imageset/192.png b/Source/Resources/Images/Images.xcassets/AlertIcon.imageset/192.png index df988e34..c05e4fcd 100644 Binary files a/Source/Resources/Images/Images.xcassets/AlertIcon.imageset/192.png and b/Source/Resources/Images/Images.xcassets/AlertIcon.imageset/192.png differ diff --git a/Source/Resources/Images/Images.xcassets/AlertIcon.imageset/64.png b/Source/Resources/Images/Images.xcassets/AlertIcon.imageset/64.png index 2a24661f..9a3639e8 100644 Binary files a/Source/Resources/Images/Images.xcassets/AlertIcon.imageset/64.png and b/Source/Resources/Images/Images.xcassets/AlertIcon.imageset/64.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/Contents.json b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/Contents.json new file mode 100644 index 00000000..e58fc60f --- /dev/null +++ b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/Contents.json @@ -0,0 +1,74 @@ +{ + "images" : [ + { + "filename" : "icon_1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + }, + { + "filename" : "icon_16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "icon_32.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "icon_32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "icon_64.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "icon_128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "icon_256.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "icon_256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "icon_512.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "icon_512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "icon_1024.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_1024.png b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_1024.png new file mode 100644 index 00000000..5b14c5d7 Binary files /dev/null and b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_1024.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_128.png b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_128.png new file mode 100644 index 00000000..2ec0fadc Binary files /dev/null and b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_128.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_16.png b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_16.png new file mode 100644 index 00000000..2ce29056 Binary files /dev/null and b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_16.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_256.png b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_256.png new file mode 100644 index 00000000..bb1cbbb7 Binary files /dev/null and b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_256.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_32.png b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_32.png new file mode 100644 index 00000000..c135fa7b Binary files /dev/null and b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_32.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_512.png b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_512.png new file mode 100644 index 00000000..a176f130 Binary files /dev/null and b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_512.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_64.png b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_64.png new file mode 100644 index 00000000..1790324d Binary files /dev/null and b/Source/Resources/Images/Images.xcassets/AppIcon-PhraseEditor.appiconset/icon_64.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/1024X1024.png b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/1024X1024.png index 4bfc4300..958e7fba 100644 Binary files a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/1024X1024.png and b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/1024X1024.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/128X128.png b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/128X128.png index 5728defa..b8884a53 100644 Binary files a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/128X128.png and b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/128X128.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/256X256.png b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/256X256.png index 2f919dd6..20492905 100644 Binary files a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/256X256.png and b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/256X256.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/32X32.png b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/32X32.png index 7a8707a4..3f982ddc 100644 Binary files a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/32X32.png and b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/32X32.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/512X512.png b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/512X512.png index 79236c1d..ef916581 100644 Binary files a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/512X512.png and b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/512X512.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/64X64.png b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/64X64.png index 2a24661f..3f46920a 100644 Binary files a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/64X64.png and b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/64X64.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/AppIconSmallest.png b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/AppIconSmallest.png index 41d63f4f..12cc4595 100644 Binary files a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/AppIconSmallest.png and b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/AppIconSmallest.png differ diff --git a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/AppIconSmallest@2x.png b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/AppIconSmallest@2x.png index 7a8707a4..0f9fb7b4 100644 Binary files a/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/AppIconSmallest@2x.png and b/Source/Resources/Images/Images.xcassets/AppIcon.appiconset/AppIconSmallest@2x.png differ diff --git a/Source/Resources/Images/RAW/Icon-vChewing.pxd b/Source/Resources/Images/RAW/Icon-vChewing.pxd new file mode 100644 index 00000000..664aa0cf Binary files /dev/null and b/Source/Resources/Images/RAW/Icon-vChewing.pxd differ diff --git a/Source/Resources/Images/RAW/Icon-vChewingPhraseEditor.pxd b/Source/Resources/Images/RAW/Icon-vChewingPhraseEditor.pxd new file mode 100644 index 00000000..444755c3 Binary files /dev/null and b/Source/Resources/Images/RAW/Icon-vChewingPhraseEditor.pxd differ diff --git a/Source/Resources/Images/RAW/March.jpg b/Source/Resources/Images/RAW/March.jpg deleted file mode 100644 index ad531bac..00000000 Binary files a/Source/Resources/Images/RAW/March.jpg and /dev/null differ diff --git a/Source/Resources/Images/RAW/vChewing-MenuIcon.svg b/Source/Resources/Images/RAW/NewIMEMenuLogo-CHS.svg similarity index 55% rename from Source/Resources/Images/RAW/vChewing-MenuIcon.svg rename to Source/Resources/Images/RAW/NewIMEMenuLogo-CHS.svg index af40f440..f73172eb 100644 --- a/Source/Resources/Images/RAW/vChewing-MenuIcon.svg +++ b/Source/Resources/Images/RAW/NewIMEMenuLogo-CHS.svg @@ -1,31 +1,21 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - diff --git a/Source/Resources/Images/RAW/NewIMEMenuLogo-CHT.svg b/Source/Resources/Images/RAW/NewIMEMenuLogo-CHT.svg new file mode 100644 index 00000000..956e32bb --- /dev/null +++ b/Source/Resources/Images/RAW/NewIMEMenuLogo-CHT.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Resources/Images/RAW/NewInstallerBg.pxd b/Source/Resources/Images/RAW/NewInstallerBg.pxd index f7e65307..53d0f7a3 100644 Binary files a/Source/Resources/Images/RAW/NewInstallerBg.pxd and b/Source/Resources/Images/RAW/NewInstallerBg.pxd differ diff --git a/Source/Resources/Images/RAW/Study.heic b/Source/Resources/Images/RAW/Study.heic deleted file mode 100644 index 0fae7c42..00000000 Binary files a/Source/Resources/Images/RAW/Study.heic and /dev/null differ diff --git a/Source/Resources/Images/RAW/WebIcon-Alt.png b/Source/Resources/Images/RAW/WebIcon-Alt.png new file mode 100644 index 00000000..7ebbb597 Binary files /dev/null and b/Source/Resources/Images/RAW/WebIcon-Alt.png differ diff --git a/Source/Resources/Images/RAW/WebIcon-Alt.svg b/Source/Resources/Images/RAW/WebIcon-Alt.svg new file mode 100644 index 00000000..6e51d028 --- /dev/null +++ b/Source/Resources/Images/RAW/WebIcon-Alt.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Resources/Images/RAW/WebIcon.png b/Source/Resources/Images/RAW/WebIcon.png new file mode 100644 index 00000000..f059edb3 Binary files /dev/null and b/Source/Resources/Images/RAW/WebIcon.png differ diff --git a/Source/Resources/Images/RAW/WebIcon.svg b/Source/Resources/Images/RAW/WebIcon.svg new file mode 100644 index 00000000..de9ca868 --- /dev/null +++ b/Source/Resources/Images/RAW/WebIcon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Resources/Images/RAW/vChewing-MenuIcon-2x.svg b/Source/Resources/Images/RAW/vChewing-MenuIcon-2x.svg deleted file mode 100644 index fd0462c5..00000000 --- a/Source/Resources/Images/RAW/vChewing-MenuIcon-2x.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Source/Resources/Images/RAW/vChewing.svg b/Source/Resources/Images/RAW/vChewing.svg deleted file mode 100644 index d7526082..00000000 --- a/Source/Resources/Images/RAW/vChewing.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Source/Resources/MenuIcons/MenuIcon-SCVIM.png b/Source/Resources/MenuIcons/MenuIcon-SCVIM.png index b5d0da9c..562b87c3 100644 Binary files a/Source/Resources/MenuIcons/MenuIcon-SCVIM.png and b/Source/Resources/MenuIcons/MenuIcon-SCVIM.png differ diff --git a/Source/Resources/MenuIcons/MenuIcon-SCVIM@2x.png b/Source/Resources/MenuIcons/MenuIcon-SCVIM@2x.png index cf1aef8a..ddbadeb2 100644 Binary files a/Source/Resources/MenuIcons/MenuIcon-SCVIM@2x.png and b/Source/Resources/MenuIcons/MenuIcon-SCVIM@2x.png differ diff --git a/Source/Resources/MenuIcons/MenuIcon-TCVIM.png b/Source/Resources/MenuIcons/MenuIcon-TCVIM.png index 41d63f4f..12cc4595 100644 Binary files a/Source/Resources/MenuIcons/MenuIcon-TCVIM.png and b/Source/Resources/MenuIcons/MenuIcon-TCVIM.png differ diff --git a/Source/Resources/MenuIcons/MenuIcon-TCVIM@2x.png b/Source/Resources/MenuIcons/MenuIcon-TCVIM@2x.png index 7a8707a4..0f9fb7b4 100644 Binary files a/Source/Resources/MenuIcons/MenuIcon-TCVIM@2x.png and b/Source/Resources/MenuIcons/MenuIcon-TCVIM@2x.png differ diff --git a/Source/Resources/en.lproj/Localizable.strings b/Source/Resources/en.lproj/Localizable.strings index 7c2d6817..f9fbaae2 100644 --- a/Source/Resources/en.lproj/Localizable.strings +++ b/Source/Resources/en.lproj/Localizable.strings @@ -38,6 +38,7 @@ "Force KangXi Writing" = "Force KangXi Writing"; "NotificationSwitchON" = "✔ ON"; "NotificationSwitchOFF" = "✘ OFF"; +"NotificationSwitchShift" = "↺ Switched"; "Edit User Phrases…" = "Edit User Phrases…"; "Reload User Phrases" = "Reload User Phrases"; "Unable to create the user phrase file." = "Unable to create the user phrase file."; diff --git a/Source/Resources/ja.lproj/Localizable.strings b/Source/Resources/ja.lproj/Localizable.strings index aa044c38..3502f69a 100644 --- a/Source/Resources/ja.lproj/Localizable.strings +++ b/Source/Resources/ja.lproj/Localizable.strings @@ -38,6 +38,7 @@ "Force KangXi Writing" = "康熙文字変換モード"; "NotificationSwitchON" = "✔ 機能起動"; "NotificationSwitchOFF" = "✘ 機能停止"; +"NotificationSwitchShift" = "↺ 切替完了"; "Edit User Phrases…" = "ユーザー辞書を編集…"; "Reload User Phrases" = "ユーザー辞書を再び読込む"; "Unable to create the user phrase file." = "ユーザー辞書ファイルの作成は失敗しました。"; diff --git a/Source/Resources/zh-Hans.lproj/Localizable.strings b/Source/Resources/zh-Hans.lproj/Localizable.strings index 740f0751..7b44bb52 100644 --- a/Source/Resources/zh-Hans.lproj/Localizable.strings +++ b/Source/Resources/zh-Hans.lproj/Localizable.strings @@ -38,6 +38,7 @@ "Force KangXi Writing" = "康熙正体字模式"; "NotificationSwitchON" = "✔ 已启用"; "NotificationSwitchOFF" = "✘ 已停用"; +"NotificationSwitchShift" = "↺ 切换完毕"; "Edit User Phrases…" = "编辑自订语汇…"; "Reload User Phrases" = "重载自订语汇"; "Unable to create the user phrase file." = "无法创建自订语汇档案。"; diff --git a/Source/Resources/zh-Hant.lproj/Localizable.strings b/Source/Resources/zh-Hant.lproj/Localizable.strings index 0507ca81..5e9fc178 100644 --- a/Source/Resources/zh-Hant.lproj/Localizable.strings +++ b/Source/Resources/zh-Hant.lproj/Localizable.strings @@ -38,6 +38,7 @@ "Force KangXi Writing" = "康熙正體字模式"; "NotificationSwitchON" = "✔ 已啟用"; "NotificationSwitchOFF" = "✘ 已停用"; +"NotificationSwitchShift" = "↺ 切換完畢"; "Edit User Phrases…" = "編輯自訂語彙…"; "Reload User Phrases" = "重載自訂語彙"; "Unable to create the user phrase file." = "無法創建自訂語彙檔案。"; diff --git a/Update-Info.plist b/Update-Info.plist index da32acad..e268cbdf 100644 --- a/Update-Info.plist +++ b/Update-Info.plist @@ -3,9 +3,9 @@ CFBundleShortVersionString - 2.7.0 + 2.7.5 CFBundleVersion - 2702 + 2750 UpdateInfoEndpoint https://gitee.com/vchewing/vChewing-macOS/raw/main/Update-Info.plist UpdateInfoSite diff --git a/vChewing.pkgproj b/vChewing.pkgproj index 0fb8d171..ac5a3869 100644 --- a/vChewing.pkgproj +++ b/vChewing.pkgproj @@ -726,7 +726,7 @@ USE_HFS+_COMPRESSION VERSION - 2.7.0 + 2.7.5 TYPE 0 diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index d3b4fb13..69f3686f 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -7,23 +7,19 @@ objects = { /* Begin PBXBuildFile section */ + 5B00FA0C28DEC17200F6D436 /* SessionCtl_IMKCandidatesData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B00FA0B28DEC17200F6D436 /* SessionCtl_IMKCandidatesData.swift */; }; 5B09307628B6FC3B0021F8C5 /* shortcuts.html in Resources */ = {isa = PBXBuildFile; fileRef = 5B09307828B6FC3B0021F8C5 /* shortcuts.html */; }; 5B0AF8B527B2C8290096FE54 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0AF8B427B2C8290096FE54 /* StringExtension.swift */; }; 5B0EF55D28CDBF7100F8F7CE /* frmClientListMgr.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B0EF55C28CDBF7100F8F7CE /* frmClientListMgr.xib */; }; 5B0EF55F28CDBF8E00F8F7CE /* ctlClientListMgr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0EF55E28CDBF8E00F8F7CE /* ctlClientListMgr.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 */; }; + 5B21176C287539BB000443A9 /* SessionCtl_HandleStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21176B287539BB000443A9 /* SessionCtl_HandleStates.swift */; }; + 5B21176E28753B35000443A9 /* SessionCtl_HandleDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21176D28753B35000443A9 /* SessionCtl_HandleDisplay.swift */; }; + 5B21177028753B9D000443A9 /* SessionCtl_Delegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21176F28753B9D000443A9 /* SessionCtl_Delegates.swift */; }; 5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */; }; 5B40113928D7050D00A9D4CB /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113828D7050D00A9D4CB /* Shared */; }; 5B40113C28D71C0100A9D4CB /* Uninstaller in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113B28D71C0100A9D4CB /* Uninstaller */; }; - 5B5F8AEE28C8826D007C11F1 /* ctlTooltip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F8AED28C8826D007C11F1 /* ctlTooltip.swift */; }; 5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */; }; - 5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34027AE7CD900A19448 /* ctlCandidate.swift */; }; - 5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34527AE7CD900A19448 /* NotifierController.swift */; }; - 5B630A3C28CC97020010D076 /* ctlPopupCompositionBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B630A3B28CC97020010D076 /* ctlPopupCompositionBuffer.swift */; }; - 5B6C141228A9D4B30098ADF8 /* ctlInputMethod_HandleEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6C141128A9D4B30098ADF8 /* ctlInputMethod_HandleEvent.swift */; }; + 5B6C141228A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6C141128A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift */; }; 5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; }; 5B782EC4280C243C007276DE /* KeyHandler_HandleCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */; }; 5B78EE0D28A562B4009456C1 /* suiPrefPaneDevZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B78EE0C28A562B4009456C1 /* suiPrefPaneDevZone.swift */; }; @@ -36,6 +32,8 @@ 5B963CA328D5C23600DCEE88 /* SwiftExtension in Frameworks */ = {isa = PBXBuildFile; productRef = 5B963CA228D5C23600DCEE88 /* SwiftExtension */; }; 5B963CA828D5DB1400DCEE88 /* PrefMgr_Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B963CA728D5DB1400DCEE88 /* PrefMgr_Core.swift */; }; 5B98114828D6198700CBC605 /* PinyinPhonaConverter in Frameworks */ = {isa = PBXBuildFile; productRef = 5B98114728D6198700CBC605 /* PinyinPhonaConverter */; }; + 5BA8C30328DF0360004C5CC4 /* CandidateWindow in Frameworks */ = {isa = PBXBuildFile; productRef = 5BA8C30228DF0360004C5CC4 /* CandidateWindow */; }; + 5BA8C30528DF0364004C5CC4 /* Voltaire in Frameworks */ = {isa = PBXBuildFile; productRef = 5BA8C30428DF0364004C5CC4 /* Voltaire */; }; 5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */; }; 5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */; }; 5BA9FD1127FEDB6B002DE248 /* ctlPrefUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0C27FEDB6B002DE248 /* ctlPrefUI.swift */; }; @@ -43,7 +41,7 @@ 5BA9FD1327FEDB6B002DE248 /* suiPrefPaneDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0E27FEDB6B002DE248 /* suiPrefPaneDictionary.swift */; }; 5BAD0CD527D701F6003D127F /* vChewingKeyLayout.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */; }; 5BAEFAD028012565001F42C9 /* LMMgr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAEFACF28012565001F42C9 /* LMMgr.swift */; }; - 5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */; }; + 5BB802DA27FABA8300CF1C19 /* SessionCtl_Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB802D927FABA8300CF1C19 /* SessionCtl_Menu.swift */; }; 5BBBB75F27AED54C0023B93A /* Beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75D27AED54C0023B93A /* Beep.m4a */; }; 5BBBB76027AED54C0023B93A /* Fart.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75E27AED54C0023B93A /* Fart.m4a */; }; 5BBBB76D27AED5DB0023B93A /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB76927AED5DB0023B93A /* frmAboutWindow.xib */; }; @@ -58,6 +56,9 @@ 5BC447A02865686500EDC323 /* data-symbols.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71E283B4AEA0078EB25 /* data-symbols.plist */; }; 5BC447A12865686500EDC323 /* data-zhuyinwen.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71F283B4AEA0078EB25 /* data-zhuyinwen.plist */; }; 5BC447A628656A1900EDC323 /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC447A228656A1900EDC323 /* Placeholder.swift */; }; + 5BC5E01E28DDE4770094E427 /* NotifierUI in Frameworks */ = {isa = PBXBuildFile; productRef = 5BC5E01D28DDE4770094E427 /* NotifierUI */; }; + 5BC5E02128DDEFE00094E427 /* TooltipUI in Frameworks */ = {isa = PBXBuildFile; productRef = 5BC5E02028DDEFE00094E427 /* TooltipUI */; }; + 5BC5E02428DE07860094E427 /* PopupCompositionBuffer in Frameworks */ = {isa = PBXBuildFile; productRef = 5BC5E02328DE07860094E427 /* PopupCompositionBuffer */; }; 5BCCAFF828DB19A300AB1B27 /* PrefMgr_Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BCCAFF728DB19A300AB1B27 /* PrefMgr_Extension.swift */; }; 5BD0113D2818543900609769 /* KeyHandler_Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0113C2818543900609769 /* KeyHandler_Core.swift */; }; 5BD05BCA27B2A43D004C4F1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; }; @@ -97,7 +98,7 @@ 5BFC63CC28D49BBC004A77B7 /* UpdateSputnik in Frameworks */ = {isa = PBXBuildFile; productRef = 5BFC63CB28D49BBC004A77B7 /* UpdateSputnik */; }; 5BFC63CF28D4ACA3004A77B7 /* LangModelAssembly in Frameworks */ = {isa = PBXBuildFile; productRef = 5BFC63CE28D4ACA3004A77B7 /* LangModelAssembly */; }; 5BFC63D128D4B9F7004A77B7 /* IMKUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 5BFC63D028D4B9F7004A77B7 /* IMKUtils */; }; - 5BFDF011289635C100417BBC /* ctlCandidateIMK.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFDF010289635C100417BBC /* ctlCandidateIMK.swift */; }; + 5BFDF011289635C100417BBC /* IMKCandidatesImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFDF010289635C100417BBC /* IMKCandidatesImpl.swift */; }; 6A187E2616004C5900466B2E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6A187E2816004C5900466B2E /* MainMenu.xib */; }; 6A225A1F23679F2600F685C6 /* NotarizedArchives in Resources */ = {isa = PBXBuildFile; fileRef = 6A225A1E23679F2600F685C6 /* NotarizedArchives */; }; 6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; }; @@ -109,7 +110,7 @@ D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427F76B278CA1BA004A2160 /* AppDelegate.swift */; }; D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; }; D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* ctlPrefWindow.swift */; }; - D4A13D5A27A59F0B003BE359 /* ctlInputMethod_Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A13D5927A59D5C003BE359 /* ctlInputMethod_Core.swift */; }; + D4A13D5A27A59F0B003BE359 /* SessionCtl_Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A13D5927A59D5C003BE359 /* SessionCtl_Core.swift */; }; D4E33D8A27A838CF006DB1CF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D4E33D8827A838CF006DB1CF /* Localizable.strings */; }; D4E33D8F27A838F0006DB1CF /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D4E33D8D27A838F0006DB1CF /* InfoPlist.strings */; }; D4F0BBDF279AF1AF0071253C /* ArchiveUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4F0BBDE279AF1AF0071253C /* ArchiveUtil.swift */; }; @@ -154,6 +155,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 5B00FA0B28DEC17200F6D436 /* SessionCtl_IMKCandidatesData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCtl_IMKCandidatesData.swift; sourceTree = ""; }; 5B04305327B529D800CB65BC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B04305427B529D800CB65BC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B04305527B529D800CB65BC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.strings"; sourceTree = ""; }; @@ -193,23 +195,18 @@ 5B18BA7427C7BD8C0056EB19 /* LICENSE-CHT.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "LICENSE-CHT.txt"; sourceTree = ""; }; 5B20430B28BEFC0C00BFC6FD /* vChewing.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = vChewing.entitlements; sourceTree = ""; }; 5B20430C28BEFC1200BFC6FD /* vChewingPhraseEditor.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = vChewingPhraseEditor.entitlements; sourceTree = ""; }; - 5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_HandleStates.swift; sourceTree = ""; }; - 5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_HandleDisplay.swift; sourceTree = ""; }; - 5B21176F28753B9D000443A9 /* ctlInputMethod_Delegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Delegates.swift; sourceTree = ""; }; - 5B242402284B0D6500520FE4 /* ctlCandidateUniversal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlCandidateUniversal.swift; sourceTree = ""; }; + 5B21176B287539BB000443A9 /* SessionCtl_HandleStates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCtl_HandleStates.swift; sourceTree = ""; }; + 5B21176D28753B35000443A9 /* SessionCtl_HandleDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCtl_HandleDisplay.swift; sourceTree = ""; }; + 5B21176F28753B9D000443A9 /* SessionCtl_Delegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCtl_Delegates.swift; sourceTree = ""; }; 5B2DB17127AF8771006D874E /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = Data/Makefile; sourceTree = ""; }; 5B2F2BB3286216A500B8557B /* vChewingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = vChewingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = vChewingKeyLayout.bundle; sourceTree = ""; }; 5B312684287800DE001AA720 /* FAQ.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = FAQ.md; sourceTree = ""; }; 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = KeyHandler_States.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B40113A28D71B8700A9D4CB /* vChewing_Uninstaller */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_Uninstaller; path = Packages/vChewing_Uninstaller; sourceTree = ""; }; - 5B5F8AED28C8826D007C11F1 /* ctlTooltip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlTooltip.swift; sourceTree = ""; }; 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlAboutWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; - 5B62A34027AE7CD900A19448 /* ctlCandidate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlCandidate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; - 5B62A34527AE7CD900A19448 /* NotifierController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = NotifierController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; - 5B630A3B28CC97020010D076 /* ctlPopupCompositionBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlPopupCompositionBuffer.swift; sourceTree = ""; }; 5B65B919284D0185007C558B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - 5B6C141128A9D4B30098ADF8 /* ctlInputMethod_HandleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_HandleEvent.swift; sourceTree = ""; }; + 5B6C141128A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCtl_HandleEvent.swift; sourceTree = ""; }; 5B73FB5427B2BD6900E9BF49 /* PhraseEditor-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "PhraseEditor-Info.plist"; path = "UserPhraseEditor/PhraseEditor-Info.plist"; sourceTree = SOURCE_ROOT; }; 5B73FB5F27B2BE1300E9BF49 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = KeyHandler_HandleCandidate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; @@ -225,13 +222,15 @@ 5B963CA128D5C22D00DCEE88 /* vChewing_SwiftExtension */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_SwiftExtension; path = Packages/vChewing_SwiftExtension; sourceTree = ""; }; 5B963CA728D5DB1400DCEE88 /* PrefMgr_Core.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefMgr_Core.swift; sourceTree = ""; }; 5B98114628D6198000CBC605 /* vChewing_PinyinPhonaConverter */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_PinyinPhonaConverter; path = Packages/vChewing_PinyinPhonaConverter; sourceTree = ""; }; + 5BA8C30028DEFE4F004C5CC4 /* OpenVanilla_Voltaire */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = OpenVanilla_Voltaire; path = Packages/OpenVanilla_Voltaire; sourceTree = ""; }; + 5BA8C30128DEFE4F004C5CC4 /* vChewing_CandidateWindow */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_CandidateWindow; path = Packages/vChewing_CandidateWindow; sourceTree = ""; }; 5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = suiPrefPaneGeneral.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = suiPrefPaneKeyboard.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BA9FD0C27FEDB6B002DE248 /* ctlPrefUI.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlPrefUI.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BA9FD0D27FEDB6B002DE248 /* suiPrefPaneExperience.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = suiPrefPaneExperience.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BA9FD0E27FEDB6B002DE248 /* suiPrefPaneDictionary.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = suiPrefPaneDictionary.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BAEFACF28012565001F42C9 /* LMMgr.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = LMMgr.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; - 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlInputMethod_Menu.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BB802D927FABA8300CF1C19 /* SessionCtl_Menu.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = SessionCtl_Menu.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = ""; }; 5BBBB75E27AED54C0023B93A /* Fart.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.m4a; sourceTree = ""; }; 5BBBB76A27AED5DB0023B93A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmAboutWindow.xib; sourceTree = ""; }; @@ -246,6 +245,9 @@ 5BC2652127E04B7B00700291 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; lineEnding = 0; path = uninstall.sh; sourceTree = ""; }; 5BC447A228656A1900EDC323 /* Placeholder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Placeholder.swift; sourceTree = ""; }; 5BC447AB2865BEF500EDC323 /* AUTHORS */ = {isa = PBXFileReference; lastKnownFileType = text; path = AUTHORS; sourceTree = ""; }; + 5BC5E01C28DDE4270094E427 /* vChewing_NotifierUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_NotifierUI; path = Packages/vChewing_NotifierUI; sourceTree = ""; }; + 5BC5E01F28DDEFD80094E427 /* vChewing_TooltipUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_TooltipUI; path = Packages/vChewing_TooltipUI; sourceTree = ""; }; + 5BC5E02228DE07250094E427 /* vChewing_PopupCompositionBuffer */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_PopupCompositionBuffer; path = Packages/vChewing_PopupCompositionBuffer; sourceTree = ""; }; 5BCCAFF728DB19A300AB1B27 /* PrefMgr_Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefMgr_Extension.swift; sourceTree = ""; }; 5BD0113C2818543900609769 /* KeyHandler_Core.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = KeyHandler_Core.swift; sourceTree = ""; usesTabs = 0; }; 5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewingPhraseEditor.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -296,7 +298,7 @@ 5BFC63C728D49511004A77B7 /* vChewing_IMKUtils */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_IMKUtils; path = Packages/vChewing_IMKUtils; sourceTree = ""; }; 5BFC63CA28D49B2B004A77B7 /* vChewing_UpdateSputnik */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_UpdateSputnik; path = Packages/vChewing_UpdateSputnik; sourceTree = ""; }; 5BFC63CD28D4AC98004A77B7 /* vChewing_LangModelAssembly */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_LangModelAssembly; path = Packages/vChewing_LangModelAssembly; sourceTree = ""; }; - 5BFDF010289635C100417BBC /* ctlCandidateIMK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlCandidateIMK.swift; sourceTree = ""; }; + 5BFDF010289635C100417BBC /* IMKCandidatesImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IMKCandidatesImpl.swift; sourceTree = ""; }; 5BFDF48C27B51867009523B6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Main.strings"; sourceTree = ""; }; 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewing.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6A0D4EF515FC0DA600ABF4B3 /* IME-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "IME-Info.plist"; sourceTree = ""; }; @@ -311,7 +313,7 @@ D427F76B278CA1BA004A2160 /* AppDelegate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = main.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; D47F7DCD278BFB57002F9DD7 /* ctlPrefWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlPrefWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; - D4A13D5927A59D5C003BE359 /* ctlInputMethod_Core.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlInputMethod_Core.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + D4A13D5927A59D5C003BE359 /* SessionCtl_Core.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = SessionCtl_Core.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; D4E33D8927A838CF006DB1CF /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; D4E33D8E27A838F0006DB1CF /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; D4F0BBDE279AF1AF0071253C /* ArchiveUtil.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ArchiveUtil.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; @@ -337,6 +339,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5BC5E02428DE07860094E427 /* PopupCompositionBuffer in Frameworks */, 5BDB7A4528D4824A001AC277 /* ShiftKeyUpChecker in Frameworks */, 5BFC63CC28D49BBC004A77B7 /* UpdateSputnik in Frameworks */, 5BDB7A4128D4824A001AC277 /* Megrez in Frameworks */, @@ -348,11 +351,15 @@ 5B963C9D28D5BFB800DCEE88 /* CocoaExtension in Frameworks */, 5B98114828D6198700CBC605 /* PinyinPhonaConverter in Frameworks */, 5BFC63CF28D4ACA3004A77B7 /* LangModelAssembly in Frameworks */, + 5BC5E02128DDEFE00094E427 /* TooltipUI in Frameworks */, 5BDB7A3928D4824A001AC277 /* BookmarkManager in Frameworks */, 5B963CA328D5C23600DCEE88 /* SwiftExtension in Frameworks */, 5B40113928D7050D00A9D4CB /* Shared in Frameworks */, 5BFC63C628D48806004A77B7 /* NSAttributedTextView in Frameworks */, 5BDB7A3B28D4824A001AC277 /* FolderMonitor in Frameworks */, + 5BA8C30328DF0360004C5CC4 /* CandidateWindow in Frameworks */, + 5BC5E01E28DDE4770094E427 /* NotifierUI in Frameworks */, + 5BA8C30528DF0364004C5CC4 /* Voltaire in Frameworks */, 5B40113C28D71C0100A9D4CB /* Uninstaller in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -434,10 +441,7 @@ isa = PBXGroup; children = ( 5B62A33E27AE7CD900A19448 /* CandidateUI */, - 5B62A34427AE7CD900A19448 /* NotifierUI */, - 5B630A3A28CC96D80010D076 /* PopupCompositionBufferUI */, 5BA9FD0927FED9F3002DE248 /* PrefUI */, - 5B62A34227AE7CD900A19448 /* TooltipUI */, ); path = UIModules; sourceTree = ""; @@ -466,29 +470,11 @@ 5B62A33E27AE7CD900A19448 /* CandidateUI */ = { isa = PBXGroup; children = ( - 5B62A34027AE7CD900A19448 /* ctlCandidate.swift */, - 5B242402284B0D6500520FE4 /* ctlCandidateUniversal.swift */, - 5BFDF010289635C100417BBC /* ctlCandidateIMK.swift */, + 5BFDF010289635C100417BBC /* IMKCandidatesImpl.swift */, ); path = CandidateUI; sourceTree = ""; }; - 5B62A34227AE7CD900A19448 /* TooltipUI */ = { - isa = PBXGroup; - children = ( - 5B5F8AED28C8826D007C11F1 /* ctlTooltip.swift */, - ); - path = TooltipUI; - sourceTree = ""; - }; - 5B62A34427AE7CD900A19448 /* NotifierUI */ = { - isa = PBXGroup; - children = ( - 5B62A34527AE7CD900A19448 /* NotifierController.swift */, - ); - path = NotifierUI; - sourceTree = ""; - }; 5B62A35027AE7F6600A19448 /* Data */ = { isa = PBXGroup; children = ( @@ -502,14 +488,6 @@ name = Data; sourceTree = ""; }; - 5B630A3A28CC96D80010D076 /* PopupCompositionBufferUI */ = { - isa = PBXGroup; - children = ( - 5B630A3B28CC97020010D076 /* ctlPopupCompositionBuffer.swift */, - ); - path = PopupCompositionBufferUI; - sourceTree = ""; - }; 5BA9FD0927FED9F3002DE248 /* PrefUI */ = { isa = PBXGroup; children = ( @@ -587,18 +565,23 @@ 5BDB7A3128D47587001AC277 /* DanielGalasko_FolderMonitor */, 5BFC63C428D487AE004A77B7 /* Fuziki_NSAttributedTextView */, 5BDB7A3028D47587001AC277 /* Jad_BookmarkManager */, + 5BA8C30028DEFE4F004C5CC4 /* OpenVanilla_Voltaire */, 5BDB7A3428D47587001AC277 /* Qwertyyb_ShiftKeyUpChecker */, 5BDB7A3528D47587001AC277 /* RMJay_LineReader */, 5BDB7A2F28D47587001AC277 /* Sindresorhus_Preferences */, + 5BA8C30128DEFE4F004C5CC4 /* vChewing_CandidateWindow */, 5B963C9B28D5BE4100DCEE88 /* vChewing_CocoaExtension */, 5BDB7A3228D47587001AC277 /* vChewing_Hotenka */, 5BFC63C728D49511004A77B7 /* vChewing_IMKUtils */, 5BFC63CD28D4AC98004A77B7 /* vChewing_LangModelAssembly */, 5BDB7A3328D47587001AC277 /* vChewing_Megrez */, + 5BC5E01C28DDE4270094E427 /* vChewing_NotifierUI */, 5B98114628D6198000CBC605 /* vChewing_PinyinPhonaConverter */, + 5BC5E02228DE07250094E427 /* vChewing_PopupCompositionBuffer */, 5B963C9E28D5C14600DCEE88 /* vChewing_Shared */, 5B963CA128D5C22D00DCEE88 /* vChewing_SwiftExtension */, 5BDB7A3628D47587001AC277 /* vChewing_Tekkon */, + 5BC5E01F28DDEFD80094E427 /* vChewing_TooltipUI */, 5B40113A28D71B8700A9D4CB /* vChewing_Uninstaller */, 5BFC63CA28D49B2B004A77B7 /* vChewing_UpdateSputnik */, ); @@ -679,12 +662,6 @@ 5B62A33A27AE7C7500A19448 /* WindowControllers */, D427F76B278CA1BA004A2160 /* AppDelegate.swift */, 5B8457A02871ADBE00C93B01 /* ChineseConverterBridge.swift */, - D4A13D5927A59D5C003BE359 /* ctlInputMethod_Core.swift */, - 5B21176F28753B9D000443A9 /* ctlInputMethod_Delegates.swift */, - 5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */, - 5B6C141128A9D4B30098ADF8 /* ctlInputMethod_HandleEvent.swift */, - 5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */, - 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */, 5BF56F9728C39A2700DD6839 /* IMEState.swift */, 5BF56F9928C39D1800DD6839 /* IMEStateData.swift */, 5BD0113C2818543900609769 /* KeyHandler_Core.swift */, @@ -696,6 +673,13 @@ D47B92BF27972AC800458394 /* main.swift */, 5B963CA728D5DB1400DCEE88 /* PrefMgr_Core.swift */, 5BCCAFF728DB19A300AB1B27 /* PrefMgr_Extension.swift */, + D4A13D5927A59D5C003BE359 /* SessionCtl_Core.swift */, + 5B21176F28753B9D000443A9 /* SessionCtl_Delegates.swift */, + 5B21176D28753B35000443A9 /* SessionCtl_HandleDisplay.swift */, + 5B6C141128A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift */, + 5B21176B287539BB000443A9 /* SessionCtl_HandleStates.swift */, + 5B00FA0B28DEC17200F6D436 /* SessionCtl_IMKCandidatesData.swift */, + 5BB802D927FABA8300CF1C19 /* SessionCtl_Menu.swift */, ); path = Modules; sourceTree = ""; @@ -795,6 +779,11 @@ 5B98114728D6198700CBC605 /* PinyinPhonaConverter */, 5B40113828D7050D00A9D4CB /* Shared */, 5B40113B28D71C0100A9D4CB /* Uninstaller */, + 5BC5E01D28DDE4770094E427 /* NotifierUI */, + 5BC5E02028DDEFE00094E427 /* TooltipUI */, + 5BC5E02328DE07860094E427 /* PopupCompositionBuffer */, + 5BA8C30228DF0360004C5CC4 /* CandidateWindow */, + 5BA8C30428DF0364004C5CC4 /* Voltaire */, ); productName = vChewing; productReference = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */; @@ -1067,33 +1056,29 @@ 5BF56F9828C39A2700DD6839 /* IMEState.swift in Sources */, 5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */, D47B92C027972AD100458394 /* main.swift in Sources */, - D4A13D5A27A59F0B003BE359 /* ctlInputMethod_Core.swift in Sources */, - 5B5F8AEE28C8826D007C11F1 /* ctlTooltip.swift in Sources */, + D4A13D5A27A59F0B003BE359 /* SessionCtl_Core.swift in Sources */, 5B0EF55F28CDBF8E00F8F7CE /* ctlClientListMgr.swift in Sources */, - 5B21177028753B9D000443A9 /* ctlInputMethod_Delegates.swift in Sources */, - 5B21176E28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift in Sources */, + 5B21177028753B9D000443A9 /* SessionCtl_Delegates.swift in Sources */, + 5B21176E28753B35000443A9 /* SessionCtl_HandleDisplay.swift in Sources */, 5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */, 5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */, 5B78EE0D28A562B4009456C1 /* suiPrefPaneDevZone.swift in Sources */, - 5B6C141228A9D4B30098ADF8 /* ctlInputMethod_HandleEvent.swift in Sources */, + 5B6C141228A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift in Sources */, D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */, 5BD0113D2818543900609769 /* KeyHandler_Core.swift in Sources */, 5BF56F9A28C39D1800DD6839 /* IMEStateData.swift in Sources */, - 5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */, - 5B21176C287539BB000443A9 /* ctlInputMethod_HandleStates.swift in Sources */, + 5B21176C287539BB000443A9 /* SessionCtl_HandleStates.swift in Sources */, 5BAEFAD028012565001F42C9 /* LMMgr.swift in Sources */, 5B782EC4280C243C007276DE /* KeyHandler_HandleCandidate.swift in Sources */, 5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */, - 5B242403284B0D6500520FE4 /* ctlCandidateUniversal.swift in Sources */, 5BCCAFF828DB19A300AB1B27 /* PrefMgr_Extension.swift in Sources */, 5BA9FD1127FEDB6B002DE248 /* ctlPrefUI.swift in Sources */, 5B8457A12871ADBE00C93B01 /* ChineseConverterBridge.swift in Sources */, 5BA9FD1327FEDB6B002DE248 /* suiPrefPaneDictionary.swift in Sources */, - 5B630A3C28CC97020010D076 /* ctlPopupCompositionBuffer.swift in Sources */, - 5BFDF011289635C100417BBC /* ctlCandidateIMK.swift in Sources */, - 5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */, - 5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */, + 5BFDF011289635C100417BBC /* IMKCandidatesImpl.swift in Sources */, + 5BB802DA27FABA8300CF1C19 /* SessionCtl_Menu.swift in Sources */, 5BE377A0288FED8D0037365B /* KeyHandler_HandleComposition.swift in Sources */, + 5B00FA0C28DEC17200F6D436 /* SessionCtl_IMKCandidatesData.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1280,7 +1265,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2702; + CURRENT_PROJECT_VERSION = 2750; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -1291,7 +1276,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13.4; - MARKETING_VERSION = 2.7.0; + MARKETING_VERSION = 2.7.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewingTests; @@ -1320,14 +1305,14 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2702; + CURRENT_PROJECT_VERSION = 2750; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13.4; - MARKETING_VERSION = 2.7.0; + MARKETING_VERSION = 2.7.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewingTests; @@ -1344,7 +1329,7 @@ 5BD05BC727B2A42A004C4F1D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-PhraseEditor"; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -1359,7 +1344,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2702; + CURRENT_PROJECT_VERSION = 2750; DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1382,7 +1367,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13.4; - MARKETING_VERSION = 2.7.0; + MARKETING_VERSION = 2.7.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor; @@ -1396,7 +1381,7 @@ 5BD05BC827B2A42A004C4F1D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-PhraseEditor"; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -1411,7 +1396,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2702; + CURRENT_PROJECT_VERSION = 2750; DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_NS_ASSERTIONS = NO; @@ -1430,7 +1415,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13.4; - MARKETING_VERSION = 2.7.0; + MARKETING_VERSION = 2.7.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor; @@ -1545,7 +1530,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2702; + CURRENT_PROJECT_VERSION = 2750; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; @@ -1575,7 +1560,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13.4; - MARKETING_VERSION = 2.7.0; + MARKETING_VERSION = 2.7.5; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1605,7 +1590,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2702; + CURRENT_PROJECT_VERSION = 2750; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; @@ -1629,7 +1614,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13.4; - MARKETING_VERSION = 2.7.0; + MARKETING_VERSION = 2.7.5; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1651,7 +1636,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2702; + CURRENT_PROJECT_VERSION = 2750; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = NO; @@ -1673,7 +1658,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13.4; - MARKETING_VERSION = 2.7.0; + MARKETING_VERSION = 2.7.5; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingInstaller; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1695,7 +1680,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2702; + CURRENT_PROJECT_VERSION = 2750; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = NO; @@ -1711,7 +1696,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13.4; - MARKETING_VERSION = 2.7.0; + MARKETING_VERSION = 2.7.5; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingInstaller; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1791,6 +1776,26 @@ isa = XCSwiftPackageProductDependency; productName = PinyinPhonaConverter; }; + 5BA8C30228DF0360004C5CC4 /* CandidateWindow */ = { + isa = XCSwiftPackageProductDependency; + productName = CandidateWindow; + }; + 5BA8C30428DF0364004C5CC4 /* Voltaire */ = { + isa = XCSwiftPackageProductDependency; + productName = Voltaire; + }; + 5BC5E01D28DDE4770094E427 /* NotifierUI */ = { + isa = XCSwiftPackageProductDependency; + productName = NotifierUI; + }; + 5BC5E02028DDEFE00094E427 /* TooltipUI */ = { + isa = XCSwiftPackageProductDependency; + productName = TooltipUI; + }; + 5BC5E02328DE07860094E427 /* PopupCompositionBuffer */ = { + isa = XCSwiftPackageProductDependency; + productName = PopupCompositionBuffer; + }; 5BDB7A3828D4824A001AC277 /* BookmarkManager */ = { isa = XCSwiftPackageProductDependency; productName = BookmarkManager;