1.5.0 Milestone 1 // Merge GitHub PR#57 from dev/1.5.x
This commit is contained in:
commit
ae053a862f
|
@ -321,7 +321,7 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
|||
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
||||
}
|
||||
}
|
||||
ntfPostInstall.beginSheetModal(for: window!) { response in
|
||||
ntfPostInstall.beginSheetModal(for: window!) { _ in
|
||||
self.endAppWithDelay()
|
||||
}
|
||||
}
|
||||
|
|
16
Makefile
16
Makefile
|
@ -20,7 +20,9 @@ debug:
|
|||
DSTROOT = /Library/Input Methods
|
||||
VC_APP_ROOT = $(DSTROOT)/vChewing.app
|
||||
|
||||
.PHONY: clang-format lint
|
||||
.PHONY: clang-format lint batchfix format
|
||||
|
||||
format: batchfix clang-format lint
|
||||
|
||||
clang-format:
|
||||
@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 ./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
|
||||
|
||||
lint:
|
||||
@swift-format lint --configuration ./.clang-format-swift.json --recursive ./DataCompiler/
|
||||
@swift-format lint --configuration ./.clang-format-swift.json --recursive ./Installer/
|
||||
@swift-format lint --configuration ./.clang-format-swift.json --recursive ./Source/
|
||||
@swift-format lint --configuration ./.clang-format-swift.json --recursive ./UserPhraseEditor/
|
||||
@swift-format lint --configuration ./.clang-format-swift.json --recursive --parallel ./DataCompiler/
|
||||
@swift-format lint --configuration ./.clang-format-swift.json --recursive --parallel ./Installer/
|
||||
@swift-format lint --configuration ./.clang-format-swift.json --recursive --parallel ./Source/
|
||||
@swift-format lint --configuration ./.clang-format-swift.json --recursive --parallel ./UserPhraseEditor/
|
||||
|
||||
batchfix:
|
||||
@swiftlint --fix ./
|
||||
|
||||
.PHONY: permission-check install-debug install-release
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ let package = Package(
|
|||
dependencies: ["OpenCC"],
|
||||
resources: [
|
||||
.copy("benchmark"),
|
||||
.copy("testcases"),
|
||||
.copy("testcases")
|
||||
]),
|
||||
.target(
|
||||
name: "copencc",
|
||||
|
@ -66,20 +66,20 @@ let package = Package(
|
|||
"deps/marisa-0.2.6/AUTHORS",
|
||||
"deps/marisa-0.2.6/CMakeLists.txt",
|
||||
"deps/marisa-0.2.6/COPYING.md",
|
||||
"deps/marisa-0.2.6/README.md",
|
||||
"deps/marisa-0.2.6/README.md"
|
||||
],
|
||||
sources: [
|
||||
"source.cpp",
|
||||
"src",
|
||||
"deps/marisa-0.2.6",
|
||||
"deps/marisa-0.2.6"
|
||||
],
|
||||
cxxSettings: [
|
||||
.headerSearchPath("src"),
|
||||
.headerSearchPath("deps/darts-clone"),
|
||||
.headerSearchPath("deps/marisa-0.2.6/include"),
|
||||
.headerSearchPath("deps/marisa-0.2.6/lib"),
|
||||
.define("ENABLE_DARTS"),
|
||||
]),
|
||||
.define("ENABLE_DARTS")
|
||||
])
|
||||
],
|
||||
cxxLanguageStandard: .cxx14
|
||||
)
|
||||
|
|
|
@ -10,7 +10,7 @@ let testCases: [(String, ChineseConverter.Options)] = [
|
|||
("s2tw", [.traditionalize, .twStandard]),
|
||||
("tw2s", [.simplify, .twStandard]),
|
||||
("s2twp", [.traditionalize, .twStandard, .twIdiom]),
|
||||
("tw2sp", [.simplify, .twStandard, .twIdiom]),
|
||||
("tw2sp", [.simplify, .twStandard, .twIdiom])
|
||||
]
|
||||
|
||||
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 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)
|
||||
class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate,
|
||||
FSEventStreamHelperDelegate
|
||||
|
@ -220,7 +70,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
|||
mgrPrefs.setMissingDefaults()
|
||||
|
||||
// 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。
|
||||
if (UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) != nil) == true {
|
||||
if (UserDefaults.standard.object(forKey: VersionUpdateApi.kCheckUpdateAutomatically) != nil) == true {
|
||||
checkForUpdate()
|
||||
}
|
||||
}
|
||||
|
@ -262,18 +112,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
|||
|
||||
// time for update?
|
||||
if !forced {
|
||||
if UserDefaults.standard.bool(forKey: kCheckUpdateAutomatically) == false {
|
||||
if UserDefaults.standard.bool(forKey: VersionUpdateApi.kCheckUpdateAutomatically) == false {
|
||||
return
|
||||
}
|
||||
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 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date())
|
||||
UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey)
|
||||
let nextUpdateDate = Date(timeInterval: VersionUpdateApi.kNextCheckInterval, since: Date())
|
||||
UserDefaults.standard.set(nextUpdateDate, forKey: VersionUpdateApi.kNextUpdateCheckDateKey)
|
||||
|
||||
checkTask = VersionUpdateApi.check(forced: forced) { [self] result in
|
||||
defer {
|
||||
|
|
|
@ -25,43 +25,30 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
import Cocoa
|
||||
|
||||
@objc class AppleKeyboardConverter: NSObject {
|
||||
@objc class func isDynamicBaseKeyboardLayoutEnabled() -> Bool {
|
||||
switch mgrPrefs.basisKeyboardLayout {
|
||||
case "com.apple.keylayout.ZhuyinBopomofo":
|
||||
return true
|
||||
case "com.apple.keylayout.ZhuyinEten":
|
||||
return true
|
||||
case "org.atelierInmu.vChewing.keyLayouts.vchewingdachen":
|
||||
return true
|
||||
case "org.atelierInmu.vChewing.keyLayouts.vchewingmitac":
|
||||
return true
|
||||
case "org.atelierInmu.vChewing.keyLayouts.vchewingibm":
|
||||
return true
|
||||
case "org.atelierInmu.vChewing.keyLayouts.vchewingseigyou":
|
||||
return true
|
||||
case "org.atelierInmu.vChewing.keyLayouts.vchewingeten":
|
||||
return true
|
||||
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
|
||||
}
|
||||
static let arrDynamicBasicKeyLayout: [String] = [
|
||||
"com.apple.keylayout.ZhuyinBopomofo",
|
||||
"com.apple.keylayout.ZhuyinEten",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingdachen",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingmitac",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingibm",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingseigyou",
|
||||
"org.atelierInmu.vChewing.keyLayouts.vchewingeten",
|
||||
"org.unknown.keylayout.vChewingDachen",
|
||||
"org.unknown.keylayout.vChewingFakeSeigyou",
|
||||
"org.unknown.keylayout.vChewingETen",
|
||||
"org.unknown.keylayout.vChewingIBM",
|
||||
"org.unknown.keylayout.vChewingMiTAC",
|
||||
]
|
||||
@objc class func isDynamicBasicKeyboardLayoutEnabled() -> Bool {
|
||||
return AppleKeyboardConverter.arrDynamicBasicKeyLayout.contains(mgrPrefs.basicKeyboardLayout)
|
||||
}
|
||||
// 處理 Apple 注音鍵盤佈局類型。
|
||||
@objc class func cnvApple2ABC(_ charCode: UniChar) -> UniChar {
|
||||
var charCode = charCode
|
||||
// 在按鍵資訊被送往 OVMandarin 之前,先轉換為可以被 OVMandarin 正常處理的資訊。
|
||||
if self.isDynamicBaseKeyboardLayoutEnabled() {
|
||||
if self.isDynamicBasicKeyboardLayoutEnabled() {
|
||||
// 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。
|
||||
switch mgrPrefs.basisKeyboardLayout {
|
||||
switch mgrPrefs.basicKeyboardLayout {
|
||||
case "com.apple.keylayout.ZhuyinBopomofo":
|
||||
do {
|
||||
if charCode == 97 { charCode = UniChar(65) }
|
||||
|
@ -186,7 +173,7 @@ import Cocoa
|
|||
// 摁了 Alt 的符號。
|
||||
if charCode == 8212 { charCode = UniChar(45) }
|
||||
// Apple 倚天注音佈局追加符號糾正項目。
|
||||
if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
|
||||
if mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
|
||||
if charCode == 65343 { charCode = UniChar(95) }
|
||||
if charCode == 65306 { charCode = UniChar(58) }
|
||||
if charCode == 65311 { charCode = UniChar(63) }
|
||||
|
@ -199,9 +186,9 @@ import Cocoa
|
|||
|
||||
@objc class func cnvStringApple2ABC(_ strProcessed: String) -> String {
|
||||
var strProcessed = strProcessed
|
||||
if self.isDynamicBaseKeyboardLayoutEnabled() {
|
||||
if self.isDynamicBasicKeyboardLayoutEnabled() {
|
||||
// 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。
|
||||
switch mgrPrefs.basisKeyboardLayout {
|
||||
switch mgrPrefs.basicKeyboardLayout {
|
||||
case "com.apple.keylayout.ZhuyinBopomofo":
|
||||
do {
|
||||
if strProcessed == "a" { strProcessed = "A" }
|
||||
|
@ -326,7 +313,7 @@ import Cocoa
|
|||
// 摁了 Alt 的符號。
|
||||
if strProcessed == "—" { strProcessed = "-" }
|
||||
// Apple 倚天注音佈局追加符號糾正項目。
|
||||
if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
|
||||
if mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
|
||||
if strProcessed == "_" { strProcessed = "_" }
|
||||
if strProcessed == ":" { strProcessed = ":" }
|
||||
if strProcessed == "?" { strProcessed = "?" }
|
||||
|
|
|
@ -243,7 +243,7 @@ class InputState: NSObject {
|
|||
self.markerIndex = markerIndex
|
||||
let begin = min(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
|
||||
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
||||
}
|
||||
|
|
|
@ -46,10 +46,8 @@ static double FindHighestScore(const std::vector<Gramambular::NodeAnchor> &nodes
|
|||
{
|
||||
double score = ni->node->highestUnigramScore();
|
||||
if (score > highestScore)
|
||||
{
|
||||
highestScore = score;
|
||||
}
|
||||
}
|
||||
return highestScore + epsilon;
|
||||
}
|
||||
|
||||
|
@ -98,14 +96,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
|
||||
- (BOOL)isBuilderEmpty
|
||||
{
|
||||
if (_builder->grid().width() == 0)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
return (_builder->grid().width() == 0);
|
||||
}
|
||||
|
||||
- (void)setInputMode:(NSString *)value
|
||||
|
@ -114,25 +105,17 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
vChewing::LMInstantiator *newLanguageModel;
|
||||
vChewing::UserOverrideModel *newUserOverrideModel;
|
||||
|
||||
if ([value isKindOfClass:[NSString class]] && [value isEqual:imeModeCHS])
|
||||
{
|
||||
newInputMode = imeModeCHS;
|
||||
newLanguageModel = [mgrLangModel lmCHS];
|
||||
newUserOverrideModel = [mgrLangModel userOverrideModelCHS];
|
||||
}
|
||||
else
|
||||
{
|
||||
newInputMode = imeModeCHT;
|
||||
newLanguageModel = [mgrLangModel lmCHT];
|
||||
newUserOverrideModel = [mgrLangModel userOverrideModelCHT];
|
||||
}
|
||||
BOOL isCHS = [value isKindOfClass:[NSString class]] && [value isEqual:imeModeCHS];
|
||||
|
||||
newInputMode = isCHS ? imeModeCHS : imeModeCHT;
|
||||
newLanguageModel = isCHS ? [mgrLangModel lmCHS] : [mgrLangModel lmCHT];
|
||||
newUserOverrideModel = isCHS ? [mgrLangModel userOverrideModelCHS] : [mgrLangModel userOverrideModelCHT];
|
||||
|
||||
// Report the current Input Mode to ctlInputMethod:
|
||||
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);
|
||||
// Also other sub language models:
|
||||
newLanguageModel->setSymbolEnabled(mgrPrefs.symbolInputEnabled);
|
||||
newLanguageModel->setCNSEnabled(mgrPrefs.cns11643Enabled);
|
||||
|
||||
|
@ -151,24 +134,16 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
}
|
||||
|
||||
if (!_bpmfReadingBuffer->isEmpty())
|
||||
{
|
||||
_bpmfReadingBuffer->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
// clean up everything
|
||||
{ // clean up everything
|
||||
if (_bpmfReadingBuffer)
|
||||
{
|
||||
delete _bpmfReadingBuffer;
|
||||
}
|
||||
|
||||
if (_builder)
|
||||
{
|
||||
delete _builder;
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
|
@ -196,36 +171,35 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
|
||||
- (void)syncWithPreferences
|
||||
{
|
||||
NSInteger layout = mgrPrefs.keyboardLayout;
|
||||
switch (layout)
|
||||
switch (mgrPrefs.mandarinParser)
|
||||
{
|
||||
case KeyboardLayoutOfStandard:
|
||||
case MandarinParserOfStandard:
|
||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout());
|
||||
break;
|
||||
case KeyboardLayoutOfEten:
|
||||
case MandarinParserOfEten:
|
||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETenLayout());
|
||||
break;
|
||||
case KeyboardLayoutOfHsu:
|
||||
case MandarinParserOfHsu:
|
||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HsuLayout());
|
||||
break;
|
||||
case KeyboardLayoutOfEen26:
|
||||
case MandarinParserOfEen26:
|
||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETen26Layout());
|
||||
break;
|
||||
case KeyboardLayoutOfIBM:
|
||||
case MandarinParserOfIBM:
|
||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::IBMLayout());
|
||||
break;
|
||||
case KeyboardLayoutOfMiTAC:
|
||||
case MandarinParserOfMiTAC:
|
||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::MiTACLayout());
|
||||
break;
|
||||
case KeyboardLayoutOfFakeSeigyou:
|
||||
case MandarinParserOfFakeSeigyou:
|
||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::FakeSeigyouLayout());
|
||||
break;
|
||||
case KeyboardLayoutOfHanyuPinyin:
|
||||
case MandarinParserOfHanyuPinyin:
|
||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HanyuPinyinLayout());
|
||||
break;
|
||||
default:
|
||||
_bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout());
|
||||
mgrPrefs.keyboardLayout = KeyboardLayoutOfStandard;
|
||||
mgrPrefs.mandarinParser = MandarinParserOfStandard;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,22 +215,17 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
// in the user override model.
|
||||
BOOL addToOverrideModel = YES;
|
||||
if (selectedNode.spanningLength != [value count])
|
||||
{
|
||||
addToOverrideModel = NO;
|
||||
}
|
||||
|
||||
if (addToOverrideModel)
|
||||
{
|
||||
double score = selectedNode.node->scoreForCandidate(stringValue);
|
||||
if (score <= -12)
|
||||
{ // 威注音的 SymbolLM 的 Score 是 -12。
|
||||
if (score <= -12) // 威注音的 SymbolLM 的 Score 是 -12。
|
||||
addToOverrideModel = NO;
|
||||
}
|
||||
}
|
||||
if (addToOverrideModel)
|
||||
{
|
||||
_userOverrideModel->observe(_walkedNodes, cursorIndex, stringValue, [[NSDate date] timeIntervalSince1970]);
|
||||
}
|
||||
}
|
||||
[self _walk];
|
||||
|
||||
if (mgrPrefs.moveCursorAfterSelectingCandidate)
|
||||
|
@ -265,16 +234,12 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
for (auto node : _walkedNodes)
|
||||
{
|
||||
if (nextPosition >= cursorIndex)
|
||||
{
|
||||
break;
|
||||
}
|
||||
nextPosition += node.spanningLength;
|
||||
}
|
||||
if (nextPosition <= _builder->length())
|
||||
{
|
||||
_builder->setCursorIndex(nextPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clear
|
||||
|
@ -284,13 +249,13 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
_walkedNodes.clear();
|
||||
}
|
||||
|
||||
- (std::string)_currentLayout
|
||||
- (std::string)_currentMandarinParser
|
||||
{
|
||||
NSString *keyboardLayoutName = mgrPrefs.keyboardLayoutName;
|
||||
std::string layout = std::string(keyboardLayoutName.UTF8String) + std::string("_");
|
||||
return layout;
|
||||
return std::string(mgrPrefs.mandarinParserName.UTF8String) + std::string("_");
|
||||
}
|
||||
|
||||
// MARK: - Handling Input
|
||||
|
||||
- (BOOL)handleInput:(keyParser *)input
|
||||
state:(InputState *)inState
|
||||
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 (!input.inputText.length)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it
|
||||
BOOL isFunctionKey =
|
||||
([input isCommandHold] || [input isOptionHotKey] || [input isNumericPad]) || [input isControlHotKey];
|
||||
if (![state isKindOfClass:[InputStateNotEmpty class]] &&
|
||||
![state isKindOfClass:[InputStateAssociatedPhrases class]] && isFunctionKey)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Caps Lock processing: if Caps Lock is ON, temporarily disable bopomofo.
|
||||
// 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.
|
||||
if ([input isShiftHold])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char
|
||||
// insertions.
|
||||
if (charCode < 0x80 && !isprint(charCode))
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// commit everything in the buffer.
|
||||
InputStateCommitting *committingState =
|
||||
|
@ -369,9 +326,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
|
||||
// MARK: Handle Candidates
|
||||
if ([state isKindOfClass:[InputStateChoosingCandidate class]])
|
||||
{
|
||||
return [self _handleCandidateState:state input:input stateCallback:stateCallback errorCallback:errorCallback];
|
||||
}
|
||||
|
||||
// MARK: Handle Associated Phrases
|
||||
if ([state isKindOfClass:[InputStateAssociatedPhrases class]])
|
||||
|
@ -381,9 +336,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
stateCallback:stateCallback
|
||||
errorCallback:errorCallback];
|
||||
if (result)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
state = [[InputStateEmpty alloc] init];
|
||||
stateCallback(state);
|
||||
}
|
||||
|
@ -396,9 +349,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
input:input
|
||||
stateCallback:stateCallback
|
||||
errorCallback:errorCallback])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
state = [marking convertToInputting];
|
||||
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
|
||||
// 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]));
|
||||
if (composeReading)
|
||||
{
|
||||
// combine the reading
|
||||
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))
|
||||
{
|
||||
[IME prtDebugIntel:@"B49C0979"];
|
||||
|
@ -493,9 +444,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
(InputStateAssociatedPhrases *)[self buildAssociatePhraseStateWithKey:text
|
||||
useVerticalMode:input.useVerticalMode];
|
||||
if (associatedPhrases)
|
||||
{
|
||||
stateCallback(associatedPhrases);
|
||||
}
|
||||
else
|
||||
{
|
||||
InputStateEmpty *empty = [[InputStateEmpty alloc] init];
|
||||
|
@ -504,10 +453,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stateCallback(choosingCandidates);
|
||||
}
|
||||
}
|
||||
|
||||
// and tells the client that the key is consumed
|
||||
return YES;
|
||||
|
@ -558,82 +505,54 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
|
||||
// MARK: Esc
|
||||
if ([input isESC])
|
||||
{
|
||||
return [self _handleEscWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||
}
|
||||
|
||||
// MARK: Cursor backward
|
||||
if ([input isCursorBackward] || emacsKey == vChewingEmacsKeyBackward)
|
||||
{
|
||||
return [self _handleBackwardWithState:state
|
||||
input:input
|
||||
stateCallback:stateCallback
|
||||
errorCallback:errorCallback];
|
||||
}
|
||||
|
||||
// MARK: Cursor forward
|
||||
if ([input isCursorForward] || emacsKey == vChewingEmacsKeyForward)
|
||||
{
|
||||
return [self _handleForwardWithState:state input:input stateCallback:stateCallback errorCallback:errorCallback];
|
||||
}
|
||||
|
||||
// MARK: Home
|
||||
if ([input isHome] || emacsKey == vChewingEmacsKeyHome)
|
||||
{
|
||||
return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||
}
|
||||
|
||||
// MARK: End
|
||||
if ([input isEnd] || emacsKey == vChewingEmacsKeyEnd)
|
||||
{
|
||||
return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||
}
|
||||
|
||||
// MARK: Ctrl+PgLf or Shift+PgLf
|
||||
if ([input isControlHold] || [input isShiftHold])
|
||||
{
|
||||
if ([input isOptionHold] && [input isLeft])
|
||||
{
|
||||
if (([input isControlHold] || [input isShiftHold]) && ([input isOptionHold] && [input isLeft]))
|
||||
return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Ctrl+PgRt or Shift+PgRt
|
||||
if ([input isControlHold] || [input isShiftHold])
|
||||
{
|
||||
if ([input isOptionHold] && [input isRight])
|
||||
{
|
||||
if (([input isControlHold] || [input isShiftHold]) && ([input isOptionHold] && [input isRight]))
|
||||
return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: AbsorbedArrowKey
|
||||
if ([input isAbsorbedArrowKey] || [input isExtraChooseCandidateKey] || [input isExtraChooseCandidateKeyReverse])
|
||||
{
|
||||
return [self _handleAbsorbedArrowKeyWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||
}
|
||||
|
||||
// MARK: Backspace
|
||||
if ([input isBackSpace])
|
||||
{
|
||||
return [self _handleBackspaceWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||
}
|
||||
|
||||
// MARK: Delete
|
||||
if ([input isDelete] || emacsKey == vChewingEmacsKeyDelete)
|
||||
{
|
||||
return [self _handleDeleteWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||
}
|
||||
|
||||
// MARK: Enter
|
||||
if ([input isEnter])
|
||||
{
|
||||
return ([input isControlHold] && [input isCommandHold])
|
||||
? [self _handleCtrlCommandEnterWithState:state
|
||||
stateCallback:stateCallback
|
||||
errorCallback:errorCallback]
|
||||
: [self _handleEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback];
|
||||
}
|
||||
|
||||
// MARK: Punctuation list
|
||||
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.
|
||||
|
||||
std::string punctuationNamePrefix;
|
||||
|
||||
if ([input isOptionHold])
|
||||
{
|
||||
punctuationNamePrefix = std::string("_alt_punctuation_");
|
||||
}
|
||||
else if ([input isControlHold])
|
||||
{
|
||||
punctuationNamePrefix = std::string("_ctrl_punctuation_");
|
||||
}
|
||||
else if (mgrPrefs.halfWidthPunctuationEnabled)
|
||||
{
|
||||
punctuationNamePrefix = std::string("_half_punctuation_");
|
||||
}
|
||||
else
|
||||
{
|
||||
punctuationNamePrefix = std::string("_punctuation_");
|
||||
}
|
||||
std::string layout = [self _currentLayout];
|
||||
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);
|
||||
if ([self _handlePunctuation:customPunctuation
|
||||
state:state
|
||||
usingVerticalMode:input.useVerticalMode
|
||||
stateCallback:stateCallback
|
||||
errorCallback:errorCallback])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
// if nothing is matched, see if it's a punctuation key.
|
||||
std::string punctuation = punctuationNamePrefix + std::string(1, (char)charCode);
|
||||
|
@ -713,9 +624,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
usingVerticalMode:input.useVerticalMode
|
||||
stateCallback:stateCallback
|
||||
errorCallback:errorCallback])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Lukhnos 這裡的處理反而會使得 Apple 倚天注音動態鍵盤佈局「敲不了半形大寫英文」的缺點曝露無疑,所以注釋掉。
|
||||
// 至於他試圖用這種處理來解決的上游 UPR293
|
||||
|
@ -728,10 +637,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
usingVerticalMode:input.useVerticalMode
|
||||
stateCallback:stateCallback
|
||||
errorCallback:errorCallback])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
// still nothing, then we update the composing buffer (some app has strange behavior if we don't do this, "thinking"
|
||||
// the key is not actually consumed) 砍掉這一段會導致「F1-F12
|
||||
|
@ -754,9 +661,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
errorCallback:(void (^)(void))errorCallback
|
||||
{
|
||||
if (![state isKindOfClass:[InputStateInputting class]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
BOOL escToClearInputBufferEnabled = mgrPrefs.escToCleanInputBuffer;
|
||||
|
||||
|
@ -800,9 +705,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
errorCallback:(void (^)(void))errorCallback
|
||||
{
|
||||
if (![state isKindOfClass:[InputStateInputting class]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!_bpmfReadingBuffer->isEmpty())
|
||||
{
|
||||
|
@ -859,9 +762,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
errorCallback:(void (^)(void))errorCallback
|
||||
{
|
||||
if (![state isKindOfClass:[InputStateInputting class]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!_bpmfReadingBuffer->isEmpty())
|
||||
{
|
||||
|
@ -917,9 +818,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
errorCallback:(void (^)(void))errorCallback
|
||||
{
|
||||
if (![state isKindOfClass:[InputStateInputting class]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!_bpmfReadingBuffer->isEmpty())
|
||||
{
|
||||
|
@ -950,9 +849,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
errorCallback:(void (^)(void))errorCallback
|
||||
{
|
||||
if (![state isKindOfClass:[InputStateInputting class]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!_bpmfReadingBuffer->isEmpty())
|
||||
{
|
||||
|
@ -983,9 +880,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
errorCallback:(void (^)(void))errorCallback
|
||||
{
|
||||
if (![state isKindOfClass:[InputStateInputting class]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!_bpmfReadingBuffer->isEmpty())
|
||||
{
|
||||
|
@ -1001,9 +896,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
errorCallback:(void (^)(void))errorCallback
|
||||
{
|
||||
if (![state isKindOfClass:[InputStateInputting class]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (_bpmfReadingBuffer->isEmpty())
|
||||
{
|
||||
|
@ -1021,9 +914,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_bpmfReadingBuffer->backspace();
|
||||
}
|
||||
|
||||
if (_bpmfReadingBuffer->isEmpty() && !_builder->length())
|
||||
{
|
||||
|
@ -1043,9 +934,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
errorCallback:(void (^)(void))errorCallback
|
||||
{
|
||||
if (![state isKindOfClass:[InputStateInputting class]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (_bpmfReadingBuffer->isEmpty())
|
||||
{
|
||||
|
@ -1106,9 +995,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
errorCallback:(void (^)(void))errorCallback
|
||||
{
|
||||
if (![state isKindOfClass:[InputStateInputting class]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self clear];
|
||||
|
||||
|
@ -1128,9 +1015,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
errorCallback:(void (^)(void))errorCallback
|
||||
{
|
||||
if (!_languageModel->hasUnigramsForKey(customPunctuation))
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *poppedText;
|
||||
if (_bpmfReadingBuffer->isEmpty())
|
||||
|
@ -1165,10 +1050,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
stateCallback(empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
stateCallback(candidateState);
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
@ -1218,10 +1101,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
stateCallback(inputting);
|
||||
}
|
||||
else
|
||||
{
|
||||
stateCallback(marking);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[IME prtDebugIntel:@"1149908D"];
|
||||
|
@ -1249,10 +1130,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
stateCallback(inputting);
|
||||
}
|
||||
else
|
||||
{
|
||||
stateCallback(marking);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[IME prtDebugIntel:@"9B51408D"];
|
||||
|
@ -1324,7 +1203,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
if ([input isTab])
|
||||
{
|
||||
BOOL updated =
|
||||
mgrPrefs.specifyTabKeyBehavior
|
||||
mgrPrefs.specifyShiftTabKeyBehavior
|
||||
? ([input isShiftHold] ? [ctlCandidateCurrent showPreviousPage] : [ctlCandidateCurrent showNextPage])
|
||||
: ([input isShiftHold] ? [ctlCandidateCurrent highlightPreviousCandidate]
|
||||
: [ctlCandidateCurrent highlightNextCandidate]);
|
||||
|
@ -1338,7 +1217,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
|
||||
if ([input isSpace])
|
||||
{
|
||||
BOOL updated = mgrPrefs.specifySpaceKeyBehavior
|
||||
BOOL updated = mgrPrefs.specifyShiftSpaceKeyBehavior
|
||||
? ([input isShiftHold] ? [ctlCandidateCurrent highlightNextCandidate]
|
||||
: [ctlCandidateCurrent showNextPage])
|
||||
: ([input isShiftHold] ? [ctlCandidateCurrent showNextPage]
|
||||
|
@ -1495,9 +1374,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
errorCallback();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctlCandidateCurrent.selectedCandidateIndex = 0;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
@ -1505,18 +1382,12 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
NSArray *candidates;
|
||||
|
||||
if ([state isKindOfClass:[InputStateChoosingCandidate class]])
|
||||
{
|
||||
candidates = [(InputStateChoosingCandidate *)state candidates];
|
||||
}
|
||||
else if ([state isKindOfClass:[InputStateAssociatedPhrases class]])
|
||||
{
|
||||
candidates = [(InputStateAssociatedPhrases *)state candidates];
|
||||
}
|
||||
|
||||
if (!candidates)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (([input isEnd] || input.emacsKey == vChewingEmacsKeyEnd) && candidates.count > 0)
|
||||
{
|
||||
|
@ -1526,30 +1397,23 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
errorCallback();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctlCandidateCurrent.selectedCandidateIndex = candidates.count - 1;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
if ([state isKindOfClass:[InputStateAssociatedPhrases class]])
|
||||
{
|
||||
if (![input isShiftHold])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
NSInteger index = NSNotFound;
|
||||
NSString *match;
|
||||
if ([state isKindOfClass:[InputStateAssociatedPhrases class]])
|
||||
{
|
||||
match = input.inputTextIgnoringModifiers;
|
||||
}
|
||||
else
|
||||
{
|
||||
match = inputText;
|
||||
}
|
||||
|
||||
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]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (mgrPrefs.useSCPCTypingMode)
|
||||
{
|
||||
std::string layout = [self _currentLayout];
|
||||
std::string punctuationNamePrefix;
|
||||
if ([input isOptionHold])
|
||||
{
|
||||
punctuationNamePrefix = std::string("_alt_punctuation_");
|
||||
}
|
||||
else if ([input isControlHold])
|
||||
{
|
||||
punctuationNamePrefix = std::string("_ctrl_punctuation_");
|
||||
}
|
||||
else if (mgrPrefs.halfWidthPunctuationEnabled)
|
||||
{
|
||||
punctuationNamePrefix = std::string("_half_punctuation_");
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
|
||||
BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((char)charCode) ||
|
||||
|
@ -1607,10 +1462,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
{
|
||||
std::string letter = std::string("_letter_") + std::string(1, (char)charCode);
|
||||
if (_languageModel->hasUnigramsForKey(letter))
|
||||
{
|
||||
shouldAutoSelectCandidate = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldAutoSelectCandidate)
|
||||
{
|
||||
|
@ -1746,7 +1599,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
- (void)_walk
|
||||
{
|
||||
// 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
|
||||
Gramambular::Walker walker(&_builder->grid());
|
||||
|
||||
|
@ -1813,10 +1666,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
const std::vector<Gramambular::KeyValuePair> &candidates = (*ni).node->candidates();
|
||||
for (std::vector<Gramambular::KeyValuePair>::const_iterator ci = candidates.begin(), ce = candidates.end();
|
||||
ci != ce; ++ci)
|
||||
{
|
||||
[candidatesArray addObject:[NSString stringWithUTF8String:(*ci).value.c_str()]];
|
||||
}
|
||||
}
|
||||
|
||||
InputStateChoosingCandidate *state =
|
||||
[[InputStateChoosingCandidate alloc] initWithComposingBuffer:currentState.composingBuffer
|
||||
|
@ -1831,9 +1682,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
size_t cursorIndex = _builder->cursorIndex();
|
||||
// MS Phonetics IME style, phrase is *after* the cursor, i.e. cursor is always *before* the phrase
|
||||
if ((mgrPrefs.selectPhraseAfterCursorAsCandidate && (cursorIndex < _builder->length())) || !cursorIndex)
|
||||
{
|
||||
++cursorIndex;
|
||||
}
|
||||
|
||||
return cursorIndex;
|
||||
}
|
||||
|
@ -1843,9 +1692,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
|||
NSMutableArray *readingsArray = [[NSMutableArray alloc] init];
|
||||
std::vector<std::string> v = _builder->readings();
|
||||
for (std::vector<std::string>::iterator it_i = v.begin(); it_i != v.end(); ++it_i)
|
||||
{
|
||||
[readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]];
|
||||
}
|
||||
return readingsArray;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ public class FSEventStreamHelper: NSObject {
|
|||
self.dispatchQueue = queue
|
||||
}
|
||||
|
||||
private var stream: FSEventStreamRef? = nil
|
||||
private var stream: FSEventStreamRef?
|
||||
|
||||
public func start() -> Bool {
|
||||
if stream != nil {
|
||||
|
@ -59,7 +59,7 @@ public class FSEventStreamHelper: NSObject {
|
|||
let stream = FSEventStreamCreate(
|
||||
nil,
|
||||
{
|
||||
(stream, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in
|
||||
(_, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in
|
||||
let helper = Unmanaged<FSEventStreamHelper>.fromOpaque(clientCallBackInfo!)
|
||||
.takeUnretainedValue()
|
||||
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.
|
||||
*/
|
||||
|
||||
import Carbon
|
||||
import Cocoa
|
||||
|
||||
@objc public class IME: NSObject {
|
||||
|
||||
static let arrSupportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"]
|
||||
static let dlgOpenPath = NSOpenPanel()
|
||||
|
||||
// MARK: - 開關判定當前應用究竟是?
|
||||
|
@ -84,7 +85,9 @@ import Cocoa
|
|||
// MARK: - Open a phrase data file.
|
||||
static func openPhraseFile(userFileAt path: String) {
|
||||
func checkIfUserFilesExist() -> Bool {
|
||||
if !mgrLangModel.checkIfUserLanguageModelFilesExist() {
|
||||
if !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHS)
|
||||
|| !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHT)
|
||||
{
|
||||
let content = String(
|
||||
format: NSLocalizedString(
|
||||
"Please check the permission at \"%@\".", comment: ""),
|
||||
|
@ -224,4 +227,120 @@ import Cocoa
|
|||
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: -
|
||||
|
||||
private var currentCandidateClient: Any?
|
||||
private var currentClient: Any?
|
||||
|
||||
private var keyHandler: KeyHandler = KeyHandler()
|
||||
private var state: InputState = InputState.Empty()
|
||||
|
@ -65,8 +65,9 @@ class ctlInputMethod: IMKInputController {
|
|||
// MARK: - Keyboard Layout Specifier
|
||||
|
||||
@objc func setKeyLayout() {
|
||||
let client = client().self as IMKTextInput
|
||||
client.overrideKeyboard(withKeyboardNamed: mgrPrefs.basisKeyboardLayout)
|
||||
if let client = currentClient {
|
||||
(client as? IMKTextInput)?.overrideKeyboard(withKeyboardNamed: mgrPrefs.basicKeyboardLayout)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - IMKInputController methods
|
||||
|
@ -76,24 +77,38 @@ class ctlInputMethod: IMKInputController {
|
|||
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
|
||||
|
||||
override func activateServer(_ client: Any!) {
|
||||
UserDefaults.standard.synchronize()
|
||||
|
||||
// Override the keyboard layout to the basic one.
|
||||
setKeyLayout()
|
||||
// reset the state
|
||||
currentCandidateClient = nil
|
||||
currentClient = client
|
||||
|
||||
keyHandler.clear()
|
||||
keyHandler.syncWithPreferences()
|
||||
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()
|
||||
}
|
||||
|
||||
override func deactivateServer(_ client: Any!) {
|
||||
keyHandler.clear()
|
||||
currentClient = nil
|
||||
self.handle(state: .Empty(), client: client)
|
||||
self.handle(state: .Deactivated(), client: client)
|
||||
}
|
||||
|
@ -110,15 +125,19 @@ class ctlInputMethod: IMKInputController {
|
|||
}
|
||||
mgrLangModel.loadDataModel(newInputMode)
|
||||
|
||||
// Remember to override the keyboard layout again -- treat this as an activate event.
|
||||
setKeyLayout()
|
||||
|
||||
if keyHandler.inputMode != newInputMode {
|
||||
|
||||
UserDefaults.standard.synchronize()
|
||||
keyHandler.clear()
|
||||
keyHandler.inputMode = newInputMode
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 讓外界知道目前的簡繁體輸入模式。
|
||||
ctlInputMethod.currentKeyHandler.inputMode = keyHandler.inputMode
|
||||
|
@ -225,7 +244,7 @@ extension ctlInputMethod {
|
|||
}
|
||||
|
||||
private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) {
|
||||
currentCandidateClient = nil
|
||||
currentClient = nil
|
||||
|
||||
ctlCandidateCurrent?.delegate = nil
|
||||
ctlCandidateCurrent?.visible = false
|
||||
|
@ -235,8 +254,8 @@ extension ctlInputMethod {
|
|||
commit(text: previous.composingBuffer, client: client)
|
||||
}
|
||||
(client as? IMKTextInput)?.setMarkedText(
|
||||
"", selectionRange: NSMakeRange(0, 0),
|
||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||
"", selectionRange: NSRange(location: 0, length: 0),
|
||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||
}
|
||||
|
||||
private func handle(state: InputState.Empty, previous: InputState, client: Any?) {
|
||||
|
@ -251,8 +270,8 @@ extension ctlInputMethod {
|
|||
commit(text: previous.composingBuffer, client: client)
|
||||
}
|
||||
client.setMarkedText(
|
||||
"", selectionRange: NSMakeRange(0, 0),
|
||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||
"", selectionRange: NSRange(location: 0, length: 0),
|
||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||
}
|
||||
|
||||
private func handle(
|
||||
|
@ -266,8 +285,8 @@ extension ctlInputMethod {
|
|||
}
|
||||
|
||||
client.setMarkedText(
|
||||
"", selectionRange: NSMakeRange(0, 0),
|
||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||
"", selectionRange: NSRange(location: 0, length: 0),
|
||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||
}
|
||||
|
||||
private func handle(state: InputState.Committing, previous: InputState, client: Any?) {
|
||||
|
@ -283,8 +302,8 @@ extension ctlInputMethod {
|
|||
commit(text: poppedText, client: client)
|
||||
}
|
||||
client.setMarkedText(
|
||||
"", selectionRange: NSMakeRange(0, 0),
|
||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||
"", selectionRange: NSRange(location: 0, length: 0),
|
||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||
}
|
||||
|
||||
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,
|
||||
// i.e. the client app needs to take care of where to put this composing buffer
|
||||
client.setMarkedText(
|
||||
state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0),
|
||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||
state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0),
|
||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||
if !state.tooltip.isEmpty {
|
||||
show(
|
||||
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,
|
||||
// i.e. the client app needs to take care of where to put this composing buffer
|
||||
client.setMarkedText(
|
||||
state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0),
|
||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||
state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0),
|
||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||
|
||||
if state.tooltip.isEmpty {
|
||||
hideTooltip()
|
||||
|
@ -344,8 +363,8 @@ extension ctlInputMethod {
|
|||
// 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
|
||||
client.setMarkedText(
|
||||
state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0),
|
||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||
state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0),
|
||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||
show(candidateWindowWith: state, client: client)
|
||||
}
|
||||
|
||||
|
@ -356,8 +375,8 @@ extension ctlInputMethod {
|
|||
return
|
||||
}
|
||||
client.setMarkedText(
|
||||
"", selectionRange: NSMakeRange(0, 0),
|
||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||
"", selectionRange: NSRange(location: 0, length: 0),
|
||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||
show(candidateWindowWith: state, client: client)
|
||||
}
|
||||
}
|
||||
|
@ -441,11 +460,11 @@ extension ctlInputMethod {
|
|||
|
||||
ctlCandidateCurrent?.delegate = self
|
||||
ctlCandidateCurrent?.reloadData()
|
||||
currentCandidateClient = client
|
||||
currentClient = client
|
||||
|
||||
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
|
||||
|
||||
if let state = state as? InputState.ChoosingCandidate {
|
||||
|
@ -463,20 +482,18 @@ extension ctlInputMethod {
|
|||
|
||||
if useVerticalMode {
|
||||
ctlCandidateCurrent?.set(
|
||||
windowTopLeftPoint: NSMakePoint(
|
||||
lineHeightRect.origin.x + lineHeightRect.size.width + 4.0,
|
||||
lineHeightRect.origin.y - 4.0),
|
||||
windowTopLeftPoint: NSPoint(
|
||||
x: lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, y: lineHeightRect.origin.y - 4.0),
|
||||
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
|
||||
} else {
|
||||
ctlCandidateCurrent?.set(
|
||||
windowTopLeftPoint: NSMakePoint(
|
||||
lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0),
|
||||
windowTopLeftPoint: NSPoint(x: lineHeightRect.origin.x, y: lineHeightRect.origin.y - 4.0),
|
||||
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if cursor == composingBuffer.count && cursor != 0 {
|
||||
cursor -= 1
|
||||
|
@ -522,14 +539,17 @@ extension ctlInputMethod: KeyHandlerDelegate {
|
|||
let refInputModeReversed: InputMode =
|
||||
(keyHandler.inputMode == InputMode.imeModeCHT)
|
||||
? InputMode.imeModeCHS : InputMode.imeModeCHT
|
||||
mgrLangModel.writeUserPhrase(
|
||||
if !mgrLangModel.writeUserPhrase(
|
||||
state.userPhrase, inputMode: keyHandler.inputMode,
|
||||
areWeDuplicating: state.chkIfUserPhraseExists,
|
||||
areWeDeleting: ctlInputMethod.areWeDeleting)
|
||||
mgrLangModel.writeUserPhrase(
|
||||
|| !mgrLangModel.writeUserPhrase(
|
||||
state.userPhraseConverted, inputMode: refInputModeReversed,
|
||||
areWeDuplicating: false,
|
||||
areWeDeleting: ctlInputMethod.areWeDeleting)
|
||||
{
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -558,7 +578,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
|||
}
|
||||
|
||||
func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt) {
|
||||
let client = currentCandidateClient
|
||||
let client = currentClient
|
||||
|
||||
if let state = state as? InputState.SymbolTable,
|
||||
let node = state.node.children?[Int(index)]
|
||||
|
@ -566,7 +586,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
|||
if let children = node.children, !children.isEmpty {
|
||||
self.handle(
|
||||
state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode),
|
||||
client: currentCandidateClient)
|
||||
client: currentClient)
|
||||
} else {
|
||||
self.handle(state: .Committing(poppedText: node.title), client: client)
|
||||
self.handle(state: .Empty(), client: client)
|
||||
|
@ -603,7 +623,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
|||
|
||||
if let state = state as? InputState.AssociatedPhrases {
|
||||
let selectedValue = state.candidates[Int(index)]
|
||||
handle(state: .Committing(poppedText: selectedValue), client: currentCandidateClient)
|
||||
handle(state: .Committing(poppedText: selectedValue), client: currentClient)
|
||||
if mgrPrefs.associatedPhrasesEnabled,
|
||||
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
||||
withKey: selectedValue, useVerticalMode: state.useVerticalMode)
|
||||
|
|
|
@ -123,10 +123,14 @@ extension ctlInputMethod {
|
|||
|
||||
menu.addItem(NSMenuItem.separator()) // ---------------------
|
||||
|
||||
if optionKeyPressed {
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("vChewing Preferences…", comment: ""),
|
||||
action: #selector(showLegacyPreferences(_:)), keyEquivalent: "")
|
||||
} else {
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("vChewing Preferences…", comment: ""),
|
||||
action: #selector(showPreferences(_:)), keyEquivalent: "")
|
||||
if !optionKeyPressed {
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("Check for Updates…", comment: ""),
|
||||
action: #selector(checkForUpdate(_:)), keyEquivalent: "")
|
||||
|
@ -152,6 +156,20 @@ extension ctlInputMethod {
|
|||
// MARK: - IME Menu Items
|
||||
|
||||
@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.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
@ -163,6 +181,7 @@ extension ctlInputMethod {
|
|||
mgrPrefs.toggleSCPCTypingModeEnabled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||
resetKeyHandler()
|
||||
}
|
||||
|
||||
@objc func toggleChineseConverter(_ sender: Any?) {
|
||||
|
@ -172,6 +191,7 @@ extension ctlInputMethod {
|
|||
mgrPrefs.toggleChineseConversionEnabled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||
resetKeyHandler()
|
||||
}
|
||||
|
||||
@objc func toggleShiftJISShinjitaiOutput(_ sender: Any?) {
|
||||
|
@ -181,6 +201,7 @@ extension ctlInputMethod {
|
|||
mgrPrefs.toggleShiftJISShinjitaiOutputEnabled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||
resetKeyHandler()
|
||||
}
|
||||
|
||||
@objc func toggleHalfWidthPunctuation(_ sender: Any?) {
|
||||
|
@ -191,6 +212,7 @@ extension ctlInputMethod {
|
|||
mgrPrefs.toggleHalfWidthPunctuationEnabled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||
resetKeyHandler()
|
||||
}
|
||||
|
||||
@objc func toggleCNS11643Enabled(_ sender: Any?) {
|
||||
|
@ -200,6 +222,7 @@ extension ctlInputMethod {
|
|||
mgrPrefs.toggleCNS11643Enabled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||
resetKeyHandler()
|
||||
}
|
||||
|
||||
@objc func toggleSymbolEnabled(_ sender: Any?) {
|
||||
|
@ -209,6 +232,7 @@ extension ctlInputMethod {
|
|||
mgrPrefs.toggleSymbolInputEnabled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||
resetKeyHandler()
|
||||
}
|
||||
|
||||
@objc func toggleAssociatedPhrasesEnabled(_ sender: Any?) {
|
||||
|
@ -219,6 +243,7 @@ extension ctlInputMethod {
|
|||
mgrPrefs.toggleAssociatedPhrasesEnabled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||
resetKeyHandler()
|
||||
}
|
||||
|
||||
@objc func togglePhraseReplacement(_ sender: Any?) {
|
||||
|
@ -228,6 +253,7 @@ extension ctlInputMethod {
|
|||
mgrPrefs.togglePhraseReplacementEnabled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
||||
resetKeyHandler()
|
||||
}
|
||||
|
||||
@objc func selfUninstall(_ sender: Any?) {
|
||||
|
|
|
@ -26,38 +26,40 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
|
||||
import Cocoa
|
||||
|
||||
private let kIsDebugModeEnabled = "_DebugMode"
|
||||
private let kUserDataFolderSpecified = "UserDataFolderSpecified"
|
||||
private let kCheckUpdateAutomatically = "CheckUpdateAutomatically"
|
||||
private let kKeyboardLayoutPreference = "KeyboardLayout"
|
||||
private let kBasisKeyboardLayoutPreference = "BasisKeyboardLayout"
|
||||
private let kShowPageButtonsInCandidateWindow = "ShowPageButtonsInCandidateWindow"
|
||||
private let kCandidateListTextSize = "CandidateListTextSize"
|
||||
private let kAppleLanguagesPreferences = "AppleLanguages"
|
||||
private let kShouldAutoReloadUserDataFiles = "ShouldAutoReloadUserDataFiles"
|
||||
private let kSelectPhraseAfterCursorAsCandidatePreference = "SelectPhraseAfterCursorAsCandidate"
|
||||
private let kUseHorizontalCandidateListPreference = "UseHorizontalCandidateList"
|
||||
private let kComposingBufferSizePreference = "ComposingBufferSize"
|
||||
private let kChooseCandidateUsingSpace = "ChooseCandidateUsingSpace"
|
||||
private let kCNS11643Enabled = "CNS11643Enabled"
|
||||
private let kSymbolInputEnabled = "SymbolInputEnabled"
|
||||
private let kChineseConversionEnabled = "ChineseConversionEnabled"
|
||||
private let kShiftJISShinjitaiOutputEnabled = "ShiftJISShinjitaiOutputEnabled"
|
||||
private let kHalfWidthPunctuationEnabled = "HalfWidthPunctuationEnable"
|
||||
private let kMoveCursorAfterSelectingCandidate = "MoveCursorAfterSelectingCandidate"
|
||||
private let kEscToCleanInputBuffer = "EscToCleanInputBuffer"
|
||||
private let kSpecifyTabKeyBehavior = "SpecifyTabKeyBehavior"
|
||||
private let kSpecifySpaceKeyBehavior = "SpecifySpaceKeyBehavior"
|
||||
private let kUseSCPCTypingMode = "UseSCPCTypingMode"
|
||||
private let kMaxCandidateLength = "MaxCandidateLength"
|
||||
private let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep"
|
||||
struct UserDef {
|
||||
static let kIsDebugModeEnabled = "_DebugMode"
|
||||
static let kUserDataFolderSpecified = "UserDataFolderSpecified"
|
||||
static let kCheckUpdateAutomatically = "CheckUpdateAutomatically"
|
||||
static let kMandarinParser = "MandarinParser"
|
||||
static let kBasicKeyboardLayout = "BasicKeyboardLayout"
|
||||
static let kShowPageButtonsInCandidateWindow = "ShowPageButtonsInCandidateWindow"
|
||||
static let kCandidateListTextSize = "CandidateListTextSize"
|
||||
static let kAppleLanguages = "AppleLanguages"
|
||||
static let kShouldAutoReloadUserDataFiles = "ShouldAutoReloadUserDataFiles"
|
||||
static let kSelectPhraseAfterCursorAsCandidate = "SelectPhraseAfterCursorAsCandidate"
|
||||
static let kUseHorizontalCandidateList = "UseHorizontalCandidateList"
|
||||
static let kComposingBufferSize = "ComposingBufferSize"
|
||||
static let kChooseCandidateUsingSpace = "ChooseCandidateUsingSpace"
|
||||
static let kCNS11643Enabled = "CNS11643Enabled"
|
||||
static let kSymbolInputEnabled = "SymbolInputEnabled"
|
||||
static let kChineseConversionEnabled = "ChineseConversionEnabled"
|
||||
static let kShiftJISShinjitaiOutputEnabled = "ShiftJISShinjitaiOutputEnabled"
|
||||
static let kHalfWidthPunctuationEnabled = "HalfWidthPunctuationEnable"
|
||||
static let kMoveCursorAfterSelectingCandidate = "MoveCursorAfterSelectingCandidate"
|
||||
static let kEscToCleanInputBuffer = "EscToCleanInputBuffer"
|
||||
static let kSpecifyShiftTabKeyBehavior = "SpecifyShiftTabKeyBehavior"
|
||||
static let kSpecifyShiftSpaceKeyBehavior = "SpecifyShiftSpaceKeyBehavior"
|
||||
static let kUseSCPCTypingMode = "UseSCPCTypingMode"
|
||||
static let kMaxCandidateLength = "MaxCandidateLength"
|
||||
static let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep"
|
||||
|
||||
private let kCandidateTextFontName = "CandidateTextFontName"
|
||||
private let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName"
|
||||
private let kCandidateKeys = "CandidateKeys"
|
||||
static let kCandidateTextFontName = "CandidateTextFontName"
|
||||
static let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName"
|
||||
static let kCandidateKeys = "CandidateKeys"
|
||||
|
||||
private let kAssociatedPhrasesEnabled = "AssociatedPhrasesEnabled"
|
||||
private let kPhraseReplacementEnabled = "PhraseReplacementEnabled"
|
||||
static let kAssociatedPhrasesEnabled = "AssociatedPhrasesEnabled"
|
||||
static let kPhraseReplacementEnabled = "PhraseReplacementEnabled"
|
||||
}
|
||||
|
||||
private let kDefaultCandidateListTextSize: CGFloat = 18
|
||||
private let kMinKeyLabelSize: CGFloat = 10
|
||||
|
@ -75,7 +77,17 @@ private let kMaxComposingBufferSize = 30
|
|||
|
||||
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
|
||||
struct UserDefault<Value> {
|
||||
|
@ -155,7 +167,7 @@ struct ComposingBufferSize {
|
|||
|
||||
// MARK: -
|
||||
|
||||
@objc enum KeyboardLayout: Int {
|
||||
@objc enum MandarinParser: Int {
|
||||
case ofStandard = 0
|
||||
case ofEten = 1
|
||||
case ofHsu = 2
|
||||
|
@ -191,248 +203,169 @@ struct ComposingBufferSize {
|
|||
@objc public class mgrPrefs: NSObject {
|
||||
static var allKeys: [String] {
|
||||
[
|
||||
kIsDebugModeEnabled,
|
||||
kUserDataFolderSpecified,
|
||||
kKeyboardLayoutPreference,
|
||||
kBasisKeyboardLayoutPreference,
|
||||
kShowPageButtonsInCandidateWindow,
|
||||
kCandidateListTextSize,
|
||||
kAppleLanguagesPreferences,
|
||||
kShouldAutoReloadUserDataFiles,
|
||||
kSelectPhraseAfterCursorAsCandidatePreference,
|
||||
kUseHorizontalCandidateListPreference,
|
||||
kComposingBufferSizePreference,
|
||||
kChooseCandidateUsingSpace,
|
||||
kCNS11643Enabled,
|
||||
kSymbolInputEnabled,
|
||||
kChineseConversionEnabled,
|
||||
kShiftJISShinjitaiOutputEnabled,
|
||||
kHalfWidthPunctuationEnabled,
|
||||
kSpecifyTabKeyBehavior,
|
||||
kSpecifySpaceKeyBehavior,
|
||||
kEscToCleanInputBuffer,
|
||||
kCandidateTextFontName,
|
||||
kCandidateKeyLabelFontName,
|
||||
kCandidateKeys,
|
||||
kMoveCursorAfterSelectingCandidate,
|
||||
kPhraseReplacementEnabled,
|
||||
kUseSCPCTypingMode,
|
||||
kMaxCandidateLength,
|
||||
kShouldNotFartInLieuOfBeep,
|
||||
kAssociatedPhrasesEnabled,
|
||||
UserDef.kIsDebugModeEnabled,
|
||||
UserDef.kUserDataFolderSpecified,
|
||||
UserDef.kMandarinParser,
|
||||
UserDef.kBasicKeyboardLayout,
|
||||
UserDef.kShowPageButtonsInCandidateWindow,
|
||||
UserDef.kCandidateListTextSize,
|
||||
UserDef.kAppleLanguages,
|
||||
UserDef.kShouldAutoReloadUserDataFiles,
|
||||
UserDef.kSelectPhraseAfterCursorAsCandidate,
|
||||
UserDef.kUseHorizontalCandidateList,
|
||||
UserDef.kComposingBufferSize,
|
||||
UserDef.kChooseCandidateUsingSpace,
|
||||
UserDef.kCNS11643Enabled,
|
||||
UserDef.kSymbolInputEnabled,
|
||||
UserDef.kChineseConversionEnabled,
|
||||
UserDef.kShiftJISShinjitaiOutputEnabled,
|
||||
UserDef.kHalfWidthPunctuationEnabled,
|
||||
UserDef.kSpecifyShiftTabKeyBehavior,
|
||||
UserDef.kSpecifyShiftSpaceKeyBehavior,
|
||||
UserDef.kEscToCleanInputBuffer,
|
||||
UserDef.kCandidateTextFontName,
|
||||
UserDef.kCandidateKeyLabelFontName,
|
||||
UserDef.kCandidateKeys,
|
||||
UserDef.kMoveCursorAfterSelectingCandidate,
|
||||
UserDef.kPhraseReplacementEnabled,
|
||||
UserDef.kUseSCPCTypingMode,
|
||||
UserDef.kMaxCandidateLength,
|
||||
UserDef.kShouldNotFartInLieuOfBeep,
|
||||
UserDef.kAssociatedPhrasesEnabled,
|
||||
]
|
||||
}
|
||||
|
||||
// MARK: - 既然 Preferences Module 的預設屬性不自動寫入 plist,那這邊就先寫入了。
|
||||
@objc public static func setMissingDefaults() {
|
||||
// 既然 Preferences Module 的預設屬性不自動寫入 plist、而且還是 private,那這邊就先寫入了。
|
||||
|
||||
// 首次啟用輸入法時不要啟用偵錯模式。
|
||||
if UserDefaults.standard.object(forKey: kIsDebugModeEnabled) == nil {
|
||||
UserDefaults.standard.set(mgrPrefs.isDebugModeEnabled, forKey: kIsDebugModeEnabled)
|
||||
}
|
||||
|
||||
// 首次啟用輸入法時設定不要自動更新,免得在某些要隔絕外部網路連線的保密機構內觸犯資安規則。
|
||||
if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil {
|
||||
UserDefaults.standard.set(false, forKey: kCheckUpdateAutomatically)
|
||||
}
|
||||
|
||||
// 預設顯示選字窗翻頁按鈕
|
||||
if UserDefaults.standard.object(forKey: kShowPageButtonsInCandidateWindow) == nil {
|
||||
UserDefaults.standard.set(
|
||||
mgrPrefs.showPageButtonsInCandidateWindow, forKey: kShowPageButtonsInCandidateWindow
|
||||
)
|
||||
}
|
||||
|
||||
// 預設啟用繪文字與符號輸入
|
||||
if UserDefaults.standard.object(forKey: kSymbolInputEnabled) == nil {
|
||||
UserDefaults.standard.set(mgrPrefs.symbolInputEnabled, forKey: kSymbolInputEnabled)
|
||||
}
|
||||
|
||||
// 預設選字窗字詞文字尺寸,設成 18 剛剛好
|
||||
if UserDefaults.standard.object(forKey: kCandidateListTextSize) == nil {
|
||||
UserDefaults.standard.set(
|
||||
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.setDefault(mgrPrefs.isDebugModeEnabled, forKey: UserDef.kIsDebugModeEnabled)
|
||||
UserDefaults.standard.setDefault(mgrPrefs.checkUpdateAutomatically, forKey: UserDef.kCheckUpdateAutomatically)
|
||||
UserDefaults.standard.setDefault(
|
||||
mgrPrefs.showPageButtonsInCandidateWindow, forKey: UserDef.kShowPageButtonsInCandidateWindow)
|
||||
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(
|
||||
mgrPrefs.shouldAutoReloadUserDataFiles, forKey: UserDef.kShouldAutoReloadUserDataFiles)
|
||||
UserDefaults.standard.setDefault(
|
||||
mgrPrefs.specifyShiftTabKeyBehavior, forKey: UserDef.kSpecifyShiftTabKeyBehavior)
|
||||
UserDefaults.standard.setDefault(
|
||||
mgrPrefs.specifyShiftSpaceKeyBehavior, forKey: UserDef.kSpecifyShiftSpaceKeyBehavior)
|
||||
UserDefaults.standard.setDefault(mgrPrefs.useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode)
|
||||
UserDefaults.standard.setDefault(mgrPrefs.associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled)
|
||||
UserDefaults.standard.setDefault(
|
||||
mgrPrefs.selectPhraseAfterCursorAsCandidate, forKey: UserDef.kSelectPhraseAfterCursorAsCandidate)
|
||||
UserDefaults.standard.setDefault(
|
||||
mgrPrefs.moveCursorAfterSelectingCandidate, forKey: UserDef.kMoveCursorAfterSelectingCandidate)
|
||||
UserDefaults.standard.setDefault(
|
||||
mgrPrefs.useHorizontalCandidateList, forKey: UserDef.kUseHorizontalCandidateList)
|
||||
UserDefaults.standard.setDefault(mgrPrefs.cns11643Enabled, forKey: UserDef.kCNS11643Enabled)
|
||||
UserDefaults.standard.setDefault(mgrPrefs.chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled)
|
||||
UserDefaults.standard.setDefault(
|
||||
mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled)
|
||||
UserDefaults.standard.setDefault(mgrPrefs.phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled)
|
||||
UserDefaults.standard.setDefault(mgrPrefs.shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep)
|
||||
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
|
||||
@UserDefault(key: kIsDebugModeEnabled, defaultValue: false)
|
||||
@UserDefault(key: UserDef.kIsDebugModeEnabled, defaultValue: false)
|
||||
@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 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]
|
||||
|
||||
@UserDefault(key: kKeyboardLayoutPreference, defaultValue: 0)
|
||||
@objc static var keyboardLayout: Int
|
||||
@UserDefault(key: UserDef.kMandarinParser, defaultValue: 0)
|
||||
@objc static var mandarinParser: Int
|
||||
|
||||
@objc static var keyboardLayoutName: String {
|
||||
(KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.ofStandard).name
|
||||
@objc static var mandarinParserName: String {
|
||||
(MandarinParser(rawValue: self.mandarinParser) ?? MandarinParser.ofStandard).name
|
||||
}
|
||||
|
||||
@UserDefault(
|
||||
key: kBasisKeyboardLayoutPreference, defaultValue: "com.apple.keylayout.ZhuyinBopomofo")
|
||||
@objc static var basisKeyboardLayout: String
|
||||
key: UserDef.kBasicKeyboardLayout, defaultValue: "com.apple.keylayout.ZhuyinBopomofo")
|
||||
@objc static var basicKeyboardLayout: String
|
||||
|
||||
@UserDefault(key: kShowPageButtonsInCandidateWindow, defaultValue: true)
|
||||
@UserDefault(key: UserDef.kShowPageButtonsInCandidateWindow, defaultValue: true)
|
||||
@objc static var showPageButtonsInCandidateWindow: Bool
|
||||
|
||||
@CandidateListTextSize(key: kCandidateListTextSize)
|
||||
@CandidateListTextSize(key: UserDef.kCandidateListTextSize)
|
||||
@objc static var candidateListTextSize: CGFloat
|
||||
|
||||
@UserDefault(key: kShouldAutoReloadUserDataFiles, defaultValue: true)
|
||||
@UserDefault(key: UserDef.kShouldAutoReloadUserDataFiles, defaultValue: true)
|
||||
@objc static var shouldAutoReloadUserDataFiles: Bool
|
||||
|
||||
@UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreference, defaultValue: false)
|
||||
@UserDefault(key: UserDef.kSelectPhraseAfterCursorAsCandidate, defaultValue: false)
|
||||
@objc static var selectPhraseAfterCursorAsCandidate: Bool
|
||||
|
||||
@UserDefault(key: kMoveCursorAfterSelectingCandidate, defaultValue: false)
|
||||
@UserDefault(key: UserDef.kMoveCursorAfterSelectingCandidate, defaultValue: true)
|
||||
@objc static var moveCursorAfterSelectingCandidate: Bool
|
||||
|
||||
@UserDefault(key: kUseHorizontalCandidateListPreference, defaultValue: true)
|
||||
@UserDefault(key: UserDef.kUseHorizontalCandidateList, defaultValue: true)
|
||||
@objc static var useHorizontalCandidateList: Bool
|
||||
|
||||
@ComposingBufferSize(key: kComposingBufferSizePreference)
|
||||
@ComposingBufferSize(key: UserDef.kComposingBufferSize)
|
||||
@objc static var composingBufferSize: Int
|
||||
|
||||
@UserDefault(key: kChooseCandidateUsingSpace, defaultValue: true)
|
||||
@UserDefault(key: UserDef.kChooseCandidateUsingSpace, defaultValue: true)
|
||||
@objc static var chooseCandidateUsingSpace: Bool
|
||||
|
||||
@UserDefault(key: kUseSCPCTypingMode, defaultValue: false)
|
||||
@UserDefault(key: UserDef.kUseSCPCTypingMode, defaultValue: false)
|
||||
@objc static var useSCPCTypingMode: Bool
|
||||
|
||||
@objc static func toggleSCPCTypingModeEnabled() -> Bool {
|
||||
useSCPCTypingMode = !useSCPCTypingMode
|
||||
UserDefaults.standard.set(useSCPCTypingMode, forKey: kUseSCPCTypingMode)
|
||||
UserDefaults.standard.set(useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode)
|
||||
return useSCPCTypingMode
|
||||
}
|
||||
|
||||
@UserDefault(key: kMaxCandidateLength, defaultValue: kDefaultComposingBufferSize * 2)
|
||||
@UserDefault(key: UserDef.kMaxCandidateLength, defaultValue: kDefaultComposingBufferSize * 2)
|
||||
@objc static var maxCandidateLength: Int
|
||||
|
||||
@UserDefault(key: kShouldNotFartInLieuOfBeep, defaultValue: true)
|
||||
@UserDefault(key: UserDef.kShouldNotFartInLieuOfBeep, defaultValue: true)
|
||||
@objc static var shouldNotFartInLieuOfBeep: Bool
|
||||
|
||||
@objc static func toggleShouldNotFartInLieuOfBeep() -> Bool {
|
||||
shouldNotFartInLieuOfBeep = !shouldNotFartInLieuOfBeep
|
||||
UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep)
|
||||
UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep)
|
||||
return shouldNotFartInLieuOfBeep
|
||||
}
|
||||
|
||||
@UserDefault(key: kCNS11643Enabled, defaultValue: false)
|
||||
@UserDefault(key: UserDef.kCNS11643Enabled, defaultValue: false)
|
||||
@objc static var cns11643Enabled: Bool
|
||||
|
||||
@objc static func toggleCNS11643Enabled() -> Bool {
|
||||
cns11643Enabled = !cns11643Enabled
|
||||
mgrLangModel.setCNSEnabled(cns11643Enabled) // 很重要
|
||||
UserDefaults.standard.set(cns11643Enabled, forKey: kCNS11643Enabled)
|
||||
UserDefaults.standard.set(cns11643Enabled, forKey: UserDef.kCNS11643Enabled)
|
||||
return cns11643Enabled
|
||||
}
|
||||
|
||||
@UserDefault(key: kSymbolInputEnabled, defaultValue: true)
|
||||
@UserDefault(key: UserDef.kSymbolInputEnabled, defaultValue: true)
|
||||
@objc static var symbolInputEnabled: Bool
|
||||
|
||||
@objc static func toggleSymbolInputEnabled() -> Bool {
|
||||
symbolInputEnabled = !symbolInputEnabled
|
||||
mgrLangModel.setSymbolEnabled(symbolInputEnabled) // 很重要
|
||||
UserDefaults.standard.set(symbolInputEnabled, forKey: kSymbolInputEnabled)
|
||||
UserDefaults.standard.set(symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled)
|
||||
return symbolInputEnabled
|
||||
}
|
||||
|
||||
@UserDefault(key: kChineseConversionEnabled, defaultValue: false)
|
||||
@UserDefault(key: UserDef.kChineseConversionEnabled, defaultValue: false)
|
||||
@objc static var chineseConversionEnabled: Bool
|
||||
|
||||
@objc @discardableResult static func toggleChineseConversionEnabled() -> Bool {
|
||||
|
@ -441,13 +374,13 @@ struct ComposingBufferSize {
|
|||
if chineseConversionEnabled && shiftJISShinjitaiOutputEnabled {
|
||||
self.toggleShiftJISShinjitaiOutputEnabled()
|
||||
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
|
||||
}
|
||||
|
||||
@UserDefault(key: kShiftJISShinjitaiOutputEnabled, defaultValue: false)
|
||||
@UserDefault(key: UserDef.kShiftJISShinjitaiOutputEnabled, defaultValue: false)
|
||||
@objc static var shiftJISShinjitaiOutputEnabled: Bool
|
||||
|
||||
@objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool {
|
||||
|
@ -457,11 +390,11 @@ struct ComposingBufferSize {
|
|||
self.toggleChineseConversionEnabled()
|
||||
}
|
||||
UserDefaults.standard.set(
|
||||
shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
|
||||
shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled)
|
||||
return shiftJISShinjitaiOutputEnabled
|
||||
}
|
||||
|
||||
@UserDefault(key: kHalfWidthPunctuationEnabled, defaultValue: false)
|
||||
@UserDefault(key: UserDef.kHalfWidthPunctuationEnabled, defaultValue: false)
|
||||
@objc static var halfWidthPunctuationEnabled: Bool
|
||||
|
||||
@objc static func toggleHalfWidthPunctuationEnabled() -> Bool {
|
||||
|
@ -469,23 +402,23 @@ struct ComposingBufferSize {
|
|||
return halfWidthPunctuationEnabled
|
||||
}
|
||||
|
||||
@UserDefault(key: kEscToCleanInputBuffer, defaultValue: true)
|
||||
@UserDefault(key: UserDef.kEscToCleanInputBuffer, defaultValue: true)
|
||||
@objc static var escToCleanInputBuffer: Bool
|
||||
|
||||
@UserDefault(key: kSpecifyTabKeyBehavior, defaultValue: false)
|
||||
@objc static var specifyTabKeyBehavior: Bool
|
||||
@UserDefault(key: UserDef.kSpecifyShiftTabKeyBehavior, defaultValue: false)
|
||||
@objc static var specifyShiftTabKeyBehavior: Bool
|
||||
|
||||
@UserDefault(key: kSpecifySpaceKeyBehavior, defaultValue: false)
|
||||
@objc static var specifySpaceKeyBehavior: Bool
|
||||
@UserDefault(key: UserDef.kSpecifyShiftSpaceKeyBehavior, defaultValue: false)
|
||||
@objc static var specifyShiftSpaceKeyBehavior: Bool
|
||||
|
||||
// MARK: - Optional settings
|
||||
@UserDefault(key: kCandidateTextFontName, defaultValue: nil)
|
||||
@UserDefault(key: UserDef.kCandidateTextFontName, defaultValue: nil)
|
||||
@objc static var candidateTextFontName: String?
|
||||
|
||||
@UserDefault(key: kCandidateKeyLabelFontName, defaultValue: nil)
|
||||
@UserDefault(key: UserDef.kCandidateKeyLabelFontName, defaultValue: nil)
|
||||
@objc static var candidateKeyLabelFontName: String?
|
||||
|
||||
@UserDefault(key: kCandidateKeys, defaultValue: kDefaultKeys)
|
||||
@UserDefault(key: UserDef.kCandidateKeys, defaultValue: kDefaultKeys)
|
||||
@objc static var candidateKeys: 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 func togglePhraseReplacementEnabled() -> Bool {
|
||||
phraseReplacementEnabled = !phraseReplacementEnabled
|
||||
mgrLangModel.setPhraseReplacementEnabled(phraseReplacementEnabled)
|
||||
UserDefaults.standard.set(phraseReplacementEnabled, forKey: kPhraseReplacementEnabled)
|
||||
UserDefaults.standard.set(phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled)
|
||||
return phraseReplacementEnabled
|
||||
}
|
||||
|
||||
@UserDefault(key: kAssociatedPhrasesEnabled, defaultValue: false)
|
||||
@UserDefault(key: UserDef.kAssociatedPhrasesEnabled, defaultValue: false)
|
||||
@objc static var associatedPhrasesEnabled: Bool
|
||||
|
||||
@objc static func toggleAssociatedPhrasesEnabled() -> Bool {
|
||||
associatedPhrasesEnabled = !associatedPhrasesEnabled
|
||||
UserDefaults.standard.set(associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled)
|
||||
UserDefaults.standard.set(associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled)
|
||||
return associatedPhrasesEnabled
|
||||
}
|
||||
|
||||
|
|
|
@ -35,29 +35,15 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
+ (void)loadUserPhrases;
|
||||
+ (void)loadUserAssociatedPhrases;
|
||||
+ (void)loadUserPhraseReplacement;
|
||||
+ (BOOL)checkIfUserLanguageModelFilesExist;
|
||||
+ (BOOL)checkIfUserDataFolderExists;
|
||||
+ (BOOL)checkIfSpecifiedUserDataFolderValid:(NSString *)folderPath;
|
||||
+ (NSString *)dataFolderPath:(bool)isDefaultFolder NS_SWIFT_NAME(dataFolderPath(isDefaultFolder:));
|
||||
|
||||
+ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase
|
||||
inputMode:(InputMode)mode
|
||||
key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:));
|
||||
+ (BOOL)writeUserPhrase:(NSString *)userPhrase
|
||||
inputMode:(InputMode)mode
|
||||
areWeDuplicating:(BOOL)areWeDuplicating
|
||||
areWeDeleting:(BOOL)areWeDeleting;
|
||||
+ (void)consolidateGivenFile:(NSString *)path shouldCheckPragma:(BOOL)shouldCheckPragma;
|
||||
+ (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled;
|
||||
+ (void)setCNSEnabled:(BOOL)cnsEnabled;
|
||||
+ (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
|
||||
|
||||
/// The following methods are merely for testing.
|
||||
|
|
|
@ -37,28 +37,16 @@ static vChewing::LMInstantiator gLangModelCHS;
|
|||
static vChewing::UserOverrideModel gUserOverrideModelCHT(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
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing::LMInstantiator &lm)
|
||||
{
|
||||
Class cls = NSClassFromString(@"ctlInputMethod");
|
||||
NSString *dataPath = [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"];
|
||||
NSString *dataPath = [mgrLangModel getBundleDataPath:filenameWithoutExtension];
|
||||
lm.loadLanguageModel([dataPath UTF8String]);
|
||||
}
|
||||
|
||||
+ (NSString *)specifyBundleDataPath:(NSString *)filenameWithoutExtension;
|
||||
{
|
||||
Class cls = NSClassFromString(@"ctlInputMethod");
|
||||
return [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"];
|
||||
}
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (void)loadDataModels
|
||||
{
|
||||
if (!gLangModelCHT.isDataModelLoaded())
|
||||
|
@ -67,15 +55,15 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
|
|||
}
|
||||
if (!gLangModelCHT.isMiscDataLoaded())
|
||||
{
|
||||
gLangModelCHT.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
||||
gLangModelCHT.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
||||
}
|
||||
if (!gLangModelCHT.isSymbolDataLoaded())
|
||||
{
|
||||
gLangModelCHT.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]);
|
||||
gLangModelCHT.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]);
|
||||
}
|
||||
if (!gLangModelCHT.isCNSDataLoaded())
|
||||
{
|
||||
gLangModelCHT.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]);
|
||||
gLangModelCHT.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]);
|
||||
}
|
||||
// -----------------
|
||||
if (!gLangModelCHS.isDataModelLoaded())
|
||||
|
@ -84,18 +72,19 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
|
|||
}
|
||||
if (!gLangModelCHS.isMiscDataLoaded())
|
||||
{
|
||||
gLangModelCHS.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
||||
gLangModelCHS.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
||||
}
|
||||
if (!gLangModelCHS.isSymbolDataLoaded())
|
||||
{
|
||||
gLangModelCHS.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]);
|
||||
gLangModelCHS.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]);
|
||||
}
|
||||
if (!gLangModelCHS.isCNSDataLoaded())
|
||||
{
|
||||
gLangModelCHS.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]);
|
||||
gLangModelCHS.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (void)loadDataModel:(InputMode)mode
|
||||
{
|
||||
if ([mode isEqualToString:imeModeCHT])
|
||||
|
@ -106,15 +95,15 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
|
|||
}
|
||||
if (!gLangModelCHT.isMiscDataLoaded())
|
||||
{
|
||||
gLangModelCHT.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
||||
gLangModelCHT.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
||||
}
|
||||
if (!gLangModelCHT.isSymbolDataLoaded())
|
||||
{
|
||||
gLangModelCHT.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]);
|
||||
gLangModelCHT.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]);
|
||||
}
|
||||
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())
|
||||
{
|
||||
gLangModelCHS.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
||||
gLangModelCHS.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]);
|
||||
}
|
||||
if (!gLangModelCHS.isSymbolDataLoaded())
|
||||
{
|
||||
gLangModelCHS.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]);
|
||||
gLangModelCHS.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]);
|
||||
}
|
||||
if (!gLangModelCHS.isCNSDataLoaded())
|
||||
{
|
||||
gLangModelCHS.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]);
|
||||
gLangModelCHS.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (void)loadUserPhrases
|
||||
{
|
||||
gLangModelCHT.loadUserPhrases([[self userPhrasesDataPath:imeModeCHT] UTF8String],
|
||||
|
@ -149,136 +139,21 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
|
|||
gLangModelCHS.loadUserSymbolData([[self userSymbolDataPath:imeModeCHS] UTF8String]);
|
||||
}
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (void)loadUserAssociatedPhrases
|
||||
{
|
||||
gLangModelCHT.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHT] UTF8String]);
|
||||
gLangModelCHS.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHS] UTF8String]);
|
||||
}
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (void)loadUserPhraseReplacement
|
||||
{
|
||||
gLangModelCHT.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHT] UTF8String]);
|
||||
gLangModelCHS.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHS] UTF8String]);
|
||||
}
|
||||
|
||||
+ (BOOL)checkIfUserDataFolderExists
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase
|
||||
inputMode:(InputMode)mode
|
||||
key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:))
|
||||
|
@ -297,144 +172,51 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
|
|||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)writeUserPhrase:(NSString *)userPhrase
|
||||
inputMode:(InputMode)mode
|
||||
areWeDuplicating:(BOOL)areWeDuplicating
|
||||
areWeDeleting:(BOOL)areWeDeleting
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (void)consolidateGivenFile:(NSString *)path shouldCheckPragma:(BOOL)shouldCheckPragma
|
||||
{
|
||||
if (![self checkIfUserLanguageModelFilesExist])
|
||||
{
|
||||
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];
|
||||
vChewing::LMConsolidator::ConsolidateContent([path UTF8String], shouldCheckPragma);
|
||||
}
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (vChewing::LMInstantiator *)lmCHT
|
||||
{
|
||||
return &gLangModelCHT;
|
||||
}
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (vChewing::LMInstantiator *)lmCHS
|
||||
{
|
||||
return &gLangModelCHS;
|
||||
}
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (vChewing::UserOverrideModel *)userOverrideModelCHT
|
||||
{
|
||||
return &gUserOverrideModelCHT;
|
||||
}
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (vChewing::UserOverrideModel *)userOverrideModelCHS
|
||||
{
|
||||
return &gUserOverrideModelCHS;
|
||||
}
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled
|
||||
{
|
||||
gLangModelCHT.setPhraseReplacementEnabled(phraseReplacementEnabled);
|
||||
gLangModelCHS.setPhraseReplacementEnabled(phraseReplacementEnabled);
|
||||
}
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (void)setCNSEnabled:(BOOL)cnsEnabled
|
||||
{
|
||||
gLangModelCHT.setCNSEnabled(cnsEnabled);
|
||||
gLangModelCHS.setCNSEnabled(cnsEnabled);
|
||||
}
|
||||
|
||||
// 這個函數無法遷移至 Swift
|
||||
+ (void)setSymbolEnabled:(BOOL)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).
|
||||
// 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
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
|
|
@ -73,3 +73,71 @@
|
|||
"catDoubleTableLines" = "DoubleTableLines";
|
||||
"catFillingBlocks" = "FillingBlocks";
|
||||
"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";
|
||||
"catFillingBlocks" = "FillingBlocks";
|
||||
"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" = "双線";
|
||||
"catFillingBlocks" = "ブロック";
|
||||
"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" = "双线";
|
||||
"catFillingBlocks" = "填色";
|
||||
"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" = "雙線";
|
||||
"catFillingBlocks" = "填色";
|
||||
"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
|
||||
|
||||
let topLeftPoint = NSMakePoint(
|
||||
frameRect.origin.x, frameRect.origin.y + frameRect.size.height)
|
||||
let topLeftPoint = NSPoint(x: frameRect.origin.x, y: frameRect.origin.y + frameRect.size.height)
|
||||
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)
|
||||
candidateView.setNeedsDisplay(candidateView.bounds)
|
||||
}
|
||||
|
|
|
@ -440,10 +440,9 @@ extension ctlCandidateVertical {
|
|||
|
||||
frameRect = window?.frame ?? NSRect.zero
|
||||
|
||||
let topLeftPoint = NSMakePoint(
|
||||
frameRect.origin.x, frameRect.origin.y + frameRect.size.height)
|
||||
let topLeftPoint = NSPoint(x: frameRect.origin.x, y: frameRect.origin.y + frameRect.size.height)
|
||||
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)
|
||||
candidateView.setNeedsDisplay(candidateView.bounds)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// 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
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
@ -123,7 +124,7 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
|||
panel.titlebarAppearsTransparent = true
|
||||
panel.titleVisibility = .hidden
|
||||
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.closeButton)?.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).
|
||||
// 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
|
||||
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 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
|
||||
// in Objective-C in order to let IMK to see the same class name as
|
||||
// the "InputMethodServerPreferencesWindowControllerClass" in Info.plist.
|
||||
@objc(ctlPrefWindow) class ctlPrefWindow: NSWindowController {
|
||||
@IBOutlet weak var fontSizePopUpButton: NSPopUpButton!
|
||||
@IBOutlet weak var uiLanguageButton: NSPopUpButton!
|
||||
@IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton!
|
||||
@IBOutlet weak var basicKeyboardLayoutButton: NSPopUpButton!
|
||||
@IBOutlet weak var selectionKeyComboBox: NSComboBox!
|
||||
@IBOutlet weak var chkTrad2KangXi: NSButton!
|
||||
@IBOutlet weak var chkTrad2JISShinjitai: NSButton!
|
||||
@IBOutlet weak var lblCurrentlySpecifiedUserDataFolder: NSTextFieldCell!
|
||||
|
||||
var currentLanguageSelectItem: NSMenuItem? = nil
|
||||
var currentLanguageSelectItem: NSMenuItem?
|
||||
|
||||
override func windowDidLoad() {
|
||||
super.windowDidLoad()
|
||||
|
@ -56,8 +48,8 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
isDefaultFolder: true)
|
||||
|
||||
let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"]
|
||||
var autoMUISelectItem: NSMenuItem? = nil
|
||||
var chosenLanguageItem: NSMenuItem? = nil
|
||||
var autoMUISelectItem: NSMenuItem?
|
||||
var chosenLanguageItem: NSMenuItem?
|
||||
uiLanguageButton.menu?.removeAllItems()
|
||||
|
||||
let appleLanguages = mgrPrefs.appleLanguages
|
||||
|
@ -82,25 +74,25 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
uiLanguageButton.select(currentLanguageSelectItem)
|
||||
|
||||
let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource]
|
||||
var usKeyboardLayoutItem: NSMenuItem? = nil
|
||||
var chosenBaseKeyboardLayoutItem: NSMenuItem? = nil
|
||||
var usKeyboardLayoutItem: NSMenuItem?
|
||||
var chosenBaseKeyboardLayoutItem: NSMenuItem?
|
||||
|
||||
basisKeyboardLayoutButton.menu?.removeAllItems()
|
||||
basicKeyboardLayoutButton.menu?.removeAllItems()
|
||||
|
||||
let itmAppleZhuyinBopomofo = NSMenuItem()
|
||||
itmAppleZhuyinBopomofo.title = String(
|
||||
format: NSLocalizedString("Apple Zhuyin Bopomofo (Dachen)", comment: ""))
|
||||
itmAppleZhuyinBopomofo.representedObject = String(
|
||||
"com.apple.keylayout.ZhuyinBopomofo")
|
||||
basisKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinBopomofo)
|
||||
basicKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinBopomofo)
|
||||
|
||||
let itmAppleZhuyinEten = NSMenuItem()
|
||||
itmAppleZhuyinEten.title = String(
|
||||
format: NSLocalizedString("Apple Zhuyin Eten (Traditional)", comment: ""))
|
||||
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 {
|
||||
if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) {
|
||||
|
@ -150,13 +142,15 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
if sourceID == "com.apple.keylayout.US" {
|
||||
usKeyboardLayoutItem = menuItem
|
||||
}
|
||||
if basisKeyboardLayoutID == sourceID {
|
||||
if basicKeyboardLayoutID == sourceID {
|
||||
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":
|
||||
chosenBaseKeyboardLayoutItem = itmAppleZhuyinBopomofo
|
||||
case "com.apple.keylayout.ZhuyinEten":
|
||||
|
@ -165,7 +159,7 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
break // nothing to do
|
||||
}
|
||||
|
||||
basisKeyboardLayoutButton.select(chosenBaseKeyboardLayoutItem ?? usKeyboardLayoutItem)
|
||||
basicKeyboardLayoutButton.select(chosenBaseKeyboardLayoutItem ?? usKeyboardLayoutItem)
|
||||
|
||||
selectionKeyComboBox.usesDataSource = false
|
||||
selectionKeyComboBox.removeAllItems()
|
||||
|
@ -201,9 +195,9 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
}
|
||||
}
|
||||
|
||||
@IBAction func updateBasisKeyboardLayoutAction(_ sender: Any) {
|
||||
if let sourceID = basisKeyboardLayoutButton.selectedItem?.representedObject as? String {
|
||||
mgrPrefs.basisKeyboardLayout = sourceID
|
||||
@IBAction func updateBasicKeyboardLayoutAction(_ sender: Any) {
|
||||
if let sourceID = basicKeyboardLayoutButton.selectedItem?.representedObject as? String {
|
||||
mgrPrefs.basicKeyboardLayout = sourceID
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,7 +241,7 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
} catch {
|
||||
if let window = window {
|
||||
let alert = NSAlert(error: error)
|
||||
alert.beginSheetModal(for: window) { response in
|
||||
alert.beginSheetModal(for: window) { _ in
|
||||
self.selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys
|
||||
}
|
||||
clsSFX.beep()
|
||||
|
@ -256,8 +250,7 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
}
|
||||
|
||||
@IBAction func resetSpecifiedUserDataFolder(_ sender: Any) {
|
||||
UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified")
|
||||
IME.initLangModels(userOnly: true)
|
||||
mgrPrefs.resetSpecifiedUserDataFolder()
|
||||
}
|
||||
|
||||
@IBAction func chooseUserDataFolderToSpecify(_ sender: Any) {
|
||||
|
@ -275,22 +268,24 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
IME.dlgOpenPath.beginSheetModal(for: self.window!) { result in
|
||||
if result == NSApplication.ModalResponse.OK {
|
||||
if IME.dlgOpenPath.url != nil {
|
||||
if mgrLangModel.checkIfSpecifiedUserDataFolderValid(
|
||||
IME.dlgOpenPath.url!.path)
|
||||
{
|
||||
mgrPrefs.userDataFolderSpecified = IME.dlgOpenPath.url!.path
|
||||
// CommonDialog 讀入的路徑沒有結尾斜槓,這會導致檔案目錄合規性判定失準。
|
||||
// 所以要手動補回來。
|
||||
var newPath = IME.dlgOpenPath.url!.path
|
||||
newPath.ensureTrailingSlash()
|
||||
if mgrLangModel.checkIfSpecifiedUserDataFolderValid(newPath) {
|
||||
mgrPrefs.userDataFolderSpecified = newPath
|
||||
IME.initLangModels(userOnly: true)
|
||||
} else {
|
||||
clsSFX.beep()
|
||||
if !bolPreviousFolderValidity {
|
||||
self.resetSpecifiedUserDataFolder(self)
|
||||
mgrPrefs.resetSpecifiedUserDataFolder()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !bolPreviousFolderValidity {
|
||||
self.resetSpecifiedUserDataFolder(self)
|
||||
mgrPrefs.resetSpecifiedUserDataFolder()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="ctlPrefWindow">
|
||||
<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="chkTrad2KangXi" destination="5IL-zZ-CL9" id="PGS-Dy-BBY"/>
|
||||
<outlet property="fontSizePopUpButton" destination="90" id="108"/>
|
||||
|
@ -163,7 +163,7 @@
|
|||
</connections>
|
||||
</matrix>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -234,22 +234,8 @@
|
|||
<rect key="frame" x="3" y="3" width="445" height="134"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<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">
|
||||
<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">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="cellTitle"/>
|
||||
|
@ -260,7 +246,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<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">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="cellTitle"/>
|
||||
|
@ -271,7 +257,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<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">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="cellTitle"/>
|
||||
|
@ -283,17 +269,14 @@
|
|||
</button>
|
||||
</subviews>
|
||||
<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="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="Yaj-QY-7xV" firstAttribute="top" secondItem="brd-6J-saN" secondAttribute="top" constant="13.5" id="Qf7-x1-bcp"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Yaj-QY-7xV" secondAttribute="trailing" constant="20" symbolic="YES" id="jqe-y8-arB"/>
|
||||
<constraint firstItem="5IL-zZ-CL9" firstAttribute="top" secondItem="brd-6J-saN" secondAttribute="top" constant="19.5" id="i7s-ez-iKF"/>
|
||||
<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="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"/>
|
||||
</constraints>
|
||||
</view>
|
||||
|
@ -445,7 +428,7 @@
|
|||
</column>
|
||||
</cells>
|
||||
<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>
|
||||
</matrix>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TMn-LX-3Ub">
|
||||
|
@ -478,7 +461,7 @@
|
|||
</column>
|
||||
</cells>
|
||||
<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>
|
||||
</matrix>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="J0f-Aw-dxC">
|
||||
|
@ -666,9 +649,24 @@
|
|||
<action selector="resetSpecifiedUserDataFolder:" target="-2" id="pqR-AG-4cm"/>
|
||||
</connections>
|
||||
</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>
|
||||
<constraints>
|
||||
<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 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"/>
|
||||
|
@ -682,7 +680,9 @@
|
|||
<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="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="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="jXe-xz-9Sd" firstAttribute="baseline" secondItem="MPN-np-SbT" secondAttribute="baseline" id="wys-ML-2Q2"/>
|
||||
</constraints>
|
||||
|
@ -755,16 +755,16 @@
|
|||
<items>
|
||||
<menuItem title="Standard" state="on" id="6"/>
|
||||
<menuItem title="ETen" tag="1" id="7"/>
|
||||
<menuItem title="Hsu" tag="2" id="8"/>
|
||||
<menuItem title="ETen26" tag="3" id="9"/>
|
||||
<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="Fake Seigyou" tag="6" id="27F-8T-FkQ"/>
|
||||
<menuItem title="Hanyu Pinyin" tag="10" id="10"/>
|
||||
</items>
|
||||
</menu>
|
||||
<connections>
|
||||
<binding destination="32" name="selectedTag" keyPath="values.KeyboardLayout" id="103"/>
|
||||
<binding destination="32" name="selectedTag" keyPath="values.MandarinParser" id="n3r-9x-E3p"/>
|
||||
</connections>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
|
@ -779,7 +779,7 @@
|
|||
<menu key="menu" title="OtherViews" id="128"/>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="updateBasisKeyboardLayoutAction:" target="-2" id="136"/>
|
||||
<action selector="updateBasicKeyboardLayoutAction:" target="-2" id="136"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="125">
|
||||
|
|
|
@ -30,7 +30,27 @@
|
|||
5B707CEC27D9F4870099EF99 /* OpenCC in Frameworks */ = {isa = PBXBuildFile; productRef = 5B707CEB27D9F4870099EF99 /* OpenCC */; };
|
||||
5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; };
|
||||
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 */; };
|
||||
5BAEFAD028012565001F42C9 /* mgrLangModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAEFACF28012565001F42C9 /* mgrLangModel.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 */; };
|
||||
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 */; };
|
||||
5BD05C6927B2BBEF004C4F1D /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6427B2BBEF004C4F1D /* WindowController.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 */; };
|
||||
5BE78BD927B3775B005EA1BE /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE78BD827B37750005EA1BE /* ctlAboutWindow.swift */; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -349,8 +391,10 @@
|
|||
5B62A30127AE732800A19448 /* 3rdParty */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5BA9FD8C28006BA7002DE248 /* VDKComboBox */,
|
||||
5B707CE627D9F43E0099EF99 /* OpenCCBridge */,
|
||||
5B62A30227AE733500A19448 /* OVMandarin */,
|
||||
5BA9FCEA27FED652002DE248 /* SindreSorhus */,
|
||||
);
|
||||
path = 3rdParty;
|
||||
sourceTree = "<group>";
|
||||
|
@ -410,6 +454,7 @@
|
|||
5B62A32227AE756300A19448 /* IMEModules */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */,
|
||||
D4A13D5927A59D5C003BE359 /* ctlInputMethod.swift */,
|
||||
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */,
|
||||
5B5E535127EF261400C6AA1E /* IME.swift */,
|
||||
|
@ -435,6 +480,7 @@
|
|||
D41355D6278D7409005E5CBD /* mgrLangModel.h */,
|
||||
D495583A27A5C6C4006ADE1C /* mgrLangModel_Privates.h */,
|
||||
D41355D7278D7409005E5CBD /* mgrLangModel.mm */,
|
||||
5BAEFACF28012565001F42C9 /* mgrLangModel.swift */,
|
||||
5B62A32527AE758000A19448 /* SubLanguageModels */,
|
||||
);
|
||||
path = LangModelRelated;
|
||||
|
@ -479,6 +525,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
5B62A33E27AE7CD900A19448 /* CandidateUI */,
|
||||
5BA9FD0927FED9F3002DE248 /* PrefUI */,
|
||||
5B62A34227AE7CD900A19448 /* TooltipUI */,
|
||||
5B62A34427AE7CD900A19448 /* NotifierUI */,
|
||||
);
|
||||
|
@ -561,6 +608,55 @@
|
|||
path = OpenCCBridge;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -824,7 +920,7 @@
|
|||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1320;
|
||||
LastUpgradeCheck = 1320;
|
||||
LastUpgradeCheck = 1330;
|
||||
TargetAttributes = {
|
||||
5BD05BB727B2A429004C4F1D = {
|
||||
CreatedOnToolsVersion = 13.2;
|
||||
|
@ -963,40 +1059,61 @@
|
|||
files = (
|
||||
5B707CE827D9F4590099EF99 /* OpenCCBridge.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 */,
|
||||
D461B792279DAC010070E734 /* InputState.swift in Sources */,
|
||||
5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */,
|
||||
D47B92C027972AD100458394 /* main.swift in Sources */,
|
||||
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.mm in Sources */,
|
||||
D4A13D5A27A59F0B003BE359 /* ctlInputMethod.swift in Sources */,
|
||||
5BA9FD4827FEF3C9002DE248 /* PreferencesWindowController.swift in Sources */,
|
||||
D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */,
|
||||
5BA9FD4627FEF3C9002DE248 /* Container.swift in Sources */,
|
||||
D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */,
|
||||
5B62A32F27AE78B000A19448 /* CoreLM.mm in Sources */,
|
||||
5BE78BE027B38804005EA1BE /* LMConsolidator.mm 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 */,
|
||||
5BA9FD4227FEF3C8002DE248 /* PreferencePane.swift in Sources */,
|
||||
5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */,
|
||||
D47D73AC27A6CAE600255A50 /* AssociatedPhrases.mm in Sources */,
|
||||
5BA9FD4A27FEF3C9002DE248 /* PreferencesTabViewController.swift in Sources */,
|
||||
5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */,
|
||||
5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */,
|
||||
D41355DB278E6D17005E5CBD /* LMInstantiator.mm in Sources */,
|
||||
5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */,
|
||||
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */,
|
||||
5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */,
|
||||
5BAEFAD028012565001F42C9 /* mgrLangModel.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 */,
|
||||
5B5E535227EF261400C6AA1E /* IME.swift in Sources */,
|
||||
5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */,
|
||||
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */,
|
||||
5B62A34827AE7CD900A19448 /* ctlCandidateVertical.swift in Sources */,
|
||||
5BA9FD4027FEF3C8002DE248 /* Localization.swift in Sources */,
|
||||
5BA9FD1327FEDB6B002DE248 /* suiPrefPaneDictionary.swift in Sources */,
|
||||
6ACC3D452793701600F1B140 /* ParselessLM.cpp in Sources */,
|
||||
5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */,
|
||||
5BA9FD4727FEF3C9002DE248 /* PreferencesStyleController.swift in Sources */,
|
||||
5BF8423127BAA942008E7E4C /* vChewingKanjiConverter.swift in Sources */,
|
||||
5B62A34627AE7CD900A19448 /* ctlCandidateHorizontal.swift in Sources */,
|
||||
5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */,
|
||||
5BA9FD3F27FEF3C8002DE248 /* Pane.swift in Sources */,
|
||||
5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */,
|
||||
D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */,
|
||||
6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */,
|
||||
D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */,
|
||||
5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1317,6 +1434,7 @@
|
|||
"$(OTHER_CFLAGS)",
|
||||
"-fcxx-modules",
|
||||
);
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
@ -1358,6 +1476,7 @@
|
|||
"-fcxx-modules",
|
||||
);
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
@ -1423,7 +1542,7 @@
|
|||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Source/Headers/vChewing-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Osize";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
|
@ -1485,7 +1604,7 @@
|
|||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Source/Headers/vChewing-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Osize";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1320"
|
||||
LastUpgradeVersion = "1330"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1320"
|
||||
LastUpgradeVersion = "1330"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1320"
|
||||
LastUpgradeVersion = "1330"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1320"
|
||||
LastUpgradeVersion = "1330"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
Loading…
Reference in New Issue