1.5.0 Milestone 1 // Merge GitHub PR#57 from dev/1.5.x
This commit is contained in:
commit
e0cd85537f
|
@ -321,7 +321,7 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ntfPostInstall.beginSheetModal(for: window!) { response in
|
ntfPostInstall.beginSheetModal(for: window!) { _ in
|
||||||
self.endAppWithDelay()
|
self.endAppWithDelay()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
16
Makefile
16
Makefile
|
@ -20,7 +20,9 @@ debug:
|
||||||
DSTROOT = /Library/Input Methods
|
DSTROOT = /Library/Input Methods
|
||||||
VC_APP_ROOT = $(DSTROOT)/vChewing.app
|
VC_APP_ROOT = $(DSTROOT)/vChewing.app
|
||||||
|
|
||||||
.PHONY: clang-format lint
|
.PHONY: clang-format lint batchfix format
|
||||||
|
|
||||||
|
format: batchfix clang-format lint
|
||||||
|
|
||||||
clang-format:
|
clang-format:
|
||||||
@swift-format format --in-place --configuration ./.clang-format-swift.json --recursive ./DataCompiler/
|
@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 ./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/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
|
@find ./Source/Modules/ -iname '*.h' -o -iname '*.cpp' -o -iname '*.mm' -o -iname '*.m' | xargs clang-format -i -style=Microsoft
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
@swift-format lint --configuration ./.clang-format-swift.json --recursive ./DataCompiler/
|
@swift-format lint --configuration ./.clang-format-swift.json --recursive --parallel ./DataCompiler/
|
||||||
@swift-format lint --configuration ./.clang-format-swift.json --recursive ./Installer/
|
@swift-format lint --configuration ./.clang-format-swift.json --recursive --parallel ./Installer/
|
||||||
@swift-format lint --configuration ./.clang-format-swift.json --recursive ./Source/
|
@swift-format lint --configuration ./.clang-format-swift.json --recursive --parallel ./Source/
|
||||||
@swift-format lint --configuration ./.clang-format-swift.json --recursive ./UserPhraseEditor/
|
@swift-format lint --configuration ./.clang-format-swift.json --recursive --parallel ./UserPhraseEditor/
|
||||||
|
|
||||||
|
batchfix:
|
||||||
|
@swiftlint --fix ./
|
||||||
|
|
||||||
.PHONY: permission-check install-debug install-release
|
.PHONY: permission-check install-debug install-release
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ let package = Package(
|
||||||
dependencies: ["OpenCC"],
|
dependencies: ["OpenCC"],
|
||||||
resources: [
|
resources: [
|
||||||
.copy("benchmark"),
|
.copy("benchmark"),
|
||||||
.copy("testcases"),
|
.copy("testcases")
|
||||||
]),
|
]),
|
||||||
.target(
|
.target(
|
||||||
name: "copencc",
|
name: "copencc",
|
||||||
|
@ -66,20 +66,20 @@ let package = Package(
|
||||||
"deps/marisa-0.2.6/AUTHORS",
|
"deps/marisa-0.2.6/AUTHORS",
|
||||||
"deps/marisa-0.2.6/CMakeLists.txt",
|
"deps/marisa-0.2.6/CMakeLists.txt",
|
||||||
"deps/marisa-0.2.6/COPYING.md",
|
"deps/marisa-0.2.6/COPYING.md",
|
||||||
"deps/marisa-0.2.6/README.md",
|
"deps/marisa-0.2.6/README.md"
|
||||||
],
|
],
|
||||||
sources: [
|
sources: [
|
||||||
"source.cpp",
|
"source.cpp",
|
||||||
"src",
|
"src",
|
||||||
"deps/marisa-0.2.6",
|
"deps/marisa-0.2.6"
|
||||||
],
|
],
|
||||||
cxxSettings: [
|
cxxSettings: [
|
||||||
.headerSearchPath("src"),
|
.headerSearchPath("src"),
|
||||||
.headerSearchPath("deps/darts-clone"),
|
.headerSearchPath("deps/darts-clone"),
|
||||||
.headerSearchPath("deps/marisa-0.2.6/include"),
|
.headerSearchPath("deps/marisa-0.2.6/include"),
|
||||||
.headerSearchPath("deps/marisa-0.2.6/lib"),
|
.headerSearchPath("deps/marisa-0.2.6/lib"),
|
||||||
.define("ENABLE_DARTS"),
|
.define("ENABLE_DARTS")
|
||||||
]),
|
])
|
||||||
],
|
],
|
||||||
cxxLanguageStandard: .cxx14
|
cxxLanguageStandard: .cxx14
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,7 +10,7 @@ let testCases: [(String, ChineseConverter.Options)] = [
|
||||||
("s2tw", [.traditionalize, .twStandard]),
|
("s2tw", [.traditionalize, .twStandard]),
|
||||||
("tw2s", [.simplify, .twStandard]),
|
("tw2s", [.simplify, .twStandard]),
|
||||||
("s2twp", [.traditionalize, .twStandard, .twIdiom]),
|
("s2twp", [.traditionalize, .twStandard, .twIdiom]),
|
||||||
("tw2sp", [.simplify, .twStandard, .twIdiom]),
|
("tw2sp", [.simplify, .twStandard, .twIdiom])
|
||||||
]
|
]
|
||||||
|
|
||||||
class OpenCCTests: XCTestCase {
|
class OpenCCTests: XCTestCase {
|
||||||
|
|
|
@ -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..<sections.count, id: \.self) { index in
|
||||||
|
viewForSection(sections, index: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modifier(Section.LabelWidthModifier(maximumWidth: $maximumLabelWidth))
|
||||||
|
.frame(width: CGFloat(contentWidth), alignment: .leading)
|
||||||
|
.padding(.vertical, 20)
|
||||||
|
.padding(.horizontal, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func viewForSection(_ sections: [Section], index: Int) -> 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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<T>`.
|
||||||
|
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<Content: View>: 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<Content: View>: NSHostingController<Content>, 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<Content>) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
35
Source/3rdParty/SindreSorhus/Preferences/PreferencesStyleController.swift
vendored
Executable file
35
Source/3rdParty/SindreSorhus/Preferences/PreferencesStyleController.swift
vendored
Executable file
|
@ -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)
|
||||||
|
}
|
261
Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift
vendored
Executable file
261
Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift
vendored
Executable file
|
@ -0,0 +1,261 @@
|
||||||
|
// 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)"
|
||||||
|
// }
|
||||||
|
var preferencesTitleName = NSLocalizedString("vChewing Preferences…", comment: "")
|
||||||
|
preferencesTitleName.removeLast()
|
||||||
|
return preferencesTitleName
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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))
|
||||||
|
}
|
||||||
|
}
|
188
Source/3rdParty/SindreSorhus/Preferences/PreferencesWindowController.swift
vendored
Executable file
188
Source/3rdParty/SindreSorhus/Preferences/PreferencesWindowController.swift
vendored
Executable file
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Label: View, Content: View>(
|
||||||
|
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<Content: View>(
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
159
Source/3rdParty/SindreSorhus/Preferences/SegmentedControlStyleViewController.swift
vendored
Executable file
159
Source/3rdParty/SindreSorhus/Preferences/SegmentedControlStyleViewController.swift
vendored
Executable file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
77
Source/3rdParty/SindreSorhus/Preferences/ToolbarItemStyleViewController.swift
vendored
Executable file
77
Source/3rdParty/SindreSorhus/Preferences/ToolbarItemStyleViewController.swift
vendored
Executable file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
?? "<Unknown App Name>"
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,156 +27,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import InputMethodKit
|
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<VersionUpdateApiResult, Error>) -> 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)
|
@objc(AppDelegate)
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate,
|
class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate,
|
||||||
FSEventStreamHelperDelegate
|
FSEventStreamHelperDelegate
|
||||||
|
@ -220,7 +70,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
||||||
mgrPrefs.setMissingDefaults()
|
mgrPrefs.setMissingDefaults()
|
||||||
|
|
||||||
// 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。
|
// 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。
|
||||||
if (UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) != nil) == true {
|
if (UserDefaults.standard.object(forKey: VersionUpdateApi.kCheckUpdateAutomatically) != nil) == true {
|
||||||
checkForUpdate()
|
checkForUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,18 +112,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
||||||
|
|
||||||
// time for update?
|
// time for update?
|
||||||
if !forced {
|
if !forced {
|
||||||
if UserDefaults.standard.bool(forKey: kCheckUpdateAutomatically) == false {
|
if UserDefaults.standard.bool(forKey: VersionUpdateApi.kCheckUpdateAutomatically) == false {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let now = Date()
|
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 {
|
if now.compare(date) == .orderedAscending {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date())
|
let nextUpdateDate = Date(timeInterval: VersionUpdateApi.kNextCheckInterval, since: Date())
|
||||||
UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey)
|
UserDefaults.standard.set(nextUpdateDate, forKey: VersionUpdateApi.kNextUpdateCheckDateKey)
|
||||||
|
|
||||||
checkTask = VersionUpdateApi.check(forced: forced) { [self] result in
|
checkTask = VersionUpdateApi.check(forced: forced) { [self] result in
|
||||||
defer {
|
defer {
|
||||||
|
|
|
@ -25,43 +25,30 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
@objc class AppleKeyboardConverter: NSObject {
|
@objc class AppleKeyboardConverter: NSObject {
|
||||||
@objc class func isDynamicBaseKeyboardLayoutEnabled() -> Bool {
|
static let arrDynamicBasicKeyLayout: [String] = [
|
||||||
switch mgrPrefs.basisKeyboardLayout {
|
"com.apple.keylayout.ZhuyinBopomofo",
|
||||||
case "com.apple.keylayout.ZhuyinBopomofo":
|
"com.apple.keylayout.ZhuyinEten",
|
||||||
return true
|
"org.atelierInmu.vChewing.keyLayouts.vchewingdachen",
|
||||||
case "com.apple.keylayout.ZhuyinEten":
|
"org.atelierInmu.vChewing.keyLayouts.vchewingmitac",
|
||||||
return true
|
"org.atelierInmu.vChewing.keyLayouts.vchewingibm",
|
||||||
case "org.atelierInmu.vChewing.keyLayouts.vchewingdachen":
|
"org.atelierInmu.vChewing.keyLayouts.vchewingseigyou",
|
||||||
return true
|
"org.atelierInmu.vChewing.keyLayouts.vchewingeten",
|
||||||
case "org.atelierInmu.vChewing.keyLayouts.vchewingmitac":
|
"org.unknown.keylayout.vChewingDachen",
|
||||||
return true
|
"org.unknown.keylayout.vChewingFakeSeigyou",
|
||||||
case "org.atelierInmu.vChewing.keyLayouts.vchewingibm":
|
"org.unknown.keylayout.vChewingETen",
|
||||||
return true
|
"org.unknown.keylayout.vChewingIBM",
|
||||||
case "org.atelierInmu.vChewing.keyLayouts.vchewingseigyou":
|
"org.unknown.keylayout.vChewingMiTAC",
|
||||||
return true
|
]
|
||||||
case "org.atelierInmu.vChewing.keyLayouts.vchewingeten":
|
@objc class func isDynamicBasicKeyboardLayoutEnabled() -> Bool {
|
||||||
return true
|
return AppleKeyboardConverter.arrDynamicBasicKeyLayout.contains(mgrPrefs.basicKeyboardLayout)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 處理 Apple 注音鍵盤佈局類型。
|
// 處理 Apple 注音鍵盤佈局類型。
|
||||||
@objc class func cnvApple2ABC(_ charCode: UniChar) -> UniChar {
|
@objc class func cnvApple2ABC(_ charCode: UniChar) -> UniChar {
|
||||||
var charCode = charCode
|
var charCode = charCode
|
||||||
// 在按鍵資訊被送往 OVMandarin 之前,先轉換為可以被 OVMandarin 正常處理的資訊。
|
// 在按鍵資訊被送往 OVMandarin 之前,先轉換為可以被 OVMandarin 正常處理的資訊。
|
||||||
if self.isDynamicBaseKeyboardLayoutEnabled() {
|
if self.isDynamicBasicKeyboardLayoutEnabled() {
|
||||||
// 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。
|
// 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。
|
||||||
switch mgrPrefs.basisKeyboardLayout {
|
switch mgrPrefs.basicKeyboardLayout {
|
||||||
case "com.apple.keylayout.ZhuyinBopomofo":
|
case "com.apple.keylayout.ZhuyinBopomofo":
|
||||||
do {
|
do {
|
||||||
if charCode == 97 { charCode = UniChar(65) }
|
if charCode == 97 { charCode = UniChar(65) }
|
||||||
|
@ -186,7 +173,7 @@ import Cocoa
|
||||||
// 摁了 Alt 的符號。
|
// 摁了 Alt 的符號。
|
||||||
if charCode == 8212 { charCode = UniChar(45) }
|
if charCode == 8212 { charCode = UniChar(45) }
|
||||||
// Apple 倚天注音佈局追加符號糾正項目。
|
// Apple 倚天注音佈局追加符號糾正項目。
|
||||||
if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
|
if mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
|
||||||
if charCode == 65343 { charCode = UniChar(95) }
|
if charCode == 65343 { charCode = UniChar(95) }
|
||||||
if charCode == 65306 { charCode = UniChar(58) }
|
if charCode == 65306 { charCode = UniChar(58) }
|
||||||
if charCode == 65311 { charCode = UniChar(63) }
|
if charCode == 65311 { charCode = UniChar(63) }
|
||||||
|
@ -199,9 +186,9 @@ import Cocoa
|
||||||
|
|
||||||
@objc class func cnvStringApple2ABC(_ strProcessed: String) -> String {
|
@objc class func cnvStringApple2ABC(_ strProcessed: String) -> String {
|
||||||
var strProcessed = strProcessed
|
var strProcessed = strProcessed
|
||||||
if self.isDynamicBaseKeyboardLayoutEnabled() {
|
if self.isDynamicBasicKeyboardLayoutEnabled() {
|
||||||
// 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。
|
// 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。
|
||||||
switch mgrPrefs.basisKeyboardLayout {
|
switch mgrPrefs.basicKeyboardLayout {
|
||||||
case "com.apple.keylayout.ZhuyinBopomofo":
|
case "com.apple.keylayout.ZhuyinBopomofo":
|
||||||
do {
|
do {
|
||||||
if strProcessed == "a" { strProcessed = "A" }
|
if strProcessed == "a" { strProcessed = "A" }
|
||||||
|
@ -326,7 +313,7 @@ import Cocoa
|
||||||
// 摁了 Alt 的符號。
|
// 摁了 Alt 的符號。
|
||||||
if strProcessed == "—" { strProcessed = "-" }
|
if strProcessed == "—" { strProcessed = "-" }
|
||||||
// Apple 倚天注音佈局追加符號糾正項目。
|
// Apple 倚天注音佈局追加符號糾正項目。
|
||||||
if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
|
if mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
|
||||||
if strProcessed == "_" { strProcessed = "_" }
|
if strProcessed == "_" { strProcessed = "_" }
|
||||||
if strProcessed == ":" { strProcessed = ":" }
|
if strProcessed == ":" { strProcessed = ":" }
|
||||||
if strProcessed == "?" { strProcessed = "?" }
|
if strProcessed == "?" { strProcessed = "?" }
|
||||||
|
|
|
@ -243,7 +243,7 @@ class InputState: NSObject {
|
||||||
self.markerIndex = markerIndex
|
self.markerIndex = markerIndex
|
||||||
let begin = min(cursorIndex, markerIndex)
|
let begin = min(cursorIndex, markerIndex)
|
||||||
let end = max(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
|
self.readings = readings
|
||||||
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,9 +46,7 @@ static double FindHighestScore(const std::vector<Gramambular::NodeAnchor> &nodes
|
||||||
{
|
{
|
||||||
double score = ni->node->highestUnigramScore();
|
double score = ni->node->highestUnigramScore();
|
||||||
if (score > highestScore)
|
if (score > highestScore)
|
||||||
{
|
|
||||||
highestScore = score;
|
highestScore = score;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return highestScore + epsilon;
|
return highestScore + epsilon;
|
||||||
}
|
}
|
||||||
|
@ -98,14 +96,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
|
|
||||||
- (BOOL)isBuilderEmpty
|
- (BOOL)isBuilderEmpty
|
||||||
{
|
{
|
||||||
if (_builder->grid().width() == 0)
|
return (_builder->grid().width() == 0);
|
||||||
{
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setInputMode:(NSString *)value
|
- (void)setInputMode:(NSString *)value
|
||||||
|
@ -114,25 +105,17 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
vChewing::LMInstantiator *newLanguageModel;
|
vChewing::LMInstantiator *newLanguageModel;
|
||||||
vChewing::UserOverrideModel *newUserOverrideModel;
|
vChewing::UserOverrideModel *newUserOverrideModel;
|
||||||
|
|
||||||
if ([value isKindOfClass:[NSString class]] && [value isEqual:imeModeCHS])
|
BOOL isCHS = [value isKindOfClass:[NSString class]] && [value isEqual:imeModeCHS];
|
||||||
{
|
|
||||||
newInputMode = imeModeCHS;
|
newInputMode = isCHS ? imeModeCHS : imeModeCHT;
|
||||||
newLanguageModel = [mgrLangModel lmCHS];
|
newLanguageModel = isCHS ? [mgrLangModel lmCHS] : [mgrLangModel lmCHT];
|
||||||
newUserOverrideModel = [mgrLangModel userOverrideModelCHS];
|
newUserOverrideModel = isCHS ? [mgrLangModel userOverrideModelCHS] : [mgrLangModel userOverrideModelCHT];
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
newInputMode = imeModeCHT;
|
|
||||||
newLanguageModel = [mgrLangModel lmCHT];
|
|
||||||
newUserOverrideModel = [mgrLangModel userOverrideModelCHT];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report the current Input Mode to ctlInputMethod:
|
// Report the current Input Mode to ctlInputMethod:
|
||||||
ctlInputMethod.currentInputMode = newInputMode;
|
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);
|
newLanguageModel->setPhraseReplacementEnabled(mgrPrefs.phraseReplacementEnabled);
|
||||||
// Also other sub language models:
|
|
||||||
newLanguageModel->setSymbolEnabled(mgrPrefs.symbolInputEnabled);
|
newLanguageModel->setSymbolEnabled(mgrPrefs.symbolInputEnabled);
|
||||||
newLanguageModel->setCNSEnabled(mgrPrefs.cns11643Enabled);
|
newLanguageModel->setCNSEnabled(mgrPrefs.cns11643Enabled);
|
||||||
|
|
||||||
|
@ -151,24 +134,16 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_bpmfReadingBuffer->isEmpty())
|
if (!_bpmfReadingBuffer->isEmpty())
|
||||||
{
|
|
||||||
_bpmfReadingBuffer->clear();
|
_bpmfReadingBuffer->clear();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{ // clean up everything
|
||||||
// clean up everything
|
|
||||||
if (_bpmfReadingBuffer)
|
if (_bpmfReadingBuffer)
|
||||||
{
|
|
||||||
delete _bpmfReadingBuffer;
|
delete _bpmfReadingBuffer;
|
||||||
}
|
|
||||||
|
|
||||||
if (_builder)
|
if (_builder)
|
||||||
{
|
|
||||||
delete _builder;
|
delete _builder;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)init
|
- (instancetype)init
|
||||||
|
@ -196,36 +171,35 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
|
|
||||||
- (void)syncWithPreferences
|
- (void)syncWithPreferences
|
||||||
{
|
{
|
||||||
NSInteger layout = mgrPrefs.keyboardLayout;
|
switch (mgrPrefs.mandarinParser)
|
||||||
switch (layout)
|
|
||||||
{
|
{
|
||||||
case KeyboardLayoutOfStandard:
|
case MandarinParserOfStandard:
|
||||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout());
|
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout());
|
||||||
break;
|
break;
|
||||||
case KeyboardLayoutOfEten:
|
case MandarinParserOfEten:
|
||||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETenLayout());
|
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETenLayout());
|
||||||
break;
|
break;
|
||||||
case KeyboardLayoutOfHsu:
|
case MandarinParserOfHsu:
|
||||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HsuLayout());
|
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HsuLayout());
|
||||||
break;
|
break;
|
||||||
case KeyboardLayoutOfEen26:
|
case MandarinParserOfEen26:
|
||||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETen26Layout());
|
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETen26Layout());
|
||||||
break;
|
break;
|
||||||
case KeyboardLayoutOfIBM:
|
case MandarinParserOfIBM:
|
||||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::IBMLayout());
|
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::IBMLayout());
|
||||||
break;
|
break;
|
||||||
case KeyboardLayoutOfMiTAC:
|
case MandarinParserOfMiTAC:
|
||||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::MiTACLayout());
|
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::MiTACLayout());
|
||||||
break;
|
break;
|
||||||
case KeyboardLayoutOfFakeSeigyou:
|
case MandarinParserOfFakeSeigyou:
|
||||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::FakeSeigyouLayout());
|
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::FakeSeigyouLayout());
|
||||||
break;
|
break;
|
||||||
case KeyboardLayoutOfHanyuPinyin:
|
case MandarinParserOfHanyuPinyin:
|
||||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HanyuPinyinLayout());
|
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HanyuPinyinLayout());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout());
|
_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.
|
// in the user override model.
|
||||||
BOOL addToOverrideModel = YES;
|
BOOL addToOverrideModel = YES;
|
||||||
if (selectedNode.spanningLength != [value count])
|
if (selectedNode.spanningLength != [value count])
|
||||||
{
|
|
||||||
addToOverrideModel = NO;
|
addToOverrideModel = NO;
|
||||||
}
|
|
||||||
if (addToOverrideModel)
|
if (addToOverrideModel)
|
||||||
{
|
{
|
||||||
double score = selectedNode.node->scoreForCandidate(stringValue);
|
double score = selectedNode.node->scoreForCandidate(stringValue);
|
||||||
if (score <= -12)
|
if (score <= -12) // 威注音的 SymbolLM 的 Score 是 -12。
|
||||||
{ // 威注音的 SymbolLM 的 Score 是 -12。
|
|
||||||
addToOverrideModel = NO;
|
addToOverrideModel = NO;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (addToOverrideModel)
|
if (addToOverrideModel)
|
||||||
{
|
|
||||||
_userOverrideModel->observe(_walkedNodes, cursorIndex, stringValue, [[NSDate date] timeIntervalSince1970]);
|
_userOverrideModel->observe(_walkedNodes, cursorIndex, stringValue, [[NSDate date] timeIntervalSince1970]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[self _walk];
|
[self _walk];
|
||||||
|
|
||||||
|
@ -265,15 +234,11 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
for (auto node : _walkedNodes)
|
for (auto node : _walkedNodes)
|
||||||
{
|
{
|
||||||
if (nextPosition >= cursorIndex)
|
if (nextPosition >= cursorIndex)
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
nextPosition += node.spanningLength;
|
nextPosition += node.spanningLength;
|
||||||
}
|
}
|
||||||
if (nextPosition <= _builder->length())
|
if (nextPosition <= _builder->length())
|
||||||
{
|
|
||||||
_builder->setCursorIndex(nextPosition);
|
_builder->setCursorIndex(nextPosition);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,13 +249,13 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
_walkedNodes.clear();
|
_walkedNodes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
- (std::string)_currentLayout
|
- (std::string)_currentMandarinParser
|
||||||
{
|
{
|
||||||
NSString *keyboardLayoutName = mgrPrefs.keyboardLayoutName;
|
return std::string(mgrPrefs.mandarinParserName.UTF8String) + std::string("_");
|
||||||
std::string layout = std::string(keyboardLayoutName.UTF8String) + std::string("_");
|
|
||||||
return layout;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Handling Input
|
||||||
|
|
||||||
- (BOOL)handleInput:(keyParser *)input
|
- (BOOL)handleInput:(keyParser *)input
|
||||||
state:(InputState *)inState
|
state:(InputState *)inState
|
||||||
stateCallback:(void (^)(InputState *))stateCallback
|
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 the inputText is empty, it's a function key combination, we ignore it
|
||||||
if (!input.inputText.length)
|
if (!input.inputText.length)
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
// if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it
|
// if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it
|
||||||
BOOL isFunctionKey =
|
BOOL isFunctionKey =
|
||||||
([input isCommandHold] || [input isOptionHotKey] || [input isNumericPad]) || [input isControlHotKey];
|
([input isCommandHold] || [input isOptionHotKey] || [input isNumericPad]) || [input isControlHotKey];
|
||||||
if (![state isKindOfClass:[InputStateNotEmpty class]] &&
|
if (![state isKindOfClass:[InputStateNotEmpty class]] &&
|
||||||
![state isKindOfClass:[InputStateAssociatedPhrases class]] && isFunctionKey)
|
![state isKindOfClass:[InputStateAssociatedPhrases class]] && isFunctionKey)
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
// Caps Lock processing: if Caps Lock is ON, temporarily disable bopomofo.
|
// Caps Lock processing: if Caps Lock is ON, temporarily disable bopomofo.
|
||||||
// Note: Alphanumerical mode processing.
|
// 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.
|
// When shift is pressed, don't do further processing, since it outputs capital letter anyway.
|
||||||
if ([input isShiftHold])
|
if ([input isShiftHold])
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
// if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char
|
// if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char
|
||||||
// insertions.
|
// insertions.
|
||||||
if (charCode < 0x80 && !isprint(charCode))
|
if (charCode < 0x80 && !isprint(charCode))
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
// commit everything in the buffer.
|
// commit everything in the buffer.
|
||||||
InputStateCommitting *committingState =
|
InputStateCommitting *committingState =
|
||||||
|
@ -369,9 +326,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
|
|
||||||
// MARK: Handle Candidates
|
// MARK: Handle Candidates
|
||||||
if ([state isKindOfClass:[InputStateChoosingCandidate class]])
|
if ([state isKindOfClass:[InputStateChoosingCandidate class]])
|
||||||
{
|
|
||||||
return [self _handleCandidateState:state input:input stateCallback:stateCallback errorCallback:errorCallback];
|
return [self _handleCandidateState:state input:input stateCallback:stateCallback errorCallback:errorCallback];
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Handle Associated Phrases
|
// MARK: Handle Associated Phrases
|
||||||
if ([state isKindOfClass:[InputStateAssociatedPhrases class]])
|
if ([state isKindOfClass:[InputStateAssociatedPhrases class]])
|
||||||
|
@ -381,9 +336,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
stateCallback:stateCallback
|
stateCallback:stateCallback
|
||||||
errorCallback:errorCallback];
|
errorCallback:errorCallback];
|
||||||
if (result)
|
if (result)
|
||||||
{
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
|
||||||
state = [[InputStateEmpty alloc] init];
|
state = [[InputStateEmpty alloc] init];
|
||||||
stateCallback(state);
|
stateCallback(state);
|
||||||
}
|
}
|
||||||
|
@ -396,9 +349,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
input:input
|
input:input
|
||||||
stateCallback:stateCallback
|
stateCallback:stateCallback
|
||||||
errorCallback:errorCallback])
|
errorCallback:errorCallback])
|
||||||
{
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
|
||||||
state = [marking convertToInputting];
|
state = [marking convertToInputting];
|
||||||
stateCallback(state);
|
stateCallback(state);
|
||||||
}
|
}
|
||||||
|
@ -426,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
|
// 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]));
|
composeReading |= (!_bpmfReadingBuffer->isEmpty() && ([input isSpace] || [input isEnter]));
|
||||||
if (composeReading)
|
if (composeReading)
|
||||||
{
|
{
|
||||||
// combine the reading
|
// combine the reading
|
||||||
std::string reading = _bpmfReadingBuffer->syllable().composedString();
|
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))
|
if (!_languageModel->hasUnigramsForKey(reading))
|
||||||
{
|
{
|
||||||
[IME prtDebugIntel:@"B49C0979"];
|
[IME prtDebugIntel:@"B49C0979"];
|
||||||
|
@ -493,9 +444,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
(InputStateAssociatedPhrases *)[self buildAssociatePhraseStateWithKey:text
|
(InputStateAssociatedPhrases *)[self buildAssociatePhraseStateWithKey:text
|
||||||
useVerticalMode:input.useVerticalMode];
|
useVerticalMode:input.useVerticalMode];
|
||||||
if (associatedPhrases)
|
if (associatedPhrases)
|
||||||
{
|
|
||||||
stateCallback(associatedPhrases);
|
stateCallback(associatedPhrases);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
InputStateEmpty *empty = [[InputStateEmpty alloc] init];
|
InputStateEmpty *empty = [[InputStateEmpty alloc] init];
|
||||||
|
@ -504,9 +453,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
stateCallback(choosingCandidates);
|
stateCallback(choosingCandidates);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// and tells the client that the key is consumed
|
// and tells the client that the key is consumed
|
||||||
|
@ -558,82 +505,54 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
|
|
||||||
// MARK: Esc
|
// MARK: Esc
|
||||||
if ([input isESC])
|
if ([input isESC])
|
||||||
{
|
|
||||||
return [self _handleEscWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
return [self _handleEscWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Cursor backward
|
// MARK: Cursor backward
|
||||||
if ([input isCursorBackward] || emacsKey == vChewingEmacsKeyBackward)
|
if ([input isCursorBackward] || emacsKey == vChewingEmacsKeyBackward)
|
||||||
{
|
|
||||||
return [self _handleBackwardWithState:state
|
return [self _handleBackwardWithState:state
|
||||||
input:input
|
input:input
|
||||||
stateCallback:stateCallback
|
stateCallback:stateCallback
|
||||||
errorCallback:errorCallback];
|
errorCallback:errorCallback];
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Cursor forward
|
// MARK: Cursor forward
|
||||||
if ([input isCursorForward] || emacsKey == vChewingEmacsKeyForward)
|
if ([input isCursorForward] || emacsKey == vChewingEmacsKeyForward)
|
||||||
{
|
|
||||||
return [self _handleForwardWithState:state input:input stateCallback:stateCallback errorCallback:errorCallback];
|
return [self _handleForwardWithState:state input:input stateCallback:stateCallback errorCallback:errorCallback];
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Home
|
// MARK: Home
|
||||||
if ([input isHome] || emacsKey == vChewingEmacsKeyHome)
|
if ([input isHome] || emacsKey == vChewingEmacsKeyHome)
|
||||||
{
|
|
||||||
return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: End
|
// MARK: End
|
||||||
if ([input isEnd] || emacsKey == vChewingEmacsKeyEnd)
|
if ([input isEnd] || emacsKey == vChewingEmacsKeyEnd)
|
||||||
{
|
|
||||||
return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Ctrl+PgLf or Shift+PgLf
|
// MARK: Ctrl+PgLf or Shift+PgLf
|
||||||
if ([input isControlHold] || [input isShiftHold])
|
if (([input isControlHold] || [input isShiftHold]) && ([input isOptionHold] && [input isLeft]))
|
||||||
{
|
return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||||
if ([input isOptionHold] && [input isLeft])
|
|
||||||
{
|
|
||||||
return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Ctrl+PgRt or Shift+PgRt
|
// MARK: Ctrl+PgRt or Shift+PgRt
|
||||||
if ([input isControlHold] || [input isShiftHold])
|
if (([input isControlHold] || [input isShiftHold]) && ([input isOptionHold] && [input isRight]))
|
||||||
{
|
return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||||
if ([input isOptionHold] && [input isRight])
|
|
||||||
{
|
|
||||||
return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: AbsorbedArrowKey
|
// MARK: AbsorbedArrowKey
|
||||||
if ([input isAbsorbedArrowKey] || [input isExtraChooseCandidateKey] || [input isExtraChooseCandidateKeyReverse])
|
if ([input isAbsorbedArrowKey] || [input isExtraChooseCandidateKey] || [input isExtraChooseCandidateKeyReverse])
|
||||||
{
|
|
||||||
return [self _handleAbsorbedArrowKeyWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
return [self _handleAbsorbedArrowKeyWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Backspace
|
// MARK: Backspace
|
||||||
if ([input isBackSpace])
|
if ([input isBackSpace])
|
||||||
{
|
|
||||||
return [self _handleBackspaceWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
return [self _handleBackspaceWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Delete
|
// MARK: Delete
|
||||||
if ([input isDelete] || emacsKey == vChewingEmacsKeyDelete)
|
if ([input isDelete] || emacsKey == vChewingEmacsKeyDelete)
|
||||||
{
|
|
||||||
return [self _handleDeleteWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
return [self _handleDeleteWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Enter
|
// MARK: Enter
|
||||||
if ([input isEnter])
|
if ([input isEnter])
|
||||||
{
|
|
||||||
return ([input isControlHold] && [input isCommandHold])
|
return ([input isControlHold] && [input isCommandHold])
|
||||||
? [self _handleCtrlCommandEnterWithState:state
|
? [self _handleCtrlCommandEnterWithState:state
|
||||||
stateCallback:stateCallback
|
stateCallback:stateCallback
|
||||||
errorCallback:errorCallback]
|
errorCallback:errorCallback]
|
||||||
: [self _handleEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
: [self _handleEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Punctuation list
|
// MARK: Punctuation list
|
||||||
if ([input isSymbolMenuPhysicalKey] && ![input isShiftHold])
|
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.
|
// if nothing is matched, see if it's a punctuation key for current layout.
|
||||||
|
|
||||||
std::string punctuationNamePrefix;
|
std::string punctuationNamePrefix;
|
||||||
|
|
||||||
if ([input isOptionHold])
|
if ([input isOptionHold])
|
||||||
{
|
|
||||||
punctuationNamePrefix = std::string("_alt_punctuation_");
|
punctuationNamePrefix = std::string("_alt_punctuation_");
|
||||||
}
|
|
||||||
else if ([input isControlHold])
|
else if ([input isControlHold])
|
||||||
{
|
|
||||||
punctuationNamePrefix = std::string("_ctrl_punctuation_");
|
punctuationNamePrefix = std::string("_ctrl_punctuation_");
|
||||||
}
|
|
||||||
else if (mgrPrefs.halfWidthPunctuationEnabled)
|
else if (mgrPrefs.halfWidthPunctuationEnabled)
|
||||||
{
|
|
||||||
punctuationNamePrefix = std::string("_half_punctuation_");
|
punctuationNamePrefix = std::string("_half_punctuation_");
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
punctuationNamePrefix = std::string("_punctuation_");
|
punctuationNamePrefix = std::string("_punctuation_");
|
||||||
}
|
|
||||||
std::string layout = [self _currentLayout];
|
std::string parser = [self _currentMandarinParser];
|
||||||
std::string customPunctuation = punctuationNamePrefix + layout + std::string(1, (char)charCode);
|
std::string customPunctuation = punctuationNamePrefix + parser + std::string(1, (char)charCode);
|
||||||
if ([self _handlePunctuation:customPunctuation
|
if ([self _handlePunctuation:customPunctuation
|
||||||
state:state
|
state:state
|
||||||
usingVerticalMode:input.useVerticalMode
|
usingVerticalMode:input.useVerticalMode
|
||||||
stateCallback:stateCallback
|
stateCallback:stateCallback
|
||||||
errorCallback:errorCallback])
|
errorCallback:errorCallback])
|
||||||
{
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
|
||||||
|
|
||||||
// if nothing is matched, see if it's a punctuation key.
|
// if nothing is matched, see if it's a punctuation key.
|
||||||
std::string punctuation = punctuationNamePrefix + std::string(1, (char)charCode);
|
std::string punctuation = punctuationNamePrefix + std::string(1, (char)charCode);
|
||||||
|
@ -713,9 +624,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
usingVerticalMode:input.useVerticalMode
|
usingVerticalMode:input.useVerticalMode
|
||||||
stateCallback:stateCallback
|
stateCallback:stateCallback
|
||||||
errorCallback:errorCallback])
|
errorCallback:errorCallback])
|
||||||
{
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
|
||||||
|
|
||||||
// Lukhnos 這裡的處理反而會使得 Apple 倚天注音動態鍵盤佈局「敲不了半形大寫英文」的缺點曝露無疑,所以注釋掉。
|
// Lukhnos 這裡的處理反而會使得 Apple 倚天注音動態鍵盤佈局「敲不了半形大寫英文」的缺點曝露無疑,所以注釋掉。
|
||||||
// 至於他試圖用這種處理來解決的上游 UPR293
|
// 至於他試圖用這種處理來解決的上游 UPR293
|
||||||
|
@ -728,9 +637,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
usingVerticalMode:input.useVerticalMode
|
usingVerticalMode:input.useVerticalMode
|
||||||
stateCallback:stateCallback
|
stateCallback:stateCallback
|
||||||
errorCallback:errorCallback])
|
errorCallback:errorCallback])
|
||||||
{
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// still nothing, then we update the composing buffer (some app has strange behavior if we don't do this, "thinking"
|
// 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
|
errorCallback:(void (^)(void))errorCallback
|
||||||
{
|
{
|
||||||
if (![state isKindOfClass:[InputStateInputting class]])
|
if (![state isKindOfClass:[InputStateInputting class]])
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
BOOL escToClearInputBufferEnabled = mgrPrefs.escToCleanInputBuffer;
|
BOOL escToClearInputBufferEnabled = mgrPrefs.escToCleanInputBuffer;
|
||||||
|
|
||||||
|
@ -800,9 +705,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
errorCallback:(void (^)(void))errorCallback
|
errorCallback:(void (^)(void))errorCallback
|
||||||
{
|
{
|
||||||
if (![state isKindOfClass:[InputStateInputting class]])
|
if (![state isKindOfClass:[InputStateInputting class]])
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
if (!_bpmfReadingBuffer->isEmpty())
|
if (!_bpmfReadingBuffer->isEmpty())
|
||||||
{
|
{
|
||||||
|
@ -859,9 +762,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
errorCallback:(void (^)(void))errorCallback
|
errorCallback:(void (^)(void))errorCallback
|
||||||
{
|
{
|
||||||
if (![state isKindOfClass:[InputStateInputting class]])
|
if (![state isKindOfClass:[InputStateInputting class]])
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
if (!_bpmfReadingBuffer->isEmpty())
|
if (!_bpmfReadingBuffer->isEmpty())
|
||||||
{
|
{
|
||||||
|
@ -917,9 +818,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
errorCallback:(void (^)(void))errorCallback
|
errorCallback:(void (^)(void))errorCallback
|
||||||
{
|
{
|
||||||
if (![state isKindOfClass:[InputStateInputting class]])
|
if (![state isKindOfClass:[InputStateInputting class]])
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
if (!_bpmfReadingBuffer->isEmpty())
|
if (!_bpmfReadingBuffer->isEmpty())
|
||||||
{
|
{
|
||||||
|
@ -950,9 +849,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
errorCallback:(void (^)(void))errorCallback
|
errorCallback:(void (^)(void))errorCallback
|
||||||
{
|
{
|
||||||
if (![state isKindOfClass:[InputStateInputting class]])
|
if (![state isKindOfClass:[InputStateInputting class]])
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
if (!_bpmfReadingBuffer->isEmpty())
|
if (!_bpmfReadingBuffer->isEmpty())
|
||||||
{
|
{
|
||||||
|
@ -983,9 +880,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
errorCallback:(void (^)(void))errorCallback
|
errorCallback:(void (^)(void))errorCallback
|
||||||
{
|
{
|
||||||
if (![state isKindOfClass:[InputStateInputting class]])
|
if (![state isKindOfClass:[InputStateInputting class]])
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
if (!_bpmfReadingBuffer->isEmpty())
|
if (!_bpmfReadingBuffer->isEmpty())
|
||||||
{
|
{
|
||||||
|
@ -1001,9 +896,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
errorCallback:(void (^)(void))errorCallback
|
errorCallback:(void (^)(void))errorCallback
|
||||||
{
|
{
|
||||||
if (![state isKindOfClass:[InputStateInputting class]])
|
if (![state isKindOfClass:[InputStateInputting class]])
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
if (_bpmfReadingBuffer->isEmpty())
|
if (_bpmfReadingBuffer->isEmpty())
|
||||||
{
|
{
|
||||||
|
@ -1021,9 +914,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
_bpmfReadingBuffer->backspace();
|
_bpmfReadingBuffer->backspace();
|
||||||
}
|
|
||||||
|
|
||||||
if (_bpmfReadingBuffer->isEmpty() && !_builder->length())
|
if (_bpmfReadingBuffer->isEmpty() && !_builder->length())
|
||||||
{
|
{
|
||||||
|
@ -1043,9 +934,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
errorCallback:(void (^)(void))errorCallback
|
errorCallback:(void (^)(void))errorCallback
|
||||||
{
|
{
|
||||||
if (![state isKindOfClass:[InputStateInputting class]])
|
if (![state isKindOfClass:[InputStateInputting class]])
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
if (_bpmfReadingBuffer->isEmpty())
|
if (_bpmfReadingBuffer->isEmpty())
|
||||||
{
|
{
|
||||||
|
@ -1106,9 +995,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
errorCallback:(void (^)(void))errorCallback
|
errorCallback:(void (^)(void))errorCallback
|
||||||
{
|
{
|
||||||
if (![state isKindOfClass:[InputStateInputting class]])
|
if (![state isKindOfClass:[InputStateInputting class]])
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
[self clear];
|
[self clear];
|
||||||
|
|
||||||
|
@ -1128,9 +1015,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
errorCallback:(void (^)(void))errorCallback
|
errorCallback:(void (^)(void))errorCallback
|
||||||
{
|
{
|
||||||
if (!_languageModel->hasUnigramsForKey(customPunctuation))
|
if (!_languageModel->hasUnigramsForKey(customPunctuation))
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
NSString *poppedText;
|
NSString *poppedText;
|
||||||
if (_bpmfReadingBuffer->isEmpty())
|
if (_bpmfReadingBuffer->isEmpty())
|
||||||
|
@ -1165,9 +1050,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
stateCallback(empty);
|
stateCallback(empty);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
stateCallback(candidateState);
|
stateCallback(candidateState);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
@ -1218,9 +1101,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
stateCallback(marking);
|
stateCallback(marking);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1249,9 +1130,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
stateCallback(marking);
|
stateCallback(marking);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1324,7 +1203,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
if ([input isTab])
|
if ([input isTab])
|
||||||
{
|
{
|
||||||
BOOL updated =
|
BOOL updated =
|
||||||
mgrPrefs.specifyTabKeyBehavior
|
mgrPrefs.specifyShiftTabKeyBehavior
|
||||||
? ([input isShiftHold] ? [ctlCandidateCurrent showPreviousPage] : [ctlCandidateCurrent showNextPage])
|
? ([input isShiftHold] ? [ctlCandidateCurrent showPreviousPage] : [ctlCandidateCurrent showNextPage])
|
||||||
: ([input isShiftHold] ? [ctlCandidateCurrent highlightPreviousCandidate]
|
: ([input isShiftHold] ? [ctlCandidateCurrent highlightPreviousCandidate]
|
||||||
: [ctlCandidateCurrent highlightNextCandidate]);
|
: [ctlCandidateCurrent highlightNextCandidate]);
|
||||||
|
@ -1338,7 +1217,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
|
|
||||||
if ([input isSpace])
|
if ([input isSpace])
|
||||||
{
|
{
|
||||||
BOOL updated = mgrPrefs.specifySpaceKeyBehavior
|
BOOL updated = mgrPrefs.specifyShiftSpaceKeyBehavior
|
||||||
? ([input isShiftHold] ? [ctlCandidateCurrent highlightNextCandidate]
|
? ([input isShiftHold] ? [ctlCandidateCurrent highlightNextCandidate]
|
||||||
: [ctlCandidateCurrent showNextPage])
|
: [ctlCandidateCurrent showNextPage])
|
||||||
: ([input isShiftHold] ? [ctlCandidateCurrent showNextPage]
|
: ([input isShiftHold] ? [ctlCandidateCurrent showNextPage]
|
||||||
|
@ -1495,9 +1374,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
errorCallback();
|
errorCallback();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
ctlCandidateCurrent.selectedCandidateIndex = 0;
|
ctlCandidateCurrent.selectedCandidateIndex = 0;
|
||||||
}
|
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
@ -1505,18 +1382,12 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
NSArray *candidates;
|
NSArray *candidates;
|
||||||
|
|
||||||
if ([state isKindOfClass:[InputStateChoosingCandidate class]])
|
if ([state isKindOfClass:[InputStateChoosingCandidate class]])
|
||||||
{
|
|
||||||
candidates = [(InputStateChoosingCandidate *)state candidates];
|
candidates = [(InputStateChoosingCandidate *)state candidates];
|
||||||
}
|
|
||||||
else if ([state isKindOfClass:[InputStateAssociatedPhrases class]])
|
else if ([state isKindOfClass:[InputStateAssociatedPhrases class]])
|
||||||
{
|
|
||||||
candidates = [(InputStateAssociatedPhrases *)state candidates];
|
candidates = [(InputStateAssociatedPhrases *)state candidates];
|
||||||
}
|
|
||||||
|
|
||||||
if (!candidates)
|
if (!candidates)
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
if (([input isEnd] || input.emacsKey == vChewingEmacsKeyEnd) && candidates.count > 0)
|
if (([input isEnd] || input.emacsKey == vChewingEmacsKeyEnd) && candidates.count > 0)
|
||||||
{
|
{
|
||||||
|
@ -1526,30 +1397,23 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
errorCallback();
|
errorCallback();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
ctlCandidateCurrent.selectedCandidateIndex = candidates.count - 1;
|
ctlCandidateCurrent.selectedCandidateIndex = candidates.count - 1;
|
||||||
}
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([state isKindOfClass:[InputStateAssociatedPhrases class]])
|
if ([state isKindOfClass:[InputStateAssociatedPhrases class]])
|
||||||
{
|
{
|
||||||
if (![input isShiftHold])
|
if (![input isShiftHold])
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NSInteger index = NSNotFound;
|
NSInteger index = NSNotFound;
|
||||||
NSString *match;
|
NSString *match;
|
||||||
if ([state isKindOfClass:[InputStateAssociatedPhrases class]])
|
if ([state isKindOfClass:[InputStateAssociatedPhrases class]])
|
||||||
{
|
|
||||||
match = input.inputTextIgnoringModifiers;
|
match = input.inputTextIgnoringModifiers;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
match = inputText;
|
match = inputText;
|
||||||
}
|
|
||||||
|
|
||||||
for (NSUInteger j = 0, c = [ctlCandidateCurrent.keyLabels count]; j < c; j++)
|
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]])
|
if ([state isKindOfClass:[InputStateAssociatedPhrases class]])
|
||||||
{
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
|
||||||
|
|
||||||
if (mgrPrefs.useSCPCTypingMode)
|
if (mgrPrefs.useSCPCTypingMode)
|
||||||
{
|
{
|
||||||
std::string layout = [self _currentLayout];
|
|
||||||
std::string punctuationNamePrefix;
|
std::string punctuationNamePrefix;
|
||||||
if ([input isOptionHold])
|
if ([input isOptionHold])
|
||||||
{
|
|
||||||
punctuationNamePrefix = std::string("_alt_punctuation_");
|
punctuationNamePrefix = std::string("_alt_punctuation_");
|
||||||
}
|
|
||||||
else if ([input isControlHold])
|
else if ([input isControlHold])
|
||||||
{
|
|
||||||
punctuationNamePrefix = std::string("_ctrl_punctuation_");
|
punctuationNamePrefix = std::string("_ctrl_punctuation_");
|
||||||
}
|
|
||||||
else if (mgrPrefs.halfWidthPunctuationEnabled)
|
else if (mgrPrefs.halfWidthPunctuationEnabled)
|
||||||
{
|
|
||||||
punctuationNamePrefix = std::string("_half_punctuation_");
|
punctuationNamePrefix = std::string("_half_punctuation_");
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
punctuationNamePrefix = std::string("_punctuation_");
|
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);
|
std::string punctuation = punctuationNamePrefix + std::string(1, (char)charCode);
|
||||||
|
|
||||||
BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((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);
|
std::string letter = std::string("_letter_") + std::string(1, (char)charCode);
|
||||||
if (_languageModel->hasUnigramsForKey(letter))
|
if (_languageModel->hasUnigramsForKey(letter))
|
||||||
{
|
|
||||||
shouldAutoSelectCandidate = YES;
|
shouldAutoSelectCandidate = YES;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldAutoSelectCandidate)
|
if (shouldAutoSelectCandidate)
|
||||||
|
@ -1746,7 +1599,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
- (void)_walk
|
- (void)_walk
|
||||||
{
|
{
|
||||||
// retrieve the most likely trellis, i.e. a Maximum Likelihood Estimation
|
// 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
|
// using the Viterbi algorithm implemented in the Gramambular library
|
||||||
Gramambular::Walker walker(&_builder->grid());
|
Gramambular::Walker walker(&_builder->grid());
|
||||||
|
|
||||||
|
@ -1813,9 +1666,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
const std::vector<Gramambular::KeyValuePair> &candidates = (*ni).node->candidates();
|
const std::vector<Gramambular::KeyValuePair> &candidates = (*ni).node->candidates();
|
||||||
for (std::vector<Gramambular::KeyValuePair>::const_iterator ci = candidates.begin(), ce = candidates.end();
|
for (std::vector<Gramambular::KeyValuePair>::const_iterator ci = candidates.begin(), ce = candidates.end();
|
||||||
ci != ce; ++ci)
|
ci != ce; ++ci)
|
||||||
{
|
|
||||||
[candidatesArray addObject:[NSString stringWithUTF8String:(*ci).value.c_str()]];
|
[candidatesArray addObject:[NSString stringWithUTF8String:(*ci).value.c_str()]];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStateChoosingCandidate *state =
|
InputStateChoosingCandidate *state =
|
||||||
|
@ -1831,9 +1682,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
size_t cursorIndex = _builder->cursorIndex();
|
size_t cursorIndex = _builder->cursorIndex();
|
||||||
// MS Phonetics IME style, phrase is *after* the cursor, i.e. cursor is always *before* the phrase
|
// MS Phonetics IME style, phrase is *after* the cursor, i.e. cursor is always *before* the phrase
|
||||||
if ((mgrPrefs.selectPhraseAfterCursorAsCandidate && (cursorIndex < _builder->length())) || !cursorIndex)
|
if ((mgrPrefs.selectPhraseAfterCursorAsCandidate && (cursorIndex < _builder->length())) || !cursorIndex)
|
||||||
{
|
|
||||||
++cursorIndex;
|
++cursorIndex;
|
||||||
}
|
|
||||||
|
|
||||||
return cursorIndex;
|
return cursorIndex;
|
||||||
}
|
}
|
||||||
|
@ -1843,9 +1692,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
NSMutableArray *readingsArray = [[NSMutableArray alloc] init];
|
NSMutableArray *readingsArray = [[NSMutableArray alloc] init];
|
||||||
std::vector<std::string> v = _builder->readings();
|
std::vector<std::string> v = _builder->readings();
|
||||||
for (std::vector<std::string>::iterator it_i = v.begin(); it_i != v.end(); ++it_i)
|
for (std::vector<std::string>::iterator it_i = v.begin(); it_i != v.end(); ++it_i)
|
||||||
{
|
|
||||||
[readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]];
|
[readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]];
|
||||||
}
|
|
||||||
return readingsArray;
|
return readingsArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class FSEventStreamHelper: NSObject {
|
||||||
self.dispatchQueue = queue
|
self.dispatchQueue = queue
|
||||||
}
|
}
|
||||||
|
|
||||||
private var stream: FSEventStreamRef? = nil
|
private var stream: FSEventStreamRef?
|
||||||
|
|
||||||
public func start() -> Bool {
|
public func start() -> Bool {
|
||||||
if stream != nil {
|
if stream != nil {
|
||||||
|
@ -59,7 +59,7 @@ public class FSEventStreamHelper: NSObject {
|
||||||
let stream = FSEventStreamCreate(
|
let stream = FSEventStreamCreate(
|
||||||
nil,
|
nil,
|
||||||
{
|
{
|
||||||
(stream, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in
|
(_, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in
|
||||||
let helper = Unmanaged<FSEventStreamHelper>.fromOpaque(clientCallBackInfo!)
|
let helper = Unmanaged<FSEventStreamHelper>.fromOpaque(clientCallBackInfo!)
|
||||||
.takeUnretainedValue()
|
.takeUnretainedValue()
|
||||||
let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer<CChar>.self)
|
let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer<CChar>.self)
|
||||||
|
|
|
@ -22,10 +22,11 @@ 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.
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import Carbon
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
@objc public class IME: NSObject {
|
@objc public class IME: NSObject {
|
||||||
|
static let arrSupportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"]
|
||||||
static let dlgOpenPath = NSOpenPanel()
|
static let dlgOpenPath = NSOpenPanel()
|
||||||
|
|
||||||
// MARK: - 開關判定當前應用究竟是?
|
// MARK: - 開關判定當前應用究竟是?
|
||||||
|
@ -84,7 +85,9 @@ import Cocoa
|
||||||
// MARK: - Open a phrase data file.
|
// MARK: - Open a phrase data file.
|
||||||
static func openPhraseFile(userFileAt path: String) {
|
static func openPhraseFile(userFileAt path: String) {
|
||||||
func checkIfUserFilesExist() -> Bool {
|
func checkIfUserFilesExist() -> Bool {
|
||||||
if !mgrLangModel.checkIfUserLanguageModelFilesExist() {
|
if !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHS)
|
||||||
|
|| !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHT)
|
||||||
|
{
|
||||||
let content = String(
|
let content = String(
|
||||||
format: NSLocalizedString(
|
format: NSLocalizedString(
|
||||||
"Please check the permission at \"%@\".", comment: ""),
|
"Please check the permission at \"%@\".", comment: ""),
|
||||||
|
@ -224,4 +227,120 @@ import Cocoa
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - 準備枚舉系統內所有的 ASCII 鍵盤佈局
|
||||||
|
struct CarbonKeyboardLayout {
|
||||||
|
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] = []
|
||||||
|
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 arrKeyLayoutsMACV: [IME.CarbonKeyboardLayout] = []
|
||||||
|
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<CFString>.fromOpaque(ptrCategory).takeUnretainedValue()
|
||||||
|
if category != kTISCategoryKeyboardInputSource {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ptrASCIICapable = TISGetInputSourceProperty(
|
||||||
|
source, kTISPropertyInputSourceIsASCIICapable)
|
||||||
|
{
|
||||||
|
let asciiCapable = Unmanaged<CFBoolean>.fromOpaque(ptrASCIICapable)
|
||||||
|
.takeUnretainedValue()
|
||||||
|
if asciiCapable != kCFBooleanTrue {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ptrSourceType = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) {
|
||||||
|
let sourceType = Unmanaged<CFString>.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<CFString>.fromOpaque(ptrSourceID).takeUnretainedValue())
|
||||||
|
let localizedName = String(
|
||||||
|
Unmanaged<CFString>.fromOpaque(localizedNamePtr).takeUnretainedValue())
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<Element>()
|
||||||
|
return filter { set.insert($0).inserted }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Error Extension
|
||||||
|
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 += "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
// 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<VersionUpdateApiResult, Error>) -> 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 preferredTags = Bundle.preferredLocalizations(from: IME.arrSupportedLocales)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,7 +49,7 @@ class ctlInputMethod: IMKInputController {
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
private var currentCandidateClient: Any?
|
private var currentClient: Any?
|
||||||
|
|
||||||
private var keyHandler: KeyHandler = KeyHandler()
|
private var keyHandler: KeyHandler = KeyHandler()
|
||||||
private var state: InputState = InputState.Empty()
|
private var state: InputState = InputState.Empty()
|
||||||
|
@ -65,8 +65,9 @@ class ctlInputMethod: IMKInputController {
|
||||||
// MARK: - Keyboard Layout Specifier
|
// MARK: - Keyboard Layout Specifier
|
||||||
|
|
||||||
@objc func setKeyLayout() {
|
@objc func setKeyLayout() {
|
||||||
let client = client().self as IMKTextInput
|
if let client = currentClient {
|
||||||
client.overrideKeyboard(withKeyboardNamed: mgrPrefs.basisKeyboardLayout)
|
(client as? IMKTextInput)?.overrideKeyboard(withKeyboardNamed: mgrPrefs.basicKeyboardLayout)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - IMKInputController methods
|
// MARK: - IMKInputController methods
|
||||||
|
@ -76,24 +77,38 @@ class ctlInputMethod: IMKInputController {
|
||||||
keyHandler.delegate = self
|
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
|
// MARK: - IMKStateSetting protocol methods
|
||||||
|
|
||||||
override func activateServer(_ client: Any!) {
|
override func activateServer(_ client: Any!) {
|
||||||
UserDefaults.standard.synchronize()
|
UserDefaults.standard.synchronize()
|
||||||
|
|
||||||
// Override the keyboard layout to the basic one.
|
|
||||||
setKeyLayout()
|
|
||||||
// reset the state
|
// reset the state
|
||||||
currentCandidateClient = nil
|
currentClient = client
|
||||||
|
|
||||||
keyHandler.clear()
|
keyHandler.clear()
|
||||||
keyHandler.syncWithPreferences()
|
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()
|
(NSApp.delegate as? AppDelegate)?.checkForUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func deactivateServer(_ client: Any!) {
|
override func deactivateServer(_ client: Any!) {
|
||||||
keyHandler.clear()
|
keyHandler.clear()
|
||||||
|
currentClient = nil
|
||||||
self.handle(state: .Empty(), client: client)
|
self.handle(state: .Empty(), client: client)
|
||||||
self.handle(state: .Deactivated(), client: client)
|
self.handle(state: .Deactivated(), client: client)
|
||||||
}
|
}
|
||||||
|
@ -110,14 +125,18 @@ class ctlInputMethod: IMKInputController {
|
||||||
}
|
}
|
||||||
mgrLangModel.loadDataModel(newInputMode)
|
mgrLangModel.loadDataModel(newInputMode)
|
||||||
|
|
||||||
// Remember to override the keyboard layout again -- treat this as an activate event.
|
|
||||||
setKeyLayout()
|
|
||||||
|
|
||||||
if keyHandler.inputMode != newInputMode {
|
if keyHandler.inputMode != newInputMode {
|
||||||
|
|
||||||
UserDefaults.standard.synchronize()
|
UserDefaults.standard.synchronize()
|
||||||
keyHandler.clear()
|
keyHandler.clear()
|
||||||
keyHandler.inputMode = newInputMode
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 讓外界知道目前的簡繁體輸入模式。
|
// 讓外界知道目前的簡繁體輸入模式。
|
||||||
|
@ -225,7 +244,7 @@ extension ctlInputMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) {
|
private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) {
|
||||||
currentCandidateClient = nil
|
currentClient = nil
|
||||||
|
|
||||||
ctlCandidateCurrent?.delegate = nil
|
ctlCandidateCurrent?.delegate = nil
|
||||||
ctlCandidateCurrent?.visible = false
|
ctlCandidateCurrent?.visible = false
|
||||||
|
@ -235,8 +254,8 @@ extension ctlInputMethod {
|
||||||
commit(text: previous.composingBuffer, client: client)
|
commit(text: previous.composingBuffer, client: client)
|
||||||
}
|
}
|
||||||
(client as? IMKTextInput)?.setMarkedText(
|
(client as? IMKTextInput)?.setMarkedText(
|
||||||
"", selectionRange: NSMakeRange(0, 0),
|
"", selectionRange: NSRange(location: 0, length: 0),
|
||||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(state: InputState.Empty, previous: InputState, client: Any?) {
|
private func handle(state: InputState.Empty, previous: InputState, client: Any?) {
|
||||||
|
@ -251,8 +270,8 @@ extension ctlInputMethod {
|
||||||
commit(text: previous.composingBuffer, client: client)
|
commit(text: previous.composingBuffer, client: client)
|
||||||
}
|
}
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
"", selectionRange: NSMakeRange(0, 0),
|
"", selectionRange: NSRange(location: 0, length: 0),
|
||||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(
|
private func handle(
|
||||||
|
@ -266,8 +285,8 @@ extension ctlInputMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
"", selectionRange: NSMakeRange(0, 0),
|
"", selectionRange: NSRange(location: 0, length: 0),
|
||||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(state: InputState.Committing, previous: InputState, client: Any?) {
|
private func handle(state: InputState.Committing, previous: InputState, client: Any?) {
|
||||||
|
@ -283,8 +302,8 @@ extension ctlInputMethod {
|
||||||
commit(text: poppedText, client: client)
|
commit(text: poppedText, client: client)
|
||||||
}
|
}
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
"", selectionRange: NSMakeRange(0, 0),
|
"", selectionRange: NSRange(location: 0, length: 0),
|
||||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(state: InputState.Inputting, previous: InputState, client: Any?) {
|
private func handle(state: InputState.Inputting, previous: InputState, client: Any?) {
|
||||||
|
@ -303,8 +322,8 @@ extension ctlInputMethod {
|
||||||
// the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound,
|
// 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
|
// i.e. the client app needs to take care of where to put this composing buffer
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0),
|
state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0),
|
||||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||||
if !state.tooltip.isEmpty {
|
if !state.tooltip.isEmpty {
|
||||||
show(
|
show(
|
||||||
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
|
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
|
||||||
|
@ -322,8 +341,8 @@ extension ctlInputMethod {
|
||||||
// the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound,
|
// 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
|
// i.e. the client app needs to take care of where to put this composing buffer
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0),
|
state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0),
|
||||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||||
|
|
||||||
if state.tooltip.isEmpty {
|
if state.tooltip.isEmpty {
|
||||||
hideTooltip()
|
hideTooltip()
|
||||||
|
@ -344,8 +363,8 @@ extension ctlInputMethod {
|
||||||
// the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound,
|
// 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
|
// i.e. the client app needs to take care of where to put this composing buffer
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0),
|
state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0),
|
||||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||||
show(candidateWindowWith: state, client: client)
|
show(candidateWindowWith: state, client: client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,8 +375,8 @@ extension ctlInputMethod {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
"", selectionRange: NSMakeRange(0, 0),
|
"", selectionRange: NSRange(location: 0, length: 0),
|
||||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||||
show(candidateWindowWith: state, client: client)
|
show(candidateWindowWith: state, client: client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -441,11 +460,11 @@ extension ctlInputMethod {
|
||||||
|
|
||||||
ctlCandidateCurrent?.delegate = self
|
ctlCandidateCurrent?.delegate = self
|
||||||
ctlCandidateCurrent?.reloadData()
|
ctlCandidateCurrent?.reloadData()
|
||||||
currentCandidateClient = client
|
currentClient = client
|
||||||
|
|
||||||
ctlCandidateCurrent?.visible = true
|
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
|
var cursor: Int = 0
|
||||||
|
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
|
@ -463,20 +482,18 @@ extension ctlInputMethod {
|
||||||
|
|
||||||
if useVerticalMode {
|
if useVerticalMode {
|
||||||
ctlCandidateCurrent?.set(
|
ctlCandidateCurrent?.set(
|
||||||
windowTopLeftPoint: NSMakePoint(
|
windowTopLeftPoint: NSPoint(
|
||||||
lineHeightRect.origin.x + lineHeightRect.size.width + 4.0,
|
x: lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, y: lineHeightRect.origin.y - 4.0),
|
||||||
lineHeightRect.origin.y - 4.0),
|
|
||||||
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
|
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
|
||||||
} else {
|
} else {
|
||||||
ctlCandidateCurrent?.set(
|
ctlCandidateCurrent?.set(
|
||||||
windowTopLeftPoint: NSMakePoint(
|
windowTopLeftPoint: NSPoint(x: lineHeightRect.origin.x, y: lineHeightRect.origin.y - 4.0),
|
||||||
lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0),
|
|
||||||
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
|
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func show(tooltip: String, composingBuffer: String, cursorIndex: UInt, client: Any!) {
|
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)
|
var cursor: Int = Int(cursorIndex)
|
||||||
if cursor == composingBuffer.count && cursor != 0 {
|
if cursor == composingBuffer.count && cursor != 0 {
|
||||||
cursor -= 1
|
cursor -= 1
|
||||||
|
@ -522,14 +539,17 @@ extension ctlInputMethod: KeyHandlerDelegate {
|
||||||
let refInputModeReversed: InputMode =
|
let refInputModeReversed: InputMode =
|
||||||
(keyHandler.inputMode == InputMode.imeModeCHT)
|
(keyHandler.inputMode == InputMode.imeModeCHT)
|
||||||
? InputMode.imeModeCHS : InputMode.imeModeCHT
|
? InputMode.imeModeCHS : InputMode.imeModeCHT
|
||||||
mgrLangModel.writeUserPhrase(
|
if !mgrLangModel.writeUserPhrase(
|
||||||
state.userPhrase, inputMode: keyHandler.inputMode,
|
state.userPhrase, inputMode: keyHandler.inputMode,
|
||||||
areWeDuplicating: state.chkIfUserPhraseExists,
|
areWeDuplicating: state.chkIfUserPhraseExists,
|
||||||
areWeDeleting: ctlInputMethod.areWeDeleting)
|
areWeDeleting: ctlInputMethod.areWeDeleting)
|
||||||
mgrLangModel.writeUserPhrase(
|
|| !mgrLangModel.writeUserPhrase(
|
||||||
state.userPhraseConverted, inputMode: refInputModeReversed,
|
state.userPhraseConverted, inputMode: refInputModeReversed,
|
||||||
areWeDuplicating: false,
|
areWeDuplicating: false,
|
||||||
areWeDeleting: ctlInputMethod.areWeDeleting)
|
areWeDeleting: ctlInputMethod.areWeDeleting)
|
||||||
|
{
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -558,7 +578,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt) {
|
func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt) {
|
||||||
let client = currentCandidateClient
|
let client = currentClient
|
||||||
|
|
||||||
if let state = state as? InputState.SymbolTable,
|
if let state = state as? InputState.SymbolTable,
|
||||||
let node = state.node.children?[Int(index)]
|
let node = state.node.children?[Int(index)]
|
||||||
|
@ -566,7 +586,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
if let children = node.children, !children.isEmpty {
|
if let children = node.children, !children.isEmpty {
|
||||||
self.handle(
|
self.handle(
|
||||||
state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode),
|
state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode),
|
||||||
client: currentCandidateClient)
|
client: currentClient)
|
||||||
} else {
|
} else {
|
||||||
self.handle(state: .Committing(poppedText: node.title), client: client)
|
self.handle(state: .Committing(poppedText: node.title), client: client)
|
||||||
self.handle(state: .Empty(), client: client)
|
self.handle(state: .Empty(), client: client)
|
||||||
|
@ -603,7 +623,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
|
|
||||||
if let state = state as? InputState.AssociatedPhrases {
|
if let state = state as? InputState.AssociatedPhrases {
|
||||||
let selectedValue = state.candidates[Int(index)]
|
let selectedValue = state.candidates[Int(index)]
|
||||||
handle(state: .Committing(poppedText: selectedValue), client: currentCandidateClient)
|
handle(state: .Committing(poppedText: selectedValue), client: currentClient)
|
||||||
if mgrPrefs.associatedPhrasesEnabled,
|
if mgrPrefs.associatedPhrasesEnabled,
|
||||||
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
||||||
withKey: selectedValue, useVerticalMode: state.useVerticalMode)
|
withKey: selectedValue, useVerticalMode: state.useVerticalMode)
|
||||||
|
|
|
@ -123,10 +123,14 @@ extension ctlInputMethod {
|
||||||
|
|
||||||
menu.addItem(NSMenuItem.separator()) // ---------------------
|
menu.addItem(NSMenuItem.separator()) // ---------------------
|
||||||
|
|
||||||
menu.addItem(
|
if optionKeyPressed {
|
||||||
withTitle: NSLocalizedString("vChewing Preferences…", comment: ""),
|
menu.addItem(
|
||||||
action: #selector(showPreferences(_:)), keyEquivalent: "")
|
withTitle: NSLocalizedString("vChewing Preferences…", comment: ""),
|
||||||
if !optionKeyPressed {
|
action: #selector(showLegacyPreferences(_:)), keyEquivalent: "")
|
||||||
|
} else {
|
||||||
|
menu.addItem(
|
||||||
|
withTitle: NSLocalizedString("vChewing Preferences…", comment: ""),
|
||||||
|
action: #selector(showPreferences(_:)), keyEquivalent: "")
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("Check for Updates…", comment: ""),
|
withTitle: NSLocalizedString("Check for Updates…", comment: ""),
|
||||||
action: #selector(checkForUpdate(_:)), keyEquivalent: "")
|
action: #selector(checkForUpdate(_:)), keyEquivalent: "")
|
||||||
|
@ -152,6 +156,20 @@ extension ctlInputMethod {
|
||||||
// MARK: - IME Menu Items
|
// MARK: - IME Menu Items
|
||||||
|
|
||||||
@objc override func showPreferences(_ sender: Any?) {
|
@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.delegate as? AppDelegate)?.showPreferences()
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
@ -163,6 +181,7 @@ extension ctlInputMethod {
|
||||||
mgrPrefs.toggleSCPCTypingModeEnabled()
|
mgrPrefs.toggleSCPCTypingModeEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||||
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleChineseConverter(_ sender: Any?) {
|
@objc func toggleChineseConverter(_ sender: Any?) {
|
||||||
|
@ -172,6 +191,7 @@ extension ctlInputMethod {
|
||||||
mgrPrefs.toggleChineseConversionEnabled()
|
mgrPrefs.toggleChineseConversionEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||||
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleShiftJISShinjitaiOutput(_ sender: Any?) {
|
@objc func toggleShiftJISShinjitaiOutput(_ sender: Any?) {
|
||||||
|
@ -181,6 +201,7 @@ extension ctlInputMethod {
|
||||||
mgrPrefs.toggleShiftJISShinjitaiOutputEnabled()
|
mgrPrefs.toggleShiftJISShinjitaiOutputEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||||
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleHalfWidthPunctuation(_ sender: Any?) {
|
@objc func toggleHalfWidthPunctuation(_ sender: Any?) {
|
||||||
|
@ -191,6 +212,7 @@ extension ctlInputMethod {
|
||||||
mgrPrefs.toggleHalfWidthPunctuationEnabled()
|
mgrPrefs.toggleHalfWidthPunctuationEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||||
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleCNS11643Enabled(_ sender: Any?) {
|
@objc func toggleCNS11643Enabled(_ sender: Any?) {
|
||||||
|
@ -200,6 +222,7 @@ extension ctlInputMethod {
|
||||||
mgrPrefs.toggleCNS11643Enabled()
|
mgrPrefs.toggleCNS11643Enabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||||
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleSymbolEnabled(_ sender: Any?) {
|
@objc func toggleSymbolEnabled(_ sender: Any?) {
|
||||||
|
@ -209,6 +232,7 @@ extension ctlInputMethod {
|
||||||
mgrPrefs.toggleSymbolInputEnabled()
|
mgrPrefs.toggleSymbolInputEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||||
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleAssociatedPhrasesEnabled(_ sender: Any?) {
|
@objc func toggleAssociatedPhrasesEnabled(_ sender: Any?) {
|
||||||
|
@ -219,6 +243,7 @@ extension ctlInputMethod {
|
||||||
mgrPrefs.toggleAssociatedPhrasesEnabled()
|
mgrPrefs.toggleAssociatedPhrasesEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||||
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func togglePhraseReplacement(_ sender: Any?) {
|
@objc func togglePhraseReplacement(_ sender: Any?) {
|
||||||
|
@ -228,6 +253,7 @@ extension ctlInputMethod {
|
||||||
mgrPrefs.togglePhraseReplacementEnabled()
|
mgrPrefs.togglePhraseReplacementEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||||
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func selfUninstall(_ sender: Any?) {
|
@objc func selfUninstall(_ sender: Any?) {
|
||||||
|
|
|
@ -26,38 +26,40 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
private let kIsDebugModeEnabled = "_DebugMode"
|
struct UserDef {
|
||||||
private let kUserDataFolderSpecified = "UserDataFolderSpecified"
|
static let kIsDebugModeEnabled = "_DebugMode"
|
||||||
private let kCheckUpdateAutomatically = "CheckUpdateAutomatically"
|
static let kUserDataFolderSpecified = "UserDataFolderSpecified"
|
||||||
private let kKeyboardLayoutPreference = "KeyboardLayout"
|
static let kCheckUpdateAutomatically = "CheckUpdateAutomatically"
|
||||||
private let kBasisKeyboardLayoutPreference = "BasisKeyboardLayout"
|
static let kMandarinParser = "MandarinParser"
|
||||||
private let kShowPageButtonsInCandidateWindow = "ShowPageButtonsInCandidateWindow"
|
static let kBasicKeyboardLayout = "BasicKeyboardLayout"
|
||||||
private let kCandidateListTextSize = "CandidateListTextSize"
|
static let kShowPageButtonsInCandidateWindow = "ShowPageButtonsInCandidateWindow"
|
||||||
private let kAppleLanguagesPreferences = "AppleLanguages"
|
static let kCandidateListTextSize = "CandidateListTextSize"
|
||||||
private let kShouldAutoReloadUserDataFiles = "ShouldAutoReloadUserDataFiles"
|
static let kAppleLanguages = "AppleLanguages"
|
||||||
private let kSelectPhraseAfterCursorAsCandidatePreference = "SelectPhraseAfterCursorAsCandidate"
|
static let kShouldAutoReloadUserDataFiles = "ShouldAutoReloadUserDataFiles"
|
||||||
private let kUseHorizontalCandidateListPreference = "UseHorizontalCandidateList"
|
static let kSelectPhraseAfterCursorAsCandidate = "SelectPhraseAfterCursorAsCandidate"
|
||||||
private let kComposingBufferSizePreference = "ComposingBufferSize"
|
static let kUseHorizontalCandidateList = "UseHorizontalCandidateList"
|
||||||
private let kChooseCandidateUsingSpace = "ChooseCandidateUsingSpace"
|
static let kComposingBufferSize = "ComposingBufferSize"
|
||||||
private let kCNS11643Enabled = "CNS11643Enabled"
|
static let kChooseCandidateUsingSpace = "ChooseCandidateUsingSpace"
|
||||||
private let kSymbolInputEnabled = "SymbolInputEnabled"
|
static let kCNS11643Enabled = "CNS11643Enabled"
|
||||||
private let kChineseConversionEnabled = "ChineseConversionEnabled"
|
static let kSymbolInputEnabled = "SymbolInputEnabled"
|
||||||
private let kShiftJISShinjitaiOutputEnabled = "ShiftJISShinjitaiOutputEnabled"
|
static let kChineseConversionEnabled = "ChineseConversionEnabled"
|
||||||
private let kHalfWidthPunctuationEnabled = "HalfWidthPunctuationEnable"
|
static let kShiftJISShinjitaiOutputEnabled = "ShiftJISShinjitaiOutputEnabled"
|
||||||
private let kMoveCursorAfterSelectingCandidate = "MoveCursorAfterSelectingCandidate"
|
static let kHalfWidthPunctuationEnabled = "HalfWidthPunctuationEnable"
|
||||||
private let kEscToCleanInputBuffer = "EscToCleanInputBuffer"
|
static let kMoveCursorAfterSelectingCandidate = "MoveCursorAfterSelectingCandidate"
|
||||||
private let kSpecifyTabKeyBehavior = "SpecifyTabKeyBehavior"
|
static let kEscToCleanInputBuffer = "EscToCleanInputBuffer"
|
||||||
private let kSpecifySpaceKeyBehavior = "SpecifySpaceKeyBehavior"
|
static let kSpecifyShiftTabKeyBehavior = "SpecifyShiftTabKeyBehavior"
|
||||||
private let kUseSCPCTypingMode = "UseSCPCTypingMode"
|
static let kSpecifyShiftSpaceKeyBehavior = "SpecifyShiftSpaceKeyBehavior"
|
||||||
private let kMaxCandidateLength = "MaxCandidateLength"
|
static let kUseSCPCTypingMode = "UseSCPCTypingMode"
|
||||||
private let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep"
|
static let kMaxCandidateLength = "MaxCandidateLength"
|
||||||
|
static let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep"
|
||||||
|
|
||||||
private let kCandidateTextFontName = "CandidateTextFontName"
|
static let kCandidateTextFontName = "CandidateTextFontName"
|
||||||
private let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName"
|
static let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName"
|
||||||
private let kCandidateKeys = "CandidateKeys"
|
static let kCandidateKeys = "CandidateKeys"
|
||||||
|
|
||||||
private let kAssociatedPhrasesEnabled = "AssociatedPhrasesEnabled"
|
static let kAssociatedPhrasesEnabled = "AssociatedPhrasesEnabled"
|
||||||
private let kPhraseReplacementEnabled = "PhraseReplacementEnabled"
|
static let kPhraseReplacementEnabled = "PhraseReplacementEnabled"
|
||||||
|
}
|
||||||
|
|
||||||
private let kDefaultCandidateListTextSize: CGFloat = 18
|
private let kDefaultCandidateListTextSize: CGFloat = 18
|
||||||
private let kMinKeyLabelSize: CGFloat = 10
|
private let kMinKeyLabelSize: CGFloat = 10
|
||||||
|
@ -75,7 +77,17 @@ private let kMaxComposingBufferSize = 30
|
||||||
|
|
||||||
private let kDefaultKeys = "123456789"
|
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
|
@propertyWrapper
|
||||||
struct UserDefault<Value> {
|
struct UserDefault<Value> {
|
||||||
|
@ -155,7 +167,7 @@ struct ComposingBufferSize {
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
@objc enum KeyboardLayout: Int {
|
@objc enum MandarinParser: Int {
|
||||||
case ofStandard = 0
|
case ofStandard = 0
|
||||||
case ofEten = 1
|
case ofEten = 1
|
||||||
case ofHsu = 2
|
case ofHsu = 2
|
||||||
|
@ -191,248 +203,169 @@ struct ComposingBufferSize {
|
||||||
@objc public class mgrPrefs: NSObject {
|
@objc public class mgrPrefs: NSObject {
|
||||||
static var allKeys: [String] {
|
static var allKeys: [String] {
|
||||||
[
|
[
|
||||||
kIsDebugModeEnabled,
|
UserDef.kIsDebugModeEnabled,
|
||||||
kUserDataFolderSpecified,
|
UserDef.kUserDataFolderSpecified,
|
||||||
kKeyboardLayoutPreference,
|
UserDef.kMandarinParser,
|
||||||
kBasisKeyboardLayoutPreference,
|
UserDef.kBasicKeyboardLayout,
|
||||||
kShowPageButtonsInCandidateWindow,
|
UserDef.kShowPageButtonsInCandidateWindow,
|
||||||
kCandidateListTextSize,
|
UserDef.kCandidateListTextSize,
|
||||||
kAppleLanguagesPreferences,
|
UserDef.kAppleLanguages,
|
||||||
kShouldAutoReloadUserDataFiles,
|
UserDef.kShouldAutoReloadUserDataFiles,
|
||||||
kSelectPhraseAfterCursorAsCandidatePreference,
|
UserDef.kSelectPhraseAfterCursorAsCandidate,
|
||||||
kUseHorizontalCandidateListPreference,
|
UserDef.kUseHorizontalCandidateList,
|
||||||
kComposingBufferSizePreference,
|
UserDef.kComposingBufferSize,
|
||||||
kChooseCandidateUsingSpace,
|
UserDef.kChooseCandidateUsingSpace,
|
||||||
kCNS11643Enabled,
|
UserDef.kCNS11643Enabled,
|
||||||
kSymbolInputEnabled,
|
UserDef.kSymbolInputEnabled,
|
||||||
kChineseConversionEnabled,
|
UserDef.kChineseConversionEnabled,
|
||||||
kShiftJISShinjitaiOutputEnabled,
|
UserDef.kShiftJISShinjitaiOutputEnabled,
|
||||||
kHalfWidthPunctuationEnabled,
|
UserDef.kHalfWidthPunctuationEnabled,
|
||||||
kSpecifyTabKeyBehavior,
|
UserDef.kSpecifyShiftTabKeyBehavior,
|
||||||
kSpecifySpaceKeyBehavior,
|
UserDef.kSpecifyShiftSpaceKeyBehavior,
|
||||||
kEscToCleanInputBuffer,
|
UserDef.kEscToCleanInputBuffer,
|
||||||
kCandidateTextFontName,
|
UserDef.kCandidateTextFontName,
|
||||||
kCandidateKeyLabelFontName,
|
UserDef.kCandidateKeyLabelFontName,
|
||||||
kCandidateKeys,
|
UserDef.kCandidateKeys,
|
||||||
kMoveCursorAfterSelectingCandidate,
|
UserDef.kMoveCursorAfterSelectingCandidate,
|
||||||
kPhraseReplacementEnabled,
|
UserDef.kPhraseReplacementEnabled,
|
||||||
kUseSCPCTypingMode,
|
UserDef.kUseSCPCTypingMode,
|
||||||
kMaxCandidateLength,
|
UserDef.kMaxCandidateLength,
|
||||||
kShouldNotFartInLieuOfBeep,
|
UserDef.kShouldNotFartInLieuOfBeep,
|
||||||
kAssociatedPhrasesEnabled,
|
UserDef.kAssociatedPhrasesEnabled,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - 既然 Preferences Module 的預設屬性不自動寫入 plist,那這邊就先寫入了。
|
||||||
@objc public static func setMissingDefaults() {
|
@objc public static func setMissingDefaults() {
|
||||||
// 既然 Preferences Module 的預設屬性不自動寫入 plist、而且還是 private,那這邊就先寫入了。
|
UserDefaults.standard.setDefault(mgrPrefs.isDebugModeEnabled, forKey: UserDef.kIsDebugModeEnabled)
|
||||||
|
UserDefaults.standard.setDefault(mgrPrefs.checkUpdateAutomatically, forKey: UserDef.kCheckUpdateAutomatically)
|
||||||
// 首次啟用輸入法時不要啟用偵錯模式。
|
UserDefaults.standard.setDefault(
|
||||||
if UserDefaults.standard.object(forKey: kIsDebugModeEnabled) == nil {
|
mgrPrefs.showPageButtonsInCandidateWindow, forKey: UserDef.kShowPageButtonsInCandidateWindow)
|
||||||
UserDefaults.standard.set(mgrPrefs.isDebugModeEnabled, forKey: kIsDebugModeEnabled)
|
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(
|
||||||
if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil {
|
mgrPrefs.shouldAutoReloadUserDataFiles, forKey: UserDef.kShouldAutoReloadUserDataFiles)
|
||||||
UserDefaults.standard.set(false, forKey: kCheckUpdateAutomatically)
|
UserDefaults.standard.setDefault(
|
||||||
}
|
mgrPrefs.specifyShiftTabKeyBehavior, forKey: UserDef.kSpecifyShiftTabKeyBehavior)
|
||||||
|
UserDefaults.standard.setDefault(
|
||||||
// 預設顯示選字窗翻頁按鈕
|
mgrPrefs.specifyShiftSpaceKeyBehavior, forKey: UserDef.kSpecifyShiftSpaceKeyBehavior)
|
||||||
if UserDefaults.standard.object(forKey: kShowPageButtonsInCandidateWindow) == nil {
|
UserDefaults.standard.setDefault(mgrPrefs.useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode)
|
||||||
UserDefaults.standard.set(
|
UserDefaults.standard.setDefault(mgrPrefs.associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled)
|
||||||
mgrPrefs.showPageButtonsInCandidateWindow, forKey: kShowPageButtonsInCandidateWindow
|
UserDefaults.standard.setDefault(
|
||||||
)
|
mgrPrefs.selectPhraseAfterCursorAsCandidate, forKey: UserDef.kSelectPhraseAfterCursorAsCandidate)
|
||||||
}
|
UserDefaults.standard.setDefault(
|
||||||
|
mgrPrefs.moveCursorAfterSelectingCandidate, forKey: UserDef.kMoveCursorAfterSelectingCandidate)
|
||||||
// 預設啟用繪文字與符號輸入
|
UserDefaults.standard.setDefault(
|
||||||
if UserDefaults.standard.object(forKey: kSymbolInputEnabled) == nil {
|
mgrPrefs.useHorizontalCandidateList, forKey: UserDef.kUseHorizontalCandidateList)
|
||||||
UserDefaults.standard.set(mgrPrefs.symbolInputEnabled, forKey: kSymbolInputEnabled)
|
UserDefaults.standard.setDefault(mgrPrefs.cns11643Enabled, forKey: UserDef.kCNS11643Enabled)
|
||||||
}
|
UserDefaults.standard.setDefault(mgrPrefs.chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled)
|
||||||
|
UserDefaults.standard.setDefault(
|
||||||
// 預設選字窗字詞文字尺寸,設成 18 剛剛好
|
mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled)
|
||||||
if UserDefaults.standard.object(forKey: kCandidateListTextSize) == nil {
|
UserDefaults.standard.setDefault(mgrPrefs.phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled)
|
||||||
UserDefaults.standard.set(
|
UserDefaults.standard.setDefault(mgrPrefs.shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep)
|
||||||
mgrPrefs.candidateListTextSize, forKey: kCandidateListTextSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 預設摁空格鍵來選字,所以設成 true
|
|
||||||
if UserDefaults.standard.object(forKey: kChooseCandidateUsingSpace) == nil {
|
|
||||||
UserDefaults.standard.set(
|
|
||||||
mgrPrefs.chooseCandidateUsingSpace, forKey: kChooseCandidateUsingSpace)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自動檢測使用者自訂語彙數據的變動並載入。
|
|
||||||
if UserDefaults.standard.object(forKey: kShouldAutoReloadUserDataFiles) == nil {
|
|
||||||
UserDefaults.standard.set(
|
|
||||||
mgrPrefs.shouldAutoReloadUserDataFiles, forKey: kShouldAutoReloadUserDataFiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 預設情況下讓 Tab 鍵在選字窗內切換候選字、而不是用來換頁。
|
|
||||||
if UserDefaults.standard.object(forKey: kSpecifyTabKeyBehavior) == nil {
|
|
||||||
UserDefaults.standard.set(
|
|
||||||
mgrPrefs.specifyTabKeyBehavior, forKey: kSpecifyTabKeyBehavior)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 預設情況下讓 Space 鍵在選字窗內切換候選字、而不是用來換頁。
|
|
||||||
if UserDefaults.standard.object(forKey: kSpecifySpaceKeyBehavior) == nil {
|
|
||||||
UserDefaults.standard.set(
|
|
||||||
mgrPrefs.specifySpaceKeyBehavior, forKey: kSpecifySpaceKeyBehavior)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 預設禁用逐字選字模式(就是每個字都要選的那種),所以設成 false
|
|
||||||
if UserDefaults.standard.object(forKey: kUseSCPCTypingMode) == nil {
|
|
||||||
UserDefaults.standard.set(mgrPrefs.useSCPCTypingMode, forKey: kUseSCPCTypingMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 預設禁用逐字選字模式時的聯想詞功能,所以設成 false
|
|
||||||
if UserDefaults.standard.object(forKey: kAssociatedPhrasesEnabled) == nil {
|
|
||||||
UserDefaults.standard.set(
|
|
||||||
mgrPrefs.associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 預設漢音風格選字,所以要設成 0
|
|
||||||
if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidatePreference)
|
|
||||||
== nil
|
|
||||||
{
|
|
||||||
UserDefaults.standard.set(
|
|
||||||
mgrPrefs.selectPhraseAfterCursorAsCandidate,
|
|
||||||
forKey: kSelectPhraseAfterCursorAsCandidatePreference)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 預設在選字後自動移動游標
|
|
||||||
if UserDefaults.standard.object(forKey: kMoveCursorAfterSelectingCandidate) == nil {
|
|
||||||
UserDefaults.standard.set(
|
|
||||||
mgrPrefs.moveCursorAfterSelectingCandidate,
|
|
||||||
forKey: kMoveCursorAfterSelectingCandidate)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 預設橫向選字窗,不爽請自行改成縱向選字窗
|
|
||||||
if UserDefaults.standard.object(forKey: kUseHorizontalCandidateListPreference) == nil {
|
|
||||||
UserDefaults.standard.set(
|
|
||||||
mgrPrefs.useHorizontalCandidateList, forKey: kUseHorizontalCandidateListPreference)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 預設停用全字庫支援
|
|
||||||
if UserDefaults.standard.object(forKey: kCNS11643Enabled) == nil {
|
|
||||||
UserDefaults.standard.set(mgrPrefs.cns11643Enabled, forKey: kCNS11643Enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 預設停用繁體轉康熙模組
|
|
||||||
if UserDefaults.standard.object(forKey: kChineseConversionEnabled) == nil {
|
|
||||||
UserDefaults.standard.set(
|
|
||||||
mgrPrefs.chineseConversionEnabled, forKey: kChineseConversionEnabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 預設停用繁體轉 JIS 當用新字體模組
|
|
||||||
if UserDefaults.standard.object(forKey: kShiftJISShinjitaiOutputEnabled) == nil {
|
|
||||||
UserDefaults.standard.set(
|
|
||||||
mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 預設停用自訂語彙置換
|
|
||||||
if UserDefaults.standard.object(forKey: kPhraseReplacementEnabled) == nil {
|
|
||||||
UserDefaults.standard.set(
|
|
||||||
mgrPrefs.phraseReplacementEnabled, forKey: kPhraseReplacementEnabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 預設沒事不要在那裡放屁
|
|
||||||
if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil {
|
|
||||||
UserDefaults.standard.set(
|
|
||||||
mgrPrefs.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep)
|
|
||||||
}
|
|
||||||
|
|
||||||
UserDefaults.standard.synchronize()
|
UserDefaults.standard.synchronize()
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(key: kIsDebugModeEnabled, defaultValue: false)
|
@UserDefault(key: UserDef.kIsDebugModeEnabled, defaultValue: false)
|
||||||
@objc static var isDebugModeEnabled: Bool
|
@objc static var isDebugModeEnabled: Bool
|
||||||
|
|
||||||
@UserDefault(key: kUserDataFolderSpecified, defaultValue: "")
|
@UserDefault(key: UserDef.kCheckUpdateAutomatically, defaultValue: false)
|
||||||
|
@objc static var checkUpdateAutomatically: Bool
|
||||||
|
|
||||||
|
@UserDefault(key: UserDef.kUserDataFolderSpecified, defaultValue: "")
|
||||||
@objc static var userDataFolderSpecified: String
|
@objc static var userDataFolderSpecified: String
|
||||||
|
|
||||||
@objc static func ifSpecifiedUserDataPathExistsInPlist() -> Bool {
|
@objc static func ifSpecifiedUserDataPathExistsInPlist() -> Bool {
|
||||||
UserDefaults.standard.object(forKey: kUserDataFolderSpecified) != nil
|
UserDefaults.standard.object(forKey: UserDef.kUserDataFolderSpecified) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(key: kAppleLanguagesPreferences, defaultValue: [])
|
@objc static func resetSpecifiedUserDataFolder() {
|
||||||
|
UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified")
|
||||||
|
IME.initLangModels(userOnly: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@UserDefault(key: UserDef.kAppleLanguages, defaultValue: [])
|
||||||
@objc static var appleLanguages: [String]
|
@objc static var appleLanguages: [String]
|
||||||
|
|
||||||
@UserDefault(key: kKeyboardLayoutPreference, defaultValue: 0)
|
@UserDefault(key: UserDef.kMandarinParser, defaultValue: 0)
|
||||||
@objc static var keyboardLayout: Int
|
@objc static var mandarinParser: Int
|
||||||
|
|
||||||
@objc static var keyboardLayoutName: String {
|
@objc static var mandarinParserName: String {
|
||||||
(KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.ofStandard).name
|
(MandarinParser(rawValue: self.mandarinParser) ?? MandarinParser.ofStandard).name
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(
|
@UserDefault(
|
||||||
key: kBasisKeyboardLayoutPreference, defaultValue: "com.apple.keylayout.ZhuyinBopomofo")
|
key: UserDef.kBasicKeyboardLayout, defaultValue: "com.apple.keylayout.ZhuyinBopomofo")
|
||||||
@objc static var basisKeyboardLayout: String
|
@objc static var basicKeyboardLayout: String
|
||||||
|
|
||||||
@UserDefault(key: kShowPageButtonsInCandidateWindow, defaultValue: true)
|
@UserDefault(key: UserDef.kShowPageButtonsInCandidateWindow, defaultValue: true)
|
||||||
@objc static var showPageButtonsInCandidateWindow: Bool
|
@objc static var showPageButtonsInCandidateWindow: Bool
|
||||||
|
|
||||||
@CandidateListTextSize(key: kCandidateListTextSize)
|
@CandidateListTextSize(key: UserDef.kCandidateListTextSize)
|
||||||
@objc static var candidateListTextSize: CGFloat
|
@objc static var candidateListTextSize: CGFloat
|
||||||
|
|
||||||
@UserDefault(key: kShouldAutoReloadUserDataFiles, defaultValue: true)
|
@UserDefault(key: UserDef.kShouldAutoReloadUserDataFiles, defaultValue: true)
|
||||||
@objc static var shouldAutoReloadUserDataFiles: Bool
|
@objc static var shouldAutoReloadUserDataFiles: Bool
|
||||||
|
|
||||||
@UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreference, defaultValue: false)
|
@UserDefault(key: UserDef.kSelectPhraseAfterCursorAsCandidate, defaultValue: false)
|
||||||
@objc static var selectPhraseAfterCursorAsCandidate: Bool
|
@objc static var selectPhraseAfterCursorAsCandidate: Bool
|
||||||
|
|
||||||
@UserDefault(key: kMoveCursorAfterSelectingCandidate, defaultValue: false)
|
@UserDefault(key: UserDef.kMoveCursorAfterSelectingCandidate, defaultValue: true)
|
||||||
@objc static var moveCursorAfterSelectingCandidate: Bool
|
@objc static var moveCursorAfterSelectingCandidate: Bool
|
||||||
|
|
||||||
@UserDefault(key: kUseHorizontalCandidateListPreference, defaultValue: true)
|
@UserDefault(key: UserDef.kUseHorizontalCandidateList, defaultValue: true)
|
||||||
@objc static var useHorizontalCandidateList: Bool
|
@objc static var useHorizontalCandidateList: Bool
|
||||||
|
|
||||||
@ComposingBufferSize(key: kComposingBufferSizePreference)
|
@ComposingBufferSize(key: UserDef.kComposingBufferSize)
|
||||||
@objc static var composingBufferSize: Int
|
@objc static var composingBufferSize: Int
|
||||||
|
|
||||||
@UserDefault(key: kChooseCandidateUsingSpace, defaultValue: true)
|
@UserDefault(key: UserDef.kChooseCandidateUsingSpace, defaultValue: true)
|
||||||
@objc static var chooseCandidateUsingSpace: Bool
|
@objc static var chooseCandidateUsingSpace: Bool
|
||||||
|
|
||||||
@UserDefault(key: kUseSCPCTypingMode, defaultValue: false)
|
@UserDefault(key: UserDef.kUseSCPCTypingMode, defaultValue: false)
|
||||||
@objc static var useSCPCTypingMode: Bool
|
@objc static var useSCPCTypingMode: Bool
|
||||||
|
|
||||||
@objc static func toggleSCPCTypingModeEnabled() -> Bool {
|
@objc static func toggleSCPCTypingModeEnabled() -> Bool {
|
||||||
useSCPCTypingMode = !useSCPCTypingMode
|
useSCPCTypingMode = !useSCPCTypingMode
|
||||||
UserDefaults.standard.set(useSCPCTypingMode, forKey: kUseSCPCTypingMode)
|
UserDefaults.standard.set(useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode)
|
||||||
return useSCPCTypingMode
|
return useSCPCTypingMode
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(key: kMaxCandidateLength, defaultValue: kDefaultComposingBufferSize * 2)
|
@UserDefault(key: UserDef.kMaxCandidateLength, defaultValue: kDefaultComposingBufferSize * 2)
|
||||||
@objc static var maxCandidateLength: Int
|
@objc static var maxCandidateLength: Int
|
||||||
|
|
||||||
@UserDefault(key: kShouldNotFartInLieuOfBeep, defaultValue: true)
|
@UserDefault(key: UserDef.kShouldNotFartInLieuOfBeep, defaultValue: true)
|
||||||
@objc static var shouldNotFartInLieuOfBeep: Bool
|
@objc static var shouldNotFartInLieuOfBeep: Bool
|
||||||
|
|
||||||
@objc static func toggleShouldNotFartInLieuOfBeep() -> Bool {
|
@objc static func toggleShouldNotFartInLieuOfBeep() -> Bool {
|
||||||
shouldNotFartInLieuOfBeep = !shouldNotFartInLieuOfBeep
|
shouldNotFartInLieuOfBeep = !shouldNotFartInLieuOfBeep
|
||||||
UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep)
|
UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep)
|
||||||
return shouldNotFartInLieuOfBeep
|
return shouldNotFartInLieuOfBeep
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(key: kCNS11643Enabled, defaultValue: false)
|
@UserDefault(key: UserDef.kCNS11643Enabled, defaultValue: false)
|
||||||
@objc static var cns11643Enabled: Bool
|
@objc static var cns11643Enabled: Bool
|
||||||
|
|
||||||
@objc static func toggleCNS11643Enabled() -> Bool {
|
@objc static func toggleCNS11643Enabled() -> Bool {
|
||||||
cns11643Enabled = !cns11643Enabled
|
cns11643Enabled = !cns11643Enabled
|
||||||
mgrLangModel.setCNSEnabled(cns11643Enabled) // 很重要
|
mgrLangModel.setCNSEnabled(cns11643Enabled) // 很重要
|
||||||
UserDefaults.standard.set(cns11643Enabled, forKey: kCNS11643Enabled)
|
UserDefaults.standard.set(cns11643Enabled, forKey: UserDef.kCNS11643Enabled)
|
||||||
return cns11643Enabled
|
return cns11643Enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(key: kSymbolInputEnabled, defaultValue: true)
|
@UserDefault(key: UserDef.kSymbolInputEnabled, defaultValue: true)
|
||||||
@objc static var symbolInputEnabled: Bool
|
@objc static var symbolInputEnabled: Bool
|
||||||
|
|
||||||
@objc static func toggleSymbolInputEnabled() -> Bool {
|
@objc static func toggleSymbolInputEnabled() -> Bool {
|
||||||
symbolInputEnabled = !symbolInputEnabled
|
symbolInputEnabled = !symbolInputEnabled
|
||||||
mgrLangModel.setSymbolEnabled(symbolInputEnabled) // 很重要
|
mgrLangModel.setSymbolEnabled(symbolInputEnabled) // 很重要
|
||||||
UserDefaults.standard.set(symbolInputEnabled, forKey: kSymbolInputEnabled)
|
UserDefaults.standard.set(symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled)
|
||||||
return symbolInputEnabled
|
return symbolInputEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(key: kChineseConversionEnabled, defaultValue: false)
|
@UserDefault(key: UserDef.kChineseConversionEnabled, defaultValue: false)
|
||||||
@objc static var chineseConversionEnabled: Bool
|
@objc static var chineseConversionEnabled: Bool
|
||||||
|
|
||||||
@objc @discardableResult static func toggleChineseConversionEnabled() -> Bool {
|
@objc @discardableResult static func toggleChineseConversionEnabled() -> Bool {
|
||||||
|
@ -441,13 +374,13 @@ struct ComposingBufferSize {
|
||||||
if chineseConversionEnabled && shiftJISShinjitaiOutputEnabled {
|
if chineseConversionEnabled && shiftJISShinjitaiOutputEnabled {
|
||||||
self.toggleShiftJISShinjitaiOutputEnabled()
|
self.toggleShiftJISShinjitaiOutputEnabled()
|
||||||
UserDefaults.standard.set(
|
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
|
return chineseConversionEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(key: kShiftJISShinjitaiOutputEnabled, defaultValue: false)
|
@UserDefault(key: UserDef.kShiftJISShinjitaiOutputEnabled, defaultValue: false)
|
||||||
@objc static var shiftJISShinjitaiOutputEnabled: Bool
|
@objc static var shiftJISShinjitaiOutputEnabled: Bool
|
||||||
|
|
||||||
@objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool {
|
@objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool {
|
||||||
|
@ -457,11 +390,11 @@ struct ComposingBufferSize {
|
||||||
self.toggleChineseConversionEnabled()
|
self.toggleChineseConversionEnabled()
|
||||||
}
|
}
|
||||||
UserDefaults.standard.set(
|
UserDefaults.standard.set(
|
||||||
shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
|
shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled)
|
||||||
return shiftJISShinjitaiOutputEnabled
|
return shiftJISShinjitaiOutputEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(key: kHalfWidthPunctuationEnabled, defaultValue: false)
|
@UserDefault(key: UserDef.kHalfWidthPunctuationEnabled, defaultValue: false)
|
||||||
@objc static var halfWidthPunctuationEnabled: Bool
|
@objc static var halfWidthPunctuationEnabled: Bool
|
||||||
|
|
||||||
@objc static func toggleHalfWidthPunctuationEnabled() -> Bool {
|
@objc static func toggleHalfWidthPunctuationEnabled() -> Bool {
|
||||||
|
@ -469,23 +402,23 @@ struct ComposingBufferSize {
|
||||||
return halfWidthPunctuationEnabled
|
return halfWidthPunctuationEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(key: kEscToCleanInputBuffer, defaultValue: true)
|
@UserDefault(key: UserDef.kEscToCleanInputBuffer, defaultValue: true)
|
||||||
@objc static var escToCleanInputBuffer: Bool
|
@objc static var escToCleanInputBuffer: Bool
|
||||||
|
|
||||||
@UserDefault(key: kSpecifyTabKeyBehavior, defaultValue: false)
|
@UserDefault(key: UserDef.kSpecifyShiftTabKeyBehavior, defaultValue: false)
|
||||||
@objc static var specifyTabKeyBehavior: Bool
|
@objc static var specifyShiftTabKeyBehavior: Bool
|
||||||
|
|
||||||
@UserDefault(key: kSpecifySpaceKeyBehavior, defaultValue: false)
|
@UserDefault(key: UserDef.kSpecifyShiftSpaceKeyBehavior, defaultValue: false)
|
||||||
@objc static var specifySpaceKeyBehavior: Bool
|
@objc static var specifyShiftSpaceKeyBehavior: Bool
|
||||||
|
|
||||||
// MARK: - Optional settings
|
// MARK: - Optional settings
|
||||||
@UserDefault(key: kCandidateTextFontName, defaultValue: nil)
|
@UserDefault(key: UserDef.kCandidateTextFontName, defaultValue: nil)
|
||||||
@objc static var candidateTextFontName: String?
|
@objc static var candidateTextFontName: String?
|
||||||
|
|
||||||
@UserDefault(key: kCandidateKeyLabelFontName, defaultValue: nil)
|
@UserDefault(key: UserDef.kCandidateKeyLabelFontName, defaultValue: nil)
|
||||||
@objc static var candidateKeyLabelFontName: String?
|
@objc static var candidateKeyLabelFontName: String?
|
||||||
|
|
||||||
@UserDefault(key: kCandidateKeys, defaultValue: kDefaultKeys)
|
@UserDefault(key: UserDef.kCandidateKeys, defaultValue: kDefaultKeys)
|
||||||
@objc static var candidateKeys: String
|
@objc static var candidateKeys: String
|
||||||
|
|
||||||
@objc static var defaultCandidateKeys: String {
|
@objc static var defaultCandidateKeys: String {
|
||||||
|
@ -548,22 +481,22 @@ struct ComposingBufferSize {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(key: kPhraseReplacementEnabled, defaultValue: false)
|
@UserDefault(key: UserDef.kPhraseReplacementEnabled, defaultValue: false)
|
||||||
@objc static var phraseReplacementEnabled: Bool
|
@objc static var phraseReplacementEnabled: Bool
|
||||||
|
|
||||||
@objc static func togglePhraseReplacementEnabled() -> Bool {
|
@objc static func togglePhraseReplacementEnabled() -> Bool {
|
||||||
phraseReplacementEnabled = !phraseReplacementEnabled
|
phraseReplacementEnabled = !phraseReplacementEnabled
|
||||||
mgrLangModel.setPhraseReplacementEnabled(phraseReplacementEnabled)
|
mgrLangModel.setPhraseReplacementEnabled(phraseReplacementEnabled)
|
||||||
UserDefaults.standard.set(phraseReplacementEnabled, forKey: kPhraseReplacementEnabled)
|
UserDefaults.standard.set(phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled)
|
||||||
return phraseReplacementEnabled
|
return phraseReplacementEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(key: kAssociatedPhrasesEnabled, defaultValue: false)
|
@UserDefault(key: UserDef.kAssociatedPhrasesEnabled, defaultValue: false)
|
||||||
@objc static var associatedPhrasesEnabled: Bool
|
@objc static var associatedPhrasesEnabled: Bool
|
||||||
|
|
||||||
@objc static func toggleAssociatedPhrasesEnabled() -> Bool {
|
@objc static func toggleAssociatedPhrasesEnabled() -> Bool {
|
||||||
associatedPhrasesEnabled = !associatedPhrasesEnabled
|
associatedPhrasesEnabled = !associatedPhrasesEnabled
|
||||||
UserDefaults.standard.set(associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled)
|
UserDefaults.standard.set(associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled)
|
||||||
return associatedPhrasesEnabled
|
return associatedPhrasesEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,29 +35,15 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
+ (void)loadUserPhrases;
|
+ (void)loadUserPhrases;
|
||||||
+ (void)loadUserAssociatedPhrases;
|
+ (void)loadUserAssociatedPhrases;
|
||||||
+ (void)loadUserPhraseReplacement;
|
+ (void)loadUserPhraseReplacement;
|
||||||
+ (BOOL)checkIfUserLanguageModelFilesExist;
|
|
||||||
+ (BOOL)checkIfUserDataFolderExists;
|
|
||||||
+ (BOOL)checkIfSpecifiedUserDataFolderValid:(NSString *)folderPath;
|
|
||||||
+ (NSString *)dataFolderPath:(bool)isDefaultFolder NS_SWIFT_NAME(dataFolderPath(isDefaultFolder:));
|
|
||||||
|
|
||||||
+ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase
|
+ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase
|
||||||
inputMode:(InputMode)mode
|
inputMode:(InputMode)mode
|
||||||
key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:));
|
key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:));
|
||||||
+ (BOOL)writeUserPhrase:(NSString *)userPhrase
|
+ (void)consolidateGivenFile:(NSString *)path shouldCheckPragma:(BOOL)shouldCheckPragma;
|
||||||
inputMode:(InputMode)mode
|
|
||||||
areWeDuplicating:(BOOL)areWeDuplicating
|
|
||||||
areWeDeleting:(BOOL)areWeDeleting;
|
|
||||||
+ (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled;
|
+ (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled;
|
||||||
+ (void)setCNSEnabled:(BOOL)cnsEnabled;
|
+ (void)setCNSEnabled:(BOOL)cnsEnabled;
|
||||||
+ (void)setSymbolEnabled:(BOOL)symbolEnabled;
|
+ (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
|
@end
|
||||||
|
|
||||||
/// The following methods are merely for testing.
|
/// The following methods are merely for testing.
|
||||||
|
|
|
@ -37,28 +37,16 @@ static vChewing::LMInstantiator gLangModelCHS;
|
||||||
static vChewing::UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife);
|
static vChewing::UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife);
|
||||||
static vChewing::UserOverrideModel gUserOverrideModelCHS(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
|
@implementation mgrLangModel
|
||||||
|
|
||||||
|
// 這個函數無法遷移至 Swift
|
||||||
static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing::LMInstantiator &lm)
|
static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing::LMInstantiator &lm)
|
||||||
{
|
{
|
||||||
Class cls = NSClassFromString(@"ctlInputMethod");
|
NSString *dataPath = [mgrLangModel getBundleDataPath:filenameWithoutExtension];
|
||||||
NSString *dataPath = [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"];
|
|
||||||
lm.loadLanguageModel([dataPath UTF8String]);
|
lm.loadLanguageModel([dataPath UTF8String]);
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSString *)specifyBundleDataPath:(NSString *)filenameWithoutExtension;
|
// 這個函數無法遷移至 Swift
|
||||||
{
|
|
||||||
Class cls = NSClassFromString(@"ctlInputMethod");
|
|
||||||
return [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"];
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (void)loadDataModels
|
+ (void)loadDataModels
|
||||||
{
|
{
|
||||||
if (!gLangModelCHT.isDataModelLoaded())
|
if (!gLangModelCHT.isDataModelLoaded())
|
||||||
|
@ -67,15 +55,15 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
|
||||||
}
|
}
|
||||||
if (!gLangModelCHT.isMiscDataLoaded())
|
if (!gLangModelCHT.isMiscDataLoaded())
|
||||||
{
|
{
|
||||||
gLangModelCHT.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
gLangModelCHT.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
||||||
}
|
}
|
||||||
if (!gLangModelCHT.isSymbolDataLoaded())
|
if (!gLangModelCHT.isSymbolDataLoaded())
|
||||||
{
|
{
|
||||||
gLangModelCHT.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]);
|
gLangModelCHT.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]);
|
||||||
}
|
}
|
||||||
if (!gLangModelCHT.isCNSDataLoaded())
|
if (!gLangModelCHT.isCNSDataLoaded())
|
||||||
{
|
{
|
||||||
gLangModelCHT.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]);
|
gLangModelCHT.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]);
|
||||||
}
|
}
|
||||||
// -----------------
|
// -----------------
|
||||||
if (!gLangModelCHS.isDataModelLoaded())
|
if (!gLangModelCHS.isDataModelLoaded())
|
||||||
|
@ -84,18 +72,19 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
|
||||||
}
|
}
|
||||||
if (!gLangModelCHS.isMiscDataLoaded())
|
if (!gLangModelCHS.isMiscDataLoaded())
|
||||||
{
|
{
|
||||||
gLangModelCHS.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
gLangModelCHS.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
||||||
}
|
}
|
||||||
if (!gLangModelCHS.isSymbolDataLoaded())
|
if (!gLangModelCHS.isSymbolDataLoaded())
|
||||||
{
|
{
|
||||||
gLangModelCHS.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]);
|
gLangModelCHS.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]);
|
||||||
}
|
}
|
||||||
if (!gLangModelCHS.isCNSDataLoaded())
|
if (!gLangModelCHS.isCNSDataLoaded())
|
||||||
{
|
{
|
||||||
gLangModelCHS.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]);
|
gLangModelCHS.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 這個函數無法遷移至 Swift
|
||||||
+ (void)loadDataModel:(InputMode)mode
|
+ (void)loadDataModel:(InputMode)mode
|
||||||
{
|
{
|
||||||
if ([mode isEqualToString:imeModeCHT])
|
if ([mode isEqualToString:imeModeCHT])
|
||||||
|
@ -106,15 +95,15 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
|
||||||
}
|
}
|
||||||
if (!gLangModelCHT.isMiscDataLoaded())
|
if (!gLangModelCHT.isMiscDataLoaded())
|
||||||
{
|
{
|
||||||
gLangModelCHT.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
gLangModelCHT.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
||||||
}
|
}
|
||||||
if (!gLangModelCHT.isSymbolDataLoaded())
|
if (!gLangModelCHT.isSymbolDataLoaded())
|
||||||
{
|
{
|
||||||
gLangModelCHT.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]);
|
gLangModelCHT.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]);
|
||||||
}
|
}
|
||||||
if (!gLangModelCHT.isCNSDataLoaded())
|
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())
|
if (!gLangModelCHS.isMiscDataLoaded())
|
||||||
{
|
{
|
||||||
gLangModelCHS.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
gLangModelCHS.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
||||||
}
|
}
|
||||||
if (!gLangModelCHS.isSymbolDataLoaded())
|
if (!gLangModelCHS.isSymbolDataLoaded())
|
||||||
{
|
{
|
||||||
gLangModelCHS.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]);
|
gLangModelCHS.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]);
|
||||||
}
|
}
|
||||||
if (!gLangModelCHS.isCNSDataLoaded())
|
if (!gLangModelCHS.isCNSDataLoaded())
|
||||||
{
|
{
|
||||||
gLangModelCHS.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]);
|
gLangModelCHS.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 這個函數無法遷移至 Swift
|
||||||
+ (void)loadUserPhrases
|
+ (void)loadUserPhrases
|
||||||
{
|
{
|
||||||
gLangModelCHT.loadUserPhrases([[self userPhrasesDataPath:imeModeCHT] UTF8String],
|
gLangModelCHT.loadUserPhrases([[self userPhrasesDataPath:imeModeCHT] UTF8String],
|
||||||
|
@ -149,136 +139,21 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
|
||||||
gLangModelCHS.loadUserSymbolData([[self userSymbolDataPath:imeModeCHS] UTF8String]);
|
gLangModelCHS.loadUserSymbolData([[self userSymbolDataPath:imeModeCHS] UTF8String]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 這個函數無法遷移至 Swift
|
||||||
+ (void)loadUserAssociatedPhrases
|
+ (void)loadUserAssociatedPhrases
|
||||||
{
|
{
|
||||||
gLangModelCHT.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHT] UTF8String]);
|
gLangModelCHT.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHT] UTF8String]);
|
||||||
gLangModelCHS.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHS] UTF8String]);
|
gLangModelCHS.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHS] UTF8String]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 這個函數無法遷移至 Swift
|
||||||
+ (void)loadUserPhraseReplacement
|
+ (void)loadUserPhraseReplacement
|
||||||
{
|
{
|
||||||
gLangModelCHT.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHT] UTF8String]);
|
gLangModelCHT.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHT] UTF8String]);
|
||||||
gLangModelCHS.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHS] UTF8String]);
|
gLangModelCHS.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHS] UTF8String]);
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (BOOL)checkIfUserDataFolderExists
|
// 這個函數無法遷移至 Swift
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase
|
+ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase
|
||||||
inputMode:(InputMode)mode
|
inputMode:(InputMode)mode
|
||||||
key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:))
|
key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:))
|
||||||
|
@ -297,144 +172,51 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (BOOL)writeUserPhrase:(NSString *)userPhrase
|
// 這個函數無法遷移至 Swift
|
||||||
inputMode:(InputMode)mode
|
+ (void)consolidateGivenFile:(NSString *)path shouldCheckPragma:(BOOL)shouldCheckPragma
|
||||||
areWeDuplicating:(BOOL)areWeDuplicating
|
|
||||||
areWeDeleting:(BOOL)areWeDeleting
|
|
||||||
{
|
{
|
||||||
if (![self checkIfUserLanguageModelFilesExist])
|
vChewing::LMConsolidator::ConsolidateContent([path UTF8String], shouldCheckPragma);
|
||||||
{
|
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 這個函數無法遷移至 Swift
|
||||||
+ (vChewing::LMInstantiator *)lmCHT
|
+ (vChewing::LMInstantiator *)lmCHT
|
||||||
{
|
{
|
||||||
return &gLangModelCHT;
|
return &gLangModelCHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 這個函數無法遷移至 Swift
|
||||||
+ (vChewing::LMInstantiator *)lmCHS
|
+ (vChewing::LMInstantiator *)lmCHS
|
||||||
{
|
{
|
||||||
return &gLangModelCHS;
|
return &gLangModelCHS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 這個函數無法遷移至 Swift
|
||||||
+ (vChewing::UserOverrideModel *)userOverrideModelCHT
|
+ (vChewing::UserOverrideModel *)userOverrideModelCHT
|
||||||
{
|
{
|
||||||
return &gUserOverrideModelCHT;
|
return &gUserOverrideModelCHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 這個函數無法遷移至 Swift
|
||||||
+ (vChewing::UserOverrideModel *)userOverrideModelCHS
|
+ (vChewing::UserOverrideModel *)userOverrideModelCHS
|
||||||
{
|
{
|
||||||
return &gUserOverrideModelCHS;
|
return &gUserOverrideModelCHS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 這個函數無法遷移至 Swift
|
||||||
+ (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled
|
+ (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled
|
||||||
{
|
{
|
||||||
gLangModelCHT.setPhraseReplacementEnabled(phraseReplacementEnabled);
|
gLangModelCHT.setPhraseReplacementEnabled(phraseReplacementEnabled);
|
||||||
gLangModelCHS.setPhraseReplacementEnabled(phraseReplacementEnabled);
|
gLangModelCHS.setPhraseReplacementEnabled(phraseReplacementEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 這個函數無法遷移至 Swift
|
||||||
+ (void)setCNSEnabled:(BOOL)cnsEnabled
|
+ (void)setCNSEnabled:(BOOL)cnsEnabled
|
||||||
{
|
{
|
||||||
gLangModelCHT.setCNSEnabled(cnsEnabled);
|
gLangModelCHT.setCNSEnabled(cnsEnabled);
|
||||||
gLangModelCHS.setCNSEnabled(cnsEnabled);
|
gLangModelCHS.setCNSEnabled(cnsEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 這個函數無法遷移至 Swift
|
||||||
+ (void)setSymbolEnabled:(BOOL)symbolEnabled
|
+ (void)setSymbolEnabled:(BOOL)symbolEnabled
|
||||||
{
|
{
|
||||||
gLangModelCHT.setSymbolEnabled(symbolEnabled);
|
gLangModelCHT.setSymbolEnabled(symbolEnabled);
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
// Copyright (c) 2022 and onwards Isaac Xen (MIT License).
|
// 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
|
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
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|
|
@ -73,3 +73,71 @@
|
||||||
"catDoubleTableLines" = "DoubleTableLines";
|
"catDoubleTableLines" = "DoubleTableLines";
|
||||||
"catFillingBlocks" = "FillingBlocks";
|
"catFillingBlocks" = "FillingBlocks";
|
||||||
"catLineSegments" = "LineSegments";
|
"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";
|
||||||
|
|
|
@ -73,3 +73,71 @@
|
||||||
"catDoubleTableLines" = "DoubleTableLines";
|
"catDoubleTableLines" = "DoubleTableLines";
|
||||||
"catFillingBlocks" = "FillingBlocks";
|
"catFillingBlocks" = "FillingBlocks";
|
||||||
"catLineSegments" = "LineSegments";
|
"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";
|
||||||
|
|
|
@ -73,3 +73,71 @@
|
||||||
"catDoubleTableLines" = "双線";
|
"catDoubleTableLines" = "双線";
|
||||||
"catFillingBlocks" = "ブロック";
|
"catFillingBlocks" = "ブロック";
|
||||||
"catLineSegments" = "線分";
|
"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" = "縦型陳列";
|
||||||
|
|
|
@ -73,3 +73,71 @@
|
||||||
"catDoubleTableLines" = "双线";
|
"catDoubleTableLines" = "双线";
|
||||||
"catFillingBlocks" = "填色";
|
"catFillingBlocks" = "填色";
|
||||||
"catLineSegments" = "线段";
|
"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" = "纵向布局";
|
||||||
|
|
|
@ -73,3 +73,71 @@
|
||||||
"catDoubleTableLines" = "雙線";
|
"catDoubleTableLines" = "雙線";
|
||||||
"catFillingBlocks" = "填色";
|
"catFillingBlocks" = "填色";
|
||||||
"catLineSegments" = "線段";
|
"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" = "縱向佈局";
|
||||||
|
|
|
@ -435,10 +435,9 @@ extension ctlCandidateHorizontal {
|
||||||
|
|
||||||
frameRect = window?.frame ?? NSRect.zero
|
frameRect = window?.frame ?? NSRect.zero
|
||||||
|
|
||||||
let topLeftPoint = NSMakePoint(
|
let topLeftPoint = NSPoint(x: frameRect.origin.x, y: frameRect.origin.y + frameRect.size.height)
|
||||||
frameRect.origin.x, frameRect.origin.y + frameRect.size.height)
|
|
||||||
frameRect.size = newSize
|
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)
|
self.window?.setFrame(frameRect, display: false)
|
||||||
candidateView.setNeedsDisplay(candidateView.bounds)
|
candidateView.setNeedsDisplay(candidateView.bounds)
|
||||||
}
|
}
|
||||||
|
|
|
@ -440,10 +440,9 @@ extension ctlCandidateVertical {
|
||||||
|
|
||||||
frameRect = window?.frame ?? NSRect.zero
|
frameRect = window?.frame ?? NSRect.zero
|
||||||
|
|
||||||
let topLeftPoint = NSMakePoint(
|
let topLeftPoint = NSPoint(x: frameRect.origin.x, y: frameRect.origin.y + frameRect.size.height)
|
||||||
frameRect.origin.x, frameRect.origin.y + frameRect.size.height)
|
|
||||||
frameRect.size = newSize
|
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)
|
self.window?.setFrame(frameRect, display: false)
|
||||||
candidateView.setNeedsDisplay(candidateView.bounds)
|
candidateView.setNeedsDisplay(candidateView.bounds)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Copyright (c) 2021 and onwards Weizhong Yang (MIT License).
|
// 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
|
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
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
@ -123,7 +124,7 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
||||||
panel.titlebarAppearsTransparent = true
|
panel.titlebarAppearsTransparent = true
|
||||||
panel.titleVisibility = .hidden
|
panel.titleVisibility = .hidden
|
||||||
panel.showsToolbarButton = false
|
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.miniaturizeButton)?.isHidden = true
|
||||||
panel.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true
|
panel.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true
|
||||||
panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true
|
panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
// 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 = {
|
||||||
|
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) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
// 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 = {
|
||||||
|
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) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
// 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 = {
|
||||||
|
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) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
// 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 = {
|
||||||
|
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) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
// Copyright (c) 2021 and onwards Weizhong Yang (MIT License).
|
// 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
|
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
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|
|
@ -27,27 +27,19 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Carbon
|
import Carbon
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
// Extend the RangeReplaceableCollection to allow it clean duplicated characters.
|
|
||||||
extension RangeReplaceableCollection where Element: Hashable {
|
|
||||||
var charDeDuplicate: Self {
|
|
||||||
var set = Set<Element>()
|
|
||||||
return filter { set.insert($0).inserted }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Please note that the class should be exposed using the same class name
|
// 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
|
// in Objective-C in order to let IMK to see the same class name as
|
||||||
// the "InputMethodServerPreferencesWindowControllerClass" in Info.plist.
|
// the "InputMethodServerPreferencesWindowControllerClass" in Info.plist.
|
||||||
@objc(ctlPrefWindow) class ctlPrefWindow: NSWindowController {
|
@objc(ctlPrefWindow) class ctlPrefWindow: NSWindowController {
|
||||||
@IBOutlet weak var fontSizePopUpButton: NSPopUpButton!
|
@IBOutlet weak var fontSizePopUpButton: NSPopUpButton!
|
||||||
@IBOutlet weak var uiLanguageButton: NSPopUpButton!
|
@IBOutlet weak var uiLanguageButton: NSPopUpButton!
|
||||||
@IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton!
|
@IBOutlet weak var basicKeyboardLayoutButton: NSPopUpButton!
|
||||||
@IBOutlet weak var selectionKeyComboBox: NSComboBox!
|
@IBOutlet weak var selectionKeyComboBox: NSComboBox!
|
||||||
@IBOutlet weak var chkTrad2KangXi: NSButton!
|
@IBOutlet weak var chkTrad2KangXi: NSButton!
|
||||||
@IBOutlet weak var chkTrad2JISShinjitai: NSButton!
|
@IBOutlet weak var chkTrad2JISShinjitai: NSButton!
|
||||||
@IBOutlet weak var lblCurrentlySpecifiedUserDataFolder: NSTextFieldCell!
|
@IBOutlet weak var lblCurrentlySpecifiedUserDataFolder: NSTextFieldCell!
|
||||||
|
|
||||||
var currentLanguageSelectItem: NSMenuItem? = nil
|
var currentLanguageSelectItem: NSMenuItem?
|
||||||
|
|
||||||
override func windowDidLoad() {
|
override func windowDidLoad() {
|
||||||
super.windowDidLoad()
|
super.windowDidLoad()
|
||||||
|
@ -56,8 +48,8 @@ extension RangeReplaceableCollection where Element: Hashable {
|
||||||
isDefaultFolder: true)
|
isDefaultFolder: true)
|
||||||
|
|
||||||
let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"]
|
let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"]
|
||||||
var autoMUISelectItem: NSMenuItem? = nil
|
var autoMUISelectItem: NSMenuItem?
|
||||||
var chosenLanguageItem: NSMenuItem? = nil
|
var chosenLanguageItem: NSMenuItem?
|
||||||
uiLanguageButton.menu?.removeAllItems()
|
uiLanguageButton.menu?.removeAllItems()
|
||||||
|
|
||||||
let appleLanguages = mgrPrefs.appleLanguages
|
let appleLanguages = mgrPrefs.appleLanguages
|
||||||
|
@ -82,25 +74,25 @@ extension RangeReplaceableCollection where Element: Hashable {
|
||||||
uiLanguageButton.select(currentLanguageSelectItem)
|
uiLanguageButton.select(currentLanguageSelectItem)
|
||||||
|
|
||||||
let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource]
|
let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource]
|
||||||
var usKeyboardLayoutItem: NSMenuItem? = nil
|
var usKeyboardLayoutItem: NSMenuItem?
|
||||||
var chosenBaseKeyboardLayoutItem: NSMenuItem? = nil
|
var chosenBaseKeyboardLayoutItem: NSMenuItem?
|
||||||
|
|
||||||
basisKeyboardLayoutButton.menu?.removeAllItems()
|
basicKeyboardLayoutButton.menu?.removeAllItems()
|
||||||
|
|
||||||
let itmAppleZhuyinBopomofo = NSMenuItem()
|
let itmAppleZhuyinBopomofo = NSMenuItem()
|
||||||
itmAppleZhuyinBopomofo.title = String(
|
itmAppleZhuyinBopomofo.title = String(
|
||||||
format: NSLocalizedString("Apple Zhuyin Bopomofo (Dachen)", comment: ""))
|
format: NSLocalizedString("Apple Zhuyin Bopomofo (Dachen)", comment: ""))
|
||||||
itmAppleZhuyinBopomofo.representedObject = String(
|
itmAppleZhuyinBopomofo.representedObject = String(
|
||||||
"com.apple.keylayout.ZhuyinBopomofo")
|
"com.apple.keylayout.ZhuyinBopomofo")
|
||||||
basisKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinBopomofo)
|
basicKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinBopomofo)
|
||||||
|
|
||||||
let itmAppleZhuyinEten = NSMenuItem()
|
let itmAppleZhuyinEten = NSMenuItem()
|
||||||
itmAppleZhuyinEten.title = String(
|
itmAppleZhuyinEten.title = String(
|
||||||
format: NSLocalizedString("Apple Zhuyin Eten (Traditional)", comment: ""))
|
format: NSLocalizedString("Apple Zhuyin Eten (Traditional)", comment: ""))
|
||||||
itmAppleZhuyinEten.representedObject = String("com.apple.keylayout.ZhuyinEten")
|
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 {
|
for source in list {
|
||||||
if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) {
|
if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) {
|
||||||
|
@ -150,13 +142,15 @@ extension RangeReplaceableCollection where Element: Hashable {
|
||||||
if sourceID == "com.apple.keylayout.US" {
|
if sourceID == "com.apple.keylayout.US" {
|
||||||
usKeyboardLayoutItem = menuItem
|
usKeyboardLayoutItem = menuItem
|
||||||
}
|
}
|
||||||
if basisKeyboardLayoutID == sourceID {
|
if basicKeyboardLayoutID == sourceID {
|
||||||
chosenBaseKeyboardLayoutItem = menuItem
|
chosenBaseKeyboardLayoutItem = menuItem
|
||||||
}
|
}
|
||||||
basisKeyboardLayoutButton.menu?.addItem(menuItem)
|
if IME.arrWhitelistedKeyLayoutsASCII.contains(sourceID) || sourceID.contains("vChewing") {
|
||||||
|
basicKeyboardLayoutButton.menu?.addItem(menuItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch basisKeyboardLayoutID {
|
switch basicKeyboardLayoutID {
|
||||||
case "com.apple.keylayout.ZhuyinBopomofo":
|
case "com.apple.keylayout.ZhuyinBopomofo":
|
||||||
chosenBaseKeyboardLayoutItem = itmAppleZhuyinBopomofo
|
chosenBaseKeyboardLayoutItem = itmAppleZhuyinBopomofo
|
||||||
case "com.apple.keylayout.ZhuyinEten":
|
case "com.apple.keylayout.ZhuyinEten":
|
||||||
|
@ -165,7 +159,7 @@ extension RangeReplaceableCollection where Element: Hashable {
|
||||||
break // nothing to do
|
break // nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
basisKeyboardLayoutButton.select(chosenBaseKeyboardLayoutItem ?? usKeyboardLayoutItem)
|
basicKeyboardLayoutButton.select(chosenBaseKeyboardLayoutItem ?? usKeyboardLayoutItem)
|
||||||
|
|
||||||
selectionKeyComboBox.usesDataSource = false
|
selectionKeyComboBox.usesDataSource = false
|
||||||
selectionKeyComboBox.removeAllItems()
|
selectionKeyComboBox.removeAllItems()
|
||||||
|
@ -201,9 +195,9 @@ extension RangeReplaceableCollection where Element: Hashable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func updateBasisKeyboardLayoutAction(_ sender: Any) {
|
@IBAction func updateBasicKeyboardLayoutAction(_ sender: Any) {
|
||||||
if let sourceID = basisKeyboardLayoutButton.selectedItem?.representedObject as? String {
|
if let sourceID = basicKeyboardLayoutButton.selectedItem?.representedObject as? String {
|
||||||
mgrPrefs.basisKeyboardLayout = sourceID
|
mgrPrefs.basicKeyboardLayout = sourceID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,7 +241,7 @@ extension RangeReplaceableCollection where Element: Hashable {
|
||||||
} catch {
|
} catch {
|
||||||
if let window = window {
|
if let window = window {
|
||||||
let alert = NSAlert(error: error)
|
let alert = NSAlert(error: error)
|
||||||
alert.beginSheetModal(for: window) { response in
|
alert.beginSheetModal(for: window) { _ in
|
||||||
self.selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys
|
self.selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys
|
||||||
}
|
}
|
||||||
clsSFX.beep()
|
clsSFX.beep()
|
||||||
|
@ -256,8 +250,7 @@ extension RangeReplaceableCollection where Element: Hashable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func resetSpecifiedUserDataFolder(_ sender: Any) {
|
@IBAction func resetSpecifiedUserDataFolder(_ sender: Any) {
|
||||||
UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified")
|
mgrPrefs.resetSpecifiedUserDataFolder()
|
||||||
IME.initLangModels(userOnly: true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func chooseUserDataFolderToSpecify(_ sender: Any) {
|
@IBAction func chooseUserDataFolderToSpecify(_ sender: Any) {
|
||||||
|
@ -275,22 +268,24 @@ extension RangeReplaceableCollection where Element: Hashable {
|
||||||
IME.dlgOpenPath.beginSheetModal(for: self.window!) { result in
|
IME.dlgOpenPath.beginSheetModal(for: self.window!) { result in
|
||||||
if result == NSApplication.ModalResponse.OK {
|
if result == NSApplication.ModalResponse.OK {
|
||||||
if IME.dlgOpenPath.url != nil {
|
if IME.dlgOpenPath.url != nil {
|
||||||
if mgrLangModel.checkIfSpecifiedUserDataFolderValid(
|
// CommonDialog 讀入的路徑沒有結尾斜槓,這會導致檔案目錄合規性判定失準。
|
||||||
IME.dlgOpenPath.url!.path)
|
// 所以要手動補回來。
|
||||||
{
|
var newPath = IME.dlgOpenPath.url!.path
|
||||||
mgrPrefs.userDataFolderSpecified = IME.dlgOpenPath.url!.path
|
newPath.ensureTrailingSlash()
|
||||||
|
if mgrLangModel.checkIfSpecifiedUserDataFolderValid(newPath) {
|
||||||
|
mgrPrefs.userDataFolderSpecified = newPath
|
||||||
IME.initLangModels(userOnly: true)
|
IME.initLangModels(userOnly: true)
|
||||||
} else {
|
} else {
|
||||||
clsSFX.beep()
|
clsSFX.beep()
|
||||||
if !bolPreviousFolderValidity {
|
if !bolPreviousFolderValidity {
|
||||||
self.resetSpecifiedUserDataFolder(self)
|
mgrPrefs.resetSpecifiedUserDataFolder()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !bolPreviousFolderValidity {
|
if !bolPreviousFolderValidity {
|
||||||
self.resetSpecifiedUserDataFolder(self)
|
mgrPrefs.resetSpecifiedUserDataFolder()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<objects>
|
<objects>
|
||||||
<customObject id="-2" userLabel="File's Owner" customClass="ctlPrefWindow">
|
<customObject id="-2" userLabel="File's Owner" customClass="ctlPrefWindow">
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="basisKeyboardLayoutButton" destination="124" id="135"/>
|
<outlet property="basicKeyboardLayoutButton" destination="124" id="135"/>
|
||||||
<outlet property="chkTrad2JISShinjitai" destination="h4r-Sp-LBI" id="pk8-xi-IJi"/>
|
<outlet property="chkTrad2JISShinjitai" destination="h4r-Sp-LBI" id="pk8-xi-IJi"/>
|
||||||
<outlet property="chkTrad2KangXi" destination="5IL-zZ-CL9" id="PGS-Dy-BBY"/>
|
<outlet property="chkTrad2KangXi" destination="5IL-zZ-CL9" id="PGS-Dy-BBY"/>
|
||||||
<outlet property="fontSizePopUpButton" destination="90" id="108"/>
|
<outlet property="fontSizePopUpButton" destination="90" id="108"/>
|
||||||
|
@ -163,7 +163,7 @@
|
||||||
</connections>
|
</connections>
|
||||||
</matrix>
|
</matrix>
|
||||||
<button verticalHuggingPriority="750" id="233">
|
<button verticalHuggingPriority="750" id="233">
|
||||||
<rect key="frame" x="169" y="25.5" width="245" height="17"/>
|
<rect key="frame" x="169" y="26.5" width="245" height="17"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<buttonCell key="cell" type="check" title="Show page buttons in candidate list" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="shc-Nu-UsM">
|
<buttonCell key="cell" type="check" title="Show page buttons in candidate list" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="shc-Nu-UsM">
|
||||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||||
|
@ -234,22 +234,8 @@
|
||||||
<rect key="frame" x="3" y="3" width="445" height="134"/>
|
<rect key="frame" x="3" y="3" width="445" height="134"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Yaj-QY-7xV" userLabel="chkCNSSupport">
|
|
||||||
<rect key="frame" x="19" y="104.5" width="406" height="17"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="16" id="KdE-Vd-Y50"/>
|
|
||||||
</constraints>
|
|
||||||
<buttonCell key="cell" type="check" title="Enable CNS11643 Support (2022-01-07)" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="W24-T4-cg0">
|
|
||||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
|
||||||
<font key="font" metaFont="cellTitle"/>
|
|
||||||
</buttonCell>
|
|
||||||
<connections>
|
|
||||||
<action selector="toggleCNSSupport:" target="-2" id="mcY-ma-N0c"/>
|
|
||||||
<binding destination="32" name="value" keyPath="values.CNS11643Enabled" id="Pbx-Gt-upm"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5IL-zZ-CL9" userLabel="chkTrad2KangXi">
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5IL-zZ-CL9" userLabel="chkTrad2KangXi">
|
||||||
<rect key="frame" x="19" y="83.5" width="406" height="16"/>
|
<rect key="frame" x="19" y="99.5" width="406" height="16"/>
|
||||||
<buttonCell key="cell" type="check" title="Auto-convert traditional Chinese glyphs to KangXi characters" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="BSK-bH-Gct">
|
<buttonCell key="cell" type="check" title="Auto-convert traditional Chinese glyphs to KangXi characters" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="BSK-bH-Gct">
|
||||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||||
<font key="font" metaFont="cellTitle"/>
|
<font key="font" metaFont="cellTitle"/>
|
||||||
|
@ -260,7 +246,7 @@
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="h4r-Sp-LBI" userLabel="chkTrad2JISShinjitai">
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="h4r-Sp-LBI" userLabel="chkTrad2JISShinjitai">
|
||||||
<rect key="frame" x="19" y="62.5" width="406" height="16"/>
|
<rect key="frame" x="19" y="78.5" width="406" height="16"/>
|
||||||
<buttonCell key="cell" type="check" title="Auto-convert traditional Chinese glyphs to JIS Shinjitai characters" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="eia-1F-Do0">
|
<buttonCell key="cell" type="check" title="Auto-convert traditional Chinese glyphs to JIS Shinjitai characters" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="eia-1F-Do0">
|
||||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||||
<font key="font" metaFont="cellTitle"/>
|
<font key="font" metaFont="cellTitle"/>
|
||||||
|
@ -271,7 +257,7 @@
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pYB-E5-4Nv">
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pYB-E5-4Nv">
|
||||||
<rect key="frame" x="19" y="41.5" width="406" height="16"/>
|
<rect key="frame" x="19" y="57.5" width="406" height="16"/>
|
||||||
<buttonCell key="cell" type="check" title="Stop farting (when typed phonetic combination is invalid, etc.)" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="62u-jY-BRh">
|
<buttonCell key="cell" type="check" title="Stop farting (when typed phonetic combination is invalid, etc.)" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="62u-jY-BRh">
|
||||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||||
<font key="font" metaFont="cellTitle"/>
|
<font key="font" metaFont="cellTitle"/>
|
||||||
|
@ -283,17 +269,14 @@
|
||||||
</button>
|
</button>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="Yaj-QY-7xV" firstAttribute="leading" secondItem="5IL-zZ-CL9" secondAttribute="leading" id="4Bc-Yl-MiB"/>
|
|
||||||
<constraint firstItem="5IL-zZ-CL9" firstAttribute="top" secondItem="Yaj-QY-7xV" secondAttribute="bottom" constant="6" symbolic="YES" id="7iI-Gm-cGt"/>
|
|
||||||
<constraint firstItem="h4r-Sp-LBI" firstAttribute="top" secondItem="5IL-zZ-CL9" secondAttribute="bottom" constant="6" symbolic="YES" id="8Pq-i7-vK8"/>
|
<constraint firstItem="h4r-Sp-LBI" firstAttribute="top" secondItem="5IL-zZ-CL9" secondAttribute="bottom" constant="6" symbolic="YES" id="8Pq-i7-vK8"/>
|
||||||
<constraint firstItem="Yaj-QY-7xV" firstAttribute="trailing" secondItem="5IL-zZ-CL9" secondAttribute="trailing" id="8QP-dq-cfD"/>
|
|
||||||
<constraint firstItem="pYB-E5-4Nv" firstAttribute="trailing" secondItem="h4r-Sp-LBI" secondAttribute="trailing" id="IWM-hB-AeP"/>
|
<constraint firstItem="pYB-E5-4Nv" firstAttribute="trailing" secondItem="h4r-Sp-LBI" secondAttribute="trailing" id="IWM-hB-AeP"/>
|
||||||
<constraint firstItem="Yaj-QY-7xV" firstAttribute="top" secondItem="brd-6J-saN" secondAttribute="top" constant="13.5" id="Qf7-x1-bcp"/>
|
<constraint firstItem="5IL-zZ-CL9" firstAttribute="top" secondItem="brd-6J-saN" secondAttribute="top" constant="19.5" id="i7s-ez-iKF"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="Yaj-QY-7xV" secondAttribute="trailing" constant="20" symbolic="YES" id="jqe-y8-arB"/>
|
|
||||||
<constraint firstItem="pYB-E5-4Nv" firstAttribute="leading" secondItem="h4r-Sp-LBI" secondAttribute="leading" id="jsE-BB-mo7"/>
|
<constraint firstItem="pYB-E5-4Nv" firstAttribute="leading" secondItem="h4r-Sp-LBI" secondAttribute="leading" id="jsE-BB-mo7"/>
|
||||||
<constraint firstItem="Yaj-QY-7xV" firstAttribute="leading" secondItem="brd-6J-saN" secondAttribute="leading" constant="20" symbolic="YES" id="mjS-PE-C1d"/>
|
<constraint firstAttribute="trailing" secondItem="5IL-zZ-CL9" secondAttribute="trailing" constant="20" symbolic="YES" id="kZf-wi-ijB"/>
|
||||||
<constraint firstItem="pYB-E5-4Nv" firstAttribute="top" secondItem="h4r-Sp-LBI" secondAttribute="bottom" constant="6" symbolic="YES" id="unl-l5-LUF"/>
|
<constraint firstItem="pYB-E5-4Nv" firstAttribute="top" secondItem="h4r-Sp-LBI" secondAttribute="bottom" constant="6" symbolic="YES" id="unl-l5-LUF"/>
|
||||||
<constraint firstItem="5IL-zZ-CL9" firstAttribute="leading" secondItem="h4r-Sp-LBI" secondAttribute="leading" id="v0c-Cb-FH7"/>
|
<constraint firstItem="5IL-zZ-CL9" firstAttribute="leading" secondItem="h4r-Sp-LBI" secondAttribute="leading" id="v0c-Cb-FH7"/>
|
||||||
|
<constraint firstItem="5IL-zZ-CL9" firstAttribute="leading" secondItem="brd-6J-saN" secondAttribute="leading" constant="20" symbolic="YES" id="xAB-f3-f0e"/>
|
||||||
<constraint firstItem="5IL-zZ-CL9" firstAttribute="trailing" secondItem="h4r-Sp-LBI" secondAttribute="trailing" id="z2C-jk-sNa"/>
|
<constraint firstItem="5IL-zZ-CL9" firstAttribute="trailing" secondItem="h4r-Sp-LBI" secondAttribute="trailing" id="z2C-jk-sNa"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
|
@ -445,7 +428,7 @@
|
||||||
</column>
|
</column>
|
||||||
</cells>
|
</cells>
|
||||||
<connections>
|
<connections>
|
||||||
<binding destination="32" name="selectedTag" keyPath="values.SpecifyTabKeyBehavior" id="ZDp-6s-2lR"/>
|
<binding destination="32" name="selectedTag" keyPath="values.SpecifyShiftTabKeyBehavior" id="R1A-Ji-8i2"/>
|
||||||
</connections>
|
</connections>
|
||||||
</matrix>
|
</matrix>
|
||||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TMn-LX-3Ub">
|
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TMn-LX-3Ub">
|
||||||
|
@ -478,7 +461,7 @@
|
||||||
</column>
|
</column>
|
||||||
</cells>
|
</cells>
|
||||||
<connections>
|
<connections>
|
||||||
<binding destination="32" name="selectedTag" keyPath="values.SpecifySpaceKeyBehavior" id="yBI-fc-n36"/>
|
<binding destination="32" name="selectedTag" keyPath="values.SpecifyShiftSpaceKeyBehavior" id="Ajq-3d-Lh1"/>
|
||||||
</connections>
|
</connections>
|
||||||
</matrix>
|
</matrix>
|
||||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="J0f-Aw-dxC">
|
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="J0f-Aw-dxC">
|
||||||
|
@ -666,9 +649,24 @@
|
||||||
<action selector="resetSpecifiedUserDataFolder:" target="-2" id="pqR-AG-4cm"/>
|
<action selector="resetSpecifiedUserDataFolder:" target="-2" id="pqR-AG-4cm"/>
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Yaj-QY-7xV" userLabel="chkCNSSupport">
|
||||||
|
<rect key="frame" x="19" y="249.5" width="406" height="17"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="16" id="KdE-Vd-Y50"/>
|
||||||
|
</constraints>
|
||||||
|
<buttonCell key="cell" type="check" title="Enable CNS11643 Support (2022-01-07)" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="W24-T4-cg0">
|
||||||
|
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||||
|
<font key="font" metaFont="cellTitle"/>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleCNSSupport:" target="-2" id="mcY-ma-N0c"/>
|
||||||
|
<binding destination="32" name="value" keyPath="values.CNS11643Enabled" id="Pbx-Gt-upm"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="s7t-Kk-EPu" firstAttribute="bottom" secondItem="MPN-np-SbT" secondAttribute="bottom" id="0Fo-ya-hQ9"/>
|
<constraint firstItem="s7t-Kk-EPu" firstAttribute="bottom" secondItem="MPN-np-SbT" secondAttribute="bottom" id="0Fo-ya-hQ9"/>
|
||||||
|
<constraint firstItem="Yaj-QY-7xV" firstAttribute="top" secondItem="O4B-Z2-XYi" secondAttribute="bottom" constant="6" symbolic="YES" id="2RJ-v9-tO3"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="FUV-qx-xkC" secondAttribute="trailing" constant="20" symbolic="YES" id="6QR-tj-5cH"/>
|
<constraint firstAttribute="trailing" secondItem="FUV-qx-xkC" secondAttribute="trailing" constant="20" symbolic="YES" id="6QR-tj-5cH"/>
|
||||||
<constraint firstItem="MPN-np-SbT" firstAttribute="leading" secondItem="p7V-IN-OTr" secondAttribute="leading" constant="346" id="7xl-xX-CEQ"/>
|
<constraint firstItem="MPN-np-SbT" firstAttribute="leading" secondItem="p7V-IN-OTr" secondAttribute="leading" constant="346" id="7xl-xX-CEQ"/>
|
||||||
<constraint firstItem="s7t-Kk-EPu" firstAttribute="leading" secondItem="MPN-np-SbT" secondAttribute="trailing" constant="-376" id="9at-E8-Bt1"/>
|
<constraint firstItem="s7t-Kk-EPu" firstAttribute="leading" secondItem="MPN-np-SbT" secondAttribute="trailing" constant="-376" id="9at-E8-Bt1"/>
|
||||||
|
@ -682,7 +680,9 @@
|
||||||
<constraint firstItem="p7V-IN-OTr" firstAttribute="trailing" secondItem="O4B-Z2-XYi" secondAttribute="trailing" id="auY-l9-U7h"/>
|
<constraint firstItem="p7V-IN-OTr" firstAttribute="trailing" secondItem="O4B-Z2-XYi" secondAttribute="trailing" id="auY-l9-U7h"/>
|
||||||
<constraint firstItem="jXe-xz-9Sd" firstAttribute="leading" secondItem="MPN-np-SbT" secondAttribute="trailing" constant="-1" id="cYQ-Rx-tuG"/>
|
<constraint firstItem="jXe-xz-9Sd" firstAttribute="leading" secondItem="MPN-np-SbT" secondAttribute="trailing" constant="-1" id="cYQ-Rx-tuG"/>
|
||||||
<constraint firstItem="s7t-Kk-EPu" firstAttribute="trailing" secondItem="p7V-IN-OTr" secondAttribute="trailing" constant="-60" id="eIn-kD-FGz"/>
|
<constraint firstItem="s7t-Kk-EPu" firstAttribute="trailing" secondItem="p7V-IN-OTr" secondAttribute="trailing" constant="-60" id="eIn-kD-FGz"/>
|
||||||
|
<constraint firstItem="Yaj-QY-7xV" firstAttribute="leading" secondItem="O4B-Z2-XYi" secondAttribute="leading" id="kPh-vq-asr"/>
|
||||||
<constraint firstItem="p7V-IN-OTr" firstAttribute="leading" secondItem="O4B-Z2-XYi" secondAttribute="leading" id="mmu-SS-jac"/>
|
<constraint firstItem="p7V-IN-OTr" firstAttribute="leading" secondItem="O4B-Z2-XYi" secondAttribute="leading" id="mmu-SS-jac"/>
|
||||||
|
<constraint firstItem="Yaj-QY-7xV" firstAttribute="trailing" secondItem="O4B-Z2-XYi" secondAttribute="trailing" id="r6q-3h-41a"/>
|
||||||
<constraint firstItem="s7t-Kk-EPu" firstAttribute="trailing" secondItem="FUV-qx-xkC" secondAttribute="trailing" constant="-60" id="vIO-x1-7Q2"/>
|
<constraint firstItem="s7t-Kk-EPu" firstAttribute="trailing" secondItem="FUV-qx-xkC" secondAttribute="trailing" constant="-60" id="vIO-x1-7Q2"/>
|
||||||
<constraint firstItem="jXe-xz-9Sd" firstAttribute="baseline" secondItem="MPN-np-SbT" secondAttribute="baseline" id="wys-ML-2Q2"/>
|
<constraint firstItem="jXe-xz-9Sd" firstAttribute="baseline" secondItem="MPN-np-SbT" secondAttribute="baseline" id="wys-ML-2Q2"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
|
@ -755,16 +755,16 @@
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Standard" state="on" id="6"/>
|
<menuItem title="Standard" state="on" id="6"/>
|
||||||
<menuItem title="ETen" tag="1" id="7"/>
|
<menuItem title="ETen" tag="1" id="7"/>
|
||||||
<menuItem title="Hsu" tag="2" id="8"/>
|
|
||||||
<menuItem title="ETen26" tag="3" id="9"/>
|
<menuItem title="ETen26" tag="3" id="9"/>
|
||||||
<menuItem title="IBM" tag="4" id="137"/>
|
<menuItem title="IBM" tag="4" id="137"/>
|
||||||
|
<menuItem title="Hsu" tag="2" id="8"/>
|
||||||
<menuItem title="MiTAC" tag="5" id="7fV-x8-WHQ"/>
|
<menuItem title="MiTAC" tag="5" id="7fV-x8-WHQ"/>
|
||||||
<menuItem title="Fake Seigyou" tag="6" id="27F-8T-FkQ"/>
|
<menuItem title="Fake Seigyou" tag="6" id="27F-8T-FkQ"/>
|
||||||
<menuItem title="Hanyu Pinyin" tag="10" id="10"/>
|
<menuItem title="Hanyu Pinyin" tag="10" id="10"/>
|
||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
<connections>
|
<connections>
|
||||||
<binding destination="32" name="selectedTag" keyPath="values.KeyboardLayout" id="103"/>
|
<binding destination="32" name="selectedTag" keyPath="values.MandarinParser" id="n3r-9x-E3p"/>
|
||||||
</connections>
|
</connections>
|
||||||
</popUpButtonCell>
|
</popUpButtonCell>
|
||||||
</popUpButton>
|
</popUpButton>
|
||||||
|
@ -779,7 +779,7 @@
|
||||||
<menu key="menu" title="OtherViews" id="128"/>
|
<menu key="menu" title="OtherViews" id="128"/>
|
||||||
</popUpButtonCell>
|
</popUpButtonCell>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="updateBasisKeyboardLayoutAction:" target="-2" id="136"/>
|
<action selector="updateBasicKeyboardLayoutAction:" target="-2" id="136"/>
|
||||||
</connections>
|
</connections>
|
||||||
</popUpButton>
|
</popUpButton>
|
||||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="125">
|
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="125">
|
||||||
|
|
|
@ -30,7 +30,27 @@
|
||||||
5B707CEC27D9F4870099EF99 /* OpenCC in Frameworks */ = {isa = PBXBuildFile; productRef = 5B707CEB27D9F4870099EF99 /* OpenCC */; };
|
5B707CEC27D9F4870099EF99 /* OpenCC in Frameworks */ = {isa = PBXBuildFile; productRef = 5B707CEB27D9F4870099EF99 /* OpenCC */; };
|
||||||
5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; };
|
5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; };
|
||||||
5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B7BC4AE27AFFBE800F66C24 /* frmPrefWindow.xib */; };
|
5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B7BC4AE27AFFBE800F66C24 /* frmPrefWindow.xib */; };
|
||||||
|
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 */; };
|
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 */; };
|
5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */; };
|
||||||
5BBBB75F27AED54C0023B93A /* Beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75D27AED54C0023B93A /* Beep.m4a */; };
|
5BBBB75F27AED54C0023B93A /* Beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75D27AED54C0023B93A /* Beep.m4a */; };
|
||||||
5BBBB76027AED54C0023B93A /* Fart.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75E27AED54C0023B93A /* Fart.m4a */; };
|
5BBBB76027AED54C0023B93A /* Fart.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75E27AED54C0023B93A /* Fart.m4a */; };
|
||||||
|
@ -50,6 +70,7 @@
|
||||||
5BD05C6827B2BBEF004C4F1D /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6327B2BBEF004C4F1D /* Content.swift */; };
|
5BD05C6827B2BBEF004C4F1D /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6327B2BBEF004C4F1D /* Content.swift */; };
|
||||||
5BD05C6927B2BBEF004C4F1D /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6427B2BBEF004C4F1D /* WindowController.swift */; };
|
5BD05C6927B2BBEF004C4F1D /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6427B2BBEF004C4F1D /* WindowController.swift */; };
|
||||||
5BD05C6A27B2BBEF004C4F1D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6527B2BBEF004C4F1D /* ViewController.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 */; };
|
5BDCBB2E27B4E67A00D0CC59 /* vChewingPhraseEditor.app in Resources */ = {isa = PBXBuildFile; fileRef = 5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */; };
|
||||||
5BE78BD927B3775B005EA1BE /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE78BD827B37750005EA1BE /* ctlAboutWindow.swift */; };
|
5BE78BD927B3775B005EA1BE /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE78BD827B37750005EA1BE /* ctlAboutWindow.swift */; };
|
||||||
5BE78BDD27B3776D005EA1BE /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BE78BDA27B37764005EA1BE /* frmAboutWindow.xib */; };
|
5BE78BDD27B3776D005EA1BE /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BE78BDA27B37764005EA1BE /* frmAboutWindow.xib */; };
|
||||||
|
@ -188,6 +209,26 @@
|
||||||
5B7BC4AF27AFFBE800F66C24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmPrefWindow.xib; sourceTree = "<group>"; };
|
5B7BC4AF27AFFBE800F66C24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmPrefWindow.xib; sourceTree = "<group>"; };
|
||||||
5B7BC4B227AFFC0B00F66C24 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmPrefWindow.strings; sourceTree = "<group>"; };
|
5B7BC4B227AFFC0B00F66C24 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmPrefWindow.strings; sourceTree = "<group>"; };
|
||||||
5B8F43ED27C9BC220069AC27 /* SymbolLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SymbolLM.h; sourceTree = "<group>"; };
|
5B8F43ED27C9BC220069AC27 /* SymbolLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SymbolLM.h; sourceTree = "<group>"; };
|
||||||
|
5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = suiPrefPaneGeneral.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = suiPrefPaneKeyboard.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD0C27FEDB6B002DE248 /* ctlPrefUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ctlPrefUI.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD0D27FEDB6B002DE248 /* suiPrefPaneExperience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = suiPrefPaneExperience.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD0E27FEDB6B002DE248 /* suiPrefPaneDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = suiPrefPaneDictionary.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD3127FEF3C8002DE248 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD3227FEF3C8002DE248 /* Pane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pane.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD3327FEF3C8002DE248 /* Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD3427FEF3C8002DE248 /* PreferencesStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesStyle.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD3527FEF3C8002DE248 /* PreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencePane.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD3627FEF3C8002DE248 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD3727FEF3C8002DE248 /* SegmentedControlStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentedControlStyleViewController.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD3827FEF3C8002DE248 /* ToolbarItemStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolbarItemStyleViewController.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD3927FEF3C8002DE248 /* Container.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD3A27FEF3C8002DE248 /* PreferencesStyleController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesStyleController.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD3B27FEF3C8002DE248 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD3C27FEF3C8002DE248 /* Section.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesTabViewController.swift; sourceTree = "<group>"; };
|
||||||
|
5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VDKComboBox.swift; sourceTree = "<group>"; };
|
||||||
|
5BAEFACF28012565001F42C9 /* mgrLangModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = mgrLangModel.swift; sourceTree = "<group>"; };
|
||||||
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Menu.swift; sourceTree = "<group>"; };
|
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Menu.swift; sourceTree = "<group>"; };
|
||||||
5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = "<group>"; };
|
5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = "<group>"; };
|
||||||
5BBBB75E27AED54C0023B93A /* Fart.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.m4a; sourceTree = "<group>"; };
|
5BBBB75E27AED54C0023B93A /* Fart.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.m4a; sourceTree = "<group>"; };
|
||||||
|
@ -212,6 +253,7 @@
|
||||||
5BD05C6327B2BBEF004C4F1D /* Content.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Content.swift; sourceTree = "<group>"; };
|
5BD05C6327B2BBEF004C4F1D /* Content.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Content.swift; sourceTree = "<group>"; };
|
||||||
5BD05C6427B2BBEF004C4F1D /* WindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; };
|
5BD05C6427B2BBEF004C4F1D /* WindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; };
|
||||||
5BD05C6527B2BBEF004C4F1D /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
5BD05C6527B2BBEF004C4F1D /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||||
|
5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = apiUpdate.swift; sourceTree = "<group>"; };
|
||||||
5BDCBB4227B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = "<group>"; };
|
5BDCBB4227B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = "<group>"; };
|
||||||
5BDCBB4327B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmAboutWindow.strings"; sourceTree = "<group>"; };
|
5BDCBB4327B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmAboutWindow.strings"; sourceTree = "<group>"; };
|
||||||
5BDCBB4527B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmPrefWindow.strings"; sourceTree = "<group>"; };
|
5BDCBB4527B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmPrefWindow.strings"; sourceTree = "<group>"; };
|
||||||
|
@ -349,8 +391,10 @@
|
||||||
5B62A30127AE732800A19448 /* 3rdParty */ = {
|
5B62A30127AE732800A19448 /* 3rdParty */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5BA9FD8C28006BA7002DE248 /* VDKComboBox */,
|
||||||
5B707CE627D9F43E0099EF99 /* OpenCCBridge */,
|
5B707CE627D9F43E0099EF99 /* OpenCCBridge */,
|
||||||
5B62A30227AE733500A19448 /* OVMandarin */,
|
5B62A30227AE733500A19448 /* OVMandarin */,
|
||||||
|
5BA9FCEA27FED652002DE248 /* SindreSorhus */,
|
||||||
);
|
);
|
||||||
path = 3rdParty;
|
path = 3rdParty;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -410,6 +454,7 @@
|
||||||
5B62A32227AE756300A19448 /* IMEModules */ = {
|
5B62A32227AE756300A19448 /* IMEModules */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */,
|
||||||
D4A13D5927A59D5C003BE359 /* ctlInputMethod.swift */,
|
D4A13D5927A59D5C003BE359 /* ctlInputMethod.swift */,
|
||||||
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */,
|
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */,
|
||||||
5B5E535127EF261400C6AA1E /* IME.swift */,
|
5B5E535127EF261400C6AA1E /* IME.swift */,
|
||||||
|
@ -435,6 +480,7 @@
|
||||||
D41355D6278D7409005E5CBD /* mgrLangModel.h */,
|
D41355D6278D7409005E5CBD /* mgrLangModel.h */,
|
||||||
D495583A27A5C6C4006ADE1C /* mgrLangModel_Privates.h */,
|
D495583A27A5C6C4006ADE1C /* mgrLangModel_Privates.h */,
|
||||||
D41355D7278D7409005E5CBD /* mgrLangModel.mm */,
|
D41355D7278D7409005E5CBD /* mgrLangModel.mm */,
|
||||||
|
5BAEFACF28012565001F42C9 /* mgrLangModel.swift */,
|
||||||
5B62A32527AE758000A19448 /* SubLanguageModels */,
|
5B62A32527AE758000A19448 /* SubLanguageModels */,
|
||||||
);
|
);
|
||||||
path = LangModelRelated;
|
path = LangModelRelated;
|
||||||
|
@ -479,6 +525,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5B62A33E27AE7CD900A19448 /* CandidateUI */,
|
5B62A33E27AE7CD900A19448 /* CandidateUI */,
|
||||||
|
5BA9FD0927FED9F3002DE248 /* PrefUI */,
|
||||||
5B62A34227AE7CD900A19448 /* TooltipUI */,
|
5B62A34227AE7CD900A19448 /* TooltipUI */,
|
||||||
5B62A34427AE7CD900A19448 /* NotifierUI */,
|
5B62A34427AE7CD900A19448 /* NotifierUI */,
|
||||||
);
|
);
|
||||||
|
@ -561,6 +608,55 @@
|
||||||
path = OpenCCBridge;
|
path = OpenCCBridge;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
5BA9FCEA27FED652002DE248 /* SindreSorhus */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
5BA9FD3027FEF3C8002DE248 /* Preferences */,
|
||||||
|
);
|
||||||
|
name = SindreSorhus;
|
||||||
|
path = Source/3rdParty/SindreSorhus;
|
||||||
|
sourceTree = SOURCE_ROOT;
|
||||||
|
};
|
||||||
|
5BA9FD0927FED9F3002DE248 /* PrefUI */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
5BA9FD0C27FEDB6B002DE248 /* ctlPrefUI.swift */,
|
||||||
|
5BA9FD0E27FEDB6B002DE248 /* suiPrefPaneDictionary.swift */,
|
||||||
|
5BA9FD0D27FEDB6B002DE248 /* suiPrefPaneExperience.swift */,
|
||||||
|
5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */,
|
||||||
|
5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */,
|
||||||
|
);
|
||||||
|
path = PrefUI;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
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 = "<group>";
|
||||||
|
};
|
||||||
|
5BA9FD8C28006BA7002DE248 /* VDKComboBox */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */,
|
||||||
|
);
|
||||||
|
path = VDKComboBox;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
5BBBB75C27AED54C0023B93A /* SoundFiles */ = {
|
5BBBB75C27AED54C0023B93A /* SoundFiles */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -824,7 +920,7 @@
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 1320;
|
LastSwiftUpdateCheck = 1320;
|
||||||
LastUpgradeCheck = 1320;
|
LastUpgradeCheck = 1330;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
5BD05BB727B2A429004C4F1D = {
|
5BD05BB727B2A429004C4F1D = {
|
||||||
CreatedOnToolsVersion = 13.2;
|
CreatedOnToolsVersion = 13.2;
|
||||||
|
@ -963,40 +1059,61 @@
|
||||||
files = (
|
files = (
|
||||||
5B707CE827D9F4590099EF99 /* OpenCCBridge.swift in Sources */,
|
5B707CE827D9F4590099EF99 /* OpenCCBridge.swift in Sources */,
|
||||||
D427F76C278CA2B0004A2160 /* AppDelegate.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 */,
|
6ACC3D442793701600F1B140 /* ParselessPhraseDB.cpp in Sources */,
|
||||||
D461B792279DAC010070E734 /* InputState.swift in Sources */,
|
D461B792279DAC010070E734 /* InputState.swift in Sources */,
|
||||||
5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */,
|
5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */,
|
||||||
D47B92C027972AD100458394 /* main.swift in Sources */,
|
D47B92C027972AD100458394 /* main.swift in Sources */,
|
||||||
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.mm in Sources */,
|
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.mm in Sources */,
|
||||||
D4A13D5A27A59F0B003BE359 /* ctlInputMethod.swift in Sources */,
|
D4A13D5A27A59F0B003BE359 /* ctlInputMethod.swift in Sources */,
|
||||||
|
5BA9FD4827FEF3C9002DE248 /* PreferencesWindowController.swift in Sources */,
|
||||||
D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */,
|
D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */,
|
||||||
|
5BA9FD4627FEF3C9002DE248 /* Container.swift in Sources */,
|
||||||
D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */,
|
D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */,
|
||||||
5B62A32F27AE78B000A19448 /* CoreLM.mm in Sources */,
|
5B62A32F27AE78B000A19448 /* CoreLM.mm in Sources */,
|
||||||
5BE78BE027B38804005EA1BE /* LMConsolidator.mm in Sources */,
|
5BE78BE027B38804005EA1BE /* LMConsolidator.mm in Sources */,
|
||||||
D456576E279E4F7B00DF6BC9 /* KeyParser.swift 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 */,
|
D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */,
|
||||||
|
5BA9FD4227FEF3C8002DE248 /* PreferencePane.swift in Sources */,
|
||||||
|
5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */,
|
||||||
D47D73AC27A6CAE600255A50 /* AssociatedPhrases.mm in Sources */,
|
D47D73AC27A6CAE600255A50 /* AssociatedPhrases.mm in Sources */,
|
||||||
|
5BA9FD4A27FEF3C9002DE248 /* PreferencesTabViewController.swift in Sources */,
|
||||||
5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */,
|
5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */,
|
||||||
5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */,
|
5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */,
|
||||||
D41355DB278E6D17005E5CBD /* LMInstantiator.mm in Sources */,
|
D41355DB278E6D17005E5CBD /* LMInstantiator.mm in Sources */,
|
||||||
5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */,
|
5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */,
|
||||||
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */,
|
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */,
|
||||||
5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */,
|
5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */,
|
||||||
|
5BAEFAD028012565001F42C9 /* mgrLangModel.swift in Sources */,
|
||||||
5B62A33827AE79CD00A19448 /* NSStringUtils.swift in Sources */,
|
5B62A33827AE79CD00A19448 /* NSStringUtils.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 */,
|
5B62A33227AE792F00A19448 /* InputSourceHelper.swift in Sources */,
|
||||||
5B5E535227EF261400C6AA1E /* IME.swift in Sources */,
|
5B5E535227EF261400C6AA1E /* IME.swift in Sources */,
|
||||||
5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */,
|
5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */,
|
||||||
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */,
|
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */,
|
||||||
5B62A34827AE7CD900A19448 /* ctlCandidateVertical.swift in Sources */,
|
5B62A34827AE7CD900A19448 /* ctlCandidateVertical.swift in Sources */,
|
||||||
|
5BA9FD4027FEF3C8002DE248 /* Localization.swift in Sources */,
|
||||||
|
5BA9FD1327FEDB6B002DE248 /* suiPrefPaneDictionary.swift in Sources */,
|
||||||
6ACC3D452793701600F1B140 /* ParselessLM.cpp in Sources */,
|
6ACC3D452793701600F1B140 /* ParselessLM.cpp in Sources */,
|
||||||
5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */,
|
5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */,
|
||||||
|
5BA9FD4727FEF3C9002DE248 /* PreferencesStyleController.swift in Sources */,
|
||||||
5BF8423127BAA942008E7E4C /* vChewingKanjiConverter.swift in Sources */,
|
5BF8423127BAA942008E7E4C /* vChewingKanjiConverter.swift in Sources */,
|
||||||
5B62A34627AE7CD900A19448 /* ctlCandidateHorizontal.swift in Sources */,
|
5B62A34627AE7CD900A19448 /* ctlCandidateHorizontal.swift in Sources */,
|
||||||
5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */,
|
5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */,
|
||||||
|
5BA9FD3F27FEF3C8002DE248 /* Pane.swift in Sources */,
|
||||||
5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */,
|
5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */,
|
||||||
D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */,
|
D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */,
|
||||||
6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */,
|
6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */,
|
||||||
D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */,
|
D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */,
|
||||||
|
5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -1317,6 +1434,7 @@
|
||||||
"$(OTHER_CFLAGS)",
|
"$(OTHER_CFLAGS)",
|
||||||
"-fcxx-modules",
|
"-fcxx-modules",
|
||||||
);
|
);
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
@ -1358,6 +1476,7 @@
|
||||||
"-fcxx-modules",
|
"-fcxx-modules",
|
||||||
);
|
);
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
@ -1423,7 +1542,7 @@
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Source/Headers/vChewing-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Source/Headers/vChewing-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Osize";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
WRAPPER_EXTENSION = app;
|
WRAPPER_EXTENSION = app;
|
||||||
};
|
};
|
||||||
|
@ -1485,7 +1604,7 @@
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Source/Headers/vChewing-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Source/Headers/vChewing-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Osize";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
WRAPPER_EXTENSION = app;
|
WRAPPER_EXTENSION = app;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1320"
|
LastUpgradeVersion = "1330"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1320"
|
LastUpgradeVersion = "1330"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1320"
|
LastUpgradeVersion = "1330"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1320"
|
LastUpgradeVersion = "1330"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
Loading…
Reference in New Issue