PrefUI // Massive renovation - phase 1.
- This is the last workable commit prior to the deprecation of the SSPreferences package.
This commit is contained in:
parent
4583bcc562
commit
109ec7382d
|
@ -86,6 +86,19 @@ class CtlPrefUIShared {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
static let formWidth: Double = {
|
||||||
|
switch PrefMgr.shared.appleLanguages[0] {
|
||||||
|
case "ja":
|
||||||
|
return 520
|
||||||
|
default:
|
||||||
|
if PrefMgr.shared.appleLanguages[0].contains("zh-Han") {
|
||||||
|
return 500
|
||||||
|
} else {
|
||||||
|
return 580
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
static var isCJKInterface: Bool {
|
static var isCJKInterface: Bool {
|
||||||
PrefMgr.shared.appleLanguages[0].contains("zh-Han") || PrefMgr.shared.appleLanguages[0] == "ja"
|
PrefMgr.shared.appleLanguages[0].contains("zh-Han") || PrefMgr.shared.appleLanguages[0] == "ja"
|
||||||
}
|
}
|
||||||
|
@ -93,3 +106,21 @@ class CtlPrefUIShared {
|
||||||
static var containerWidth: Double { contentWidth + 60 }
|
static var containerWidth: Double { contentWidth + 60 }
|
||||||
static var maxDescriptionWidth: Double { contentWidth * 0.8 }
|
static var maxDescriptionWidth: Double { contentWidth * 0.8 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(macOS 10.15, *)
|
||||||
|
public extension View {
|
||||||
|
func settingsDescription(maxWidth: CGFloat? = .infinity) -> some View {
|
||||||
|
controlSize(.small)
|
||||||
|
.frame(maxWidth: maxWidth, alignment: .leading)
|
||||||
|
// TODO: Use `.foregroundStyle` when targeting macOS 12.
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(macOS 10.15, *)
|
||||||
|
public extension View {
|
||||||
|
func formStyled() -> some View {
|
||||||
|
if #available(macOS 13, *) { return self.formStyle(.grouped) }
|
||||||
|
return self.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -73,8 +73,9 @@ struct VwrPrefPaneBehavior: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
SSPreferences.Settings.Container(contentWidth: CtlPrefUIShared.contentWidth) {
|
Form {
|
||||||
SSPreferences.Settings.Section(title: "Space:".localized, bottomDivider: true) {
|
Section {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Enable Space key for calling candidate window"),
|
LocalizedStringKey("Enable Space key for calling candidate window"),
|
||||||
isOn: $chooseCandidateUsingSpace
|
isOn: $chooseCandidateUsingSpace
|
||||||
|
@ -84,9 +85,10 @@ struct VwrPrefPaneBehavior: View {
|
||||||
"If disabled, this will insert space instead."
|
"If disabled, this will insert space instead."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "ESC:".localized, bottomDivider: true) {
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Use ESC key to clear the entire input buffer"),
|
LocalizedStringKey("Use ESC key to clear the entire input buffer"),
|
||||||
isOn: $escToCleanInputBuffer
|
isOn: $escToCleanInputBuffer
|
||||||
|
@ -96,9 +98,9 @@ struct VwrPrefPaneBehavior: View {
|
||||||
"If unchecked, the ESC key will try cleaning the unfinished readings / strokes first, and will commit the current composition buffer if there's no unfinished readings / strokes."
|
"If unchecked, the ESC key will try cleaning the unfinished readings / strokes first, and will commit the current composition buffer if there's no unfinished readings / strokes."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Enter:".localized, bottomDivider: true) {
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Allow using Enter key to confirm associated candidate selection"),
|
LocalizedStringKey("Allow using Enter key to confirm associated candidate selection"),
|
||||||
isOn: $alsoConfirmAssociatedCandidatesByEnter
|
isOn: $alsoConfirmAssociatedCandidatesByEnter
|
||||||
|
@ -108,56 +110,53 @@ struct VwrPrefPaneBehavior: View {
|
||||||
"Otherwise, only the candidate keys are allowed to confirm associates."
|
"Otherwise, only the candidate keys are allowed to confirm associates."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Shift+BackSpace:".localized, bottomDivider: true) {
|
VStack(alignment: .leading) {
|
||||||
Picker(
|
Picker(
|
||||||
"",
|
"Shift+BackSpace:",
|
||||||
selection: $specifyShiftBackSpaceKeyBehavior
|
selection: $specifyShiftBackSpaceKeyBehavior
|
||||||
) {
|
) {
|
||||||
Text(LocalizedStringKey("Disassemble the previous reading, dropping its intonation")).tag(0)
|
Text(LocalizedStringKey("Disassemble the previous reading, dropping its intonation")).tag(0)
|
||||||
Text(LocalizedStringKey("Clear the entire inline composition buffer like Shift+Delete")).tag(1)
|
Text(LocalizedStringKey("Clear the entire inline composition buffer like Shift+Delete")).tag(1)
|
||||||
Text(LocalizedStringKey("Always drop the previous reading")).tag(2)
|
Text(LocalizedStringKey("Always drop the previous reading")).tag(2)
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
|
||||||
.pickerStyle(RadioGroupPickerStyle())
|
|
||||||
Text(LocalizedStringKey("Disassembling process does not work with non-phonetic reading keys."))
|
Text(LocalizedStringKey("Disassembling process does not work with non-phonetic reading keys."))
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "(Shift+)Tab:", bottomDivider: true) {
|
VStack(alignment: .leading) {
|
||||||
Picker(
|
Picker(
|
||||||
"",
|
"(Shift+)Tab:",
|
||||||
selection: $specifyShiftTabKeyBehavior
|
selection: $specifyShiftTabKeyBehavior
|
||||||
) {
|
) {
|
||||||
Text(LocalizedStringKey("for revolving candidates")).tag(false)
|
Text(LocalizedStringKey("for revolving candidates")).tag(false)
|
||||||
Text(LocalizedStringKey("for revolving pages")).tag(true)
|
Text(LocalizedStringKey("for revolving pages")).tag(true)
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
|
||||||
.horizontalRadioGroupLayout()
|
|
||||||
.pickerStyle(RadioGroupPickerStyle())
|
.pickerStyle(RadioGroupPickerStyle())
|
||||||
Text(LocalizedStringKey("Choose the behavior of (Shift+)Tab key in the candidate window."))
|
Text(LocalizedStringKey("Choose the behavior of (Shift+)Tab key in the candidate window."))
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "(Shift+)Space:".localized, bottomDivider: true) {
|
VStack(alignment: .leading) {
|
||||||
Picker(
|
Picker(
|
||||||
"",
|
"(Shift+)Space:",
|
||||||
selection: $specifyShiftSpaceKeyBehavior
|
selection: $specifyShiftSpaceKeyBehavior
|
||||||
) {
|
) {
|
||||||
Text(LocalizedStringKey("Space to +revolve candidates, Shift+Space to +revolve pages")).tag(false)
|
Text(LocalizedStringKey("Space to +revolve candidates, Shift+Space to +revolve pages")).tag(false)
|
||||||
Text(LocalizedStringKey("Space to +revolve pages, Shift+Space to +revolve candidates")).tag(true)
|
Text(LocalizedStringKey("Space to +revolve pages, Shift+Space to +revolve candidates")).tag(true)
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
Spacer()
|
||||||
.pickerStyle(RadioGroupPickerStyle())
|
|
||||||
Text(LocalizedStringKey("Choose the behavior of (Shift+)Space key with candidates."))
|
Text(LocalizedStringKey("Choose the behavior of (Shift+)Space key with candidates."))
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Use Space to confirm highlighted candidate in Per-Char Select Mode"),
|
LocalizedStringKey("Use Space to confirm highlighted candidate in Per-Char Select Mode"),
|
||||||
isOn: $useSpaceToCommitHighlightedSCPCCandidate
|
isOn: $useSpaceToCommitHighlightedSCPCCandidate
|
||||||
).controlSize(.small)
|
)
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Shift+Letter:".localized, bottomDivider: true) {
|
}
|
||||||
|
Section {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Picker(
|
Picker(
|
||||||
"",
|
"Shift+Letter:",
|
||||||
selection: $upperCaseLetterKeyBehavior
|
selection: $upperCaseLetterKeyBehavior
|
||||||
) {
|
) {
|
||||||
Text(LocalizedStringKey("Type them into inline composition buffer")).tag(0)
|
Text(LocalizedStringKey("Type them into inline composition buffer")).tag(0)
|
||||||
|
@ -166,32 +165,45 @@ struct VwrPrefPaneBehavior: View {
|
||||||
Text(LocalizedStringKey("Directly commit lowercased letters only if the compositor is empty")).tag(3)
|
Text(LocalizedStringKey("Directly commit lowercased letters only if the compositor is empty")).tag(3)
|
||||||
Text(LocalizedStringKey("Directly commit uppercased letters only if the compositor is empty")).tag(4)
|
Text(LocalizedStringKey("Directly commit uppercased letters only if the compositor is empty")).tag(4)
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
|
||||||
.pickerStyle(RadioGroupPickerStyle())
|
|
||||||
Text(LocalizedStringKey("Choose the behavior of Shift+Letter key with letter inputs."))
|
Text(LocalizedStringKey("Choose the behavior of Shift+Letter key with letter inputs."))
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Intonation Key:".localized, bottomDivider: true) {
|
}
|
||||||
|
Section {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Picker(
|
Picker(
|
||||||
"",
|
"Intonation Key:",
|
||||||
selection: $specifyIntonationKeyBehavior
|
selection: $specifyIntonationKeyBehavior
|
||||||
) {
|
) {
|
||||||
Text(LocalizedStringKey("Override the previous reading's intonation with candidate-reset")).tag(0)
|
Text(LocalizedStringKey("Override the previous reading's intonation with candidate-reset")).tag(0)
|
||||||
Text(LocalizedStringKey("Only override the intonation of the previous reading if different")).tag(1)
|
Text(LocalizedStringKey("Only override the intonation of the previous reading if different")).tag(1)
|
||||||
Text(LocalizedStringKey("Always type intonations to the inline composition buffer")).tag(2)
|
Text(LocalizedStringKey("Always type intonations to the inline composition buffer")).tag(2)
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
|
||||||
.pickerStyle(RadioGroupPickerStyle())
|
|
||||||
Text(LocalizedStringKey("Specify the behavior of intonation key when syllable composer is empty."))
|
Text(LocalizedStringKey("Specify the behavior of intonation key when syllable composer is empty."))
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
|
}
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Accept leading intonations in rare cases"),
|
LocalizedStringKey("Accept leading intonations in rare cases"),
|
||||||
isOn: $acceptLeadingIntonations
|
isOn: $acceptLeadingIntonations
|
||||||
).controlSize(.small)
|
)
|
||||||
|
Spacer()
|
||||||
Text(LocalizedStringKey("This feature accommodates certain typing mistakes that the intonation mark might be typed at first (which is sequentially wrong from a common sense that intonation marks are supposed to be used for confirming combinations). It won't work if the current parser is of (any) pinyin. Also, this feature won't work when an intonation override is possible (and enabled)."))
|
Text(LocalizedStringKey("This feature accommodates certain typing mistakes that the intonation mark might be typed at first (which is sequentially wrong from a common sense that intonation marks are supposed to be used for confirming combinations). It won't work if the current parser is of (any) pinyin. Also, this feature won't work when an intonation override is possible (and enabled)."))
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Shift:", bottomDivider: true) {
|
}
|
||||||
|
Section {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Toggle(
|
||||||
|
LocalizedStringKey("Share alphanumerical mode status across all clients"),
|
||||||
|
isOn: $shareAlphanumericalModeStatusAcrossClients
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"This only works when being toggled by Shift key and JIS Eisu key.".localized
|
||||||
|
)
|
||||||
|
.settingsDescription()
|
||||||
|
}
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Toggle alphanumerical mode with Left-Shift"),
|
LocalizedStringKey("Toggle alphanumerical mode with Left-Shift"),
|
||||||
isOn: $togglingAlphanumericalModeWithLShift.onChange {
|
isOn: $togglingAlphanumericalModeWithLShift.onChange {
|
||||||
|
@ -204,21 +216,16 @@ struct VwrPrefPaneBehavior: View {
|
||||||
SessionCtl.theShiftKeyDetector.toggleWithRShift = togglingAlphanumericalModeWithRShift
|
SessionCtl.theShiftKeyDetector.toggleWithRShift = togglingAlphanumericalModeWithRShift
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
Spacer()
|
||||||
Text(
|
Text(
|
||||||
"This feature requires macOS 10.15 and above.".localized + CtlPrefUIShared.sentenceSeparator
|
"This feature requires macOS 10.15 and above.".localized + CtlPrefUIShared.sentenceSeparator
|
||||||
+ "This feature only needs to parse consecutive NSEvents passed by macOS built-in InputMethodKit framework, hence no necessity of asking end-users for extra privileges of monitoring global keyboard inputs. You are free to investigate our codebase or reverse-engineer this input method to see whether the above statement is trustable.".localized
|
+ "This feature only needs to parse consecutive NSEvents passed by macOS built-in InputMethodKit framework, hence no necessity of asking end-users for extra privileges of monitoring global keyboard inputs. You are free to investigate our codebase or reverse-engineer this input method to see whether the above statement is trustable.".localized
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
Toggle(
|
|
||||||
LocalizedStringKey("Share alphanumerical mode status across all clients"),
|
|
||||||
isOn: $shareAlphanumericalModeStatusAcrossClients
|
|
||||||
).controlSize(.small)
|
|
||||||
Text(
|
|
||||||
"This only works when being toggled by Shift key and JIS Eisu key.".localized
|
|
||||||
)
|
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Caps Lock:", bottomDivider: true) {
|
}
|
||||||
|
Section {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Show notifications when toggling Caps Lock"),
|
LocalizedStringKey("Show notifications when toggling Caps Lock"),
|
||||||
isOn: $showNotificationsWhenTogglingCapsLock.onChange {
|
isOn: $showNotificationsWhenTogglingCapsLock.onChange {
|
||||||
|
@ -230,9 +237,9 @@ struct VwrPrefPaneBehavior: View {
|
||||||
Text(
|
Text(
|
||||||
"This feature requires macOS 12 and above.".localized
|
"This feature requires macOS 12 and above.".localized
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Misc Settings:".localized) {
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Always show tooltip texts horizontally"),
|
LocalizedStringKey("Always show tooltip texts horizontally"),
|
||||||
isOn: $alwaysShowTooltipTextsHorizontally
|
isOn: $alwaysShowTooltipTextsHorizontally
|
||||||
|
@ -242,9 +249,10 @@ struct VwrPrefPaneBehavior: View {
|
||||||
"Key names in tooltip will be shown as symbols when the tooltip is vertical. However, this option will be ignored since tooltip will always be horizontal if the UI language is English."
|
"Key names in tooltip will be shown as symbols when the tooltip is vertical. However, this option will be ignored since tooltip will always be horizontal if the UI language is English."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.formStyled().frame(width: CtlPrefUIShared.formWidth)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,52 +54,47 @@ struct VwrPrefPaneCandidates: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
SSPreferences.Settings.Container(contentWidth: CtlPrefUIShared.contentWidth) {
|
Form {
|
||||||
SSPreferences.Settings.Section(title: "Selection Keys:".localized, bottomDivider: true) {
|
Section {
|
||||||
VwrPrefPaneCandidates_SelectionKeys()
|
VStack(alignment: .leading) {
|
||||||
}
|
|
||||||
SSPreferences.Settings.Section(title: "Candidate Layout:".localized, bottomDivider: true) {
|
|
||||||
Picker(
|
Picker(
|
||||||
"",
|
"Cursor Selection:",
|
||||||
|
selection: $useRearCursorMode
|
||||||
|
) {
|
||||||
|
Text(LocalizedStringKey("in front of the phrase (like macOS built-in Zhuyin IME)")).tag(false)
|
||||||
|
Text(LocalizedStringKey("at the rear of the phrase (like Microsoft New Phonetic)")).tag(true)
|
||||||
|
}
|
||||||
|
Text(LocalizedStringKey("Choose the cursor position where you want to list possible candidates."))
|
||||||
|
.settingsDescription()
|
||||||
|
}
|
||||||
|
Toggle(
|
||||||
|
LocalizedStringKey("Push the cursor in front of the phrase after selection"),
|
||||||
|
isOn: $moveCursorAfterSelectingCandidate
|
||||||
|
)
|
||||||
|
if !useRearCursorMode {
|
||||||
|
Toggle(
|
||||||
|
LocalizedStringKey("Adjust candidate window location according to current node length"),
|
||||||
|
isOn: $useDynamicCandidateWindowOrigin
|
||||||
|
).disabled(useRearCursorMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Section {
|
||||||
|
VStack(alignment: .leading) { VwrPrefPaneCandidates_SelectionKeys() }
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Picker(
|
||||||
|
"Candidate Layout:",
|
||||||
selection: $useHorizontalCandidateList
|
selection: $useHorizontalCandidateList
|
||||||
) {
|
) {
|
||||||
Text(LocalizedStringKey("Vertical")).tag(false)
|
Text(LocalizedStringKey("Vertical")).tag(false)
|
||||||
Text(LocalizedStringKey("Horizontal")).tag(true)
|
Text(LocalizedStringKey("Horizontal")).tag(true)
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
|
||||||
.horizontalRadioGroupLayout()
|
|
||||||
.pickerStyle(RadioGroupPickerStyle())
|
.pickerStyle(RadioGroupPickerStyle())
|
||||||
Text(LocalizedStringKey("Choose your preferred layout of the candidate window."))
|
Text(LocalizedStringKey("Choose your preferred layout of the candidate window."))
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
let candidateLayoutSubOptions = Group {
|
|
||||||
Toggle(
|
|
||||||
LocalizedStringKey("Use only one row / column in candidate window"),
|
|
||||||
isOn: $candidateWindowShowOnlyOneLine
|
|
||||||
)
|
|
||||||
.controlSize(.small)
|
|
||||||
Toggle(
|
|
||||||
LocalizedStringKey("Always expand candidate window panel"),
|
|
||||||
isOn: $alwaysExpandCandidateWindow
|
|
||||||
)
|
|
||||||
.controlSize(.small).disabled(candidateWindowShowOnlyOneLine)
|
|
||||||
}
|
}
|
||||||
if CtlPrefUIShared.isCJKInterface {
|
|
||||||
HStack {
|
|
||||||
candidateLayoutSubOptions
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
candidateLayoutSubOptions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
"Tadokoro candidate window shows 4 rows / columns by default, providing similar experiences from Microsoft New Phonetic IME and macOS bult-in Chinese IME (since macOS 10.9). However, for some users who have presbyopia, they prefer giant candidate font sizes, resulting a concern that multiple rows / columns of candidates can make the candidate window looks too big, hence this option. Note that this option will be dismissed if the typing context is vertical, forcing the candidates to be shown in only one row / column. Only one reverse-lookup result can be made available in single row / column mode due to reduced candidate window size.".localized
|
|
||||||
)
|
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
|
||||||
}
|
|
||||||
SSPreferences.Settings.Section(title: "Candidate Size:".localized, bottomDivider: true) {
|
|
||||||
Picker(
|
Picker(
|
||||||
"",
|
"Candidate Size:",
|
||||||
selection: $candidateListTextSize.onChange {
|
selection: $candidateListTextSize.onChange {
|
||||||
guard !(12 ... 196).contains(candidateListTextSize) else { return }
|
guard !(12 ... 196).contains(candidateListTextSize) else { return }
|
||||||
candidateListTextSize = max(12, min(candidateListTextSize, 196))
|
candidateListTextSize = max(12, min(candidateListTextSize, 196))
|
||||||
|
@ -121,33 +116,32 @@ struct VwrPrefPaneCandidates: View {
|
||||||
Text("96").tag(96.0)
|
Text("96").tag(96.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
|
||||||
.frame(width: 120.0)
|
|
||||||
Text(LocalizedStringKey("Choose candidate font size for better visual clarity."))
|
Text(LocalizedStringKey("Choose candidate font size for better visual clarity."))
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Cursor Selection:".localized, bottomDivider: true) {
|
VStack(alignment: .leading) {
|
||||||
Picker(
|
|
||||||
"",
|
|
||||||
selection: $useRearCursorMode
|
|
||||||
) {
|
|
||||||
Text(LocalizedStringKey("in front of the phrase (like macOS built-in Zhuyin IME)")).tag(false)
|
|
||||||
Text(LocalizedStringKey("at the rear of the phrase (like Microsoft New Phonetic)")).tag(true)
|
|
||||||
}
|
|
||||||
.labelsHidden()
|
|
||||||
.pickerStyle(RadioGroupPickerStyle())
|
|
||||||
Text(LocalizedStringKey("Choose the cursor position where you want to list possible candidates."))
|
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Push the cursor in front of the phrase after selection"),
|
LocalizedStringKey("Use only one row / column in candidate window"),
|
||||||
isOn: $moveCursorAfterSelectingCandidate
|
isOn: $candidateWindowShowOnlyOneLine
|
||||||
).controlSize(.small)
|
)
|
||||||
Toggle(
|
Text(
|
||||||
LocalizedStringKey("Adjust candidate window location according to current node length"),
|
"Tadokoro candidate window shows 4 rows / columns by default, providing similar experiences from Microsoft New Phonetic IME and macOS bult-in Chinese IME (since macOS 10.9). However, for some users who have presbyopia, they prefer giant candidate font sizes, resulting a concern that multiple rows / columns of candidates can make the candidate window looks too big, hence this option. Note that this option will be dismissed if the typing context is vertical, forcing the candidates to be shown in only one row / column. Only one reverse-lookup result can be made available in single row / column mode due to reduced candidate window size.".localized
|
||||||
isOn: $useDynamicCandidateWindowOrigin
|
)
|
||||||
).controlSize(.small).disabled(useRearCursorMode)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Misc Settings:".localized, bottomDivider: true) {
|
if !candidateWindowShowOnlyOneLine {
|
||||||
|
Toggle(
|
||||||
|
LocalizedStringKey("Always expand candidate window panel"),
|
||||||
|
isOn: $alwaysExpandCandidateWindow
|
||||||
|
)
|
||||||
|
.disabled(candidateWindowShowOnlyOneLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: (header: Text("Misc Settings:"))
|
||||||
|
|
||||||
|
Section {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Show available reverse-lookup results in candidate window"),
|
LocalizedStringKey("Show available reverse-lookup results in candidate window"),
|
||||||
isOn: $showReverseLookupInCandidateUI
|
isOn: $showReverseLookupInCandidateUI
|
||||||
|
@ -155,7 +149,9 @@ struct VwrPrefPaneCandidates: View {
|
||||||
Text(
|
Text(
|
||||||
"The lookup results are supplied by the CIN cassette module.".localized
|
"The lookup results are supplied by the CIN cassette module.".localized
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
|
}
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Always use fixed listing order in candidate window"),
|
LocalizedStringKey("Always use fixed listing order in candidate window"),
|
||||||
isOn: $useFixedCandidateOrderOnSelection
|
isOn: $useFixedCandidateOrderOnSelection
|
||||||
|
@ -165,7 +161,9 @@ struct VwrPrefPaneCandidates: View {
|
||||||
"This will stop user override model from affecting how candidates get sorted."
|
"This will stop user override model from affecting how candidates get sorted."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
|
}
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Consolidate the context on confirming candidate selection"),
|
LocalizedStringKey("Consolidate the context on confirming candidate selection"),
|
||||||
isOn: $consolidateContextOnCandidateSelection
|
isOn: $consolidateContextOnCandidateSelection
|
||||||
|
@ -173,22 +171,27 @@ struct VwrPrefPaneCandidates: View {
|
||||||
Text(
|
Text(
|
||||||
"For example: When typing “章太炎” and you want to override the “太” with “泰”, and the raw operation index range [1,2) which bounds are cutting the current node “章太炎” in range [0,3). If having lack of the pre-consolidation process, this word will become something like “張泰言” after the candidate selection. Only if we enable this consolidation, this word will become “章泰炎” which is the expected result that the context is kept as-is.".localized
|
"For example: When typing “章太炎” and you want to override the “太” with “泰”, and the raw operation index range [1,2) which bounds are cutting the current node “章太炎” in range [0,3). If having lack of the pre-consolidation process, this word will become something like “張泰言” after the candidate selection. Only if we enable this consolidation, this word will become “章泰炎” which is the expected result that the context is kept as-is.".localized
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Experimental:".localized) {
|
}
|
||||||
|
|
||||||
|
// MARK: (header: Text("Experimental:"))
|
||||||
|
|
||||||
|
let imkEOSNoticeButton = Button("Where's IMK Candidate Window?") {
|
||||||
|
if let window = CtlPrefUIShared.sharedWindow {
|
||||||
|
let title = "The End of Support for IMK Candidate Window"
|
||||||
|
let explanation = "1) Only macOS has IMKCandidates. Since it relies on a dedicated ObjC Bridging Header to expose necessary internal APIs to work, it hinders vChewing from completely modularized for multi-platform support.\n\n2) IMKCandidates is buggy. It is not likely to be completely fixed by Apple, and its devs are not allowed to talk about it to non-Apple individuals. That's why we have had enough with IMKCandidates. It is likely the reason why Apple had never used IMKCandidates in their official InputMethodKit sample projects (as of August 2023)."
|
||||||
|
window.callAlert(title: title.localized, text: explanation.localized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Section(footer: imkEOSNoticeButton) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Enable mouse wheel support for Tadokoro Candidate Window"),
|
LocalizedStringKey("Enable mouse wheel support for Tadokoro Candidate Window"),
|
||||||
isOn: $enableMouseScrollingForTDKCandidatesCocoa
|
isOn: $enableMouseScrollingForTDKCandidatesCocoa
|
||||||
)
|
)
|
||||||
Button("Where's IMK Candidate Window?") {
|
|
||||||
if let window = CtlPrefUIShared.sharedWindow {
|
|
||||||
let title = "The End of Support for IMK Candidate Window"
|
|
||||||
let explanation = "1) Only macOS has IMKCandidates. Since it relies on a ObjC Bridging Header to expose necessary internal APIs to work, it hinders vChewing from completely modularized for multi-platform support.\n\n2) IMKCandidates is buggy. It is not likely to be completely fixed by Apple, and its devs are not allowed to talk about it to non-Apple individuals. That's why we have had enough with IMKCandidates. It is likely the reason why Apple had never used IMKCandidates in their official InputMethodKit sample projects (as of August 2023)."
|
|
||||||
window.callAlert(title: title.localized, text: explanation.localized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}.formStyled().frame(width: CtlPrefUIShared.formWidth)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
||||||
}
|
}
|
||||||
|
@ -213,6 +216,9 @@ private struct VwrPrefPaneCandidates_SelectionKeys: View {
|
||||||
// MARK: - Main View
|
// MARK: - Main View
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
Text("Selection Keys:")
|
||||||
|
Spacer()
|
||||||
ComboBox(
|
ComboBox(
|
||||||
items: CandidateKey.suggestions,
|
items: CandidateKey.suggestions,
|
||||||
text: $candidateKeys.onChange {
|
text: $candidateKeys.onChange {
|
||||||
|
@ -231,12 +237,79 @@ private struct VwrPrefPaneCandidates_SelectionKeys: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).frame(width: 180)
|
).frame(width: 180)
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
"Choose or hit Enter to confim your prefered keys for selecting candidates.".localized
|
"Choose or hit Enter to confim your prefered keys for selecting candidates.".localized
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "This will also affect the row / column capacity of the candidate window.".localized
|
+ "This will also affect the row / column capacity of the candidate window.".localized
|
||||||
)
|
)
|
||||||
|
.settingsDescription()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
// MARK: - NSComboBox
|
||||||
|
|
||||||
|
// Ref: https://stackoverflow.com/a/71058587/4162914
|
||||||
|
// License: https://creativecommons.org/licenses/by-sa/4.0/
|
||||||
|
|
||||||
|
@available(macOS 10.15, *)
|
||||||
|
public struct ComboBox: NSViewRepresentable {
|
||||||
|
// The items that will show up in the pop-up menu:
|
||||||
|
public 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 public var text: String
|
||||||
|
|
||||||
|
public func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Coordinator: NSObject, NSComboBoxDelegate {
|
||||||
|
public var parent: ComboBox
|
||||||
|
public var ignoreSelectionChanges = false
|
||||||
|
|
||||||
|
public init(_ parent: ComboBox) {
|
||||||
|
self.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
public func comboBoxSelectionDidChange(_ notification: Notification) {
|
||||||
|
if !ignoreSelectionChanges,
|
||||||
|
let box: NSComboBox = notification.object as? NSComboBox,
|
||||||
|
let newStringValue: String = box.objectValueOfSelectedItem as? String
|
||||||
|
{
|
||||||
|
parent.text = newStringValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func controlTextDidEndEditing(_ obj: Notification) {
|
||||||
|
if let textField = obj.object as? NSTextField {
|
||||||
|
parent.text = textField.stringValue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,16 +39,15 @@ struct VwrPrefPaneCassette: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
SSPreferences.Settings.Container(contentWidth: CtlPrefUIShared.contentWidth) {
|
Form {
|
||||||
// MARK: - Cassette Data Path Management
|
// MARK: - Cassette Data Path Management
|
||||||
|
|
||||||
SSPreferences.Settings.Section(bottomDivider: true) {
|
Section {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Text(LocalizedStringKey("Choose your desired cassette file path. Will be omitted if invalid."))
|
Text(LocalizedStringKey("Choose your desired cassette file path. Will be omitted if invalid."))
|
||||||
HStack {
|
HStack(spacing: 3) {
|
||||||
PathControl(pathDroppable: $cassettePath) { pathControl in
|
PathControl(pathDroppable: $cassettePath) { pathControl in
|
||||||
pathControl.allowedTypes = ["cin2", "cin", "vcin"]
|
pathControl.allowedTypes = ["cin2", "cin", "vcin"]
|
||||||
pathControl.heightAnchor.constraint(equalToConstant: 20).isActive = true
|
|
||||||
pathControl.widthAnchor.constraint(equalToConstant: CtlPrefUIShared.maxDescriptionWidth).isActive = true
|
|
||||||
pathControl.placeholderString = "Please drag the desired target from Finder to this place.".localized
|
pathControl.placeholderString = "Please drag the desired target from Finder to this place.".localized
|
||||||
} acceptDrop: { pathControl, info in
|
} acceptDrop: { pathControl, info in
|
||||||
let urls = info.draggingPasteboard.readObjects(forClasses: [NSURL.self])
|
let urls = info.draggingPasteboard.readObjects(forClasses: [NSURL.self])
|
||||||
|
@ -119,6 +118,13 @@ struct VwrPrefPaneCassette: View {
|
||||||
Text("×")
|
Text("×")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Spacer()
|
||||||
|
Text(
|
||||||
|
LocalizedStringKey(
|
||||||
|
"Cassette mode is similar to the CIN support of the Yahoo Kimo IME, allowing users to use their own CIN tables to implement their stroked-based input schema (e.g. Wubi, Cangjie, Boshiamy, etc.) as a plan-B in vChewing IME. However, since vChewing won't compromise its phonabet input mode experience for this cassette mode, users might not feel comfortable enough comparing to their experiences with RIME (recommended) or OpenVanilla (deprecated)."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.settingsDescription()
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Enable cassette mode, suppressing phonabet input"),
|
LocalizedStringKey("Enable cassette mode, suppressing phonabet input"),
|
||||||
isOn: $cassetteEnabled.onChange {
|
isOn: $cassetteEnabled.onChange {
|
||||||
|
@ -139,22 +145,18 @@ struct VwrPrefPaneCassette: View {
|
||||||
}
|
}
|
||||||
LMMgr.setCassetteEnabled(cassetteEnabled)
|
LMMgr.setCassetteEnabled(cassetteEnabled)
|
||||||
}
|
}
|
||||||
).controlSize(.small)
|
|
||||||
Text(
|
|
||||||
LocalizedStringKey(
|
|
||||||
"Cassette mode is similar to the CIN support of the Yahoo Kimo IME, allowing users to use their own CIN tables to implement their stroked-based input schema (e.g. Wubi, Cangjie, Boshiamy, etc.) as a plan-B in vChewing IME. However, since vChewing won't compromise its phonabet input mode experience for this cassette mode, users might not feel comfortable enough comparing to their experiences with RIME (recommended) or OpenVanilla (deprecated)."
|
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Something Else
|
// MARK: - Something Else
|
||||||
|
|
||||||
SSPreferences.Settings.Section {
|
Section {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Auto-composite when the longest possible key is formed"),
|
LocalizedStringKey("Auto-composite when the longest possible key is formed"),
|
||||||
isOn: $autoCompositeWithLongestPossibleCassetteKey
|
isOn: $autoCompositeWithLongestPossibleCassetteKey
|
||||||
)
|
)
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Show translated strokes in composition buffer"),
|
LocalizedStringKey("Show translated strokes in composition buffer"),
|
||||||
isOn: $showTranslatedStrokesInCompositionBuffer
|
isOn: $showTranslatedStrokesInCompositionBuffer
|
||||||
|
@ -164,9 +166,11 @@ struct VwrPrefPaneCassette: View {
|
||||||
"All strokes in the composition buffer will be shown as ASCII keyboard characters unless this option is enabled. Stroke is definable in the “%keyname” section of the CIN file."
|
"All strokes in the composition buffer will be shown as ASCII keyboard characters unless this option is enabled. Stroke is definable in the “%keyname” section of the CIN file."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
|
}
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Picker(
|
Picker(
|
||||||
"",
|
"Chinese Conversion:",
|
||||||
selection: $forceCassetteChineseConversion
|
selection: $forceCassetteChineseConversion
|
||||||
) {
|
) {
|
||||||
Text(LocalizedStringKey("Disable forced conversion for cassette outputs")).tag(0)
|
Text(LocalizedStringKey("Disable forced conversion for cassette outputs")).tag(0)
|
||||||
|
@ -174,16 +178,15 @@ struct VwrPrefPaneCassette: View {
|
||||||
Text(LocalizedStringKey("Only enforce conversion in Simplified Chinese mode")).tag(2)
|
Text(LocalizedStringKey("Only enforce conversion in Simplified Chinese mode")).tag(2)
|
||||||
Text(LocalizedStringKey("Only enforce conversion in Traditional Chinese mode")).tag(3)
|
Text(LocalizedStringKey("Only enforce conversion in Traditional Chinese mode")).tag(3)
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
|
||||||
.pickerStyle(RadioGroupPickerStyle())
|
|
||||||
Text(
|
Text(
|
||||||
LocalizedStringKey(
|
LocalizedStringKey(
|
||||||
"This conversion only affects the cassette module, converting typed contents to either Simplified Chinese or Traditional Chinese in accordance with this setting and your current input mode."
|
"This conversion only affects the cassette module, converting typed contents to either Simplified Chinese or Traditional Chinese in accordance with this setting and your current input mode."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.formStyled().frame(width: CtlPrefUIShared.formWidth)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,19 +32,11 @@ struct VwrPrefPaneDevZone: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
SSPreferences.Settings.Container(contentWidth: CtlPrefUIShared.contentWidth) {
|
Form {
|
||||||
SSPreferences.Settings.Section(bottomDivider: true) {
|
Section(header: Text(
|
||||||
Text(
|
"Warning: This page is for testing future features. \nFeatures listed here may not work as expected."
|
||||||
LocalizedStringKey(
|
), footer: Text("Some previous options are moved to other tabs.".localized).settingsDescription()) {
|
||||||
"Warning: This page is for testing future features. \nFeatures listed here may not work as expected.")
|
VStack(alignment: .leading) {
|
||||||
)
|
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
Divider()
|
|
||||||
HStack {
|
|
||||||
Text("Some previous options are moved to other tabs.".localized)
|
|
||||||
|
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
|
||||||
}
|
|
||||||
Toggle(
|
Toggle(
|
||||||
UserDef.kSecurityHardenedCompositionBuffer.metaData?.shortTitle?.localized ?? "",
|
UserDef.kSecurityHardenedCompositionBuffer.metaData?.shortTitle?.localized ?? "",
|
||||||
isOn: $securityHardenedCompositionBuffer
|
isOn: $securityHardenedCompositionBuffer
|
||||||
|
@ -52,7 +44,9 @@ struct VwrPrefPaneDevZone: View {
|
||||||
Text(
|
Text(
|
||||||
UserDef.kSecurityHardenedCompositionBuffer.metaData?.description?.localized ?? ""
|
UserDef.kSecurityHardenedCompositionBuffer.metaData?.description?.localized ?? ""
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
|
}
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
"Disable segmented thick underline in marking mode for managed clients".localized,
|
"Disable segmented thick underline in marking mode for managed clients".localized,
|
||||||
isOn: $disableSegmentedThickUnderlineInMarkingModeForManagedClients
|
isOn: $disableSegmentedThickUnderlineInMarkingModeForManagedClients
|
||||||
|
@ -60,9 +54,10 @@ struct VwrPrefPaneDevZone: View {
|
||||||
Text(
|
Text(
|
||||||
"Some clients with web-based front UI may have issues rendering segmented thick underlines drawn by their implemented “setMarkedText()”. This option stops the input method from delivering segmented thick underlines to “client().setMarkedText()”. Note that segmented thick underlines are only used in marking mode, unless the client itself misimplements the IMKTextInput method “setMarkedText()”. This option only affects the inline composition buffer.".localized
|
"Some clients with web-based front UI may have issues rendering segmented thick underlines drawn by their implemented “setMarkedText()”. This option stops the input method from delivering segmented thick underlines to “client().setMarkedText()”. Note that segmented thick underlines are only used in marking mode, unless the client itself misimplements the IMKTextInput method “setMarkedText()”. This option only affects the inline composition buffer.".localized
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.formStyled().frame(width: CtlPrefUIShared.formWidth)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,17 +55,17 @@ struct VwrPrefPaneDictionary: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
SSPreferences.Settings.Container(contentWidth: CtlPrefUIShared.contentWidth) {
|
Form {
|
||||||
// MARK: - User Data Folder Path Management
|
// MARK: - User Data Folder Path Management
|
||||||
|
|
||||||
SSPreferences.Settings.Section(bottomDivider: true) {
|
Section {
|
||||||
Group {
|
Group {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Text(LocalizedStringKey("Choose your desired user data folder path. Will be omitted if invalid."))
|
Text(LocalizedStringKey("Choose your desired user data folder path. Will be omitted if invalid."))
|
||||||
HStack {
|
HStack(spacing: 3) {
|
||||||
PathControl(pathDroppable: $userDataFolderSpecified) { pathControl in
|
PathControl(pathDroppable: $userDataFolderSpecified) { pathControl in
|
||||||
pathControl.allowedTypes = ["public.folder", "public.directory"]
|
pathControl.allowedTypes = ["public.folder", "public.directory"]
|
||||||
pathControl.heightAnchor.constraint(equalToConstant: 20).isActive = true
|
pathControl.placeholderString = "Please drag the desired target from Finder to this place.".localized
|
||||||
pathControl.widthAnchor.constraint(equalToConstant: CtlPrefUIShared.maxDescriptionWidth).isActive = true
|
|
||||||
} acceptDrop: { pathControl, info in
|
} acceptDrop: { pathControl, info in
|
||||||
let urls = info.draggingPasteboard.readObjects(forClasses: [NSURL.self])
|
let urls = info.draggingPasteboard.readObjects(forClasses: [NSURL.self])
|
||||||
guard let url = urls?.first as? URL else { return false }
|
guard let url = urls?.first as? URL else { return false }
|
||||||
|
@ -136,13 +136,20 @@ struct VwrPrefPaneDictionary: View {
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Text("...")
|
Text("...")
|
||||||
}
|
}.frame(minWidth: 25)
|
||||||
Button {
|
Button {
|
||||||
userDataFolderSpecified = fdrUserDataDefault
|
userDataFolderSpecified = fdrUserDataDefault
|
||||||
} label: {
|
} label: {
|
||||||
Text("↻")
|
Text("↻")
|
||||||
|
}.frame(minWidth: 25)
|
||||||
}
|
}
|
||||||
}
|
Spacer()
|
||||||
|
Text(
|
||||||
|
LocalizedStringKey(
|
||||||
|
"Due to security concerns, we don't consider implementing anything related to shell script execution here. An input method doing this without implementing App Sandbox will definitely have system-wide vulnerabilities, considering that its related UserDefaults are easily tamperable to execute malicious shell scripts. vChewing is designed to be invulnerable from this kind of attack. Also, official releases of vChewing are Sandboxed."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.settingsDescription()
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Automatically reload user data files if changes detected"),
|
LocalizedStringKey("Automatically reload user data files if changes detected"),
|
||||||
isOn: $shouldAutoReloadUserDataFiles.onChange {
|
isOn: $shouldAutoReloadUserDataFiles.onChange {
|
||||||
|
@ -150,16 +157,13 @@ struct VwrPrefPaneDictionary: View {
|
||||||
LMMgr.initUserLangModels()
|
LMMgr.initUserLangModels()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).controlSize(.small)
|
|
||||||
Text(
|
|
||||||
LocalizedStringKey(
|
|
||||||
"Due to security concerns, we don't consider implementing anything related to shell script execution here. An input method doing this without implementing App Sandbox will definitely have system-wide vulnerabilities, considering that its related UserDefaults are easily tamperable to execute malicious shell scripts. vChewing is designed to be invulnerable from this kind of attack. Also, official releases of vChewing are Sandboxed."
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
|
||||||
}
|
}
|
||||||
Divider()
|
}
|
||||||
Group {
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Read external factory dictionary files if possible"),
|
LocalizedStringKey("Read external factory dictionary files if possible"),
|
||||||
isOn: $useExternalFactoryDict.onChange {
|
isOn: $useExternalFactoryDict.onChange {
|
||||||
|
@ -171,7 +175,8 @@ struct VwrPrefPaneDictionary: View {
|
||||||
"This will use the plist files deployed by the “make install” command from libvChewing-Data if possible."
|
"This will use the plist files deployed by the “make install” command from libvChewing-Data if possible."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
|
}
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Only load factory language models if needed"),
|
LocalizedStringKey("Only load factory language models if needed"),
|
||||||
isOn: $onlyLoadFactoryLangModelsIfNeeded.onChange {
|
isOn: $onlyLoadFactoryLangModelsIfNeeded.onChange {
|
||||||
|
@ -190,6 +195,7 @@ struct VwrPrefPaneDictionary: View {
|
||||||
LMMgr.setSymbolEnabled(symbolInputEnabled)
|
LMMgr.setSymbolEnabled(symbolInputEnabled)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Applying typing suggestions from half-life user override model"),
|
LocalizedStringKey("Applying typing suggestions from half-life user override model"),
|
||||||
isOn: $fetchSuggestionsFromUserOverrideModel
|
isOn: $fetchSuggestionsFromUserOverrideModel
|
||||||
|
@ -197,7 +203,9 @@ struct VwrPrefPaneDictionary: View {
|
||||||
Text(
|
Text(
|
||||||
"The user override model only possesses memories temporarily. Each memory record gradually becomes ineffective within approximately less than 6 days. You can erase all memory records through the input method menu.".localized
|
"The user override model only possesses memories temporarily. Each memory record gradually becomes ineffective within approximately less than 6 days. You can erase all memory records through the input method menu.".localized
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
|
}
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Enable phrase replacement table"),
|
LocalizedStringKey("Enable phrase replacement table"),
|
||||||
isOn: $phraseReplacementEnabled.onChange {
|
isOn: $phraseReplacementEnabled.onChange {
|
||||||
|
@ -208,10 +216,11 @@ struct VwrPrefPaneDictionary: View {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Text("This will batch-replace specified candidates.".localized)
|
Text("This will batch-replace specified candidates.".localized)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
Divider()
|
}
|
||||||
Group {
|
Section {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Allow boosting / excluding a candidate of single kanji when marking"),
|
LocalizedStringKey("Allow boosting / excluding a candidate of single kanji when marking"),
|
||||||
isOn: $allowBoostingSingleKanjiAsUserPhrase
|
isOn: $allowBoostingSingleKanjiAsUserPhrase
|
||||||
|
@ -221,10 +230,10 @@ struct VwrPrefPaneDictionary: View {
|
||||||
"⚠︎ This may hinder the walking algorithm from giving appropriate results."
|
"⚠︎ This may hinder the walking algorithm from giving appropriate results."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.formStyled().frame(width: CtlPrefUIShared.formWidth)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,8 +71,8 @@ struct VwrPrefPaneGeneral: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
SSPreferences.Settings.Container(contentWidth: CtlPrefUIShared.contentWidth) {
|
Form {
|
||||||
SSPreferences.Settings.Section {
|
VStack(alignment: .leading) {
|
||||||
Text(
|
Text(
|
||||||
"\u{2022} "
|
"\u{2022} "
|
||||||
+ NSLocalizedString(
|
+ NSLocalizedString(
|
||||||
|
@ -84,28 +84,21 @@ struct VwrPrefPaneGeneral: View {
|
||||||
comment: ""
|
comment: ""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth).padding(.bottom, NSFont.systemFontSize)
|
.settingsDescription()
|
||||||
}
|
Picker("UI Language:", selection: $appleLanguageTag) {
|
||||||
SSPreferences.Settings.Section(title: "UI Language:".localized) {
|
|
||||||
HStack {
|
|
||||||
Picker(
|
|
||||||
LocalizedStringKey("Follow OS settings"),
|
|
||||||
selection: $appleLanguageTag
|
|
||||||
) {
|
|
||||||
Text(LocalizedStringKey("Follow OS settings")).tag("auto")
|
Text(LocalizedStringKey("Follow OS settings")).tag("auto")
|
||||||
Text(LocalizedStringKey("Simplified Chinese")).tag("zh-Hans")
|
Text(LocalizedStringKey("Simplified Chinese")).tag("zh-Hans")
|
||||||
Text(LocalizedStringKey("Traditional Chinese")).tag("zh-Hant")
|
Text(LocalizedStringKey("Traditional Chinese")).tag("zh-Hant")
|
||||||
Text(LocalizedStringKey("Japanese")).tag("ja")
|
Text(LocalizedStringKey("Japanese")).tag("ja")
|
||||||
Text(LocalizedStringKey("English")).tag("en")
|
Text(LocalizedStringKey("English")).tag("en")
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
|
||||||
.frame(width: 180.0)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
Text(LocalizedStringKey("Change user interface language (will reboot the IME)."))
|
Text(LocalizedStringKey("Change user interface language (will reboot the IME)."))
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(label: { Text(LocalizedStringKey("Typing Settings:")) }) {
|
|
||||||
|
// MARK: (header: Text("Typing Settings:"))
|
||||||
|
|
||||||
|
Section {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Automatically correct reading combinations when typing"),
|
LocalizedStringKey("Automatically correct reading combinations when typing"),
|
||||||
isOn: $autoCorrectReadingCombination
|
isOn: $autoCorrectReadingCombination
|
||||||
|
@ -122,6 +115,7 @@ struct VwrPrefPaneGeneral: View {
|
||||||
LocalizedStringKey("Also use “\\” or “¥” key for Hanin Keyboard Symbol Input"),
|
LocalizedStringKey("Also use “\\” or “¥” key for Hanin Keyboard Symbol Input"),
|
||||||
isOn: $classicHaninKeyboardSymbolModeShortcutEnabled
|
isOn: $classicHaninKeyboardSymbolModeShortcutEnabled
|
||||||
)
|
)
|
||||||
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Emulating select-candidate-per-character mode"),
|
LocalizedStringKey("Emulating select-candidate-per-character mode"),
|
||||||
isOn: $useSCPCTypingMode.onChange {
|
isOn: $useSCPCTypingMode.onChange {
|
||||||
|
@ -130,7 +124,8 @@ struct VwrPrefPaneGeneral: View {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Text(LocalizedStringKey("An accommodation for elder computer users."))
|
Text(LocalizedStringKey("An accommodation for elder computer users."))
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
|
}
|
||||||
if Date.isTodayTheDate(from: 0401) {
|
if Date.isTodayTheDate(from: 0401) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Stop farting (when typed phonetic combination is invalid, etc.)"),
|
LocalizedStringKey("Stop farting (when typed phonetic combination is invalid, etc.)"),
|
||||||
|
@ -168,19 +163,20 @@ struct VwrPrefPaneGeneral: View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(label: { Text(LocalizedStringKey("Misc Settings:")).controlSize(.small) }) {
|
|
||||||
|
// MARK: (header: Text("Misc Settings:"))
|
||||||
|
|
||||||
|
Section {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Check for updates automatically"),
|
LocalizedStringKey("Check for updates automatically"),
|
||||||
isOn: $checkUpdateAutomatically
|
isOn: $checkUpdateAutomatically
|
||||||
)
|
)
|
||||||
.controlSize(.small)
|
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Debug Mode"),
|
LocalizedStringKey("Debug Mode"),
|
||||||
isOn: $isDebugModeEnabled
|
isOn: $isDebugModeEnabled
|
||||||
)
|
)
|
||||||
.controlSize(.small)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}.formStyled().frame(width: CtlPrefUIShared.formWidth)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,11 @@ struct VwrPrefPaneKeyboard: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
SSPreferences.Settings.Container(contentWidth: CtlPrefUIShared.contentWidth) {
|
Form {
|
||||||
SSPreferences.Settings.Section(title: "Quick Setup:".localized) {
|
Section {
|
||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
|
Text("Quick Setup:")
|
||||||
|
Spacer()
|
||||||
Button {
|
Button {
|
||||||
keyboardParser = 0
|
keyboardParser = 0
|
||||||
basicKeyboardLayout = "com.apple.keylayout.ZhuyinBopomofo"
|
basicKeyboardLayout = "com.apple.keylayout.ZhuyinBopomofo"
|
||||||
|
@ -58,12 +60,10 @@ struct VwrPrefPaneKeyboard: View {
|
||||||
} label: {
|
} label: {
|
||||||
Text("↻A")
|
Text("↻A")
|
||||||
}
|
}
|
||||||
}.controlSize(.small)
|
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Phonetic Parser:".localized) {
|
VStack(alignment: .leading) {
|
||||||
HStack {
|
|
||||||
Picker(
|
Picker(
|
||||||
"",
|
"Phonetic Parser:",
|
||||||
selection: $keyboardParser
|
selection: $keyboardParser
|
||||||
) {
|
) {
|
||||||
ForEach(KeyboardParser.allCases, id: \.self) { item in
|
ForEach(KeyboardParser.allCases, id: \.self) { item in
|
||||||
|
@ -71,17 +71,15 @@ struct VwrPrefPaneKeyboard: View {
|
||||||
Text(item.localizedMenuName).tag(item.rawValue)
|
Text(item.localizedMenuName).tag(item.rawValue)
|
||||||
}.id(UUID())
|
}.id(UUID())
|
||||||
}
|
}
|
||||||
.fixedSize()
|
Spacer()
|
||||||
.labelsHidden()
|
|
||||||
Spacer(minLength: NSFont.systemFontSize)
|
|
||||||
}
|
|
||||||
Text(NSLocalizedString("Choose the phonetic layout for Mandarin parser.", comment: ""))
|
Text(NSLocalizedString("Choose the phonetic layout for Mandarin parser.", comment: ""))
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Basic Keyboard Layout:".localized) {
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
HStack {
|
HStack {
|
||||||
Picker(
|
Picker(
|
||||||
"",
|
"Basic Keyboard Layout:",
|
||||||
selection: $basicKeyboardLayout
|
selection: $basicKeyboardLayout
|
||||||
) {
|
) {
|
||||||
ForEach(0 ... (IMKHelper.allowedBasicLayoutsAsTISInputSources.count - 1), id: \.self) { id in
|
ForEach(0 ... (IMKHelper.allowedBasicLayoutsAsTISInputSources.count - 1), id: \.self) { id in
|
||||||
|
@ -93,21 +91,20 @@ struct VwrPrefPaneKeyboard: View {
|
||||||
}
|
}
|
||||||
}.id(UUID())
|
}.id(UUID())
|
||||||
}
|
}
|
||||||
.labelsHidden().frame(width: 290)
|
|
||||||
Spacer(minLength: NSFont.systemFontSize)
|
|
||||||
}
|
}
|
||||||
|
Spacer()
|
||||||
Text(
|
Text(
|
||||||
NSLocalizedString(
|
NSLocalizedString(
|
||||||
"Choose the macOS-level basic keyboard layout. Non-QWERTY alphanumerical keyboard layouts are for Pinyin parser only. This option will only affect the appearance of the on-screen-keyboard if the current Mandarin parser is neither (any) pinyin nor dynamically reparsable with different western keyboard layouts (like Eten 26, Hsu, etc.).",
|
"Choose the macOS-level basic keyboard layout. Non-QWERTY alphanumerical keyboard layouts are for Pinyin parser only. This option will only affect the appearance of the on-screen-keyboard if the current Mandarin parser is neither (any) pinyin nor dynamically reparsable with different western keyboard layouts (like Eten 26, Hsu, etc.).",
|
||||||
comment: ""
|
comment: ""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Alphanumerical Layout:".localized) {
|
VStack(alignment: .leading) {
|
||||||
HStack {
|
HStack {
|
||||||
Picker(
|
Picker(
|
||||||
"",
|
"Alphanumerical Layout:",
|
||||||
selection: $alphanumericalKeyboardLayout
|
selection: $alphanumericalKeyboardLayout
|
||||||
) {
|
) {
|
||||||
ForEach(0 ... (IMKHelper.allowedAlphanumericalTISInputSources.count - 1), id: \.self) { id in
|
ForEach(0 ... (IMKHelper.allowedAlphanumericalTISInputSources.count - 1), id: \.self) { id in
|
||||||
|
@ -115,21 +112,21 @@ struct VwrPrefPaneKeyboard: View {
|
||||||
Text(theEntry.vChewingLocalizedName).tag(theEntry.identifier)
|
Text(theEntry.vChewingLocalizedName).tag(theEntry.identifier)
|
||||||
}.id(UUID())
|
}.id(UUID())
|
||||||
}
|
}
|
||||||
.labelsHidden().frame(width: 290)
|
|
||||||
Spacer(minLength: NSFont.systemFontSize)
|
|
||||||
}
|
}
|
||||||
|
Spacer()
|
||||||
Text(
|
Text(
|
||||||
NSLocalizedString(
|
NSLocalizedString(
|
||||||
"Choose the macOS-level alphanumerical keyboard layout. This setting is for Shift-toggled alphanumerical mode only.",
|
"Choose the macOS-level alphanumerical keyboard layout. This setting is for Shift-toggled alphanumerical mode only.",
|
||||||
comment: ""
|
comment: ""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
.settingsDescription()
|
||||||
}
|
}
|
||||||
SSPreferences.Settings.Section(title: "Keyboard Shortcuts:".localized) {
|
}
|
||||||
|
Section(header: Text("Keyboard Shortcuts:")) {
|
||||||
VwrPrefPaneKeyboard_KeyboardShortcuts()
|
VwrPrefPaneKeyboard_KeyboardShortcuts()
|
||||||
}
|
}
|
||||||
}
|
}.formStyled().frame(width: CtlPrefUIShared.formWidth)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
||||||
}
|
}
|
||||||
|
@ -195,6 +192,7 @@ private struct VwrPrefPaneKeyboard_KeyboardShortcuts: View {
|
||||||
isOn: $usingHotKeyRevLookup
|
isOn: $usingHotKeyRevLookup
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Divider()
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("JIS Shinjitai Output"),
|
LocalizedStringKey("JIS Shinjitai Output"),
|
||||||
|
@ -227,70 +225,3 @@ struct VwrPrefPaneKeyboard_Previews: PreviewProvider {
|
||||||
VwrPrefPaneKeyboard()
|
VwrPrefPaneKeyboard()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - NSComboBox
|
|
||||||
|
|
||||||
// Ref: https://stackoverflow.com/a/71058587/4162914
|
|
||||||
// License: https://creativecommons.org/licenses/by-sa/4.0/
|
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
|
||||||
public struct ComboBox: NSViewRepresentable {
|
|
||||||
// The items that will show up in the pop-up menu:
|
|
||||||
public 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 public var text: String
|
|
||||||
|
|
||||||
public func makeCoordinator() -> Coordinator {
|
|
||||||
Coordinator(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
public 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
|
|
||||||
}
|
|
||||||
|
|
||||||
public 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
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Coordinator: NSObject, NSComboBoxDelegate {
|
|
||||||
public var parent: ComboBox
|
|
||||||
public var ignoreSelectionChanges = false
|
|
||||||
|
|
||||||
public init(_ parent: ComboBox) {
|
|
||||||
self.parent = parent
|
|
||||||
}
|
|
||||||
|
|
||||||
public func comboBoxSelectionDidChange(_ notification: Notification) {
|
|
||||||
if !ignoreSelectionChanges,
|
|
||||||
let box: NSComboBox = notification.object as? NSComboBox,
|
|
||||||
let newStringValue: String = box.objectValueOfSelectedItem as? String
|
|
||||||
{
|
|
||||||
parent.text = newStringValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func controlTextDidEndEditing(_ obj: Notification) {
|
|
||||||
if let textField = obj.object as? NSTextField {
|
|
||||||
parent.text = textField.stringValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -35,9 +35,8 @@ struct VwrPrefPaneOutput: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
SSPreferences.Settings.Container(contentWidth: CtlPrefUIShared.contentWidth) {
|
Form {
|
||||||
SSPreferences.Settings.Section(title: "Output Settings:".localized, bottomDivider: true) {
|
Section {
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Auto-convert traditional Chinese glyphs to KangXi characters"),
|
LocalizedStringKey("Auto-convert traditional Chinese glyphs to KangXi characters"),
|
||||||
isOn: $chineseConversionEnabled.onChange {
|
isOn: $chineseConversionEnabled.onChange {
|
||||||
|
@ -63,20 +62,19 @@ struct VwrPrefPaneOutput: View {
|
||||||
isOn: $trimUnfinishedReadingsOnCommit
|
isOn: $trimUnfinishedReadingsOnCommit
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
Section(header: Text("Experimental:")) {
|
||||||
SSPreferences.Settings.Section(title: "Experimental:".localized) {
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Harden vertical punctuations during vertical typing (not recommended)"),
|
LocalizedStringKey("Harden vertical punctuations during vertical typing (not recommended)"),
|
||||||
isOn: $hardenVerticalPunctuations
|
isOn: $hardenVerticalPunctuations
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
LocalizedStringKey(
|
|
||||||
"⚠︎ This feature is useful ONLY WHEN the font you are using doesn't support dynamic vertical punctuations. However, typed vertical punctuations will always shown as vertical punctuations EVEN IF your editor has changed the typing direction to horizontal."
|
"⚠︎ This feature is useful ONLY WHEN the font you are using doesn't support dynamic vertical punctuations. However, typed vertical punctuations will always shown as vertical punctuations EVEN IF your editor has changed the typing direction to horizontal."
|
||||||
)
|
)
|
||||||
)
|
.settingsDescription()
|
||||||
.preferenceDescription(maxWidth: CtlPrefUIShared.maxDescriptionWidth)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.formStyled().frame(width: CtlPrefUIShared.formWidth)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,16 @@ struct VwrPrefPanePhrases: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack {
|
VStack {
|
||||||
|
GroupBox {
|
||||||
VwrPhraseEditorUI(delegate: LMMgr.shared, window: CtlPrefUIShared.sharedWindow)
|
VwrPhraseEditorUI(delegate: LMMgr.shared, window: CtlPrefUIShared.sharedWindow)
|
||||||
.frame(width: CtlPrefUIShared.contentWidth + 28, height: 445)
|
.padding(4)
|
||||||
Spacer()
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: 440)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.padding(4)
|
||||||
.padding()
|
.padding()
|
||||||
|
.frame(width: CtlPrefUIShared.formWidth)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
.frame(maxHeight: CtlPrefUIShared.contentMaxHeight)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue