From 8249c612ff0556d2ea65917abc388bf479dd8734 Mon Sep 17 00:00:00 2001 From: zonble Date: Sun, 16 Jan 2022 23:30:34 +0800 Subject: [PATCH] Adds a validator for candidate keys in the preference window. --- Source/Preferences.swift | 89 +++++++++++++++--------- Source/PreferencesWindowController.swift | 30 +++++--- Source/en.lproj/Localizable.strings | 13 ++++ Source/zh-Hant.lproj/Localizable.strings | 12 ++++ 4 files changed, 100 insertions(+), 44 deletions(-) diff --git a/Source/Preferences.swift b/Source/Preferences.swift index 61ecead0..538484b8 100644 --- a/Source/Preferences.swift +++ b/Source/Preferences.swift @@ -146,36 +146,6 @@ struct ComposingBufferSize { } } -@propertyWrapper -struct ComposingKeys { - let key: String - let defaultValue: String? = kCandidateKeys - lazy var container: UserDefault = { - UserDefault(key: key, defaultValue: defaultValue) }() - - var wrappedValue: String? { - mutating get { - let value = container.wrappedValue - if let value = value { - if value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - return nil - } - } - return value - } - set { - let value = newValue - if let value = value { - if value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - container.wrappedValue = nil - return - } - } - container.wrappedValue = value - } - } -} - // MARK: - @objc enum KeyboardLayout: Int { @@ -293,12 +263,65 @@ class Preferences: NSObject { @UserDefault(key: kCandidateKeyLabelFontName, defaultValue: nil) @objc static var candidateKeyLabelFontName: String? - @ComposingKeys(key: kCandidateKeys) - @objc static var candidateKeys: String? + @UserDefault(key: kCandidateKeys, defaultValue: kDefaultKeys) + @objc static var candidateKeys: String - @objc static var defaultKeys: String { + @objc static var defaultCandidateKeys: String { kDefaultKeys } + @objc static var suggestedCandidateKeys: [String] { + [kDefaultKeys, "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 latin characters and numbers.", 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("The length of your candidate keys can not be less than 4 characters.", comment: "") + case .tooLong: + return NSLocalizedString("The length of your candidate keys can not be larger than 15 characters.", comment: "") + } + } + + } @UserDefault(key: kPhraseReplacementEnabledKey, defaultValue: false) @objc static var phraseReplacementEnabled: Bool diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index 55e27407..4d756da8 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -126,11 +126,11 @@ import Carbon basisKeyboardLayoutButton.select(chosenItem ?? usKeyboardLayoutItem) selectionKeyComboBox.usesDataSource = false selectionKeyComboBox.removeAllItems() - selectionKeyComboBox.addItems(withObjectValues: [Preferences.defaultKeys, "asdfghjkl", "asdfzxcvb"]) + selectionKeyComboBox.addItems(withObjectValues: Preferences.suggestedCandidateKeys) - var candidateSelectionKeys = Preferences.candidateKeys ?? Preferences.defaultKeys + var candidateSelectionKeys = Preferences.candidateKeys if candidateSelectionKeys.isEmpty { - candidateSelectionKeys = Preferences.defaultKeys + candidateSelectionKeys = Preferences.defaultCandidateKeys } selectionKeyComboBox.stringValue = candidateSelectionKeys @@ -143,16 +143,24 @@ import Carbon } @IBAction func changeSelectionKeyAction(_ sender: Any) { - let keys = (sender as AnyObject).stringValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - if keys.count != 9 || !keys.canBeConverted(to: .ascii) { - selectionKeyComboBox.stringValue = Preferences.defaultKeys - Preferences.candidateKeys = nil - NSSound.beep() + guard let keys = (sender as AnyObject).stringValue?.trimmingCharacters(in: .whitespacesAndNewlines) else { return } - - selectionKeyComboBox.stringValue = keys - Preferences.candidateKeys = keys + do { + try Preferences.validate(candidateKeys: keys) + Preferences.candidateKeys = keys + } + catch Preferences.CandidateKeyError.empty { + selectionKeyComboBox.stringValue = Preferences.candidateKeys + } + catch { + if let window = window { + let alert = NSAlert(error: error) + alert.beginSheetModal(for: window) { response in + self.selectionKeyComboBox.stringValue = Preferences.candidateKeys + } + } + } } } diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 2f07e92d..dade91ce 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -76,3 +76,16 @@ "Edit Phrase Replacement Table" = "Edit Phrase Replacement Table"; "Use Phrase Replacement" = "Use Phrase Replacement"; + +"Candidates keys cannot be empty." = "Candidates keys cannot be empty."; + +"Candidate keys can only contain latin characters and numbers." = "Candidate keys can only contain latin characters and numbers."; + +"Candidate keys cannot contain space." = "Candidate keys cannot contain space."; + +"There should not be duplicated keys." = "There should not be duplicated keys."; + +"The length of your candidate keys can not be less than 4 characters." = "The length of your candidate keys can not be less than 4 characters."; + +"The length of your candidate keys can not be larger than 15 characters." = "The length of your candidate keys can not be larger than 15 characters."; + diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index c68a3e42..12785f77 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -76,3 +76,15 @@ "Edit Phrase Replacement Table" = "編輯詞彙替換表格"; "Use Phrase Replacement" = "使用詞彙替換"; + +"Candidates keys cannot be empty." = "選字鍵不可為空。"; + +"Candidate keys can only contain latin characters and numbers." = "選字鍵只可包含英數與半型標點。"; + +"Candidate keys cannot contain space." = "選字鍵不可包含空白。"; + +"There should not be duplicated keys." = "選字鍵中不可包含重複的字元。"; + +"The length of your candidate keys can not be less than 4 characters." = "選字按鍵數量不可小於 4。"; + +"The length of your candidate keys can not be larger than 15 characters." = "選字按鍵數量不可大於 15。";