diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidateCellDataExtension.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidateCellDataExtension.swift new file mode 100644 index 00000000..4016374c --- /dev/null +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidateCellDataExtension.swift @@ -0,0 +1,37 @@ +// (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// No trademark license is granted to use the trade names, trademarks, service +// marks, or product names of Contributor, except as required to fulfill notice +// requirements defined in MIT License. + +import Shared +import SwiftUI + +@available(macOS 12, *) +extension CandidateCellData { + public var attributedStringForSwiftUI: some View { + var result: some View { + ZStack(alignment: .leading) { + if isSelected { + Color(nsColor: CandidateCellData.highlightBackground).ignoresSafeArea().cornerRadius(6) + } + VStack(spacing: 0) { + HStack(spacing: 4) { + if UserDefaults.standard.bool(forKey: UserDef.kHandleDefaultCandidateFontsByLangIdentifier.rawValue) { + Text(AttributedString(attributedStringHeader)).frame(width: CandidateCellData.unifiedSize / 2) + Text(AttributedString(attributedString)) + } else { + Text(key).font(.system(size: fontSizeKey).monospaced()) + .foregroundColor(.init(nsColor: fontColorKey)).lineLimit(1) + Text(displayedText).font(.system(size: fontSizeCandidate)) + .foregroundColor(.init(nsColor: fontColorCandidate)).lineLimit(1) + } + }.padding(4) + } + }.fixedSize(horizontal: false, vertical: true) + } + return result + } +} diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidatePool.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidatePool.swift index e0a6fd23..a63d4a20 100644 --- a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidatePool.swift +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CandidatePool.swift @@ -12,53 +12,105 @@ import Shared /// 候選字窗會用到的資料池單位。 public class CandidatePool { public let blankCell = CandidateCellData(key: " ", displayedText: " ", isSelected: false) - public var currentRowNumber = 0 - public var maximumLinesPerPage = 3 + public private(set) var candidateDataAll: [CandidateCellData] = [] public private(set) var selectionKeys: String public private(set) var highlightedIndex: Int = 0 - public private(set) var maxColumnCapacity: Int = 6 - public private(set) var candidateDataAll: [CandidateCellData] = [] + + // 下述變數只有橫排選字窗才會用到 + public var currentRowNumber = 0 + public var maximumRowsPerPage = 3 + public private(set) var maxRowCapacity: Int = 6 public private(set) var candidateRows: [[CandidateCellData]] = [] - public var isVerticalLayout: Bool { maxColumnCapacity == 1 } - public var maxColumnWidth: Int { Int(Double(maxColumnCapacity + 3) * 2) * Int(ceil(CandidateCellData.unifiedSize)) } + + // 下述變數只有縱排選字窗才會用到 + public var currentColumnNumber = 0 + public var maximumColumnsPerPage = 3 + public private(set) var maxColumnCapacity: Int = 6 + public private(set) var candidateColumns: [[CandidateCellData]] = [] + + // 動態變數 + public var maxRowWidth: Int { Int(Double(maxRowCapacity + 3) * 2) * Int(ceil(CandidateCellData.unifiedSize)) } public var maxWindowWidth: Double { - ceil(Double(maxColumnCapacity + 3) * 2.7 * ceil(CandidateCellData.unifiedSize) * 1.2) + ceil(Double(maxRowCapacity + 3) * 2.7 * ceil(CandidateCellData.unifiedSize) * 1.2) } - public var rangeForCurrentPage: Range { - currentRowNumber.. { + currentRowNumber.. { 0..<(maximumLinesPerPage - rangeForCurrentPage.count) } + public var rangeForCurrentVerticalPage: Range { + currentColumnNumber.. { + 0..<(maximumRowsPerPage - rangeForCurrentHorizontalPage.count) + } + + public var rangeForLastVerticalPageBlanked: Range { + 0..<(maximumColumnsPerPage - rangeForCurrentVerticalPage.count) + } public enum VerticalDirection { case up case down } - /// 初期化一個候選字池。 + public enum HorizontalDirection { + case left + case right + } + + /// 初期化一個縱排候選字窗專用資料池。 /// - Parameters: /// - candidates: 要塞入的候選字詞陣列。 - /// - columnCapacity: (第一行的最大候選字詞數量, 陣列畫面展開之後的每一行的最大候選字詞數量)。 - public init(candidates: [String], columnCapacity: Int = 6, selectionKeys: String = "123456789", locale: String = "") { + /// - columnCapacity: (第一縱列的最大候選字詞數量, 陣列畫面展開之後的每一縱列的最大候選字詞數量)。 + /// - selectionKeys: 選字鍵。 + /// - locale: 區域編碼。例:「zh-Hans」或「zh-Hant」。 + public init(candidates: [String], columnCapacity: Int, selectionKeys: String = "123456789", locale: String = "") { maxColumnCapacity = max(1, columnCapacity) self.selectionKeys = selectionKeys candidateDataAll = candidates.map { .init(key: "0", displayedText: $0) } var currentColumn: [CandidateCellData] = [] for (i, candidate) in candidateDataAll.enumerated() { candidate.index = i - candidate.whichRow = candidateRows.count - let isOverflown: Bool = currentColumn.map(\.cellLength).reduce(0, +) + candidate.cellLength > maxColumnWidth - if isOverflown || currentColumn.count == maxColumnCapacity, !currentColumn.isEmpty { - candidateRows.append(currentColumn) + candidate.whichColumn = candidateColumns.count + if currentColumn.count == maxColumnCapacity, !currentColumn.isEmpty { + candidateColumns.append(currentColumn) currentColumn.removeAll() - candidate.whichRow += 1 + candidate.whichColumn += 1 } candidate.subIndex = currentColumn.count candidate.locale = locale currentColumn.append(candidate) } - candidateRows.append(currentColumn) + candidateColumns.append(currentColumn) + } + + /// 初期化一個橫排候選字窗專用資料池。 + /// - Parameters: + /// - candidates: 要塞入的候選字詞陣列。 + /// - rowCapacity: (第一橫行的最大候選字詞數量, 陣列畫面展開之後的每一橫行的最大候選字詞數量)。 + /// - selectionKeys: 選字鍵。 + /// - locale: 區域編碼。例:「zh-Hans」或「zh-Hant」。 + public init(candidates: [String], rowCapacity: Int, selectionKeys: String = "123456789", locale: String = "") { + maxRowCapacity = max(1, rowCapacity) + self.selectionKeys = selectionKeys + candidateDataAll = candidates.map { .init(key: "0", displayedText: $0) } + var currentRow: [CandidateCellData] = [] + for (i, candidate) in candidateDataAll.enumerated() { + candidate.index = i + candidate.whichRow = candidateRows.count + let isOverflown: Bool = currentRow.map(\.cellLength).reduce(0, +) + candidate.cellLength > maxRowWidth + if isOverflown || currentRow.count == maxRowCapacity, !currentRow.isEmpty { + candidateRows.append(currentRow) + currentRow.removeAll() + candidate.whichRow += 1 + } + candidate.subIndex = currentRow.count + candidate.locale = locale + currentRow.append(candidate) + } + candidateRows.append(currentRow) } public func selectNewNeighborRow(direction: VerticalDirection) { @@ -70,7 +122,7 @@ public class CandidatePool { if candidateRows.isEmpty { break } let firstRow = candidateRows[0] let newSubIndex = min(currentSubIndex, firstRow.count - 1) - highlight(at: firstRow[newSubIndex].index) + highlightHorizontal(at: firstRow[newSubIndex].index) break } if currentRowNumber >= candidateRows.count - 1 { currentRowNumber = candidateRows.count - 1 } @@ -80,13 +132,13 @@ public class CandidatePool { } let targetRow = candidateRows[currentRowNumber - 1] let newSubIndex = min(result, targetRow.count - 1) - highlight(at: targetRow[newSubIndex].index) + highlightHorizontal(at: targetRow[newSubIndex].index) case .down: if currentRowNumber >= candidateRows.count - 1 { if candidateRows.isEmpty { break } let finalRow = candidateRows[candidateRows.count - 1] let newSubIndex = min(currentSubIndex, finalRow.count - 1) - highlight(at: finalRow[newSubIndex].index) + highlightHorizontal(at: finalRow[newSubIndex].index) break } if candidateRows[currentRowNumber].count != candidateRows[currentRowNumber + 1].count { @@ -95,11 +147,40 @@ public class CandidatePool { } let targetRow = candidateRows[currentRowNumber + 1] let newSubIndex = min(result, targetRow.count - 1) - highlight(at: targetRow[newSubIndex].index) + highlightHorizontal(at: targetRow[newSubIndex].index) } } - public func highlight(at indexSpecified: Int) { + public func selectNewNeighborColumn(direction: HorizontalDirection) { + let currentSubIndex = candidateDataAll[highlightedIndex].subIndex + switch direction { + case .left: + if currentColumnNumber <= 0 { + if candidateColumns.isEmpty { break } + let firstColumn = candidateColumns[0] + let newSubIndex = min(currentSubIndex, firstColumn.count - 1) + highlightVertical(at: firstColumn[newSubIndex].index) + break + } + if currentColumnNumber >= candidateColumns.count - 1 { currentColumnNumber = candidateColumns.count - 1 } + let targetColumn = candidateColumns[currentColumnNumber - 1] + let newSubIndex = min(currentSubIndex, targetColumn.count - 1) + highlightVertical(at: targetColumn[newSubIndex].index) + case .right: + if currentColumnNumber >= candidateColumns.count - 1 { + if candidateColumns.isEmpty { break } + let finalColumn = candidateColumns[candidateColumns.count - 1] + let newSubIndex = min(currentSubIndex, finalColumn.count - 1) + highlightVertical(at: finalColumn[newSubIndex].index) + break + } + let targetColumn = candidateColumns[currentColumnNumber + 1] + let newSubIndex = min(currentSubIndex, targetColumn.count - 1) + highlightVertical(at: targetColumn[newSubIndex].index) + } + } + + public func highlightHorizontal(at indexSpecified: Int) { var indexSpecified = indexSpecified highlightedIndex = indexSpecified if !(0.. Bool { + false + } + + @discardableResult open func showPreviousLine() -> Bool { + false + } + @discardableResult open func highlightNextCandidate() -> Bool { false } diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CtlCandidateTDK.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CtlCandidateTDK.swift deleted file mode 100644 index f611393a..00000000 --- a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/CtlCandidateTDK.swift +++ /dev/null @@ -1,122 +0,0 @@ -// (c) 2022 and onwards The vChewing Project (MIT-NTL License). -// ==================== -// This code is released under the MIT license (SPDX-License-Identifier: MIT) -// ... with NTL restriction stating that: -// No trademark license is granted to use the trade names, trademarks, service -// marks, or product names of Contributor, except as required to fulfill notice -// requirements defined in MIT License. - -import Cocoa -import CocoaExtension -import Shared -import SwiftUI - -@available(macOS 12, *) -public class CtlCandidateTDK: CtlCandidate { - public var thePool: CandidatePool = .init(candidates: []) - public var theView: VwrCandidateTDK { .init(controller: self, thePool: thePool, hint: hint) } - public required init(_ layout: NSUserInterfaceLayoutOrientation = .horizontal) { - var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) - let styleMask: NSWindow.StyleMask = [.nonactivatingPanel] - let panel = NSPanel( - contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false - ) - panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 2) - panel.hasShadow = true - panel.isOpaque = false - panel.backgroundColor = NSColor.clear - - contentRect.origin = NSPoint.zero - - super.init(layout) - window = panel - currentLayout = layout - reloadData() - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override public func reloadData() { - CandidateCellData.highlightBackground = highlightedColor() - CandidateCellData.unifiedSize = candidateFont.pointSize - guard let delegate = delegate else { return } - thePool = .init( - candidates: delegate.candidatePairs(conv: true).map(\.1), - selectionKeys: delegate.selectionKeys, locale: locale - ) - thePool.highlight(at: 0) - updateDisplay() - } - - override open func updateDisplay() { - DispatchQueue.main.async { [self] in - let newView = NSHostingView(rootView: theView.fixedSize()) - let newSize = newView.fittingSize - var newFrame = NSRect.zero - if let window = window { newFrame = window.frame } - newFrame.size = newSize - window?.setFrame(newFrame, display: false) - window?.contentView = NSHostingView(rootView: theView.fixedSize()) - window?.setContentSize(newSize) - } - } - - @discardableResult override public func showNextPage() -> Bool { - for _ in 0.. Bool { - for _ in 0.. Bool { - thePool.selectNewNeighborRow(direction: .down) - updateDisplay() - return true - } - - @discardableResult public func showPreviousLine() -> Bool { - thePool.selectNewNeighborRow(direction: .up) - updateDisplay() - return true - } - - @discardableResult override public func highlightNextCandidate() -> Bool { - thePool.highlight(at: thePool.highlightedIndex + 1) - updateDisplay() - return true - } - - @discardableResult override public func highlightPreviousCandidate() -> Bool { - thePool.highlight(at: thePool.highlightedIndex - 1) - updateDisplay() - return true - } - - override public func candidateIndexAtKeyLabelIndex(_ id: Int) -> Int { - let currentRow = thePool.candidateRows[thePool.currentRowNumber] - let actualID = max(0, min(id, currentRow.count - 1)) - return thePool.candidateRows[thePool.currentRowNumber][actualID].index - } - - override public var selectedCandidateIndex: Int { - get { - thePool.highlightedIndex - } - set { - thePool.highlight(at: newValue) - updateDisplay() - } - } -} diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/CtlCandidateTDK.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/CtlCandidateTDK.swift new file mode 100644 index 00000000..493517cb --- /dev/null +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/CtlCandidateTDK.swift @@ -0,0 +1,209 @@ +// (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// No trademark license is granted to use the trade names, trademarks, service +// marks, or product names of Contributor, except as required to fulfill notice +// requirements defined in MIT License. + +import Cocoa +import CocoaExtension +import Shared +import SwiftUI + +@available(macOS 12, *) +public class CtlCandidateTDK: CtlCandidate { + public var thePoolHorizontal: CandidatePool = .init(candidates: [], rowCapacity: 6) + public var theViewHorizontal: VwrCandidateHorizontal { + .init(controller: self, thePool: thePoolHorizontal, hint: hint) + } + + public var thePoolVertical: CandidatePool = .init(candidates: [], columnCapacity: 6) + public var theViewVertical: VwrCandidateVertical { .init(controller: self, thePool: thePoolVertical, hint: hint) } + public required init(_ layout: NSUserInterfaceLayoutOrientation = .horizontal) { + var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) + let styleMask: NSWindow.StyleMask = [.nonactivatingPanel] + let panel = NSPanel( + contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false + ) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 2) + panel.hasShadow = true + panel.isOpaque = false + panel.backgroundColor = NSColor.clear + + contentRect.origin = NSPoint.zero + + super.init(layout) + window = panel + currentLayout = layout + reloadData() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func reloadData() { + CandidateCellData.highlightBackground = highlightedColor() + CandidateCellData.unifiedSize = candidateFont.pointSize + guard let delegate = delegate else { return } + switch currentLayout { + case .horizontal: + thePoolHorizontal = .init( + candidates: delegate.candidatePairs(conv: true).map(\.1), rowCapacity: 6, + selectionKeys: delegate.selectionKeys, locale: locale + ) + thePoolHorizontal.highlightHorizontal(at: 0) + case .vertical: + thePoolVertical = .init( + candidates: delegate.candidatePairs(conv: true).map(\.1), columnCapacity: 6, + selectionKeys: delegate.selectionKeys, locale: locale + ) + thePoolVertical.highlightVertical(at: 0) + @unknown default: + return + } + updateDisplay() + } + + override open func updateDisplay() { + switch currentLayout { + case .horizontal: + DispatchQueue.main.async { [self] in + let newView = NSHostingView(rootView: theViewHorizontal) + let newSize = newView.fittingSize + window?.contentView = newView + window?.setContentSize(newSize) + } + case .vertical: + DispatchQueue.main.async { [self] in + let newView = NSHostingView(rootView: theViewVertical) + let newSize = newView.fittingSize + window?.contentView = newView + window?.setContentSize(newSize) + } + @unknown default: + return + } + } + + @discardableResult override public func showNextPage() -> Bool { + switch currentLayout { + case .horizontal: + for _ in 0.. Bool { + switch currentLayout { + case .horizontal: + for _ in 0.. Bool { + switch currentLayout { + case .horizontal: + thePoolHorizontal.selectNewNeighborRow(direction: .down) + case .vertical: + thePoolVertical.selectNewNeighborColumn(direction: .right) + @unknown default: + return false + } + updateDisplay() + return true + } + + @discardableResult override public func showPreviousLine() -> Bool { + switch currentLayout { + case .horizontal: + thePoolHorizontal.selectNewNeighborRow(direction: .up) + case .vertical: + thePoolVertical.selectNewNeighborColumn(direction: .left) + @unknown default: + return false + } + updateDisplay() + return true + } + + @discardableResult override public func highlightNextCandidate() -> Bool { + switch currentLayout { + case .horizontal: + thePoolHorizontal.highlightHorizontal(at: thePoolHorizontal.highlightedIndex + 1) + case .vertical: + thePoolVertical.highlightVertical(at: thePoolVertical.highlightedIndex + 1) + @unknown default: + return false + } + updateDisplay() + return true + } + + @discardableResult override public func highlightPreviousCandidate() -> Bool { + switch currentLayout { + case .horizontal: + thePoolHorizontal.highlightHorizontal(at: thePoolHorizontal.highlightedIndex - 1) + case .vertical: + thePoolVertical.highlightVertical(at: thePoolVertical.highlightedIndex - 1) + @unknown default: + return false + } + updateDisplay() + return true + } + + override public func candidateIndexAtKeyLabelIndex(_ id: Int) -> Int { + switch currentLayout { + case .horizontal: + let currentRow = thePoolHorizontal.candidateRows[thePoolHorizontal.currentRowNumber] + let actualID = max(0, min(id, currentRow.count - 1)) + return thePoolHorizontal.candidateRows[thePoolHorizontal.currentRowNumber][actualID].index + case .vertical: + let currentColumn = thePoolVertical.candidateColumns[thePoolVertical.currentColumnNumber] + let actualID = max(0, min(id, currentColumn.count - 1)) + return thePoolVertical.candidateColumns[thePoolVertical.currentColumnNumber][actualID].index + @unknown default: + return 0 + } + } + + override public var selectedCandidateIndex: Int { + get { + switch currentLayout { + case .horizontal: return thePoolHorizontal.highlightedIndex + case .vertical: return thePoolVertical.highlightedIndex + @unknown default: return 0 + } + } + set { + switch currentLayout { + case .horizontal: thePoolHorizontal.highlightHorizontal(at: newValue) + case .vertical: thePoolVertical.highlightVertical(at: newValue) + @unknown default: return + } + updateDisplay() + } + } +} diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/VwrCandidateTDK.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/VwrCandidateHorizontal.swift similarity index 55% rename from Packages/vChewing_CandidateWindow/Sources/CandidateWindow/VwrCandidateTDK.swift rename to Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/VwrCandidateHorizontal.swift index 414d156c..ec36a61e 100644 --- a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/VwrCandidateTDK.swift +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/VwrCandidateHorizontal.swift @@ -13,27 +13,28 @@ import SwiftUI // MARK: - Some useless tests @available(macOS 12, *) -struct CandidatePoolViewUI_Previews: PreviewProvider { +struct CandidatePoolViewUIHorizontal_Previews: PreviewProvider { @State static var testCandidates: [String] = [ "八月中秋山林涼", "八月中秋", "風吹大地", "山林涼", "草枝擺", "八月", "中秋", + "🐂🍺🐂🍺", "🐃🍺", "🐂🍺", "🐃🐂🍺🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺", "🐂🍺", "🐃🍺", "山林", "風吹", "大地", "草枝", "八", "月", "中", "秋", "山", "林", "涼", "風", "吹", "大", "地", "草", "枝", "擺", "八", "月", "中", "秋", "山", "林", "涼", "風", "吹", "大", "地", "草", "枝", "擺", ] static var thePool: CandidatePool { - let result = CandidatePool(candidates: testCandidates, columnCapacity: 6) + let result = CandidatePool(candidates: testCandidates, rowCapacity: 6) // 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。 - result.highlight(at: 14) + result.highlightHorizontal(at: 5) return result } static var previews: some View { - VwrCandidateTDK(controller: .init(.horizontal), thePool: thePool).fixedSize() + VwrCandidateHorizontal(controller: .init(.horizontal), thePool: thePool).fixedSize() } } @available(macOS 12, *) -public struct VwrCandidateTDK: View { +public struct VwrCandidateHorizontal: View { public var controller: CtlCandidateTDK @State public var thePool: CandidatePool @State public var hint: String = "" @@ -52,26 +53,28 @@ public struct VwrCandidateTDK: View { VStack(alignment: .leading, spacing: 0) { ScrollView(.vertical, showsIndicators: true) { VStack(alignment: .leading, spacing: 1.6) { - ForEach(thePool.rangeForCurrentPage, id: \.self) { columnIndex in + ForEach(thePool.rangeForCurrentHorizontalPage, id: \.self) { rowIndex in HStack(spacing: 10) { - ForEach(Array(thePool.candidateRows[columnIndex]), id: \.self) { currentCandidate in + ForEach(Array(thePool.candidateRows[rowIndex]), id: \.self) { currentCandidate in currentCandidate.attributedStringForSwiftUI.fixedSize() - .frame(maxWidth: .infinity, alignment: .topLeading) + .frame( + maxWidth: .infinity, + alignment: .topLeading + ) .contentShape(Rectangle()) .onTapGesture { didSelectCandidateAt(currentCandidate.index) } } - Spacer() }.frame( minWidth: 0, maxWidth: .infinity, alignment: .topLeading - ).id(columnIndex) + ).id(rowIndex) Divider() } - if thePool.maximumLinesPerPage - thePool.rangeForCurrentPage.count > 0 { - ForEach(thePool.rangeForLastPageBlanked, id: \.self) { _ in + if thePool.maximumRowsPerPage - thePool.rangeForCurrentHorizontalPage.count > 0 { + ForEach(thePool.rangeForLastHorizontalPageBlanked, id: \.self) { _ in HStack(spacing: 0) { - thePool.blankCell.attributedStringForSwiftUI.fixedSize() + thePool.blankCell.attributedStringForSwiftUI .frame(maxWidth: .infinity, alignment: .topLeading) .contentShape(Rectangle()) Spacer() @@ -87,41 +90,20 @@ public struct VwrCandidateTDK: View { } .fixedSize(horizontal: false, vertical: true).padding(5) .background(Color(nsColor: NSColor.controlBackgroundColor).ignoresSafeArea()) - HStack(alignment: .bottom) { - Text(hint).font(.system(size: max(CandidateCellData.unifiedSize * 0.7, 11), weight: .bold)).lineLimit(1) - Spacer() - Text(positionLabel).font(.system(size: max(CandidateCellData.unifiedSize * 0.7, 11), weight: .bold)).lineLimit( - 1) - }.padding(6).foregroundColor(.init(nsColor: .controlTextColor)) - .shadow(color: .init(nsColor: .textBackgroundColor), radius: 1) + ZStack(alignment: .leading) { + Color(nsColor: hint.isEmpty ? .windowBackgroundColor : CandidateCellData.highlightBackground).ignoresSafeArea() + HStack(alignment: .bottom) { + Text(hint).font(.system(size: max(CandidateCellData.unifiedSize * 0.7, 11), weight: .bold)).lineLimit(1) + Spacer() + Text(positionLabel).font(.system(size: max(CandidateCellData.unifiedSize * 0.7, 11), weight: .bold)) + .lineLimit( + 1) + } + .padding(6).foregroundColor( + .init(nsColor: hint.isEmpty ? .controlTextColor : .selectedMenuItemTextColor.withAlphaComponent(0.9)) + ) + } } .frame(minWidth: thePool.maxWindowWidth, maxWidth: thePool.maxWindowWidth) } } - -@available(macOS 12, *) -extension CandidateCellData { - public var attributedStringForSwiftUI: some View { - var result: some View { - ZStack(alignment: .leading) { - if isSelected { - Color(nsColor: CandidateCellData.highlightBackground).ignoresSafeArea().cornerRadius(6) - } - VStack(spacing: 0) { - HStack(spacing: 4) { - if UserDefaults.standard.bool(forKey: UserDef.kHandleDefaultCandidateFontsByLangIdentifier.rawValue) { - Text(AttributedString(attributedStringHeader)).frame(width: CandidateCellData.unifiedSize / 2) - Text(AttributedString(attributedString)) - } else { - Text(key).font(.system(size: fontSizeKey).monospaced()) - .foregroundColor(.init(nsColor: fontColorKey)).lineLimit(1) - Text(displayedText).font(.system(size: fontSizeCandidate)) - .foregroundColor(.init(nsColor: fontColorCandidate)).lineLimit(1) - } - }.padding(4) - } - }.fixedSize(horizontal: false, vertical: true) - } - return result - } -} diff --git a/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/VwrCandidateVertical.swift b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/VwrCandidateVertical.swift new file mode 100644 index 00000000..8531bf40 --- /dev/null +++ b/Packages/vChewing_CandidateWindow/Sources/CandidateWindow/INMUCandidateSuite/VwrCandidateVertical.swift @@ -0,0 +1,109 @@ +// (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// No trademark license is granted to use the trade names, trademarks, service +// marks, or product names of Contributor, except as required to fulfill notice +// requirements defined in MIT License. + +import Cocoa +import Shared +import SwiftUI + +// MARK: - Some useless tests + +@available(macOS 12, *) +struct CandidatePoolViewUIVertical_Previews: PreviewProvider { + @State static var testCandidates: [String] = [ + "八月中秋山林涼", "八月中秋", "風吹大地", "山林涼", "草枝擺", "🐂🍺", "🐃🍺", "八月", "中秋", + "山林", "風吹", "大地", "草枝", "八", "月", "中", "秋", "山", "林", "涼", "風", + "吹", "大", "地", "草", "枝", "擺", "八", "月", "中", "秋", "山", "林", "涼", "風", + "吹", "大", "地", "草", "枝", "擺", + ] + static var thePool: CandidatePool { + let result = CandidatePool(candidates: testCandidates, columnCapacity: 6, selectionKeys: "123456789") + // 下一行待解決:無論這裡怎麼指定高亮選中項是哪一筆,其所在行都得被卷動到使用者眼前。 + result.highlightVertical(at: 5) + return result + } + + static var previews: some View { + VwrCandidateVertical(controller: .init(.horizontal), thePool: thePool).fixedSize() + } +} + +@available(macOS 12, *) +public struct VwrCandidateVertical: View { + public var controller: CtlCandidateTDK + @State public var thePool: CandidatePool + @State public var hint: String = "" + + private var positionLabel: String { + (thePool.highlightedIndex + 1).description + "/" + thePool.candidateDataAll.count.description + } + + private func didSelectCandidateAt(_ pos: Int) { + if let delegate = controller.delegate { + delegate.candidatePairSelected(at: pos) + } + } + + public var body: some View { + VStack(alignment: .leading, spacing: 0) { + ScrollView(.horizontal, showsIndicators: true) { + HStack(alignment: .top, spacing: 10) { + ForEach(thePool.rangeForCurrentVerticalPage, id: \.self) { columnIndex in + VStack(alignment: .leading, spacing: 0) { + ForEach(Array(thePool.candidateColumns[columnIndex]), id: \.self) { currentCandidate in + HStack(spacing: 0) { + currentCandidate.attributedStringForSwiftUI.fixedSize(horizontal: false, vertical: true) + .frame( + maxWidth: .infinity, + alignment: .topLeading + ) + .contentShape(Rectangle()) + .onTapGesture { didSelectCandidateAt(currentCandidate.index) } + } + } + }.frame( + minWidth: Double(CandidateCellData.unifiedSize * 5), + alignment: .topLeading + ).id(columnIndex) + Divider() + } + if thePool.maximumColumnsPerPage - thePool.rangeForCurrentVerticalPage.count > 0 { + ForEach(thePool.rangeForLastVerticalPageBlanked, id: \.self) { _ in + VStack(alignment: .leading, spacing: 0) { + ForEach(0.. Bool func showPreviousPage() -> Bool + func showNextLine() -> Bool + func showPreviousLine() -> Bool func highlightNextCandidate() -> Bool func highlightPreviousCandidate() -> Bool func candidateIndexAtKeyLabelIndex(_: Int) -> Int diff --git a/Packages/vChewing_Shared/Sources/Shared/Protocols/PrefMgrProtocol.swift b/Packages/vChewing_Shared/Sources/Shared/Protocols/PrefMgrProtocol.swift index 98f9593a..b4261cdc 100644 --- a/Packages/vChewing_Shared/Sources/Shared/Protocols/PrefMgrProtocol.swift +++ b/Packages/vChewing_Shared/Sources/Shared/Protocols/PrefMgrProtocol.swift @@ -19,7 +19,7 @@ public protocol PrefMgrProtocol { var keyboardParser: Int { get set } var basicKeyboardLayout: String { get set } var alphanumericalKeyboardLayout: String { get set } - var showPageButtonsInCandidateWindow: Bool { get set } + var showNotificationsWhenTogglingCapsLock: Bool { get set } var candidateListTextSize: Double { get set } var shouldAutoReloadUserDataFiles: Bool { get set } var useRearCursorMode: Bool { get set } diff --git a/Packages/vChewing_Shared/Sources/Shared/Shared.swift b/Packages/vChewing_Shared/Sources/Shared/Shared.swift index d7ba66e8..785ae131 100644 --- a/Packages/vChewing_Shared/Sources/Shared/Shared.swift +++ b/Packages/vChewing_Shared/Sources/Shared/Shared.swift @@ -7,6 +7,7 @@ // requirements defined in MIT License. import Foundation +import SwiftExtension // MARK: - UserDef Snapshot Manager @@ -20,7 +21,7 @@ public enum UserDef: String, CaseIterable { case kKeyboardParser = "KeyboardParser" case kBasicKeyboardLayout = "BasicKeyboardLayout" case kAlphanumericalKeyboardLayout = "AlphanumericalKeyboardLayout" - case kShowPageButtonsInCandidateWindow = "ShowPageButtonsInCandidateWindow" + case kShowNotificationsWhenTogglingCapsLock = "ShowNotificationsWhenTogglingCapsLock" case kCandidateListTextSize = "CandidateListTextSize" case kAppleLanguages = "AppleLanguages" case kShouldAutoReloadUserDataFiles = "ShouldAutoReloadUserDataFiles" @@ -214,10 +215,10 @@ public enum CandidateKey { return NSLocalizedString("There should not be duplicated keys.", comment: "") case .tooShort: return NSLocalizedString( - "Please specify at least 4 candidate keys.", comment: "" + "Please specify at least 6 candidate keys.", comment: "" ) case .tooLong: - return NSLocalizedString("Maximum 15 candidate keys allowed.", comment: "") + return NSLocalizedString("Maximum 9 candidate keys allowed.", comment: "") } } } @@ -233,10 +234,10 @@ public enum CandidateKey { if trimmed.contains(" ") { throw CandidateKey.ErrorType.containSpace } - if trimmed.count < 4 { + if trimmed.count < 6 { throw CandidateKey.ErrorType.tooShort } - if trimmed.count > 15 { + if trimmed.count > 9 { throw CandidateKey.ErrorType.tooLong } let set = Set(Array(trimmed)) diff --git a/Packages/vChewing_SwiftExtension/Sources/SwiftExtension/SwiftExtension.swift b/Packages/vChewing_SwiftExtension/Sources/SwiftExtension/SwiftExtension.swift index 6677a294..f32b21a6 100644 --- a/Packages/vChewing_SwiftExtension/Sources/SwiftExtension/SwiftExtension.swift +++ b/Packages/vChewing_SwiftExtension/Sources/SwiftExtension/SwiftExtension.swift @@ -13,7 +13,7 @@ import Foundation // Extend the RangeReplaceableCollection to allow it clean duplicated characters. // Ref: https://stackoverflow.com/questions/25738817/ extension RangeReplaceableCollection where Element: Hashable { - public var deduplicate: Self { + public var deduplicated: Self { var set = Set() return filter { set.insert($0).inserted } } diff --git a/Source/Modules/KeyHandler_Core.swift b/Source/Modules/KeyHandler_Core.swift index 4145ba92..e5a5ab68 100644 --- a/Source/Modules/KeyHandler_Core.swift +++ b/Source/Modules/KeyHandler_Core.swift @@ -275,7 +275,7 @@ public class KeyHandler { Megrez.Compositor.KeyValuePaired(key: $0.0, value: $0.1.value) } arrCandidates = arrSuggestedCandidates.filter { arrCandidates.contains($0) } + arrCandidates - arrCandidates = arrCandidates.deduplicate + arrCandidates = arrCandidates.deduplicated arrCandidates = arrCandidates.stableSort { $0.key.split(separator: "-").count > $1.key.split(separator: "-").count } return arrCandidates.map { ($0.key, $0.value) } } diff --git a/Source/Modules/KeyHandler_HandleCandidate.swift b/Source/Modules/KeyHandler_HandleCandidate.swift index d70e10d6..6f928b24 100644 --- a/Source/Modules/KeyHandler_HandleCandidate.swift +++ b/Source/Modules/KeyHandler_HandleCandidate.swift @@ -74,8 +74,8 @@ extension KeyHandler { let updated: Bool = prefs.specifyShiftTabKeyBehavior ? (input.isShiftHold - ? ctlCandidate.showPreviousPage() - : ctlCandidate.showNextPage()) + ? ctlCandidate.showPreviousLine() + : ctlCandidate.showNextLine()) : (input.isShiftHold ? ctlCandidate.highlightPreviousCandidate() : ctlCandidate.highlightNextCandidate()) @@ -92,9 +92,9 @@ extension KeyHandler { prefs.specifyShiftSpaceKeyBehavior ? (input.isShiftHold ? ctlCandidate.highlightNextCandidate() - : ctlCandidate.showNextPage()) + : ctlCandidate.showNextLine()) : (input.isShiftHold - ? ctlCandidate.showNextPage() + ? ctlCandidate.showNextLine() : ctlCandidate.highlightNextCandidate()) if !updated { errorCallback("A11C781F") @@ -131,7 +131,7 @@ extension KeyHandler { errorCallback("1145148D") } case .vertical: - if !ctlCandidate.showPreviousPage() { + if !ctlCandidate.showPreviousLine() { errorCallback("1919810D") } @unknown default: @@ -149,7 +149,7 @@ extension KeyHandler { errorCallback("9B65138D") } case .vertical: - if !ctlCandidate.showNextPage() { + if !ctlCandidate.showNextLine() { errorCallback("9244908D") } @unknown default: @@ -163,21 +163,8 @@ extension KeyHandler { if input.isUp { switch ctlCandidate.currentLayout { case .horizontal: - if #available(macOS 12, *) { - if let ctlCandidate = ctlCandidate as? CtlCandidateTDK { - ctlCandidate.showPreviousLine() - break - } else { - if !ctlCandidate.showPreviousPage() { - errorCallback("9B614524") - break - } - } - } else { - if !ctlCandidate.showPreviousPage() { - errorCallback("9B614524") - break - } + if !ctlCandidate.showPreviousLine() { + errorCallback("9B614524") } case .vertical: if !ctlCandidate.highlightPreviousCandidate() { @@ -194,21 +181,9 @@ extension KeyHandler { if input.isDown { switch ctlCandidate.currentLayout { case .horizontal: - if #available(macOS 12, *) { - if let ctlCandidate = ctlCandidate as? CtlCandidateTDK { - ctlCandidate.showNextLine() - break - } else { - if !ctlCandidate.showNextPage() { - errorCallback("92B990DD") - break - } - } - } else { - if !ctlCandidate.showNextPage() { - errorCallback("92B990DD") - break - } + if !ctlCandidate.showNextLine() { + errorCallback("92B990DD") + break } case .vertical: if !ctlCandidate.highlightNextCandidate() { @@ -322,15 +297,7 @@ extension KeyHandler { if input.isSymbolMenuPhysicalKey { var updated = true - if #available(macOS 12, *) { - if let ctlCandidate = ctlCandidate as? CtlCandidateTDK { - updated = input.isShiftHold ? ctlCandidate.showPreviousLine() : ctlCandidate.showNextLine() - } else { - updated = input.isShiftHold ? ctlCandidate.showPreviousPage() : ctlCandidate.showNextPage() - } - } else { - updated = input.isShiftHold ? ctlCandidate.showPreviousPage() : ctlCandidate.showNextPage() - } + updated = input.isShiftHold ? ctlCandidate.showPreviousLine() : ctlCandidate.showNextLine() if !updated { errorCallback("66F3477B") } diff --git a/Source/Modules/PrefMgr_Core.swift b/Source/Modules/PrefMgr_Core.swift index 47051530..00fd83a9 100644 --- a/Source/Modules/PrefMgr_Core.swift +++ b/Source/Modules/PrefMgr_Core.swift @@ -53,8 +53,8 @@ public class PrefMgr: PrefMgrProtocol { ) public var alphanumericalKeyboardLayout: String - @AppProperty(key: UserDef.kShowPageButtonsInCandidateWindow.rawValue, defaultValue: true) - public var showPageButtonsInCandidateWindow: Bool + @AppProperty(key: UserDef.kShowNotificationsWhenTogglingCapsLock.rawValue, defaultValue: true) + public var showNotificationsWhenTogglingCapsLock: Bool @AppProperty(key: UserDef.kCandidateListTextSize.rawValue, defaultValue: 18) public var candidateListTextSize: Double { @@ -233,7 +233,10 @@ public class PrefMgr: PrefMgrProtocol { @AppProperty(key: UserDef.kCandidateKeys.rawValue, defaultValue: kDefaultCandidateKeys) public var candidateKeys: String { didSet { - if useIMKCandidateWindow { + if candidateKeys != candidateKeys.deduplicated { + candidateKeys = candidateKeys.deduplicated + } + if !(6...9).contains(candidateKeys.count) { candidateKeys = Self.kDefaultCandidateKeys } } diff --git a/Source/Modules/PrefMgr_Extension.swift b/Source/Modules/PrefMgr_Extension.swift index 5f52af93..9595bf63 100644 --- a/Source/Modules/PrefMgr_Extension.swift +++ b/Source/Modules/PrefMgr_Extension.swift @@ -22,6 +22,8 @@ extension PrefMgr { disableShiftTogglingAlphanumericalMode = false togglingAlphanumericalModeWithLShift = false } + // 自動糾正選字鍵 (利用其 didSet 特性) + candidateKeys = candidateKeys // 客體黑名單自動排序去重複。 clientsIMKTextInputIncapable = Array(Set(clientsIMKTextInputIncapable)).sorted() // 注拼槽注音排列選項糾錯。 diff --git a/Source/Modules/SessionCtl_Core.swift b/Source/Modules/SessionCtl_Core.swift index 27be3928..16262948 100644 --- a/Source/Modules/SessionCtl_Core.swift +++ b/Source/Modules/SessionCtl_Core.swift @@ -47,7 +47,7 @@ class SessionCtl: IMKInputController { // MARK: - - /// 當前 CapsLock 按鍵是否被摁下。 + /// 當前 Caps Lock 按鍵是否被摁下。 var isCapsLocked: Bool { NSEvent.modifierFlags.intersection(.deviceIndependentFlagsMask).contains(.capsLock) } /// 當前這個 SessionCtl 副本是否處於英數輸入模式。 @@ -68,7 +68,7 @@ class SessionCtl: IMKInputController { } /// Shift 按鍵事件分析器的副本。 - /// - Remark: 警告:該工具必須為 Struct 且全專案只能有一個唯一初期化副本。否則會在動 CapsLock 的時候誤以為是在摁 Shift。 + /// - Remark: 警告:該工具必須為 Struct 且全專案只能有一個唯一初期化副本。否則會在動 Caps Lock 的時候誤以為是在摁 Shift。 static var theShiftKeyDetector = ShiftKeyUpChecker(useLShift: PrefMgr.shared.togglingAlphanumericalModeWithLShift) /// `handle(event:)` 會利用這個參數判定某次 Shift 按鍵是否用來切換中英文輸入。 @@ -170,7 +170,7 @@ extension SessionCtl { keyHandler.ensureKeyboardParser() if isASCIIMode, !isCapsLocked, PrefMgr.shared.disableShiftTogglingAlphanumericalMode { isASCIIMode = false } - if isCapsLocked { isASCIIMode = isCapsLocked } // 同步 CapsLock 狀態。 + if isCapsLocked { isASCIIMode = isCapsLocked } // 同步 Caps Lock 狀態。 /// 必須加上下述條件,否則會在每次切換至輸入法本體的視窗(比如偏好設定視窗)時會卡死。 /// 這是很多 macOS 副廠輸入法的常見失誤之處。 diff --git a/Source/Modules/SessionCtl_HandleDisplay.swift b/Source/Modules/SessionCtl_HandleDisplay.swift index a0ff1734..c81dfe8f 100644 --- a/Source/Modules/SessionCtl_HandleDisplay.swift +++ b/Source/Modules/SessionCtl_HandleDisplay.swift @@ -142,7 +142,6 @@ extension SessionCtl { Self.ctlCandidateCurrent.hint = NSLocalizedString("Hold ⇧ to choose associates.", comment: "") } - Self.ctlCandidateCurrent.showPageButtons = PrefMgr.shared.showPageButtonsInCandidateWindow Self.ctlCandidateCurrent.useLangIdentifier = PrefMgr.shared.handleDefaultCandidateFontsByLangIdentifier Self.ctlCandidateCurrent.locale = { switch inputMode { diff --git a/Source/Modules/SessionCtl_HandleEvent.swift b/Source/Modules/SessionCtl_HandleEvent.swift index 801c7242..cebf4250 100644 --- a/Source/Modules/SessionCtl_HandleEvent.swift +++ b/Source/Modules/SessionCtl_HandleEvent.swift @@ -37,14 +37,18 @@ extension SessionCtl { // Caps Lock 通知與切換處理。 if event.type == .flagsChanged, event.keyCode == KeyCode.kCapsLock.rawValue { - let isCapsLockTurnedOn = event.modifierFlags.intersection(.deviceIndependentFlagsMask).contains(.capsLock) - let status = NSLocalizedString("NotificationSwitchASCII", comment: "") - Notifier.notify( - message: isCapsLockTurnedOn - ? "Caps Lock" + NSLocalizedString("Alphanumerical Input Mode", comment: "") + "\n" + status - : NSLocalizedString("Chinese Input Mode", comment: "") + "\n" + status - ) - isASCIIMode = isCapsLockTurnedOn + DispatchQueue.main.async { + let isCapsLockTurnedOn = event.modifierFlags.intersection(.deviceIndependentFlagsMask).contains(.capsLock) + let status = NSLocalizedString("NotificationSwitchASCII", comment: "") + if PrefMgr.shared.showNotificationsWhenTogglingCapsLock { + Notifier.notify( + message: isCapsLockTurnedOn + ? "Caps Lock" + NSLocalizedString("Alphanumerical Input Mode", comment: "") + "\n" + status + : NSLocalizedString("Chinese Input Mode", comment: "") + "\n" + status + ) + } + self.isASCIIMode = isCapsLockTurnedOn + } } // 用 Shift 開關半形英數模式,僅對 macOS 10.15 及之後的 macOS 有效。 diff --git a/Source/Modules/UIModules/CandidateUI/IMKCandidatesImpl.swift b/Source/Modules/UIModules/CandidateUI/IMKCandidatesImpl.swift index 5257ab85..973fb49f 100644 --- a/Source/Modules/UIModules/CandidateUI/IMKCandidatesImpl.swift +++ b/Source/Modules/UIModules/CandidateUI/IMKCandidatesImpl.swift @@ -12,7 +12,6 @@ import Shared /// 威注音自用的 IMKCandidates 型別。因為有用到 bridging header,所以無法弄成 Swift Package。 public class CtlCandidateIMK: IMKCandidates, CtlCandidateProtocol { public var hint: String = "" - public var showPageButtons: Bool = false public var locale: String = "" public var useLangIdentifier: Bool = false public var currentLayout: NSUserInterfaceLayoutOrientation = .horizontal @@ -149,6 +148,18 @@ public class CtlCandidateIMK: IMKCandidates, CtlCandidateProtocol { return true } + // 該函式會影響 IMK 選字窗。 + public func showNextLine() -> Bool { + do { currentLayout == .vertical ? moveRight(self) : moveDown(self) } + return true + } + + // 該函式會影響 IMK 選字窗。 + public func showPreviousLine() -> Bool { + do { currentLayout == .vertical ? moveLeft(self) : moveUp(self) } + return true + } + public func candidateIndexAtKeyLabelIndex(_ index: Int) -> Int { guard let delegate = delegate else { return Int.max } let result = currentPageIndex * keyLabels.count + index diff --git a/Source/Modules/UIModules/PrefUI/suiPrefPaneExperience.swift b/Source/Modules/UIModules/PrefUI/suiPrefPaneExperience.swift index 29c281ea..2afb228c 100644 --- a/Source/Modules/UIModules/PrefUI/suiPrefPaneExperience.swift +++ b/Source/Modules/UIModules/PrefUI/suiPrefPaneExperience.swift @@ -47,6 +47,8 @@ struct suiPrefPaneExperience: View { forKey: UserDef.kTrimUnfinishedReadingsOnCommit.rawValue) @State private var selAlwaysShowTooltipTextsHorizontally = UserDefaults.standard.bool( forKey: UserDef.kAlwaysShowTooltipTextsHorizontally.rawValue) + @State private var selShowNotificationsWhenTogglingCapsLock = UserDefaults.standard.bool( + forKey: UserDef.kShowNotificationsWhenTogglingCapsLock.rawValue) private let contentMaxHeight: Double = 440 private let contentWidth: Double = { @@ -195,6 +197,14 @@ struct suiPrefPaneExperience: View { } ).disabled(PrefMgr.shared.disableShiftTogglingAlphanumericalMode == true) } + Preferences.Section(title: "Caps Lock:") { + Toggle( + LocalizedStringKey("Show notifications when toggling Caps Lock"), + isOn: $selShowNotificationsWhenTogglingCapsLock.onChange { + PrefMgr.shared.showNotificationsWhenTogglingCapsLock = selShowNotificationsWhenTogglingCapsLock + } + ) + } Preferences.Section(label: { Text(LocalizedStringKey("Misc Settings:")) }) { Toggle( LocalizedStringKey("Enable Space key for calling candidate window"), diff --git a/Source/Modules/UIModules/PrefUI/suiPrefPaneGeneral.swift b/Source/Modules/UIModules/PrefUI/suiPrefPaneGeneral.swift index 5a344ed7..e99bdcd9 100644 --- a/Source/Modules/UIModules/PrefUI/suiPrefPaneGeneral.swift +++ b/Source/Modules/UIModules/PrefUI/suiPrefPaneGeneral.swift @@ -23,8 +23,6 @@ struct suiPrefPaneGeneral: View { : ["auto"] @State private var selEnableHorizontalCandidateLayout = UserDefaults.standard.bool( forKey: UserDef.kUseHorizontalCandidateList.rawValue) - @State private var selShowPageButtonsInCandidateUI = UserDefaults.standard.bool( - forKey: UserDef.kShowPageButtonsInCandidateWindow.rawValue) @State private var selEnableKanjiConvToKangXi = UserDefaults.standard.bool( forKey: UserDef.kChineseConversionEnabled.rawValue) @State private var selEnableKanjiConvToJIS = UserDefaults.standard.bool( @@ -129,18 +127,8 @@ struct suiPrefPaneGeneral: View { .labelsHidden() .horizontalRadioGroupLayout() .pickerStyle(RadioGroupPickerStyle()) - .disabled(!PrefMgr.shared.useIMKCandidateWindow) - if PrefMgr.shared.useIMKCandidateWindow { - Text(LocalizedStringKey("Choose your preferred layout of the candidate window.")) - .preferenceDescription() - } else { - Text( - LocalizedStringKey( - "Tadokoro candidate window only supports horizontal grid view. Enable IMK candidate window in DevZone page first if you want to choose vertical candidate window." - ) - ) + Text(LocalizedStringKey("Choose your preferred layout of the candidate window.")) .preferenceDescription() - } } Preferences.Section(label: { Text(LocalizedStringKey("Output Settings:")) }) { Toggle( diff --git a/Source/Modules/UIModules/PrefUI/suiPrefPaneKeyboard.swift b/Source/Modules/UIModules/PrefUI/suiPrefPaneKeyboard.swift index db5b8442..f0009d3d 100644 --- a/Source/Modules/UIModules/PrefUI/suiPrefPaneKeyboard.swift +++ b/Source/Modules/UIModules/PrefUI/suiPrefPaneKeyboard.swift @@ -56,7 +56,7 @@ struct suiPrefPaneKeyboard: View { items: CandidateKey.suggestions, text: $selSelectionKeys.onChange { let value = selSelectionKeys - let keys: String = value.trimmingCharacters(in: .whitespacesAndNewlines).deduplicate + let keys: String = value.trimmingCharacters(in: .whitespacesAndNewlines).deduplicated do { try CandidateKey.validate(keys: keys) PrefMgr.shared.candidateKeys = keys @@ -64,8 +64,8 @@ struct suiPrefPaneKeyboard: View { } catch CandidateKey.ErrorType.empty { selSelectionKeys = PrefMgr.shared.candidateKeys } catch { - if let window = ctlPrefUI.shared.controller.window { - let alert = NSAlert(error: error) + if let window = ctlPrefUI.shared.controller.window, let error = error as? CandidateKey.ErrorType { + let alert = NSAlert(error: error.errorDescription) alert.beginSheetModal(for: window) { _ in selSelectionKeys = PrefMgr.shared.candidateKeys } diff --git a/Source/Modules/WindowControllers/ctlPrefWindow.swift b/Source/Modules/WindowControllers/ctlPrefWindow.swift index 0272d84e..17afd11f 100644 --- a/Source/Modules/WindowControllers/ctlPrefWindow.swift +++ b/Source/Modules/WindowControllers/ctlPrefWindow.swift @@ -135,7 +135,9 @@ class ctlPrefWindow: NSWindowController { } selectionKeyComboBox.stringValue = candidateSelectionKeys - selectionKeyComboBox.isEnabled = false // 無法與 IMKCandidates 協作,故禁用。 + if PrefMgr.shared.useIMKCandidateWindow { + selectionKeyComboBox.isEnabled = false // 無法與 IMKCandidates 協作,故禁用。 + } } // 這裡有必要加上這段處理,用來確保藉由偏好設定介面動過的 CNS 開關能夠立刻生效。 @@ -198,7 +200,7 @@ class ctlPrefWindow: NSWindowController { let keys = (sender as AnyObject).stringValue?.trimmingCharacters( in: .whitespacesAndNewlines ) - .deduplicate + .deduplicated else { return } @@ -209,8 +211,8 @@ class ctlPrefWindow: NSWindowController { } catch CandidateKey.ErrorType.empty { selectionKeyComboBox.stringValue = PrefMgr.shared.candidateKeys } catch { - if let window = window { - let alert = NSAlert(error: error) + if let window = window, let error = error as? CandidateKey.ErrorType { + let alert = NSAlert(error: error.errorDescription) alert.beginSheetModal(for: window) { _ in self.selectionKeyComboBox.stringValue = PrefMgr.shared.candidateKeys } diff --git a/Source/Resources/Base.lproj/Localizable.strings b/Source/Resources/Base.lproj/Localizable.strings index c31aa78e..95929ca2 100644 --- a/Source/Resources/Base.lproj/Localizable.strings +++ b/Source/Resources/Base.lproj/Localizable.strings @@ -51,12 +51,11 @@ "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude." = "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude."; "Edit Phrase Replacement Table…" = "Edit Phrase Replacement Table…"; "Use Phrase Replacement" = "Use Phrase Replacement"; -"Candidates keys cannot be empty." = "Candidates keys cannot be empty."; "Candidate keys can only contain ASCII characters like alphanumericals." = "Candidate keys can only contain ASCII characters like alphanumericals."; "Candidate keys cannot contain space." = "Candidate keys cannot contain space."; "There should not be duplicated keys." = "There should not be duplicated keys."; -"Please specify at least 4 candidate keys." = "Please specify at least 4 candidate keys."; -"Maximum 15 candidate keys allowed." = "Maximum 15 candidate keys allowed."; +"Please specify at least 6 candidate keys." = "Please specify at least 6 candidate keys."; +"Maximum 9 candidate keys allowed." = "Maximum 9 candidate keys allowed."; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ Phrase replacement mode enabled, interfering user phrase entry."; "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match." = "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match."; "Per-Char Select Mode" = "Per-Char Select Mode"; @@ -209,7 +208,7 @@ "Shift+BackSpace:" = "Shift+BackSpace:"; "Shift+Letter:" = "Shift+Letter:"; "Show Hanyu-Pinyin in the inline composition buffer" = "Show Hanyu-Pinyin in the inline composition buffer"; -"Show page buttons in candidate window" = "Show page buttons in candidate window"; +"Show notifications when toggling Caps Lock" = "Show notifications when toggling Caps Lock"; "Simplified Chinese" = "Simplified Chinese"; "Some client apps (like Chromium-cored browsers: MS Edge, Google Chrome, etc.) may duplicate Shift-key inputs due to their internal bugs, and their devs are less likely to fix their bugs of such. vChewing has its accommodation procedures enabled by default for known Chromium-cored browsers. Here you can customize how the accommodation should work." = "Some client apps (like Chromium-cored browsers: MS Edge, Google Chrome, etc.) may duplicate Shift-key inputs due to their internal bugs, and their devs are less likely to fix their bugs of such. vChewing has its accommodation procedures enabled by default for known Chromium-cored browsers. Here you can customize how the accommodation should work."; "Space to +cycle candidates, Shift+Space to +cycle pages" = "Space to +cycle candidates, Shift+Space to +cycle pages"; @@ -217,7 +216,6 @@ "Specify the behavior of intonation key when syllable composer is empty." = "Specify the behavior of intonation key when syllable composer is empty."; "Starlight" = "Starlight"; "Stop farting (when typed phonetic combination is invalid, etc.)" = "Stop farting (when typed phonetic combination is invalid, etc.)"; -"Tadokoro candidate window only supports horizontal grid view. Enable IMK candidate window in DevZone page first if you want to choose vertical candidate window." = "Tadokoro candidate window only supports horizontal grid view. Enable IMK candidate window in DevZone page first if you want to choose vertical candidate window."; "This only works with Tadokoro candidate window." = "This only works with Tadokoro candidate window."; "Traditional Chinese" = "Traditional Chinese"; "Trim unfinished readings on commit" = "Trim unfinished readings on commit"; diff --git a/Source/Resources/en.lproj/Localizable.strings b/Source/Resources/en.lproj/Localizable.strings index c31aa78e..95929ca2 100644 --- a/Source/Resources/en.lproj/Localizable.strings +++ b/Source/Resources/en.lproj/Localizable.strings @@ -51,12 +51,11 @@ "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude." = "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude."; "Edit Phrase Replacement Table…" = "Edit Phrase Replacement Table…"; "Use Phrase Replacement" = "Use Phrase Replacement"; -"Candidates keys cannot be empty." = "Candidates keys cannot be empty."; "Candidate keys can only contain ASCII characters like alphanumericals." = "Candidate keys can only contain ASCII characters like alphanumericals."; "Candidate keys cannot contain space." = "Candidate keys cannot contain space."; "There should not be duplicated keys." = "There should not be duplicated keys."; -"Please specify at least 4 candidate keys." = "Please specify at least 4 candidate keys."; -"Maximum 15 candidate keys allowed." = "Maximum 15 candidate keys allowed."; +"Please specify at least 6 candidate keys." = "Please specify at least 6 candidate keys."; +"Maximum 9 candidate keys allowed." = "Maximum 9 candidate keys allowed."; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ Phrase replacement mode enabled, interfering user phrase entry."; "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match." = "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match."; "Per-Char Select Mode" = "Per-Char Select Mode"; @@ -209,7 +208,7 @@ "Shift+BackSpace:" = "Shift+BackSpace:"; "Shift+Letter:" = "Shift+Letter:"; "Show Hanyu-Pinyin in the inline composition buffer" = "Show Hanyu-Pinyin in the inline composition buffer"; -"Show page buttons in candidate window" = "Show page buttons in candidate window"; +"Show notifications when toggling Caps Lock" = "Show notifications when toggling Caps Lock"; "Simplified Chinese" = "Simplified Chinese"; "Some client apps (like Chromium-cored browsers: MS Edge, Google Chrome, etc.) may duplicate Shift-key inputs due to their internal bugs, and their devs are less likely to fix their bugs of such. vChewing has its accommodation procedures enabled by default for known Chromium-cored browsers. Here you can customize how the accommodation should work." = "Some client apps (like Chromium-cored browsers: MS Edge, Google Chrome, etc.) may duplicate Shift-key inputs due to their internal bugs, and their devs are less likely to fix their bugs of such. vChewing has its accommodation procedures enabled by default for known Chromium-cored browsers. Here you can customize how the accommodation should work."; "Space to +cycle candidates, Shift+Space to +cycle pages" = "Space to +cycle candidates, Shift+Space to +cycle pages"; @@ -217,7 +216,6 @@ "Specify the behavior of intonation key when syllable composer is empty." = "Specify the behavior of intonation key when syllable composer is empty."; "Starlight" = "Starlight"; "Stop farting (when typed phonetic combination is invalid, etc.)" = "Stop farting (when typed phonetic combination is invalid, etc.)"; -"Tadokoro candidate window only supports horizontal grid view. Enable IMK candidate window in DevZone page first if you want to choose vertical candidate window." = "Tadokoro candidate window only supports horizontal grid view. Enable IMK candidate window in DevZone page first if you want to choose vertical candidate window."; "This only works with Tadokoro candidate window." = "This only works with Tadokoro candidate window."; "Traditional Chinese" = "Traditional Chinese"; "Trim unfinished readings on commit" = "Trim unfinished readings on commit"; diff --git a/Source/Resources/ja.lproj/Localizable.strings b/Source/Resources/ja.lproj/Localizable.strings index 40cf9a33..10d56374 100644 --- a/Source/Resources/ja.lproj/Localizable.strings +++ b/Source/Resources/ja.lproj/Localizable.strings @@ -51,12 +51,11 @@ "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude." = "「%@」は既存語彙:\n ENTER で最優先にし、SHIFT+COMMAND+ENTER で優先順位を下げる;\n BackSpace 或いは Delete で排除。"; "Edit Phrase Replacement Table…" = "言葉置換表を編集…"; "Use Phrase Replacement" = "言葉置換機能"; -"Candidates keys cannot be empty." = "言選り用キー陣列に何かキーをご登録ください。"; "Candidate keys can only contain ASCII characters like alphanumericals." = "言選り用キー陣列にはASCII文字だけをご登録ください(英数など)。"; "Candidate keys cannot contain space." = "言選り用キー陣列にスペースキーは登録できません。"; "There should not be duplicated keys." = "言選り用キー陣列に同じキーの重複登録はできません。"; -"Please specify at least 4 candidate keys." = "言選り用キー陣列に少なくとも4つのキーをご登録ください。"; -"Maximum 15 candidate keys allowed." = "言選り用キー陣列には最多15つキー登録できます。"; +"Please specify at least 6 candidate keys." = "言選り用キー陣列に少なくとも6つのキーをご登録ください。"; +"Maximum 9 candidate keys allowed." = "言選り用キー陣列には最多9つキー登録できます。"; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 言葉置換機能稼働中、新添付言葉にも影響。"; "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match." = "⚠︎ 対処不可:緩衝列の字数は読みの数と不同等。"; "Per-Char Select Mode" = "全候補入力モード"; @@ -82,7 +81,7 @@ "Optimize Memorized Phrases" = "臨時記憶資料を整う"; "Clear Memorized Phrases" = "臨時記憶資料を削除"; "Currency Numeral Output" = "数字大字変換"; -"Hold ⇧ to choose associates." = "⇧を押しながら連想候補をご選択ください。"; +"Hold ⇧ to choose associates." = "⇧を押しながら連想候補を選択。"; // The followings are the category names used in the Symbol menu. "catCommonSymbols" = "常用"; @@ -209,7 +208,7 @@ "Shift+BackSpace:" = "Shift+BackSpace:"; "Shift+Letter:" = "Shift+文字キー:"; "Show Hanyu-Pinyin in the inline composition buffer" = "弁音合併入力(入力緩衝列で音読みを漢語弁音に)"; -"Show page buttons in candidate window" = "入力候補陳列の側にページボタンを表示"; +"Show notifications when toggling Caps Lock" = "Caps Lock で切り替えの時に吹出通知メッセージを"; "Simplified Chinese" = "簡体中国語"; "Some client apps (like Chromium-cored browsers: MS Edge, Google Chrome, etc.) may duplicate Shift-key inputs due to their internal bugs, and their devs are less likely to fix their bugs of such. vChewing has its accommodation procedures enabled by default for known Chromium-cored browsers. Here you can customize how the accommodation should work." = "いくつかのアプリ(例えば MS Edge や Google Chrome などのような Chromium 系ブラウザー)には「Shift キーの入力イベントを複数化してしまう」という支障があり、そしてアプリそれぞれの開発元に修復される可能性はほぼゼロだと言える。威注音入力アプリは対策用の特殊措置を普段に起用している。ご自分の必要によって設定してください。"; "Space to +cycle candidates, Shift+Space to +cycle pages" = "Shift+Space で次のページ、Space で次の候補文字を"; @@ -217,7 +216,6 @@ "Specify the behavior of intonation key when syllable composer is empty." = "音読組立緩衝列が空かされた時の音調キーの行為をご指定ください。"; "Starlight" = "星光配列"; "Stop farting (when typed phonetic combination is invalid, etc.)" = "マナーモード // 外すと入力間違った時に変な声が出る"; -"Tadokoro candidate window only supports horizontal grid view. Enable IMK candidate window in DevZone page first if you want to choose vertical candidate window." = "田所候補陳列ウィンドウは格子状横型陳列しかできません。開発道場のページで IMK 候補陳列ウィンドウを起用してから、今のこのページで縦型・横型陳列の選択はできます。"; "This only works with Tadokoro candidate window." = "これは田所候補陳列ウィンドウだけに効ける機能である。"; "Traditional Chinese" = "繁体中国語"; "Trim unfinished readings on commit" = "送り出す緩衝列内容から未完成な音読みを除く"; diff --git a/Source/Resources/zh-Hans.lproj/Localizable.strings b/Source/Resources/zh-Hans.lproj/Localizable.strings index 6bda81d9..6b3fff45 100644 --- a/Source/Resources/zh-Hans.lproj/Localizable.strings +++ b/Source/Resources/zh-Hans.lproj/Localizable.strings @@ -51,12 +51,11 @@ "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude." = "「%@」已存在:\n 敲 Enter 以升权、敲 Shift+Command+Enter 以降权;\n 敲 BackSpace 或 Delete 以排除。"; "Edit Phrase Replacement Table…" = "编辑语汇置换表…"; "Use Phrase Replacement" = "使用语汇置换"; -"Candidates keys cannot be empty." = "您必须指定选字键。"; "Candidate keys can only contain ASCII characters like alphanumericals." = "选字键只能是英数等 ASCII 字符。"; "Candidate keys cannot contain space." = "选字键不得包含空格。"; "There should not be duplicated keys." = "选字键不得重复。"; -"Please specify at least 4 candidate keys." = "请至少指定四个选字键。"; -"Maximum 15 candidate keys allowed." = "选字键最多只能指定十五个。"; +"Please specify at least 6 candidate keys." = "请至少指定 6 个选字键。"; +"Maximum 9 candidate keys allowed." = "选字键最多只能指定 9 个。"; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 语汇置换功能已启用,会波及语汇自订。"; "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match." = "⚠︎ 无法处理:组字区字数与读音数不对应。"; "Per-Char Select Mode" = "模拟逐字选字输入"; @@ -209,7 +208,7 @@ "Shift+BackSpace:" = "Shift+退格键:"; "Shift+Letter:" = "Shift+字母键:"; "Show Hanyu-Pinyin in the inline composition buffer" = "拼音并击(组字区内显示汉语拼音)"; -"Show page buttons in candidate window" = "在选字窗内显示翻页按钮"; +"Show notifications when toggling Caps Lock" = "以 Caps Lock 切换输入法/中英模式时显示通知"; "Simplified Chinese" = "简体中文"; "Some client apps (like Chromium-cored browsers: MS Edge, Google Chrome, etc.) may duplicate Shift-key inputs due to their internal bugs, and their devs are less likely to fix their bugs of such. vChewing has its accommodation procedures enabled by default for known Chromium-cored browsers. Here you can customize how the accommodation should work." = "某些应用(比如像是 MS Edge 或者 Chrome 这样的 Chromium 核心的浏览器)可能会使输入的 Shift 按键讯号被重复输入,且其有关研发方很可能不打算修复这些产品缺陷。威注音针对这些应用预设启用了相容措施。请在此根据您的需求自订。"; "Space to +cycle candidates, Shift+Space to +cycle pages" = "Shift+空格键 换下一页,空格键 换选下一个候选字"; @@ -217,7 +216,6 @@ "Specify the behavior of intonation key when syllable composer is empty." = "指定声调键(在注拼槽为「空」状态时)的行为。"; "Starlight" = "星光排列"; "Stop farting (when typed phonetic combination is invalid, etc.)" = "廉耻模式 // 取消勾选的话,敲错字时会有异音"; -"Tadokoro candidate window only supports horizontal grid view. Enable IMK candidate window in DevZone page first if you want to choose vertical candidate window." = "田所选字窗仅支援横排矩阵布局模式。如欲使用纵排布局模式者,请在开发道场内先启用 IMK 选字窗。"; "This only works with Tadokoro candidate window." = "该方法仅对田所选字窗起作用。"; "Traditional Chinese" = "繁体中文"; "Trim unfinished readings on commit" = "在递交时清理未完成拼写的读音"; diff --git a/Source/Resources/zh-Hant.lproj/Localizable.strings b/Source/Resources/zh-Hant.lproj/Localizable.strings index c5adcb9e..07a09382 100644 --- a/Source/Resources/zh-Hant.lproj/Localizable.strings +++ b/Source/Resources/zh-Hant.lproj/Localizable.strings @@ -51,12 +51,11 @@ "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude." = "「%@」已存在:\n 敲 Enter 以升權、敲 Shift+Command+Enter 以降權;\n 敲 BackSpace 或 Delete 以排除。"; "Edit Phrase Replacement Table…" = "編輯語彙置換表…"; "Use Phrase Replacement" = "使用語彙置換"; -"Candidates keys cannot be empty." = "您必須指定選字鍵。"; "Candidate keys can only contain ASCII characters like alphanumericals." = "選字鍵只能是英數等 ASCII 字符。"; "Candidate keys cannot contain space." = "選字鍵不得包含空格。"; "There should not be duplicated keys." = "選字鍵不得重複。"; -"Please specify at least 4 candidate keys." = "請至少指定四個選字鍵。"; -"Maximum 15 candidate keys allowed." = "選字鍵最多只能指定十五個。"; +"Please specify at least 6 candidate keys." = "請至少指定 6 個選字鍵。"; +"Maximum 9 candidate keys allowed." = "選字鍵最多只能指定 9 個。"; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 語彙置換功能已啟用,會波及語彙自訂。"; "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match." = "⚠︎ 無法處理:組字區字數與讀音數不對應。"; "Per-Char Select Mode" = "模擬逐字選字輸入"; @@ -209,7 +208,7 @@ "Shift+BackSpace:" = "Shift+退格鍵:"; "Shift+Letter:" = "Shift+字母鍵:"; "Show Hanyu-Pinyin in the inline composition buffer" = "拼音並擊(組字區內顯示漢語拼音)"; -"Show page buttons in candidate window" = "在選字窗內顯示翻頁按鈕"; +"Show notifications when toggling Caps Lock" = "以 Caps Lock 切換輸入法/中英模式時顯示通知"; "Simplified Chinese" = "簡體中文"; "Some client apps (like Chromium-cored browsers: MS Edge, Google Chrome, etc.) may duplicate Shift-key inputs due to their internal bugs, and their devs are less likely to fix their bugs of such. vChewing has its accommodation procedures enabled by default for known Chromium-cored browsers. Here you can customize how the accommodation should work." = "某些應用(比如像是 MS Edge 或者 Chrome 這樣的 Chromium 核心的瀏覽器)可能會使輸入的 Shift 按鍵訊號被重複輸入,且其有關研發方很可能不打算修復這些產品缺陷。威注音針對這些應用預設啟用了相容措施。請在此根據您的需求自訂。"; "Space to +cycle candidates, Shift+Space to +cycle pages" = "Shift+空格鍵 換下一頁,空格鍵 換選下一個候選字"; @@ -217,7 +216,6 @@ "Specify the behavior of intonation key when syllable composer is empty." = "指定聲調鍵(在注拼槽為「空」狀態時)的行為。"; "Starlight" = "星光排列"; "Stop farting (when typed phonetic combination is invalid, etc.)" = "廉恥模式 // 取消勾選的話,敲錯字時會有異音"; -"Tadokoro candidate window only supports horizontal grid view. Enable IMK candidate window in DevZone page first if you want to choose vertical candidate window." = "田所選字窗僅支援橫排矩陣佈局模式。如欲使用縱排佈局模式者,請在開發道場內先啟用 IMK 選字窗。"; "This only works with Tadokoro candidate window." = "該方法僅對田所選字窗起作用。"; "Traditional Chinese" = "繁體中文"; "Trim unfinished readings on commit" = "在遞交時清理未完成拼寫的讀音"; diff --git a/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib b/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib index 9ef193a4..496e6a54 100644 --- a/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib +++ b/Source/WindowNIBs/Base.lproj/frmPrefWindow.xib @@ -1,6 +1,7 @@ + @@ -36,14 +37,14 @@ - + - + - + @@ -266,10 +267,10 @@ - + - + @@ -319,7 +320,7 @@ - + @@ -365,10 +366,10 @@ - + + @@ -457,6 +475,7 @@ + @@ -467,6 +486,7 @@ + @@ -496,7 +516,7 @@ - + @@ -511,7 +531,6 @@ - @@ -635,7 +654,7 @@ - + @@ -649,7 +668,7 @@ - + @@ -659,7 +678,7 @@ - + @@ -692,25 +711,7 @@ - - - + diff --git a/Source/WindowNIBs/en.lproj/frmPrefWindow.strings b/Source/WindowNIBs/en.lproj/frmPrefWindow.strings index 1f6473fd..d96c6c46 100644 --- a/Source/WindowNIBs/en.lproj/frmPrefWindow.strings +++ b/Source/WindowNIBs/en.lproj/frmPrefWindow.strings @@ -90,7 +90,7 @@ "RUG-ls-KyA.title" = "Push the cursor in front of the phrase after selection"; "rVQ-Hx-cGi.title" = "Japanese"; "s7u-Fm-dVg.title" = "Cycling Pages"; -"shc-Nu-UsM.title" = "Show page buttons in candidate list"; +"shc-Nu-UsM.title" = "Show notifications when toggling Caps Lock"; "tglDevZoneIMKCandidate.title" = "Use IMK Candidate Window instead of Tadokoro (will reboot the IME)"; "tglTrimUnfinishedReadingsOnCommit.title" = "Trim unfinished readings on commit"; "TXr-FF-ehw.title" = "Traditional Chinese"; diff --git a/Source/WindowNIBs/ja.lproj/frmPrefWindow.strings b/Source/WindowNIBs/ja.lproj/frmPrefWindow.strings index 02c9a2cf..9ed31a9a 100644 --- a/Source/WindowNIBs/ja.lproj/frmPrefWindow.strings +++ b/Source/WindowNIBs/ja.lproj/frmPrefWindow.strings @@ -90,7 +90,7 @@ "RUG-ls-KyA.title" = "候補選択の直後、すぐカーソルを単語の向こうに推す"; "rVQ-Hx-cGi.title" = "和語"; "s7u-Fm-dVg.title" = "ページ"; -"shc-Nu-UsM.title" = "ページボタンを表示"; +"shc-Nu-UsM.title" = "Caps Lock で切り替えの時に吹出通知メッセージを"; "tglDevZoneIMKCandidate.title" = "IMK 候補陳列ウィンドウを起用(入力アプリは自動的に再起動)"; "tglTrimUnfinishedReadingsOnCommit.title" = "送り出す緩衝列内容から未完成な音読みを除く"; "TXr-FF-ehw.title" = "繁体中国語"; diff --git a/Source/WindowNIBs/zh-Hans.lproj/frmPrefWindow.strings b/Source/WindowNIBs/zh-Hans.lproj/frmPrefWindow.strings index a55ad56f..4600a74d 100644 --- a/Source/WindowNIBs/zh-Hans.lproj/frmPrefWindow.strings +++ b/Source/WindowNIBs/zh-Hans.lproj/frmPrefWindow.strings @@ -90,7 +90,7 @@ "RUG-ls-KyA.title" = "在选字后将光标置于该字词的前方"; "rVQ-Hx-cGi.title" = "和语"; "s7u-Fm-dVg.title" = "轮替页面"; -"shc-Nu-UsM.title" = "在选字窗内显示翻页按钮"; +"shc-Nu-UsM.title" = "以 Caps Lock 切换输入法/中英模式时显示通知"; "tglDevZoneIMKCandidate.title" = "启用与 macOS 内建输入法相同的 IMK 选字窗(会自动重启输入法)"; "tglTrimUnfinishedReadingsOnCommit.title" = "在递交时清理未完成拼写的读音"; "TXr-FF-ehw.title" = "繁体中文"; diff --git a/Source/WindowNIBs/zh-Hant.lproj/frmPrefWindow.strings b/Source/WindowNIBs/zh-Hant.lproj/frmPrefWindow.strings index dc9f7a90..debf9684 100644 --- a/Source/WindowNIBs/zh-Hant.lproj/frmPrefWindow.strings +++ b/Source/WindowNIBs/zh-Hant.lproj/frmPrefWindow.strings @@ -90,7 +90,7 @@ "RUG-ls-KyA.title" = "在選字後將游標置於該字詞的前方"; "rVQ-Hx-cGi.title" = "和語"; "s7u-Fm-dVg.title" = "輪替頁面"; -"shc-Nu-UsM.title" = "在選字窗內顯示翻頁按鈕"; +"shc-Nu-UsM.title" = "以 Caps Lock 切換輸入法/中英模式時顯示通知"; "tglDevZoneIMKCandidate.title" = "啟用與 macOS 內建輸入法相同的 IMK 選字窗(會自動重啟輸入法)"; "tglTrimUnfinishedReadingsOnCommit.title" = "在遞交時清理未完成拼寫的讀音"; "TXr-FF-ehw.title" = "繁體中文"; diff --git a/Update-Info.plist b/Update-Info.plist index c2b0b8e7..7cefa8ed 100644 --- a/Update-Info.plist +++ b/Update-Info.plist @@ -5,7 +5,7 @@ CFBundleShortVersionString 2.8.0 CFBundleVersion - 2800 + 2802 UpdateInfoEndpoint https://gitee.com/vchewing/vChewing-macOS/raw/main/Update-Info.plist UpdateInfoSite diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 89799ee0..c98266f6 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -1262,7 +1262,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2800; + CURRENT_PROJECT_VERSION = 2802; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -1302,7 +1302,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2800; + CURRENT_PROJECT_VERSION = 2802; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -1341,7 +1341,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2800; + CURRENT_PROJECT_VERSION = 2802; DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1394,7 +1394,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2800; + CURRENT_PROJECT_VERSION = 2802; DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_NS_ASSERTIONS = NO; @@ -1529,7 +1529,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2800; + CURRENT_PROJECT_VERSION = 2802; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; @@ -1590,7 +1590,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2800; + CURRENT_PROJECT_VERSION = 2802; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; @@ -1638,7 +1638,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2800; + CURRENT_PROJECT_VERSION = 2802; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; @@ -1684,7 +1684,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2800; + CURRENT_PROJECT_VERSION = 2802; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES;