From cfeead747a3c14bd8bd06d52afb1d0194275e21c Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 10 Apr 2022 19:37:02 +0800 Subject: [PATCH 01/36] UPR: ctlIME // Reset KeyHandler when toggling funcModes. - Also fixing a typo inherited from the upstream. --- .../Modules/IMEModules/ctlInputMethod.swift | 24 +++++++++++++------ .../IMEModules/ctlInputMethod_Menu.swift | 8 +++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index ef75f81d..d6983324 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -49,7 +49,7 @@ class ctlInputMethod: IMKInputController { // MARK: - - private var currentCandidateClient: Any? + private var currentClient: Any? private var keyHandler: KeyHandler = KeyHandler() private var state: InputState = InputState.Empty() @@ -76,6 +76,15 @@ class ctlInputMethod: IMKInputController { keyHandler.delegate = self } + // MARK: - KeyHandler Reset Command + + func resetKeyHandler() { + if let currentClient = currentClient { + keyHandler.clear() + self.handle(state: InputState.Empty(), client: currentClient) + } + } + // MARK: - IMKStateSetting protocol methods override func activateServer(_ client: Any!) { @@ -84,7 +93,7 @@ class ctlInputMethod: IMKInputController { // Override the keyboard layout to the basic one. setKeyLayout() // reset the state - currentCandidateClient = nil + currentClient = client keyHandler.clear() keyHandler.syncWithPreferences() @@ -94,6 +103,7 @@ class ctlInputMethod: IMKInputController { override func deactivateServer(_ client: Any!) { keyHandler.clear() + currentClient = nil self.handle(state: .Empty(), client: client) self.handle(state: .Deactivated(), client: client) } @@ -225,7 +235,7 @@ extension ctlInputMethod { } private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) { - currentCandidateClient = nil + currentClient = nil ctlCandidateCurrent?.delegate = nil ctlCandidateCurrent?.visible = false @@ -441,7 +451,7 @@ extension ctlInputMethod { ctlCandidateCurrent?.delegate = self ctlCandidateCurrent?.reloadData() - currentCandidateClient = client + currentClient = client ctlCandidateCurrent?.visible = true @@ -558,7 +568,7 @@ extension ctlInputMethod: ctlCandidateDelegate { } func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt) { - let client = currentCandidateClient + let client = currentClient if let state = state as? InputState.SymbolTable, let node = state.node.children?[Int(index)] @@ -566,7 +576,7 @@ extension ctlInputMethod: ctlCandidateDelegate { if let children = node.children, !children.isEmpty { self.handle( state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode), - client: currentCandidateClient) + client: currentClient) } else { self.handle(state: .Committing(poppedText: node.title), client: client) self.handle(state: .Empty(), client: client) @@ -603,7 +613,7 @@ extension ctlInputMethod: ctlCandidateDelegate { if let state = state as? InputState.AssociatedPhrases { let selectedValue = state.candidates[Int(index)] - handle(state: .Committing(poppedText: selectedValue), client: currentCandidateClient) + handle(state: .Committing(poppedText: selectedValue), client: currentClient) if mgrPrefs.associatedPhrasesEnabled, let associatePhrases = keyHandler.buildAssociatePhraseState( withKey: selectedValue, useVerticalMode: state.useVerticalMode) diff --git a/Source/Modules/IMEModules/ctlInputMethod_Menu.swift b/Source/Modules/IMEModules/ctlInputMethod_Menu.swift index a9152e56..a6a5aca2 100644 --- a/Source/Modules/IMEModules/ctlInputMethod_Menu.swift +++ b/Source/Modules/IMEModules/ctlInputMethod_Menu.swift @@ -163,6 +163,7 @@ extension ctlInputMethod { mgrPrefs.toggleSCPCTypingModeEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + resetKeyHandler() } @objc func toggleChineseConverter(_ sender: Any?) { @@ -172,6 +173,7 @@ extension ctlInputMethod { mgrPrefs.toggleChineseConversionEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + resetKeyHandler() } @objc func toggleShiftJISShinjitaiOutput(_ sender: Any?) { @@ -181,6 +183,7 @@ extension ctlInputMethod { mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + resetKeyHandler() } @objc func toggleHalfWidthPunctuation(_ sender: Any?) { @@ -191,6 +194,7 @@ extension ctlInputMethod { mgrPrefs.toggleHalfWidthPunctuationEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + resetKeyHandler() } @objc func toggleCNS11643Enabled(_ sender: Any?) { @@ -200,6 +204,7 @@ extension ctlInputMethod { mgrPrefs.toggleCNS11643Enabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + resetKeyHandler() } @objc func toggleSymbolEnabled(_ sender: Any?) { @@ -209,6 +214,7 @@ extension ctlInputMethod { mgrPrefs.toggleSymbolInputEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + resetKeyHandler() } @objc func toggleAssociatedPhrasesEnabled(_ sender: Any?) { @@ -219,6 +225,7 @@ extension ctlInputMethod { mgrPrefs.toggleAssociatedPhrasesEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + resetKeyHandler() } @objc func togglePhraseReplacement(_ sender: Any?) { @@ -228,6 +235,7 @@ extension ctlInputMethod { mgrPrefs.togglePhraseReplacementEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + resetKeyHandler() } @objc func selfUninstall(_ sender: Any?) { From 4eb08931e737079e5fa7948584b085054c1c467a Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 10 Apr 2022 16:21:40 +0800 Subject: [PATCH 02/36] Proj // Update several clang-formatted file headers. --- Source/Modules/SFX/clsSFX.swift | 3 ++- Source/UI/NotifierUI/NotifierController.swift | 3 ++- Source/UI/TooltipUI/TooltipController.swift | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/Modules/SFX/clsSFX.swift b/Source/Modules/SFX/clsSFX.swift index 6b2f2d1e..2b8b55dc 100644 --- a/Source/Modules/SFX/clsSFX.swift +++ b/Source/Modules/SFX/clsSFX.swift @@ -1,5 +1,6 @@ // Copyright (c) 2022 and onwards Isaac Xen (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/Source/UI/NotifierUI/NotifierController.swift b/Source/UI/NotifierUI/NotifierController.swift index 54bcbfff..8962a639 100644 --- a/Source/UI/NotifierUI/NotifierController.swift +++ b/Source/UI/NotifierUI/NotifierController.swift @@ -1,5 +1,6 @@ // Copyright (c) 2021 and onwards Weizhong Yang (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/Source/UI/TooltipUI/TooltipController.swift b/Source/UI/TooltipUI/TooltipController.swift index 7a2f0c81..b7dbdbed 100644 --- a/Source/UI/TooltipUI/TooltipController.swift +++ b/Source/UI/TooltipUI/TooltipController.swift @@ -1,5 +1,6 @@ // Copyright (c) 2021 and onwards Weizhong Yang (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From eea20e4db8f71b72e5e27cd21aabb3cc34d20fce Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 7 Apr 2022 18:36:18 +0800 Subject: [PATCH 03/36] Xcode // Update Swift compilation mode, etc. --- vChewing.xcodeproj/project.pbxproj | 8 +++++--- vChewing.xcodeproj/xcshareddata/xcschemes/Data.xcscheme | 2 +- .../xcshareddata/xcschemes/vChewing.xcscheme | 2 +- .../xcshareddata/xcschemes/vChewingInstaller.xcscheme | 2 +- .../xcshareddata/xcschemes/vChewingPhraseEditor.xcscheme | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 4d17c874..a059fde2 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -824,7 +824,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1320; - LastUpgradeCheck = 1320; + LastUpgradeCheck = 1330; TargetAttributes = { 5BD05BB727B2A429004C4F1D = { CreatedOnToolsVersion = 13.2; @@ -1317,6 +1317,7 @@ "$(OTHER_CFLAGS)", "-fcxx-modules", ); + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; @@ -1358,6 +1359,7 @@ "-fcxx-modules", ); SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Release; }; @@ -1423,7 +1425,7 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; SWIFT_OBJC_BRIDGING_HEADER = "Source/Headers/vChewing-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Osize"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; WRAPPER_EXTENSION = app; }; @@ -1485,7 +1487,7 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; SWIFT_OBJC_BRIDGING_HEADER = "Source/Headers/vChewing-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Osize"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; WRAPPER_EXTENSION = app; }; diff --git a/vChewing.xcodeproj/xcshareddata/xcschemes/Data.xcscheme b/vChewing.xcodeproj/xcshareddata/xcschemes/Data.xcscheme index ca7f9894..1c4da1d1 100644 --- a/vChewing.xcodeproj/xcshareddata/xcschemes/Data.xcscheme +++ b/vChewing.xcodeproj/xcshareddata/xcschemes/Data.xcscheme @@ -1,6 +1,6 @@ Date: Mon, 11 Apr 2022 08:20:21 +0800 Subject: [PATCH 04/36] Makefile // Use SwiftLint to optimize swift codebase. --- Makefile | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 57943fb0..4717b8e9 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,9 @@ debug: DSTROOT = /Library/Input Methods VC_APP_ROOT = $(DSTROOT)/vChewing.app -.PHONY: clang-format lint +.PHONY: clang-format lint batchfix format + +format: batchfix clang-format lint clang-format: @swift-format format --in-place --configuration ./.clang-format-swift.json --recursive ./DataCompiler/ @@ -30,11 +32,15 @@ clang-format: @find ./Installer/ -iname '*.h' -o -iname '*.m' | xargs clang-format -i -style=Microsoft @find ./Source/3rdParty/OVMandarin -iname '*.h' -o -iname '*.cpp' -o -iname '*.mm' -o -iname '*.m' | xargs clang-format -i -style=Microsoft @find ./Source/Modules/ -iname '*.h' -o -iname '*.cpp' -o -iname '*.mm' -o -iname '*.m' | xargs clang-format -i -style=Microsoft + lint: - @swift-format lint --configuration ./.clang-format-swift.json --recursive ./DataCompiler/ - @swift-format lint --configuration ./.clang-format-swift.json --recursive ./Installer/ - @swift-format lint --configuration ./.clang-format-swift.json --recursive ./Source/ - @swift-format lint --configuration ./.clang-format-swift.json --recursive ./UserPhraseEditor/ + @swift-format lint --configuration ./.clang-format-swift.json --recursive --parallel ./DataCompiler/ + @swift-format lint --configuration ./.clang-format-swift.json --recursive --parallel ./Installer/ + @swift-format lint --configuration ./.clang-format-swift.json --recursive --parallel ./Source/ + @swift-format lint --configuration ./.clang-format-swift.json --recursive --parallel ./UserPhraseEditor/ + +batchfix: + @swiftlint --fix ./ .PHONY: permission-check install-debug install-release From 1edd086f60a4457413671d85fb83c087e2f5cf68 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 11 Apr 2022 08:22:20 +0800 Subject: [PATCH 05/36] SwiftyOpenCC // SwiftLint. --- Packages/SwiftyOpenCC/Package.swift | 10 +++++----- .../SwiftyOpenCC/Tests/OpenCCTests/OpenCCTests.swift | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Packages/SwiftyOpenCC/Package.swift b/Packages/SwiftyOpenCC/Package.swift index 115ce9e0..5e7b0387 100644 --- a/Packages/SwiftyOpenCC/Package.swift +++ b/Packages/SwiftyOpenCC/Package.swift @@ -21,7 +21,7 @@ let package = Package( dependencies: ["OpenCC"], resources: [ .copy("benchmark"), - .copy("testcases"), + .copy("testcases") ]), .target( name: "copencc", @@ -66,20 +66,20 @@ let package = Package( "deps/marisa-0.2.6/AUTHORS", "deps/marisa-0.2.6/CMakeLists.txt", "deps/marisa-0.2.6/COPYING.md", - "deps/marisa-0.2.6/README.md", + "deps/marisa-0.2.6/README.md" ], sources: [ "source.cpp", "src", - "deps/marisa-0.2.6", + "deps/marisa-0.2.6" ], cxxSettings: [ .headerSearchPath("src"), .headerSearchPath("deps/darts-clone"), .headerSearchPath("deps/marisa-0.2.6/include"), .headerSearchPath("deps/marisa-0.2.6/lib"), - .define("ENABLE_DARTS"), - ]), + .define("ENABLE_DARTS") + ]) ], cxxLanguageStandard: .cxx14 ) diff --git a/Packages/SwiftyOpenCC/Tests/OpenCCTests/OpenCCTests.swift b/Packages/SwiftyOpenCC/Tests/OpenCCTests/OpenCCTests.swift index 6ce821cc..12dd45dd 100644 --- a/Packages/SwiftyOpenCC/Tests/OpenCCTests/OpenCCTests.swift +++ b/Packages/SwiftyOpenCC/Tests/OpenCCTests/OpenCCTests.swift @@ -10,7 +10,7 @@ let testCases: [(String, ChineseConverter.Options)] = [ ("s2tw", [.traditionalize, .twStandard]), ("tw2s", [.simplify, .twStandard]), ("s2twp", [.traditionalize, .twStandard, .twIdiom]), - ("tw2sp", [.simplify, .twStandard, .twIdiom]), + ("tw2sp", [.simplify, .twStandard, .twIdiom]) ] class OpenCCTests: XCTestCase { From ca30715e838c9a62bd06208c591b27743b5b8b65 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 11 Apr 2022 08:22:11 +0800 Subject: [PATCH 06/36] FSESHlpr // SwiftLint. --- Source/Modules/FileHandlers/FSEventStreamHelper.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Modules/FileHandlers/FSEventStreamHelper.swift b/Source/Modules/FileHandlers/FSEventStreamHelper.swift index 59948360..994aeede 100644 --- a/Source/Modules/FileHandlers/FSEventStreamHelper.swift +++ b/Source/Modules/FileHandlers/FSEventStreamHelper.swift @@ -47,7 +47,7 @@ public class FSEventStreamHelper: NSObject { self.dispatchQueue = queue } - private var stream: FSEventStreamRef? = nil + private var stream: FSEventStreamRef? public func start() -> Bool { if stream != nil { @@ -59,7 +59,7 @@ public class FSEventStreamHelper: NSObject { let stream = FSEventStreamCreate( nil, { - (stream, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in + (_, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in let helper = Unmanaged.fromOpaque(clientCallBackInfo!) .takeUnretainedValue() let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer.self) From 6b9e775d06dc93c82d15214c3aa74b8f87636e28 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 11 Apr 2022 08:22:22 +0800 Subject: [PATCH 07/36] AppDelegate @ Installer // SwiftLint. --- Installer/AppDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Installer/AppDelegate.swift b/Installer/AppDelegate.swift index 996d9688..2298efc1 100644 --- a/Installer/AppDelegate.swift +++ b/Installer/AppDelegate.swift @@ -321,7 +321,7 @@ class AppDelegate: NSWindowController, NSApplicationDelegate { ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: "")) } } - ntfPostInstall.beginSheetModal(for: window!) { response in + ntfPostInstall.beginSheetModal(for: window!) { _ in self.endAppWithDelay() } } From 8d651739e4ea7305a8571f46ba5ad20b8df71899 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 11 Apr 2022 08:22:13 +0800 Subject: [PATCH 08/36] InputState // SwiftLint: NSMake???? -> NS????. --- Source/Modules/ControllerModules/InputState.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Modules/ControllerModules/InputState.swift b/Source/Modules/ControllerModules/InputState.swift index 2d159521..7ea21974 100644 --- a/Source/Modules/ControllerModules/InputState.swift +++ b/Source/Modules/ControllerModules/InputState.swift @@ -243,7 +243,7 @@ class InputState: NSObject { self.markerIndex = markerIndex let begin = min(cursorIndex, markerIndex) let end = max(cursorIndex, markerIndex) - markedRange = NSMakeRange(Int(begin), Int(end - begin)) + markedRange = NSRange(location: Int(begin), length: Int(end - begin)) self.readings = readings super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) } From 6b688c691c3c9c662fafc59c0ae233c6bfa7541b Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 8 Apr 2022 14:08:43 +0800 Subject: [PATCH 09/36] IME // +RangeReplaceableCollection <- ctlPrefWindow. --- Source/Modules/IMEModules/IME.swift | 11 +++++++++++ Source/WindowControllers/ctlPrefWindow.swift | 8 -------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Source/Modules/IMEModules/IME.swift b/Source/Modules/IMEModules/IME.swift index 2e60ec79..6ac4a888 100644 --- a/Source/Modules/IMEModules/IME.swift +++ b/Source/Modules/IMEModules/IME.swift @@ -225,3 +225,14 @@ import Cocoa } } + +// MARK: - Root Extensions + +// Extend the RangeReplaceableCollection to allow it clean duplicated characters. +// Ref: https://stackoverflow.com/questions/25738817/ +extension RangeReplaceableCollection where Element: Hashable { + var charDeDuplicate: Self { + var set = Set() + return filter { set.insert($0).inserted } + } +} diff --git a/Source/WindowControllers/ctlPrefWindow.swift b/Source/WindowControllers/ctlPrefWindow.swift index 061aa472..aea18141 100644 --- a/Source/WindowControllers/ctlPrefWindow.swift +++ b/Source/WindowControllers/ctlPrefWindow.swift @@ -27,14 +27,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Carbon import Cocoa -// Extend the RangeReplaceableCollection to allow it clean duplicated characters. -extension RangeReplaceableCollection where Element: Hashable { - var charDeDuplicate: Self { - var set = Set() - return filter { set.insert($0).inserted } - } -} - // Please note that the class should be exposed using the same class name // in Objective-C in order to let IMK to see the same class name as // the "InputMethodServerPreferencesWindowControllerClass" in Info.plist. From d6a1470bb0c3096b393be21e1f030ed9df0141b9 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 8 Apr 2022 15:38:25 +0800 Subject: [PATCH 10/36] IME // Enumerate ASCII KeyLayouts into an array. --- Source/Modules/IMEModules/IME.swift | 70 +++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/Source/Modules/IMEModules/IME.swift b/Source/Modules/IMEModules/IME.swift index 6ac4a888..3cc832cc 100644 --- a/Source/Modules/IMEModules/IME.swift +++ b/Source/Modules/IMEModules/IME.swift @@ -22,6 +22,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import Carbon import Cocoa @objc public class IME: NSObject { @@ -224,6 +225,75 @@ import Cocoa return 0 } + // MARK: - 準備枚舉系統內所有的 ASCII 鍵盤佈局 + struct CarbonKeyboardLayout { + var strName: String = "" + var strValue: String = "" + } + static var arrEnumerateSystemKeyboardLayouts: [IME.CarbonKeyboardLayout] { + // 提前塞入 macOS 內建的兩款動態鍵盤佈局 + var arrKeyLayouts: [IME.CarbonKeyboardLayout] = [] + arrKeyLayouts += [ + IME.CarbonKeyboardLayout.init( + strName: NSLocalizedString("Apple Chewing - Dachen", comment: ""), + strValue: "com.apple.keylayout.ZhuyinBopomofo"), + IME.CarbonKeyboardLayout.init( + strName: NSLocalizedString("Apple Chewing - Eten Traditional", comment: ""), + strValue: "com.apple.keylayout.ZhuyinEten"), + ] + + // 準備枚舉系統內所有的 ASCII 鍵盤佈局 + var arrKeyLayoutsASCII: [IME.CarbonKeyboardLayout] = [] + let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] + for source in list { + if let ptrCategory = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) { + let category = Unmanaged.fromOpaque(ptrCategory).takeUnretainedValue() + if category != kTISCategoryKeyboardInputSource { + continue + } + } else { + continue + } + + if let ptrASCIICapable = TISGetInputSourceProperty( + source, kTISPropertyInputSourceIsASCIICapable) + { + let asciiCapable = Unmanaged.fromOpaque(ptrASCIICapable) + .takeUnretainedValue() + if asciiCapable != kCFBooleanTrue { + continue + } + } else { + continue + } + + if let ptrSourceType = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) { + let sourceType = Unmanaged.fromOpaque(ptrSourceType).takeUnretainedValue() + if sourceType != kTISTypeKeyboardLayout { + continue + } + } else { + continue + } + + guard let ptrSourceID = TISGetInputSourceProperty(source, kTISPropertyInputSourceID), + let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) + else { + continue + } + + let sourceID = String(Unmanaged.fromOpaque(ptrSourceID).takeUnretainedValue()) + let localizedName = String( + Unmanaged.fromOpaque(localizedNamePtr).takeUnretainedValue()) + + arrKeyLayoutsASCII += [ + IME.CarbonKeyboardLayout.init(strName: localizedName, strValue: sourceID) + ] + } + arrKeyLayouts += arrKeyLayoutsASCII + return arrKeyLayouts + } + } // MARK: - Root Extensions From 1a74d9de14151ebc165059c57488a6d5834fc676 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 10 Apr 2022 11:53:55 +0800 Subject: [PATCH 11/36] IME // Implement a whitelist towards enumerated keyLayouts. --- Source/Modules/IMEModules/IME.swift | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Source/Modules/IMEModules/IME.swift b/Source/Modules/IMEModules/IME.swift index 3cc832cc..349c813c 100644 --- a/Source/Modules/IMEModules/IME.swift +++ b/Source/Modules/IMEModules/IME.swift @@ -230,6 +230,17 @@ import Cocoa var strName: String = "" var strValue: String = "" } + static let arrWhitelistedKeyLayoutsASCII: [String] = [ + "com.apple.keylayout.ABC", + "com.apple.keylayout.ABC-AZERTY", + "com.apple.keylayout.ABC-QWERTZ", + "com.apple.keylayout.British", + "com.apple.keylayout.Colemak", + "com.apple.keylayout.Dvorak", + "com.apple.keylayout.Dvorak-Left", + "com.apple.keylayout.DVORAK-QWERTYCMD", + "com.apple.keylayout.Dvorak-Right", + ] static var arrEnumerateSystemKeyboardLayouts: [IME.CarbonKeyboardLayout] { // 提前塞入 macOS 內建的兩款動態鍵盤佈局 var arrKeyLayouts: [IME.CarbonKeyboardLayout] = [] @@ -243,6 +254,7 @@ import Cocoa ] // 準備枚舉系統內所有的 ASCII 鍵盤佈局 + var arrKeyLayoutsMACV: [IME.CarbonKeyboardLayout] = [] var arrKeyLayoutsASCII: [IME.CarbonKeyboardLayout] = [] let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] for source in list { @@ -286,10 +298,19 @@ import Cocoa let localizedName = String( Unmanaged.fromOpaque(localizedNamePtr).takeUnretainedValue()) - arrKeyLayoutsASCII += [ - IME.CarbonKeyboardLayout.init(strName: localizedName, strValue: sourceID) - ] + if sourceID.contains("vChewing") { + arrKeyLayoutsMACV += [ + IME.CarbonKeyboardLayout.init(strName: localizedName, strValue: sourceID) + ] + } + + if IME.arrWhitelistedKeyLayoutsASCII.contains(sourceID) { + arrKeyLayoutsASCII += [ + IME.CarbonKeyboardLayout.init(strName: localizedName, strValue: sourceID) + ] + } } + arrKeyLayouts += arrKeyLayoutsMACV arrKeyLayouts += arrKeyLayoutsASCII return arrKeyLayouts } From ea0a6ea3f4d821c979134ae1c514d3b794094b26 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 11 Apr 2022 09:50:09 +0800 Subject: [PATCH 12/36] IME // Allow throwing Strings as Errors. --- Source/Modules/IMEModules/IME.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Modules/IMEModules/IME.swift b/Source/Modules/IMEModules/IME.swift index 349c813c..c3c34aad 100644 --- a/Source/Modules/IMEModules/IME.swift +++ b/Source/Modules/IMEModules/IME.swift @@ -327,3 +327,9 @@ extension RangeReplaceableCollection where Element: Hashable { return filter { set.insert($0).inserted } } } + +// MARK: - Error Extension +extension String: Error {} +extension String: LocalizedError { + public var errorDescription: String? { return self } +} From a09799b380bc983a1c941f8aa11082c99f48c9c9 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 11 Apr 2022 09:55:30 +0800 Subject: [PATCH 13/36] IME // Allow a string to ensure its trailing slash. --- Source/Modules/IMEModules/IME.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/Modules/IMEModules/IME.swift b/Source/Modules/IMEModules/IME.swift index c3c34aad..b18300d3 100644 --- a/Source/Modules/IMEModules/IME.swift +++ b/Source/Modules/IMEModules/IME.swift @@ -333,3 +333,12 @@ extension String: Error {} extension String: LocalizedError { public var errorDescription: String? { return self } } + +// MARK: - Ensuring trailing slash of a string: +extension String { + mutating func ensureTrailingSlash() { + if !self.hasSuffix("/") { + self += "/" + } + } +} From 1d409457060f5719507ba223467df95aa32321f5 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 7 Apr 2022 17:41:59 +0800 Subject: [PATCH 14/36] mgrPref // Handle "kCheckUpdateAutomatically". --- Source/Modules/IMEModules/mgrPrefs.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Modules/IMEModules/mgrPrefs.swift b/Source/Modules/IMEModules/mgrPrefs.swift index d1f0a82a..fb18383d 100644 --- a/Source/Modules/IMEModules/mgrPrefs.swift +++ b/Source/Modules/IMEModules/mgrPrefs.swift @@ -346,6 +346,9 @@ struct ComposingBufferSize { @UserDefault(key: kIsDebugModeEnabled, defaultValue: false) @objc static var isDebugModeEnabled: Bool + @UserDefault(key: kCheckUpdateAutomatically, defaultValue: false) + @objc static var checkUpdateAutomatically: Bool + @UserDefault(key: kUserDataFolderSpecified, defaultValue: "") @objc static var userDataFolderSpecified: String From 18fd4975ff6cf48d3d1fc80f837125d2367628b0 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 7 Apr 2022 00:11:07 +0800 Subject: [PATCH 15/36] AppDelegate // Make UpdateAPI into a standalone module. --- Source/Modules/AppDelegate.swift | 160 +------------------- Source/Modules/IMEModules/apiUpdate.swift | 176 ++++++++++++++++++++++ vChewing.xcodeproj/project.pbxproj | 4 + 3 files changed, 185 insertions(+), 155 deletions(-) create mode 100644 Source/Modules/IMEModules/apiUpdate.swift diff --git a/Source/Modules/AppDelegate.swift b/Source/Modules/AppDelegate.swift index 912c789b..2de9b799 100644 --- a/Source/Modules/AppDelegate.swift +++ b/Source/Modules/AppDelegate.swift @@ -27,156 +27,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa import InputMethodKit -private let kCheckUpdateAutomatically = "CheckUpdateAutomatically" -private let kNextUpdateCheckDateKey = "NextUpdateCheckDate" -private let kUpdateInfoEndpointKey = "UpdateInfoEndpoint" -private let kUpdateInfoSiteKey = "UpdateInfoSite" -private let kVersionDescription = "VersionDescription" -private let kNextCheckInterval: TimeInterval = 86400.0 -private let kTimeoutInterval: TimeInterval = 60.0 - -struct VersionUpdateReport { - var siteUrl: URL? - var currentShortVersion: String = "" - var currentVersion: String = "" - var remoteShortVersion: String = "" - var remoteVersion: String = "" - var versionDescription: String = "" -} - -enum VersionUpdateApiResult { - case shouldUpdate(report: VersionUpdateReport) - case noNeedToUpdate - case ignored -} - -enum VersionUpdateApiError: Error, LocalizedError { - case connectionError(message: String) - - var errorDescription: String? { - switch self { - case .connectionError(let message): - return String( - format: NSLocalizedString( - "There may be no internet connection or the server failed to respond.\n\nError message: %@", - comment: ""), message) - } - } -} - -struct VersionUpdateApi { - static func check( - forced: Bool, callback: @escaping (Result) -> Void - ) -> URLSessionTask? { - guard let infoDict = Bundle.main.infoDictionary, - let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, - let updateInfoURL = URL(string: updateInfoURLString) - else { - return nil - } - - let request = URLRequest( - url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, - timeoutInterval: kTimeoutInterval) - let task = URLSession.shared.dataTask(with: request) { data, response, error in - if let error = error { - DispatchQueue.main.async { - forced - ? callback( - .failure( - VersionUpdateApiError.connectionError( - message: error.localizedDescription))) - : callback(.success(.ignored)) - } - return - } - - do { - guard - let plist = try PropertyListSerialization.propertyList( - from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any], - let remoteVersion = plist[kCFBundleVersionKey] as? String, - let infoDict = Bundle.main.infoDictionary - else { - DispatchQueue.main.async { - forced - ? callback(.success(.noNeedToUpdate)) - : callback(.success(.ignored)) - } - return - } - - // TODO: Validate info (e.g. bundle identifier) - // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this - - let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? "" - let result = currentVersion.compare( - remoteVersion, options: .numeric, range: nil, locale: nil) - - if result != .orderedAscending { - DispatchQueue.main.async { - forced - ? callback(.success(.noNeedToUpdate)) - : callback(.success(.ignored)) - } - IME.prtDebugIntel( - "vChewingDebug: Update // Order is not Ascending, assuming that there's no new version available." - ) - return - } - IME.prtDebugIntel( - "vChewingDebug: Update // New version detected, proceeding to the next phase.") - guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, - let siteInfoURL = URL(string: siteInfoURLString) - else { - DispatchQueue.main.async { - forced - ? callback(.success(.noNeedToUpdate)) - : callback(.success(.ignored)) - } - IME.prtDebugIntel( - "vChewingDebug: Update // Failed from retrieving / parsing URL intel.") - return - } - IME.prtDebugIntel( - "vChewingDebug: Update // URL intel retrieved, proceeding to the next phase.") - var report = VersionUpdateReport(siteUrl: siteInfoURL) - var versionDescription = "" - let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any] - if let versionDescriptions = versionDescriptions { - var locale = "en" - let supportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"] - let preferredTags = Bundle.preferredLocalizations(from: supportedLocales) - if let first = preferredTags.first { - locale = first - } - versionDescription = - versionDescriptions[locale] as? String ?? versionDescriptions["en"] - as? String ?? "" - if !versionDescription.isEmpty { - versionDescription = "\n\n" + versionDescription - } - } - report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? "" - report.currentVersion = currentVersion - report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? "" - report.remoteVersion = remoteVersion - report.versionDescription = versionDescription - DispatchQueue.main.async { - callback(.success(.shouldUpdate(report: report))) - } - IME.prtDebugIntel("vChewingDebug: Update // Callbck Complete.") - } catch { - DispatchQueue.main.async { - forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) - } - } - } - task.resume() - return task - } -} - @objc(AppDelegate) class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate, FSEventStreamHelperDelegate @@ -220,7 +70,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega mgrPrefs.setMissingDefaults() // 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。 - if (UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) != nil) == true { + if (UserDefaults.standard.object(forKey: VersionUpdateApi.kCheckUpdateAutomatically) != nil) == true { checkForUpdate() } } @@ -262,18 +112,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega // time for update? if !forced { - if UserDefaults.standard.bool(forKey: kCheckUpdateAutomatically) == false { + if UserDefaults.standard.bool(forKey: VersionUpdateApi.kCheckUpdateAutomatically) == false { return } let now = Date() - let date = UserDefaults.standard.object(forKey: kNextUpdateCheckDateKey) as? Date ?? now + let date = UserDefaults.standard.object(forKey: VersionUpdateApi.kNextUpdateCheckDateKey) as? Date ?? now if now.compare(date) == .orderedAscending { return } } - let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date()) - UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey) + let nextUpdateDate = Date(timeInterval: VersionUpdateApi.kNextCheckInterval, since: Date()) + UserDefaults.standard.set(nextUpdateDate, forKey: VersionUpdateApi.kNextUpdateCheckDateKey) checkTask = VersionUpdateApi.check(forced: forced) { [self] result in defer { diff --git a/Source/Modules/IMEModules/apiUpdate.swift b/Source/Modules/IMEModules/apiUpdate.swift new file mode 100644 index 00000000..127acb12 --- /dev/null +++ b/Source/Modules/IMEModules/apiUpdate.swift @@ -0,0 +1,176 @@ +// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa + +struct VersionUpdateReport { + var siteUrl: URL? + var currentShortVersion: String = "" + var currentVersion: String = "" + var remoteShortVersion: String = "" + var remoteVersion: String = "" + var versionDescription: String = "" +} + +enum VersionUpdateApiResult { + case shouldUpdate(report: VersionUpdateReport) + case noNeedToUpdate + case ignored +} + +enum VersionUpdateApiError: Error, LocalizedError { + case connectionError(message: String) + + var errorDescription: String? { + switch self { + case .connectionError(let message): + return String( + format: NSLocalizedString( + "There may be no internet connection or the server failed to respond.\n\nError message: %@", + comment: ""), message) + } + } +} + +struct VersionUpdateApi { + static let kCheckUpdateAutomatically = "CheckUpdateAutomatically" + static let kNextUpdateCheckDateKey = "NextUpdateCheckDate" + static let kUpdateInfoEndpointKey = "UpdateInfoEndpoint" + static let kUpdateInfoSiteKey = "UpdateInfoSite" + static let kVersionDescription = "VersionDescription" + static let kNextCheckInterval: TimeInterval = 86400.0 + static let kTimeoutInterval: TimeInterval = 60.0 + static func check( + forced: Bool, callback: @escaping (Result) -> Void + ) -> URLSessionTask? { + guard let infoDict = Bundle.main.infoDictionary, + let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, + let updateInfoURL = URL(string: updateInfoURLString) + else { + return nil + } + + let request = URLRequest( + url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, + timeoutInterval: kTimeoutInterval) + let task = URLSession.shared.dataTask(with: request) { data, _, error in + if let error = error { + DispatchQueue.main.async { + forced + ? callback( + .failure( + VersionUpdateApiError.connectionError( + message: error.localizedDescription))) + : callback(.success(.ignored)) + } + return + } + + do { + guard + let plist = try PropertyListSerialization.propertyList( + from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any], + let remoteVersion = plist[kCFBundleVersionKey] as? String, + let infoDict = Bundle.main.infoDictionary + else { + DispatchQueue.main.async { + forced + ? callback(.success(.noNeedToUpdate)) + : callback(.success(.ignored)) + } + return + } + + // TODO: Validate info (e.g. bundle identifier) + // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this + + let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? "" + let result = currentVersion.compare( + remoteVersion, options: .numeric, range: nil, locale: nil) + + if result != .orderedAscending { + DispatchQueue.main.async { + forced + ? callback(.success(.noNeedToUpdate)) + : callback(.success(.ignored)) + } + IME.prtDebugIntel( + "vChewingDebug: Update // Order is not Ascending, assuming that there's no new version available." + ) + return + } + IME.prtDebugIntel( + "vChewingDebug: Update // New version detected, proceeding to the next phase.") + guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, + let siteInfoURL = URL(string: siteInfoURLString) + else { + DispatchQueue.main.async { + forced + ? callback(.success(.noNeedToUpdate)) + : callback(.success(.ignored)) + } + IME.prtDebugIntel( + "vChewingDebug: Update // Failed from retrieving / parsing URL intel.") + return + } + IME.prtDebugIntel( + "vChewingDebug: Update // URL intel retrieved, proceeding to the next phase.") + var report = VersionUpdateReport(siteUrl: siteInfoURL) + var versionDescription = "" + let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any] + if let versionDescriptions = versionDescriptions { + var locale = "en" + let supportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"] + let preferredTags = Bundle.preferredLocalizations(from: supportedLocales) + if let first = preferredTags.first { + locale = first + } + versionDescription = + versionDescriptions[locale] as? String ?? versionDescriptions["en"] + as? String ?? "" + if !versionDescription.isEmpty { + versionDescription = "\n\n" + versionDescription + } + } + report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? "" + report.currentVersion = currentVersion + report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? "" + report.remoteVersion = remoteVersion + report.versionDescription = versionDescription + DispatchQueue.main.async { + callback(.success(.shouldUpdate(report: report))) + } + IME.prtDebugIntel("vChewingDebug: Update // Callbck Complete.") + } catch { + DispatchQueue.main.async { + forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) + } + } + } + task.resume() + return task + } +} diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index a059fde2..1181b2e5 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ 5BD05C6827B2BBEF004C4F1D /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6327B2BBEF004C4F1D /* Content.swift */; }; 5BD05C6927B2BBEF004C4F1D /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6427B2BBEF004C4F1D /* WindowController.swift */; }; 5BD05C6A27B2BBEF004C4F1D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6527B2BBEF004C4F1D /* ViewController.swift */; }; + 5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */; }; 5BDCBB2E27B4E67A00D0CC59 /* vChewingPhraseEditor.app in Resources */ = {isa = PBXBuildFile; fileRef = 5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */; }; 5BE78BD927B3775B005EA1BE /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE78BD827B37750005EA1BE /* ctlAboutWindow.swift */; }; 5BE78BDD27B3776D005EA1BE /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BE78BDA27B37764005EA1BE /* frmAboutWindow.xib */; }; @@ -212,6 +213,7 @@ 5BD05C6327B2BBEF004C4F1D /* Content.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Content.swift; sourceTree = ""; }; 5BD05C6427B2BBEF004C4F1D /* WindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = ""; }; 5BD05C6527B2BBEF004C4F1D /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = apiUpdate.swift; sourceTree = ""; }; 5BDCBB4227B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = ""; }; 5BDCBB4327B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmAboutWindow.strings"; sourceTree = ""; }; 5BDCBB4527B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmPrefWindow.strings"; sourceTree = ""; }; @@ -410,6 +412,7 @@ 5B62A32227AE756300A19448 /* IMEModules */ = { isa = PBXGroup; children = ( + 5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */, D4A13D5927A59D5C003BE359 /* ctlInputMethod.swift */, 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */, 5B5E535127EF261400C6AA1E /* IME.swift */, @@ -997,6 +1000,7 @@ D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */, 6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */, D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */, + 5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 6d37b137a63d33420493669bf5c3bf29c0bc3aeb Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 8 Apr 2022 19:40:15 +0800 Subject: [PATCH 16/36] apiUpdate // Move supportedLocales to IME module. --- Source/Modules/IMEModules/IME.swift | 2 +- Source/Modules/IMEModules/apiUpdate.swift | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/Modules/IMEModules/IME.swift b/Source/Modules/IMEModules/IME.swift index b18300d3..3aa8290a 100644 --- a/Source/Modules/IMEModules/IME.swift +++ b/Source/Modules/IMEModules/IME.swift @@ -26,7 +26,7 @@ import Carbon import Cocoa @objc public class IME: NSObject { - + static let arrSupportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"] static let dlgOpenPath = NSOpenPanel() // MARK: - 開關判定當前應用究竟是? diff --git a/Source/Modules/IMEModules/apiUpdate.swift b/Source/Modules/IMEModules/apiUpdate.swift index 127acb12..6827ff50 100644 --- a/Source/Modules/IMEModules/apiUpdate.swift +++ b/Source/Modules/IMEModules/apiUpdate.swift @@ -143,8 +143,7 @@ struct VersionUpdateApi { let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any] if let versionDescriptions = versionDescriptions { var locale = "en" - let supportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"] - let preferredTags = Bundle.preferredLocalizations(from: supportedLocales) + let preferredTags = Bundle.preferredLocalizations(from: IME.arrSupportedLocales) if let first = preferredTags.first { locale = first } From 52df58436155bda8ce753f93a9447d4e0997c51d Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 7 Apr 2022 00:09:25 +0800 Subject: [PATCH 17/36] NotifierUI // macOS Sierra Compatibility. --- Source/UI/NotifierUI/NotifierController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/UI/NotifierUI/NotifierController.swift b/Source/UI/NotifierUI/NotifierController.swift index 8962a639..a99302d9 100644 --- a/Source/UI/NotifierUI/NotifierController.swift +++ b/Source/UI/NotifierUI/NotifierController.swift @@ -124,7 +124,7 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate { panel.titlebarAppearsTransparent = true panel.titleVisibility = .hidden panel.showsToolbarButton = false - panel.standardWindowButton(NSWindow.ButtonType.fullScreenButton)?.isHidden = true + 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 From 080429b99d3afcfc21ce9334f11d5a4c65fe2fca Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 7 Apr 2022 21:04:56 +0800 Subject: [PATCH 18/36] mgrPrefs // Make UserDefaults variable names static, etc. - Also shortened some variable identifier names, plus some nomenclatural updates. - Set the default value of moveCursorAfterSelectingCandidate to true. --- .../Modules/ControllerModules/KeyHandler.mm | 4 +- Source/Modules/IMEModules/mgrPrefs.swift | 280 +++++++++--------- 2 files changed, 143 insertions(+), 141 deletions(-) diff --git a/Source/Modules/ControllerModules/KeyHandler.mm b/Source/Modules/ControllerModules/KeyHandler.mm index 8b912ea6..4dcb06e1 100644 --- a/Source/Modules/ControllerModules/KeyHandler.mm +++ b/Source/Modules/ControllerModules/KeyHandler.mm @@ -1324,7 +1324,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if ([input isTab]) { BOOL updated = - mgrPrefs.specifyTabKeyBehavior + mgrPrefs.specifyShiftTabKeyBehavior ? ([input isShiftHold] ? [ctlCandidateCurrent showPreviousPage] : [ctlCandidateCurrent showNextPage]) : ([input isShiftHold] ? [ctlCandidateCurrent highlightPreviousCandidate] : [ctlCandidateCurrent highlightNextCandidate]); @@ -1338,7 +1338,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if ([input isSpace]) { - BOOL updated = mgrPrefs.specifySpaceKeyBehavior + BOOL updated = mgrPrefs.specifyShiftSpaceKeyBehavior ? ([input isShiftHold] ? [ctlCandidateCurrent highlightNextCandidate] : [ctlCandidateCurrent showNextPage]) : ([input isShiftHold] ? [ctlCandidateCurrent showNextPage] diff --git a/Source/Modules/IMEModules/mgrPrefs.swift b/Source/Modules/IMEModules/mgrPrefs.swift index fb18383d..0ceb9705 100644 --- a/Source/Modules/IMEModules/mgrPrefs.swift +++ b/Source/Modules/IMEModules/mgrPrefs.swift @@ -26,38 +26,40 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa -private let kIsDebugModeEnabled = "_DebugMode" -private let kUserDataFolderSpecified = "UserDataFolderSpecified" -private let kCheckUpdateAutomatically = "CheckUpdateAutomatically" -private let kKeyboardLayoutPreference = "KeyboardLayout" -private let kBasisKeyboardLayoutPreference = "BasisKeyboardLayout" -private let kShowPageButtonsInCandidateWindow = "ShowPageButtonsInCandidateWindow" -private let kCandidateListTextSize = "CandidateListTextSize" -private let kAppleLanguagesPreferences = "AppleLanguages" -private let kShouldAutoReloadUserDataFiles = "ShouldAutoReloadUserDataFiles" -private let kSelectPhraseAfterCursorAsCandidatePreference = "SelectPhraseAfterCursorAsCandidate" -private let kUseHorizontalCandidateListPreference = "UseHorizontalCandidateList" -private let kComposingBufferSizePreference = "ComposingBufferSize" -private let kChooseCandidateUsingSpace = "ChooseCandidateUsingSpace" -private let kCNS11643Enabled = "CNS11643Enabled" -private let kSymbolInputEnabled = "SymbolInputEnabled" -private let kChineseConversionEnabled = "ChineseConversionEnabled" -private let kShiftJISShinjitaiOutputEnabled = "ShiftJISShinjitaiOutputEnabled" -private let kHalfWidthPunctuationEnabled = "HalfWidthPunctuationEnable" -private let kMoveCursorAfterSelectingCandidate = "MoveCursorAfterSelectingCandidate" -private let kEscToCleanInputBuffer = "EscToCleanInputBuffer" -private let kSpecifyTabKeyBehavior = "SpecifyTabKeyBehavior" -private let kSpecifySpaceKeyBehavior = "SpecifySpaceKeyBehavior" -private let kUseSCPCTypingMode = "UseSCPCTypingMode" -private let kMaxCandidateLength = "MaxCandidateLength" -private let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep" +struct UserDef { + static let kIsDebugModeEnabled = "_DebugMode" + static let kUserDataFolderSpecified = "UserDataFolderSpecified" + static let kCheckUpdateAutomatically = "CheckUpdateAutomatically" + static let kKeyboardLayout = "KeyboardLayout" + static let kBasisKeyboardLayout = "BasisKeyboardLayout" + static let kShowPageButtonsInCandidateWindow = "ShowPageButtonsInCandidateWindow" + static let kCandidateListTextSize = "CandidateListTextSize" + static let kAppleLanguages = "AppleLanguages" + static let kShouldAutoReloadUserDataFiles = "ShouldAutoReloadUserDataFiles" + static let kSelectPhraseAfterCursorAsCandidate = "SelectPhraseAfterCursorAsCandidate" + static let kUseHorizontalCandidateList = "UseHorizontalCandidateList" + static let kComposingBufferSize = "ComposingBufferSize" + static let kChooseCandidateUsingSpace = "ChooseCandidateUsingSpace" + static let kCNS11643Enabled = "CNS11643Enabled" + static let kSymbolInputEnabled = "SymbolInputEnabled" + static let kChineseConversionEnabled = "ChineseConversionEnabled" + static let kShiftJISShinjitaiOutputEnabled = "ShiftJISShinjitaiOutputEnabled" + static let kHalfWidthPunctuationEnabled = "HalfWidthPunctuationEnable" + static let kMoveCursorAfterSelectingCandidate = "MoveCursorAfterSelectingCandidate" + static let kEscToCleanInputBuffer = "EscToCleanInputBuffer" + static let kSpecifyShiftTabKeyBehavior = "SpecifyShiftTabKeyBehavior" + static let kSpecifyShiftSpaceKeyBehavior = "SpecifyShiftSpaceKeyBehavior" + static let kUseSCPCTypingMode = "UseSCPCTypingMode" + static let kMaxCandidateLength = "MaxCandidateLength" + static let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep" -private let kCandidateTextFontName = "CandidateTextFontName" -private let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName" -private let kCandidateKeys = "CandidateKeys" + static let kCandidateTextFontName = "CandidateTextFontName" + static let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName" + static let kCandidateKeys = "CandidateKeys" -private let kAssociatedPhrasesEnabled = "AssociatedPhrasesEnabled" -private let kPhraseReplacementEnabled = "PhraseReplacementEnabled" + static let kAssociatedPhrasesEnabled = "AssociatedPhrasesEnabled" + static let kPhraseReplacementEnabled = "PhraseReplacementEnabled" +} private let kDefaultCandidateListTextSize: CGFloat = 18 private let kMinKeyLabelSize: CGFloat = 10 @@ -191,35 +193,35 @@ struct ComposingBufferSize { @objc public class mgrPrefs: NSObject { static var allKeys: [String] { [ - kIsDebugModeEnabled, - kUserDataFolderSpecified, - kKeyboardLayoutPreference, - kBasisKeyboardLayoutPreference, - kShowPageButtonsInCandidateWindow, - kCandidateListTextSize, - kAppleLanguagesPreferences, - kShouldAutoReloadUserDataFiles, - kSelectPhraseAfterCursorAsCandidatePreference, - kUseHorizontalCandidateListPreference, - kComposingBufferSizePreference, - kChooseCandidateUsingSpace, - kCNS11643Enabled, - kSymbolInputEnabled, - kChineseConversionEnabled, - kShiftJISShinjitaiOutputEnabled, - kHalfWidthPunctuationEnabled, - kSpecifyTabKeyBehavior, - kSpecifySpaceKeyBehavior, - kEscToCleanInputBuffer, - kCandidateTextFontName, - kCandidateKeyLabelFontName, - kCandidateKeys, - kMoveCursorAfterSelectingCandidate, - kPhraseReplacementEnabled, - kUseSCPCTypingMode, - kMaxCandidateLength, - kShouldNotFartInLieuOfBeep, - kAssociatedPhrasesEnabled, + UserDef.kIsDebugModeEnabled, + UserDef.kUserDataFolderSpecified, + UserDef.kKeyboardLayout, + UserDef.kBasisKeyboardLayout, + UserDef.kShowPageButtonsInCandidateWindow, + UserDef.kCandidateListTextSize, + UserDef.kAppleLanguages, + UserDef.kShouldAutoReloadUserDataFiles, + UserDef.kSelectPhraseAfterCursorAsCandidate, + UserDef.kUseHorizontalCandidateList, + UserDef.kComposingBufferSize, + UserDef.kChooseCandidateUsingSpace, + UserDef.kCNS11643Enabled, + UserDef.kSymbolInputEnabled, + UserDef.kChineseConversionEnabled, + UserDef.kShiftJISShinjitaiOutputEnabled, + UserDef.kHalfWidthPunctuationEnabled, + UserDef.kSpecifyShiftTabKeyBehavior, + UserDef.kSpecifyShiftSpaceKeyBehavior, + UserDef.kEscToCleanInputBuffer, + UserDef.kCandidateTextFontName, + UserDef.kCandidateKeyLabelFontName, + UserDef.kCandidateKeys, + UserDef.kMoveCursorAfterSelectingCandidate, + UserDef.kPhraseReplacementEnabled, + UserDef.kUseSCPCTypingMode, + UserDef.kMaxCandidateLength, + UserDef.kShouldNotFartInLieuOfBeep, + UserDef.kAssociatedPhrasesEnabled, ] } @@ -227,139 +229,139 @@ struct ComposingBufferSize { // 既然 Preferences Module 的預設屬性不自動寫入 plist、而且還是 private,那這邊就先寫入了。 // 首次啟用輸入法時不要啟用偵錯模式。 - if UserDefaults.standard.object(forKey: kIsDebugModeEnabled) == nil { - UserDefaults.standard.set(mgrPrefs.isDebugModeEnabled, forKey: kIsDebugModeEnabled) + if UserDefaults.standard.object(forKey: UserDef.kIsDebugModeEnabled) == nil { + UserDefaults.standard.set(mgrPrefs.isDebugModeEnabled, forKey: UserDef.kIsDebugModeEnabled) } // 首次啟用輸入法時設定不要自動更新,免得在某些要隔絕外部網路連線的保密機構內觸犯資安規則。 - if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil { - UserDefaults.standard.set(false, forKey: kCheckUpdateAutomatically) + if UserDefaults.standard.object(forKey: UserDef.kCheckUpdateAutomatically) == nil { + UserDefaults.standard.set(false, forKey: UserDef.kCheckUpdateAutomatically) } // 預設顯示選字窗翻頁按鈕 - if UserDefaults.standard.object(forKey: kShowPageButtonsInCandidateWindow) == nil { + if UserDefaults.standard.object(forKey: UserDef.kShowPageButtonsInCandidateWindow) == nil { UserDefaults.standard.set( - mgrPrefs.showPageButtonsInCandidateWindow, forKey: kShowPageButtonsInCandidateWindow + mgrPrefs.showPageButtonsInCandidateWindow, forKey: UserDef.kShowPageButtonsInCandidateWindow ) } // 預設啟用繪文字與符號輸入 - if UserDefaults.standard.object(forKey: kSymbolInputEnabled) == nil { - UserDefaults.standard.set(mgrPrefs.symbolInputEnabled, forKey: kSymbolInputEnabled) + if UserDefaults.standard.object(forKey: UserDef.kSymbolInputEnabled) == nil { + UserDefaults.standard.set(mgrPrefs.symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled) } // 預設選字窗字詞文字尺寸,設成 18 剛剛好 - if UserDefaults.standard.object(forKey: kCandidateListTextSize) == nil { + if UserDefaults.standard.object(forKey: UserDef.kCandidateListTextSize) == nil { UserDefaults.standard.set( - mgrPrefs.candidateListTextSize, forKey: kCandidateListTextSize) + mgrPrefs.candidateListTextSize, forKey: UserDef.kCandidateListTextSize) } // 預設摁空格鍵來選字,所以設成 true - if UserDefaults.standard.object(forKey: kChooseCandidateUsingSpace) == nil { + if UserDefaults.standard.object(forKey: UserDef.kChooseCandidateUsingSpace) == nil { UserDefaults.standard.set( - mgrPrefs.chooseCandidateUsingSpace, forKey: kChooseCandidateUsingSpace) + mgrPrefs.chooseCandidateUsingSpace, forKey: UserDef.kChooseCandidateUsingSpace) } // 自動檢測使用者自訂語彙數據的變動並載入。 - if UserDefaults.standard.object(forKey: kShouldAutoReloadUserDataFiles) == nil { + if UserDefaults.standard.object(forKey: UserDef.kShouldAutoReloadUserDataFiles) == nil { UserDefaults.standard.set( - mgrPrefs.shouldAutoReloadUserDataFiles, forKey: kShouldAutoReloadUserDataFiles) + mgrPrefs.shouldAutoReloadUserDataFiles, forKey: UserDef.kShouldAutoReloadUserDataFiles) } // 預設情況下讓 Tab 鍵在選字窗內切換候選字、而不是用來換頁。 - if UserDefaults.standard.object(forKey: kSpecifyTabKeyBehavior) == nil { + if UserDefaults.standard.object(forKey: UserDef.kSpecifyShiftTabKeyBehavior) == nil { UserDefaults.standard.set( - mgrPrefs.specifyTabKeyBehavior, forKey: kSpecifyTabKeyBehavior) + mgrPrefs.specifyShiftTabKeyBehavior, forKey: UserDef.kSpecifyShiftTabKeyBehavior) } // 預設情況下讓 Space 鍵在選字窗內切換候選字、而不是用來換頁。 - if UserDefaults.standard.object(forKey: kSpecifySpaceKeyBehavior) == nil { + if UserDefaults.standard.object(forKey: UserDef.kSpecifyShiftSpaceKeyBehavior) == nil { UserDefaults.standard.set( - mgrPrefs.specifySpaceKeyBehavior, forKey: kSpecifySpaceKeyBehavior) + mgrPrefs.specifyShiftSpaceKeyBehavior, forKey: UserDef.kSpecifyShiftSpaceKeyBehavior) } // 預設禁用逐字選字模式(就是每個字都要選的那種),所以設成 false - if UserDefaults.standard.object(forKey: kUseSCPCTypingMode) == nil { - UserDefaults.standard.set(mgrPrefs.useSCPCTypingMode, forKey: kUseSCPCTypingMode) + if UserDefaults.standard.object(forKey: UserDef.kUseSCPCTypingMode) == nil { + UserDefaults.standard.set(mgrPrefs.useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode) } // 預設禁用逐字選字模式時的聯想詞功能,所以設成 false - if UserDefaults.standard.object(forKey: kAssociatedPhrasesEnabled) == nil { + if UserDefaults.standard.object(forKey: UserDef.kAssociatedPhrasesEnabled) == nil { UserDefaults.standard.set( - mgrPrefs.associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled) + mgrPrefs.associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled) } // 預設漢音風格選字,所以要設成 0 - if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidatePreference) + if UserDefaults.standard.object(forKey: UserDef.kSelectPhraseAfterCursorAsCandidate) == nil { UserDefaults.standard.set( mgrPrefs.selectPhraseAfterCursorAsCandidate, - forKey: kSelectPhraseAfterCursorAsCandidatePreference) + forKey: UserDef.kSelectPhraseAfterCursorAsCandidate) } // 預設在選字後自動移動游標 - if UserDefaults.standard.object(forKey: kMoveCursorAfterSelectingCandidate) == nil { + if UserDefaults.standard.object(forKey: UserDef.kMoveCursorAfterSelectingCandidate) == nil { UserDefaults.standard.set( mgrPrefs.moveCursorAfterSelectingCandidate, - forKey: kMoveCursorAfterSelectingCandidate) + forKey: UserDef.kMoveCursorAfterSelectingCandidate) } // 預設橫向選字窗,不爽請自行改成縱向選字窗 - if UserDefaults.standard.object(forKey: kUseHorizontalCandidateListPreference) == nil { + if UserDefaults.standard.object(forKey: UserDef.kUseHorizontalCandidateList) == nil { UserDefaults.standard.set( - mgrPrefs.useHorizontalCandidateList, forKey: kUseHorizontalCandidateListPreference) + mgrPrefs.useHorizontalCandidateList, forKey: UserDef.kUseHorizontalCandidateList) } // 預設停用全字庫支援 - if UserDefaults.standard.object(forKey: kCNS11643Enabled) == nil { - UserDefaults.standard.set(mgrPrefs.cns11643Enabled, forKey: kCNS11643Enabled) + if UserDefaults.standard.object(forKey: UserDef.kCNS11643Enabled) == nil { + UserDefaults.standard.set(mgrPrefs.cns11643Enabled, forKey: UserDef.kCNS11643Enabled) } // 預設停用繁體轉康熙模組 - if UserDefaults.standard.object(forKey: kChineseConversionEnabled) == nil { + if UserDefaults.standard.object(forKey: UserDef.kChineseConversionEnabled) == nil { UserDefaults.standard.set( - mgrPrefs.chineseConversionEnabled, forKey: kChineseConversionEnabled) + mgrPrefs.chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled) } // 預設停用繁體轉 JIS 當用新字體模組 - if UserDefaults.standard.object(forKey: kShiftJISShinjitaiOutputEnabled) == nil { + if UserDefaults.standard.object(forKey: UserDef.kShiftJISShinjitaiOutputEnabled) == nil { UserDefaults.standard.set( - mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) + mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled) } // 預設停用自訂語彙置換 - if UserDefaults.standard.object(forKey: kPhraseReplacementEnabled) == nil { + if UserDefaults.standard.object(forKey: UserDef.kPhraseReplacementEnabled) == nil { UserDefaults.standard.set( - mgrPrefs.phraseReplacementEnabled, forKey: kPhraseReplacementEnabled) + mgrPrefs.phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled) } // 預設沒事不要在那裡放屁 - if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil { + if UserDefaults.standard.object(forKey: UserDef.kShouldNotFartInLieuOfBeep) == nil { UserDefaults.standard.set( - mgrPrefs.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep) + mgrPrefs.shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep) } UserDefaults.standard.synchronize() } - @UserDefault(key: kIsDebugModeEnabled, defaultValue: false) + @UserDefault(key: UserDef.kIsDebugModeEnabled, defaultValue: false) @objc static var isDebugModeEnabled: Bool - @UserDefault(key: kCheckUpdateAutomatically, defaultValue: false) + @UserDefault(key: UserDef.kCheckUpdateAutomatically, defaultValue: false) @objc static var checkUpdateAutomatically: Bool - @UserDefault(key: kUserDataFolderSpecified, defaultValue: "") + @UserDefault(key: UserDef.kUserDataFolderSpecified, defaultValue: "") @objc static var userDataFolderSpecified: String @objc static func ifSpecifiedUserDataPathExistsInPlist() -> Bool { - UserDefaults.standard.object(forKey: kUserDataFolderSpecified) != nil + UserDefaults.standard.object(forKey: UserDef.kUserDataFolderSpecified) != nil } - @UserDefault(key: kAppleLanguagesPreferences, defaultValue: []) + @UserDefault(key: UserDef.kAppleLanguages, defaultValue: []) @objc static var appleLanguages: [String] - @UserDefault(key: kKeyboardLayoutPreference, defaultValue: 0) + @UserDefault(key: UserDef.kKeyboardLayout, defaultValue: 0) @objc static var keyboardLayout: Int @objc static var keyboardLayoutName: String { @@ -367,75 +369,75 @@ struct ComposingBufferSize { } @UserDefault( - key: kBasisKeyboardLayoutPreference, defaultValue: "com.apple.keylayout.ZhuyinBopomofo") + key: UserDef.kBasisKeyboardLayout, defaultValue: "com.apple.keylayout.ZhuyinBopomofo") @objc static var basisKeyboardLayout: String - @UserDefault(key: kShowPageButtonsInCandidateWindow, defaultValue: true) + @UserDefault(key: UserDef.kShowPageButtonsInCandidateWindow, defaultValue: true) @objc static var showPageButtonsInCandidateWindow: Bool - @CandidateListTextSize(key: kCandidateListTextSize) + @CandidateListTextSize(key: UserDef.kCandidateListTextSize) @objc static var candidateListTextSize: CGFloat - @UserDefault(key: kShouldAutoReloadUserDataFiles, defaultValue: true) + @UserDefault(key: UserDef.kShouldAutoReloadUserDataFiles, defaultValue: true) @objc static var shouldAutoReloadUserDataFiles: Bool - @UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreference, defaultValue: false) + @UserDefault(key: UserDef.kSelectPhraseAfterCursorAsCandidate, defaultValue: false) @objc static var selectPhraseAfterCursorAsCandidate: Bool - @UserDefault(key: kMoveCursorAfterSelectingCandidate, defaultValue: false) + @UserDefault(key: UserDef.kMoveCursorAfterSelectingCandidate, defaultValue: true) @objc static var moveCursorAfterSelectingCandidate: Bool - @UserDefault(key: kUseHorizontalCandidateListPreference, defaultValue: true) + @UserDefault(key: UserDef.kUseHorizontalCandidateList, defaultValue: true) @objc static var useHorizontalCandidateList: Bool - @ComposingBufferSize(key: kComposingBufferSizePreference) + @ComposingBufferSize(key: UserDef.kComposingBufferSize) @objc static var composingBufferSize: Int - @UserDefault(key: kChooseCandidateUsingSpace, defaultValue: true) + @UserDefault(key: UserDef.kChooseCandidateUsingSpace, defaultValue: true) @objc static var chooseCandidateUsingSpace: Bool - @UserDefault(key: kUseSCPCTypingMode, defaultValue: false) + @UserDefault(key: UserDef.kUseSCPCTypingMode, defaultValue: false) @objc static var useSCPCTypingMode: Bool @objc static func toggleSCPCTypingModeEnabled() -> Bool { useSCPCTypingMode = !useSCPCTypingMode - UserDefaults.standard.set(useSCPCTypingMode, forKey: kUseSCPCTypingMode) + UserDefaults.standard.set(useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode) return useSCPCTypingMode } - @UserDefault(key: kMaxCandidateLength, defaultValue: kDefaultComposingBufferSize * 2) + @UserDefault(key: UserDef.kMaxCandidateLength, defaultValue: kDefaultComposingBufferSize * 2) @objc static var maxCandidateLength: Int - @UserDefault(key: kShouldNotFartInLieuOfBeep, defaultValue: true) + @UserDefault(key: UserDef.kShouldNotFartInLieuOfBeep, defaultValue: true) @objc static var shouldNotFartInLieuOfBeep: Bool @objc static func toggleShouldNotFartInLieuOfBeep() -> Bool { shouldNotFartInLieuOfBeep = !shouldNotFartInLieuOfBeep - UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep) + UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep) return shouldNotFartInLieuOfBeep } - @UserDefault(key: kCNS11643Enabled, defaultValue: false) + @UserDefault(key: UserDef.kCNS11643Enabled, defaultValue: false) @objc static var cns11643Enabled: Bool @objc static func toggleCNS11643Enabled() -> Bool { cns11643Enabled = !cns11643Enabled mgrLangModel.setCNSEnabled(cns11643Enabled) // 很重要 - UserDefaults.standard.set(cns11643Enabled, forKey: kCNS11643Enabled) + UserDefaults.standard.set(cns11643Enabled, forKey: UserDef.kCNS11643Enabled) return cns11643Enabled } - @UserDefault(key: kSymbolInputEnabled, defaultValue: true) + @UserDefault(key: UserDef.kSymbolInputEnabled, defaultValue: true) @objc static var symbolInputEnabled: Bool @objc static func toggleSymbolInputEnabled() -> Bool { symbolInputEnabled = !symbolInputEnabled mgrLangModel.setSymbolEnabled(symbolInputEnabled) // 很重要 - UserDefaults.standard.set(symbolInputEnabled, forKey: kSymbolInputEnabled) + UserDefaults.standard.set(symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled) return symbolInputEnabled } - @UserDefault(key: kChineseConversionEnabled, defaultValue: false) + @UserDefault(key: UserDef.kChineseConversionEnabled, defaultValue: false) @objc static var chineseConversionEnabled: Bool @objc @discardableResult static func toggleChineseConversionEnabled() -> Bool { @@ -444,13 +446,13 @@ struct ComposingBufferSize { if chineseConversionEnabled && shiftJISShinjitaiOutputEnabled { self.toggleShiftJISShinjitaiOutputEnabled() UserDefaults.standard.set( - shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) + shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled) } - UserDefaults.standard.set(chineseConversionEnabled, forKey: kChineseConversionEnabled) + UserDefaults.standard.set(chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled) return chineseConversionEnabled } - @UserDefault(key: kShiftJISShinjitaiOutputEnabled, defaultValue: false) + @UserDefault(key: UserDef.kShiftJISShinjitaiOutputEnabled, defaultValue: false) @objc static var shiftJISShinjitaiOutputEnabled: Bool @objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool { @@ -460,11 +462,11 @@ struct ComposingBufferSize { self.toggleChineseConversionEnabled() } UserDefaults.standard.set( - shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) + shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled) return shiftJISShinjitaiOutputEnabled } - @UserDefault(key: kHalfWidthPunctuationEnabled, defaultValue: false) + @UserDefault(key: UserDef.kHalfWidthPunctuationEnabled, defaultValue: false) @objc static var halfWidthPunctuationEnabled: Bool @objc static func toggleHalfWidthPunctuationEnabled() -> Bool { @@ -472,23 +474,23 @@ struct ComposingBufferSize { return halfWidthPunctuationEnabled } - @UserDefault(key: kEscToCleanInputBuffer, defaultValue: true) + @UserDefault(key: UserDef.kEscToCleanInputBuffer, defaultValue: true) @objc static var escToCleanInputBuffer: Bool - @UserDefault(key: kSpecifyTabKeyBehavior, defaultValue: false) - @objc static var specifyTabKeyBehavior: Bool + @UserDefault(key: UserDef.kSpecifyShiftTabKeyBehavior, defaultValue: false) + @objc static var specifyShiftTabKeyBehavior: Bool - @UserDefault(key: kSpecifySpaceKeyBehavior, defaultValue: false) - @objc static var specifySpaceKeyBehavior: Bool + @UserDefault(key: UserDef.kSpecifyShiftSpaceKeyBehavior, defaultValue: false) + @objc static var specifyShiftSpaceKeyBehavior: Bool // MARK: - Optional settings - @UserDefault(key: kCandidateTextFontName, defaultValue: nil) + @UserDefault(key: UserDef.kCandidateTextFontName, defaultValue: nil) @objc static var candidateTextFontName: String? - @UserDefault(key: kCandidateKeyLabelFontName, defaultValue: nil) + @UserDefault(key: UserDef.kCandidateKeyLabelFontName, defaultValue: nil) @objc static var candidateKeyLabelFontName: String? - @UserDefault(key: kCandidateKeys, defaultValue: kDefaultKeys) + @UserDefault(key: UserDef.kCandidateKeys, defaultValue: kDefaultKeys) @objc static var candidateKeys: String @objc static var defaultCandidateKeys: String { @@ -551,22 +553,22 @@ struct ComposingBufferSize { } - @UserDefault(key: kPhraseReplacementEnabled, defaultValue: false) + @UserDefault(key: UserDef.kPhraseReplacementEnabled, defaultValue: false) @objc static var phraseReplacementEnabled: Bool @objc static func togglePhraseReplacementEnabled() -> Bool { phraseReplacementEnabled = !phraseReplacementEnabled mgrLangModel.setPhraseReplacementEnabled(phraseReplacementEnabled) - UserDefaults.standard.set(phraseReplacementEnabled, forKey: kPhraseReplacementEnabled) + UserDefaults.standard.set(phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled) return phraseReplacementEnabled } - @UserDefault(key: kAssociatedPhrasesEnabled, defaultValue: false) + @UserDefault(key: UserDef.kAssociatedPhrasesEnabled, defaultValue: false) @objc static var associatedPhrasesEnabled: Bool @objc static func toggleAssociatedPhrasesEnabled() -> Bool { associatedPhrasesEnabled = !associatedPhrasesEnabled - UserDefaults.standard.set(associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled) + UserDefaults.standard.set(associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled) return associatedPhrasesEnabled } From 9647249c198fbce3fe0c53e8d261badd9bdd97ee Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 8 Apr 2022 17:09:55 +0800 Subject: [PATCH 19/36] mgrPrefs // Simplify the way of synchronizing default values. --- Source/Modules/IMEModules/mgrPrefs.swift | 155 ++++++----------------- 1 file changed, 39 insertions(+), 116 deletions(-) diff --git a/Source/Modules/IMEModules/mgrPrefs.swift b/Source/Modules/IMEModules/mgrPrefs.swift index 0ceb9705..ee22bad8 100644 --- a/Source/Modules/IMEModules/mgrPrefs.swift +++ b/Source/Modules/IMEModules/mgrPrefs.swift @@ -77,7 +77,17 @@ private let kMaxComposingBufferSize = 30 private let kDefaultKeys = "123456789" -// MARK: Property wrappers +// MARK: - UserDefaults extension. + +@objc extension UserDefaults { + func setDefault(_ value: Any?, forKey defaultName: String) { + if self.object(forKey: defaultName) == nil { + self.set(value, forKey: defaultName) + } + } +} + +// MARK: - Property wrappers @propertyWrapper struct UserDefault { @@ -225,122 +235,35 @@ struct ComposingBufferSize { ] } + // MARK: - 既然 Preferences Module 的預設屬性不自動寫入 plist,那這邊就先寫入了。 @objc public static func setMissingDefaults() { - // 既然 Preferences Module 的預設屬性不自動寫入 plist、而且還是 private,那這邊就先寫入了。 - - // 首次啟用輸入法時不要啟用偵錯模式。 - if UserDefaults.standard.object(forKey: UserDef.kIsDebugModeEnabled) == nil { - UserDefaults.standard.set(mgrPrefs.isDebugModeEnabled, forKey: UserDef.kIsDebugModeEnabled) - } - - // 首次啟用輸入法時設定不要自動更新,免得在某些要隔絕外部網路連線的保密機構內觸犯資安規則。 - if UserDefaults.standard.object(forKey: UserDef.kCheckUpdateAutomatically) == nil { - UserDefaults.standard.set(false, forKey: UserDef.kCheckUpdateAutomatically) - } - - // 預設顯示選字窗翻頁按鈕 - if UserDefaults.standard.object(forKey: UserDef.kShowPageButtonsInCandidateWindow) == nil { - UserDefaults.standard.set( - mgrPrefs.showPageButtonsInCandidateWindow, forKey: UserDef.kShowPageButtonsInCandidateWindow - ) - } - - // 預設啟用繪文字與符號輸入 - if UserDefaults.standard.object(forKey: UserDef.kSymbolInputEnabled) == nil { - UserDefaults.standard.set(mgrPrefs.symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled) - } - - // 預設選字窗字詞文字尺寸,設成 18 剛剛好 - if UserDefaults.standard.object(forKey: UserDef.kCandidateListTextSize) == nil { - UserDefaults.standard.set( - mgrPrefs.candidateListTextSize, forKey: UserDef.kCandidateListTextSize) - } - - // 預設摁空格鍵來選字,所以設成 true - if UserDefaults.standard.object(forKey: UserDef.kChooseCandidateUsingSpace) == nil { - UserDefaults.standard.set( - mgrPrefs.chooseCandidateUsingSpace, forKey: UserDef.kChooseCandidateUsingSpace) - } - - // 自動檢測使用者自訂語彙數據的變動並載入。 - if UserDefaults.standard.object(forKey: UserDef.kShouldAutoReloadUserDataFiles) == nil { - UserDefaults.standard.set( - mgrPrefs.shouldAutoReloadUserDataFiles, forKey: UserDef.kShouldAutoReloadUserDataFiles) - } - - // 預設情況下讓 Tab 鍵在選字窗內切換候選字、而不是用來換頁。 - if UserDefaults.standard.object(forKey: UserDef.kSpecifyShiftTabKeyBehavior) == nil { - UserDefaults.standard.set( - mgrPrefs.specifyShiftTabKeyBehavior, forKey: UserDef.kSpecifyShiftTabKeyBehavior) - } - - // 預設情況下讓 Space 鍵在選字窗內切換候選字、而不是用來換頁。 - if UserDefaults.standard.object(forKey: UserDef.kSpecifyShiftSpaceKeyBehavior) == nil { - UserDefaults.standard.set( - mgrPrefs.specifyShiftSpaceKeyBehavior, forKey: UserDef.kSpecifyShiftSpaceKeyBehavior) - } - - // 預設禁用逐字選字模式(就是每個字都要選的那種),所以設成 false - if UserDefaults.standard.object(forKey: UserDef.kUseSCPCTypingMode) == nil { - UserDefaults.standard.set(mgrPrefs.useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode) - } - - // 預設禁用逐字選字模式時的聯想詞功能,所以設成 false - if UserDefaults.standard.object(forKey: UserDef.kAssociatedPhrasesEnabled) == nil { - UserDefaults.standard.set( - mgrPrefs.associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled) - } - - // 預設漢音風格選字,所以要設成 0 - if UserDefaults.standard.object(forKey: UserDef.kSelectPhraseAfterCursorAsCandidate) - == nil - { - UserDefaults.standard.set( - mgrPrefs.selectPhraseAfterCursorAsCandidate, - forKey: UserDef.kSelectPhraseAfterCursorAsCandidate) - } - - // 預設在選字後自動移動游標 - if UserDefaults.standard.object(forKey: UserDef.kMoveCursorAfterSelectingCandidate) == nil { - UserDefaults.standard.set( - mgrPrefs.moveCursorAfterSelectingCandidate, - forKey: UserDef.kMoveCursorAfterSelectingCandidate) - } - - // 預設橫向選字窗,不爽請自行改成縱向選字窗 - if UserDefaults.standard.object(forKey: UserDef.kUseHorizontalCandidateList) == nil { - UserDefaults.standard.set( - mgrPrefs.useHorizontalCandidateList, forKey: UserDef.kUseHorizontalCandidateList) - } - - // 預設停用全字庫支援 - if UserDefaults.standard.object(forKey: UserDef.kCNS11643Enabled) == nil { - UserDefaults.standard.set(mgrPrefs.cns11643Enabled, forKey: UserDef.kCNS11643Enabled) - } - - // 預設停用繁體轉康熙模組 - if UserDefaults.standard.object(forKey: UserDef.kChineseConversionEnabled) == nil { - UserDefaults.standard.set( - mgrPrefs.chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled) - } - - // 預設停用繁體轉 JIS 當用新字體模組 - if UserDefaults.standard.object(forKey: UserDef.kShiftJISShinjitaiOutputEnabled) == nil { - UserDefaults.standard.set( - mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled) - } - - // 預設停用自訂語彙置換 - if UserDefaults.standard.object(forKey: UserDef.kPhraseReplacementEnabled) == nil { - UserDefaults.standard.set( - mgrPrefs.phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled) - } - - // 預設沒事不要在那裡放屁 - if UserDefaults.standard.object(forKey: UserDef.kShouldNotFartInLieuOfBeep) == nil { - UserDefaults.standard.set( - mgrPrefs.shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep) - } + UserDefaults.standard.setDefault(mgrPrefs.isDebugModeEnabled, forKey: UserDef.kIsDebugModeEnabled) + UserDefaults.standard.setDefault(mgrPrefs.checkUpdateAutomatically, forKey: UserDef.kCheckUpdateAutomatically) + UserDefaults.standard.setDefault( + mgrPrefs.showPageButtonsInCandidateWindow, forKey: UserDef.kShowPageButtonsInCandidateWindow) + UserDefaults.standard.setDefault(mgrPrefs.symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled) + UserDefaults.standard.setDefault(mgrPrefs.candidateListTextSize, forKey: UserDef.kCandidateListTextSize) + UserDefaults.standard.setDefault(mgrPrefs.chooseCandidateUsingSpace, forKey: UserDef.kChooseCandidateUsingSpace) + UserDefaults.standard.setDefault( + mgrPrefs.shouldAutoReloadUserDataFiles, forKey: UserDef.kShouldAutoReloadUserDataFiles) + UserDefaults.standard.setDefault( + mgrPrefs.specifyShiftTabKeyBehavior, forKey: UserDef.kSpecifyShiftTabKeyBehavior) + UserDefaults.standard.setDefault( + mgrPrefs.specifyShiftSpaceKeyBehavior, forKey: UserDef.kSpecifyShiftSpaceKeyBehavior) + UserDefaults.standard.setDefault(mgrPrefs.useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode) + UserDefaults.standard.setDefault(mgrPrefs.associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled) + UserDefaults.standard.setDefault( + mgrPrefs.selectPhraseAfterCursorAsCandidate, forKey: UserDef.kSelectPhraseAfterCursorAsCandidate) + UserDefaults.standard.setDefault( + mgrPrefs.moveCursorAfterSelectingCandidate, forKey: UserDef.kMoveCursorAfterSelectingCandidate) + UserDefaults.standard.setDefault( + mgrPrefs.useHorizontalCandidateList, forKey: UserDef.kUseHorizontalCandidateList) + UserDefaults.standard.setDefault(mgrPrefs.cns11643Enabled, forKey: UserDef.kCNS11643Enabled) + UserDefaults.standard.setDefault(mgrPrefs.chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled) + UserDefaults.standard.setDefault( + mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled) + UserDefaults.standard.setDefault(mgrPrefs.phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled) + UserDefaults.standard.setDefault(mgrPrefs.shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep) UserDefaults.standard.synchronize() } From 02fbffc1439b5dd592138f98b13133ae3ffd355a Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 9 Apr 2022 00:06:02 +0800 Subject: [PATCH 20/36] mgrPrefs // +resetSpecifiedUserDataFolder() --- Source/Modules/IMEModules/mgrPrefs.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Modules/IMEModules/mgrPrefs.swift b/Source/Modules/IMEModules/mgrPrefs.swift index ee22bad8..617cf5ff 100644 --- a/Source/Modules/IMEModules/mgrPrefs.swift +++ b/Source/Modules/IMEModules/mgrPrefs.swift @@ -281,6 +281,11 @@ struct ComposingBufferSize { UserDefaults.standard.object(forKey: UserDef.kUserDataFolderSpecified) != nil } + @objc static func resetSpecifiedUserDataFolder() { + UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified") + IME.initLangModels(userOnly: true) + } + @UserDefault(key: UserDef.kAppleLanguages, defaultValue: []) @objc static var appleLanguages: [String] From 718d6ad4475d23c5dd324257aa10d94b9d8b70fc Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 8 Apr 2022 15:09:53 +0800 Subject: [PATCH 21/36] mgrPrefs, etc. // Nomenclatural updates. - KeyLayout -> MandarinParser. - BasisKeyboardLayout -> BasicKeyboardLayout. - Also optimizing KeyCov.isDynamicBasicKeyboardLayoutEnabled(). --- .../AppleKeyboardConverter.swift | 57 ++--- .../Modules/ControllerModules/KeyHandler.mm | 225 +++--------------- .../Modules/IMEModules/ctlInputMethod.swift | 2 +- Source/Modules/IMEModules/mgrPrefs.swift | 22 +- Source/WindowControllers/ctlPrefWindow.swift | 24 +- .../WindowNIBs/Base.lproj/frmPrefWindow.xib | 6 +- 6 files changed, 85 insertions(+), 251 deletions(-) diff --git a/Source/Modules/ControllerModules/AppleKeyboardConverter.swift b/Source/Modules/ControllerModules/AppleKeyboardConverter.swift index 1b3c4618..7d8131b8 100644 --- a/Source/Modules/ControllerModules/AppleKeyboardConverter.swift +++ b/Source/Modules/ControllerModules/AppleKeyboardConverter.swift @@ -25,43 +25,30 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa @objc class AppleKeyboardConverter: NSObject { - @objc class func isDynamicBaseKeyboardLayoutEnabled() -> Bool { - switch mgrPrefs.basisKeyboardLayout { - case "com.apple.keylayout.ZhuyinBopomofo": - return true - case "com.apple.keylayout.ZhuyinEten": - return true - case "org.atelierInmu.vChewing.keyLayouts.vchewingdachen": - return true - case "org.atelierInmu.vChewing.keyLayouts.vchewingmitac": - return true - case "org.atelierInmu.vChewing.keyLayouts.vchewingibm": - return true - case "org.atelierInmu.vChewing.keyLayouts.vchewingseigyou": - return true - case "org.atelierInmu.vChewing.keyLayouts.vchewingeten": - return true - case "org.unknown.keylayout.vChewingDachen": - return true - case "org.unknown.keylayout.vChewingFakeSeigyou": - return true - case "org.unknown.keylayout.vChewingETen": - return true - case "org.unknown.keylayout.vChewingIBM": - return true - case "org.unknown.keylayout.vChewingMiTAC": - return true - default: - return false - } + static let arrDynamicBasicKeyLayout: [String] = [ + "com.apple.keylayout.ZhuyinBopomofo", + "com.apple.keylayout.ZhuyinEten", + "org.atelierInmu.vChewing.keyLayouts.vchewingdachen", + "org.atelierInmu.vChewing.keyLayouts.vchewingmitac", + "org.atelierInmu.vChewing.keyLayouts.vchewingibm", + "org.atelierInmu.vChewing.keyLayouts.vchewingseigyou", + "org.atelierInmu.vChewing.keyLayouts.vchewingeten", + "org.unknown.keylayout.vChewingDachen", + "org.unknown.keylayout.vChewingFakeSeigyou", + "org.unknown.keylayout.vChewingETen", + "org.unknown.keylayout.vChewingIBM", + "org.unknown.keylayout.vChewingMiTAC", + ] + @objc class func isDynamicBasicKeyboardLayoutEnabled() -> Bool { + return AppleKeyboardConverter.arrDynamicBasicKeyLayout.contains(mgrPrefs.basicKeyboardLayout) } // 處理 Apple 注音鍵盤佈局類型。 @objc class func cnvApple2ABC(_ charCode: UniChar) -> UniChar { var charCode = charCode // 在按鍵資訊被送往 OVMandarin 之前,先轉換為可以被 OVMandarin 正常處理的資訊。 - if self.isDynamicBaseKeyboardLayoutEnabled() { + if self.isDynamicBasicKeyboardLayoutEnabled() { // 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。 - switch mgrPrefs.basisKeyboardLayout { + switch mgrPrefs.basicKeyboardLayout { case "com.apple.keylayout.ZhuyinBopomofo": do { if charCode == 97 { charCode = UniChar(65) } @@ -186,7 +173,7 @@ import Cocoa // 摁了 Alt 的符號。 if charCode == 8212 { charCode = UniChar(45) } // Apple 倚天注音佈局追加符號糾正項目。 - if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { + if mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { if charCode == 65343 { charCode = UniChar(95) } if charCode == 65306 { charCode = UniChar(58) } if charCode == 65311 { charCode = UniChar(63) } @@ -199,9 +186,9 @@ import Cocoa @objc class func cnvStringApple2ABC(_ strProcessed: String) -> String { var strProcessed = strProcessed - if self.isDynamicBaseKeyboardLayoutEnabled() { + if self.isDynamicBasicKeyboardLayoutEnabled() { // 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。 - switch mgrPrefs.basisKeyboardLayout { + switch mgrPrefs.basicKeyboardLayout { case "com.apple.keylayout.ZhuyinBopomofo": do { if strProcessed == "a" { strProcessed = "A" } @@ -326,7 +313,7 @@ import Cocoa // 摁了 Alt 的符號。 if strProcessed == "—" { strProcessed = "-" } // Apple 倚天注音佈局追加符號糾正項目。 - if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { + if mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { if strProcessed == "_" { strProcessed = "_" } if strProcessed == ":" { strProcessed = ":" } if strProcessed == "?" { strProcessed = "?" } diff --git a/Source/Modules/ControllerModules/KeyHandler.mm b/Source/Modules/ControllerModules/KeyHandler.mm index 4dcb06e1..53c297d6 100644 --- a/Source/Modules/ControllerModules/KeyHandler.mm +++ b/Source/Modules/ControllerModules/KeyHandler.mm @@ -46,9 +46,7 @@ static double FindHighestScore(const std::vector &nodes { double score = ni->node->highestUnigramScore(); if (score > highestScore) - { highestScore = score; - } } return highestScore + epsilon; } @@ -98,14 +96,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; - (BOOL)isBuilderEmpty { - if (_builder->grid().width() == 0) - { - return YES; - } - else - { - return NO; - } + return (_builder->grid().width() == 0); } - (void)setInputMode:(NSString *)value @@ -114,25 +105,17 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; vChewing::LMInstantiator *newLanguageModel; vChewing::UserOverrideModel *newUserOverrideModel; - if ([value isKindOfClass:[NSString class]] && [value isEqual:imeModeCHS]) - { - newInputMode = imeModeCHS; - newLanguageModel = [mgrLangModel lmCHS]; - newUserOverrideModel = [mgrLangModel userOverrideModelCHS]; - } - else - { - newInputMode = imeModeCHT; - newLanguageModel = [mgrLangModel lmCHT]; - newUserOverrideModel = [mgrLangModel userOverrideModelCHT]; - } + BOOL isCHS = [value isKindOfClass:[NSString class]] && [value isEqual:imeModeCHS]; + + newInputMode = isCHS ? imeModeCHS : imeModeCHT; + newLanguageModel = isCHS ? [mgrLangModel lmCHS] : [mgrLangModel lmCHT]; + newUserOverrideModel = isCHS ? [mgrLangModel userOverrideModelCHS] : [mgrLangModel userOverrideModelCHT]; // Report the current Input Mode to ctlInputMethod: ctlInputMethod.currentInputMode = newInputMode; - // Synchronize the Preference Setting "setPhraseReplacementEnabled" to the new LM. + // Synchronize the sub-languageModel state settings to the new LM. newLanguageModel->setPhraseReplacementEnabled(mgrPrefs.phraseReplacementEnabled); - // Also other sub language models: newLanguageModel->setSymbolEnabled(mgrPrefs.symbolInputEnabled); newLanguageModel->setCNSEnabled(mgrPrefs.cns11643Enabled); @@ -151,24 +134,16 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } if (!_bpmfReadingBuffer->isEmpty()) - { _bpmfReadingBuffer->clear(); - } } } - (void)dealloc -{ - // clean up everything +{ // clean up everything if (_bpmfReadingBuffer) - { delete _bpmfReadingBuffer; - } - if (_builder) - { delete _builder; - } } - (instancetype)init @@ -196,36 +171,35 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; - (void)syncWithPreferences { - NSInteger layout = mgrPrefs.keyboardLayout; - switch (layout) + switch (mgrPrefs.mandarinParser) { - case KeyboardLayoutOfStandard: + case MandarinParserOfStandard: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); break; - case KeyboardLayoutOfEten: + case MandarinParserOfEten: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETenLayout()); break; - case KeyboardLayoutOfHsu: + case MandarinParserOfHsu: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HsuLayout()); break; - case KeyboardLayoutOfEen26: + case MandarinParserOfEen26: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETen26Layout()); break; - case KeyboardLayoutOfIBM: + case MandarinParserOfIBM: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::IBMLayout()); break; - case KeyboardLayoutOfMiTAC: + case MandarinParserOfMiTAC: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::MiTACLayout()); break; - case KeyboardLayoutOfFakeSeigyou: + case MandarinParserOfFakeSeigyou: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::FakeSeigyouLayout()); break; - case KeyboardLayoutOfHanyuPinyin: + case MandarinParserOfHanyuPinyin: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HanyuPinyinLayout()); break; default: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); - mgrPrefs.keyboardLayout = KeyboardLayoutOfStandard; + mgrPrefs.mandarinParser = MandarinParserOfStandard; } } @@ -241,21 +215,16 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // in the user override model. BOOL addToOverrideModel = YES; if (selectedNode.spanningLength != [value count]) - { addToOverrideModel = NO; - } + if (addToOverrideModel) { double score = selectedNode.node->scoreForCandidate(stringValue); - if (score <= -12) - { // 威注音的 SymbolLM 的 Score 是 -12。 + if (score <= -12) // 威注音的 SymbolLM 的 Score 是 -12。 addToOverrideModel = NO; - } } if (addToOverrideModel) - { _userOverrideModel->observe(_walkedNodes, cursorIndex, stringValue, [[NSDate date] timeIntervalSince1970]); - } } [self _walk]; @@ -265,15 +234,11 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; for (auto node : _walkedNodes) { if (nextPosition >= cursorIndex) - { break; - } nextPosition += node.spanningLength; } if (nextPosition <= _builder->length()) - { _builder->setCursorIndex(nextPosition); - } } } @@ -284,13 +249,13 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; _walkedNodes.clear(); } -- (std::string)_currentLayout +- (std::string)_currentMandarinParser { - NSString *keyboardLayoutName = mgrPrefs.keyboardLayoutName; - std::string layout = std::string(keyboardLayoutName.UTF8String) + std::string("_"); - return layout; + return std::string(mgrPrefs.mandarinParserName.UTF8String) + std::string("_"); } +// MARK: - Handling Input + - (BOOL)handleInput:(keyParser *)input state:(InputState *)inState stateCallback:(void (^)(InputState *))stateCallback @@ -302,18 +267,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // if the inputText is empty, it's a function key combination, we ignore it if (!input.inputText.length) - { return NO; - } // if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it BOOL isFunctionKey = ([input isCommandHold] || [input isOptionHotKey] || [input isNumericPad]) || [input isControlHotKey]; if (![state isKindOfClass:[InputStateNotEmpty class]] && ![state isKindOfClass:[InputStateAssociatedPhrases class]] && isFunctionKey) - { return NO; - } // Caps Lock processing: if Caps Lock is ON, temporarily disable bopomofo. // Note: Alphanumerical mode processing. @@ -331,16 +292,12 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // When shift is pressed, don't do further processing, since it outputs capital letter anyway. if ([input isShiftHold]) - { return NO; - } // if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char // insertions. if (charCode < 0x80 && !isprint(charCode)) - { return NO; - } // commit everything in the buffer. InputStateCommitting *committingState = @@ -369,9 +326,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // MARK: Handle Candidates if ([state isKindOfClass:[InputStateChoosingCandidate class]]) - { return [self _handleCandidateState:state input:input stateCallback:stateCallback errorCallback:errorCallback]; - } // MARK: Handle Associated Phrases if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) @@ -381,9 +336,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; stateCallback:stateCallback errorCallback:errorCallback]; if (result) - { return YES; - } state = [[InputStateEmpty alloc] init]; stateCallback(state); } @@ -396,9 +349,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; input:input stateCallback:stateCallback errorCallback:errorCallback]) - { return YES; - } state = [marking convertToInputting]; stateCallback(state); } @@ -493,9 +444,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; (InputStateAssociatedPhrases *)[self buildAssociatePhraseStateWithKey:text useVerticalMode:input.useVerticalMode]; if (associatedPhrases) - { stateCallback(associatedPhrases); - } else { InputStateEmpty *empty = [[InputStateEmpty alloc] init]; @@ -504,9 +453,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } } else - { stateCallback(choosingCandidates); - } } // and tells the client that the key is consumed @@ -558,82 +505,54 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // MARK: Esc if ([input isESC]) - { return [self _handleEscWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - } // MARK: Cursor backward if ([input isCursorBackward] || emacsKey == vChewingEmacsKeyBackward) - { return [self _handleBackwardWithState:state input:input stateCallback:stateCallback errorCallback:errorCallback]; - } // MARK: Cursor forward if ([input isCursorForward] || emacsKey == vChewingEmacsKeyForward) - { return [self _handleForwardWithState:state input:input stateCallback:stateCallback errorCallback:errorCallback]; - } // MARK: Home if ([input isHome] || emacsKey == vChewingEmacsKeyHome) - { return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - } // MARK: End if ([input isEnd] || emacsKey == vChewingEmacsKeyEnd) - { return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - } // MARK: Ctrl+PgLf or Shift+PgLf - if ([input isControlHold] || [input isShiftHold]) - { - if ([input isOptionHold] && [input isLeft]) - { - return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - } - } + if (([input isControlHold] || [input isShiftHold]) && ([input isOptionHold] && [input isLeft])) + return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback]; // MARK: Ctrl+PgRt or Shift+PgRt - if ([input isControlHold] || [input isShiftHold]) - { - if ([input isOptionHold] && [input isRight]) - { - return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - } - } + if (([input isControlHold] || [input isShiftHold]) && ([input isOptionHold] && [input isRight])) + return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback]; // MARK: AbsorbedArrowKey if ([input isAbsorbedArrowKey] || [input isExtraChooseCandidateKey] || [input isExtraChooseCandidateKeyReverse]) - { return [self _handleAbsorbedArrowKeyWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - } // MARK: Backspace if ([input isBackSpace]) - { return [self _handleBackspaceWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - } // MARK: Delete if ([input isDelete] || emacsKey == vChewingEmacsKeyDelete) - { return [self _handleDeleteWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - } // MARK: Enter if ([input isEnter]) - { return ([input isControlHold] && [input isCommandHold]) ? [self _handleCtrlCommandEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback] : [self _handleEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - } // MARK: Punctuation list if ([input isSymbolMenuPhysicalKey] && ![input isShiftHold]) @@ -679,32 +598,24 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // if nothing is matched, see if it's a punctuation key for current layout. std::string punctuationNamePrefix; + if ([input isOptionHold]) - { punctuationNamePrefix = std::string("_alt_punctuation_"); - } else if ([input isControlHold]) - { punctuationNamePrefix = std::string("_ctrl_punctuation_"); - } else if (mgrPrefs.halfWidthPunctuationEnabled) - { punctuationNamePrefix = std::string("_half_punctuation_"); - } else - { punctuationNamePrefix = std::string("_punctuation_"); - } - std::string layout = [self _currentLayout]; - std::string customPunctuation = punctuationNamePrefix + layout + std::string(1, (char)charCode); + + std::string parser = [self _currentMandarinParser]; + std::string customPunctuation = punctuationNamePrefix + parser + std::string(1, (char)charCode); if ([self _handlePunctuation:customPunctuation state:state usingVerticalMode:input.useVerticalMode stateCallback:stateCallback errorCallback:errorCallback]) - { return YES; - } // if nothing is matched, see if it's a punctuation key. std::string punctuation = punctuationNamePrefix + std::string(1, (char)charCode); @@ -713,9 +624,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; usingVerticalMode:input.useVerticalMode stateCallback:stateCallback errorCallback:errorCallback]) - { return YES; - } // Lukhnos 這裡的處理反而會使得 Apple 倚天注音動態鍵盤佈局「敲不了半形大寫英文」的缺點曝露無疑,所以注釋掉。 // 至於他試圖用這種處理來解決的上游 UPR293 @@ -728,9 +637,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; usingVerticalMode:input.useVerticalMode stateCallback:stateCallback errorCallback:errorCallback]) - { return YES; - } } // still nothing, then we update the composing buffer (some app has strange behavior if we don't do this, "thinking" @@ -754,9 +661,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; errorCallback:(void (^)(void))errorCallback { if (![state isKindOfClass:[InputStateInputting class]]) - { return NO; - } BOOL escToClearInputBufferEnabled = mgrPrefs.escToCleanInputBuffer; @@ -800,9 +705,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; errorCallback:(void (^)(void))errorCallback { if (![state isKindOfClass:[InputStateInputting class]]) - { return NO; - } if (!_bpmfReadingBuffer->isEmpty()) { @@ -859,9 +762,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; errorCallback:(void (^)(void))errorCallback { if (![state isKindOfClass:[InputStateInputting class]]) - { return NO; - } if (!_bpmfReadingBuffer->isEmpty()) { @@ -917,9 +818,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; errorCallback:(void (^)(void))errorCallback { if (![state isKindOfClass:[InputStateInputting class]]) - { return NO; - } if (!_bpmfReadingBuffer->isEmpty()) { @@ -950,9 +849,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; errorCallback:(void (^)(void))errorCallback { if (![state isKindOfClass:[InputStateInputting class]]) - { return NO; - } if (!_bpmfReadingBuffer->isEmpty()) { @@ -983,9 +880,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; errorCallback:(void (^)(void))errorCallback { if (![state isKindOfClass:[InputStateInputting class]]) - { return NO; - } if (!_bpmfReadingBuffer->isEmpty()) { @@ -1001,9 +896,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; errorCallback:(void (^)(void))errorCallback { if (![state isKindOfClass:[InputStateInputting class]]) - { return NO; - } if (_bpmfReadingBuffer->isEmpty()) { @@ -1021,9 +914,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } } else - { _bpmfReadingBuffer->backspace(); - } if (_bpmfReadingBuffer->isEmpty() && !_builder->length()) { @@ -1043,9 +934,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; errorCallback:(void (^)(void))errorCallback { if (![state isKindOfClass:[InputStateInputting class]]) - { return NO; - } if (_bpmfReadingBuffer->isEmpty()) { @@ -1106,9 +995,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; errorCallback:(void (^)(void))errorCallback { if (![state isKindOfClass:[InputStateInputting class]]) - { return NO; - } [self clear]; @@ -1128,9 +1015,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; errorCallback:(void (^)(void))errorCallback { if (!_languageModel->hasUnigramsForKey(customPunctuation)) - { return NO; - } NSString *poppedText; if (_bpmfReadingBuffer->isEmpty()) @@ -1165,9 +1050,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; stateCallback(empty); } else - { stateCallback(candidateState); - } } return YES; } @@ -1218,9 +1101,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; stateCallback(inputting); } else - { stateCallback(marking); - } } else { @@ -1249,9 +1130,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; stateCallback(inputting); } else - { stateCallback(marking); - } } else { @@ -1495,9 +1374,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; errorCallback(); } else - { ctlCandidateCurrent.selectedCandidateIndex = 0; - } return YES; } @@ -1505,18 +1382,12 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSArray *candidates; if ([state isKindOfClass:[InputStateChoosingCandidate class]]) - { candidates = [(InputStateChoosingCandidate *)state candidates]; - } else if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) - { candidates = [(InputStateAssociatedPhrases *)state candidates]; - } if (!candidates) - { return NO; - } if (([input isEnd] || input.emacsKey == vChewingEmacsKeyEnd) && candidates.count > 0) { @@ -1526,30 +1397,23 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; errorCallback(); } else - { ctlCandidateCurrent.selectedCandidateIndex = candidates.count - 1; - } + return YES; } if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) { if (![input isShiftHold]) - { return NO; - } } NSInteger index = NSNotFound; NSString *match; if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) - { match = input.inputTextIgnoringModifiers; - } else - { match = inputText; - } for (NSUInteger j = 0, c = [ctlCandidateCurrent.keyLabels count]; j < c; j++) { @@ -1572,31 +1436,22 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) - { return NO; - } if (mgrPrefs.useSCPCTypingMode) { - std::string layout = [self _currentLayout]; std::string punctuationNamePrefix; if ([input isOptionHold]) - { punctuationNamePrefix = std::string("_alt_punctuation_"); - } else if ([input isControlHold]) - { punctuationNamePrefix = std::string("_ctrl_punctuation_"); - } else if (mgrPrefs.halfWidthPunctuationEnabled) - { punctuationNamePrefix = std::string("_half_punctuation_"); - } else - { punctuationNamePrefix = std::string("_punctuation_"); - } - std::string customPunctuation = punctuationNamePrefix + layout + std::string(1, (char)charCode); + + std::string parser = [self _currentMandarinParser]; + std::string customPunctuation = punctuationNamePrefix + parser + std::string(1, (char)charCode); std::string punctuation = punctuationNamePrefix + std::string(1, (char)charCode); BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((char)charCode) || @@ -1607,9 +1462,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; { std::string letter = std::string("_letter_") + std::string(1, (char)charCode); if (_languageModel->hasUnigramsForKey(letter)) - { shouldAutoSelectCandidate = YES; - } } if (shouldAutoSelectCandidate) @@ -1813,9 +1666,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; const std::vector &candidates = (*ni).node->candidates(); for (std::vector::const_iterator ci = candidates.begin(), ce = candidates.end(); ci != ce; ++ci) - { [candidatesArray addObject:[NSString stringWithUTF8String:(*ci).value.c_str()]]; - } } InputStateChoosingCandidate *state = @@ -1831,9 +1682,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; size_t cursorIndex = _builder->cursorIndex(); // MS Phonetics IME style, phrase is *after* the cursor, i.e. cursor is always *before* the phrase if ((mgrPrefs.selectPhraseAfterCursorAsCandidate && (cursorIndex < _builder->length())) || !cursorIndex) - { ++cursorIndex; - } return cursorIndex; } @@ -1843,9 +1692,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSMutableArray *readingsArray = [[NSMutableArray alloc] init]; std::vector v = _builder->readings(); for (std::vector::iterator it_i = v.begin(); it_i != v.end(); ++it_i) - { [readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]]; - } return readingsArray; } diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index d6983324..a2c78f0e 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -66,7 +66,7 @@ class ctlInputMethod: IMKInputController { @objc func setKeyLayout() { let client = client().self as IMKTextInput - client.overrideKeyboard(withKeyboardNamed: mgrPrefs.basisKeyboardLayout) + client.overrideKeyboard(withKeyboardNamed: mgrPrefs.basicKeyboardLayout) } // MARK: - IMKInputController methods diff --git a/Source/Modules/IMEModules/mgrPrefs.swift b/Source/Modules/IMEModules/mgrPrefs.swift index 617cf5ff..b865c979 100644 --- a/Source/Modules/IMEModules/mgrPrefs.swift +++ b/Source/Modules/IMEModules/mgrPrefs.swift @@ -30,8 +30,8 @@ struct UserDef { static let kIsDebugModeEnabled = "_DebugMode" static let kUserDataFolderSpecified = "UserDataFolderSpecified" static let kCheckUpdateAutomatically = "CheckUpdateAutomatically" - static let kKeyboardLayout = "KeyboardLayout" - static let kBasisKeyboardLayout = "BasisKeyboardLayout" + static let kMandarinParser = "MandarinParser" + static let kBasicKeyboardLayout = "BasicKeyboardLayout" static let kShowPageButtonsInCandidateWindow = "ShowPageButtonsInCandidateWindow" static let kCandidateListTextSize = "CandidateListTextSize" static let kAppleLanguages = "AppleLanguages" @@ -167,7 +167,7 @@ struct ComposingBufferSize { // MARK: - -@objc enum KeyboardLayout: Int { +@objc enum MandarinParser: Int { case ofStandard = 0 case ofEten = 1 case ofHsu = 2 @@ -205,8 +205,8 @@ struct ComposingBufferSize { [ UserDef.kIsDebugModeEnabled, UserDef.kUserDataFolderSpecified, - UserDef.kKeyboardLayout, - UserDef.kBasisKeyboardLayout, + UserDef.kMandarinParser, + UserDef.kBasicKeyboardLayout, UserDef.kShowPageButtonsInCandidateWindow, UserDef.kCandidateListTextSize, UserDef.kAppleLanguages, @@ -289,16 +289,16 @@ struct ComposingBufferSize { @UserDefault(key: UserDef.kAppleLanguages, defaultValue: []) @objc static var appleLanguages: [String] - @UserDefault(key: UserDef.kKeyboardLayout, defaultValue: 0) - @objc static var keyboardLayout: Int + @UserDefault(key: UserDef.kMandarinParser, defaultValue: 0) + @objc static var mandarinParser: Int - @objc static var keyboardLayoutName: String { - (KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.ofStandard).name + @objc static var mandarinParserName: String { + (MandarinParser(rawValue: self.mandarinParser) ?? MandarinParser.ofStandard).name } @UserDefault( - key: UserDef.kBasisKeyboardLayout, defaultValue: "com.apple.keylayout.ZhuyinBopomofo") - @objc static var basisKeyboardLayout: String + key: UserDef.kBasicKeyboardLayout, defaultValue: "com.apple.keylayout.ZhuyinBopomofo") + @objc static var basicKeyboardLayout: String @UserDefault(key: UserDef.kShowPageButtonsInCandidateWindow, defaultValue: true) @objc static var showPageButtonsInCandidateWindow: Bool diff --git a/Source/WindowControllers/ctlPrefWindow.swift b/Source/WindowControllers/ctlPrefWindow.swift index aea18141..2fb201be 100644 --- a/Source/WindowControllers/ctlPrefWindow.swift +++ b/Source/WindowControllers/ctlPrefWindow.swift @@ -33,7 +33,7 @@ import Cocoa @objc(ctlPrefWindow) class ctlPrefWindow: NSWindowController { @IBOutlet weak var fontSizePopUpButton: NSPopUpButton! @IBOutlet weak var uiLanguageButton: NSPopUpButton! - @IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton! + @IBOutlet weak var basicKeyboardLayoutButton: NSPopUpButton! @IBOutlet weak var selectionKeyComboBox: NSComboBox! @IBOutlet weak var chkTrad2KangXi: NSButton! @IBOutlet weak var chkTrad2JISShinjitai: NSButton! @@ -77,22 +77,22 @@ import Cocoa var usKeyboardLayoutItem: NSMenuItem? = nil var chosenBaseKeyboardLayoutItem: NSMenuItem? = nil - basisKeyboardLayoutButton.menu?.removeAllItems() + basicKeyboardLayoutButton.menu?.removeAllItems() let itmAppleZhuyinBopomofo = NSMenuItem() itmAppleZhuyinBopomofo.title = String( format: NSLocalizedString("Apple Zhuyin Bopomofo (Dachen)", comment: "")) itmAppleZhuyinBopomofo.representedObject = String( "com.apple.keylayout.ZhuyinBopomofo") - basisKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinBopomofo) + basicKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinBopomofo) let itmAppleZhuyinEten = NSMenuItem() itmAppleZhuyinEten.title = String( format: NSLocalizedString("Apple Zhuyin Eten (Traditional)", comment: "")) itmAppleZhuyinEten.representedObject = String("com.apple.keylayout.ZhuyinEten") - basisKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinEten) + basicKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinEten) - let basisKeyboardLayoutID = mgrPrefs.basisKeyboardLayout + let basicKeyboardLayoutID = mgrPrefs.basicKeyboardLayout for source in list { if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) { @@ -142,13 +142,13 @@ import Cocoa if sourceID == "com.apple.keylayout.US" { usKeyboardLayoutItem = menuItem } - if basisKeyboardLayoutID == sourceID { + if basicKeyboardLayoutID == sourceID { chosenBaseKeyboardLayoutItem = menuItem } - basisKeyboardLayoutButton.menu?.addItem(menuItem) + basicKeyboardLayoutButton.menu?.addItem(menuItem) } - switch basisKeyboardLayoutID { + switch basicKeyboardLayoutID { case "com.apple.keylayout.ZhuyinBopomofo": chosenBaseKeyboardLayoutItem = itmAppleZhuyinBopomofo case "com.apple.keylayout.ZhuyinEten": @@ -157,7 +157,7 @@ import Cocoa break // nothing to do } - basisKeyboardLayoutButton.select(chosenBaseKeyboardLayoutItem ?? usKeyboardLayoutItem) + basicKeyboardLayoutButton.select(chosenBaseKeyboardLayoutItem ?? usKeyboardLayoutItem) selectionKeyComboBox.usesDataSource = false selectionKeyComboBox.removeAllItems() @@ -193,9 +193,9 @@ import Cocoa } } - @IBAction func updateBasisKeyboardLayoutAction(_ sender: Any) { - if let sourceID = basisKeyboardLayoutButton.selectedItem?.representedObject as? String { - mgrPrefs.basisKeyboardLayout = sourceID + @IBAction func updateBasicKeyboardLayoutAction(_ sender: Any) { + if let sourceID = basicKeyboardLayoutButton.selectedItem?.representedObject as? String { + mgrPrefs.basicKeyboardLayout = sourceID } } diff --git a/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib b/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib index c6501643..a5950fb4 100644 --- a/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib +++ b/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib @@ -8,7 +8,7 @@ - + @@ -764,7 +764,7 @@ - + @@ -779,7 +779,7 @@ - + From 40124f243bb7222928b6ad7a61ff412a83a92847 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 10 Apr 2022 10:14:00 +0800 Subject: [PATCH 22/36] frmPrefWindow // Sync nomenclatural UserDefaults key changes. --- Source/WindowNIBs/Base.lproj/frmPrefWindow.xib | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib b/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib index a5950fb4..2feb573d 100644 --- a/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib +++ b/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib @@ -445,7 +445,7 @@ - + @@ -478,7 +478,7 @@ - + From 9b85039069028049b63de5263791532b8f24292e Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 9 Apr 2022 00:08:37 +0800 Subject: [PATCH 23/36] ctlPrefWindow // Using mgrPrefs.resetSpecifiedUserDataFolder(). --- Source/WindowControllers/ctlPrefWindow.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/WindowControllers/ctlPrefWindow.swift b/Source/WindowControllers/ctlPrefWindow.swift index 2fb201be..5c3fd242 100644 --- a/Source/WindowControllers/ctlPrefWindow.swift +++ b/Source/WindowControllers/ctlPrefWindow.swift @@ -248,8 +248,7 @@ import Cocoa } @IBAction func resetSpecifiedUserDataFolder(_ sender: Any) { - UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified") - IME.initLangModels(userOnly: true) + mgrPrefs.resetSpecifiedUserDataFolder() } @IBAction func chooseUserDataFolderToSpecify(_ sender: Any) { @@ -275,14 +274,14 @@ import Cocoa } else { clsSFX.beep() if !bolPreviousFolderValidity { - self.resetSpecifiedUserDataFolder(self) + mgrPrefs.resetSpecifiedUserDataFolder() } return } } } else { if !bolPreviousFolderValidity { - self.resetSpecifiedUserDataFolder(self) + mgrPrefs.resetSpecifiedUserDataFolder() } return } From e1c934d272924e63d66105230143a042827d8536 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 9 Apr 2022 14:56:53 +0800 Subject: [PATCH 24/36] ctlPrefWindow // Append trailing slash to the path read by ComDlg32. - Already patched in the incoming Dictionary pane of the PrefUI. --- Source/WindowControllers/ctlPrefWindow.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/WindowControllers/ctlPrefWindow.swift b/Source/WindowControllers/ctlPrefWindow.swift index 5c3fd242..d4489122 100644 --- a/Source/WindowControllers/ctlPrefWindow.swift +++ b/Source/WindowControllers/ctlPrefWindow.swift @@ -266,10 +266,12 @@ import Cocoa IME.dlgOpenPath.beginSheetModal(for: self.window!) { result in if result == NSApplication.ModalResponse.OK { if IME.dlgOpenPath.url != nil { - if mgrLangModel.checkIfSpecifiedUserDataFolderValid( - IME.dlgOpenPath.url!.path) - { - mgrPrefs.userDataFolderSpecified = IME.dlgOpenPath.url!.path + // CommonDialog 讀入的路徑沒有結尾斜槓,這會導致檔案目錄合規性判定失準。 + // 所以要手動補回來。 + var newPath = IME.dlgOpenPath.url!.path + newPath.ensureTrailingSlash() + if mgrLangModel.checkIfSpecifiedUserDataFolderValid(newPath) { + mgrPrefs.userDataFolderSpecified = newPath IME.initLangModels(userOnly: true) } else { clsSFX.beep() From 1c3ea4cf466ce68b24165efa57a7f778488c0262 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 11 Apr 2022 08:21:47 +0800 Subject: [PATCH 25/36] ctlPrefWindow // SwiftLint. --- Source/WindowControllers/ctlPrefWindow.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/WindowControllers/ctlPrefWindow.swift b/Source/WindowControllers/ctlPrefWindow.swift index d4489122..29381fc4 100644 --- a/Source/WindowControllers/ctlPrefWindow.swift +++ b/Source/WindowControllers/ctlPrefWindow.swift @@ -39,7 +39,7 @@ import Cocoa @IBOutlet weak var chkTrad2JISShinjitai: NSButton! @IBOutlet weak var lblCurrentlySpecifiedUserDataFolder: NSTextFieldCell! - var currentLanguageSelectItem: NSMenuItem? = nil + var currentLanguageSelectItem: NSMenuItem? override func windowDidLoad() { super.windowDidLoad() @@ -48,8 +48,8 @@ import Cocoa isDefaultFolder: true) let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"] - var autoMUISelectItem: NSMenuItem? = nil - var chosenLanguageItem: NSMenuItem? = nil + var autoMUISelectItem: NSMenuItem? + var chosenLanguageItem: NSMenuItem? uiLanguageButton.menu?.removeAllItems() let appleLanguages = mgrPrefs.appleLanguages @@ -74,8 +74,8 @@ import Cocoa uiLanguageButton.select(currentLanguageSelectItem) let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] - var usKeyboardLayoutItem: NSMenuItem? = nil - var chosenBaseKeyboardLayoutItem: NSMenuItem? = nil + var usKeyboardLayoutItem: NSMenuItem? + var chosenBaseKeyboardLayoutItem: NSMenuItem? basicKeyboardLayoutButton.menu?.removeAllItems() @@ -239,7 +239,7 @@ import Cocoa } catch { if let window = window { let alert = NSAlert(error: error) - alert.beginSheetModal(for: window) { response in + alert.beginSheetModal(for: window) { _ in self.selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys } clsSFX.beep() From 94de281acad390a257d400ffb0c21b170021dd60 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 10 Apr 2022 12:32:16 +0800 Subject: [PATCH 26/36] ctlPrefWindow // Whitelist the keyLayouts. --- Source/WindowControllers/ctlPrefWindow.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/WindowControllers/ctlPrefWindow.swift b/Source/WindowControllers/ctlPrefWindow.swift index 29381fc4..96d8f19f 100644 --- a/Source/WindowControllers/ctlPrefWindow.swift +++ b/Source/WindowControllers/ctlPrefWindow.swift @@ -145,7 +145,9 @@ import Cocoa if basicKeyboardLayoutID == sourceID { chosenBaseKeyboardLayoutItem = menuItem } - basicKeyboardLayoutButton.menu?.addItem(menuItem) + if IME.arrWhitelistedKeyLayoutsASCII.contains(sourceID) || sourceID.contains("vChewing") { + basicKeyboardLayoutButton.menu?.addItem(menuItem) + } } switch basicKeyboardLayoutID { From 597ec55fc37b3c23b4bb4e3d105ea531c41c1bf5 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 7 Apr 2022 16:35:32 +0800 Subject: [PATCH 27/36] Xcode // Add SindreSorhus's Preferences module. --- .../SindreSorhus/Preferences/Container.swift | 103 +++++++ .../Preferences/Localization.swift | 155 +++++++++++ .../SindreSorhus/Preferences/Pane.swift | 112 ++++++++ .../Preferences/PreferencePane.swift | 59 ++++ .../Preferences/Preferences.swift | 22 ++ .../Preferences/PreferencesStyle.swift | 28 ++ .../PreferencesStyleController.swift | 35 +++ .../PreferencesTabViewController.swift | 258 ++++++++++++++++++ .../PreferencesWindowController.swift | 188 +++++++++++++ .../SindreSorhus/Preferences/Section.swift | 138 ++++++++++ .../SegmentedControlStyleViewController.swift | 159 +++++++++++ .../ToolbarItemStyleViewController.swift | 77 ++++++ .../SindreSorhus/Preferences/Utilities.swift | 136 +++++++++ vChewing.xcodeproj/project.pbxproj | 69 +++++ 14 files changed, 1539 insertions(+) create mode 100755 Source/3rdParty/SindreSorhus/Preferences/Container.swift create mode 100755 Source/3rdParty/SindreSorhus/Preferences/Localization.swift create mode 100755 Source/3rdParty/SindreSorhus/Preferences/Pane.swift create mode 100755 Source/3rdParty/SindreSorhus/Preferences/PreferencePane.swift create mode 100755 Source/3rdParty/SindreSorhus/Preferences/Preferences.swift create mode 100755 Source/3rdParty/SindreSorhus/Preferences/PreferencesStyle.swift create mode 100755 Source/3rdParty/SindreSorhus/Preferences/PreferencesStyleController.swift create mode 100755 Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift create mode 100755 Source/3rdParty/SindreSorhus/Preferences/PreferencesWindowController.swift create mode 100755 Source/3rdParty/SindreSorhus/Preferences/Section.swift create mode 100755 Source/3rdParty/SindreSorhus/Preferences/SegmentedControlStyleViewController.swift create mode 100755 Source/3rdParty/SindreSorhus/Preferences/ToolbarItemStyleViewController.swift create mode 100755 Source/3rdParty/SindreSorhus/Preferences/Utilities.swift diff --git a/Source/3rdParty/SindreSorhus/Preferences/Container.swift b/Source/3rdParty/SindreSorhus/Preferences/Container.swift new file mode 100755 index 00000000..7ccba756 --- /dev/null +++ b/Source/3rdParty/SindreSorhus/Preferences/Container.swift @@ -0,0 +1,103 @@ +// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import SwiftUI + +@available(macOS 10.15, *) +extension Preferences { + /** + Function builder for `Preferences` components used in order to restrict types of child views to be of type `Section`. + */ + @resultBuilder + public struct SectionBuilder { + public static func buildBlock(_ sections: Section...) -> [Section] { + sections + } + } + + /** + A view which holds `Preferences.Section` views and does all the alignment magic similar to `NSGridView` from AppKit. + */ + public struct Container: View { + private let sectionBuilder: () -> [Section] + private let contentWidth: Double + private let minimumLabelWidth: Double + @State private var maximumLabelWidth = 0.0 + + /** + Creates an instance of container component, which handles layout of stacked `Preferences.Section` views. + + Custom alignment requires content width to be specified beforehand. + + - Parameters: + - contentWidth: A fixed width of the container's content (excluding paddings). + - minimumLabelWidth: A minimum width for labels within this container. By default, it will fit to the largest label. + - builder: A view builder that creates `Preferences.Section`'s of this container. + */ + public init( + contentWidth: Double, + minimumLabelWidth: Double = 0, + @SectionBuilder builder: @escaping () -> [Section] + ) { + self.sectionBuilder = builder + self.contentWidth = contentWidth + self.minimumLabelWidth = minimumLabelWidth + } + + public var body: some View { + let sections = sectionBuilder() + + return VStack(alignment: .preferenceSectionLabel) { + ForEach(0.. some View { + sections[index] + 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) + .alignmentGuide(.preferenceSectionLabel) { + $0[.leading] + CGFloat(max(minimumLabelWidth, maximumLabelWidth)) + } + } + } + } +} + +/// Extension with custom alignment guide for section title labels. +@available(macOS 10.15, *) +extension HorizontalAlignment { + private enum PreferenceSectionLabelAlignment: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context[HorizontalAlignment.leading] + } + } + + static let preferenceSectionLabel = HorizontalAlignment(PreferenceSectionLabelAlignment.self) +} diff --git a/Source/3rdParty/SindreSorhus/Preferences/Localization.swift b/Source/3rdParty/SindreSorhus/Preferences/Localization.swift new file mode 100755 index 00000000..9d19e190 --- /dev/null +++ b/Source/3rdParty/SindreSorhus/Preferences/Localization.swift @@ -0,0 +1,155 @@ +// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Foundation + +struct Localization { + enum Identifier { + case preferences + case preferencesEllipsized + } + + private static let localizedStrings: [Identifier: [String: String]] = [ + .preferences: [ + "ar": "تفضيلات", + "ca": "Preferències", + "cs": "Předvolby", + "da": "Indstillinger", + "de": "Einstellungen", + "el": "Προτιμήσεις", + "en": "Preferences", + "en-AU": "Preferences", + "en-GB": "Preferences", + "es": "Preferencias", + "es-419": "Preferencias", + "fi": "Asetukset", + "fr": "Préférences", + "fr-CA": "Préférences", + "he": "העדפות", + "hi": "प्राथमिकता", + "hr": "Postavke", + "hu": "Beállítások", + "id": "Preferensi", + "it": "Preferenze", + "ja": "環境設定", + "ko": "환경설정", + "ms": "Keutamaan", + "nl": "Voorkeuren", + "no": "Valg", + "pl": "Preferencje", + "pt": "Preferências", + "pt-PT": "Preferências", + "ro": "Preferințe", + "ru": "Настройки", + "sk": "Nastavenia", + "sv": "Inställningar", + "th": "การตั้งค่า", + "tr": "Tercihler", + "uk": "Параметри", + "vi": "Tùy chọn", + "zh-CN": "偏好设置", + "zh-HK": "偏好設定", + "zh-TW": "偏好設定", + ], + .preferencesEllipsized: [ + "ar": "تفضيلات…", + "ca": "Preferències…", + "cs": "Předvolby…", + "da": "Indstillinger…", + "de": "Einstellungen…", + "el": "Προτιμήσεις…", + "en": "Preferences…", + "en-AU": "Preferences…", + "en-GB": "Preferences…", + "es": "Preferencias…", + "es-419": "Preferencias…", + "fi": "Asetukset…", + "fr": "Préférences…", + "fr-CA": "Préférences…", + "he": "העדפות…", + "hi": "प्राथमिकता…", + "hr": "Postavke…", + "hu": "Beállítások…", + "id": "Preferensi…", + "it": "Preferenze…", + "ja": "環境設定…", + "ko": "환경설정...", + "ms": "Keutamaan…", + "nl": "Voorkeuren…", + "no": "Valg…", + "pl": "Preferencje…", + "pt": "Preferências…", + "pt-PT": "Preferências…", + "ro": "Preferințe…", + "ru": "Настройки…", + "sk": "Nastavenia…", + "sv": "Inställningar…", + "th": "การตั้งค่า…", + "tr": "Tercihler…", + "uk": "Параметри…", + "vi": "Tùy chọn…", + "zh-CN": "偏好设置…", + "zh-HK": "偏好設定⋯", + "zh-TW": "偏好設定⋯", + ], + ] + + /** + Returns the localized version of the given string. + + - Parameter identifier: Identifier of the string to localize. + + - Note: If the system's locale can't be determined, the English localization of the string will be returned. + */ + static subscript(identifier: Identifier) -> String { + // Force-unwrapped since all of the involved code is under our control. + let localizedDict = Localization.localizedStrings[identifier]! + let defaultLocalizedString = localizedDict["en"]! + + // Iterate through all user-preferred languages until we find one that has a valid language code. + let preferredLocale = + Locale.preferredLanguages + .lazy + .map { Locale(identifier: $0) } + .first { $0.languageCode != nil } + ?? .current + + guard let languageCode = preferredLocale.languageCode else { + return defaultLocalizedString + } + + // Chinese is the only language where different region codes result in different translations. + if languageCode == "zh" { + let regionCode = preferredLocale.regionCode ?? "" + if regionCode == "HK" || regionCode == "TW" { + return localizedDict["\(languageCode)-\(regionCode)"]! + } else { + // Fall back to "regular" zh-CN if neither the HK or TW region codes are found. + return localizedDict["\(languageCode)-CN"]! + } + } else { + if let localizedString = localizedDict[languageCode] { + return localizedString + } + } + + return defaultLocalizedString + } +} diff --git a/Source/3rdParty/SindreSorhus/Preferences/Pane.swift b/Source/3rdParty/SindreSorhus/Preferences/Pane.swift new file mode 100755 index 00000000..31f5e391 --- /dev/null +++ b/Source/3rdParty/SindreSorhus/Preferences/Pane.swift @@ -0,0 +1,112 @@ +// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import SwiftUI + +/// Represents a type that can be converted to `PreferencePane`. +/// +/// Acts as type-eraser for `Preferences.Pane`. +public protocol PreferencePaneConvertible { + /** + Convert `self` to equivalent `PreferencePane`. + */ + func asPreferencePane() -> PreferencePane +} + +@available(macOS 10.15, *) +extension Preferences { + /** + Create a SwiftUI-based preference pane. + + SwiftUI equivalent of the `PreferencePane` protocol. + */ + public struct Pane: View, PreferencePaneConvertible { + let identifier: PaneIdentifier + let title: String + let toolbarIcon: NSImage + let content: Content + + public init( + identifier: PaneIdentifier, + title: String, + toolbarIcon: NSImage, + contentView: () -> Content + ) { + self.identifier = identifier + self.title = title + self.toolbarIcon = toolbarIcon + self.content = contentView() + } + + public var body: some View { content } + + public func asPreferencePane() -> PreferencePane { + PaneHostingController(pane: self) + } + } + + /** + Hosting controller enabling `Preferences.Pane` to be used alongside AppKit `NSViewController`'s. + */ + public final class PaneHostingController: NSHostingController, PreferencePane { + public let preferencePaneIdentifier: PaneIdentifier + public let preferencePaneTitle: String + public let toolbarItemIcon: NSImage + + init( + identifier: PaneIdentifier, + title: String, + toolbarIcon: NSImage, + content: Content + ) { + self.preferencePaneIdentifier = identifier + self.preferencePaneTitle = title + self.toolbarItemIcon = toolbarIcon + super.init(rootView: content) + } + + public convenience init(pane: Pane) { + self.init( + identifier: pane.identifier, + title: pane.title, + toolbarIcon: pane.toolbarIcon, + content: pane.content + ) + } + + @available(*, unavailable) + @objc + dynamic required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } +} + +@available(macOS 10.15, *) +extension View { + /** + Applies font and color for a label used for describing a preference. + */ + public func preferenceDescription() -> some View { + font(.system(size: 11.0)) + // TODO: Use `.foregroundStyle` when targeting macOS 12. + .foregroundColor(.secondary) + } +} diff --git a/Source/3rdParty/SindreSorhus/Preferences/PreferencePane.swift b/Source/3rdParty/SindreSorhus/Preferences/PreferencePane.swift new file mode 100755 index 00000000..a62a0743 --- /dev/null +++ b/Source/3rdParty/SindreSorhus/Preferences/PreferencePane.swift @@ -0,0 +1,59 @@ +// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa + +extension Preferences { + public struct PaneIdentifier: Hashable, RawRepresentable, Codable { + public let rawValue: String + + public init(rawValue: String) { + self.rawValue = rawValue + } + } +} + +public protocol PreferencePane: NSViewController { + var preferencePaneIdentifier: Preferences.PaneIdentifier { get } + var preferencePaneTitle: String { get } + var toolbarItemIcon: NSImage { get } +} + +extension PreferencePane { + public var toolbarItemIdentifier: NSToolbarItem.Identifier { + preferencePaneIdentifier.toolbarItemIdentifier + } + + public var toolbarItemIcon: NSImage { .empty } +} + +extension Preferences.PaneIdentifier { + public init(_ rawValue: String) { + self.init(rawValue: rawValue) + } + + public init(fromToolbarItemIdentifier itemIdentifier: NSToolbarItem.Identifier) { + self.init(rawValue: itemIdentifier.rawValue) + } + + public var toolbarItemIdentifier: NSToolbarItem.Identifier { + NSToolbarItem.Identifier(rawValue) + } +} diff --git a/Source/3rdParty/SindreSorhus/Preferences/Preferences.swift b/Source/3rdParty/SindreSorhus/Preferences/Preferences.swift new file mode 100755 index 00000000..de297837 --- /dev/null +++ b/Source/3rdParty/SindreSorhus/Preferences/Preferences.swift @@ -0,0 +1,22 @@ +// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +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. +*/ + +/// The namespace for this package. +public enum Preferences {} diff --git a/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyle.swift b/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyle.swift new file mode 100755 index 00000000..c8c7130c --- /dev/null +++ b/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyle.swift @@ -0,0 +1,28 @@ +// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa + +extension Preferences { + public enum Style { + case toolbarItems + case segmentedControl + } +} diff --git a/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyleController.swift b/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyleController.swift new file mode 100755 index 00000000..b696a53d --- /dev/null +++ b/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyleController.swift @@ -0,0 +1,35 @@ +// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa + +protocol PreferencesStyleController: AnyObject { + var delegate: PreferencesStyleControllerDelegate? { get set } + var isKeepingWindowCentered: Bool { get } + + func toolbarItemIdentifiers() -> [NSToolbarItem.Identifier] + func toolbarItem(preferenceIdentifier: Preferences.PaneIdentifier) -> NSToolbarItem? + func selectTab(index: Int) +} + +protocol PreferencesStyleControllerDelegate: AnyObject { + func activateTab(preferenceIdentifier: Preferences.PaneIdentifier, animated: Bool) + func activateTab(index: Int, animated: Bool) +} diff --git a/Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift b/Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift new file mode 100755 index 00000000..8d7a9f43 --- /dev/null +++ b/Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift @@ -0,0 +1,258 @@ +// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa + +final class PreferencesTabViewController: NSViewController, PreferencesStyleControllerDelegate { + private var activeTab: Int? + private var preferencePanes = [PreferencePane]() + private var style: Preferences.Style? + internal var preferencePanesCount: Int { preferencePanes.count } + private var preferencesStyleController: PreferencesStyleController! + private var isKeepingWindowCentered: Bool { preferencesStyleController.isKeepingWindowCentered } + + private var toolbarItemIdentifiers: [NSToolbarItem.Identifier] { + preferencesStyleController?.toolbarItemIdentifiers() ?? [] + } + + var window: NSWindow! { view.window } + + var isAnimated = true + + var activeViewController: NSViewController? { + guard let activeTab = activeTab else { + return nil + } + + return preferencePanes[activeTab] + } + + override func loadView() { + view = NSView() + view.translatesAutoresizingMaskIntoConstraints = false + } + + func configure(preferencePanes: [PreferencePane], style: Preferences.Style) { + self.preferencePanes = preferencePanes + self.style = style + children = preferencePanes + + let toolbar = NSToolbar(identifier: "PreferencesToolbar") + toolbar.allowsUserCustomization = false + toolbar.displayMode = .iconAndLabel + toolbar.showsBaselineSeparator = true + toolbar.delegate = self + + switch style { + case .segmentedControl: + preferencesStyleController = SegmentedControlStyleViewController(preferencePanes: preferencePanes) + case .toolbarItems: + preferencesStyleController = ToolbarItemStyleViewController( + preferencePanes: preferencePanes, + toolbar: toolbar, + centerToolbarItems: false + ) + } + preferencesStyleController.delegate = self + + // Called last so that `preferencesStyleController` can be asked for items. + window.toolbar = toolbar + } + + func activateTab(preferencePane: PreferencePane, animated: Bool) { + activateTab(preferenceIdentifier: preferencePane.preferencePaneIdentifier, animated: animated) + } + + func activateTab(preferenceIdentifier: Preferences.PaneIdentifier, animated: Bool) { + guard let index = (preferencePanes.firstIndex { $0.preferencePaneIdentifier == preferenceIdentifier }) else { + return activateTab(index: 0, animated: animated) + } + + activateTab(index: index, animated: animated) + } + + func activateTab(index: Int, animated: Bool) { + defer { + activeTab = index + preferencesStyleController.selectTab(index: index) + updateWindowTitle(tabIndex: index) + } + + if activeTab == nil { + immediatelyDisplayTab(index: index) + } else { + guard index != activeTab else { + return + } + + animateTabTransition(index: index, animated: animated) + } + } + + func restoreInitialTab() { + if activeTab == nil { + activateTab(index: 0, animated: false) + } + } + + private func updateWindowTitle(tabIndex: Int) { + window.title = { + if preferencePanes.count > 1 { + return preferencePanes[tabIndex].preferencePaneTitle + } else { + let preferences = Localization[.preferences] + let appName = Bundle.main.appName + return "\(appName) \(preferences)" + } + }() + } + + /// Cached constraints that pin `childViewController` views to the content view. + private var activeChildViewConstraints = [NSLayoutConstraint]() + + private func immediatelyDisplayTab(index: Int) { + let toViewController = preferencePanes[index] + view.addSubview(toViewController.view) + activeChildViewConstraints = toViewController.view.constrainToSuperviewBounds() + setWindowFrame(for: toViewController, animated: false) + } + + private func animateTabTransition(index: Int, animated: Bool) { + guard let activeTab = activeTab else { + assertionFailure( + "animateTabTransition called before a tab was displayed; transition only works from one tab to another") + immediatelyDisplayTab(index: index) + return + } + + let fromViewController = preferencePanes[activeTab] + let toViewController = preferencePanes[index] + + // View controller animations only work on macOS 10.14 and newer. + let options: NSViewController.TransitionOptions + if #available(macOS 10.14, *) { + options = animated && isAnimated ? [.crossfade] : [] + } else { + options = [] + } + + view.removeConstraints(activeChildViewConstraints) + + transition( + from: fromViewController, + to: toViewController, + options: options + ) { [self] in + activeChildViewConstraints = toViewController.view.constrainToSuperviewBounds() + } + } + + override func transition( + from fromViewController: NSViewController, + to toViewController: NSViewController, + options: NSViewController.TransitionOptions = [], + completionHandler completion: (() -> Void)? = nil + ) { + let isAnimated = + options + .intersection([ + .crossfade, + .slideUp, + .slideDown, + .slideForward, + .slideBackward, + .slideLeft, + .slideRight, + ]) + .isEmpty == false + + if isAnimated { + NSAnimationContext.runAnimationGroup( + { context in + context.allowsImplicitAnimation = true + context.duration = 0.25 + context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + setWindowFrame(for: toViewController, animated: true) + + super.transition( + from: fromViewController, + to: toViewController, + options: options, + completionHandler: completion + ) + }, completionHandler: nil) + } else { + super.transition( + from: fromViewController, + to: toViewController, + options: options, + completionHandler: completion + ) + } + } + + private func setWindowFrame(for viewController: NSViewController, animated: Bool = false) { + guard let window = window else { + preconditionFailure() + } + + let contentSize = viewController.view.fittingSize + + let newWindowSize = window.frameRect(forContentRect: CGRect(origin: .zero, size: contentSize)).size + var frame = window.frame + frame.origin.y += frame.height - newWindowSize.height + frame.size = newWindowSize + + if isKeepingWindowCentered { + let horizontalDiff = (window.frame.width - newWindowSize.width) / 2 + frame.origin.x += horizontalDiff + } + + let animatableWindow = animated ? window.animator() : window + animatableWindow.setFrame(frame, display: false) + } +} + +extension PreferencesTabViewController: NSToolbarDelegate { + func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { + toolbarItemIdentifiers + } + + func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { + toolbarItemIdentifiers + } + + func toolbarSelectableItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { + style == .segmentedControl ? [] : toolbarItemIdentifiers + } + + public func toolbar( + _ toolbar: NSToolbar, + itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, + willBeInsertedIntoToolbar flag: Bool + ) -> NSToolbarItem? { + if itemIdentifier == .flexibleSpace { + return nil + } + + return preferencesStyleController.toolbarItem( + preferenceIdentifier: Preferences.PaneIdentifier(fromToolbarItemIdentifier: itemIdentifier)) + } +} diff --git a/Source/3rdParty/SindreSorhus/Preferences/PreferencesWindowController.swift b/Source/3rdParty/SindreSorhus/Preferences/PreferencesWindowController.swift new file mode 100755 index 00000000..2ada7810 --- /dev/null +++ b/Source/3rdParty/SindreSorhus/Preferences/PreferencesWindowController.swift @@ -0,0 +1,188 @@ +// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa + +extension NSWindow.FrameAutosaveName { + static let preferences: NSWindow.FrameAutosaveName = "com.sindresorhus.Preferences.FrameAutosaveName" +} + +public final class PreferencesWindowController: NSWindowController { + private let tabViewController = PreferencesTabViewController() + + public var isAnimated: Bool { + get { tabViewController.isAnimated } + set { + tabViewController.isAnimated = newValue + } + } + + public var hidesToolbarForSingleItem: Bool { + didSet { + updateToolbarVisibility() + } + } + + private func updateToolbarVisibility() { + window?.toolbar?.isVisible = + (hidesToolbarForSingleItem == false) + || (tabViewController.preferencePanesCount > 1) + } + + public init( + preferencePanes: [PreferencePane], + style: Preferences.Style = .toolbarItems, + animated: Bool = true, + hidesToolbarForSingleItem: Bool = true + ) { + precondition(!preferencePanes.isEmpty, "You need to set at least one view controller") + + let window = UserInteractionPausableWindow( + contentRect: preferencePanes[0].view.bounds, + styleMask: [ + .titled, + .closable, + ], + backing: .buffered, + defer: true + ) + self.hidesToolbarForSingleItem = hidesToolbarForSingleItem + super.init(window: window) + + window.contentViewController = tabViewController + + window.titleVisibility = { + switch style { + case .toolbarItems: + return .visible + case .segmentedControl: + return preferencePanes.count <= 1 ? .visible : .hidden + } + }() + + if #available(macOS 11.0, *), style == .toolbarItems { + window.toolbarStyle = .preference + } + + tabViewController.isAnimated = animated + tabViewController.configure(preferencePanes: preferencePanes, style: style) + updateToolbarVisibility() + } + + @available(*, unavailable) + override public init(window: NSWindow?) { + fatalError("init(window:) is not supported, use init(preferences:style:animated:)") + } + + @available(*, unavailable) + public required init?(coder: NSCoder) { + fatalError("init(coder:) is not supported, use init(preferences:style:animated:)") + } + + /** + Show the preferences window and brings it to front. + + If you pass a `Preferences.PaneIdentifier`, the window will activate the corresponding tab. + + - Parameter preferencePane: Identifier of the preference pane to display, or `nil` to show the tab that was open when the user last closed the window. + + - Note: Unless you need to open a specific pane, prefer not to pass a parameter at all or `nil`. + + - See `close()` to close the window again. + - See `showWindow(_:)` to show the window without the convenience of activating the app. + */ + public func show(preferencePane preferenceIdentifier: Preferences.PaneIdentifier? = nil) { + if let preferenceIdentifier = preferenceIdentifier { + tabViewController.activateTab(preferenceIdentifier: preferenceIdentifier, animated: false) + } else { + tabViewController.restoreInitialTab() + } + + showWindow(self) + restoreWindowPosition() + NSApp.activate(ignoringOtherApps: true) + } + + private func restoreWindowPosition() { + guard + let window = window, + let screenContainingWindow = window.screen + else { + return + } + + window.setFrameOrigin( + CGPoint( + x: screenContainingWindow.visibleFrame.midX - window.frame.width / 2, + y: screenContainingWindow.visibleFrame.midY - window.frame.height / 2 + )) + window.setFrameUsingName(.preferences) + window.setFrameAutosaveName(.preferences) + } +} + +extension PreferencesWindowController { + /// Returns the active pane if it responds to the given action. + override public func supplementalTarget(forAction action: Selector, sender: Any?) -> Any? { + if let target = super.supplementalTarget(forAction: action, sender: sender) { + return target + } + + guard let activeViewController = tabViewController.activeViewController else { + return nil + } + + if let target = NSApp.target(forAction: action, to: activeViewController, from: sender) as? NSResponder, + target.responds(to: action) + { + return target + } + + if let target = activeViewController.supplementalTarget(forAction: action, sender: sender) as? NSResponder, + target.responds(to: action) + { + return target + } + + return nil + } +} + +@available(macOS 10.15, *) +extension PreferencesWindowController { + /** + Create a preferences window from only SwiftUI-based preference panes. + */ + public convenience init( + panes: [PreferencePaneConvertible], + style: Preferences.Style = .toolbarItems, + animated: Bool = true, + hidesToolbarForSingleItem: Bool = true + ) { + let preferencePanes = panes.map { $0.asPreferencePane() } + + self.init( + preferencePanes: preferencePanes, + style: style, + animated: animated, + hidesToolbarForSingleItem: hidesToolbarForSingleItem + ) + } +} diff --git a/Source/3rdParty/SindreSorhus/Preferences/Section.swift b/Source/3rdParty/SindreSorhus/Preferences/Section.swift new file mode 100755 index 00000000..15ad0400 --- /dev/null +++ b/Source/3rdParty/SindreSorhus/Preferences/Section.swift @@ -0,0 +1,138 @@ +// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import SwiftUI + +@available(macOS 10.15, *) +extension Preferences { + /** + Represents a section with right-aligned title and optional bottom divider. + */ + @available(macOS 10.15, *) + public struct Section: View { + /** + Preference key holding max width of section labels. + */ + private struct LabelWidthPreferenceKey: PreferenceKey { + typealias Value = Double + + static var defaultValue = 0.0 + + static func reduce(value: inout Double, nextValue: () -> Double) { + let next = nextValue() + value = next > value ? next : value + } + } + + /** + Convenience overlay for finding a label's dimensions using `GeometryReader`. + */ + private struct LabelOverlay: View { + var body: some View { + GeometryReader { geometry in + Color.clear + .preference(key: LabelWidthPreferenceKey.self, value: Double(geometry.size.width)) + } + } + } + + /** + Convenience modifier for applying `LabelWidthPreferenceKey`. + */ + struct LabelWidthModifier: ViewModifier { + @Binding var maximumWidth: Double + + func body(content: Content) -> some View { + content + .onPreferenceChange(LabelWidthPreferenceKey.self) { newMaximumWidth in + maximumWidth = Double(newMaximumWidth) + } + } + } + + public let label: AnyView + public let content: AnyView + public let bottomDivider: Bool + public let verticalAlignment: VerticalAlignment + + /** + A section is responsible for controlling a single preference. + + - Parameters: + - bottomDivider: Whether to place a `Divider` after the section content. Default is `false`. + - verticalAlignement: The vertical alignment of the section content. + - label: A view describing preference handled by this section. + - content: A content view. + */ + public init( + bottomDivider: Bool = false, + verticalAlignment: VerticalAlignment = .firstTextBaseline, + label: @escaping () -> Label, + @ViewBuilder content: @escaping () -> Content + ) { + self.label = label() + .overlay(LabelOverlay()) + .eraseToAnyView() // TODO: Remove use of `AnyView`. + self.bottomDivider = bottomDivider + self.verticalAlignment = verticalAlignment + let stack = VStack(alignment: .leading) { content() } + self.content = stack.eraseToAnyView() + } + + /** + Creates instance of section, responsible for controling single preference with `Text` as a `Label`. + + - Parameters: + - title: A string describing preference handled by this section. + - bottomDivider: Whether to place a `Divider` after the section content. Default is `false`. + - verticalAlignement: The vertical alignment of the section content. + - content: A content view. + */ + public init( + title: String, + bottomDivider: Bool = false, + verticalAlignment: VerticalAlignment = .firstTextBaseline, + @ViewBuilder content: @escaping () -> Content + ) { + let textLabel = { + Text(title) + .font(.system(size: 13.0)) + .overlay(LabelOverlay()) + .eraseToAnyView() + } + + self.init( + bottomDivider: bottomDivider, + verticalAlignment: verticalAlignment, + label: textLabel, + content: content + ) + } + + public var body: some View { + HStack(alignment: verticalAlignment) { + label + .alignmentGuide(.preferenceSectionLabel) { $0[.trailing] } + content + Spacer() + } + } + } +} diff --git a/Source/3rdParty/SindreSorhus/Preferences/SegmentedControlStyleViewController.swift b/Source/3rdParty/SindreSorhus/Preferences/SegmentedControlStyleViewController.swift new file mode 100755 index 00000000..d16bd748 --- /dev/null +++ b/Source/3rdParty/SindreSorhus/Preferences/SegmentedControlStyleViewController.swift @@ -0,0 +1,159 @@ +// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa + +extension NSToolbarItem.Identifier { + static let toolbarSegmentedControlItem = Self("toolbarSegmentedControlItem") +} + +extension NSUserInterfaceItemIdentifier { + static let toolbarSegmentedControl = Self("toolbarSegmentedControl") +} + +final class SegmentedControlStyleViewController: NSViewController, PreferencesStyleController { + var segmentedControl: NSSegmentedControl! { + get { view as? NSSegmentedControl } + set { + view = newValue + } + } + + var isKeepingWindowCentered: Bool { true } + + weak var delegate: PreferencesStyleControllerDelegate? + + private var preferencePanes: [PreferencePane]! + + required init(preferencePanes: [PreferencePane]) { + super.init(nibName: nil, bundle: nil) + self.preferencePanes = preferencePanes + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = createSegmentedControl(preferencePanes: preferencePanes) + } + + fileprivate func createSegmentedControl(preferencePanes: [PreferencePane]) -> NSSegmentedControl { + let segmentedControl = NSSegmentedControl() + segmentedControl.segmentCount = preferencePanes.count + segmentedControl.segmentStyle = .texturedSquare + segmentedControl.target = self + segmentedControl.action = #selector(segmentedControlAction) + segmentedControl.identifier = .toolbarSegmentedControl + + if let cell = segmentedControl.cell as? NSSegmentedCell { + cell.controlSize = .regular + cell.trackingMode = .selectOne + } + + let segmentSize: CGSize = { + let insets = CGSize(width: 36, height: 12) + var maxSize = CGSize.zero + + for preferencePane in preferencePanes { + let title = preferencePane.preferencePaneTitle + let titleSize = title.size( + withAttributes: [ + .font: NSFont.systemFont(ofSize: NSFont.systemFontSize(for: .regular)) + ] + ) + + maxSize = CGSize( + width: max(titleSize.width, maxSize.width), + height: max(titleSize.height, maxSize.height) + ) + } + + return CGSize( + width: maxSize.width + insets.width, + height: maxSize.height + insets.height + ) + }() + + let segmentBorderWidth = CGFloat(preferencePanes.count) + 1 + let segmentWidth = segmentSize.width * CGFloat(preferencePanes.count) + segmentBorderWidth + let segmentHeight = segmentSize.height + segmentedControl.frame = CGRect(x: 0, y: 0, width: segmentWidth, height: segmentHeight) + + for (index, preferencePane) in preferencePanes.enumerated() { + segmentedControl.setLabel(preferencePane.preferencePaneTitle, forSegment: index) + segmentedControl.setWidth(segmentSize.width, forSegment: index) + if let cell = segmentedControl.cell as? NSSegmentedCell { + cell.setTag(index, forSegment: index) + } + } + + return segmentedControl + } + + @IBAction private func segmentedControlAction(_ control: NSSegmentedControl) { + delegate?.activateTab(index: control.selectedSegment, animated: true) + } + + func selectTab(index: Int) { + segmentedControl.selectedSegment = index + } + + func toolbarItemIdentifiers() -> [NSToolbarItem.Identifier] { + [ + .flexibleSpace, + .toolbarSegmentedControlItem, + .flexibleSpace, + ] + } + + func toolbarItem(preferenceIdentifier: Preferences.PaneIdentifier) -> NSToolbarItem? { + let toolbarItemIdentifier = preferenceIdentifier.toolbarItemIdentifier + precondition(toolbarItemIdentifier == .toolbarSegmentedControlItem) + + // When the segments outgrow the window, we need to provide a group of + // NSToolbarItems with custom menu item labels and action handling for the + // context menu that pops up at the right edge of the window. + let toolbarItemGroup = NSToolbarItemGroup(itemIdentifier: toolbarItemIdentifier) + toolbarItemGroup.view = segmentedControl + toolbarItemGroup.subitems = preferencePanes.enumerated().map { index, preferenceable -> NSToolbarItem in + let item = NSToolbarItem(itemIdentifier: .init("segment-\(preferenceable.preferencePaneTitle)")) + item.label = preferenceable.preferencePaneTitle + + let menuItem = NSMenuItem( + title: preferenceable.preferencePaneTitle, + action: #selector(segmentedControlMenuAction), + keyEquivalent: "" + ) + menuItem.tag = index + menuItem.target = self + item.menuFormRepresentation = menuItem + + return item + } + + return toolbarItemGroup + } + + @IBAction private func segmentedControlMenuAction(_ menuItem: NSMenuItem) { + delegate?.activateTab(index: menuItem.tag, animated: true) + } +} diff --git a/Source/3rdParty/SindreSorhus/Preferences/ToolbarItemStyleViewController.swift b/Source/3rdParty/SindreSorhus/Preferences/ToolbarItemStyleViewController.swift new file mode 100755 index 00000000..34cdf5c1 --- /dev/null +++ b/Source/3rdParty/SindreSorhus/Preferences/ToolbarItemStyleViewController.swift @@ -0,0 +1,77 @@ +// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa + +final class ToolbarItemStyleViewController: NSObject, PreferencesStyleController { + let toolbar: NSToolbar + let centerToolbarItems: Bool + let preferencePanes: [PreferencePane] + var isKeepingWindowCentered: Bool { centerToolbarItems } + weak var delegate: PreferencesStyleControllerDelegate? + + init(preferencePanes: [PreferencePane], toolbar: NSToolbar, centerToolbarItems: Bool) { + self.preferencePanes = preferencePanes + self.toolbar = toolbar + self.centerToolbarItems = centerToolbarItems + } + + func toolbarItemIdentifiers() -> [NSToolbarItem.Identifier] { + var toolbarItemIdentifiers = [NSToolbarItem.Identifier]() + + if centerToolbarItems { + toolbarItemIdentifiers.append(.flexibleSpace) + } + + for preferencePane in preferencePanes { + toolbarItemIdentifiers.append(preferencePane.toolbarItemIdentifier) + } + + if centerToolbarItems { + toolbarItemIdentifiers.append(.flexibleSpace) + } + + return toolbarItemIdentifiers + } + + func toolbarItem(preferenceIdentifier: Preferences.PaneIdentifier) -> NSToolbarItem? { + guard let preference = (preferencePanes.first { $0.preferencePaneIdentifier == preferenceIdentifier }) else { + preconditionFailure() + } + + let toolbarItem = NSToolbarItem(itemIdentifier: preferenceIdentifier.toolbarItemIdentifier) + toolbarItem.label = preference.preferencePaneTitle + toolbarItem.image = preference.toolbarItemIcon + toolbarItem.target = self + toolbarItem.action = #selector(toolbarItemSelected) + return toolbarItem + } + + @IBAction private func toolbarItemSelected(_ toolbarItem: NSToolbarItem) { + delegate?.activateTab( + preferenceIdentifier: Preferences.PaneIdentifier(fromToolbarItemIdentifier: toolbarItem.itemIdentifier), + animated: true + ) + } + + func selectTab(index: Int) { + toolbar.selectedItemIdentifier = preferencePanes[index].toolbarItemIdentifier + } +} diff --git a/Source/3rdParty/SindreSorhus/Preferences/Utilities.swift b/Source/3rdParty/SindreSorhus/Preferences/Utilities.swift new file mode 100755 index 00000000..849e65b4 --- /dev/null +++ b/Source/3rdParty/SindreSorhus/Preferences/Utilities.swift @@ -0,0 +1,136 @@ +// Copyright (c) 2018 and onwards Sindre Sorhus (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa +import SwiftUI + +extension NSImage { + static var empty: NSImage { NSImage(size: .zero) } +} + +extension NSView { + @discardableResult + func constrainToSuperviewBounds() -> [NSLayoutConstraint] { + guard let superview = superview else { + preconditionFailure("superview has to be set first") + } + + var result = [NSLayoutConstraint]() + result.append( + contentsOf: NSLayoutConstraint.constraints( + withVisualFormat: "H:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, + views: ["subview": self])) + result.append( + contentsOf: NSLayoutConstraint.constraints( + withVisualFormat: "V:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, + views: ["subview": self])) + translatesAutoresizingMaskIntoConstraints = false + superview.addConstraints(result) + + return result + } +} + +extension NSEvent { + /// Events triggered by user interaction. + static let userInteractionEvents: [NSEvent.EventType] = { + var events: [NSEvent.EventType] = [ + .leftMouseDown, + .leftMouseUp, + .rightMouseDown, + .rightMouseUp, + .leftMouseDragged, + .rightMouseDragged, + .keyDown, + .keyUp, + .scrollWheel, + .tabletPoint, + .otherMouseDown, + .otherMouseUp, + .otherMouseDragged, + .gesture, + .magnify, + .swipe, + .rotate, + .beginGesture, + .endGesture, + .smartMagnify, + .quickLook, + .directTouch, + ] + + if #available(macOS 10.10.3, *) { + events.append(.pressure) + } + + return events + }() + + /// Whether the event was triggered by user interaction. + var isUserInteraction: Bool { NSEvent.userInteractionEvents.contains(type) } +} + +extension Bundle { + var appName: String { + string(forInfoDictionaryKey: "CFBundleDisplayName") + ?? string(forInfoDictionaryKey: "CFBundleName") + ?? string(forInfoDictionaryKey: "CFBundleExecutable") + ?? "" + } + + private func string(forInfoDictionaryKey key: String) -> String? { + // `object(forInfoDictionaryKey:)` prefers localized info dictionary over the regular one automatically + object(forInfoDictionaryKey: key) as? String + } +} + +/// A window that allows you to disable all user interactions via `isUserInteractionEnabled`. +/// +/// Used to avoid breaking animations when the user clicks too fast. Disable user interactions during animations and you're set. +class UserInteractionPausableWindow: NSWindow { // swiftlint:disable:this final_class + var isUserInteractionEnabled = true + + override func sendEvent(_ event: NSEvent) { + guard isUserInteractionEnabled || !event.isUserInteraction else { + return + } + + super.sendEvent(event) + } + + override func responds(to selector: Selector!) -> Bool { + // Deactivate toolbar interactions from the Main Menu. + if selector == #selector(NSWindow.toggleToolbarShown(_:)) { + return false + } + + return super.responds(to: selector) + } +} + +@available(macOS 10.15, *) +extension View { + /** + Equivalent to `.eraseToAnyPublisher()` from the Combine framework. + */ + func eraseToAnyView() -> AnyView { + AnyView(self) + } +} diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 1181b2e5..ce3b8e7d 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -30,6 +30,19 @@ 5B707CEC27D9F4870099EF99 /* OpenCC in Frameworks */ = {isa = PBXBuildFile; productRef = 5B707CEB27D9F4870099EF99 /* OpenCC */; }; 5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; }; 5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B7BC4AE27AFFBE800F66C24 /* frmPrefWindow.xib */; }; + 5BA9FD2327FEF39C002DE248 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1627FEF39C002DE248 /* Utilities.swift */; }; + 5BA9FD2427FEF39C002DE248 /* Pane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1727FEF39C002DE248 /* Pane.swift */; }; + 5BA9FD2527FEF39C002DE248 /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1827FEF39C002DE248 /* Localization.swift */; }; + 5BA9FD2627FEF39C002DE248 /* PreferencesStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1927FEF39C002DE248 /* PreferencesStyle.swift */; }; + 5BA9FD2727FEF39C002DE248 /* PreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1A27FEF39C002DE248 /* PreferencePane.swift */; }; + 5BA9FD2827FEF39C002DE248 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1B27FEF39C002DE248 /* Preferences.swift */; }; + 5BA9FD2927FEF39C002DE248 /* SegmentedControlStyleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1C27FEF39C002DE248 /* SegmentedControlStyleViewController.swift */; }; + 5BA9FD2A27FEF39C002DE248 /* ToolbarItemStyleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1D27FEF39C002DE248 /* ToolbarItemStyleViewController.swift */; }; + 5BA9FD2B27FEF39C002DE248 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1E27FEF39C002DE248 /* Container.swift */; }; + 5BA9FD2C27FEF39C002DE248 /* PreferencesStyleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1F27FEF39C002DE248 /* PreferencesStyleController.swift */; }; + 5BA9FD2D27FEF39C002DE248 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD2027FEF39C002DE248 /* PreferencesWindowController.swift */; }; + 5BA9FD2E27FEF39C002DE248 /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD2127FEF39C002DE248 /* Section.swift */; }; + 5BA9FD2F27FEF39C002DE248 /* PreferencesTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD2227FEF39C002DE248 /* PreferencesTabViewController.swift */; }; 5BAD0CD527D701F6003D127F /* vChewingKeyLayout.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */; }; 5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */; }; 5BBBB75F27AED54C0023B93A /* Beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75D27AED54C0023B93A /* Beep.m4a */; }; @@ -189,6 +202,19 @@ 5B7BC4AF27AFFBE800F66C24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmPrefWindow.xib; sourceTree = ""; }; 5B7BC4B227AFFC0B00F66C24 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmPrefWindow.strings; sourceTree = ""; }; 5B8F43ED27C9BC220069AC27 /* SymbolLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SymbolLM.h; sourceTree = ""; }; + 5BA9FD1627FEF39C002DE248 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; + 5BA9FD1727FEF39C002DE248 /* Pane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pane.swift; sourceTree = ""; }; + 5BA9FD1827FEF39C002DE248 /* Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; + 5BA9FD1927FEF39C002DE248 /* PreferencesStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesStyle.swift; sourceTree = ""; }; + 5BA9FD1A27FEF39C002DE248 /* PreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencePane.swift; sourceTree = ""; }; + 5BA9FD1B27FEF39C002DE248 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; + 5BA9FD1C27FEF39C002DE248 /* SegmentedControlStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentedControlStyleViewController.swift; sourceTree = ""; }; + 5BA9FD1D27FEF39C002DE248 /* ToolbarItemStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolbarItemStyleViewController.swift; sourceTree = ""; }; + 5BA9FD1E27FEF39C002DE248 /* Container.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; + 5BA9FD1F27FEF39C002DE248 /* PreferencesStyleController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesStyleController.swift; sourceTree = ""; }; + 5BA9FD2027FEF39C002DE248 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; + 5BA9FD2127FEF39C002DE248 /* Section.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; + 5BA9FD2227FEF39C002DE248 /* PreferencesTabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesTabViewController.swift; sourceTree = ""; }; 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Menu.swift; sourceTree = ""; }; 5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = ""; }; 5BBBB75E27AED54C0023B93A /* Fart.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.m4a; sourceTree = ""; }; @@ -353,6 +379,7 @@ children = ( 5B707CE627D9F43E0099EF99 /* OpenCCBridge */, 5B62A30227AE733500A19448 /* OVMandarin */, + 5BA9FCEA27FED652002DE248 /* SindreSorhus */, ); path = 3rdParty; sourceTree = ""; @@ -564,6 +591,35 @@ path = OpenCCBridge; sourceTree = ""; }; + 5BA9FCEA27FED652002DE248 /* SindreSorhus */ = { + isa = PBXGroup; + children = ( + 5BA9FD1527FEF39C002DE248 /* Preferences */, + ); + name = SindreSorhus; + path = Source/3rdParty/SindreSorhus; + sourceTree = SOURCE_ROOT; + }; + 5BA9FD1527FEF39C002DE248 /* Preferences */ = { + isa = PBXGroup; + children = ( + 5BA9FD1E27FEF39C002DE248 /* Container.swift */, + 5BA9FD1827FEF39C002DE248 /* Localization.swift */, + 5BA9FD1727FEF39C002DE248 /* Pane.swift */, + 5BA9FD1A27FEF39C002DE248 /* PreferencePane.swift */, + 5BA9FD1B27FEF39C002DE248 /* Preferences.swift */, + 5BA9FD1927FEF39C002DE248 /* PreferencesStyle.swift */, + 5BA9FD1F27FEF39C002DE248 /* PreferencesStyleController.swift */, + 5BA9FD2227FEF39C002DE248 /* PreferencesTabViewController.swift */, + 5BA9FD2027FEF39C002DE248 /* PreferencesWindowController.swift */, + 5BA9FD2127FEF39C002DE248 /* Section.swift */, + 5BA9FD1C27FEF39C002DE248 /* SegmentedControlStyleViewController.swift */, + 5BA9FD1D27FEF39C002DE248 /* ToolbarItemStyleViewController.swift */, + 5BA9FD1627FEF39C002DE248 /* Utilities.swift */, + ); + path = Preferences; + sourceTree = ""; + }; 5BBBB75C27AED54C0023B93A /* SoundFiles */ = { isa = PBXGroup; children = ( @@ -970,7 +1026,9 @@ D461B792279DAC010070E734 /* InputState.swift in Sources */, 5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */, D47B92C027972AD100458394 /* main.swift in Sources */, + 5BA9FD2527FEF39C002DE248 /* Localization.swift in Sources */, D44FB74D2792189A003C80A6 /* PhraseReplacementMap.mm in Sources */, + 5BA9FD2E27FEF39C002DE248 /* Section.swift in Sources */, D4A13D5A27A59F0B003BE359 /* ctlInputMethod.swift in Sources */, D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */, D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */, @@ -978,16 +1036,26 @@ 5BE78BE027B38804005EA1BE /* LMConsolidator.mm in Sources */, D456576E279E4F7B00DF6BC9 /* KeyParser.swift in Sources */, D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */, + 5BA9FD2927FEF39C002DE248 /* SegmentedControlStyleViewController.swift in Sources */, D47D73AC27A6CAE600255A50 /* AssociatedPhrases.mm in Sources */, 5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */, + 5BA9FD2827FEF39C002DE248 /* Preferences.swift in Sources */, + 5BA9FD2D27FEF39C002DE248 /* PreferencesWindowController.swift in Sources */, + 5BA9FD2B27FEF39C002DE248 /* Container.swift in Sources */, + 5BA9FD2427FEF39C002DE248 /* Pane.swift in Sources */, 5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */, D41355DB278E6D17005E5CBD /* LMInstantiator.mm in Sources */, 5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */, D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */, 5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */, 5B62A33827AE79CD00A19448 /* NSStringUtils.swift in Sources */, + 5BA9FD2327FEF39C002DE248 /* Utilities.swift in Sources */, 5B62A33227AE792F00A19448 /* InputSourceHelper.swift in Sources */, + 5BA9FD2C27FEF39C002DE248 /* PreferencesStyleController.swift in Sources */, + 5BA9FD2A27FEF39C002DE248 /* ToolbarItemStyleViewController.swift in Sources */, + 5BA9FD2F27FEF39C002DE248 /* PreferencesTabViewController.swift in Sources */, 5B5E535227EF261400C6AA1E /* IME.swift in Sources */, + 5BA9FD2627FEF39C002DE248 /* PreferencesStyle.swift in Sources */, 5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, 5B62A34827AE7CD900A19448 /* ctlCandidateVertical.swift in Sources */, @@ -999,6 +1067,7 @@ 5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */, D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */, 6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */, + 5BA9FD2727FEF39C002DE248 /* PreferencePane.swift in Sources */, D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */, 5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */, ); From 40226f68d18448f2c1e29c7df9cb4330b6a73d21 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 10 Apr 2022 13:08:03 +0800 Subject: [PATCH 28/36] PrefPackage // Patch the behavior of handling window titles. --- .../PreferencesTabViewController.swift | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift b/Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift index 8d7a9f43..18c9b177 100755 --- a/Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift +++ b/Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift @@ -114,13 +114,16 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont private func updateWindowTitle(tabIndex: Int) { window.title = { - if preferencePanes.count > 1 { - return preferencePanes[tabIndex].preferencePaneTitle - } else { - let preferences = Localization[.preferences] - let appName = Bundle.main.appName - return "\(appName) \(preferences)" - } + // if preferencePanes.count > 1 { + // return preferencePanes[tabIndex].preferencePaneTitle + // } else { + // let preferences = Localization[.preferences] + // let appName = Bundle.main.appName + // return "\(appName) \(preferences)" + // } + var preferencesTitleName = NSLocalizedString("vChewing Preferences…", comment: "") + preferencesTitleName.removeLast() + return preferencesTitleName }() } From 65b158dbed6cdc94917f85d38340bb4b20b65ea7 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 7 Apr 2022 17:16:44 +0800 Subject: [PATCH 29/36] PrefUI // Initial commit, exclusive for macOS 11 and later. --- Source/3rdParty/VDKComboBox/VDKComboBox.swift | 70 ++++++++ .../IMEModules/ctlInputMethod_Menu.swift | 26 ++- .../Resources/Base.lproj/Localizable.strings | 68 ++++++++ Source/Resources/en.lproj/Localizable.strings | 68 ++++++++ Source/Resources/ja.lproj/Localizable.strings | 68 ++++++++ .../zh-Hans.lproj/Localizable.strings | 68 ++++++++ .../zh-Hant.lproj/Localizable.strings | 68 ++++++++ .../CandidateUI/ctlCandidateHorizontal.swift | 5 +- .../UI/CandidateUI/ctlCandidateVertical.swift | 5 +- Source/UI/PrefUI/ctlPrefUI.swift | 71 ++++++++ Source/UI/PrefUI/suiPrefPaneDictionary.swift | 126 ++++++++++++++ Source/UI/PrefUI/suiPrefPaneExperience.swift | 155 +++++++++++++++++ Source/UI/PrefUI/suiPrefPaneGeneral.swift | 158 ++++++++++++++++++ Source/UI/PrefUI/suiPrefPaneKeyboard.swift | 98 +++++++++++ vChewing.xcodeproj/project.pbxproj | 148 ++++++++++------ 15 files changed, 1138 insertions(+), 64 deletions(-) create mode 100644 Source/3rdParty/VDKComboBox/VDKComboBox.swift create mode 100644 Source/UI/PrefUI/ctlPrefUI.swift create mode 100644 Source/UI/PrefUI/suiPrefPaneDictionary.swift create mode 100644 Source/UI/PrefUI/suiPrefPaneExperience.swift create mode 100644 Source/UI/PrefUI/suiPrefPaneGeneral.swift create mode 100644 Source/UI/PrefUI/suiPrefPaneKeyboard.swift diff --git a/Source/3rdParty/VDKComboBox/VDKComboBox.swift b/Source/3rdParty/VDKComboBox/VDKComboBox.swift new file mode 100644 index 00000000..2c14bc58 --- /dev/null +++ b/Source/3rdParty/VDKComboBox/VDKComboBox.swift @@ -0,0 +1,70 @@ +// +// Ref: https://stackoverflow.com/a/71058587/4162914 +// License: https://creativecommons.org/licenses/by-sa/4.0/ +// + +import SwiftUI + +// MARK: - NSComboBox +// Ref: https://stackoverflow.com/a/71058587/4162914 +@available(macOS 11.0, *) +struct ComboBox: NSViewRepresentable { + // The items that will show up in the pop-up menu: + var items: [String] + + // The property on our parent view that gets synced to the current + // stringValue of the NSComboBox, whether the user typed it in or + // selected it from the list: + @Binding var text: String + + func makeCoordinator() -> Coordinator { + return Coordinator(self) + } + + func makeNSView(context: Context) -> NSComboBox { + let comboBox = NSComboBox() + comboBox.usesDataSource = false + comboBox.completes = false + comboBox.delegate = context.coordinator + comboBox.intercellSpacing = NSSize(width: 0.0, height: 10.0) + return comboBox + } + + func updateNSView(_ nsView: NSComboBox, context: Context) { + nsView.removeAllItems() + nsView.addItems(withObjectValues: items) + + // ComboBox doesn't automatically select the item matching its text; + // we must do that manually. But we need the delegate to ignore that + // selection-change or we'll get a "state modified during view update; + // will cause undefined behavior" warning. + context.coordinator.ignoreSelectionChanges = true + nsView.stringValue = text + nsView.selectItem(withObjectValue: text) + context.coordinator.ignoreSelectionChanges = false + } + + class Coordinator: NSObject, NSComboBoxDelegate { + var parent: ComboBox + var ignoreSelectionChanges: Bool = false + + init(_ parent: ComboBox) { + self.parent = parent + } + + func comboBoxSelectionDidChange(_ notification: Notification) { + if !ignoreSelectionChanges, + let box: NSComboBox = notification.object as? NSComboBox, + let newStringValue: String = box.objectValueOfSelectedItem as? String + { + parent.text = newStringValue + } + } + + func controlTextDidEndEditing(_ obj: Notification) { + if let textField = obj.object as? NSTextField { + parent.text = textField.stringValue + } + } + } +} diff --git a/Source/Modules/IMEModules/ctlInputMethod_Menu.swift b/Source/Modules/IMEModules/ctlInputMethod_Menu.swift index a6a5aca2..bdd05ea4 100644 --- a/Source/Modules/IMEModules/ctlInputMethod_Menu.swift +++ b/Source/Modules/IMEModules/ctlInputMethod_Menu.swift @@ -123,10 +123,14 @@ extension ctlInputMethod { menu.addItem(NSMenuItem.separator()) // --------------------- - menu.addItem( - withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), - action: #selector(showPreferences(_:)), keyEquivalent: "") - if !optionKeyPressed { + if optionKeyPressed { + menu.addItem( + withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), + action: #selector(showLegacyPreferences(_:)), keyEquivalent: "") + } else { + menu.addItem( + withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), + action: #selector(showPreferences(_:)), keyEquivalent: "") menu.addItem( withTitle: NSLocalizedString("Check for Updates…", comment: ""), action: #selector(checkForUpdate(_:)), keyEquivalent: "") @@ -152,6 +156,20 @@ extension ctlInputMethod { // MARK: - IME Menu Items @objc override func showPreferences(_ sender: Any?) { + if #available(macOS 11.0, *) { + NSApp.setActivationPolicy(.accessory) + ctlPrefUI.shared.controller.show(preferencePane: Preferences.PaneIdentifier(rawValue: "General")) + ctlPrefUI.shared.controller.window?.level = .floating + } else { + showPrefWindowTraditional() + } + } + + @objc func showLegacyPreferences(_ sender: Any?) { + showPrefWindowTraditional() + } + + private func showPrefWindowTraditional() { (NSApp.delegate as? AppDelegate)?.showPreferences() NSApp.activate(ignoringOtherApps: true) } diff --git a/Source/Resources/Base.lproj/Localizable.strings b/Source/Resources/Base.lproj/Localizable.strings index a1ce8b59..540c3ddc 100644 --- a/Source/Resources/Base.lproj/Localizable.strings +++ b/Source/Resources/Base.lproj/Localizable.strings @@ -73,3 +73,71 @@ "catDoubleTableLines" = "DoubleTableLines"; "catFillingBlocks" = "FillingBlocks"; "catLineSegments" = "LineSegments"; + +// SwiftUI Preferences +"(Shift+)Space:" = "(Shift+)Space:"; +"An accomodation for elder computer users." = "An accomodation for elder computer users."; +"Apple ABC (equivalent to English US)" = "Apple ABC (equivalent to English US)"; +"Apple Chewing - Dachen" = "Apple Chewing - Dachen"; +"Apple Chewing - Eten Traditional" = "Apple Chewing - Eten Traditional"; +"Apple Dynamic Bopomofo Basic Keyboard Layouts (Dachen & Eten Traditional) must match the Dachen parser in order to be functional." = "Apple Dynamic Bopomofo Basic Keyboard Layouts (Dachen & Eten Traditional) must match the Dachen parser in order to be functional."; +"Auto-convert traditional Chinese glyphs to JIS Shinjitai characters" = "Auto-convert traditional Chinese glyphs to JIS Shinjitai characters"; +"Auto-convert traditional Chinese glyphs to KangXi characters" = "Auto-convert traditional Chinese glyphs to KangXi characters"; +"Automatically reload user data files if changes detected" = "Automatically reload user data files if changes detected"; +"Basic Keyboard Layout:" = "Basic Keyboard Layout:"; +"Candidate Layout:" = "Candidate Layout:"; +"Candidate Size:" = "Candidate Size:"; +"Change user interface language (will reboot the IME)." = "Change user interface language (will reboot the IME)."; +"Check for updates automatically" = "Check for updates automatically"; +"Choose candidate font size for better visual clarity." = "Choose candidate font size for better visual clarity."; +"Choose or hit Enter to confim your prefered keys for selecting candidates." = "Choose or hit Enter to confim your prefered keys for selecting candidates."; +"Choose the behavior of (Shift+)Space key in the candidate window." = "Choose the behavior of (Shift+)Space key in the candidate window."; +"Choose the behavior of (Shift+)Tab key in the candidate window." = "Choose the behavior of (Shift+)Tab key in the candidate window."; +"Choose the cursor position where you want to list possible candidates." = "Choose the cursor position where you want to list possible candidates."; +"Choose the macOS-level basic keyboard layout." = "Choose the macOS-level basic keyboard layout."; +"Choose the phonetic layout for Mandarin parser." = "Choose the phonetic layout for Mandarin parser."; +"Choose your desired user data folder path. Will be omitted if invalid." = "Choose your desired user data folder path. Will be omitted if invalid."; +"Choose your preferred layout of the candidate window." = "Choose your preferred layout of the candidate window."; +"Cursor Selection:" = "Cursor Selection:"; +"Dachen (Microsoft Standard / Wang / 01, etc.)" = "Dachen (Microsoft Standard / Wang / 01, etc.)"; +"Debug Mode" = "Debug Mode"; +"Dictionary" = "Dictionary"; +"Emulating select-candidate-per-character mode" = "Emulating select-candidate-per-character mode"; +"Enable CNS11643 Support (2022-01-27)" = "Enable CNS11643 Support (2022-01-27)"; +"Enable Space key for calling candidate window" = "Enable Space key for calling candidate window"; +"Enable symbol input support (incl. certain emoji symbols)" = "Enable symbol input support (incl. certain emoji symbols)"; +"English" = "English"; +"Eten 26" = "Eten 26"; +"Eten Traditional" = "Eten Traditional"; +"Experience" = "Experience"; +"Fake Seigyou" = "Fake Seigyou"; +"Follow OS settings" = "Follow OS settings"; +"for cycling candidates" = "for cycling candidates"; +"for cycling pages" = "for cycling pages"; +"General" = "General"; +"Hanyu Pinyin with Numeral Intonation" = "Hanyu Pinyin with Numeral Intonation"; +"Horizontal" = "Horizontal"; +"Hsu" = "Hsu"; +"IBM" = "IBM"; +"Japanese" = "Japanese"; +"Keyboard" = "Keyboard"; +"Misc Settings:" = "Misc Settings:"; +"MiTAC" = "MiTAC"; +"Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only." = "Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only."; +"Output Settings:" = "Output Settings:"; +"Phonetic Parser:" = "Phonetic Parser:"; +"Push the cursor to the front of the phrase after selection" = "Push the cursor to the front of the phrase after selection"; +"Selection Keys:" = "Selection Keys:"; +"Show page buttons in candidate window" = "Show page buttons in candidate window"; +"Simplified Chinese" = "Simplified Chinese"; +"Space & ESC Key:" = "Space & ESC Key:"; +"Space to +cycle candidates, Shift+Space to +cycle pages" = "Space to +cycle candidates, Shift+Space to +cycle pages"; +"Space to +cycle pages, Shift+Space to +cycle candidates" = "Space to +cycle pages, Shift+Space to +cycle candidates"; +"Stop farting (when typed phonetic combination is invalid, etc.)" = "Stop farting (when typed phonetic combination is invalid, etc.)"; +"to the front of the phrase (like Matsushita Hanin IME)" = "to the front of the phrase (like Matsushita Hanin IME)"; +"to the rear of the phrase (like MS New-Phonetic IME)" = "to the rear of the phrase (like MS New-Phonetic IME)"; +"Traditional Chinese" = "Traditional Chinese"; +"Typing Style:" = "Typing Style:"; +"UI Language:" = "UI Language:"; +"Use ESC key to clear the entire input buffer" = "Use ESC key to clear the entire input buffer"; +"Vertical" = "Vertical"; diff --git a/Source/Resources/en.lproj/Localizable.strings b/Source/Resources/en.lproj/Localizable.strings index a1ce8b59..540c3ddc 100644 --- a/Source/Resources/en.lproj/Localizable.strings +++ b/Source/Resources/en.lproj/Localizable.strings @@ -73,3 +73,71 @@ "catDoubleTableLines" = "DoubleTableLines"; "catFillingBlocks" = "FillingBlocks"; "catLineSegments" = "LineSegments"; + +// SwiftUI Preferences +"(Shift+)Space:" = "(Shift+)Space:"; +"An accomodation for elder computer users." = "An accomodation for elder computer users."; +"Apple ABC (equivalent to English US)" = "Apple ABC (equivalent to English US)"; +"Apple Chewing - Dachen" = "Apple Chewing - Dachen"; +"Apple Chewing - Eten Traditional" = "Apple Chewing - Eten Traditional"; +"Apple Dynamic Bopomofo Basic Keyboard Layouts (Dachen & Eten Traditional) must match the Dachen parser in order to be functional." = "Apple Dynamic Bopomofo Basic Keyboard Layouts (Dachen & Eten Traditional) must match the Dachen parser in order to be functional."; +"Auto-convert traditional Chinese glyphs to JIS Shinjitai characters" = "Auto-convert traditional Chinese glyphs to JIS Shinjitai characters"; +"Auto-convert traditional Chinese glyphs to KangXi characters" = "Auto-convert traditional Chinese glyphs to KangXi characters"; +"Automatically reload user data files if changes detected" = "Automatically reload user data files if changes detected"; +"Basic Keyboard Layout:" = "Basic Keyboard Layout:"; +"Candidate Layout:" = "Candidate Layout:"; +"Candidate Size:" = "Candidate Size:"; +"Change user interface language (will reboot the IME)." = "Change user interface language (will reboot the IME)."; +"Check for updates automatically" = "Check for updates automatically"; +"Choose candidate font size for better visual clarity." = "Choose candidate font size for better visual clarity."; +"Choose or hit Enter to confim your prefered keys for selecting candidates." = "Choose or hit Enter to confim your prefered keys for selecting candidates."; +"Choose the behavior of (Shift+)Space key in the candidate window." = "Choose the behavior of (Shift+)Space key in the candidate window."; +"Choose the behavior of (Shift+)Tab key in the candidate window." = "Choose the behavior of (Shift+)Tab key in the candidate window."; +"Choose the cursor position where you want to list possible candidates." = "Choose the cursor position where you want to list possible candidates."; +"Choose the macOS-level basic keyboard layout." = "Choose the macOS-level basic keyboard layout."; +"Choose the phonetic layout for Mandarin parser." = "Choose the phonetic layout for Mandarin parser."; +"Choose your desired user data folder path. Will be omitted if invalid." = "Choose your desired user data folder path. Will be omitted if invalid."; +"Choose your preferred layout of the candidate window." = "Choose your preferred layout of the candidate window."; +"Cursor Selection:" = "Cursor Selection:"; +"Dachen (Microsoft Standard / Wang / 01, etc.)" = "Dachen (Microsoft Standard / Wang / 01, etc.)"; +"Debug Mode" = "Debug Mode"; +"Dictionary" = "Dictionary"; +"Emulating select-candidate-per-character mode" = "Emulating select-candidate-per-character mode"; +"Enable CNS11643 Support (2022-01-27)" = "Enable CNS11643 Support (2022-01-27)"; +"Enable Space key for calling candidate window" = "Enable Space key for calling candidate window"; +"Enable symbol input support (incl. certain emoji symbols)" = "Enable symbol input support (incl. certain emoji symbols)"; +"English" = "English"; +"Eten 26" = "Eten 26"; +"Eten Traditional" = "Eten Traditional"; +"Experience" = "Experience"; +"Fake Seigyou" = "Fake Seigyou"; +"Follow OS settings" = "Follow OS settings"; +"for cycling candidates" = "for cycling candidates"; +"for cycling pages" = "for cycling pages"; +"General" = "General"; +"Hanyu Pinyin with Numeral Intonation" = "Hanyu Pinyin with Numeral Intonation"; +"Horizontal" = "Horizontal"; +"Hsu" = "Hsu"; +"IBM" = "IBM"; +"Japanese" = "Japanese"; +"Keyboard" = "Keyboard"; +"Misc Settings:" = "Misc Settings:"; +"MiTAC" = "MiTAC"; +"Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only." = "Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only."; +"Output Settings:" = "Output Settings:"; +"Phonetic Parser:" = "Phonetic Parser:"; +"Push the cursor to the front of the phrase after selection" = "Push the cursor to the front of the phrase after selection"; +"Selection Keys:" = "Selection Keys:"; +"Show page buttons in candidate window" = "Show page buttons in candidate window"; +"Simplified Chinese" = "Simplified Chinese"; +"Space & ESC Key:" = "Space & ESC Key:"; +"Space to +cycle candidates, Shift+Space to +cycle pages" = "Space to +cycle candidates, Shift+Space to +cycle pages"; +"Space to +cycle pages, Shift+Space to +cycle candidates" = "Space to +cycle pages, Shift+Space to +cycle candidates"; +"Stop farting (when typed phonetic combination is invalid, etc.)" = "Stop farting (when typed phonetic combination is invalid, etc.)"; +"to the front of the phrase (like Matsushita Hanin IME)" = "to the front of the phrase (like Matsushita Hanin IME)"; +"to the rear of the phrase (like MS New-Phonetic IME)" = "to the rear of the phrase (like MS New-Phonetic IME)"; +"Traditional Chinese" = "Traditional Chinese"; +"Typing Style:" = "Typing Style:"; +"UI Language:" = "UI Language:"; +"Use ESC key to clear the entire input buffer" = "Use ESC key to clear the entire input buffer"; +"Vertical" = "Vertical"; diff --git a/Source/Resources/ja.lproj/Localizable.strings b/Source/Resources/ja.lproj/Localizable.strings index c5f672a1..8129b74d 100644 --- a/Source/Resources/ja.lproj/Localizable.strings +++ b/Source/Resources/ja.lproj/Localizable.strings @@ -73,3 +73,71 @@ "catDoubleTableLines" = "双線"; "catFillingBlocks" = "ブロック"; "catLineSegments" = "線分"; + +// SwiftUI Preferences +"(Shift+)Space:" = "(Shift+)Space:"; +"An accomodation for elder computer users." = "年配なるユーザーのために提供した機能である。"; +"Apple ABC (equivalent to English US)" = "Apple ABC (Apple U.S. キーボードと同じ)"; +"Apple Chewing - Dachen" = "Apple 大千注音キーボード"; +"Apple Chewing - Eten Traditional" = "Apple 倚天傳統キーボード"; +"Apple Dynamic Bopomofo Basic Keyboard Layouts (Dachen & Eten Traditional) must match the Dachen parser in order to be functional." = "Apple 動態注音キーボード (大千と倚天伝統) を使うには、共通語分析器の配列を大千と設定すべきである。"; +"Auto-convert traditional Chinese glyphs to JIS Shinjitai characters" = "入力した繁体字を日文 JIS 新字体と自動変換"; +"Auto-convert traditional Chinese glyphs to KangXi characters" = "入力した繁体字を康熙字体と自動変換"; +"Automatically reload user data files if changes detected" = "ユーザー辞書データの変更を自動検出し、自動的に再読込"; +"Basic Keyboard Layout:" = "基礎キーボード:"; +"Candidate Layout:" = "入力候補陳列の仕様"; +"Candidate Size:" = "候補文字の字号:"; +"Change user interface language (will reboot the IME)." = "アプリ表示用言語をご指定下さい、そして入力アプリは自動的に再起動。"; +"Check for updates automatically" = "アプリの更新通知を受く"; +"Choose candidate font size for better visual clarity." = "入力候補陳列の候補文字の字号をご指定ください。"; +"Choose or hit Enter to confim your prefered keys for selecting candidates." = "お好きなる言選り用キー陣列をご指定ください。新しい組み合わせは Enter で効かす。"; +"Choose the behavior of (Shift+)Space key in the candidate window." = "入力候補陳列での (Shift+)Space キーの輪番切替対象をご指定ください。"; +"Choose the behavior of (Shift+)Tab key in the candidate window." = "入力候補陳列での (Shift+)Tab キーの輪番切替対象をご指定ください。"; +"Choose the cursor position where you want to list possible candidates." = "カーソルはどこで入力候補を呼び出すかとご指定ださい。"; +"Choose the macOS-level basic keyboard layout." = "macOS 基礎キーボード配置をご指定ください。"; +"Choose the phonetic layout for Mandarin parser." = "共通語分析器の注音配列をご指定ください。"; +"Choose your desired user data folder path. Will be omitted if invalid." = "欲しがるユーザー辞書保存先をご指定ください。無効なる保存先設定は省かれる。"; +"Choose your preferred layout of the candidate window." = "入力候補陳列の仕様をご指定ください。"; +"Cursor Selection:" = "カーソル候補呼出:"; +"Dachen (Microsoft Standard / Wang / 01, etc.)" = "大千配列 (Microsoft 標準・王安・零壹など)"; +"Debug Mode" = "欠陥辿着モード"; +"Dictionary" = "辞書設定"; +"Emulating select-candidate-per-character mode" = "漢字1つづつ全候補選択入力モード"; +"Enable CNS11643 Support (2022-01-27)" = "全字庫モード // 入力可能な漢字数を倍増す (2022-01-07)"; +"Enable Space key for calling candidate window" = "Space キーで入力候補を呼び出す"; +"Enable symbol input support (incl. certain emoji symbols)" = "僅かなる絵文字も含む符号入力サポートを起用"; +"English" = "英語"; +"Eten 26" = "倚天形忘れ配列 (26 キー)"; +"Eten Traditional" = "倚天伝統配列"; +"Experience" = "体験設定"; +"Fake Seigyou" = "偽精業配列"; +"Follow OS settings" = "システム設定に準ず"; +"for cycling candidates" = "候補文字そのもの"; +"for cycling pages" = "候補陳列ページ"; +"General" = "全般設定"; +"Hanyu Pinyin with Numeral Intonation" = "漢語弁音 (ローマ字+数字音調)"; +"Horizontal" = "横型陳列"; +"Hsu" = "許氏国音自然配列"; +"IBM" = "IBM 配列"; +"Japanese" = "和語"; +"Keyboard" = "配列設定"; +"Misc Settings:" = "他の設定:"; +"MiTAC" = "神通配列"; +"Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only." = "QWERTY 以外の英数キーボードは漢語弁音以外の配列に不適用。"; +"Output Settings:" = "出力設定:"; +"Phonetic Parser:" = "注音配列:"; +"Push the cursor to the front of the phrase after selection" = "候補選択の直後、すぐカーソルを単語の向こうに推し進める"; +"Selection Keys:" = "言選り用キー:"; +"Show page buttons in candidate window" = "入力候補陳列の側にページボタンを表示"; +"Simplified Chinese" = "簡体中国語"; +"Space & ESC Key:" = "ESC と Space:"; +"Space to +cycle candidates, Shift+Space to +cycle pages" = "Shift+Space で次のページ、Space で次の候補文字を"; +"Space to +cycle pages, Shift+Space to +cycle candidates" = "Space で次のページ、Shift+Space で次の候補文字を"; +"Stop farting (when typed phonetic combination is invalid, etc.)" = "マナーモード // 外すと入力間違った時に変な声が出る"; +"to the front of the phrase (like Matsushita Hanin IME)" = "単語の前で // パナソニック漢音のやり方"; +"to the rear of the phrase (like MS New-Phonetic IME)" = "単語の後で // Microsoft 新注音のやり方"; +"Traditional Chinese" = "繁体中国語"; +"Typing Style:" = "入力習慣:"; +"UI Language:" = "表示用言語:"; +"Use ESC key to clear the entire input buffer" = "ESC キーで入力緩衝列を消す"; +"Vertical" = "縦型陳列"; diff --git a/Source/Resources/zh-Hans.lproj/Localizable.strings b/Source/Resources/zh-Hans.lproj/Localizable.strings index cc518504..a6dbdc3e 100644 --- a/Source/Resources/zh-Hans.lproj/Localizable.strings +++ b/Source/Resources/zh-Hans.lproj/Localizable.strings @@ -73,3 +73,71 @@ "catDoubleTableLines" = "双线"; "catFillingBlocks" = "填色"; "catLineSegments" = "线段"; + +// SwiftUI Preferences +"(Shift+)Space:" = "(Shift+)空格键:"; +"An accomodation for elder computer users." = "针对年长使用者的习惯而提供。"; +"Apple ABC (equivalent to English US)" = "Apple ABC (与 Apple 美规键盘等价)"; +"Apple Chewing - Dachen" = "Apple 大千注音键盘配列"; +"Apple Chewing - Eten Traditional" = "Apple 倚天传统键盘配列"; +"Apple Dynamic Bopomofo Basic Keyboard Layouts (Dachen & Eten Traditional) must match the Dachen parser in order to be functional." = "Apple 动态注音键盘布局(大千与倚天)要求普通话/国音分析器得配置为大千配列。"; +"Auto-convert traditional Chinese glyphs to JIS Shinjitai characters" = "自动将繁体中文字转为日文 JIS 新字体"; +"Auto-convert traditional Chinese glyphs to KangXi characters" = "自动将繁体中文字转为康熙字"; +"Automatically reload user data files if changes detected" = "自动检测并载入使用者语汇档案变更"; +"Basic Keyboard Layout:" = "基础键盘布局:"; +"Candidate Layout:" = "候选字窗布局:"; +"Candidate Size:" = "候选字窗字号:"; +"Change user interface language (will reboot the IME)." = "变更输入法介面语言,会自动重启输入法。"; +"Check for updates automatically" = "自动检查软体更新"; +"Choose candidate font size for better visual clarity." = "变更候选字窗的字型大小。"; +"Choose or hit Enter to confim your prefered keys for selecting candidates." = "请选择您所偏好的用来选字的按键组合。自订组合需敲 Enter 键生效。"; +"Choose the behavior of (Shift+)Space key in the candidate window." = "指定 (Shift+)空格键 在选字窗内的轮替操作对象。"; +"Choose the behavior of (Shift+)Tab key in the candidate window." = "指定 (Shift+)Tab 在选字窗内的轮替操作对象。"; +"Choose the cursor position where you want to list possible candidates." = "请选择用以触发选字的游标相对位置。"; +"Choose the macOS-level basic keyboard layout." = "请选择 macOS 基础键盘布局。"; +"Choose the phonetic layout for Mandarin parser." = "请指定普通话/国音分析器所使用的注音配列。"; +"Choose your desired user data folder path. Will be omitted if invalid." = "请在此指定您想指定的使用者语汇档案目录。无效值会被忽略。"; +"Choose your preferred layout of the candidate window." = "选择您所偏好的候选字窗布局。"; +"Cursor Selection:" = "选字游标:"; +"Dachen (Microsoft Standard / Wang / 01, etc.)" = "大千配列 (微软标准/王安/零壹/仲鼎/国乔)"; +"Debug Mode" = "侦错模式"; +"Dictionary" = "辞典"; +"Emulating select-candidate-per-character mode" = "模拟 90 年代前期注音逐字选字输入风格"; +"Enable CNS11643 Support (2022-01-27)" = "启用 CNS11643 全字库支援 (2022-01-07)"; +"Enable Space key for calling candidate window" = "敲空格键以呼出候选字窗"; +"Enable symbol input support (incl. certain emoji symbols)" = "启用包括少许绘文字在内的符号输入支援"; +"English" = "英语"; +"Eten 26" = "倚天忘形配列 (26 键)"; +"Eten Traditional" = "倚天传统配列"; +"Experience" = "体验"; +"Fake Seigyou" = "伪精业配列"; +"Follow OS settings" = "依系统设定"; +"for cycling candidates" = "轮替候选字"; +"for cycling pages" = "轮替页面"; +"General" = "通用"; +"Hanyu Pinyin with Numeral Intonation" = "汉语拼音+数字标调"; +"Horizontal" = "横向布局"; +"Hsu" = "许氏国音自然配列"; +"IBM" = "IBM 配列"; +"Japanese" = "和语"; +"Keyboard" = "键盘"; +"Misc Settings:" = "杂项:"; +"MiTAC" = "神通配列"; +"Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only." = "QWERTY 以外的英数布局是为了汉语拼音配列使用者而准备的。"; +"Output Settings:" = "输出设定:"; +"Phonetic Parser:" = "注音配列:"; +"Push the cursor to the front of the phrase after selection" = "在选字后将游标置于该字词的前方"; +"Selection Keys:" = "选字键:"; +"Show page buttons in candidate window" = "在选字窗内显示翻页按钮"; +"Simplified Chinese" = "简体中文"; +"Space & ESC Key:" = "ESC 与空格键:"; +"Space to +cycle candidates, Shift+Space to +cycle pages" = "Shift+空格键 换下一页,空格键 换选下一个后选字"; +"Space to +cycle pages, Shift+Space to +cycle candidates" = "空格键 换下一页,Shift+空格键 换选下一个后选字"; +"Stop farting (when typed phonetic combination is invalid, etc.)" = "不要放屁 // 例:当输入的音韵有误时,等"; +"to the front of the phrase (like Matsushita Hanin IME)" = "将游标置于词语前方 // 松下汉音风格"; +"to the rear of the phrase (like MS New-Phonetic IME)" = "将游标置于词语后方 // 微软新注音风格"; +"Traditional Chinese" = "繁体中文"; +"Typing Style:" = "输入风格:"; +"UI Language:" = "介面语言:"; +"Use ESC key to clear the entire input buffer" = "敲 ESC 键以清空整个组字缓冲区"; +"Vertical" = "纵向布局"; diff --git a/Source/Resources/zh-Hant.lproj/Localizable.strings b/Source/Resources/zh-Hant.lproj/Localizable.strings index 6f789838..98ff5818 100644 --- a/Source/Resources/zh-Hant.lproj/Localizable.strings +++ b/Source/Resources/zh-Hant.lproj/Localizable.strings @@ -73,3 +73,71 @@ "catDoubleTableLines" = "雙線"; "catFillingBlocks" = "填色"; "catLineSegments" = "線段"; + +// SwiftUI Preferences +"(Shift+)Space:" = "(Shift+)空格鍵:"; +"An accomodation for elder computer users." = "針對年長使用者的習慣而提供。"; +"Apple ABC (equivalent to English US)" = "Apple ABC (與 Apple 美規鍵盤等價)"; +"Apple Chewing - Dachen" = "Apple 大千注音鍵盤佈局"; +"Apple Chewing - Eten Traditional" = "Apple 倚天傳統鍵盤佈局"; +"Apple Dynamic Bopomofo Basic Keyboard Layouts (Dachen & Eten Traditional) must match the Dachen parser in order to be functional." = "Apple 動態注音鍵盤佈局(大千與倚天)要求普通話/國音分析器得配置為大千配列。"; +"Auto-convert traditional Chinese glyphs to JIS Shinjitai characters" = "自動將繁體中文字轉為日文 JIS 新字體"; +"Auto-convert traditional Chinese glyphs to KangXi characters" = "自動將繁體中文字轉為康熙字"; +"Automatically reload user data files if changes detected" = "自動檢測並載入使用者語彙檔案變更"; +"Basic Keyboard Layout:" = "基礎鍵盤佈局:"; +"Candidate Layout:" = "候選字窗佈局:"; +"Candidate Size:" = "候選字窗字號:"; +"Change user interface language (will reboot the IME)." = "變更輸入法介面語言,會自動重啟輸入法。"; +"Check for updates automatically" = "自動檢查軟體更新"; +"Choose candidate font size for better visual clarity." = "變更候選字窗的字型大小。"; +"Choose or hit Enter to confim your prefered keys for selecting candidates." = "請選擇您所偏好的用來選字的按鍵組合。自訂組合需敲 Enter 鍵生效。"; +"Choose the behavior of (Shift+)Space key in the candidate window." = "指定 (Shift+)空格鍵 在選字窗內的輪替操作對象。"; +"Choose the behavior of (Shift+)Tab key in the candidate window." = "指定 (Shift+)Tab 在選字窗內的輪替操作對象。"; +"Choose the cursor position where you want to list possible candidates." = "請選擇用以觸發選字的游標相對位置。"; +"Choose the macOS-level basic keyboard layout." = "請選擇 macOS 基礎鍵盤佈局。"; +"Choose the phonetic layout for Mandarin parser." = "請指定普通話/國音分析器所使用的注音配列。"; +"Choose your desired user data folder path. Will be omitted if invalid." = "請在此指定您想指定的使用者語彙檔案目錄。無效值會被忽略。"; +"Choose your preferred layout of the candidate window." = "選擇您所偏好的候選字窗佈局。"; +"Cursor Selection:" = "選字游標:"; +"Dachen (Microsoft Standard / Wang / 01, etc.)" = "大千配列 (微軟標準/王安/零壹/仲鼎/國喬)"; +"Debug Mode" = "偵錯模式"; +"Dictionary" = "辭典"; +"Emulating select-candidate-per-character mode" = "模擬 90 年代前期注音逐字選字輸入風格"; +"Enable CNS11643 Support (2022-01-27)" = "啟用 CNS11643 全字庫支援 (2022-01-07)"; +"Enable Space key for calling candidate window" = "敲空格鍵以呼出候選字窗"; +"Enable symbol input support (incl. certain emoji symbols)" = "啟用包括少許繪文字在內的符號輸入支援"; +"English" = "英語"; +"Eten 26" = "倚天忘形配列 (26 鍵)"; +"Eten Traditional" = "倚天傳統配列"; +"Experience" = "體驗"; +"Fake Seigyou" = "偽精業配列"; +"Follow OS settings" = "依系統設定"; +"for cycling candidates" = "輪替候選字"; +"for cycling pages" = "輪替頁面"; +"General" = "通用"; +"Hanyu Pinyin with Numeral Intonation" = "漢語拼音+數字標調"; +"Horizontal" = "橫向佈局"; +"Hsu" = "許氏國音自然配列"; +"IBM" = "IBM 配列"; +"Japanese" = "和語"; +"Keyboard" = "鍵盤"; +"Misc Settings:" = "雜項:"; +"MiTAC" = "神通配列"; +"Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only." = "QWERTY 以外的英數佈局是為了漢語拼音配列使用者而準備的。"; +"Output Settings:" = "輸出設定:"; +"Phonetic Parser:" = "注音配列:"; +"Push the cursor to the front of the phrase after selection" = "在選字後將游標置於該字詞的前方"; +"Selection Keys:" = "選字鍵:"; +"Show page buttons in candidate window" = "在選字窗內顯示翻頁按鈕"; +"Simplified Chinese" = "簡體中文"; +"Space & ESC Key:" = "ESC 與空格鍵:"; +"Space to +cycle candidates, Shift+Space to +cycle pages" = "Shift+空格鍵 換下一頁,空格鍵 換選下一個後選字"; +"Space to +cycle pages, Shift+Space to +cycle candidates" = "空格鍵 換下一頁,Shift+空格鍵 換選下一個後選字"; +"Stop farting (when typed phonetic combination is invalid, etc.)" = "不要放屁 // 例:當輸入的音韻有誤時,等"; +"to the front of the phrase (like Matsushita Hanin IME)" = "將游標置於詞語前方 // 松下漢音風格"; +"to the rear of the phrase (like MS New-Phonetic IME)" = "將游標置於詞語後方 // 微軟新注音風格"; +"Traditional Chinese" = "繁體中文"; +"Typing Style:" = "輸入風格:"; +"UI Language:" = "介面語言:"; +"Use ESC key to clear the entire input buffer" = "敲 ESC 鍵以清空整個組字緩衝區"; +"Vertical" = "縱向佈局"; diff --git a/Source/UI/CandidateUI/ctlCandidateHorizontal.swift b/Source/UI/CandidateUI/ctlCandidateHorizontal.swift index 027703e9..e2e78b23 100644 --- a/Source/UI/CandidateUI/ctlCandidateHorizontal.swift +++ b/Source/UI/CandidateUI/ctlCandidateHorizontal.swift @@ -435,10 +435,9 @@ extension ctlCandidateHorizontal { frameRect = window?.frame ?? NSRect.zero - let topLeftPoint = NSMakePoint( - frameRect.origin.x, frameRect.origin.y + frameRect.size.height) + let topLeftPoint = NSPoint(x: frameRect.origin.x, y: frameRect.origin.y + frameRect.size.height) frameRect.size = newSize - frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height) + frameRect.origin = NSPoint(x: topLeftPoint.x, y: topLeftPoint.y - frameRect.size.height) self.window?.setFrame(frameRect, display: false) candidateView.setNeedsDisplay(candidateView.bounds) } diff --git a/Source/UI/CandidateUI/ctlCandidateVertical.swift b/Source/UI/CandidateUI/ctlCandidateVertical.swift index 8e5c4cd5..dfeb93f7 100644 --- a/Source/UI/CandidateUI/ctlCandidateVertical.swift +++ b/Source/UI/CandidateUI/ctlCandidateVertical.swift @@ -440,10 +440,9 @@ extension ctlCandidateVertical { frameRect = window?.frame ?? NSRect.zero - let topLeftPoint = NSMakePoint( - frameRect.origin.x, frameRect.origin.y + frameRect.size.height) + let topLeftPoint = NSPoint(x: frameRect.origin.x, y: frameRect.origin.y + frameRect.size.height) frameRect.size = newSize - frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height) + frameRect.origin = NSPoint(x: topLeftPoint.x, y: topLeftPoint.y - frameRect.size.height) self.window?.setFrame(frameRect, display: false) candidateView.setNeedsDisplay(candidateView.bounds) } diff --git a/Source/UI/PrefUI/ctlPrefUI.swift b/Source/UI/PrefUI/ctlPrefUI.swift new file mode 100644 index 00000000..048f43f4 --- /dev/null +++ b/Source/UI/PrefUI/ctlPrefUI.swift @@ -0,0 +1,71 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa + +@available(macOS 11.0, *) +class ctlPrefUI { + lazy var controller = PreferencesWindowController( + panes: [ + Preferences.Pane( + identifier: Preferences.PaneIdentifier(rawValue: "General"), + title: NSLocalizedString("General", comment: ""), + toolbarIcon: NSImage( + systemSymbolName: "wrench.and.screwdriver.fill", accessibilityDescription: "General Preferences") + ?? NSImage(named: NSImage.homeTemplateName)! + ) { + suiPrefPaneGeneral() + }, + Preferences.Pane( + identifier: Preferences.PaneIdentifier(rawValue: "Experiences"), + title: NSLocalizedString("Experience", comment: ""), + toolbarIcon: NSImage( + systemSymbolName: "person.fill.questionmark", accessibilityDescription: "Experiences Preferences") + ?? NSImage(named: NSImage.listViewTemplateName)! + ) { + suiPrefPaneExperience() + }, + Preferences.Pane( + identifier: Preferences.PaneIdentifier(rawValue: "Dictionary"), + title: NSLocalizedString("Dictionary", comment: ""), + toolbarIcon: NSImage( + systemSymbolName: "character.book.closed.fill", accessibilityDescription: "Dictionary Preferences") + ?? NSImage(named: NSImage.bookmarksTemplateName)! + ) { + suiPrefPaneDictionary() + }, + Preferences.Pane( + identifier: Preferences.PaneIdentifier(rawValue: "Keyboard"), + title: NSLocalizedString("Keyboard", comment: ""), + toolbarIcon: NSImage( + systemSymbolName: "keyboard.macwindow", accessibilityDescription: "Keyboard Preferences") + ?? NSImage(named: NSImage.actionTemplateName)! + ) { + suiPrefPaneKeyboard() + }, + ], + style: .toolbarItems + ) + static let shared = ctlPrefUI() +} diff --git a/Source/UI/PrefUI/suiPrefPaneDictionary.swift b/Source/UI/PrefUI/suiPrefPaneDictionary.swift new file mode 100644 index 00000000..c99cf168 --- /dev/null +++ b/Source/UI/PrefUI/suiPrefPaneDictionary.swift @@ -0,0 +1,126 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import SwiftUI + +@available(macOS 11.0, *) +struct suiPrefPaneDictionary: View { + private var fdrDefault = mgrLangModel.dataFolderPath(isDefaultFolder: true) + @State private var tbxUserDataPathSpecified: String = + UserDefaults.standard.string(forKey: UserDef.kUserDataFolderSpecified) + ?? mgrLangModel.dataFolderPath(isDefaultFolder: true) + @State private var selAutoReloadUserData: Bool = UserDefaults.standard.bool( + forKey: UserDef.kShouldAutoReloadUserDataFiles) + @State private var selEnableCNS11643: Bool = UserDefaults.standard.bool(forKey: UserDef.kCNS11643Enabled) + @State private var selEnableSymbolInputSupport: Bool = UserDefaults.standard.bool( + forKey: UserDef.kSymbolInputEnabled) + private let contentWidth: Double = 560.0 + + var body: some View { + Preferences.Container(contentWidth: contentWidth) { + Preferences.Section(title: "", bottomDivider: true) { + Text(LocalizedStringKey("Choose your desired user data folder path. Will be omitted if invalid.")) + HStack { + TextField(fdrDefault, text: $tbxUserDataPathSpecified).disabled(true) + .help(tbxUserDataPathSpecified) + Button { + IME.dlgOpenPath.title = NSLocalizedString( + "Choose your desired user data folder.", comment: "") + IME.dlgOpenPath.showsResizeIndicator = true + IME.dlgOpenPath.showsHiddenFiles = true + IME.dlgOpenPath.canChooseFiles = false + IME.dlgOpenPath.canChooseDirectories = true + + let bolPreviousFolderValidity = mgrLangModel.checkIfSpecifiedUserDataFolderValid( + NSString(string: mgrPrefs.userDataFolderSpecified).expandingTildeInPath) + + if let window = ctlPrefUI.shared.controller.window { + IME.dlgOpenPath.beginSheetModal(for: window) { result in + if result == NSApplication.ModalResponse.OK { + if IME.dlgOpenPath.url != nil { + // CommonDialog 讀入的路徑沒有結尾斜槓,這會導致檔案目錄合規性判定失準。 + // 所以要手動補回來。 + var newPath = IME.dlgOpenPath.url!.path + newPath.ensureTrailingSlash() + if mgrLangModel.checkIfSpecifiedUserDataFolderValid(newPath) { + mgrPrefs.userDataFolderSpecified = newPath + tbxUserDataPathSpecified = mgrPrefs.userDataFolderSpecified + IME.initLangModels(userOnly: true) + } else { + clsSFX.beep() + if !bolPreviousFolderValidity { + mgrPrefs.resetSpecifiedUserDataFolder() + } + return + } + } + } else { + if !bolPreviousFolderValidity { + mgrPrefs.resetSpecifiedUserDataFolder() + } + return + } + } + } // End If self.window != nil + } label: { + Text("...") + } + Button { + mgrPrefs.resetSpecifiedUserDataFolder() + tbxUserDataPathSpecified = "" + } label: { + Text("↻") + } + + } + Toggle( + LocalizedStringKey("Automatically reload user data files if changes detected"), + isOn: $selAutoReloadUserData + ).controlSize(.small).onChange(of: selAutoReloadUserData) { (value) in + mgrPrefs.shouldAutoReloadUserDataFiles = value + } + Divider() + Toggle(LocalizedStringKey("Enable CNS11643 Support (2022-01-27)"), isOn: $selEnableCNS11643) + .onChange(of: selEnableCNS11643) { (value) in + mgrPrefs.cns11643Enabled = value + } + Toggle( + LocalizedStringKey("Enable symbol input support (incl. certain emoji symbols)"), + isOn: $selEnableSymbolInputSupport + ) + .onChange(of: selEnableSymbolInputSupport) { (value) in + mgrPrefs.symbolInputEnabled = value + } + + } + } + } +} + +@available(macOS 11.0, *) +struct suiPrefPaneDictionary_Previews: PreviewProvider { + static var previews: some View { + suiPrefPaneDictionary() + } +} diff --git a/Source/UI/PrefUI/suiPrefPaneExperience.swift b/Source/UI/PrefUI/suiPrefPaneExperience.swift new file mode 100644 index 00000000..55c40fc8 --- /dev/null +++ b/Source/UI/PrefUI/suiPrefPaneExperience.swift @@ -0,0 +1,155 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa +import SwiftUI + +@available(macOS 11.0, *) +struct suiPrefPaneExperience: View { + @State private var selSelectionKeysList = mgrPrefs.suggestedCandidateKeys + @State private var selSelectionKeys = + (UserDefaults.standard.string(forKey: UserDef.kCandidateKeys) ?? mgrPrefs.defaultCandidateKeys) as String + @State private var selCursorPosition = + UserDefaults.standard.bool( + forKey: UserDef.kSelectPhraseAfterCursorAsCandidate) ? 1 : 0 + @State private var selPushCursorAfterSelection = UserDefaults.standard.bool( + forKey: UserDef.kMoveCursorAfterSelectingCandidate) + @State private var selKeyBehaviorShiftTab = + UserDefaults.standard.bool(forKey: UserDef.kSpecifyShiftTabKeyBehavior) ? 1 : 0 + @State private var selKeyBehaviorShiftSpace = + UserDefaults.standard.bool( + forKey: UserDef.kSpecifyShiftSpaceKeyBehavior) ? 1 : 0 + @State private var selKeyBehaviorSpaceForCallingCandidate = UserDefaults.standard.bool( + forKey: UserDef.kChooseCandidateUsingSpace) + @State private var selKeyBehaviorESCForClearingTheBuffer = UserDefaults.standard.bool( + forKey: UserDef.kEscToCleanInputBuffer) + @State private var selEnableSCPCTypingMode = UserDefaults.standard.bool(forKey: UserDef.kUseSCPCTypingMode) + private let contentWidth: Double = 560.0 + + var body: some View { + Preferences.Container(contentWidth: contentWidth) { + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Selection Keys:")) }) { + ComboBox(items: mgrPrefs.suggestedCandidateKeys, text: $selSelectionKeys).frame(width: 180).onChange( + of: selSelectionKeys + ) { (value) in + let keys: String = (value.trimmingCharacters(in: .whitespacesAndNewlines) as String).charDeDuplicate + do { + try mgrPrefs.validate(candidateKeys: keys) + mgrPrefs.candidateKeys = keys + selSelectionKeys = mgrPrefs.candidateKeys + } catch mgrPrefs.CandidateKeyError.empty { + selSelectionKeys = mgrPrefs.candidateKeys + } catch { + if let window = ctlPrefUI.shared.controller.window { + let alert = NSAlert(error: error) + alert.beginSheetModal(for: window) { _ in + selSelectionKeys = mgrPrefs.candidateKeys + } + clsSFX.beep() + } + } + } + Text( + LocalizedStringKey( + "Choose or hit Enter to confim your prefered keys for selecting candidates." + ) + ) + .preferenceDescription() + } + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Cursor Selection:")) }) { + Picker("", selection: $selCursorPosition) { + Text(LocalizedStringKey("to the front of the phrase (like Matsushita Hanin IME)")).tag(0) + Text(LocalizedStringKey("to the rear of the phrase (like MS New-Phonetic IME)")).tag(1) + }.onChange(of: selCursorPosition) { (value) in + mgrPrefs.selectPhraseAfterCursorAsCandidate = (value == 1) ? true : false + } + .labelsHidden() + .pickerStyle(RadioGroupPickerStyle()) + Text(LocalizedStringKey("Choose the cursor position where you want to list possible candidates.")) + .preferenceDescription() + Toggle( + LocalizedStringKey("Push the cursor to the front of the phrase after selection"), + isOn: $selPushCursorAfterSelection + ).onChange(of: selPushCursorAfterSelection) { (value) in + mgrPrefs.moveCursorAfterSelectingCandidate = value + }.controlSize(.small) + } + Preferences.Section(title: "(Shift+)Tab:", bottomDivider: true) { + Picker("", selection: $selKeyBehaviorShiftTab) { + Text(LocalizedStringKey("for cycling candidates")).tag(0) + Text(LocalizedStringKey("for cycling pages")).tag(1) + }.onChange(of: selKeyBehaviorShiftTab) { (value) in + mgrPrefs.specifyShiftTabKeyBehavior = (value == 1) ? true : false + } + .labelsHidden() + .horizontalRadioGroupLayout() + .pickerStyle(RadioGroupPickerStyle()) + Text(LocalizedStringKey("Choose the behavior of (Shift+)Tab key in the candidate window.")) + .preferenceDescription() + } + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("(Shift+)Space:")) }) { + Picker("", selection: $selKeyBehaviorShiftSpace) { + Text(LocalizedStringKey("Space to +cycle candidates, Shift+Space to +cycle pages")).tag(0) + Text(LocalizedStringKey("Space to +cycle pages, Shift+Space to +cycle candidates")).tag(1) + }.onChange(of: selKeyBehaviorShiftSpace) { (value) in + mgrPrefs.specifyShiftSpaceKeyBehavior = (value == 1) ? true : false + } + .labelsHidden() + .pickerStyle(RadioGroupPickerStyle()) + Text(LocalizedStringKey("Choose the behavior of (Shift+)Space key in the candidate window.")) + .preferenceDescription() + } + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Space & ESC Key:")) }) { + Toggle( + LocalizedStringKey("Enable Space key for calling candidate window"), + isOn: $selKeyBehaviorSpaceForCallingCandidate + ).onChange(of: selKeyBehaviorSpaceForCallingCandidate) { (value) in + mgrPrefs.chooseCandidateUsingSpace = value + } + Toggle( + LocalizedStringKey("Use ESC key to clear the entire input buffer"), + isOn: $selKeyBehaviorESCForClearingTheBuffer + ).onChange(of: selKeyBehaviorESCForClearingTheBuffer) { (value) in + mgrPrefs.escToCleanInputBuffer = value + } + } + Preferences.Section(label: { Text(LocalizedStringKey("Typing Style:")) }) { + Toggle( + LocalizedStringKey("Emulating select-candidate-per-character mode"), isOn: $selEnableSCPCTypingMode + ).onChange(of: selEnableSCPCTypingMode) { (value) in + mgrPrefs.useSCPCTypingMode = value + } + Text(LocalizedStringKey("An accomodation for elder computer users.")) + .preferenceDescription() + } + } + } +} + +@available(macOS 11.0, *) +struct suiPrefPaneExperience_Previews: PreviewProvider { + static var previews: some View { + suiPrefPaneExperience() + } +} diff --git a/Source/UI/PrefUI/suiPrefPaneGeneral.swift b/Source/UI/PrefUI/suiPrefPaneGeneral.swift new file mode 100644 index 00000000..d8b544bc --- /dev/null +++ b/Source/UI/PrefUI/suiPrefPaneGeneral.swift @@ -0,0 +1,158 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa +import SwiftUI + +@available(macOS 11.0, *) +struct suiPrefPaneGeneral: View { + @State private var selCandidateUIFontSize = UserDefaults.standard.integer(forKey: UserDef.kCandidateListTextSize) + @State private var selUILanguage: [String] = + IME.arrSupportedLocales.contains( + ((UserDefaults.standard.object(forKey: UserDef.kAppleLanguages) == nil) + ? ["auto"] : UserDefaults.standard.array(forKey: UserDef.kAppleLanguages) as? [String] ?? ["auto"])[0]) + ? ((UserDefaults.standard.object(forKey: UserDef.kAppleLanguages) == nil) + ? ["auto"] : UserDefaults.standard.array(forKey: UserDef.kAppleLanguages) as? [String] ?? ["auto"]) + : ["auto"] + @State private var selEnableHorizontalCandidateLayout = UserDefaults.standard.bool( + forKey: UserDef.kUseHorizontalCandidateList) + @State private var selShowPageButtonsInCandidateUI = UserDefaults.standard.bool( + forKey: UserDef.kShowPageButtonsInCandidateWindow) + @State private var selEnableKanjiConvToKangXi = UserDefaults.standard.bool( + forKey: UserDef.kChineseConversionEnabled) + @State private var selEnableKanjiConvToJIS = UserDefaults.standard.bool( + forKey: UserDef.kShiftJISShinjitaiOutputEnabled) + @State private var selEnableFartSuppressor = UserDefaults.standard.bool(forKey: UserDef.kShouldNotFartInLieuOfBeep) + @State private var selEnableAutoUpdateCheck = UserDefaults.standard.bool(forKey: UserDef.kCheckUpdateAutomatically) + @State private var selEnableDebugMode = UserDefaults.standard.bool(forKey: UserDef.kIsDebugModeEnabled) + + private let contentWidth: Double = 560.0 + + var body: some View { + Preferences.Container(contentWidth: contentWidth) { + Preferences.Section(bottomDivider: false, label: { Text(LocalizedStringKey("Candidate Size:")) }) { + Picker("", selection: $selCandidateUIFontSize) { + Text("12").tag(12) + Text("14").tag(14) + Text("16").tag(16) + Text("18").tag(18) + Text("24").tag(24) + Text("32").tag(32) + Text("64").tag(64) + Text("96").tag(96) + }.onChange(of: selCandidateUIFontSize) { (value) in + mgrPrefs.candidateListTextSize = CGFloat(value) + } + .labelsHidden() + .frame(width: 120.0) + Text(LocalizedStringKey("Choose candidate font size for better visual clarity.")) + .preferenceDescription() + } + Preferences.Section(bottomDivider: false, label: { Text(LocalizedStringKey("UI Language:")) }) { + Picker(LocalizedStringKey("Follow OS settings"), selection: $selUILanguage) { + Text(LocalizedStringKey("Follow OS settings")).tag(["auto"]) + Text(LocalizedStringKey("Simplified Chinese")).tag(["zh-Hans"]) + Text(LocalizedStringKey("Traditional Chinese")).tag(["zh-Hant"]) + Text(LocalizedStringKey("Japanese")).tag(["ja"]) + Text(LocalizedStringKey("English")).tag(["en"]) + }.onChange(of: selUILanguage) { (value) in + IME.prtDebugIntel(value[0]) + if selUILanguage == mgrPrefs.appleLanguages + || (selUILanguage[0] == "auto" + && UserDefaults.standard.object(forKey: UserDef.kAppleLanguages) == nil) + { + return + } + if selUILanguage[0] != "auto" { + mgrPrefs.appleLanguages = value + } else { + UserDefaults.standard.removeObject(forKey: UserDef.kAppleLanguages) + } + NSLog("vChewing App self-terminated due to UI language change.") + NSApplication.shared.terminate(nil) + } + .labelsHidden() + .frame(width: 180.0) + + Text(LocalizedStringKey("Change user interface language (will reboot the IME).")) + .preferenceDescription() + } + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Candidate Layout:")) }) { + Picker("", selection: $selEnableHorizontalCandidateLayout) { + Text(LocalizedStringKey("Vertical")).tag(false) + Text(LocalizedStringKey("Horizontal")).tag(true) + }.onChange(of: selEnableHorizontalCandidateLayout) { (value) in + mgrPrefs.useHorizontalCandidateList = value + } + .labelsHidden() + .horizontalRadioGroupLayout() + .pickerStyle(RadioGroupPickerStyle()) + Text(LocalizedStringKey("Choose your preferred layout of the candidate window.")) + .preferenceDescription() + Toggle( + LocalizedStringKey("Show page buttons in candidate window"), isOn: $selShowPageButtonsInCandidateUI + ).controlSize(.small) + } + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Output Settings:")) }) { + Toggle( + LocalizedStringKey("Auto-convert traditional Chinese glyphs to KangXi characters"), + isOn: $selEnableKanjiConvToKangXi + ).onChange(of: selEnableKanjiConvToKangXi) { (value) in + mgrPrefs.chineseConversionEnabled = value + } + Toggle( + LocalizedStringKey("Auto-convert traditional Chinese glyphs to JIS Shinjitai characters"), + isOn: $selEnableKanjiConvToJIS + ).onChange(of: selEnableKanjiConvToJIS) { (value) in + mgrPrefs.shiftJISShinjitaiOutputEnabled = value + } + Toggle( + LocalizedStringKey("Stop farting (when typed phonetic combination is invalid, etc.)"), + isOn: $selEnableFartSuppressor + ).onChange(of: selEnableFartSuppressor) { (value) in + mgrPrefs.shouldNotFartInLieuOfBeep = value + clsSFX.beep() + } + } + Preferences.Section(label: { Text(LocalizedStringKey("Misc Settings:")).controlSize(.small) }) { + Toggle(LocalizedStringKey("Check for updates automatically"), isOn: $selEnableAutoUpdateCheck) + .onChange(of: selEnableAutoUpdateCheck) { (value) in + mgrPrefs.checkUpdateAutomatically = value + } + .controlSize(.small) + Toggle(LocalizedStringKey("Debug Mode"), isOn: $selEnableDebugMode).controlSize(.small) + .onChange(of: selEnableDebugMode) { (value) in + mgrPrefs.isDebugModeEnabled = value + } + } + } + } +} + +@available(macOS 11.0, *) +struct suiPrefPaneGeneral_Previews: PreviewProvider { + static var previews: some View { + suiPrefPaneGeneral() + } +} diff --git a/Source/UI/PrefUI/suiPrefPaneKeyboard.swift b/Source/UI/PrefUI/suiPrefPaneKeyboard.swift new file mode 100644 index 00000000..93772500 --- /dev/null +++ b/Source/UI/PrefUI/suiPrefPaneKeyboard.swift @@ -0,0 +1,98 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import SwiftUI + +@available(macOS 11.0, *) +struct suiPrefPaneKeyboard: View { + @State private var selMandarinParser = UserDefaults.standard.integer(forKey: UserDef.kMandarinParser) + @State private var selBasicKeyboardLayout: String = + UserDefaults.standard.string(forKey: UserDef.kBasicKeyboardLayout) ?? mgrPrefs.basicKeyboardLayout + private let contentWidth: Double = 560.0 + + var body: some View { + Preferences.Container(contentWidth: contentWidth) { + Preferences.Section(label: { Text(LocalizedStringKey("Phonetic Parser:")) }) { + Picker("", selection: $selMandarinParser) { + Text(LocalizedStringKey("Dachen (Microsoft Standard / Wang / 01, etc.)")).tag(0) + Text(LocalizedStringKey("Eten Traditional")).tag(1) + Text(LocalizedStringKey("Eten 26")).tag(3) + Text(LocalizedStringKey("IBM")).tag(4) + Text(LocalizedStringKey("Hsu")).tag(2) + Text(LocalizedStringKey("MiTAC")).tag(5) + Text(LocalizedStringKey("Fake Seigyou")).tag(6) + Text(LocalizedStringKey("Hanyu Pinyin with Numeral Intonation")).tag(10) + }.onChange(of: selMandarinParser) { (value) in + mgrPrefs.mandarinParser = value + } + .labelsHidden() + .frame(width: 320.0) + Text(LocalizedStringKey("Choose the phonetic layout for Mandarin parser.")) + .preferenceDescription() + } + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Basic Keyboard Layout:")) }) { + HStack { + Picker("", selection: $selBasicKeyboardLayout) { + ForEach(0...(IME.arrEnumerateSystemKeyboardLayouts.count - 1), id: \.self) { id in + Text(IME.arrEnumerateSystemKeyboardLayouts[id].strName).tag( + IME.arrEnumerateSystemKeyboardLayouts[id].strValue) + }.id(UUID()) + }.onChange(of: selBasicKeyboardLayout) { (value) in + mgrPrefs.basicKeyboardLayout = value + } + .labelsHidden() + .frame(width: 240.0) + } + Text(LocalizedStringKey("Choose the macOS-level basic keyboard layout.")) + .preferenceDescription() + } + } + Divider() + Preferences.Container(contentWidth: contentWidth) { + Preferences.Section(title: "") { + VStack(alignment: .leading, spacing: 10) { + Text( + LocalizedStringKey( + "Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only." + ) + ) + .preferenceDescription() + Text( + LocalizedStringKey( + "Apple Dynamic Bopomofo Basic Keyboard Layouts (Dachen & Eten Traditional) must match the Dachen parser in order to be functional." + ) + ) + .preferenceDescription() + } + } + } + } +} + +@available(macOS 11.0, *) +struct suiPrefPaneKeyboard_Previews: PreviewProvider { + static var previews: some View { + suiPrefPaneKeyboard() + } +} diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index ce3b8e7d..43f06194 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -30,19 +30,25 @@ 5B707CEC27D9F4870099EF99 /* OpenCC in Frameworks */ = {isa = PBXBuildFile; productRef = 5B707CEB27D9F4870099EF99 /* OpenCC */; }; 5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; }; 5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B7BC4AE27AFFBE800F66C24 /* frmPrefWindow.xib */; }; - 5BA9FD2327FEF39C002DE248 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1627FEF39C002DE248 /* Utilities.swift */; }; - 5BA9FD2427FEF39C002DE248 /* Pane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1727FEF39C002DE248 /* Pane.swift */; }; - 5BA9FD2527FEF39C002DE248 /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1827FEF39C002DE248 /* Localization.swift */; }; - 5BA9FD2627FEF39C002DE248 /* PreferencesStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1927FEF39C002DE248 /* PreferencesStyle.swift */; }; - 5BA9FD2727FEF39C002DE248 /* PreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1A27FEF39C002DE248 /* PreferencePane.swift */; }; - 5BA9FD2827FEF39C002DE248 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1B27FEF39C002DE248 /* Preferences.swift */; }; - 5BA9FD2927FEF39C002DE248 /* SegmentedControlStyleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1C27FEF39C002DE248 /* SegmentedControlStyleViewController.swift */; }; - 5BA9FD2A27FEF39C002DE248 /* ToolbarItemStyleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1D27FEF39C002DE248 /* ToolbarItemStyleViewController.swift */; }; - 5BA9FD2B27FEF39C002DE248 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1E27FEF39C002DE248 /* Container.swift */; }; - 5BA9FD2C27FEF39C002DE248 /* PreferencesStyleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD1F27FEF39C002DE248 /* PreferencesStyleController.swift */; }; - 5BA9FD2D27FEF39C002DE248 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD2027FEF39C002DE248 /* PreferencesWindowController.swift */; }; - 5BA9FD2E27FEF39C002DE248 /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD2127FEF39C002DE248 /* Section.swift */; }; - 5BA9FD2F27FEF39C002DE248 /* PreferencesTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD2227FEF39C002DE248 /* PreferencesTabViewController.swift */; }; + 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 */; }; + 5BA9FD1227FEDB6B002DE248 /* suiPrefPaneExperience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0D27FEDB6B002DE248 /* suiPrefPaneExperience.swift */; }; + 5BA9FD1327FEDB6B002DE248 /* suiPrefPaneDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0E27FEDB6B002DE248 /* suiPrefPaneDictionary.swift */; }; + 5BA9FD3E27FEF3C8002DE248 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3127FEF3C8002DE248 /* Utilities.swift */; }; + 5BA9FD3F27FEF3C8002DE248 /* Pane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3227FEF3C8002DE248 /* Pane.swift */; }; + 5BA9FD4027FEF3C8002DE248 /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3327FEF3C8002DE248 /* Localization.swift */; }; + 5BA9FD4127FEF3C8002DE248 /* PreferencesStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3427FEF3C8002DE248 /* PreferencesStyle.swift */; }; + 5BA9FD4227FEF3C8002DE248 /* PreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3527FEF3C8002DE248 /* PreferencePane.swift */; }; + 5BA9FD4327FEF3C8002DE248 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3627FEF3C8002DE248 /* Preferences.swift */; }; + 5BA9FD4427FEF3C8002DE248 /* SegmentedControlStyleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3727FEF3C8002DE248 /* SegmentedControlStyleViewController.swift */; }; + 5BA9FD4527FEF3C9002DE248 /* ToolbarItemStyleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3827FEF3C8002DE248 /* ToolbarItemStyleViewController.swift */; }; + 5BA9FD4627FEF3C9002DE248 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3927FEF3C8002DE248 /* Container.swift */; }; + 5BA9FD4727FEF3C9002DE248 /* PreferencesStyleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3A27FEF3C8002DE248 /* PreferencesStyleController.swift */; }; + 5BA9FD4827FEF3C9002DE248 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3B27FEF3C8002DE248 /* PreferencesWindowController.swift */; }; + 5BA9FD4927FEF3C9002DE248 /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3C27FEF3C8002DE248 /* Section.swift */; }; + 5BA9FD4A27FEF3C9002DE248 /* PreferencesTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */; }; + 5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */; }; 5BAD0CD527D701F6003D127F /* vChewingKeyLayout.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */; }; 5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */; }; 5BBBB75F27AED54C0023B93A /* Beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75D27AED54C0023B93A /* Beep.m4a */; }; @@ -202,19 +208,25 @@ 5B7BC4AF27AFFBE800F66C24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmPrefWindow.xib; sourceTree = ""; }; 5B7BC4B227AFFC0B00F66C24 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmPrefWindow.strings; sourceTree = ""; }; 5B8F43ED27C9BC220069AC27 /* SymbolLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SymbolLM.h; sourceTree = ""; }; - 5BA9FD1627FEF39C002DE248 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; - 5BA9FD1727FEF39C002DE248 /* Pane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pane.swift; sourceTree = ""; }; - 5BA9FD1827FEF39C002DE248 /* Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; - 5BA9FD1927FEF39C002DE248 /* PreferencesStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesStyle.swift; sourceTree = ""; }; - 5BA9FD1A27FEF39C002DE248 /* PreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencePane.swift; sourceTree = ""; }; - 5BA9FD1B27FEF39C002DE248 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; - 5BA9FD1C27FEF39C002DE248 /* SegmentedControlStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentedControlStyleViewController.swift; sourceTree = ""; }; - 5BA9FD1D27FEF39C002DE248 /* ToolbarItemStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolbarItemStyleViewController.swift; sourceTree = ""; }; - 5BA9FD1E27FEF39C002DE248 /* Container.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; - 5BA9FD1F27FEF39C002DE248 /* PreferencesStyleController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesStyleController.swift; sourceTree = ""; }; - 5BA9FD2027FEF39C002DE248 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; - 5BA9FD2127FEF39C002DE248 /* Section.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; - 5BA9FD2227FEF39C002DE248 /* PreferencesTabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesTabViewController.swift; sourceTree = ""; }; + 5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = suiPrefPaneGeneral.swift; sourceTree = ""; }; + 5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = suiPrefPaneKeyboard.swift; sourceTree = ""; }; + 5BA9FD0C27FEDB6B002DE248 /* ctlPrefUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ctlPrefUI.swift; sourceTree = ""; }; + 5BA9FD0D27FEDB6B002DE248 /* suiPrefPaneExperience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = suiPrefPaneExperience.swift; sourceTree = ""; }; + 5BA9FD0E27FEDB6B002DE248 /* suiPrefPaneDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = suiPrefPaneDictionary.swift; sourceTree = ""; }; + 5BA9FD3127FEF3C8002DE248 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; + 5BA9FD3227FEF3C8002DE248 /* Pane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pane.swift; sourceTree = ""; }; + 5BA9FD3327FEF3C8002DE248 /* Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; + 5BA9FD3427FEF3C8002DE248 /* PreferencesStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesStyle.swift; sourceTree = ""; }; + 5BA9FD3527FEF3C8002DE248 /* PreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencePane.swift; sourceTree = ""; }; + 5BA9FD3627FEF3C8002DE248 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; + 5BA9FD3727FEF3C8002DE248 /* SegmentedControlStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentedControlStyleViewController.swift; sourceTree = ""; }; + 5BA9FD3827FEF3C8002DE248 /* ToolbarItemStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolbarItemStyleViewController.swift; sourceTree = ""; }; + 5BA9FD3927FEF3C8002DE248 /* Container.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; + 5BA9FD3A27FEF3C8002DE248 /* PreferencesStyleController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesStyleController.swift; sourceTree = ""; }; + 5BA9FD3B27FEF3C8002DE248 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; + 5BA9FD3C27FEF3C8002DE248 /* Section.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; + 5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesTabViewController.swift; sourceTree = ""; }; + 5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VDKComboBox.swift; sourceTree = ""; }; 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Menu.swift; sourceTree = ""; }; 5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = ""; }; 5BBBB75E27AED54C0023B93A /* Fart.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.m4a; sourceTree = ""; }; @@ -377,6 +389,7 @@ 5B62A30127AE732800A19448 /* 3rdParty */ = { isa = PBXGroup; children = ( + 5BA9FD8C28006BA7002DE248 /* VDKComboBox */, 5B707CE627D9F43E0099EF99 /* OpenCCBridge */, 5B62A30227AE733500A19448 /* OVMandarin */, 5BA9FCEA27FED652002DE248 /* SindreSorhus */, @@ -509,6 +522,7 @@ isa = PBXGroup; children = ( 5B62A33E27AE7CD900A19448 /* CandidateUI */, + 5BA9FD0927FED9F3002DE248 /* PrefUI */, 5B62A34227AE7CD900A19448 /* TooltipUI */, 5B62A34427AE7CD900A19448 /* NotifierUI */, ); @@ -594,32 +608,52 @@ 5BA9FCEA27FED652002DE248 /* SindreSorhus */ = { isa = PBXGroup; children = ( - 5BA9FD1527FEF39C002DE248 /* Preferences */, + 5BA9FD3027FEF3C8002DE248 /* Preferences */, ); name = SindreSorhus; path = Source/3rdParty/SindreSorhus; sourceTree = SOURCE_ROOT; }; - 5BA9FD1527FEF39C002DE248 /* Preferences */ = { + 5BA9FD0927FED9F3002DE248 /* PrefUI */ = { isa = PBXGroup; children = ( - 5BA9FD1E27FEF39C002DE248 /* Container.swift */, - 5BA9FD1827FEF39C002DE248 /* Localization.swift */, - 5BA9FD1727FEF39C002DE248 /* Pane.swift */, - 5BA9FD1A27FEF39C002DE248 /* PreferencePane.swift */, - 5BA9FD1B27FEF39C002DE248 /* Preferences.swift */, - 5BA9FD1927FEF39C002DE248 /* PreferencesStyle.swift */, - 5BA9FD1F27FEF39C002DE248 /* PreferencesStyleController.swift */, - 5BA9FD2227FEF39C002DE248 /* PreferencesTabViewController.swift */, - 5BA9FD2027FEF39C002DE248 /* PreferencesWindowController.swift */, - 5BA9FD2127FEF39C002DE248 /* Section.swift */, - 5BA9FD1C27FEF39C002DE248 /* SegmentedControlStyleViewController.swift */, - 5BA9FD1D27FEF39C002DE248 /* ToolbarItemStyleViewController.swift */, - 5BA9FD1627FEF39C002DE248 /* Utilities.swift */, + 5BA9FD0C27FEDB6B002DE248 /* ctlPrefUI.swift */, + 5BA9FD0E27FEDB6B002DE248 /* suiPrefPaneDictionary.swift */, + 5BA9FD0D27FEDB6B002DE248 /* suiPrefPaneExperience.swift */, + 5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */, + 5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */, + ); + path = PrefUI; + sourceTree = ""; + }; + 5BA9FD3027FEF3C8002DE248 /* Preferences */ = { + isa = PBXGroup; + children = ( + 5BA9FD3927FEF3C8002DE248 /* Container.swift */, + 5BA9FD3327FEF3C8002DE248 /* Localization.swift */, + 5BA9FD3227FEF3C8002DE248 /* Pane.swift */, + 5BA9FD3527FEF3C8002DE248 /* PreferencePane.swift */, + 5BA9FD3627FEF3C8002DE248 /* Preferences.swift */, + 5BA9FD3427FEF3C8002DE248 /* PreferencesStyle.swift */, + 5BA9FD3A27FEF3C8002DE248 /* PreferencesStyleController.swift */, + 5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */, + 5BA9FD3B27FEF3C8002DE248 /* PreferencesWindowController.swift */, + 5BA9FD3C27FEF3C8002DE248 /* Section.swift */, + 5BA9FD3727FEF3C8002DE248 /* SegmentedControlStyleViewController.swift */, + 5BA9FD3827FEF3C8002DE248 /* ToolbarItemStyleViewController.swift */, + 5BA9FD3127FEF3C8002DE248 /* Utilities.swift */, ); path = Preferences; sourceTree = ""; }; + 5BA9FD8C28006BA7002DE248 /* VDKComboBox */ = { + isa = PBXGroup; + children = ( + 5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */, + ); + path = VDKComboBox; + sourceTree = ""; + }; 5BBBB75C27AED54C0023B93A /* SoundFiles */ = { isa = PBXGroup; children = ( @@ -1022,52 +1056,58 @@ files = ( 5B707CE827D9F4590099EF99 /* OpenCCBridge.swift in Sources */, D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */, + 5BA9FD4527FEF3C9002DE248 /* ToolbarItemStyleViewController.swift in Sources */, + 5BA9FD4127FEF3C8002DE248 /* PreferencesStyle.swift in Sources */, + 5BA9FD1227FEDB6B002DE248 /* suiPrefPaneExperience.swift in Sources */, 6ACC3D442793701600F1B140 /* ParselessPhraseDB.cpp in Sources */, D461B792279DAC010070E734 /* InputState.swift in Sources */, 5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */, D47B92C027972AD100458394 /* main.swift in Sources */, - 5BA9FD2527FEF39C002DE248 /* Localization.swift in Sources */, D44FB74D2792189A003C80A6 /* PhraseReplacementMap.mm in Sources */, - 5BA9FD2E27FEF39C002DE248 /* Section.swift in Sources */, D4A13D5A27A59F0B003BE359 /* ctlInputMethod.swift in Sources */, + 5BA9FD4827FEF3C9002DE248 /* PreferencesWindowController.swift in Sources */, D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */, + 5BA9FD4627FEF3C9002DE248 /* Container.swift in Sources */, D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */, 5B62A32F27AE78B000A19448 /* CoreLM.mm in Sources */, 5BE78BE027B38804005EA1BE /* LMConsolidator.mm in Sources */, D456576E279E4F7B00DF6BC9 /* KeyParser.swift in Sources */, + 5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */, + 5BA9FD4327FEF3C8002DE248 /* Preferences.swift in Sources */, + 5BA9FD4427FEF3C8002DE248 /* SegmentedControlStyleViewController.swift in Sources */, D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */, - 5BA9FD2927FEF39C002DE248 /* SegmentedControlStyleViewController.swift in Sources */, + 5BA9FD4227FEF3C8002DE248 /* PreferencePane.swift in Sources */, + 5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */, D47D73AC27A6CAE600255A50 /* AssociatedPhrases.mm in Sources */, + 5BA9FD4A27FEF3C9002DE248 /* PreferencesTabViewController.swift in Sources */, 5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */, - 5BA9FD2827FEF39C002DE248 /* Preferences.swift in Sources */, - 5BA9FD2D27FEF39C002DE248 /* PreferencesWindowController.swift in Sources */, - 5BA9FD2B27FEF39C002DE248 /* Container.swift in Sources */, - 5BA9FD2427FEF39C002DE248 /* Pane.swift in Sources */, 5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */, D41355DB278E6D17005E5CBD /* LMInstantiator.mm in Sources */, 5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */, D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */, 5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */, 5B62A33827AE79CD00A19448 /* NSStringUtils.swift in Sources */, - 5BA9FD2327FEF39C002DE248 /* Utilities.swift in Sources */, + 5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */, + 5BA9FD4927FEF3C9002DE248 /* Section.swift in Sources */, + 5BA9FD3E27FEF3C8002DE248 /* Utilities.swift in Sources */, + 5BA9FD1127FEDB6B002DE248 /* ctlPrefUI.swift in Sources */, 5B62A33227AE792F00A19448 /* InputSourceHelper.swift in Sources */, - 5BA9FD2C27FEF39C002DE248 /* PreferencesStyleController.swift in Sources */, - 5BA9FD2A27FEF39C002DE248 /* ToolbarItemStyleViewController.swift in Sources */, - 5BA9FD2F27FEF39C002DE248 /* PreferencesTabViewController.swift in Sources */, 5B5E535227EF261400C6AA1E /* IME.swift in Sources */, - 5BA9FD2627FEF39C002DE248 /* PreferencesStyle.swift in Sources */, 5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, 5B62A34827AE7CD900A19448 /* ctlCandidateVertical.swift in Sources */, + 5BA9FD4027FEF3C8002DE248 /* Localization.swift in Sources */, + 5BA9FD1327FEDB6B002DE248 /* suiPrefPaneDictionary.swift in Sources */, 6ACC3D452793701600F1B140 /* ParselessLM.cpp in Sources */, 5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */, + 5BA9FD4727FEF3C9002DE248 /* PreferencesStyleController.swift in Sources */, 5BF8423127BAA942008E7E4C /* vChewingKanjiConverter.swift in Sources */, 5B62A34627AE7CD900A19448 /* ctlCandidateHorizontal.swift in Sources */, 5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */, + 5BA9FD3F27FEF3C8002DE248 /* Pane.swift in Sources */, 5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */, D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */, 6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */, - 5BA9FD2727FEF39C002DE248 /* PreferencePane.swift in Sources */, D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */, 5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */, ); From 07f8d237b1e0850356d4c9ba313b7c42248e8b40 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 10 Apr 2022 14:18:00 +0800 Subject: [PATCH 30/36] PrefUI // Use different pane widths per each UI language. --- Source/UI/PrefUI/suiPrefPaneDictionary.swift | 13 ++++++++++++- Source/UI/PrefUI/suiPrefPaneExperience.swift | 13 ++++++++++++- Source/UI/PrefUI/suiPrefPaneGeneral.swift | 14 ++++++++++++-- Source/UI/PrefUI/suiPrefPaneKeyboard.swift | 13 ++++++++++++- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/Source/UI/PrefUI/suiPrefPaneDictionary.swift b/Source/UI/PrefUI/suiPrefPaneDictionary.swift index c99cf168..fe9d0405 100644 --- a/Source/UI/PrefUI/suiPrefPaneDictionary.swift +++ b/Source/UI/PrefUI/suiPrefPaneDictionary.swift @@ -35,7 +35,18 @@ struct suiPrefPaneDictionary: View { @State private var selEnableCNS11643: Bool = UserDefaults.standard.bool(forKey: UserDef.kCNS11643Enabled) @State private var selEnableSymbolInputSupport: Bool = UserDefaults.standard.bool( forKey: UserDef.kSymbolInputEnabled) - private let contentWidth: Double = 560.0 + private let contentWidth: Double = { + switch mgrPrefs.appleLanguages[0] { + case "ja": + return 520 + default: + if mgrPrefs.appleLanguages[0].contains("zh-Han") { + return 480 + } else { + return 550 + } + } + }() var body: some View { Preferences.Container(contentWidth: contentWidth) { diff --git a/Source/UI/PrefUI/suiPrefPaneExperience.swift b/Source/UI/PrefUI/suiPrefPaneExperience.swift index 55c40fc8..c91b6a80 100644 --- a/Source/UI/PrefUI/suiPrefPaneExperience.swift +++ b/Source/UI/PrefUI/suiPrefPaneExperience.swift @@ -45,7 +45,18 @@ struct suiPrefPaneExperience: View { @State private var selKeyBehaviorESCForClearingTheBuffer = UserDefaults.standard.bool( forKey: UserDef.kEscToCleanInputBuffer) @State private var selEnableSCPCTypingMode = UserDefaults.standard.bool(forKey: UserDef.kUseSCPCTypingMode) - private let contentWidth: Double = 560.0 + private let contentWidth: Double = { + switch mgrPrefs.appleLanguages[0] { + case "ja": + return 520 + default: + if mgrPrefs.appleLanguages[0].contains("zh-Han") { + return 480 + } else { + return 550 + } + } + }() var body: some View { Preferences.Container(contentWidth: contentWidth) { diff --git a/Source/UI/PrefUI/suiPrefPaneGeneral.swift b/Source/UI/PrefUI/suiPrefPaneGeneral.swift index d8b544bc..bf105adc 100644 --- a/Source/UI/PrefUI/suiPrefPaneGeneral.swift +++ b/Source/UI/PrefUI/suiPrefPaneGeneral.swift @@ -46,8 +46,18 @@ struct suiPrefPaneGeneral: View { @State private var selEnableFartSuppressor = UserDefaults.standard.bool(forKey: UserDef.kShouldNotFartInLieuOfBeep) @State private var selEnableAutoUpdateCheck = UserDefaults.standard.bool(forKey: UserDef.kCheckUpdateAutomatically) @State private var selEnableDebugMode = UserDefaults.standard.bool(forKey: UserDef.kIsDebugModeEnabled) - - private let contentWidth: Double = 560.0 + private let contentWidth: Double = { + switch mgrPrefs.appleLanguages[0] { + case "ja": + return 465 + default: + if mgrPrefs.appleLanguages[0].contains("zh-Han") { + return 450 + } else { + return 550 + } + } + }() var body: some View { Preferences.Container(contentWidth: contentWidth) { diff --git a/Source/UI/PrefUI/suiPrefPaneKeyboard.swift b/Source/UI/PrefUI/suiPrefPaneKeyboard.swift index 93772500..896faee5 100644 --- a/Source/UI/PrefUI/suiPrefPaneKeyboard.swift +++ b/Source/UI/PrefUI/suiPrefPaneKeyboard.swift @@ -29,7 +29,18 @@ struct suiPrefPaneKeyboard: View { @State private var selMandarinParser = UserDefaults.standard.integer(forKey: UserDef.kMandarinParser) @State private var selBasicKeyboardLayout: String = UserDefaults.standard.string(forKey: UserDef.kBasicKeyboardLayout) ?? mgrPrefs.basicKeyboardLayout - private let contentWidth: Double = 560.0 + private let contentWidth: Double = { + switch mgrPrefs.appleLanguages[0] { + case "ja": + return 520 + default: + if mgrPrefs.appleLanguages[0].contains("zh-Han") { + return 480 + } else { + return 550 + } + } + }() var body: some View { Preferences.Container(contentWidth: contentWidth) { From ca3d473937a7ef4bb5bc04710702a7468e70f366 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 10 Apr 2022 10:13:21 +0800 Subject: [PATCH 31/36] frmPrefWindow // Sync UI changes from PrefUI. --- .../WindowNIBs/Base.lproj/frmPrefWindow.xib | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib b/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib index 2feb573d..7c56403c 100644 --- a/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib +++ b/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib @@ -163,7 +163,7 @@ - - - - - + - + + @@ -666,9 +649,24 @@ + + @@ -682,7 +680,9 @@ + + @@ -755,9 +755,9 @@ - + From f0d6ade3c7a446c214a32dafb1cd629db6185074 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 11 Apr 2022 08:22:07 +0800 Subject: [PATCH 32/36] ctlIME // SwiftLint: NSMake???? -> NS????. --- .../Modules/IMEModules/ctlInputMethod.swift | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index a2c78f0e..c79812ac 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -245,8 +245,8 @@ extension ctlInputMethod { commit(text: previous.composingBuffer, client: client) } (client as? IMKTextInput)?.setMarkedText( - "", selectionRange: NSMakeRange(0, 0), - replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + "", selectionRange: NSRange(location: 0, length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) } private func handle(state: InputState.Empty, previous: InputState, client: Any?) { @@ -261,8 +261,8 @@ extension ctlInputMethod { commit(text: previous.composingBuffer, client: client) } client.setMarkedText( - "", selectionRange: NSMakeRange(0, 0), - replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + "", selectionRange: NSRange(location: 0, length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) } private func handle( @@ -276,8 +276,8 @@ extension ctlInputMethod { } client.setMarkedText( - "", selectionRange: NSMakeRange(0, 0), - replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + "", selectionRange: NSRange(location: 0, length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) } private func handle(state: InputState.Committing, previous: InputState, client: Any?) { @@ -293,8 +293,8 @@ extension ctlInputMethod { commit(text: poppedText, client: client) } client.setMarkedText( - "", selectionRange: NSMakeRange(0, 0), - replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + "", selectionRange: NSRange(location: 0, length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) } private func handle(state: InputState.Inputting, previous: InputState, client: Any?) { @@ -313,8 +313,8 @@ extension ctlInputMethod { // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, // i.e. the client app needs to take care of where to put this composing buffer client.setMarkedText( - state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), - replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) if !state.tooltip.isEmpty { show( tooltip: state.tooltip, composingBuffer: state.composingBuffer, @@ -332,8 +332,8 @@ extension ctlInputMethod { // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, // i.e. the client app needs to take care of where to put this composing buffer client.setMarkedText( - state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), - replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) if state.tooltip.isEmpty { hideTooltip() @@ -354,8 +354,8 @@ extension ctlInputMethod { // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, // i.e. the client app needs to take care of where to put this composing buffer client.setMarkedText( - state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), - replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) show(candidateWindowWith: state, client: client) } @@ -366,8 +366,8 @@ extension ctlInputMethod { return } client.setMarkedText( - "", selectionRange: NSMakeRange(0, 0), - replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + "", selectionRange: NSRange(location: 0, length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) show(candidateWindowWith: state, client: client) } } @@ -455,7 +455,7 @@ extension ctlInputMethod { ctlCandidateCurrent?.visible = true - var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0) + var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0) var cursor: Int = 0 if let state = state as? InputState.ChoosingCandidate { @@ -473,20 +473,18 @@ extension ctlInputMethod { if useVerticalMode { ctlCandidateCurrent?.set( - windowTopLeftPoint: NSMakePoint( - lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, - lineHeightRect.origin.y - 4.0), + windowTopLeftPoint: NSPoint( + x: lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, y: lineHeightRect.origin.y - 4.0), bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0) } else { ctlCandidateCurrent?.set( - windowTopLeftPoint: NSMakePoint( - lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0), + windowTopLeftPoint: NSPoint(x: lineHeightRect.origin.x, y: lineHeightRect.origin.y - 4.0), bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0) } } private func show(tooltip: String, composingBuffer: String, cursorIndex: UInt, client: Any!) { - var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0) + var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0) var cursor: Int = Int(cursorIndex) if cursor == composingBuffer.count && cursor != 0 { cursor -= 1 From 27af2f569d217ad22c4898eb5d31bd859b551b33 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 10 Apr 2022 19:40:42 +0800 Subject: [PATCH 33/36] ctlIME // Fix IME-windows hang when regaining focus. - Certain IMK functions could result in infinite loops when an IME-domestic Window is being focused regardless whether the current IME is switched to something else). --- .../Modules/IMEModules/ctlInputMethod.swift | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index c79812ac..4b088eb4 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -65,8 +65,9 @@ class ctlInputMethod: IMKInputController { // MARK: - Keyboard Layout Specifier @objc func setKeyLayout() { - let client = client().self as IMKTextInput - client.overrideKeyboard(withKeyboardNamed: mgrPrefs.basicKeyboardLayout) + if let client = currentClient { + (client as? IMKTextInput)?.overrideKeyboard(withKeyboardNamed: mgrPrefs.basicKeyboardLayout) + } } // MARK: - IMKInputController methods @@ -90,14 +91,18 @@ class ctlInputMethod: IMKInputController { override func activateServer(_ client: Any!) { UserDefaults.standard.synchronize() - // Override the keyboard layout to the basic one. - setKeyLayout() // reset the state currentClient = client keyHandler.clear() keyHandler.syncWithPreferences() - self.handle(state: .Empty(), client: client) + if let bundleCheckID = (client as? IMKTextInput)?.bundleIdentifier() { + if bundleCheckID != Bundle.main.bundleIdentifier { + // Override the keyboard layout to the basic one. + setKeyLayout() + self.handle(state: .Empty(), client: client) + } + } (NSApp.delegate as? AppDelegate)?.checkForUpdate() } @@ -120,14 +125,18 @@ class ctlInputMethod: IMKInputController { } mgrLangModel.loadDataModel(newInputMode) - // Remember to override the keyboard layout again -- treat this as an activate event. - setKeyLayout() - if keyHandler.inputMode != newInputMode { + UserDefaults.standard.synchronize() keyHandler.clear() keyHandler.inputMode = newInputMode - self.handle(state: .Empty(), client: client) + if let bundleCheckID = (client as? IMKTextInput)?.bundleIdentifier() { + if bundleCheckID != Bundle.main.bundleIdentifier { + // Remember to override the keyboard layout again -- treat this as an activate event. + setKeyLayout() + self.handle(state: .Empty(), client: client) + } + } } // 讓外界知道目前的簡繁體輸入模式。 From 649a680fd6c50b123e2ba4121803e7965ecafb83 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 11 Apr 2022 00:42:13 +0800 Subject: [PATCH 34/36] ctlIME // Correctly handle results from writeUserPhrase(). --- Source/Modules/IMEModules/ctlInputMethod.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 4b088eb4..afb63f02 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -539,14 +539,17 @@ extension ctlInputMethod: KeyHandlerDelegate { let refInputModeReversed: InputMode = (keyHandler.inputMode == InputMode.imeModeCHT) ? InputMode.imeModeCHS : InputMode.imeModeCHT - mgrLangModel.writeUserPhrase( + if !mgrLangModel.writeUserPhrase( state.userPhrase, inputMode: keyHandler.inputMode, areWeDuplicating: state.chkIfUserPhraseExists, areWeDeleting: ctlInputMethod.areWeDeleting) - mgrLangModel.writeUserPhrase( - state.userPhraseConverted, inputMode: refInputModeReversed, - areWeDuplicating: false, - areWeDeleting: ctlInputMethod.areWeDeleting) + || !mgrLangModel.writeUserPhrase( + state.userPhraseConverted, inputMode: refInputModeReversed, + areWeDuplicating: false, + areWeDeleting: ctlInputMethod.areWeDeleting) + { + return false + } return true } } From 4b87138dc08f1a08c3f6a614809de799ed062c7d Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 9 Apr 2022 00:30:11 +0800 Subject: [PATCH 35/36] mgrLM // Swiftify everything except Cpp-related stuff. - This is not a complete swiftification since all ObjCpp-related parts are not swiftifiable. - Rename invalid folder path target in lieu of removal. - Let dataFolderPath() always ensure trailing slash. - Also added process of verifying folder write access. - Also simplify chkUserLMFilesExist(InputMode). - Also enveloped LMConsolidator into an ObjC command in order to swiftify and refactor writeUserPhrase(). --- Source/Modules/IMEModules/IME.swift | 4 +- .../Modules/LangModelRelated/mgrLangModel.h | 16 +- .../Modules/LangModelRelated/mgrLangModel.mm | 278 ++---------------- .../LangModelRelated/mgrLangModel.swift | 242 +++++++++++++++ vChewing.xcodeproj/project.pbxproj | 4 + 5 files changed, 280 insertions(+), 264 deletions(-) create mode 100644 Source/Modules/LangModelRelated/mgrLangModel.swift diff --git a/Source/Modules/IMEModules/IME.swift b/Source/Modules/IMEModules/IME.swift index 3aa8290a..2616ca25 100644 --- a/Source/Modules/IMEModules/IME.swift +++ b/Source/Modules/IMEModules/IME.swift @@ -85,7 +85,9 @@ import Cocoa // MARK: - Open a phrase data file. static func openPhraseFile(userFileAt path: String) { func checkIfUserFilesExist() -> Bool { - if !mgrLangModel.checkIfUserLanguageModelFilesExist() { + if !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHS) + || !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHT) + { let content = String( format: NSLocalizedString( "Please check the permission at \"%@\".", comment: ""), diff --git a/Source/Modules/LangModelRelated/mgrLangModel.h b/Source/Modules/LangModelRelated/mgrLangModel.h index 816d44cb..b7bfbae1 100644 --- a/Source/Modules/LangModelRelated/mgrLangModel.h +++ b/Source/Modules/LangModelRelated/mgrLangModel.h @@ -35,29 +35,15 @@ NS_ASSUME_NONNULL_BEGIN + (void)loadUserPhrases; + (void)loadUserAssociatedPhrases; + (void)loadUserPhraseReplacement; -+ (BOOL)checkIfUserLanguageModelFilesExist; -+ (BOOL)checkIfUserDataFolderExists; -+ (BOOL)checkIfSpecifiedUserDataFolderValid:(NSString *)folderPath; -+ (NSString *)dataFolderPath:(bool)isDefaultFolder NS_SWIFT_NAME(dataFolderPath(isDefaultFolder:)); + (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase inputMode:(InputMode)mode key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:)); -+ (BOOL)writeUserPhrase:(NSString *)userPhrase - inputMode:(InputMode)mode - areWeDuplicating:(BOOL)areWeDuplicating - areWeDeleting:(BOOL)areWeDeleting; ++ (void)consolidateGivenFile:(NSString *)path shouldCheckPragma:(BOOL)shouldCheckPragma; + (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled; + (void)setCNSEnabled:(BOOL)cnsEnabled; + (void)setSymbolEnabled:(BOOL)symbolEnabled; -+ (NSString *)specifyBundleDataPath:(NSString *)filename; -+ (NSString *)userPhrasesDataPath:(InputMode)mode; -+ (NSString *)userSymbolDataPath:(InputMode)mode; -+ (NSString *)userAssociatedPhrasesDataPath:(InputMode)mode; -+ (NSString *)excludedPhrasesDataPath:(InputMode)mode; -+ (NSString *)phraseReplacementDataPath:(InputMode)mode; - @end /// The following methods are merely for testing. diff --git a/Source/Modules/LangModelRelated/mgrLangModel.mm b/Source/Modules/LangModelRelated/mgrLangModel.mm index 9b055161..081c798d 100644 --- a/Source/Modules/LangModelRelated/mgrLangModel.mm +++ b/Source/Modules/LangModelRelated/mgrLangModel.mm @@ -37,28 +37,16 @@ static vChewing::LMInstantiator gLangModelCHS; static vChewing::UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife); static vChewing::UserOverrideModel gUserOverrideModelCHS(kUserOverrideModelCapacity, kObservedOverrideHalflife); -static NSString *const kUserDataTemplateName = @"template-data"; -static NSString *const kUserAssDataTemplateName = @"template-data"; -static NSString *const kExcludedPhrasesvChewingTemplateName = @"template-exclude-phrases"; -static NSString *const kPhraseReplacementTemplateName = @"template-phrases-replacement"; -static NSString *const kUserSymbolDataTemplateName = @"template-user-symbol-data"; -static NSString *const kTemplateExtension = @".txt"; - @implementation mgrLangModel +// 這個函數無法遷移至 Swift static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing::LMInstantiator &lm) { - Class cls = NSClassFromString(@"ctlInputMethod"); - NSString *dataPath = [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"]; + NSString *dataPath = [mgrLangModel getBundleDataPath:filenameWithoutExtension]; lm.loadLanguageModel([dataPath UTF8String]); } -+ (NSString *)specifyBundleDataPath:(NSString *)filenameWithoutExtension; -{ - Class cls = NSClassFromString(@"ctlInputMethod"); - return [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"]; -} - +// 這個函數無法遷移至 Swift + (void)loadDataModels { if (!gLangModelCHT.isDataModelLoaded()) @@ -67,15 +55,15 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing } if (!gLangModelCHT.isMiscDataLoaded()) { - gLangModelCHT.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]); + gLangModelCHT.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]); } if (!gLangModelCHT.isSymbolDataLoaded()) { - gLangModelCHT.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]); + gLangModelCHT.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]); } if (!gLangModelCHT.isCNSDataLoaded()) { - gLangModelCHT.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]); + gLangModelCHT.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]); } // ----------------- if (!gLangModelCHS.isDataModelLoaded()) @@ -84,18 +72,19 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing } if (!gLangModelCHS.isMiscDataLoaded()) { - gLangModelCHS.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]); + gLangModelCHS.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]); } if (!gLangModelCHS.isSymbolDataLoaded()) { - gLangModelCHS.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]); + gLangModelCHS.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]); } if (!gLangModelCHS.isCNSDataLoaded()) { - gLangModelCHS.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]); + gLangModelCHS.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]); } } +// 這個函數無法遷移至 Swift + (void)loadDataModel:(InputMode)mode { if ([mode isEqualToString:imeModeCHT]) @@ -106,15 +95,15 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing } if (!gLangModelCHT.isMiscDataLoaded()) { - gLangModelCHT.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]); + gLangModelCHT.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]); } if (!gLangModelCHT.isSymbolDataLoaded()) { - gLangModelCHT.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]); + gLangModelCHT.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]); } if (!gLangModelCHT.isCNSDataLoaded()) { - gLangModelCHT.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]); + gLangModelCHT.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]); } } @@ -126,19 +115,20 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing } if (!gLangModelCHS.isMiscDataLoaded()) { - gLangModelCHS.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]); + gLangModelCHS.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]); } if (!gLangModelCHS.isSymbolDataLoaded()) { - gLangModelCHS.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]); + gLangModelCHS.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]); } if (!gLangModelCHS.isCNSDataLoaded()) { - gLangModelCHS.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]); + gLangModelCHS.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]); } } } +// 這個函數無法遷移至 Swift + (void)loadUserPhrases { gLangModelCHT.loadUserPhrases([[self userPhrasesDataPath:imeModeCHT] UTF8String], @@ -149,136 +139,21 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing gLangModelCHS.loadUserSymbolData([[self userSymbolDataPath:imeModeCHS] UTF8String]); } +// 這個函數無法遷移至 Swift + (void)loadUserAssociatedPhrases { gLangModelCHT.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHT] UTF8String]); gLangModelCHS.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHS] UTF8String]); } +// 這個函數無法遷移至 Swift + (void)loadUserPhraseReplacement { gLangModelCHT.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHT] UTF8String]); gLangModelCHS.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHS] UTF8String]); } -+ (BOOL)checkIfUserDataFolderExists -{ - NSString *folderPath = [self dataFolderPath:false]; - BOOL isFolder = NO; - BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder]; - if (folderExist && !isFolder) - { - NSError *error = nil; - [[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error]; - if (error) - { - NSLog(@"Failed to remove folder %@", error); - return NO; - } - folderExist = NO; - } - if (!folderExist) - { - NSError *error = nil; - [[NSFileManager defaultManager] createDirectoryAtPath:folderPath - withIntermediateDirectories:YES - attributes:nil - error:&error]; - if (error) - { - NSLog(@"Failed to create folder %@", error); - return NO; - } - } - return YES; -} - -+ (BOOL)checkIfSpecifiedUserDataFolderValid:(NSString *)folderPath -{ - BOOL isFolder = NO; - BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder]; - if ((folderExist && !isFolder) || (!folderExist)) - { - return NO; - } - return YES; -} - -+ (BOOL)ensureFileExists:(NSString *)filePath - populateWithTemplate:(NSString *)templateBasename - extension:(NSString *)ext -{ - if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) - { - - NSURL *templateURL = [[NSBundle mainBundle] URLForResource:templateBasename withExtension:ext]; - NSData *templateData; - if (templateURL) - { - templateData = [NSData dataWithContentsOfURL:templateURL]; - } - else - { - templateData = [@"" dataUsingEncoding:NSUTF8StringEncoding]; - } - - BOOL result = [templateData writeToFile:filePath atomically:YES]; - if (!result) - { - NSLog(@"Failed to write file"); - return NO; - } - } - return YES; -} - -+ (BOOL)checkIfUserLanguageModelFilesExist -{ - if (![self checkIfUserDataFolderExists]) - return NO; - if (![self ensureFileExists:[self userPhrasesDataPath:imeModeCHS] - populateWithTemplate:kUserDataTemplateName - extension:kTemplateExtension]) - return NO; - if (![self ensureFileExists:[self userPhrasesDataPath:imeModeCHT] - populateWithTemplate:kUserDataTemplateName - extension:kTemplateExtension]) - return NO; - if (![self ensureFileExists:[self userAssociatedPhrasesDataPath:imeModeCHS] - populateWithTemplate:kUserAssDataTemplateName - extension:kTemplateExtension]) - return NO; - if (![self ensureFileExists:[self userAssociatedPhrasesDataPath:imeModeCHT] - populateWithTemplate:kUserAssDataTemplateName - extension:kTemplateExtension]) - return NO; - if (![self ensureFileExists:[self excludedPhrasesDataPath:imeModeCHS] - populateWithTemplate:kExcludedPhrasesvChewingTemplateName - extension:kTemplateExtension]) - return NO; - if (![self ensureFileExists:[self excludedPhrasesDataPath:imeModeCHT] - populateWithTemplate:kExcludedPhrasesvChewingTemplateName - extension:kTemplateExtension]) - return NO; - if (![self ensureFileExists:[self phraseReplacementDataPath:imeModeCHS] - populateWithTemplate:kPhraseReplacementTemplateName - extension:kTemplateExtension]) - return NO; - if (![self ensureFileExists:[self phraseReplacementDataPath:imeModeCHT] - populateWithTemplate:kPhraseReplacementTemplateName - extension:kTemplateExtension]) - return NO; - if (![self ensureFileExists:[self userSymbolDataPath:imeModeCHT] - populateWithTemplate:kUserSymbolDataTemplateName - extension:kTemplateExtension]) - return NO; - if (![self ensureFileExists:[self userSymbolDataPath:imeModeCHS] - populateWithTemplate:kUserSymbolDataTemplateName - extension:kTemplateExtension]) - return NO; - return YES; -} - +// 這個函數無法遷移至 Swift + (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase inputMode:(InputMode)mode key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:)) @@ -297,144 +172,51 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing return NO; } -+ (BOOL)writeUserPhrase:(NSString *)userPhrase - inputMode:(InputMode)mode - areWeDuplicating:(BOOL)areWeDuplicating - areWeDeleting:(BOOL)areWeDeleting +// 這個函數無法遷移至 Swift ++ (void)consolidateGivenFile:(NSString *)path shouldCheckPragma:(BOOL)shouldCheckPragma { - if (![self checkIfUserLanguageModelFilesExist]) - { - return NO; - } - - // BOOL addLineBreakAtFront = NO; - NSString *path = areWeDeleting ? [self excludedPhrasesDataPath:mode] : [self userPhrasesDataPath:mode]; - - NSMutableString *currentMarkedPhrase = [NSMutableString string]; - // if (addLineBreakAtFront) { - // [currentMarkedPhrase appendString:@"\n"]; - // } - [currentMarkedPhrase appendString:userPhrase]; - if (areWeDuplicating && !areWeDeleting) - { - // Do not use ASCII characters to comment here. - // Otherwise, it will be scrambled by cnvHYPYtoBPMF module shipped in the vChewing Phrase Editor. - [currentMarkedPhrase appendString:@"\t#𝙾𝚟𝚎𝚛𝚛𝚒𝚍𝚎"]; - } - [currentMarkedPhrase appendString:@"\n"]; - - NSFileHandle *writeFile = [NSFileHandle fileHandleForUpdatingAtPath:path]; - if (!writeFile) - { - return NO; - } - [writeFile seekToEndOfFile]; - NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding]; - [writeFile writeData:data]; - [writeFile closeFile]; - - // We enforce the format consolidation here, since the pragma header will let the UserPhraseLM bypasses the - // consolidating process on load. - vChewing::LMConsolidator::ConsolidateContent([path UTF8String], false); - - // We use FSEventStream to monitor the change of the user phrase folder, - // so we don't have to load data here unless FSEventStream is disabled by user. - if (!mgrPrefs.shouldAutoReloadUserDataFiles) - { - [self loadUserPhrases]; - } - return YES; -} - -+ (NSString *)dataFolderPath:(bool)isDefaultFolder -{ - // 此處不能用「~」來取代當前使用者目錄名稱。不然的話,一旦輸入法被系統的沙箱干預的話,則反而會定位到沙箱目錄內。 - NSString *appSupportPath = [NSFileManager.defaultManager URLsForDirectory:NSApplicationSupportDirectory - inDomains:NSUserDomainMask][0].path; - NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"vChewing"].stringByExpandingTildeInPath; - if (mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath == userDictPath || isDefaultFolder) - { - return userDictPath; - } - if ([mgrPrefs ifSpecifiedUserDataPathExistsInPlist]) - { - if ([self checkIfSpecifiedUserDataFolderValid:mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath]) - { - return mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath; - } - else - { - [NSUserDefaults.standardUserDefaults removeObjectForKey:@"UserDataFolderSpecified"]; - } - } - return userDictPath; -} - -+ (NSString *)userPhrasesDataPath:(InputMode)mode; -{ - NSString *fileName = [mode isEqualToString:imeModeCHT] ? @"userdata-cht.txt" : @"userdata-chs.txt"; - return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName]; -} - -+ (NSString *)userSymbolDataPath:(InputMode)mode; -{ - NSString *fileName = - [mode isEqualToString:imeModeCHT] ? @"usersymbolphrases-cht.txt" : @"usersymbolphrases-chs.txt"; - return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName]; -} - -+ (NSString *)userAssociatedPhrasesDataPath:(InputMode)mode; -{ - NSString *fileName = - [mode isEqualToString:imeModeCHT] ? @"associatedPhrases-cht.txt" : @"associatedPhrases-chs.txt"; - return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName]; -} - -+ (NSString *)excludedPhrasesDataPath:(InputMode)mode; -{ - NSString *fileName = [mode isEqualToString:imeModeCHT] ? @"exclude-phrases-cht.txt" : @"exclude-phrases-chs.txt"; - return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName]; -} - -+ (NSString *)phraseReplacementDataPath:(InputMode)mode; -{ - NSString *fileName = - [mode isEqualToString:imeModeCHT] ? @"phrases-replacement-cht.txt" : @"phrases-replacement-chs.txt"; - return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName]; + vChewing::LMConsolidator::ConsolidateContent([path UTF8String], shouldCheckPragma); } +// 這個函數無法遷移至 Swift + (vChewing::LMInstantiator *)lmCHT { return &gLangModelCHT; } +// 這個函數無法遷移至 Swift + (vChewing::LMInstantiator *)lmCHS { return &gLangModelCHS; } +// 這個函數無法遷移至 Swift + (vChewing::UserOverrideModel *)userOverrideModelCHT { return &gUserOverrideModelCHT; } +// 這個函數無法遷移至 Swift + (vChewing::UserOverrideModel *)userOverrideModelCHS { return &gUserOverrideModelCHS; } +// 這個函數無法遷移至 Swift + (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled { gLangModelCHT.setPhraseReplacementEnabled(phraseReplacementEnabled); gLangModelCHS.setPhraseReplacementEnabled(phraseReplacementEnabled); } +// 這個函數無法遷移至 Swift + (void)setCNSEnabled:(BOOL)cnsEnabled { gLangModelCHT.setCNSEnabled(cnsEnabled); gLangModelCHS.setCNSEnabled(cnsEnabled); } +// 這個函數無法遷移至 Swift + (void)setSymbolEnabled:(BOOL)symbolEnabled { gLangModelCHT.setSymbolEnabled(symbolEnabled); diff --git a/Source/Modules/LangModelRelated/mgrLangModel.swift b/Source/Modules/LangModelRelated/mgrLangModel.swift new file mode 100644 index 00000000..9b99890a --- /dev/null +++ b/Source/Modules/LangModelRelated/mgrLangModel.swift @@ -0,0 +1,242 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa + +@objc extension mgrLangModel { + + // MARK: - 獲取當前輸入法封包內的原廠核心語彙檔案所在路徑 + static func getBundleDataPath(_ filenameSansExt: String) -> String { + return Bundle.main.path(forResource: filenameSansExt, ofType: "txt")! + } + + // MARK: - 使用者語彙檔案的具體檔案名稱路徑定義 + // Swift 的 appendingPathComponent 需要藉由 URL 完成,最後再用 .path 轉為路徑。 + + static func userPhrasesDataPath(_ mode: InputMode) -> String { + let fileName = (mode == InputMode.imeModeCHT) ? "userdata-cht.txt" : "userdata-chs.txt" + return URL(fileURLWithPath: self.dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path + } + + static func userSymbolDataPath(_ mode: InputMode) -> String { + let fileName = (mode == InputMode.imeModeCHT) ? "usersymbolphrases-cht.txt" : "usersymbolphrases-chs.txt" + return URL(fileURLWithPath: self.dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path + } + + static func userAssociatedPhrasesDataPath(_ mode: InputMode) -> String { + let fileName = (mode == InputMode.imeModeCHT) ? "associatedPhrases-cht.txt" : "associatedPhrases-chs.txt" + return URL(fileURLWithPath: self.dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path + } + + static func excludedPhrasesDataPath(_ mode: InputMode) -> String { + let fileName = (mode == InputMode.imeModeCHT) ? "exclude-phrases-cht.txt" : "exclude-phrases-chs.txt" + return URL(fileURLWithPath: self.dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path + } + + static func phraseReplacementDataPath(_ mode: InputMode) -> String { + let fileName = (mode == InputMode.imeModeCHT) ? "phrases-replacement-cht.txt" : "phrases-replacement-chs.txt" + return URL(fileURLWithPath: self.dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path + } + + // MARK: - 檢查具體的使用者語彙檔案是否存在 + + static func ensureFileExists( + _ filePath: String, populateWithTemplate templateBasename: String = "1145141919810", + extension ext: String = "txt" + ) -> Bool { + if !FileManager.default.fileExists(atPath: filePath) { + let templateURL = Bundle.main.url(forResource: templateBasename, withExtension: ext) + var templateData = Data("".utf8) + if templateBasename != "" { + do { + try templateData = Data(contentsOf: templateURL ?? URL(fileURLWithPath: "")) + } catch { + templateData = Data("".utf8) + } + do { + try templateData.write(to: URL(fileURLWithPath: filePath)) + } catch { + IME.prtDebugIntel("Failed to write file") + return false + } + } + } + return true + } + + static func chkUserLMFilesExist(_ mode: InputMode) -> Bool { + if !self.checkIfUserDataFolderExists() { + return false + } + if !ensureFileExists(userPhrasesDataPath(mode)) + || !ensureFileExists(userAssociatedPhrasesDataPath(mode)) + || !ensureFileExists(excludedPhrasesDataPath(mode)) + || !ensureFileExists(phraseReplacementDataPath(mode)) + || !ensureFileExists(userSymbolDataPath(mode)) + { + return false + } + + return true + } + + // MARK: - 使用者語彙檔案專用目錄的合規性檢查 + + // 一次性檢查給定的目錄是否存在寫入合規性(僅用於偏好設定檢查等初步檢查場合,不做任何糾偏行為) + static func checkIfSpecifiedUserDataFolderValid(_ folderPath: String?) -> Bool { + var isFolder = ObjCBool(false) + let folderExist = FileManager.default.fileExists(atPath: folderPath ?? "", isDirectory: &isFolder) + // The above "&" mutates the "isFolder" value to the real one received by the "folderExist". + + // 路徑沒有結尾斜槓的話,會導致目錄合規性判定失準。 + // 出於每個型別每個函數的自我責任原則,這裡多檢查一遍也不壞。 + var folderPath = folderPath // Convert the incoming constant to a variable. + if isFolder.boolValue { + folderPath?.ensureTrailingSlash() + } + let isFolderWritable = FileManager.default.isWritableFile(atPath: folderPath ?? "") + + if ((folderExist && !isFolder.boolValue) || !folderExist) || !isFolderWritable { + return false + } + + return true + } + + // ⚠︎ 私有函數:檢查且糾偏,不接受任何傳入變數。該函數不用於其他型別。 + // 待辦事項:擇日合併至另一個同類型的函數當中。 + static func checkIfUserDataFolderExists() -> Bool { + let folderPath = mgrLangModel.dataFolderPath(isDefaultFolder: false) + var isFolder = ObjCBool(false) + var folderExist = FileManager.default.fileExists(atPath: folderPath, isDirectory: &isFolder) + // The above "&" mutates the "isFolder" value to the real one received by the "folderExist". + // 發現目標路徑不是目錄的話: + // 如果要找的目標路徑是原廠目標路徑的話,先將這個路徑的所指對象更名、再認為目錄不存在。 + // 如果要找的目標路徑不是原廠目標路徑的話,則直接報錯。 + if folderExist && !isFolder.boolValue { + do { + if self.dataFolderPath(isDefaultFolder: false) + == self.dataFolderPath(isDefaultFolder: true) + { + let formatter = DateFormatter.init() + formatter.dateFormat = "YYYYMMDD-HHMM'Hrs'-ss's'" + let dirAlternative = folderPath + formatter.string(from: Date()) + try FileManager.default.moveItem(atPath: folderPath, toPath: dirAlternative) + } else { + throw folderPath + } + } catch { + print("Failed to make path available at: \(error)") + return false + } + folderExist = false + } + if !folderExist { + do { + try FileManager.default.createDirectory( + atPath: folderPath, + withIntermediateDirectories: true, + attributes: nil) + } catch { + print("Failed to create folder: \(error)") + return false + } + } + return true + } + + // MARK: - 用以讀取使用者語彙檔案目錄的函數,會自動對 mgrPrefs 當中的參數糾偏。 + // 當且僅當 mgrPrefs 當中的參數不合規(比如非實在路徑、或者無權限寫入)時,才會糾偏。 + + static func dataFolderPath(isDefaultFolder: Bool) -> String { + let appSupportPath = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0].path + var userDictPathSpecified = (mgrPrefs.userDataFolderSpecified as NSString).expandingTildeInPath + var userDictPathDefault = + (URL(fileURLWithPath: appSupportPath).appendingPathComponent("vChewing").path as NSString) + .expandingTildeInPath + + userDictPathDefault.ensureTrailingSlash() + userDictPathSpecified.ensureTrailingSlash() + + if (userDictPathSpecified == userDictPathDefault) + || isDefaultFolder + { + return userDictPathDefault + } + if mgrPrefs.ifSpecifiedUserDataPathExistsInPlist() { + if mgrLangModel.checkIfSpecifiedUserDataFolderValid(userDictPathSpecified) { + return userDictPathSpecified + } else { + UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified") + } + } + return userDictPathDefault + } + + // MARK: - 寫入使用者檔案 + static func writeUserPhrase( + _ userPhrase: String?, inputMode mode: InputMode, areWeDuplicating: Bool, areWeDeleting: Bool + ) -> Bool { + if var currentMarkedPhrase: String = userPhrase { + if !self.chkUserLMFilesExist(InputMode.imeModeCHS) + || !self.chkUserLMFilesExist(InputMode.imeModeCHT) + { + return false + } + + let path = areWeDeleting ? self.excludedPhrasesDataPath(mode) : self.userPhrasesDataPath(mode) + + if areWeDuplicating && !areWeDeleting { + // Do not use ASCII characters to comment here. + // Otherwise, it will be scrambled by cnvHYPYtoBPMF + // module shipped in the vChewing Phrase Editor. + currentMarkedPhrase += "\t#𝙾𝚟𝚎𝚛𝚛𝚒𝚍𝚎" + } + currentMarkedPhrase += "\n" + + if let writeFile = FileHandle(forUpdatingAtPath: path), + let data = currentMarkedPhrase.data(using: .utf8) + { + writeFile.seekToEndOfFile() + writeFile.write(data) + writeFile.closeFile() + } else { + return false + } + + // We enforce the format consolidation here, since the pragma header + // will let the UserPhraseLM bypasses the consolidating process on load. + self.consolidate(givenFile: path, shouldCheckPragma: false) + + // We use FSEventStream to monitor possible changes of the user phrase folder, hence the + // lack of the needs of manually load data here unless FSEventStream is disabled by user. + if !mgrPrefs.shouldAutoReloadUserDataFiles { + self.loadUserPhrases() + } + return true + } + return false + } + +} diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 43f06194..1c2e109f 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ 5BA9FD4A27FEF3C9002DE248 /* PreferencesTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */; }; 5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */; }; 5BAD0CD527D701F6003D127F /* vChewingKeyLayout.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */; }; + 5BAEFAD028012565001F42C9 /* mgrLangModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAEFACF28012565001F42C9 /* mgrLangModel.swift */; }; 5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */; }; 5BBBB75F27AED54C0023B93A /* Beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75D27AED54C0023B93A /* Beep.m4a */; }; 5BBBB76027AED54C0023B93A /* Fart.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75E27AED54C0023B93A /* Fart.m4a */; }; @@ -227,6 +228,7 @@ 5BA9FD3C27FEF3C8002DE248 /* Section.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; 5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesTabViewController.swift; sourceTree = ""; }; 5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VDKComboBox.swift; sourceTree = ""; }; + 5BAEFACF28012565001F42C9 /* mgrLangModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = mgrLangModel.swift; sourceTree = ""; }; 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Menu.swift; sourceTree = ""; }; 5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = ""; }; 5BBBB75E27AED54C0023B93A /* Fart.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.m4a; sourceTree = ""; }; @@ -478,6 +480,7 @@ D41355D6278D7409005E5CBD /* mgrLangModel.h */, D495583A27A5C6C4006ADE1C /* mgrLangModel_Privates.h */, D41355D7278D7409005E5CBD /* mgrLangModel.mm */, + 5BAEFACF28012565001F42C9 /* mgrLangModel.swift */, 5B62A32527AE758000A19448 /* SubLanguageModels */, ); path = LangModelRelated; @@ -1086,6 +1089,7 @@ 5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */, D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */, 5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */, + 5BAEFAD028012565001F42C9 /* mgrLangModel.swift in Sources */, 5B62A33827AE79CD00A19448 /* NSStringUtils.swift in Sources */, 5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */, 5BA9FD4927FEF3C9002DE248 /* Section.swift in Sources */, From d70cf4e4afa33c05dfdd9e6f1dfd4dde1622a81c Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 11 Apr 2022 13:44:52 +0800 Subject: [PATCH 36/36] KeyHandler // Typo fix. --- Source/Modules/ControllerModules/KeyHandler.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Modules/ControllerModules/KeyHandler.mm b/Source/Modules/ControllerModules/KeyHandler.mm index 53c297d6..5bcba51a 100644 --- a/Source/Modules/ControllerModules/KeyHandler.mm +++ b/Source/Modules/ControllerModules/KeyHandler.mm @@ -377,14 +377,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } // see if we have composition if Enter/Space is hit and buffer is not empty - // this is bit-OR'ed so that the tone marker key is also taken into account + // we use "OR" conditioning so that the tone marker key is also taken into account composeReading |= (!_bpmfReadingBuffer->isEmpty() && ([input isSpace] || [input isEnter])); if (composeReading) { // combine the reading std::string reading = _bpmfReadingBuffer->syllable().composedString(); - // see if we have a unigram for this + // see if we have an unigram for this if (!_languageModel->hasUnigramsForKey(reading)) { [IME prtDebugIntel:@"B49C0979"]; @@ -1599,7 +1599,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; - (void)_walk { // retrieve the most likely trellis, i.e. a Maximum Likelihood Estimation - // of the best possible Mandarain characters given the input syllables, + // of the best possible Mandarin characters given the input syllables, // using the Viterbi algorithm implemented in the Gramambular library Gramambular::Walker walker(&_builder->grid());