813 lines
32 KiB
Swift
813 lines
32 KiB
Swift
// (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||
// All possible vChewing-specific modifications are of:
|
||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||
// ====================
|
||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||
// ... with NTL restriction stating that:
|
||
// No trademark license is granted to use the trade names, trademarks, service
|
||
// marks, or product names of Contributor, except as required to fulfill notice
|
||
// requirements defined in MIT License.
|
||
|
||
import Cocoa
|
||
|
||
public enum UserDef: String, CaseIterable {
|
||
case kIsDebugModeEnabled = "_DebugMode"
|
||
case kFailureFlagForUOMObservation = "_FailureFlag_UOMObservation"
|
||
case kDeltaOfCalendarYears = "DeltaOfCalendarYears"
|
||
case kMostRecentInputMode = "MostRecentInputMode"
|
||
case kUserDataFolderSpecified = "UserDataFolderSpecified"
|
||
case kCheckUpdateAutomatically = "CheckUpdateAutomatically"
|
||
case kMandarinParser = "MandarinParser"
|
||
case kBasicKeyboardLayout = "BasicKeyboardLayout"
|
||
case kAlphanumericalKeyboardLayout = "AlphanumericalKeyboardLayout"
|
||
case kShowPageButtonsInCandidateWindow = "ShowPageButtonsInCandidateWindow"
|
||
case kCandidateListTextSize = "CandidateListTextSize"
|
||
case kAppleLanguages = "AppleLanguages"
|
||
case kShouldAutoReloadUserDataFiles = "ShouldAutoReloadUserDataFiles"
|
||
case kUseRearCursorMode = "UseRearCursorMode"
|
||
case kUseHorizontalCandidateList = "UseHorizontalCandidateList"
|
||
case kChooseCandidateUsingSpace = "ChooseCandidateUsingSpace"
|
||
case kCNS11643Enabled = "CNS11643Enabled"
|
||
case kSymbolInputEnabled = "SymbolInputEnabled"
|
||
case kChineseConversionEnabled = "ChineseConversionEnabled"
|
||
case kShiftJISShinjitaiOutputEnabled = "ShiftJISShinjitaiOutputEnabled"
|
||
case kCurrencyNumeralsEnabled = "CurrencyNumeralsEnabled"
|
||
case kHalfWidthPunctuationEnabled = "HalfWidthPunctuationEnable"
|
||
case kMoveCursorAfterSelectingCandidate = "MoveCursorAfterSelectingCandidate"
|
||
case kEscToCleanInputBuffer = "EscToCleanInputBuffer"
|
||
case kSpecifyIntonationKeyBehavior = "SecifyIntonationKeyBehavior"
|
||
case kSpecifyShiftBackSpaceKeyBehavior = "SpecifyShiftBackSpaceKeyBehavior"
|
||
case kSpecifyShiftTabKeyBehavior = "SpecifyShiftTabKeyBehavior"
|
||
case kSpecifyShiftSpaceKeyBehavior = "SpecifyShiftSpaceKeyBehavior"
|
||
case kAllowBoostingSingleKanjiAsUserPhrase = "AllowBoostingSingleKanjiAsUserPhrase"
|
||
case kUseSCPCTypingMode = "UseSCPCTypingMode"
|
||
case kMaxCandidateLength = "MaxCandidateLength"
|
||
case kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep"
|
||
case kShowHanyuPinyinInCompositionBuffer = "ShowHanyuPinyinInCompositionBuffer"
|
||
case kInlineDumpPinyinInLieuOfZhuyin = "InlineDumpPinyinInLieuOfZhuyin"
|
||
case kFetchSuggestionsFromUserOverrideModel = "FetchSuggestionsFromUserOverrideModel"
|
||
case kUseFixecCandidateOrderOnSelection = "UseFixecCandidateOrderOnSelection"
|
||
case kAutoCorrectReadingCombination = "AutoCorrectReadingCombination"
|
||
case kAlsoConfirmAssociatedCandidatesByEnter = "AlsoConfirmAssociatedCandidatesByEnter"
|
||
case kKeepReadingUponCompositionError = "KeepReadingUponCompositionError"
|
||
case kTogglingAlphanumericalModeWithLShift = "TogglingAlphanumericalModeWithLShift"
|
||
case kUpperCaseLetterKeyBehavior = "UpperCaseLetterKeyBehavior"
|
||
case kDisableShiftTogglingAlphanumericalMode = "DisableShiftTogglingAlphanumericalMode"
|
||
case kConsolidateContextOnCandidateSelection = "ConsolidateContextOnCandidateSelection"
|
||
case kHardenVerticalPunctuations = "HardenVerticalPunctuations"
|
||
case kTrimUnfinishedReadingsOnCommit = "TrimUnfinishedReadingsOnCommit"
|
||
case kAlwaysShowTooltipTextsHorizontally = "AlwaysShowTooltipTextsHorizontally"
|
||
case kClientsIMKTextInputIncapable = "ClientsIMKTextInputIncapable"
|
||
|
||
case kUseIMKCandidateWindow = "UseIMKCandidateWindow"
|
||
case kHandleDefaultCandidateFontsByLangIdentifier = "HandleDefaultCandidateFontsByLangIdentifier"
|
||
case kShiftKeyAccommodationBehavior = "ShiftKeyAccommodationBehavior"
|
||
|
||
case kCandidateTextFontName = "CandidateTextFontName"
|
||
case kCandidateKeyLabelFontName = "CandidateKeyLabelFontName"
|
||
case kCandidateKeys = "CandidateKeys"
|
||
|
||
case kAssociatedPhrasesEnabled = "AssociatedPhrasesEnabled"
|
||
case kPhraseReplacementEnabled = "PhraseReplacementEnabled"
|
||
|
||
case kUsingHotKeySCPC = "UsingHotKeySCPC"
|
||
case kUsingHotKeyAssociates = "UsingHotKeyAssociates"
|
||
case kUsingHotKeyCNS = "UsingHotKeyCNS"
|
||
case kUsingHotKeyKangXi = "UsingHotKeyKangXi"
|
||
case kUsingHotKeyJIS = "UsingHotKeyJIS"
|
||
case kUsingHotKeyHalfWidthASCII = "UsingHotKeyHalfWidthASCII"
|
||
case kUsingHotKeyCurrencyNumerals = "UsingHotKeyCurrencyNumerals"
|
||
}
|
||
|
||
private let kDefaultCandidateListTextSize: CGFloat = 18
|
||
private let kDefaultMinKeyLabelSize: CGFloat = 10
|
||
private let kMinCandidateListTextSize: CGFloat = 12
|
||
private let kMaxCandidateListTextSize: CGFloat = 196
|
||
|
||
private let kDefaultKeys = "123456789"
|
||
|
||
private let kDefaultBasicKeyboardLayout = "com.apple.keylayout.ZhuyinBopomofo"
|
||
private let kDefaultAlphanumericalKeyboardLayout = "com.apple.keylayout.ABC"
|
||
|
||
// MARK: - UserDefaults extension.
|
||
|
||
extension UserDefaults {
|
||
func setDefault(_ value: Any?, forKey defaultName: String) {
|
||
if object(forKey: defaultName) == nil {
|
||
set(value, forKey: defaultName)
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - Property wrappers
|
||
|
||
@propertyWrapper
|
||
struct UserDefault<Value> {
|
||
let key: String
|
||
let defaultValue: Value
|
||
var container: UserDefaults = .standard
|
||
|
||
var wrappedValue: Value {
|
||
get {
|
||
container.object(forKey: key) as? Value ?? defaultValue
|
||
}
|
||
set {
|
||
container.set(newValue, forKey: key)
|
||
}
|
||
}
|
||
}
|
||
|
||
@propertyWrapper
|
||
struct CandidateListTextSize {
|
||
let key: String
|
||
let defaultValue: CGFloat = kDefaultCandidateListTextSize
|
||
lazy var container: UserDefault = .init(key: key, defaultValue: defaultValue)
|
||
|
||
var wrappedValue: CGFloat {
|
||
mutating get {
|
||
var value = container.wrappedValue
|
||
if value < kMinCandidateListTextSize {
|
||
value = kMinCandidateListTextSize
|
||
} else if value > kMaxCandidateListTextSize {
|
||
value = kMaxCandidateListTextSize
|
||
}
|
||
return value
|
||
}
|
||
set {
|
||
var value = newValue
|
||
if value < kMinCandidateListTextSize {
|
||
value = kMinCandidateListTextSize
|
||
} else if value > kMaxCandidateListTextSize {
|
||
value = kMaxCandidateListTextSize
|
||
}
|
||
container.wrappedValue = value
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: -
|
||
|
||
enum MandarinParser: Int, CaseIterable {
|
||
case ofStandard = 0
|
||
case ofETen = 1
|
||
case ofHsu = 2
|
||
case ofETen26 = 3
|
||
case ofIBM = 4
|
||
case ofMiTAC = 5
|
||
case ofFakeSeigyou = 6
|
||
case ofDachen26 = 7
|
||
case ofSeigyou = 8
|
||
case ofStarlight = 9
|
||
case ofHanyuPinyin = 10
|
||
case ofSecondaryPinyin = 11
|
||
case ofYalePinyin = 12
|
||
case ofHualuoPinyin = 13
|
||
case ofUniversalPinyin = 14
|
||
|
||
var name: String {
|
||
switch self {
|
||
case .ofStandard:
|
||
return "Standard"
|
||
case .ofETen:
|
||
return "ETen"
|
||
case .ofHsu:
|
||
return "Hsu"
|
||
case .ofETen26:
|
||
return "ETen26"
|
||
case .ofIBM:
|
||
return "IBM"
|
||
case .ofMiTAC:
|
||
return "MiTAC"
|
||
case .ofFakeSeigyou:
|
||
return "FakeSeigyou"
|
||
case .ofDachen26:
|
||
return "Dachen26"
|
||
case .ofSeigyou:
|
||
return "Seigyou"
|
||
case .ofStarlight:
|
||
return "Starlight"
|
||
case .ofHanyuPinyin:
|
||
return "HanyuPinyin"
|
||
case .ofSecondaryPinyin:
|
||
return "SecondaryPinyin"
|
||
case .ofYalePinyin:
|
||
return "YalePinyin"
|
||
case .ofHualuoPinyin:
|
||
return "HualuoPinyin"
|
||
case .ofUniversalPinyin:
|
||
return "UniversalPinyin"
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: -
|
||
|
||
public enum mgrPrefs {
|
||
public static func setMissingDefaults() {
|
||
UserDefaults.standard.setDefault(mgrPrefs.isDebugModeEnabled, forKey: UserDef.kIsDebugModeEnabled.rawValue)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.failureFlagForUOMObservation, forKey: UserDef.kFailureFlagForUOMObservation.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.deltaOfCalendarYears, forKey: UserDef.kDeltaOfCalendarYears.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(mgrPrefs.mostRecentInputMode, forKey: UserDef.kMostRecentInputMode.rawValue)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.checkUpdateAutomatically, forKey: UserDef.kCheckUpdateAutomatically.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.showPageButtonsInCandidateWindow, forKey: UserDef.kShowPageButtonsInCandidateWindow.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(mgrPrefs.symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled.rawValue)
|
||
UserDefaults.standard.setDefault(mgrPrefs.candidateListTextSize, forKey: UserDef.kCandidateListTextSize.rawValue)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.chooseCandidateUsingSpace, forKey: UserDef.kChooseCandidateUsingSpace.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.shouldAutoReloadUserDataFiles, forKey: UserDef.kShouldAutoReloadUserDataFiles.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.specifyIntonationKeyBehavior, forKey: UserDef.kSpecifyIntonationKeyBehavior.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.specifyShiftBackSpaceKeyBehavior, forKey: UserDef.kSpecifyShiftBackSpaceKeyBehavior.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.specifyShiftTabKeyBehavior, forKey: UserDef.kSpecifyShiftTabKeyBehavior.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.specifyShiftSpaceKeyBehavior, forKey: UserDef.kSpecifyShiftSpaceKeyBehavior.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(mgrPrefs.useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode.rawValue)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.useRearCursorMode, forKey: UserDef.kUseRearCursorMode.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.moveCursorAfterSelectingCandidate, forKey: UserDef.kMoveCursorAfterSelectingCandidate.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.useHorizontalCandidateList, forKey: UserDef.kUseHorizontalCandidateList.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(mgrPrefs.cns11643Enabled, forKey: UserDef.kCNS11643Enabled.rawValue)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.showHanyuPinyinInCompositionBuffer, forKey: UserDef.kShowHanyuPinyinInCompositionBuffer.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.inlineDumpPinyinInLieuOfZhuyin, forKey: UserDef.kInlineDumpPinyinInLieuOfZhuyin.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.allowBoostingSingleKanjiAsUserPhrase, forKey: UserDef.kAllowBoostingSingleKanjiAsUserPhrase.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.fetchSuggestionsFromUserOverrideModel, forKey: UserDef.kFetchSuggestionsFromUserOverrideModel.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.useFixecCandidateOrderOnSelection, forKey: UserDef.kUseFixecCandidateOrderOnSelection.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.autoCorrectReadingCombination, forKey: UserDef.kAutoCorrectReadingCombination.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.alsoConfirmAssociatedCandidatesByEnter, forKey: UserDef.kAlsoConfirmAssociatedCandidatesByEnter.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.currencyNumeralsEnabled, forKey: UserDef.kCurrencyNumeralsEnabled.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.keepReadingUponCompositionError, forKey: UserDef.kKeepReadingUponCompositionError.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.togglingAlphanumericalModeWithLShift, forKey: UserDef.kTogglingAlphanumericalModeWithLShift.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.upperCaseLetterKeyBehavior, forKey: UserDef.kUpperCaseLetterKeyBehavior.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.disableShiftTogglingAlphanumericalMode, forKey: UserDef.kDisableShiftTogglingAlphanumericalMode.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.consolidateContextOnCandidateSelection, forKey: UserDef.kConsolidateContextOnCandidateSelection.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.hardenVerticalPunctuations, forKey: UserDef.kHardenVerticalPunctuations.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.trimUnfinishedReadingsOnCommit, forKey: UserDef.kTrimUnfinishedReadingsOnCommit.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.clientsIMKTextInputIncapable, forKey: UserDef.kClientsIMKTextInputIncapable.rawValue
|
||
)
|
||
|
||
// -----
|
||
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.useIMKCandidateWindow, forKey: UserDef.kUseIMKCandidateWindow.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.handleDefaultCandidateFontsByLangIdentifier,
|
||
forKey: UserDef.kHandleDefaultCandidateFontsByLangIdentifier.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.shiftKeyAccommodationBehavior, forKey: UserDef.kShiftKeyAccommodationBehavior.rawValue
|
||
)
|
||
|
||
// -----
|
||
|
||
UserDefaults.standard.setDefault(mgrPrefs.usingHotKeySCPC, forKey: UserDef.kUsingHotKeySCPC.rawValue)
|
||
UserDefaults.standard.setDefault(mgrPrefs.usingHotKeyAssociates, forKey: UserDef.kUsingHotKeyAssociates.rawValue)
|
||
UserDefaults.standard.setDefault(mgrPrefs.usingHotKeyCNS, forKey: UserDef.kUsingHotKeyCNS.rawValue)
|
||
UserDefaults.standard.setDefault(mgrPrefs.usingHotKeyKangXi, forKey: UserDef.kUsingHotKeyKangXi.rawValue)
|
||
UserDefaults.standard.setDefault(mgrPrefs.usingHotKeyJIS, forKey: UserDef.kUsingHotKeyJIS.rawValue)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.usingHotKeyHalfWidthASCII, forKey: UserDef.kUsingHotKeyHalfWidthASCII.rawValue
|
||
)
|
||
UserDefaults.standard.setDefault(
|
||
mgrPrefs.usingHotKeyCurrencyNumerals, forKey: UserDef.kUsingHotKeyCurrencyNumerals.rawValue
|
||
)
|
||
|
||
UserDefaults.standard.synchronize()
|
||
}
|
||
|
||
// MARK: - Settings (Tier 1)
|
||
|
||
@UserDefault(key: UserDef.kIsDebugModeEnabled.rawValue, defaultValue: false)
|
||
static var isDebugModeEnabled: Bool
|
||
|
||
@UserDefault(key: UserDef.kFailureFlagForUOMObservation.rawValue, defaultValue: false)
|
||
static var failureFlagForUOMObservation: Bool
|
||
|
||
@UserDefault(key: UserDef.kDeltaOfCalendarYears.rawValue, defaultValue: -2000)
|
||
static var deltaOfCalendarYears: Int
|
||
|
||
@UserDefault(key: UserDef.kMostRecentInputMode.rawValue, defaultValue: "")
|
||
static var mostRecentInputMode: String
|
||
|
||
@UserDefault(key: UserDef.kCheckUpdateAutomatically.rawValue, defaultValue: false)
|
||
static var checkUpdateAutomatically: Bool
|
||
|
||
@UserDefault(key: UserDef.kUserDataFolderSpecified.rawValue, defaultValue: "")
|
||
static var userDataFolderSpecified: String
|
||
|
||
static func ifSpecifiedUserDataPathExistsInPlist() -> Bool {
|
||
UserDefaults.standard.object(forKey: UserDef.kUserDataFolderSpecified.rawValue) != nil
|
||
}
|
||
|
||
static func resetSpecifiedUserDataFolder() {
|
||
UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified")
|
||
IME.initLangModels(userOnly: true)
|
||
}
|
||
|
||
@UserDefault(key: UserDef.kAppleLanguages.rawValue, defaultValue: [])
|
||
static var appleLanguages: [String]
|
||
|
||
@UserDefault(key: UserDef.kMandarinParser.rawValue, defaultValue: 0)
|
||
static var mandarinParser: Int
|
||
|
||
static var mandarinParserName: String {
|
||
(MandarinParser(rawValue: mandarinParser) ?? MandarinParser.ofStandard).name
|
||
}
|
||
|
||
@UserDefault(
|
||
key: UserDef.kBasicKeyboardLayout.rawValue, defaultValue: kDefaultBasicKeyboardLayout
|
||
)
|
||
static var basicKeyboardLayout: String
|
||
|
||
@UserDefault(
|
||
key: UserDef.kAlphanumericalKeyboardLayout.rawValue, defaultValue: kDefaultAlphanumericalKeyboardLayout
|
||
)
|
||
static var alphanumericalKeyboardLayout: String
|
||
|
||
@UserDefault(key: UserDef.kShowPageButtonsInCandidateWindow.rawValue, defaultValue: true)
|
||
static var showPageButtonsInCandidateWindow: Bool
|
||
|
||
@CandidateListTextSize(key: UserDef.kCandidateListTextSize.rawValue)
|
||
static var candidateListTextSize: CGFloat
|
||
|
||
static var minKeyLabelSize: CGFloat { kDefaultMinKeyLabelSize }
|
||
|
||
@UserDefault(key: UserDef.kShouldAutoReloadUserDataFiles.rawValue, defaultValue: true)
|
||
static var shouldAutoReloadUserDataFiles: Bool
|
||
|
||
@UserDefault(key: UserDef.kUseRearCursorMode.rawValue, defaultValue: false)
|
||
static var useRearCursorMode: Bool
|
||
|
||
@UserDefault(key: UserDef.kMoveCursorAfterSelectingCandidate.rawValue, defaultValue: true)
|
||
static var moveCursorAfterSelectingCandidate: Bool
|
||
|
||
@UserDefault(key: UserDef.kUseHorizontalCandidateList.rawValue, defaultValue: true)
|
||
static var useHorizontalCandidateList: Bool
|
||
|
||
@UserDefault(key: UserDef.kChooseCandidateUsingSpace.rawValue, defaultValue: true)
|
||
static var chooseCandidateUsingSpace: Bool
|
||
|
||
@UserDefault(key: UserDef.kAllowBoostingSingleKanjiAsUserPhrase.rawValue, defaultValue: false)
|
||
static var allowBoostingSingleKanjiAsUserPhrase: Bool
|
||
|
||
@UserDefault(key: UserDef.kFetchSuggestionsFromUserOverrideModel.rawValue, defaultValue: true)
|
||
static var fetchSuggestionsFromUserOverrideModel: Bool
|
||
|
||
@UserDefault(key: UserDef.kUseFixecCandidateOrderOnSelection.rawValue, defaultValue: false)
|
||
static var useFixecCandidateOrderOnSelection: Bool
|
||
|
||
@UserDefault(key: UserDef.kAutoCorrectReadingCombination.rawValue, defaultValue: true)
|
||
static var autoCorrectReadingCombination: Bool
|
||
|
||
@UserDefault(key: UserDef.kAlsoConfirmAssociatedCandidatesByEnter.rawValue, defaultValue: true)
|
||
static var alsoConfirmAssociatedCandidatesByEnter: Bool
|
||
|
||
@UserDefault(key: UserDef.kKeepReadingUponCompositionError.rawValue, defaultValue: false)
|
||
static var keepReadingUponCompositionError: Bool
|
||
|
||
@UserDefault(key: UserDef.kUpperCaseLetterKeyBehavior.rawValue, defaultValue: 0)
|
||
static var upperCaseLetterKeyBehavior: Int
|
||
|
||
@UserDefault(key: UserDef.kTogglingAlphanumericalModeWithLShift.rawValue, defaultValue: true)
|
||
static var togglingAlphanumericalModeWithLShift: Bool
|
||
|
||
@UserDefault(key: UserDef.kDisableShiftTogglingAlphanumericalMode.rawValue, defaultValue: false)
|
||
static var disableShiftTogglingAlphanumericalMode: Bool
|
||
|
||
@UserDefault(key: UserDef.kConsolidateContextOnCandidateSelection.rawValue, defaultValue: true)
|
||
static var consolidateContextOnCandidateSelection: Bool
|
||
|
||
@UserDefault(key: UserDef.kHardenVerticalPunctuations.rawValue, defaultValue: false)
|
||
static var hardenVerticalPunctuations: Bool
|
||
|
||
@UserDefault(key: UserDef.kTrimUnfinishedReadingsOnCommit.rawValue, defaultValue: true)
|
||
static var trimUnfinishedReadingsOnCommit: Bool
|
||
|
||
@UserDefault(key: UserDef.kAlwaysShowTooltipTextsHorizontally.rawValue, defaultValue: false)
|
||
static var alwaysShowTooltipTextsHorizontally: Bool
|
||
|
||
@UserDefault(key: UserDef.kClientsIMKTextInputIncapable.rawValue, defaultValue: ["com.valvesoftware.steam"])
|
||
static var clientsIMKTextInputIncapable: [String]
|
||
|
||
// MARK: - Settings (Tier 2)
|
||
|
||
@UserDefault(key: UserDef.kUseIMKCandidateWindow.rawValue, defaultValue: false)
|
||
static var useIMKCandidateWindow: Bool
|
||
|
||
@UserDefault(key: UserDef.kHandleDefaultCandidateFontsByLangIdentifier.rawValue, defaultValue: false)
|
||
static var handleDefaultCandidateFontsByLangIdentifier: Bool
|
||
|
||
@UserDefault(key: UserDef.kShiftKeyAccommodationBehavior.rawValue, defaultValue: 0)
|
||
static var shiftKeyAccommodationBehavior: Int
|
||
|
||
// MARK: - Settings (Tier 3)
|
||
|
||
static var minCandidateLength: Int {
|
||
mgrPrefs.allowBoostingSingleKanjiAsUserPhrase ? 1 : 2
|
||
}
|
||
|
||
@UserDefault(key: UserDef.kMaxCandidateLength.rawValue, defaultValue: 10)
|
||
static var maxCandidateLength: Int
|
||
|
||
static var allowedMarkLengthRange: ClosedRange<Int> { mgrPrefs.minCandidateLength...mgrPrefs.maxCandidateLength }
|
||
|
||
@UserDefault(key: UserDef.kShouldNotFartInLieuOfBeep.rawValue, defaultValue: true)
|
||
static var shouldNotFartInLieuOfBeep: Bool
|
||
|
||
@UserDefault(key: UserDef.kShowHanyuPinyinInCompositionBuffer.rawValue, defaultValue: false)
|
||
static var showHanyuPinyinInCompositionBuffer: Bool
|
||
|
||
@UserDefault(key: UserDef.kInlineDumpPinyinInLieuOfZhuyin.rawValue, defaultValue: false)
|
||
static var inlineDumpPinyinInLieuOfZhuyin: Bool
|
||
|
||
static func toggleShouldNotFartInLieuOfBeep() -> Bool {
|
||
shouldNotFartInLieuOfBeep = !shouldNotFartInLieuOfBeep
|
||
UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep.rawValue)
|
||
return shouldNotFartInLieuOfBeep
|
||
}
|
||
|
||
@UserDefault(key: UserDef.kCNS11643Enabled.rawValue, defaultValue: false)
|
||
static var cns11643Enabled: Bool
|
||
|
||
static func toggleCNS11643Enabled() -> Bool {
|
||
cns11643Enabled = !cns11643Enabled
|
||
mgrLangModel.setCNSEnabled(cns11643Enabled) // 很重要
|
||
UserDefaults.standard.set(cns11643Enabled, forKey: UserDef.kCNS11643Enabled.rawValue)
|
||
return cns11643Enabled
|
||
}
|
||
|
||
@UserDefault(key: UserDef.kSymbolInputEnabled.rawValue, defaultValue: true)
|
||
static var symbolInputEnabled: Bool
|
||
|
||
static func toggleSymbolInputEnabled() -> Bool {
|
||
symbolInputEnabled = !symbolInputEnabled
|
||
mgrLangModel.setSymbolEnabled(symbolInputEnabled) // 很重要
|
||
UserDefaults.standard.set(symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled.rawValue)
|
||
return symbolInputEnabled
|
||
}
|
||
|
||
@UserDefault(key: UserDef.kChineseConversionEnabled.rawValue, defaultValue: false)
|
||
static var chineseConversionEnabled: Bool {
|
||
didSet {
|
||
// 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況
|
||
if chineseConversionEnabled, shiftJISShinjitaiOutputEnabled {
|
||
toggleShiftJISShinjitaiOutputEnabled()
|
||
UserDefaults.standard.set(
|
||
shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled.rawValue
|
||
)
|
||
}
|
||
UserDefaults.standard.set(
|
||
chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled.rawValue
|
||
)
|
||
}
|
||
}
|
||
|
||
@discardableResult static func toggleChineseConversionEnabled() -> Bool {
|
||
chineseConversionEnabled = !chineseConversionEnabled
|
||
return chineseConversionEnabled
|
||
}
|
||
|
||
@UserDefault(key: UserDef.kShiftJISShinjitaiOutputEnabled.rawValue, defaultValue: false)
|
||
static var shiftJISShinjitaiOutputEnabled: Bool {
|
||
didSet {
|
||
// 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況
|
||
if shiftJISShinjitaiOutputEnabled, chineseConversionEnabled {
|
||
toggleChineseConversionEnabled()
|
||
UserDefaults.standard.set(
|
||
chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled.rawValue
|
||
)
|
||
}
|
||
UserDefaults.standard.set(
|
||
shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled.rawValue
|
||
)
|
||
}
|
||
}
|
||
|
||
@discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool {
|
||
shiftJISShinjitaiOutputEnabled = !shiftJISShinjitaiOutputEnabled
|
||
return shiftJISShinjitaiOutputEnabled
|
||
}
|
||
|
||
@UserDefault(key: UserDef.kCurrencyNumeralsEnabled.rawValue, defaultValue: false)
|
||
static var currencyNumeralsEnabled: Bool
|
||
|
||
static func toggleCurrencyNumeralsEnabled() -> Bool {
|
||
currencyNumeralsEnabled = !currencyNumeralsEnabled
|
||
return currencyNumeralsEnabled
|
||
}
|
||
|
||
@UserDefault(key: UserDef.kHalfWidthPunctuationEnabled.rawValue, defaultValue: false)
|
||
static var halfWidthPunctuationEnabled: Bool
|
||
|
||
static func toggleHalfWidthPunctuationEnabled() -> Bool {
|
||
halfWidthPunctuationEnabled = !halfWidthPunctuationEnabled
|
||
return halfWidthPunctuationEnabled
|
||
}
|
||
|
||
@UserDefault(key: UserDef.kEscToCleanInputBuffer.rawValue, defaultValue: true)
|
||
static var escToCleanInputBuffer: Bool
|
||
|
||
@UserDefault(key: UserDef.kSpecifyIntonationKeyBehavior.rawValue, defaultValue: 0)
|
||
static var specifyIntonationKeyBehavior: Int
|
||
|
||
@UserDefault(key: UserDef.kSpecifyShiftBackSpaceKeyBehavior.rawValue, defaultValue: 0)
|
||
static var specifyShiftBackSpaceKeyBehavior: Int
|
||
|
||
@UserDefault(key: UserDef.kSpecifyShiftTabKeyBehavior.rawValue, defaultValue: false)
|
||
static var specifyShiftTabKeyBehavior: Bool
|
||
|
||
@UserDefault(key: UserDef.kSpecifyShiftSpaceKeyBehavior.rawValue, defaultValue: false)
|
||
static var specifyShiftSpaceKeyBehavior: Bool
|
||
|
||
// MARK: - Optional settings
|
||
|
||
@UserDefault(key: UserDef.kCandidateTextFontName.rawValue, defaultValue: nil)
|
||
static var candidateTextFontName: String?
|
||
|
||
@UserDefault(key: UserDef.kCandidateKeyLabelFontName.rawValue, defaultValue: nil)
|
||
static var candidateKeyLabelFontName: String?
|
||
|
||
@UserDefault(key: UserDef.kCandidateKeys.rawValue, defaultValue: kDefaultKeys)
|
||
static var candidateKeys: String {
|
||
didSet {
|
||
if mgrPrefs.useIMKCandidateWindow {
|
||
mgrPrefs.candidateKeys = kDefaultKeys
|
||
}
|
||
}
|
||
}
|
||
|
||
static var defaultCandidateKeys: String {
|
||
kDefaultKeys
|
||
}
|
||
|
||
static var suggestedCandidateKeys: [String] {
|
||
[kDefaultKeys, "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB"]
|
||
}
|
||
|
||
static func validate(candidateKeys: String) throws {
|
||
let trimmed = candidateKeys.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
if trimmed.isEmpty {
|
||
throw CandidateKeyError.empty
|
||
}
|
||
if !trimmed.canBeConverted(to: .ascii) {
|
||
throw CandidateKeyError.invalidCharacters
|
||
}
|
||
if trimmed.contains(" ") {
|
||
throw CandidateKeyError.containSpace
|
||
}
|
||
if trimmed.count < 4 {
|
||
throw CandidateKeyError.tooShort
|
||
}
|
||
if trimmed.count > 15 {
|
||
throw CandidateKeyError.tooLong
|
||
}
|
||
let set = Set(Array(trimmed))
|
||
if set.count != trimmed.count {
|
||
throw CandidateKeyError.duplicatedCharacters
|
||
}
|
||
}
|
||
|
||
enum CandidateKeyError: Error, LocalizedError {
|
||
case empty
|
||
case invalidCharacters
|
||
case containSpace
|
||
case duplicatedCharacters
|
||
case tooShort
|
||
case tooLong
|
||
|
||
var errorDescription: String? {
|
||
switch self {
|
||
case .empty:
|
||
return NSLocalizedString("Candidates keys cannot be empty.", comment: "")
|
||
case .invalidCharacters:
|
||
return NSLocalizedString(
|
||
"Candidate keys can only contain ASCII characters like alphanumericals.",
|
||
comment: ""
|
||
)
|
||
case .containSpace:
|
||
return NSLocalizedString("Candidate keys cannot contain space.", comment: "")
|
||
case .duplicatedCharacters:
|
||
return NSLocalizedString("There should not be duplicated keys.", comment: "")
|
||
case .tooShort:
|
||
return NSLocalizedString(
|
||
"Please specify at least 4 candidate keys.", comment: ""
|
||
)
|
||
case .tooLong:
|
||
return NSLocalizedString("Maximum 15 candidate keys allowed.", comment: "")
|
||
}
|
||
}
|
||
}
|
||
|
||
@UserDefault(key: UserDef.kUseSCPCTypingMode.rawValue, defaultValue: false)
|
||
static var useSCPCTypingMode: Bool {
|
||
willSet {
|
||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
|
||
mgrLangModel.loadUserSCPCSequencesData()
|
||
}
|
||
}
|
||
}
|
||
|
||
static func toggleSCPCTypingModeEnabled() -> Bool {
|
||
useSCPCTypingMode = !useSCPCTypingMode
|
||
UserDefaults.standard.set(useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode.rawValue)
|
||
return useSCPCTypingMode
|
||
}
|
||
|
||
@UserDefault(key: UserDef.kPhraseReplacementEnabled.rawValue, defaultValue: false)
|
||
static var phraseReplacementEnabled: Bool {
|
||
willSet {
|
||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
|
||
mgrLangModel.loadUserPhraseReplacement()
|
||
}
|
||
}
|
||
}
|
||
|
||
static func togglePhraseReplacementEnabled() -> Bool {
|
||
phraseReplacementEnabled = !phraseReplacementEnabled
|
||
mgrLangModel.setPhraseReplacementEnabled(phraseReplacementEnabled)
|
||
UserDefaults.standard.set(phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled.rawValue)
|
||
return phraseReplacementEnabled
|
||
}
|
||
|
||
@UserDefault(key: UserDef.kAssociatedPhrasesEnabled.rawValue, defaultValue: false)
|
||
static var associatedPhrasesEnabled: Bool {
|
||
willSet {
|
||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
|
||
mgrLangModel.loadUserAssociatesData()
|
||
}
|
||
}
|
||
}
|
||
|
||
static func toggleAssociatedPhrasesEnabled() -> Bool {
|
||
associatedPhrasesEnabled = !associatedPhrasesEnabled
|
||
UserDefaults.standard.set(associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled.rawValue)
|
||
return associatedPhrasesEnabled
|
||
}
|
||
|
||
// MARK: - Keyboard HotKey Enable / Disable
|
||
|
||
@UserDefault(key: UserDef.kUsingHotKeySCPC.rawValue, defaultValue: true)
|
||
static var usingHotKeySCPC: Bool
|
||
|
||
@UserDefault(key: UserDef.kUsingHotKeyAssociates.rawValue, defaultValue: true)
|
||
static var usingHotKeyAssociates: Bool
|
||
|
||
@UserDefault(key: UserDef.kUsingHotKeyCNS.rawValue, defaultValue: true)
|
||
static var usingHotKeyCNS: Bool
|
||
|
||
@UserDefault(key: UserDef.kUsingHotKeyKangXi.rawValue, defaultValue: true)
|
||
static var usingHotKeyKangXi: Bool
|
||
|
||
@UserDefault(key: UserDef.kUsingHotKeyJIS.rawValue, defaultValue: true)
|
||
static var usingHotKeyJIS: Bool
|
||
|
||
@UserDefault(key: UserDef.kUsingHotKeyHalfWidthASCII.rawValue, defaultValue: true)
|
||
static var usingHotKeyHalfWidthASCII: Bool
|
||
|
||
@UserDefault(key: UserDef.kUsingHotKeyCurrencyNumerals.rawValue, defaultValue: true)
|
||
static var usingHotKeyCurrencyNumerals: Bool
|
||
}
|
||
|
||
// MARK: Auto parameter fix procedures, executed everytime on ctlInputMethod.activateServer().
|
||
|
||
extension mgrPrefs {
|
||
static func fixOddPreferences() {
|
||
// 防呆。macOS 10.11 用 IMK 選字窗會崩潰,macOS 10.13 的 IMK 選字窗仍有問題。
|
||
// 一般人想用的 IMK 選字窗基於 macOS 10.09 系統內建的注音輸入法的那種矩陣選字窗。
|
||
// 然而,該選字窗的體驗直到 macOS 10.14 開始才在 IMKCandidates 當中正式提供。
|
||
if #unavailable(macOS 10.14) {
|
||
mgrPrefs.useIMKCandidateWindow = false
|
||
}
|
||
if #unavailable(macOS 10.15) {
|
||
mgrPrefs.handleDefaultCandidateFontsByLangIdentifier = false
|
||
mgrPrefs.shiftKeyAccommodationBehavior = 0
|
||
mgrPrefs.disableShiftTogglingAlphanumericalMode = false
|
||
mgrPrefs.togglingAlphanumericalModeWithLShift = false
|
||
}
|
||
// 客體黑名單自動排序去重複。
|
||
clientsIMKTextInputIncapable = Array(Set(clientsIMKTextInputIncapable)).sorted()
|
||
// 注拼槽注音排列選項糾錯。
|
||
var isMandarinParserOptionValid = false
|
||
MandarinParser.allCases.forEach {
|
||
if $0.rawValue == mandarinParser { isMandarinParserOptionValid = true }
|
||
}
|
||
if !isMandarinParserOptionValid {
|
||
mandarinParser = 0
|
||
}
|
||
// 基礎鍵盤排列選項糾錯。
|
||
if TISInputSource.generate(from: mgrPrefs.basicKeyboardLayout) == nil {
|
||
mgrPrefs.basicKeyboardLayout = kDefaultBasicKeyboardLayout
|
||
}
|
||
if TISInputSource.generate(from: mgrPrefs.alphanumericalKeyboardLayout) == nil {
|
||
mgrPrefs.alphanumericalKeyboardLayout = kDefaultAlphanumericalKeyboardLayout
|
||
}
|
||
// 其它多元選項參數自動糾錯。
|
||
if ![0, 1, 2].contains(specifyIntonationKeyBehavior) {
|
||
specifyIntonationKeyBehavior = 0
|
||
}
|
||
if ![0, 1, 2].contains(specifyShiftBackSpaceKeyBehavior) {
|
||
specifyShiftBackSpaceKeyBehavior = 0
|
||
}
|
||
if ![0, 1, 2].contains(upperCaseLetterKeyBehavior) {
|
||
upperCaseLetterKeyBehavior = 0
|
||
}
|
||
if ![0, 1, 2].contains(shiftKeyAccommodationBehavior) {
|
||
shiftKeyAccommodationBehavior = 0
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: Snapshot Extension
|
||
|
||
var snapshot: [String: Any]?
|
||
|
||
extension mgrPrefs {
|
||
func reset() {
|
||
UserDef.allCases.forEach {
|
||
UserDefaults.standard.removeObject(forKey: $0.rawValue)
|
||
}
|
||
}
|
||
|
||
func makeSnapshot() -> [String: Any] {
|
||
var dict = [String: Any]()
|
||
UserDef.allCases.forEach {
|
||
dict[$0.rawValue] = UserDefaults.standard.object(forKey: $0.rawValue)
|
||
}
|
||
return dict
|
||
}
|
||
|
||
func restore(from snapshot: [String: Any]) {
|
||
UserDef.allCases.forEach {
|
||
UserDefaults.standard.set(snapshot[$0.rawValue], forKey: $0.rawValue)
|
||
}
|
||
}
|
||
}
|