From 0e192c4efb8d3833f16c750c60fc60f54c6ff375 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 6 Feb 2022 20:48:23 +0800 Subject: [PATCH] Reset // Let AboutWindow and PrefWindow work. --- Source/Modules/AppDelegate.swift | 16 +- .../IMEModules/PreferencesModule.swift | 4 +- .../Modules/IMEModules/ctlInputMethod.swift | 5 +- Source/Resources/Base.lproj/InfoPlist.strings | 3 +- Source/Resources/en.lproj/InfoPlist.strings | 6 + Source/Resources/en.lproj/Localizable.strings | 94 +++++++++ Source/WindowControllers/ctlPrefWindow.swift | 134 ++++++++---- .../WindowNIBs/Base.lproj/frmPrefWindow.xib} | 59 ++++-- .../WindowNIBs/en.lproj/frmPrefWindow.strings | 192 ++++++++++++++++++ vChewing.xcodeproj/project.pbxproj | 32 +-- 10 files changed, 470 insertions(+), 75 deletions(-) create mode 100644 Source/Resources/en.lproj/InfoPlist.strings create mode 100644 Source/Resources/en.lproj/Localizable.strings rename Source/WindowNIBs/{Base.lproj/frmPreferences.xib => Source/WindowNIBs/Base.lproj/frmPrefWindow.xib} (95%) create mode 100644 Source/WindowNIBs/Source/WindowNIBs/en.lproj/frmPrefWindow.strings diff --git a/Source/Modules/AppDelegate.swift b/Source/Modules/AppDelegate.swift index f5a5664a..3403d09d 100644 --- a/Source/Modules/AppDelegate.swift +++ b/Source/Modules/AppDelegate.swift @@ -165,12 +165,20 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega } @objc func showPreferences() { - if ctlPrefWindowInstance == nil { + if (ctlPrefWindowInstance == nil) { ctlPrefWindowInstance = ctlPrefWindow.init(windowNibName: "frmPrefWindow") } ctlPrefWindowInstance?.window?.center() ctlPrefWindowInstance?.window?.orderFrontRegardless() // 逼著屬性視窗往最前方顯示 } + + // New About Window + @objc func showAbout() { + if (ctlAboutWindowInstance == nil) { + ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow") + } + ctlAboutWindowInstance?.window?.center() + ctlAboutWindowInstance?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示 } @objc(checkForUpdate) @@ -244,4 +252,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega func ctlNonModalAlertWindowDidCancel(_ controller: ctlNonModalAlertWindow) { updateNextStepURL = nil } + + // New About Window + @IBAction func about(_ sender: Any) { + (NSApp.delegate as? AppDelegate)?.showAbout() + NSApplication.shared.activate(ignoringOtherApps: true) + } } diff --git a/Source/Modules/IMEModules/PreferencesModule.swift b/Source/Modules/IMEModules/PreferencesModule.swift index a6f8b535..1ddee3fa 100644 --- a/Source/Modules/IMEModules/PreferencesModule.swift +++ b/Source/Modules/IMEModules/PreferencesModule.swift @@ -21,7 +21,7 @@ private let kShouldAutoSortPhraseReplacementMapOnLoad = "ShouldAutoSortPhraseRep private let kSelectPhraseAfterCursorAsCandidatePreference = "SelectPhraseAfterCursorAsCandidate" private let kUseHorizontalCandidateListPreference = "UseHorizontalCandidateList" private let kComposingBufferSizePreference = "ComposingBufferSize" -private let kChooseCandidateUsingSpace = "ChooseCandidateUsingSpaceKey" +private let kChooseCandidateUsingSpace = "ChooseCandidateUsingSpace" private let kCNS11643Enabled = "CNS11643Enabled" private let kChineseConversionEnabled = "ChineseConversionEnabled" private let kHalfWidthPunctuationEnabled = "HalfWidthPunctuationEnable" @@ -331,7 +331,7 @@ struct ComposingBufferSize { @UserDefault(key: kShouldAutoSortPhraseReplacementMapOnLoad, defaultValue: false) @objc static var shouldAutoSortPhraseReplacementMapOnLoad: Bool - @UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreference, defaultValue: false) + @UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreference, defaultValue: true) @objc static var selectPhraseAfterCursorAsCandidate: Bool @UserDefault(key: kMoveCursorAfterSelectingCandidate, defaultValue: false) diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index fcc861aa..02598347 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -170,7 +170,8 @@ class ctlInputMethod: IMKInputController { // MARK: - Menu Items @objc override func showPreferences(_ sender: Any?) { - super.showPreferences(sender) + (NSApp.delegate as? AppDelegate)?.showPreferences() + NSApp.activate(ignoringOtherApps: true) } @objc func toggleChineseConverter(_ sender: Any?) { @@ -231,7 +232,7 @@ class ctlInputMethod: IMKInputController { } @objc func showAbout(_ sender: Any?) { - NSApp.orderFrontStandardAboutPanel(sender) + (NSApp.delegate as? AppDelegate)?.showAbout() NSApp.activate(ignoringOtherApps: true) } diff --git a/Source/Resources/Base.lproj/InfoPlist.strings b/Source/Resources/Base.lproj/InfoPlist.strings index bd58d749..37e86649 100644 --- a/Source/Resources/Base.lproj/InfoPlist.strings +++ b/Source/Resources/Base.lproj/InfoPlist.strings @@ -1,5 +1,6 @@ CFBundleName = "vChewing"; CFBundleDisplayName = "vChewing"; -NSHumanReadableCopyright = "Copyright © 2011-2022 Mengjuei Hsieh et al.\nAll Rights Reserved."; +NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; "org.atelierInmu.inputmethod.vChewing.IMECHT" = "vChewing-CHT"; "org.atelierInmu.inputmethod.vChewing.IMECHS" = "vChewing-CHS"; +CFEULAContent = "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"; diff --git a/Source/Resources/en.lproj/InfoPlist.strings b/Source/Resources/en.lproj/InfoPlist.strings new file mode 100644 index 00000000..37e86649 --- /dev/null +++ b/Source/Resources/en.lproj/InfoPlist.strings @@ -0,0 +1,6 @@ +CFBundleName = "vChewing"; +CFBundleDisplayName = "vChewing"; +NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; +"org.atelierInmu.inputmethod.vChewing.IMECHT" = "vChewing-CHT"; +"org.atelierInmu.inputmethod.vChewing.IMECHS" = "vChewing-CHS"; +CFEULAContent = "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"; diff --git a/Source/Resources/en.lproj/Localizable.strings b/Source/Resources/en.lproj/Localizable.strings new file mode 100644 index 00000000..9a2bfde2 --- /dev/null +++ b/Source/Resources/en.lproj/Localizable.strings @@ -0,0 +1,94 @@ +/* No comment provided by engineer. */ +"About vChewing…" = "About vChewing…"; + +/* No comment provided by engineer. */ +"vChewing Preferences" = "vChewing Preferences"; + +/* No comment provided by engineer. */ +"Check for Updates…" = "Check for Updates…"; + +/* No comment provided by engineer. */ +"Update Check Failed" = "Update Check Failed"; + +/* No comment provided by engineer. */ +"There may be no internet connection or the server failed to respond.\n\nError message: %@" = "There may be no internet connection or the server failed to respond.\n\nError message: %@"; + +/* No comment provided by engineer. */ +"OK" = "OK"; + +/* No comment provided by engineer. */ +"Dismiss" = "Dismiss"; + +/* No comment provided by engineer. */ +"New Version Available" = "New Version Available"; + +/* No comment provided by engineer. */ +"Not Now" = "Not Now"; + +/* No comment provided by engineer. */ +"Visit Website" = "Visit Website"; + +/* No comment provided by engineer. */ +"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@"; + +"Chinese Conversion" = "Convert to Simplified Chinese"; + +"User Phrases" = "User Phrases"; + +"Edit User Phrases" = "Edit User Phrases"; + +"Reload User Phrases" = "Reload User Phrases"; + +"Unable to create the user phrase file." = "Unable to create the user phrase file."; + +"Please check the permission of at \"%@\"." = "Please check the permission of at \"%@\"."; + +"Edit Excluded Phrases" = "Edit Excluded Phrases"; + +"Use Half-Width Punctuations" = "Use Half-Width Punctuations"; + +"You are now selecting \"%@\". You can add a phrase with two or more characters." = "You are now selecting \"%@\". You can add a phrase with two or more characters."; + +"You are now selecting \"%@\". Press enter to add a new phrase." = "You are now selecting \"%@\". Press enter to add a new phrase."; + +"You are now selecting \"%@\". A phrase cannot be longer than %d characters." = "You are now selecting \"%@\". A phrase cannot be longer than %d characters."; + +"Chinese conversion on" = "Chinese conversion on"; + +"Chinese conversion off" = "Chinese conversion off"; + +"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."; + +"Phrase replacement mode is on. Not suggested to add phrase in the mode." = "Phrase replacement mode is on. Not suggested to add phrase in the mode."; + +"Model based Chinese conversion is on. Not suggested to add phrase in the mode." = "Model based Chinese conversion is on. Not suggested to add phrase in the mode."; + +"Half-width punctuation on" = "Half-width punctuation on"; + +"Half-width punctuation off" = "Half-width punctuation off"; + +"Associated Phrases" = "Associated Phrases"; + +"There are special phrases in your text. We don't support adding new phrases in this case." = "There are special phrases in your text. We don't support adding new phrases in this case."; + +"Cursor is before \"%@\"." = "Cursor is before \"%@\"."; + +"Cursor is after \"%@\"." = "Cursor is after \"%@\"."; + +"Cursor is between \"%@\" and \"%@\"." = "Cursor is between \"%@\" and \"%@\"."; + +"You are now selecting \"%@\". The phrase already exists." = "You are now selecting \"%@\". The phrase already exists."; diff --git a/Source/WindowControllers/ctlPrefWindow.swift b/Source/WindowControllers/ctlPrefWindow.swift index 271c9cee..49c75618 100644 --- a/Source/WindowControllers/ctlPrefWindow.swift +++ b/Source/WindowControllers/ctlPrefWindow.swift @@ -1,16 +1,61 @@ +/* + * ctlPrefWindow.swift + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ import Cocoa import Carbon -// Please note that the class should be exposed as "ctlPrefWindow" +// 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 "InputMethodServerctlPrefWindowClass" in Info.plist. +// the "InputMethodServerPreferencesWindowControllerClass" in Info.plist. @objc(ctlPrefWindow) class ctlPrefWindow: NSWindowController { @IBOutlet weak var fontSizePopUpButton: NSPopUpButton! + @IBOutlet weak var uiLanguageButton: NSPopUpButton! @IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton! @IBOutlet weak var selectionKeyComboBox: NSComboBox! + @IBOutlet weak var clickedWhetherIMEShouldNotFartToggle: NSButton! + + var currentLanguageSelectItem: NSMenuItem? = nil override func awakeFromNib() { + let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"] + var autoSelectItem: NSMenuItem? = nil + var chosenLanguageItem: NSMenuItem? = nil + uiLanguageButton.menu?.removeAllItems() + + let appleLanguages = Preferences.appleLanguages + for language in languages { + let menuItem = NSMenuItem() + menuItem.title = NSLocalizedString(language, comment: "") + menuItem.representedObject = language + + if language == "auto" { + autoSelectItem = menuItem + } + + if !appleLanguages.isEmpty { + if appleLanguages[0] == language { + chosenLanguageItem = menuItem + } + } + uiLanguageButton.menu?.addItem(menuItem) + } + + currentLanguageSelectItem = chosenLanguageItem ?? autoSelectItem + uiLanguageButton.select(currentLanguageSelectItem) + let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] var usKeyboardLayoutItem: NSMenuItem? = nil var chosenItem: NSMenuItem? = nil @@ -18,6 +63,7 @@ import Carbon basisKeyboardLayoutButton.menu?.removeAllItems() let basisKeyboardLayoutID = Preferences.basisKeyboardLayout + for source in list { func getString(_ key: CFString) -> String? { @@ -67,21 +113,6 @@ import Carbon menuItem.title = localizedName menuItem.representedObject = sourceID - if let iconPtr = TISGetInputSourceProperty(source, kTISPropertyIconRef) { - let icon = IconRef(iconPtr) - let image = NSImage(iconRef: icon) - - func resize(_ image: NSImage) -> NSImage { - let newImage = NSImage(size: NSSize(width: 16, height: 16)) - newImage.lockFocus() - image.draw(in: NSRect(x: 0, y: 0, width: 16, height: 16)) - newImage.unlockFocus() - return newImage - } - - menuItem.image = resize(image) - } - if sourceID == "com.apple.keylayout.US" { usKeyboardLayoutItem = menuItem } @@ -91,6 +122,11 @@ import Carbon basisKeyboardLayoutButton.menu?.addItem(menuItem) } + let menuItem = NSMenuItem() + menuItem.title = String(format: NSLocalizedString("Apple Zhuyin Bopomofo", comment: "")) + menuItem.representedObject = String("com.apple.keylayout.ZhuyinBopomofo") + basisKeyboardLayoutButton.menu?.addItem(menuItem) + basisKeyboardLayoutButton.select(chosenItem ?? usKeyboardLayoutItem) selectionKeyComboBox.usesDataSource = false selectionKeyComboBox.removeAllItems() @@ -109,27 +145,53 @@ import Carbon Preferences.basisKeyboardLayout = sourceID } } - - @IBAction func changeSelectionKeyAction(_ sender: Any) { - guard let keys = (sender as AnyObject).stringValue? - .trimmingCharacters(in: .whitespacesAndNewlines) - .lowercased() else { - return - } - 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 - } + + @IBAction func updateUiLanguageAction(_ sender: Any) { + if let selectItem = uiLanguageButton.selectedItem { + if currentLanguageSelectItem == selectItem { + return } } + if let language = uiLanguageButton.selectedItem?.representedObject as? String { + if (language != "auto") { + Preferences.appleLanguages = [language] + } + else { + UserDefaults.standard.removeObject(forKey: "AppleLanguages") + } + + NSLog("vChewing App self-terminated due to UI language change.") + NSApplication.shared.terminate(nil) + } } -} + @IBAction func clickedWhetherIMEShouldNotFartToggleAction(_ sender: Any) { + clsSFX.beep() + } + @IBAction func changeSelectionKeyAction(_ sender: Any) { + guard let keys = (sender as AnyObject).stringValue?.trimmingCharacters(in: .whitespacesAndNewlines).charDeDuplicate else { + return + } + 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 + } + clsSFX.beep() + } + } + + selectionKeyComboBox.stringValue = keys + Preferences.candidateKeys = keys + } + +} diff --git a/Source/WindowNIBs/Base.lproj/frmPreferences.xib b/Source/WindowNIBs/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib similarity index 95% rename from Source/WindowNIBs/Base.lproj/frmPreferences.xib rename to Source/WindowNIBs/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib index 5d62f70a..4388296a 100644 --- a/Source/WindowNIBs/Base.lproj/frmPreferences.xib +++ b/Source/WindowNIBs/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib @@ -17,7 +17,7 @@ - + @@ -252,7 +252,7 @@ - + + + @@ -428,6 +443,8 @@ + + @@ -439,13 +456,13 @@ - + - + - + @@ -456,7 +473,7 @@