From 950288c682c8ff4d6f77208b841c9296fc56ed67 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 13 Sep 2022 13:38:58 +0800 Subject: [PATCH 1/9] ctlIME // Secure the inline composition buffer when PCB is on. --- .../Modules/ControllerModules/ctlInputMethod_Core.swift | 5 +++-- .../ControllerModules/ctlInputMethod_HandleDisplay.swift | 9 +++++++++ .../ControllerModules/ctlInputMethod_HandleStates.swift | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Core.swift b/Source/Modules/ControllerModules/ctlInputMethod_Core.swift index 231442c5..880c3dc2 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_Core.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_Core.swift @@ -81,11 +81,12 @@ class ctlInputMethod: IMKInputController { keyHandler.composer.clear() handle(state: keyHandler.buildInputtingState) } - if state.hasComposition { + let isSecureMode = mgrPrefs.clientsIMKTextInputIncapable.contains(clientBundleIdentifier) + if state.hasComposition, !isSecureMode { /// 將傳回的新狀態交給調度函式。 handle(state: IMEState.ofCommitting(textToCommit: state.displayedText)) } - handle(state: IMEState.ofEmpty()) + handle(state: isSecureMode ? IMEState.ofAbortion() : IMEState.ofEmpty()) } // MARK: - IMKInputController 方法 diff --git a/Source/Modules/ControllerModules/ctlInputMethod_HandleDisplay.swift b/Source/Modules/ControllerModules/ctlInputMethod_HandleDisplay.swift index 7e7d25b2..a3d64931 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_HandleDisplay.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_HandleDisplay.swift @@ -13,6 +13,15 @@ import Cocoa // MARK: - Tooltip Display and Candidate Display Methods extension ctlInputMethod { + // 有些 App 會濫用內文組字區的內容來預測使用者的輸入行為。 + // 對此類 App 有疑慮者,可以將這類 App 登記到客體管理員當中。 + // 這樣,不但強制使用(限制讀音 20 個的)浮動組字窗,而且內文組字區只會顯示一個空格。 + var attributedStringSecured: (NSAttributedString, NSRange) { + mgrPrefs.clientsIMKTextInputIncapable.contains(clientBundleIdentifier) + ? (state.data.attributedStringPlaceholder, NSRange(location: 0, length: 0)) + : (state.attributedString, NSRange(state.data.u16MarkedRange)) + } + func lineHeightRect(zeroCursor: Bool = false) -> NSRect { var lineHeightRect = NSRect.seniorTheBeast guard let client = client() else { diff --git a/Source/Modules/ControllerModules/ctlInputMethod_HandleStates.swift b/Source/Modules/ControllerModules/ctlInputMethod_HandleStates.swift index 12423441..410ce31f 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_HandleStates.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_HandleStates.swift @@ -96,7 +96,7 @@ extension ctlInputMethod { guard let client = client() else { return } if state.type == .ofAssociates { client.setMarkedText( - state.attributedString, selectionRange: NSRange(location: 0, length: 0), + state.data.attributedStringPlaceholder, selectionRange: NSRange(location: 0, length: 0), replacementRange: NSRange(location: NSNotFound, length: NSNotFound) ) return @@ -107,7 +107,7 @@ extension ctlInputMethod { /// 是 0 且取代範圍(replacementRange)為「NSNotFound」罷了。 /// 也就是說,內文組字區該在哪裡出現,得由客體軟體來作主。 client.setMarkedText( - state.attributedString, selectionRange: NSRange(state.data.u16MarkedRange), + attributedStringSecured.0, selectionRange: attributedStringSecured.1, replacementRange: NSRange(location: NSNotFound, length: NSNotFound) ) return From 4f3ab28d6ac7e0180afca060ee0fa38a409e01ba Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 13 Sep 2022 14:02:10 +0800 Subject: [PATCH 2/9] ClientMgr // Update UI terms describing additional features. --- Source/Resources/Base.lproj/Localizable.strings | 2 +- Source/Resources/en.lproj/Localizable.strings | 2 +- Source/Resources/ja.lproj/Localizable.strings | 2 +- Source/Resources/zh-Hans.lproj/Localizable.strings | 2 +- Source/Resources/zh-Hant.lproj/Localizable.strings | 2 +- Source/WindowControllers/ctlClientListMgr.swift | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Resources/Base.lproj/Localizable.strings b/Source/Resources/Base.lproj/Localizable.strings index 7fae8b95..dd952131 100644 --- a/Source/Resources/Base.lproj/Localizable.strings +++ b/Source/Resources/Base.lproj/Localizable.strings @@ -3,7 +3,7 @@ "One record per line. Use Option+Enter to break lines.\nBlank lines will be dismissed." = "One record per line. Use Option+Enter to break lines.\nBlank lines will be dismissed."; "Just Select" = "Just Select"; "Client Manager" = "Client Manager"; -"Please manage the list of IMKTextInput-incompatible clients here. Clients listed here will trigger vChewing's built-in popup composition buffer window with maximum 20 reading counts holdable." = "Please manage the list of IMKTextInput-incompatible clients here. Clients listed here will trigger vChewing's built-in popup composition buffer window with maximum 20 reading counts holdable."; +"Please manage the list of those clients here which are: 1) IMKTextInput-incompatible; 2) suspected from abusing the contents of the inline composition buffer. Clients listed here will only use popup composition buffer with maximum 20 reading counts holdable." = "Please manage the list of those clients here which are: 1) IMKTextInput-incompatible; 2) suspected from abusing the contents of the inline composition buffer. Clients listed here will only use popup composition buffer with maximum 20 reading counts holdable."; "Add Client" = "Add Client"; "Remove Selected" = "Remove Selected"; "Choose the target application bundle." = "Choose the target application bundle."; diff --git a/Source/Resources/en.lproj/Localizable.strings b/Source/Resources/en.lproj/Localizable.strings index 7fae8b95..dd952131 100644 --- a/Source/Resources/en.lproj/Localizable.strings +++ b/Source/Resources/en.lproj/Localizable.strings @@ -3,7 +3,7 @@ "One record per line. Use Option+Enter to break lines.\nBlank lines will be dismissed." = "One record per line. Use Option+Enter to break lines.\nBlank lines will be dismissed."; "Just Select" = "Just Select"; "Client Manager" = "Client Manager"; -"Please manage the list of IMKTextInput-incompatible clients here. Clients listed here will trigger vChewing's built-in popup composition buffer window with maximum 20 reading counts holdable." = "Please manage the list of IMKTextInput-incompatible clients here. Clients listed here will trigger vChewing's built-in popup composition buffer window with maximum 20 reading counts holdable."; +"Please manage the list of those clients here which are: 1) IMKTextInput-incompatible; 2) suspected from abusing the contents of the inline composition buffer. Clients listed here will only use popup composition buffer with maximum 20 reading counts holdable." = "Please manage the list of those clients here which are: 1) IMKTextInput-incompatible; 2) suspected from abusing the contents of the inline composition buffer. Clients listed here will only use popup composition buffer with maximum 20 reading counts holdable."; "Add Client" = "Add Client"; "Remove Selected" = "Remove Selected"; "Choose the target application bundle." = "Choose the target application bundle."; diff --git a/Source/Resources/ja.lproj/Localizable.strings b/Source/Resources/ja.lproj/Localizable.strings index fecd955d..454f6e6e 100644 --- a/Source/Resources/ja.lproj/Localizable.strings +++ b/Source/Resources/ja.lproj/Localizable.strings @@ -3,7 +3,7 @@ "One record per line. Use Option+Enter to break lines.\nBlank lines will be dismissed." = "毎行は1つ記録とみなす。Option+Enter キーで改行。\n空白の記録値は無視される。"; "Just Select" = "直接に選ぶ"; "Client Manager" = "客体アプリの管理"; -"Please manage the list of IMKTextInput-incompatible clients here. Clients listed here will trigger vChewing's built-in popup composition buffer window with maximum 20 reading counts holdable." = "IMKTextInput 議定規約に従っていない客体アプリはここでご登録ください。登録済みのアプリは客体アプリ(文字入力を受くアプリ)とされた時に、威注音入力アプリは「吹き出し入力緩衝列ウィンドウ」と「緩衝列容量制限」を起用し、その容量制限は最大限音読み20箇とする。"; +"Please manage the list of those clients here which are: 1) IMKTextInput-incompatible; 2) suspected from abusing the contents of the inline composition buffer. Clients listed here will only use popup composition buffer with maximum 20 reading counts holdable." = "下記の種類の客体アプリはここでご登録ください:1)IMKTextInput 議定規約に反するもの;2)文脈内入力緩衝列の内容の不正利用の疑いのあるもの。登録済みのアプリには、威注音入力アプリは「吹き出し入力緩衝列ウィンドウ」と「緩衝列容量制限」を起用し、容量制限は最大限音読み20箇とする。"; "Add Client" = "入れる"; "Remove Selected" = "外す"; "Choose the target application bundle." = "登録したいアプリのバンドルのお選びを。"; diff --git a/Source/Resources/zh-Hans.lproj/Localizable.strings b/Source/Resources/zh-Hans.lproj/Localizable.strings index 52754d5c..5c983e90 100644 --- a/Source/Resources/zh-Hans.lproj/Localizable.strings +++ b/Source/Resources/zh-Hans.lproj/Localizable.strings @@ -3,7 +3,7 @@ "One record per line. Use Option+Enter to break lines.\nBlank lines will be dismissed." = "每行一笔记录,用 Option+Enter 换行。\n空白值会被无视。"; "Just Select" = "直接选取"; "Client Manager" = "管理客体应用"; -"Please manage the list of IMKTextInput-incompatible clients here. Clients listed here will trigger vChewing's built-in popup composition buffer window with maximum 20 reading counts holdable." = "请在此管理那些不遵守 IMKTextInput 协定的客体应用。威注音输入法对于任何位列在此的客体应用均启用浮动组字窗、且对组字区内容设定容量上限(最多二十个读音)。"; +"Please manage the list of those clients here which are: 1) IMKTextInput-incompatible; 2) suspected from abusing the contents of the inline composition buffer. Clients listed here will only use popup composition buffer with maximum 20 reading counts holdable." = "请在此管理这两类客体应用:1) 不遵守 IMKTextInput 协定;2)有滥用内文组字区的嫌疑。威注音输入法对于任何位列在此的客体应用均启用浮动组字窗、且对组字区内容设定容量上限(最多二十个读音)。"; "Add Client" = "登记新客体"; "Remove Selected" = "移除所选条目"; "Choose the target application bundle." = "请选择要登记的应用程式的封包。"; diff --git a/Source/Resources/zh-Hant.lproj/Localizable.strings b/Source/Resources/zh-Hant.lproj/Localizable.strings index e681e0d5..8fc5a76b 100644 --- a/Source/Resources/zh-Hant.lproj/Localizable.strings +++ b/Source/Resources/zh-Hant.lproj/Localizable.strings @@ -3,7 +3,7 @@ "One record per line. Use Option+Enter to break lines.\nBlank lines will be dismissed." = "每行一筆記錄,用 Option+Enter 換行。\n空白值會被無視。"; "Just Select" = "直接選取"; "Client Manager" = "管理客體應用"; -"Please manage the list of IMKTextInput-incompatible clients here. Clients listed here will trigger vChewing's built-in popup composition buffer window with maximum 20 reading counts holdable." = "請在此管理那些不遵守 IMKTextInput 協定的客體應用。威注音輸入法對於任何位列在此的客體應用均啟用浮動組字窗、且對組字區內容設定容量上限(最多二十個讀音)。"; +"Please manage the list of those clients here which are: 1) IMKTextInput-incompatible; 2) suspected from abusing the contents of the inline composition buffer. Clients listed here will only use popup composition buffer with maximum 20 reading counts holdable." = "請在此管理這兩類客體應用:1) 不遵守 IMKTextInput 協定;2)有濫用內文組字區的嫌疑。威注音輸入法對於任何位列在此的客體應用均啟用浮動組字窗、且對組字區內容設定容量上限(最多二十個讀音)。"; "Add Client" = "登記新客體"; "Remove Selected" = "移除所選條目"; "Choose the target application bundle." = "請選擇要登記的應用程式的封包。"; diff --git a/Source/WindowControllers/ctlClientListMgr.swift b/Source/WindowControllers/ctlClientListMgr.swift index 23cda7a1..fd1cfb7c 100644 --- a/Source/WindowControllers/ctlClientListMgr.swift +++ b/Source/WindowControllers/ctlClientListMgr.swift @@ -165,7 +165,7 @@ extension ctlClientListMgr { guard let window = window else { return } window.title = NSLocalizedString("Client Manager", comment: "") lblClientMgrWindow.stringValue = NSLocalizedString( - "Please manage the list of IMKTextInput-incompatible clients here. Clients listed here will trigger vChewing's built-in popup composition buffer window with maximum 20 reading counts holdable.", + "Please manage the list of those clients here which are: 1) IMKTextInput-incompatible; 2) suspected from abusing the contents of the inline composition buffer. Clients listed here will only use popup composition buffer with maximum 20 reading counts holdable.", comment: "" ) btnAddClient.title = NSLocalizedString("Add Client", comment: "") From 36b45c0a2da715256af25a6c16ce07fb160a3bbf Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 13 Sep 2022 14:38:29 +0800 Subject: [PATCH 3/9] NSAttributedTextView // Better area calculation, etc. --- .../NSAttributedTextView.swift | 95 +++++++++++++++---- .../UIModules/TooltipUI/ctlTooltip.swift | 27 +----- 2 files changed, 81 insertions(+), 41 deletions(-) diff --git a/Source/3rdParty/NSAttributedTextView/NSAttributedTextView.swift b/Source/3rdParty/NSAttributedTextView/NSAttributedTextView.swift index f8c8be20..b75bd429 100644 --- a/Source/3rdParty/NSAttributedTextView/NSAttributedTextView.swift +++ b/Source/3rdParty/NSAttributedTextView/NSAttributedTextView.swift @@ -60,7 +60,7 @@ public class NSAttributedTextView: NSView { } } - public var attributedStringValue: NSAttributedString { + public func attributedStringValue(areaCalculation: Bool = false) -> NSAttributedString { var newAttributes = attributes let isVertical: Bool = !(direction == .horizontal) newAttributes[.verticalGlyphForm] = isVertical @@ -71,23 +71,14 @@ public class NSAttributedTextView: NSView { newStyle.minimumLineHeight = fontSize * 1.1 } newAttributes[.paragraphStyle] = newStyle - var text: String = text ?? "" - if !(direction == .horizontal) { - text = text.replacingOccurrences(of: "˙", with: "・") - text = text.replacingOccurrences(of: "\u{A0}", with: " ") - text = text.replacingOccurrences(of: "+", with: "") - text = text.replacingOccurrences(of: "Shift", with: "⇧") - text = text.replacingOccurrences(of: "Control", with: "⌃") - text = text.replacingOccurrences(of: "Enter", with: "⏎") - text = text.replacingOccurrences(of: "Command", with: "⌘") - text = text.replacingOccurrences(of: "Delete", with: "⌦") - text = text.replacingOccurrences(of: "BackSpace", with: "⌫") - text = text.replacingOccurrences(of: "SHIFT", with: "⇧") - text = text.replacingOccurrences(of: "CONTROL", with: "⌃") - text = text.replacingOccurrences(of: "ENTER", with: "⏎") - text = text.replacingOccurrences(of: "COMMAND", with: "⌘") - text = text.replacingOccurrences(of: "DELETE", with: "⌦") - text = text.replacingOccurrences(of: "BACKSPACE", with: "⌫") + var text: String = text ?? text ?? "" + if areaCalculation { + text = text.replacingOccurrences( + of: "[^\n]", + with: "國", + options: .regularExpression, + range: text.range(of: text) + ) } let attributedText = NSMutableAttributedString(string: text, attributes: newAttributes) return attributedText @@ -109,10 +100,33 @@ public class NSAttributedTextView: NSView { private var ctFrame: CTFrame? private(set) var currentRect: NSRect? + @discardableResult public func shrinkFrame() -> NSRect { + let attrString: NSAttributedString = { + switch direction { + case .horizontal: return attributedStringValue() + default: return attributedStringValue(areaCalculation: true) + } + }() + var rect = attrString.boundingRect( + with: NSSize(width: 1600.0, height: 1600.0), + options: [.usesLineFragmentOrigin, .usesFontLeading, .usesDeviceMetrics] + ) + rect.size.height *= 1.03 + rect.size.height = max(rect.size.height, NSFont.systemFontSize * 1.1) + rect.size.height = ceil(rect.size.height) + rect.size.width *= 1.03 + rect.size.width = max(rect.size.width, NSFont.systemFontSize * 1.05) + rect.size.width = ceil(rect.size.width) + if direction != .horizontal { + rect = .init(x: rect.minX, y: rect.minY, width: rect.height, height: rect.width) + } + return rect + } + override public func draw(_ rect: CGRect) { let context = NSGraphicsContext.current?.cgContext guard let context = context else { return } - let setter = CTFramesetterCreateWithAttributedString(attributedStringValue) + let setter = CTFramesetterCreateWithAttributedString(attributedStringValue()) let path = CGPath(rect: rect, transform: nil) let theCTFrameProgression: CTFrameProgression = { switch direction { @@ -134,3 +148,46 @@ public class NSAttributedTextView: NSView { CTFrameDraw(newFrame, context) } } + +public class NSAttributedTooltipTextView: NSAttributedTextView { + override public func attributedStringValue(areaCalculation: Bool = false) -> NSAttributedString { + var newAttributes = attributes + let isVertical: Bool = !(direction == .horizontal) + newAttributes[.verticalGlyphForm] = isVertical + let newStyle: NSMutableParagraphStyle = newAttributes[.paragraphStyle] as! NSMutableParagraphStyle + if #available(macOS 10.13, *) { + newStyle.lineSpacing = isVertical ? (fontSize / -2) : fontSize * 0.1 + newStyle.maximumLineHeight = fontSize * 1.1 + newStyle.minimumLineHeight = fontSize * 1.1 + } + newAttributes[.paragraphStyle] = newStyle + var text: String = text ?? text ?? "" + if !(direction == .horizontal) { + text = text.replacingOccurrences(of: "˙", with: "・") + text = text.replacingOccurrences(of: "\u{A0}", with: " ") + text = text.replacingOccurrences(of: "+", with: "") + text = text.replacingOccurrences(of: "Shift", with: "⇧") + text = text.replacingOccurrences(of: "Control", with: "⌃") + text = text.replacingOccurrences(of: "Enter", with: "⏎") + text = text.replacingOccurrences(of: "Command", with: "⌘") + text = text.replacingOccurrences(of: "Delete", with: "⌦") + text = text.replacingOccurrences(of: "BackSpace", with: "⌫") + text = text.replacingOccurrences(of: "SHIFT", with: "⇧") + text = text.replacingOccurrences(of: "CONTROL", with: "⌃") + text = text.replacingOccurrences(of: "ENTER", with: "⏎") + text = text.replacingOccurrences(of: "COMMAND", with: "⌘") + text = text.replacingOccurrences(of: "DELETE", with: "⌦") + text = text.replacingOccurrences(of: "BACKSPACE", with: "⌫") + } + if areaCalculation { + text = text.replacingOccurrences( + of: "[^\n]", + with: "國", + options: .regularExpression, + range: text.range(of: text) + ) + } + let attributedText = NSMutableAttributedString(string: text, attributes: newAttributes) + return attributedText + } +} diff --git a/Source/Modules/UIModules/TooltipUI/ctlTooltip.swift b/Source/Modules/UIModules/TooltipUI/ctlTooltip.swift index 1e298c05..bf5236ed 100644 --- a/Source/Modules/UIModules/TooltipUI/ctlTooltip.swift +++ b/Source/Modules/UIModules/TooltipUI/ctlTooltip.swift @@ -18,7 +18,7 @@ public class ctlTooltip: NSWindowController { case prompt } - private var messageText: NSAttributedTextView + private var messageText: NSAttributedTooltipTextView private var tooltip: String = "" { didSet { messageText.text = tooltip.isEmpty ? nil : tooltip @@ -26,7 +26,7 @@ public class ctlTooltip: NSWindowController { } } - public var direction: NSAttributedTextView.writingDirection = .horizontal { + public var direction: NSAttributedTooltipTextView.writingDirection = .horizontal { didSet { if #unavailable(macOS 10.13) { direction = .horizontal } if Bundle.main.preferredLocalizations[0] == "en" { direction = .horizontal } @@ -43,7 +43,7 @@ public class ctlTooltip: NSWindowController { panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 2) panel.hasShadow = true panel.backgroundColor = NSColor.controlBackgroundColor - messageText = NSAttributedTextView() + messageText = NSAttributedTooltipTextView() messageText.backgroundColor = NSColor.controlBackgroundColor messageText.textColor = NSColor.textColor panel.contentView?.addSubview(messageText) @@ -58,7 +58,7 @@ public class ctlTooltip: NSWindowController { public func show( tooltip: String = "", at point: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: CGFloat, - direction: NSAttributedTextView.writingDirection = .horizontal + direction: NSAttributedTooltipTextView.writingDirection = .horizontal ) { self.direction = direction self.tooltip = tooltip @@ -147,24 +147,7 @@ public class ctlTooltip: NSWindowController { } private func adjustSize() { - let attrString = messageText.attributedStringValue - var rect = attrString.boundingRect( - with: NSSize(width: 1600.0, height: 1600.0), - options: [.usesLineFragmentOrigin, .usesFontLeading, .usesDeviceMetrics] - ) - if direction != .horizontal { - rect = .init(x: rect.minX, y: rect.minY, width: rect.height, height: rect.width) - rect.size.height += NSFont.systemFontSize - rect.size.width *= 1.03 - rect.size.width = max(rect.size.width, NSFont.systemFontSize * 1.05) - rect.size.width = ceil(rect.size.width) - } else { - rect = .init(x: rect.minX, y: rect.minY, width: rect.width, height: rect.height) - rect.size.width += NSFont.systemFontSize - rect.size.height *= 1.03 - rect.size.height = max(rect.size.height, NSFont.systemFontSize * 1.05) - rect.size.height = ceil(rect.size.height) - } + var rect = messageText.shrinkFrame() var bigRect = rect bigRect.size.width += NSFont.systemFontSize bigRect.size.height += NSFont.systemFontSize From df57b81bfdc8f521e738a51aa92c46744843e976 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 13 Sep 2022 16:48:24 +0800 Subject: [PATCH 4/9] PCB // Add vertical display property. --- .../ctlInputMethod_HandleStates.swift | 1 + .../ctlPopupCompositionBuffer.swift | 110 ++++++++++++++---- 2 files changed, 87 insertions(+), 24 deletions(-) diff --git a/Source/Modules/ControllerModules/ctlInputMethod_HandleStates.swift b/Source/Modules/ControllerModules/ctlInputMethod_HandleStates.swift index 410ce31f..0896bd8c 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_HandleStates.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_HandleStates.swift @@ -83,6 +83,7 @@ extension ctlInputMethod { } // 浮動組字窗的顯示判定 if state.hasComposition, mgrPrefs.clientsIMKTextInputIncapable.contains(clientBundleIdentifier) { + ctlInputMethod.popupCompositionBuffer.isTypingDirectionVertical = isVerticalTyping ctlInputMethod.popupCompositionBuffer.show( state: state, at: lineHeightRect(zeroCursor: true).origin ) diff --git a/Source/Modules/UIModules/PopupCompositionBufferUI/ctlPopupCompositionBuffer.swift b/Source/Modules/UIModules/PopupCompositionBufferUI/ctlPopupCompositionBuffer.swift index 6e3c8d52..a63bef92 100644 --- a/Source/Modules/UIModules/PopupCompositionBufferUI/ctlPopupCompositionBuffer.swift +++ b/Source/Modules/UIModules/PopupCompositionBufferUI/ctlPopupCompositionBuffer.swift @@ -9,6 +9,14 @@ import Cocoa public class ctlPopupCompositionBuffer: NSWindowController { + public var isTypingDirectionVertical: Bool = false { + didSet { + if #unavailable(macOS 10.14) { + isTypingDirectionVertical = false + } + } + } + private var messageTextField: NSTextField private var textShown: NSAttributedString = .init(string: "") { didSet { @@ -18,18 +26,15 @@ public class ctlPopupCompositionBuffer: NSWindowController { } public init() { - let transparentVisualEffect = NSVisualEffectView() - transparentVisualEffect.blendingMode = .behindWindow - transparentVisualEffect.state = .active let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0) let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] let panel = NSPanel( contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false ) - panel.contentView = transparentVisualEffect panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) panel.hasShadow = true - panel.backgroundColor = NSColor.clear + panel.backgroundColor = NSColor.controlBackgroundColor + panel.styleMask = .fullSizeContentView messageTextField = NSTextField() messageTextField.isEditable = false @@ -40,6 +45,7 @@ public class ctlPopupCompositionBuffer: NSWindowController { messageTextField.backgroundColor = NSColor.clear messageTextField.font = .systemFont(ofSize: 18) panel.contentView?.addSubview(messageTextField) + panel.contentView?.wantsLayer = true super.init(window: panel) } @@ -53,31 +59,74 @@ public class ctlPopupCompositionBuffer: NSWindowController { hide() return } - // 在這個視窗內的下畫線繪製方法就得單獨設計了。 + let attrString: NSMutableAttributedString = .init(string: state.data.displayedTextConverted) - attrString.setAttributes( - [ - .backgroundColor: NSColor.alternateSelectedControlColor, - .foregroundColor: NSColor.alternateSelectedControlTextColor, + let verticalAttributes: [NSAttributedString.Key: Any] = [ + .verticalGlyphForm: true, + .paragraphStyle: { + let newStyle = NSMutableParagraphStyle() + if #available(macOS 10.13, *) { + let fontSize = messageTextField.font?.pointSize ?? 18 + newStyle.lineSpacing = fontSize / -3 + newStyle.maximumLineHeight = fontSize + newStyle.minimumLineHeight = fontSize + } + return newStyle + }(), + ] + + if isTypingDirectionVertical { + attrString.setAttributes( + verticalAttributes, range: NSRange(location: 0, length: attrString.string.utf16.count) + ) + } + + let markerAttributes: [NSAttributedString.Key: Any] = { + var result: [NSAttributedString.Key: Any] = [ + .backgroundColor: IME.isDarkMode ? NSColor.systemRed : NSColor.systemYellow, .markedClauseSegment: 0, - ], + ] + if isTypingDirectionVertical { + result[.paragraphStyle] = verticalAttributes[.paragraphStyle] + result[.verticalGlyphForm] = true + } + return result + }() + + // 在這個視窗內的下畫線繪製方法就得單獨設計了。 + attrString.setAttributes( + markerAttributes, range: NSRange( location: state.data.u16MarkedRange.lowerBound, length: state.data.u16MarkedRange.upperBound - state.data.u16MarkedRange.lowerBound ) ) - let attrCursor = NSMutableAttributedString(string: "_") - if #available(macOS 10.13, *) { - attrCursor.setAttributes( - [ - .kern: -18, - .baselineOffset: -2, - .markedClauseSegment: 1, - ], - range: NSRange(location: 0, length: attrCursor.string.utf16.count) - ) - } + + let cursorAttributes: [NSAttributedString.Key: Any] = { + var result: [NSAttributedString.Key: Any] = [ + .kern: -18, + .foregroundColor: NSColor.textColor, + ] + if isTypingDirectionVertical { + result[.paragraphStyle] = verticalAttributes[.paragraphStyle] + result[.verticalGlyphForm] = true + result[.baselineOffset] = 3 + } else { + result[.baselineOffset] = -2 + } + if #unavailable(macOS 10.13) { + result[.kern] = 0 + result[.baselineOffset] = 0 + } + return result + }() + + let attrCursor: NSAttributedString = + isTypingDirectionVertical + ? NSMutableAttributedString(string: "▔", attributes: cursorAttributes) + : NSMutableAttributedString(string: "_", attributes: cursorAttributes) attrString.insert(attrCursor, at: state.data.u16Cursor) + textShown = attrString messageTextField.maximumNumberOfLines = 1 if let editor = messageTextField.currentEditor() { @@ -105,7 +154,11 @@ public class ctlPopupCompositionBuffer: NSWindowController { adjustedPoint.y = min(max(adjustedPoint.y, screenFrame.minY + windowSize.height), screenFrame.maxY) adjustedPoint.x = min(max(adjustedPoint.x, screenFrame.minX), screenFrame.maxX - windowSize.width) - window.setFrameOrigin(adjustedPoint) + if isTypingDirectionVertical { + window.setFrameTopLeftPoint(adjustedPoint) + } else { + window.setFrameOrigin(adjustedPoint) + } } private func adjustSize() { @@ -115,12 +168,21 @@ public class ctlPopupCompositionBuffer: NSWindowController { options: [.usesLineFragmentOrigin, .usesFontLeading] ) rect.size.width = max(rect.size.width, 20 * CGFloat(attrString.string.count)) + 2 - rect.size.height = 22 + rect.size.height *= 1.2 + rect.size.height = max(22, rect.size.height) + if isTypingDirectionVertical { + rect = .init(x: rect.minX, y: rect.minY, width: rect.height, height: rect.width) + } var bigRect = rect bigRect.size.width += NSFont.systemFontSize bigRect.size.height += NSFont.systemFontSize rect.origin.x += ceil(NSFont.systemFontSize / 2) rect.origin.y += ceil(NSFont.systemFontSize / 2) + if isTypingDirectionVertical { + messageTextField.boundsRotation = 90 + } else { + messageTextField.boundsRotation = 0 + } messageTextField.frame = rect window?.setFrame(bigRect, display: true) } From 4db10fb9e3e8ca268913b6d7abf25b0c0c456f35 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 13 Sep 2022 20:18:52 +0800 Subject: [PATCH 5/9] KeyHandler // Fix an issue with length limitation in PCB. --- .../ControllerModules/KeyHandler_HandleInput.swift | 10 ++++++++-- .../Modules/ControllerModules/KeyHandler_States.swift | 5 ++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift index 6af1aee1..414fc743 100644 --- a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift @@ -174,7 +174,10 @@ extension KeyHandler { } else if currentLM.hasUnigramsFor(key: " ") { compositor.insertKey(" ") walk() - let inputting = buildInputtingState + // 一邊吃一邊屙(僅對位列黑名單的 App 用這招限制組字區長度)。 + let textToCommit = commitOverflownComposition + var inputting = buildInputtingState + inputting.textToCommit = textToCommit stateCallback(inputting) } return true @@ -293,7 +296,10 @@ extension KeyHandler { if composer.isEmpty { compositor.insertKey("_punctuation_list") walk() - let inputting = buildInputtingState + // 一邊吃一邊屙(僅對位列黑名單的 App 用這招限制組字區長度)。 + let textToCommit = commitOverflownComposition + var inputting = buildInputtingState + inputting.textToCommit = textToCommit stateCallback(inputting) stateCallback(buildCandidate(state: inputting)) } else { // 不要在注音沒敲完整的情況下叫出統合符號選單。 diff --git a/Source/Modules/ControllerModules/KeyHandler_States.swift b/Source/Modules/ControllerModules/KeyHandler_States.swift index 0f54b7eb..37c2c223 100644 --- a/Source/Modules/ControllerModules/KeyHandler_States.swift +++ b/Source/Modules/ControllerModules/KeyHandler_States.swift @@ -274,7 +274,10 @@ extension KeyHandler { compositor.insertKey(customPunctuation) walk() - let inputting = buildInputtingState + // 一邊吃一邊屙(僅對位列黑名單的 App 用這招限制組字區長度)。 + let textToCommit = commitOverflownComposition + var inputting = buildInputtingState + inputting.textToCommit = textToCommit stateCallback(inputting) // 從這一行之後開始,就是針對逐字選字模式的單獨處理。 From efe4929ad20ae1bd5b3ee008822030dadc4da514 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 13 Sep 2022 21:04:30 +0800 Subject: [PATCH 6/9] ctlIME // DispatchQueue showCheatSheet(). --- Source/Modules/ControllerModules/ctlInputMethod_Menu.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Menu.swift b/Source/Modules/ControllerModules/ctlInputMethod_Menu.swift index 8a6aaf60..847d55fd 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_Menu.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_Menu.swift @@ -208,7 +208,9 @@ extension ctlInputMethod { @objc func showCheatSheet(_: Any?) { guard let url = Bundle.main.url(forResource: "shortcuts", withExtension: "html") else { return } - NSWorkspace.shared.openFile(url.path, withApplication: "Safari") + DispatchQueue.main.async { + NSWorkspace.shared.openFile(url.path, withApplication: "Safari") + } } @objc func showClientListMgr(_: Any?) { From c04ae907e1eda7daea1e734e2a997416c7c545c4 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 13 Sep 2022 21:05:12 +0800 Subject: [PATCH 7/9] IME // DispatchQueue openPhraseFile(). --- Source/Modules/IMEModules/IME.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Modules/IMEModules/IME.swift b/Source/Modules/IMEModules/IME.swift index 327a78c5..ada3262d 100644 --- a/Source/Modules/IMEModules/IME.swift +++ b/Source/Modules/IMEModules/IME.swift @@ -125,7 +125,9 @@ public enum IME { // MARK: - Open a phrase data file. static func openPhraseFile(fromURL url: URL) { - openPhraseFile(userFileAt: url.path) + DispatchQueue.main.async { + openPhraseFile(userFileAt: url.path) + } } static func openPhraseFile(userFileAt path: String) { From 11b28880980cf0de49b56e4d42efe3bc8ce69f60 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 13 Sep 2022 20:54:07 +0800 Subject: [PATCH 8/9] Update Data - 20220913 --- Source/Data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Data b/Source/Data index eeff2e3f..c5b2fe7f 160000 --- a/Source/Data +++ b/Source/Data @@ -1 +1 @@ -Subproject commit eeff2e3f7073873c07b4011717e32430db01227f +Subproject commit c5b2fe7f08971cf2719f553de9de967097bd56b6 From 81ffdd0d8131fe01faf9e987c55c1dccf1a6814b Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 13 Sep 2022 20:54:41 +0800 Subject: [PATCH 9/9] Bump version to 2.6.1 Build 2610. --- Update-Info.plist | 4 ++-- vChewing.pkgproj | 2 +- vChewing.xcodeproj/project.pbxproj | 32 +++++++++++++++--------------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Update-Info.plist b/Update-Info.plist index 38292138..653bb0ac 100644 --- a/Update-Info.plist +++ b/Update-Info.plist @@ -3,9 +3,9 @@ CFBundleShortVersionString - 2.6.0 + 2.6.1 CFBundleVersion - 2602 + 2610 UpdateInfoEndpoint https://gitee.com/vchewing/vChewing-macOS/raw/main/Update-Info.plist UpdateInfoSite diff --git a/vChewing.pkgproj b/vChewing.pkgproj index 3672605f..2d496215 100644 --- a/vChewing.pkgproj +++ b/vChewing.pkgproj @@ -726,7 +726,7 @@ USE_HFS+_COMPRESSION VERSION - 2.6.0 + 2.6.1 TYPE 0 diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 419795f1..4efbc018 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -1483,7 +1483,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2602; + CURRENT_PROJECT_VERSION = 2610; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -1493,7 +1493,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 2.6.0; + MARKETING_VERSION = 2.6.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewingTests; @@ -1522,13 +1522,13 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2602; + CURRENT_PROJECT_VERSION = 2610; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 2.6.0; + MARKETING_VERSION = 2.6.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewingTests; @@ -1560,7 +1560,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2602; + CURRENT_PROJECT_VERSION = 2610; DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1582,7 +1582,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 2.6.0; + MARKETING_VERSION = 2.6.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor; @@ -1612,7 +1612,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2602; + CURRENT_PROJECT_VERSION = 2610; DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_NS_ASSERTIONS = NO; @@ -1630,7 +1630,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 2.6.0; + MARKETING_VERSION = 2.6.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor; @@ -1746,7 +1746,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2602; + CURRENT_PROJECT_VERSION = 2610; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; @@ -1775,7 +1775,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 2.6.0; + MARKETING_VERSION = 2.6.1; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1805,7 +1805,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2602; + CURRENT_PROJECT_VERSION = 2610; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; @@ -1828,7 +1828,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 2.6.0; + MARKETING_VERSION = 2.6.1; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1852,7 +1852,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2602; + CURRENT_PROJECT_VERSION = 2610; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; @@ -1873,7 +1873,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 2.6.0; + MARKETING_VERSION = 2.6.1; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingInstaller; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1896,7 +1896,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2602; + CURRENT_PROJECT_VERSION = 2610; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; @@ -1911,7 +1911,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 2.6.0; + MARKETING_VERSION = 2.6.1; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingInstaller; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "";