vChewing-macOS/Packages/vChewing_Shared/Sources/Shared/Shared.swift

370 lines
12 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// (c) 2022 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 AppKit
import Foundation
import SwiftExtension
// MARK: - Tooltip Color States
public enum TooltipColorState {
case normal
case information
case redAlert
case warning
case denialOverflow
case denialInsufficiency
case prompt
case succeeded
}
// MARK: - IMEState types.
/// enum
public enum StateType: String {
/// ** .ofDeactivated**: 使使使
case ofDeactivated = "Deactivated"
/// ** .ofEmpty**: 使
///
/// /
/// 使
///
case ofEmpty = "Empty"
/// ** .ofAbortion**: .ofEmpty()
/// .ofEmpty()
case ofAbortion = "Abortion"
/// ** .ofCommitting**:
/// .ofEmpty()
/// .ofEmpty()
case ofCommitting = "Committing"
/// ** .ofAssociates**:
case ofAssociates = "Associates"
/// ** .ofInputting**: 使Compositor
case ofInputting = "Inputting"
/// ** .ofMarking**: 使
///
case ofMarking = "Marking"
/// ** .ofCandidates**: 使
case ofCandidates = "Candidates"
/// ** .ofSymbolTable**:
case ofSymbolTable = "SymbolTable"
}
// MARK: - Parser for Syllable composer
public enum KeyboardParser: Int, CaseIterable {
case ofStandard = 0
case ofETen = 1
case ofIBM = 4
case ofMiTAC = 5
case ofSeigyou = 8
case ofFakeSeigyou = 6
case ofDachen26 = 7
case ofETen26 = 3
case ofHsu = 2
case ofStarlight = 9
case ofAlvinLiu = 10
case ofHanyuPinyin = 100
case ofSecondaryPinyin = 101
case ofYalePinyin = 102
case ofHualuoPinyin = 103
case ofUniversalPinyin = 104
case ofWadeGilesPinyin = 105
public var localizedMenuName: String {
let rawString: String = {
switch self {
case .ofStandard: return "Dachen (Microsoft Standard / Wang / 01, etc.)"
case .ofETen: return "Eten Traditional"
case .ofIBM: return "IBM"
case .ofMiTAC: return "MiTAC"
case .ofSeigyou: return "Seigyou"
case .ofFakeSeigyou: return "Fake Seigyou"
case .ofDachen26: return "Dachen 26 (libChewing)"
case .ofETen26: return "Eten 26"
case .ofHsu: return "Hsu"
case .ofStarlight: return "Starlight"
case .ofAlvinLiu: return "Alvin Liu (Imitative)"
case .ofHanyuPinyin: return "Hanyu Pinyin with Numeral Intonation"
case .ofSecondaryPinyin: return "Secondary Pinyin with Numeral Intonation"
case .ofYalePinyin: return "Yale Pinyin with Numeral Intonation"
case .ofHualuoPinyin: return "Hualuo Pinyin with Numeral Intonation"
case .ofUniversalPinyin: return "Universal Pinyin with Numeral Intonation"
case .ofWadeGilesPinyin: return "Wade-Giles Pinyin with Numeral Intonation"
}
}()
return NSLocalizedString(rawString, comment: "")
}
public 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 .ofAlvinLiu:
return "AlvinLiu"
case .ofHanyuPinyin:
return "HanyuPinyin"
case .ofSecondaryPinyin:
return "SecondaryPinyin"
case .ofYalePinyin:
return "YalePinyin"
case .ofHualuoPinyin:
return "HualuoPinyin"
case .ofUniversalPinyin:
return "UniversalPinyin"
case .ofWadeGilesPinyin:
return "WadeGilesPinyin"
}
}
}
public enum CandidateKey {
public static var defaultKeys: String { suggestions[0] }
public static let suggestions: [String] = [
"123456", "123456789", "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGH", "ASDFZXCVB",
]
///
public enum ValidationError {
case noError
case invalidCharacters
case countMismatch
public var description: String {
switch self {
case .invalidCharacters:
return "- "
+ NSLocalizedString(
"Candidate keys can only contain printable ASCII characters like alphanumericals.",
comment: ""
) + "\n" + "- " + NSLocalizedString(
"i18n:CandidateKey.ValidationError.AssignedForOtherPurposes", comment: ""
) + "\n" + "- " + NSLocalizedString(
"Candidate keys cannot contain space.", comment: ""
)
case .countMismatch:
return "- "
+ NSLocalizedString(
"Minimum 6 candidate keys allowed.", comment: ""
) + "\n" + "- " + NSLocalizedString("Maximum 10 candidate keys allowed.", comment: "")
case .noError:
return ""
}
}
}
///
/// - Remark:
/// ```
/// .trimmingCharacters(in: .whitespacesAndNewlines).deduplicated
/// ```
/// - Parameters:
/// - candidateKeys:
/// - excluding:
/// - Returns: nil
public static func validate(
keys candidateKeys: String, excluding forbiddenChars: String = ""
) -> String? {
let candidateKeys = candidateKeys.lowercased()
let forbiddenChars = forbiddenChars.lowercased()
var result = ValidationError.noError
charValidityCheck: for neta in candidateKeys {
if String(neta) == " " || forbiddenChars.contains(neta) {
result = CandidateKey.ValidationError.invalidCharacters
break charValidityCheck
}
for subNeta in neta.unicodeScalars {
if !subNeta.isPrintableASCII {
result = CandidateKey.ValidationError.invalidCharacters
break charValidityCheck
}
}
}
if !(6 ... 10).contains(candidateKeys.count) {
result = CandidateKey.ValidationError.countMismatch
}
return result == ValidationError.noError ? nil : result.description
}
}
public func vCLog(_ strPrint: StringLiteralType) {
if UserDefaults.current.bool(forKey: "_DebugMode") {
NSLog("vChewingDebug: %@", strPrint)
}
}
public enum Shared {
// Supported locales.
public static let arrSupportedLocales: [String] = ["en", "zh-Hant", "zh-Hans", "ja"]
// The type of input modes.
public enum InputMode: String, CaseIterable, Identifiable {
public var id: ObjectIdentifier { .init(rawValue as AnyObject) }
case imeModeCHS = "org.atelierInmu.inputmethod.vChewing.IMECHS"
case imeModeCHT = "org.atelierInmu.inputmethod.vChewing.IMECHT"
case imeModeNULL = ""
public var reversed: Shared.InputMode {
switch self {
case .imeModeCHS:
return .imeModeCHT
case .imeModeCHT:
return .imeModeCHS
case .imeModeNULL:
return .imeModeNULL
}
}
public static var validCases: [InputMode] {
[.imeModeCHS, .imeModeCHT]
}
public var localizedDescription: String { NSLocalizedString(description, comment: "") }
public var description: String {
switch self {
case .imeModeCHS:
return "Simplified Chinese"
case .imeModeCHT:
return "Traditional Chinese"
case .imeModeNULL:
return "Please select…"
}
}
public var nonUTFEncoding: CFStringEncodings? {
switch self {
case .imeModeCHS: return .GB_18030_2000
case .imeModeCHT: return .big5_HKSCS_1999
default: return nil
}
}
public var nonUTFEncodingInitials: String? {
switch self {
case .imeModeCHS: return "GB"
case .imeModeCHT: return "Big5"
default: return nil
}
}
}
}
// MARK: - PEReloadEventObserver
@available(macOS 10.15, *)
public class PEReloadEventObserver: NSObject, ObservableObject {
public static let shared = PEReloadEventObserver()
private var observation: NSKeyValueObservation?
@Published public var id = UUID().uuidString
override public init() {
super.init()
observation = Broadcaster.shared.observe(\.eventForReloadingPhraseEditor, options: [.new]) { _, _ in
self.touch()
}
}
public static func == (lhs: PEReloadEventObserver, rhs: PEReloadEventObserver) -> Bool { lhs.id == rhs.id }
public func touch() {
id = UUID().uuidString
}
}
// MARK: - File Open Method
public enum FileOpenMethod: String {
case finder = "Finder"
case textEdit = "TextEdit"
case safari = "Safari"
public var appName: String {
let englishFallback: String = {
switch self {
case .finder: return "Finder"
case .textEdit: return "TextEdit"
case .safari: return "Safari"
}
}()
let tag = Locale.preferredLanguages.first?.lowercased().replacingOccurrences(of: "_", with: "-")
guard let tag = tag else { return englishFallback }
switch tag.prefix(2) {
case "ja":
switch self {
case .finder: return "Finder"
case .textEdit: return "テキストエディット"
case .safari: return "Safari"
}
case "zh" where tag.hasSuffix("-cn") || tag.hasSuffix("-hans"):
if #available(macOS 10.13.2, *) {
switch self {
case .finder: return "访达"
case .textEdit: return "文本编辑"
case .safari: return "Safari浏览器"
}
} else {
switch self {
case .finder: return "Finder"
case .textEdit: return "文本编辑"
case .safari: return "Safari"
}
}
case "zh-hant" where tag.hasSuffix("-tw") || tag.hasSuffix("-hk") || tag.hasSuffix("-hant"):
switch self {
case .finder: return "Finder"
case .textEdit: return "文字編輯"
case .safari: return "Safari"
}
default:
return englishFallback
}
}
public var bundleIdentifier: String {
switch self {
case .finder: return "com.apple.finder"
case .textEdit: return "com.apple.TextEdit"
case .safari: return "com.apple.Safari"
}
}
public func open(url: URL) {
switch self {
case .finder: NSWorkspace.shared.activateFileViewerSelecting([url])
default:
if #unavailable(macOS 10.15) {
NSWorkspace.shared.openFile(url.path, withApplication: appName)
return
} else {
let textEditURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleIdentifier)
guard let textEditURL = textEditURL else { return }
let configuration = NSWorkspace.OpenConfiguration()
configuration.promptsUserIfNeeded = true
NSWorkspace.shared.open([url], withApplicationAt: textEditURL, configuration: configuration)
}
}
}
}