Repo // Deprecating Zonble's Voltaire-Swift candidate window.
This commit is contained in:
parent
c9b42ca9a5
commit
b117710579
3
AUTHORS
3
AUTHORS
|
@ -1,6 +1,6 @@
|
||||||
$ Main contributors and volunteers of this repository (vChewing for macOS):
|
$ Main contributors and volunteers of this repository (vChewing for macOS):
|
||||||
|
|
||||||
- Shiki Suen // Main developer of vChewing for macOS, Megrez language engine, Hotenka Chinese Converter, and Tekkon syllable composer engine.
|
- Shiki Suen // Main developer of vChewing for macOS, Megrez language engine, Hotenka Chinese Converter, Tadokoro Candidate Window, and Tekkon syllable composer engine.
|
||||||
- Hiraku Wang // Technical reinforcement in Cocoa during the Object-Cpp dev period of this project.
|
- Hiraku Wang // Technical reinforcement in Cocoa during the Object-Cpp dev period of this project.
|
||||||
- Isaac Xen // Technical reinforcement in Swift: SFX Module (NSSound ver.) and StringView Ranges Extension.
|
- Isaac Xen // Technical reinforcement in Swift: SFX Module (NSSound ver.) and StringView Ranges Extension.
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ $ Contributors and volunteers of the upstream repo, having no responsibility in
|
||||||
|
|
||||||
- Zonble Yang:
|
- Zonble Yang:
|
||||||
- McBopomofo for macOS 2.x architect.
|
- McBopomofo for macOS 2.x architect.
|
||||||
- Voltaire candidate window MK2 (massively modified as MK3 in vChewing by Shiki Suen).
|
|
||||||
- App-style installer (only preserved for developer purposes).
|
- App-style installer (only preserved for developer purposes).
|
||||||
- Mengjuei Hsieh:
|
- Mengjuei Hsieh:
|
||||||
- McBopomofo for macOS 1.x main developer and architect.
|
- McBopomofo for macOS 1.x main developer and architect.
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
.DS_Store
|
|
||||||
/.build
|
|
||||||
/Packages
|
|
||||||
/*.xcodeproj
|
|
||||||
xcuserdata/
|
|
||||||
DerivedData/
|
|
||||||
.swiftpm/config/registries.json
|
|
||||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
|
||||||
.netrc
|
|
|
@ -1,28 +0,0 @@
|
||||||
// swift-tools-version:5.3
|
|
||||||
import PackageDescription
|
|
||||||
|
|
||||||
let package = Package(
|
|
||||||
name: "Voltaire",
|
|
||||||
platforms: [
|
|
||||||
.macOS(.v10_11)
|
|
||||||
],
|
|
||||||
products: [
|
|
||||||
.library(
|
|
||||||
name: "Voltaire",
|
|
||||||
targets: ["Voltaire"]
|
|
||||||
)
|
|
||||||
],
|
|
||||||
dependencies: [
|
|
||||||
.package(path: "../vChewing_CandidateWindow"),
|
|
||||||
.package(path: "../vChewing_Shared"),
|
|
||||||
],
|
|
||||||
targets: [
|
|
||||||
.target(
|
|
||||||
name: "Voltaire",
|
|
||||||
dependencies: [
|
|
||||||
.product(name: "Shared", package: "vChewing_Shared"),
|
|
||||||
.product(name: "CandidateWindow", package: "vChewing_CandidateWindow"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
|
@ -1,48 +0,0 @@
|
||||||
# Voltaire
|
|
||||||
|
|
||||||
Voltaire is a UI component replacement for Apple's InputMethodKit (IMK). The
|
|
||||||
built-in candidate UI has a limited interaction model and is not very
|
|
||||||
extensible nor customizable.
|
|
||||||
|
|
||||||
The project also comes with a test app that demonstrates the features of the
|
|
||||||
UI component.
|
|
||||||
|
|
||||||
Voltaire MK3 provides following new features comparing to MK1 and MK2:
|
|
||||||
|
|
||||||
1. A brand-new candidate window design conforming to the latest macOS UI design style, plus a floating label indicating the current page number of candidates (a frequently-asked feature by vChewing users).
|
|
||||||
2. One class for both vertical and horizontal display purposes. This can be specified as a parameter on init().
|
|
||||||
|
|
||||||
3. Can specify whether default candidate fonts conform to MOE glyphs standard or continental glyphs standard, requiring macOS 12 Monterey or later.
|
|
||||||
4. Can specify whether page buttons are shown.
|
|
||||||
|
|
||||||
Regarding the horizontal and vertical layout:
|
|
||||||
|
|
||||||
1. It is recommended to use init() in lieu of directly changing the layout variable since the latter doesn't redraw page buttons correctly.
|
|
||||||
2. Vertical candidate mode doesn't support scrolling. This is a deliberated design.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Copyrights:
|
|
||||||
```
|
|
||||||
- (c) 2022 Shiki Suen for all modifications introduced to Voltaire MK3.
|
|
||||||
- (c) 2021 Zonble Yang for rewriting Voltaire MK2 in Swift.
|
|
||||||
- (c) 2012 Lukhnos Liu for Voltaire MK1 development in Objective-C.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
```
|
|
|
@ -1,597 +0,0 @@
|
||||||
// (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
|
||||||
// All possible vChewing-specific modifications are of:
|
|
||||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
|
||||||
// ====================
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
// 將之前 Zonble 重寫的 Voltaire 選字窗隔的橫向版本與縱向版本合併到同一個型別實體內。
|
|
||||||
|
|
||||||
import CandidateWindow
|
|
||||||
import Cocoa
|
|
||||||
import Shared
|
|
||||||
|
|
||||||
private class vwrCandidateUniversal: NSView {
|
|
||||||
var highlightedIndex: Int = 0 {
|
|
||||||
didSet { highlightedIndex = min(max(highlightedIndex, 0), dispCandidatesWithLabels.count - 1) }
|
|
||||||
}
|
|
||||||
|
|
||||||
var action: Selector?
|
|
||||||
weak var target: AnyObject?
|
|
||||||
weak var controller: AnyObject?
|
|
||||||
var isVerticalLayout = false
|
|
||||||
var fractionFontSize: Double = 12.0
|
|
||||||
|
|
||||||
private var keyLabels: [String] = []
|
|
||||||
private var displayedCandidates: [String] = []
|
|
||||||
private var dispCandidatesWithLabels: [String] = []
|
|
||||||
private var keyLabelHeight: Double = 0
|
|
||||||
private var keyLabelWidth: Double = 0
|
|
||||||
private var candidateTextHeight: Double = 0
|
|
||||||
private var cellPadding: Double = 0
|
|
||||||
private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:]
|
|
||||||
private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:]
|
|
||||||
private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:]
|
|
||||||
private var windowWidth: Double = 0 // 縱排專用
|
|
||||||
private var elementWidths: [Double] = []
|
|
||||||
private var elementHeights: [Double] = [] // 縱排專用
|
|
||||||
private var trackingHighlightedIndex: Int = .max {
|
|
||||||
didSet { trackingHighlightedIndex = max(trackingHighlightedIndex, 0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override var isFlipped: Bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
var sizeForView: NSSize {
|
|
||||||
var result = NSSize.zero
|
|
||||||
|
|
||||||
if !elementWidths.isEmpty {
|
|
||||||
switch isVerticalLayout {
|
|
||||||
case true:
|
|
||||||
result.width = windowWidth
|
|
||||||
result.height = elementHeights.reduce(0, +)
|
|
||||||
case false:
|
|
||||||
result.width = elementWidths.reduce(0, +) + Double(elementWidths.count)
|
|
||||||
result.height = candidateTextHeight + cellPadding
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func set(keyLabels labels: [String], displayedCandidates candidates: [String]) {
|
|
||||||
guard let delegate = (controller as? CtlCandidateUniversal)?.delegate else { return }
|
|
||||||
let candidates = candidates.map { theCandidate -> String in
|
|
||||||
let theConverted = delegate.kanjiConversionIfRequired(theCandidate)
|
|
||||||
return (theCandidate == theConverted) ? theCandidate : "\(theConverted)(\(theCandidate))"
|
|
||||||
}
|
|
||||||
|
|
||||||
let count = min(labels.count, candidates.count)
|
|
||||||
keyLabels = Array(labels[0..<count])
|
|
||||||
displayedCandidates = Array(candidates[0..<count])
|
|
||||||
dispCandidatesWithLabels = zip(keyLabels, displayedCandidates).map { $0 + $1 }
|
|
||||||
|
|
||||||
var newWidths = [Double]()
|
|
||||||
var calculatedWindowWidth = Double()
|
|
||||||
var newHeights = [Double]()
|
|
||||||
let baseSize = NSSize(width: 10240.0, height: 10240.0)
|
|
||||||
for index in 0..<count {
|
|
||||||
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(
|
|
||||||
with: baseSize, options: .usesLineFragmentOrigin,
|
|
||||||
attributes: candidateWithLabelAttrDict
|
|
||||||
)
|
|
||||||
var cellWidth = rctCandidate.size.width + cellPadding
|
|
||||||
let cellHeight = rctCandidate.size.height + cellPadding
|
|
||||||
switch isVerticalLayout {
|
|
||||||
case true:
|
|
||||||
if calculatedWindowWidth < rctCandidate.size.width {
|
|
||||||
calculatedWindowWidth = rctCandidate.size.width + cellPadding * 2
|
|
||||||
}
|
|
||||||
case false:
|
|
||||||
if cellWidth < cellHeight * 1.4 {
|
|
||||||
cellWidth = cellHeight * 1.4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newWidths.append(round(cellWidth))
|
|
||||||
newHeights.append(round(cellHeight)) // 縱排專用
|
|
||||||
}
|
|
||||||
elementWidths = newWidths
|
|
||||||
elementHeights = newHeights
|
|
||||||
// 縱排專用,防止窗體右側邊框粗細不一
|
|
||||||
windowWidth = round(calculatedWindowWidth + cellPadding)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc(setKeyLabelFont:candidateFont:)
|
|
||||||
func set(keyLabelFont labelFont: NSFont, candidateFont: NSFont) {
|
|
||||||
let paraStyle = NSMutableParagraphStyle()
|
|
||||||
paraStyle.setParagraphStyle(NSParagraphStyle.default)
|
|
||||||
paraStyle.alignment = isVerticalLayout ? .left : .center
|
|
||||||
|
|
||||||
candidateWithLabelAttrDict = [
|
|
||||||
.font: candidateFont,
|
|
||||||
.paragraphStyle: paraStyle,
|
|
||||||
.foregroundColor: NSColor.labelColor,
|
|
||||||
] // We still need this dummy section to make sure that…
|
|
||||||
// …the space occupations of the candidates are correct.
|
|
||||||
|
|
||||||
keyLabelAttrDict = [
|
|
||||||
.font: labelFont,
|
|
||||||
.paragraphStyle: paraStyle,
|
|
||||||
.verticalGlyphForm: true as AnyObject,
|
|
||||||
.foregroundColor: NSColor.secondaryLabelColor,
|
|
||||||
] // Candidate phrase text color
|
|
||||||
candidateAttrDict = [
|
|
||||||
.font: candidateFont,
|
|
||||||
.paragraphStyle: paraStyle,
|
|
||||||
.foregroundColor: NSColor.labelColor,
|
|
||||||
] // Candidate index text color
|
|
||||||
let labelFontSize = labelFont.pointSize
|
|
||||||
let candidateFontSize = candidateFont.pointSize
|
|
||||||
let biggestSize = max(labelFontSize, candidateFontSize)
|
|
||||||
fractionFontSize = round(biggestSize * 0.75)
|
|
||||||
keyLabelWidth = ceil(labelFontSize)
|
|
||||||
keyLabelHeight = ceil(labelFontSize * 2)
|
|
||||||
candidateTextHeight = ceil(candidateFontSize * 1.20)
|
|
||||||
cellPadding = ceil(biggestSize / 4.0) * 2
|
|
||||||
}
|
|
||||||
|
|
||||||
override func draw(_: NSRect) {
|
|
||||||
guard let controller = controller as? CtlCandidateUniversal else { return }
|
|
||||||
let bounds = bounds
|
|
||||||
NSColor.controlBackgroundColor.setFill() // Candidate list panel base background
|
|
||||||
NSBezierPath.fill(bounds)
|
|
||||||
|
|
||||||
switch isVerticalLayout {
|
|
||||||
case true:
|
|
||||||
var accuHeight: Double = 0
|
|
||||||
for (index, elementHeight) in elementHeights.enumerated() {
|
|
||||||
let currentHeight = elementHeight
|
|
||||||
let rctCandidateArea = NSRect(
|
|
||||||
x: 3.0, y: accuHeight + 3.0, width: windowWidth - 6.0,
|
|
||||||
height: candidateTextHeight + cellPadding - 6.0
|
|
||||||
)
|
|
||||||
let rctLabel = NSRect(
|
|
||||||
x: cellPadding / 2 + 2, y: accuHeight + cellPadding / 2, width: keyLabelWidth,
|
|
||||||
height: keyLabelHeight * 2.0
|
|
||||||
)
|
|
||||||
let rctCandidatePhrase = NSRect(
|
|
||||||
x: cellPadding / 2 + 2 + keyLabelWidth, y: accuHeight + cellPadding / 2 - 1,
|
|
||||||
width: windowWidth - keyLabelWidth, height: candidateTextHeight
|
|
||||||
)
|
|
||||||
|
|
||||||
var activeCandidateIndexAttr = keyLabelAttrDict
|
|
||||||
var activeCandidateAttr = candidateAttrDict
|
|
||||||
if index == highlightedIndex {
|
|
||||||
controller.highlightedColor().setFill()
|
|
||||||
// Highlightened index text color
|
|
||||||
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
|
|
||||||
.withAlphaComponent(0.84)
|
|
||||||
// Highlightened phrase text color
|
|
||||||
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
|
|
||||||
let path: NSBezierPath = .init(roundedRect: rctCandidateArea, xRadius: 6, yRadius: 6)
|
|
||||||
path.fill()
|
|
||||||
}
|
|
||||||
if #available(macOS 12, *) {
|
|
||||||
if controller.useLangIdentifier {
|
|
||||||
activeCandidateAttr[.languageIdentifier] = controller.locale as AnyObject
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(keyLabels[index] as NSString).draw(
|
|
||||||
in: rctLabel, withAttributes: activeCandidateIndexAttr
|
|
||||||
)
|
|
||||||
(displayedCandidates[index] as NSString).draw(
|
|
||||||
in: rctCandidatePhrase, withAttributes: activeCandidateAttr
|
|
||||||
)
|
|
||||||
accuHeight += currentHeight
|
|
||||||
}
|
|
||||||
case false:
|
|
||||||
var accuWidth: Double = 0
|
|
||||||
for (index, elementWidth) in elementWidths.enumerated() {
|
|
||||||
let currentWidth = elementWidth
|
|
||||||
let rctCandidateArea = NSRect(
|
|
||||||
x: accuWidth + 3.0, y: 3.0, width: currentWidth + 1.0 - 6.0,
|
|
||||||
height: candidateTextHeight + cellPadding - 6.0
|
|
||||||
)
|
|
||||||
let rctLabel = NSRect(
|
|
||||||
x: accuWidth + cellPadding / 2 - 1, y: cellPadding / 2, width: keyLabelWidth,
|
|
||||||
height: keyLabelHeight * 2.0
|
|
||||||
)
|
|
||||||
let rctCandidatePhrase = NSRect(
|
|
||||||
x: accuWidth + keyLabelWidth - 1, y: cellPadding / 2 - 1,
|
|
||||||
width: currentWidth - keyLabelWidth,
|
|
||||||
height: candidateTextHeight
|
|
||||||
)
|
|
||||||
|
|
||||||
var activeCandidateIndexAttr = keyLabelAttrDict
|
|
||||||
var activeCandidateAttr = candidateAttrDict
|
|
||||||
if index == highlightedIndex {
|
|
||||||
controller.highlightedColor().setFill()
|
|
||||||
// Highlightened index text color
|
|
||||||
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
|
|
||||||
.withAlphaComponent(0.84)
|
|
||||||
// Highlightened phrase text color
|
|
||||||
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
|
|
||||||
let path: NSBezierPath = .init(roundedRect: rctCandidateArea, xRadius: 6, yRadius: 6)
|
|
||||||
path.fill()
|
|
||||||
}
|
|
||||||
if #available(macOS 12, *) {
|
|
||||||
if controller.useLangIdentifier {
|
|
||||||
activeCandidateAttr[.languageIdentifier] = controller.locale as AnyObject
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(keyLabels[index] as NSString).draw(
|
|
||||||
in: rctLabel, withAttributes: activeCandidateIndexAttr
|
|
||||||
)
|
|
||||||
(displayedCandidates[index] as NSString).draw(
|
|
||||||
in: rctCandidatePhrase, withAttributes: activeCandidateAttr
|
|
||||||
)
|
|
||||||
accuWidth += currentWidth + 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func findHitIndex(event: NSEvent) -> Int {
|
|
||||||
let location = convert(event.locationInWindow, to: nil)
|
|
||||||
if !bounds.contains(location) {
|
|
||||||
return NSNotFound
|
|
||||||
}
|
|
||||||
switch isVerticalLayout {
|
|
||||||
case true:
|
|
||||||
var accuHeight = 0.0
|
|
||||||
for (index, elementHeight) in elementHeights.enumerated() {
|
|
||||||
let currentHeight = elementHeight
|
|
||||||
|
|
||||||
if location.y >= accuHeight, location.y <= accuHeight + currentHeight {
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
accuHeight += currentHeight
|
|
||||||
}
|
|
||||||
case false:
|
|
||||||
var accuWidth = 0.0
|
|
||||||
for (index, elementWidth) in elementWidths.enumerated() {
|
|
||||||
let currentWidth = elementWidth
|
|
||||||
|
|
||||||
if location.x >= accuWidth, location.x <= accuWidth + currentWidth {
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
accuWidth += currentWidth + 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NSNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
override func mouseUp(with event: NSEvent) {
|
|
||||||
trackingHighlightedIndex = highlightedIndex
|
|
||||||
let newIndex = findHitIndex(event: event)
|
|
||||||
guard newIndex != NSNotFound else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
highlightedIndex = newIndex
|
|
||||||
setNeedsDisplay(bounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func mouseDown(with event: NSEvent) {
|
|
||||||
let newIndex = findHitIndex(event: event)
|
|
||||||
guard newIndex != NSNotFound else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var triggerAction = false
|
|
||||||
if newIndex == highlightedIndex {
|
|
||||||
triggerAction = true
|
|
||||||
} else {
|
|
||||||
highlightedIndex = trackingHighlightedIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
trackingHighlightedIndex = 0
|
|
||||||
setNeedsDisplay(bounds)
|
|
||||||
if triggerAction {
|
|
||||||
if let target = target as? NSObject, let action = action {
|
|
||||||
target.perform(action, with: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CtlCandidateUniversal: CtlCandidate {
|
|
||||||
private var candidateView: vwrCandidateUniversal
|
|
||||||
private var prevPageButton: NSButton
|
|
||||||
private var nextPageButton: NSButton
|
|
||||||
private var pageCounterLabel: NSTextField
|
|
||||||
private var currentPageIndex: Int = 0
|
|
||||||
override public var currentLayout: NSUserInterfaceLayoutOrientation {
|
|
||||||
get { candidateView.isVerticalLayout ? .vertical : .horizontal }
|
|
||||||
set {
|
|
||||||
switch newValue {
|
|
||||||
case .vertical: candidateView.isVerticalLayout = true
|
|
||||||
case .horizontal: candidateView.isVerticalLayout = false
|
|
||||||
@unknown default: candidateView.isVerticalLayout = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
candidateView = vwrCandidateUniversal(frame: contentRect)
|
|
||||||
|
|
||||||
candidateView.wantsLayer = true
|
|
||||||
// candidateView.layer?.borderColor = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.20).cgColor
|
|
||||||
// candidateView.layer?.borderWidth = 1.0
|
|
||||||
if #available(macOS 10.13, *) {
|
|
||||||
candidateView.layer?.cornerRadius = 9.0
|
|
||||||
}
|
|
||||||
|
|
||||||
panel.contentView?.addSubview(candidateView)
|
|
||||||
|
|
||||||
// MARK: Add Buttons
|
|
||||||
|
|
||||||
contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width
|
|
||||||
let buttonAttribute: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: 9.0)]
|
|
||||||
|
|
||||||
nextPageButton = .init(frame: contentRect)
|
|
||||||
nextPageButton.wantsLayer = true
|
|
||||||
nextPageButton.layer?.masksToBounds = true
|
|
||||||
nextPageButton.layer?.borderColor = NSColor.clear.cgColor
|
|
||||||
nextPageButton.layer?.borderWidth = 0.0
|
|
||||||
nextPageButton.setButtonType(.momentaryLight)
|
|
||||||
nextPageButton.bezelStyle = .disclosure
|
|
||||||
nextPageButton.userInterfaceLayoutDirection = .leftToRight
|
|
||||||
nextPageButton.attributedTitle = NSMutableAttributedString(
|
|
||||||
string: " ", attributes: buttonAttribute
|
|
||||||
) // Next Page Arrow
|
|
||||||
prevPageButton = .init(frame: contentRect)
|
|
||||||
prevPageButton.wantsLayer = true
|
|
||||||
prevPageButton.layer?.masksToBounds = true
|
|
||||||
prevPageButton.layer?.borderColor = NSColor.clear.cgColor
|
|
||||||
prevPageButton.layer?.borderWidth = 0.0
|
|
||||||
prevPageButton.setButtonType(.momentaryLight)
|
|
||||||
prevPageButton.bezelStyle = .disclosure
|
|
||||||
prevPageButton.userInterfaceLayoutDirection = .rightToLeft
|
|
||||||
prevPageButton.attributedTitle = NSMutableAttributedString(
|
|
||||||
string: " ", attributes: buttonAttribute
|
|
||||||
) // Previous Page Arrow
|
|
||||||
panel.contentView?.addSubview(nextPageButton)
|
|
||||||
panel.contentView?.addSubview(prevPageButton)
|
|
||||||
|
|
||||||
// MARK: Add Page Counter
|
|
||||||
|
|
||||||
contentRect = NSRect(x: 128.0, y: 128.0, width: 48.0, height: 20.0)
|
|
||||||
pageCounterLabel = .init(frame: contentRect)
|
|
||||||
pageCounterLabel.isEditable = false
|
|
||||||
pageCounterLabel.isSelectable = false
|
|
||||||
pageCounterLabel.isBezeled = false
|
|
||||||
pageCounterLabel.attributedStringValue = NSMutableAttributedString(
|
|
||||||
string: " ", attributes: buttonAttribute
|
|
||||||
)
|
|
||||||
panel.contentView?.addSubview(pageCounterLabel)
|
|
||||||
|
|
||||||
// MARK: Post-Init()
|
|
||||||
|
|
||||||
super.init(layout)
|
|
||||||
window = panel
|
|
||||||
currentLayout = layout
|
|
||||||
candidateView.controller = self
|
|
||||||
|
|
||||||
candidateView.target = self
|
|
||||||
candidateView.action = #selector(candidateViewMouseDidClick(_:))
|
|
||||||
|
|
||||||
nextPageButton.target = self
|
|
||||||
nextPageButton.action = #selector(pageButtonAction(_:))
|
|
||||||
|
|
||||||
prevPageButton.target = self
|
|
||||||
prevPageButton.action = #selector(pageButtonAction(_:))
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(*, unavailable)
|
|
||||||
required init?(coder _: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func reloadData() {
|
|
||||||
candidateView.highlightedIndex = 0
|
|
||||||
currentPageIndex = 0
|
|
||||||
layoutCandidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult override public func showNextPage() -> Bool {
|
|
||||||
guard let delegate = delegate else { return false }
|
|
||||||
if pageCount == 1 { return highlightNextCandidate() }
|
|
||||||
if currentPageIndex + 1 >= pageCount { delegate.buzz() }
|
|
||||||
currentPageIndex = (currentPageIndex + 1 >= pageCount) ? 0 : currentPageIndex + 1
|
|
||||||
if currentPageIndex == pageCount - 1 {
|
|
||||||
candidateView.highlightedIndex = min(lastPageContentCount - 1, candidateView.highlightedIndex)
|
|
||||||
}
|
|
||||||
// candidateView.highlightedIndex = 0
|
|
||||||
layoutCandidateView()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult override public func showPreviousPage() -> Bool {
|
|
||||||
guard let delegate = delegate else { return false }
|
|
||||||
if pageCount == 1 { return highlightPreviousCandidate() }
|
|
||||||
if currentPageIndex == 0 { delegate.buzz() }
|
|
||||||
currentPageIndex = (currentPageIndex == 0) ? pageCount - 1 : currentPageIndex - 1
|
|
||||||
if currentPageIndex == pageCount - 1 {
|
|
||||||
candidateView.highlightedIndex = min(lastPageContentCount - 1, candidateView.highlightedIndex)
|
|
||||||
}
|
|
||||||
// candidateView.highlightedIndex = 0
|
|
||||||
layoutCandidateView()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult override public func highlightNextCandidate() -> Bool {
|
|
||||||
guard let delegate = delegate else { return false }
|
|
||||||
selectedCandidateIndex =
|
|
||||||
(selectedCandidateIndex + 1 >= delegate.candidatePairs(conv: false).count)
|
|
||||||
? 0 : selectedCandidateIndex + 1
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult override public func highlightPreviousCandidate() -> Bool {
|
|
||||||
guard let delegate = delegate else { return false }
|
|
||||||
selectedCandidateIndex =
|
|
||||||
(selectedCandidateIndex == 0)
|
|
||||||
? delegate.candidatePairs(conv: false).count - 1 : selectedCandidateIndex - 1
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func candidateIndexAtKeyLabelIndex(_ index: Int) -> Int {
|
|
||||||
guard let delegate = delegate else {
|
|
||||||
return Int.max
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = currentPageIndex * keyLabels.count + index
|
|
||||||
return result < delegate.candidatePairs(conv: false).count ? result : Int.max
|
|
||||||
}
|
|
||||||
|
|
||||||
override public var selectedCandidateIndex: Int {
|
|
||||||
get {
|
|
||||||
currentPageIndex * keyLabels.count + candidateView.highlightedIndex
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
guard let delegate = delegate else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let keyLabelCount = keyLabels.count
|
|
||||||
if newValue < delegate.candidatePairs(conv: false).count {
|
|
||||||
currentPageIndex = newValue / keyLabelCount
|
|
||||||
candidateView.highlightedIndex = newValue % keyLabelCount
|
|
||||||
layoutCandidateView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CtlCandidateUniversal {
|
|
||||||
private var pageCount: Int {
|
|
||||||
guard let delegate = delegate else {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
let totalCount = delegate.candidatePairs(conv: false).count
|
|
||||||
let keyLabelCount = keyLabels.count
|
|
||||||
return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var lastPageContentCount: Int {
|
|
||||||
guard let delegate = delegate else {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
let totalCount = delegate.candidatePairs(conv: false).count
|
|
||||||
let keyLabelCount = keyLabels.count
|
|
||||||
return totalCount % keyLabelCount
|
|
||||||
}
|
|
||||||
|
|
||||||
private func layoutCandidateView() {
|
|
||||||
guard let delegate = delegate, let window = window else { return }
|
|
||||||
|
|
||||||
candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont)
|
|
||||||
var candidates = [(String, String)]()
|
|
||||||
let count = delegate.candidatePairs(conv: false).count
|
|
||||||
let keyLabelCount = keyLabels.count
|
|
||||||
|
|
||||||
let begin = currentPageIndex * keyLabelCount
|
|
||||||
for index in begin..<min(begin + keyLabelCount, count) {
|
|
||||||
let candidate = delegate.candidatePairAt(index)
|
|
||||||
candidates.append(candidate)
|
|
||||||
}
|
|
||||||
candidateView.set(
|
|
||||||
keyLabels: keyLabels.map(\.displayedText), displayedCandidates: candidates.map(\.1)
|
|
||||||
)
|
|
||||||
var newSize = candidateView.sizeForView
|
|
||||||
var frameRect = candidateView.frame
|
|
||||||
frameRect.size = newSize
|
|
||||||
candidateView.frame = frameRect
|
|
||||||
let counterHeight: Double = newSize.height - 24.0
|
|
||||||
|
|
||||||
if pageCount > 1, showPageButtons {
|
|
||||||
var buttonRect = nextPageButton.frame
|
|
||||||
let spacing = 0.0
|
|
||||||
|
|
||||||
if currentLayout == .horizontal { buttonRect.size.height = floor(newSize.height / 2) }
|
|
||||||
let buttonOriginY: Double = {
|
|
||||||
if currentLayout == .vertical {
|
|
||||||
return counterHeight
|
|
||||||
}
|
|
||||||
return (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0
|
|
||||||
}()
|
|
||||||
buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY)
|
|
||||||
nextPageButton.frame = buttonRect
|
|
||||||
buttonRect.origin = NSPoint(
|
|
||||||
x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing
|
|
||||||
)
|
|
||||||
prevPageButton.frame = buttonRect
|
|
||||||
newSize.width += 20
|
|
||||||
nextPageButton.isHidden = false
|
|
||||||
prevPageButton.isHidden = false
|
|
||||||
} else {
|
|
||||||
nextPageButton.isHidden = true
|
|
||||||
prevPageButton.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if pageCount >= 2 {
|
|
||||||
let attrString = NSMutableAttributedString(
|
|
||||||
string: "\(currentPageIndex + 1)/\(pageCount)",
|
|
||||||
attributes: [
|
|
||||||
.font: NSFont.systemFont(ofSize: candidateView.fractionFontSize)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
pageCounterLabel.attributedStringValue = attrString
|
|
||||||
var rect = attrString.boundingRect(
|
|
||||||
with: NSSize(width: 1600.0, height: 1600.0),
|
|
||||||
options: .usesLineFragmentOrigin
|
|
||||||
)
|
|
||||||
|
|
||||||
rect.size.height += 3
|
|
||||||
rect.size.width += 4
|
|
||||||
let rectOriginY: Double =
|
|
||||||
(currentLayout == .horizontal)
|
|
||||||
? (newSize.height - rect.height) / 2
|
|
||||||
: counterHeight
|
|
||||||
let rectOriginX: Double =
|
|
||||||
showPageButtons
|
|
||||||
? newSize.width
|
|
||||||
: newSize.width + 4
|
|
||||||
rect.origin = NSPoint(x: rectOriginX, y: rectOriginY)
|
|
||||||
pageCounterLabel.frame = rect
|
|
||||||
newSize.width += rect.width + 4
|
|
||||||
pageCounterLabel.isHidden = false
|
|
||||||
} else {
|
|
||||||
pageCounterLabel.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
frameRect = window.frame
|
|
||||||
|
|
||||||
let topLeftPoint = NSPoint(x: frameRect.origin.x, y: frameRect.origin.y + frameRect.size.height)
|
|
||||||
frameRect.size = newSize
|
|
||||||
frameRect.origin = NSPoint(x: topLeftPoint.x, y: topLeftPoint.y - frameRect.size.height)
|
|
||||||
window.setFrame(frameRect, display: false)
|
|
||||||
candidateView.setNeedsDisplay(candidateView.bounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func pageButtonAction(_ sender: Any) {
|
|
||||||
guard let sender = sender as? NSButton else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if sender == nextPageButton {
|
|
||||||
_ = showNextPage()
|
|
||||||
} else if sender == prevPageButton {
|
|
||||||
_ = showPreviousPage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func candidateViewMouseDidClick(_: Any) {
|
|
||||||
delegate?.candidatePairSelected(at: selectedCandidateIndex)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -91,7 +91,6 @@ P.S.: 威注音输入法的 Shift 按键监测功能仅借由对 NSEvent 讯号
|
||||||
威注音专案目前仅用到小麦注音的下述程式组件(MIT License):
|
威注音专案目前仅用到小麦注音的下述程式组件(MIT License):
|
||||||
|
|
||||||
- 仅供研发人员调试方便而使用的 App 版安装程式 (by Zonble Yang),不对公众使用。
|
- 仅供研发人员调试方便而使用的 App 版安装程式 (by Zonble Yang),不对公众使用。
|
||||||
- Voltaire MK2 选字窗 (by Zonble Yang),有大幅度修改。
|
|
||||||
|
|
||||||
威注音专案目前还用到如下的来自 Lukhnos Liu 的算法:
|
威注音专案目前还用到如下的来自 Lukhnos Liu 的算法:
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,6 @@ P.S.: 威注音輸入法的 Shift 按鍵監測功能僅藉由對 NSEvent 訊號
|
||||||
威注音專案目前僅用到小麥注音的下述程式組件(MIT License):
|
威注音專案目前僅用到小麥注音的下述程式組件(MIT License):
|
||||||
|
|
||||||
- 僅供研發人員調試方便而使用的 App 版安裝程式 (by Zonble Yang),不對公眾使用。
|
- 僅供研發人員調試方便而使用的 App 版安裝程式 (by Zonble Yang),不對公眾使用。
|
||||||
- Voltaire MK2 選字窗 (by Zonble Yang),有大幅度修改。
|
|
||||||
|
|
||||||
威注音專案目前還用到如下的來自 Lukhnos Liu 的算法:
|
威注音專案目前還用到如下的來自 Lukhnos Liu 的算法:
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
5B963CA828D5DB1400DCEE88 /* PrefMgr_Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B963CA728D5DB1400DCEE88 /* PrefMgr_Core.swift */; };
|
5B963CA828D5DB1400DCEE88 /* PrefMgr_Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B963CA728D5DB1400DCEE88 /* PrefMgr_Core.swift */; };
|
||||||
5B98114828D6198700CBC605 /* PinyinPhonaConverter in Frameworks */ = {isa = PBXBuildFile; productRef = 5B98114728D6198700CBC605 /* PinyinPhonaConverter */; };
|
5B98114828D6198700CBC605 /* PinyinPhonaConverter in Frameworks */ = {isa = PBXBuildFile; productRef = 5B98114728D6198700CBC605 /* PinyinPhonaConverter */; };
|
||||||
5BA8C30328DF0360004C5CC4 /* CandidateWindow in Frameworks */ = {isa = PBXBuildFile; productRef = 5BA8C30228DF0360004C5CC4 /* CandidateWindow */; };
|
5BA8C30328DF0360004C5CC4 /* CandidateWindow in Frameworks */ = {isa = PBXBuildFile; productRef = 5BA8C30228DF0360004C5CC4 /* CandidateWindow */; };
|
||||||
5BA8C30528DF0364004C5CC4 /* Voltaire in Frameworks */ = {isa = PBXBuildFile; productRef = 5BA8C30428DF0364004C5CC4 /* Voltaire */; };
|
|
||||||
5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */; };
|
5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */; };
|
||||||
5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */; };
|
5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */; };
|
||||||
5BA9FD1127FEDB6B002DE248 /* ctlPrefUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0C27FEDB6B002DE248 /* ctlPrefUI.swift */; };
|
5BA9FD1127FEDB6B002DE248 /* ctlPrefUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0C27FEDB6B002DE248 /* ctlPrefUI.swift */; };
|
||||||
|
@ -222,7 +221,6 @@
|
||||||
5B963CA128D5C22D00DCEE88 /* vChewing_SwiftExtension */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_SwiftExtension; path = Packages/vChewing_SwiftExtension; sourceTree = "<group>"; };
|
5B963CA128D5C22D00DCEE88 /* vChewing_SwiftExtension */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_SwiftExtension; path = Packages/vChewing_SwiftExtension; sourceTree = "<group>"; };
|
||||||
5B963CA728D5DB1400DCEE88 /* PrefMgr_Core.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefMgr_Core.swift; sourceTree = "<group>"; };
|
5B963CA728D5DB1400DCEE88 /* PrefMgr_Core.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefMgr_Core.swift; sourceTree = "<group>"; };
|
||||||
5B98114628D6198000CBC605 /* vChewing_PinyinPhonaConverter */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_PinyinPhonaConverter; path = Packages/vChewing_PinyinPhonaConverter; sourceTree = "<group>"; };
|
5B98114628D6198000CBC605 /* vChewing_PinyinPhonaConverter */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_PinyinPhonaConverter; path = Packages/vChewing_PinyinPhonaConverter; sourceTree = "<group>"; };
|
||||||
5BA8C30028DEFE4F004C5CC4 /* OpenVanilla_Voltaire */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = OpenVanilla_Voltaire; path = Packages/OpenVanilla_Voltaire; sourceTree = "<group>"; };
|
|
||||||
5BA8C30128DEFE4F004C5CC4 /* vChewing_CandidateWindow */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_CandidateWindow; path = Packages/vChewing_CandidateWindow; sourceTree = "<group>"; };
|
5BA8C30128DEFE4F004C5CC4 /* vChewing_CandidateWindow */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_CandidateWindow; path = Packages/vChewing_CandidateWindow; sourceTree = "<group>"; };
|
||||||
5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = suiPrefPaneGeneral.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = suiPrefPaneGeneral.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||||
5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = suiPrefPaneKeyboard.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = suiPrefPaneKeyboard.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||||
|
@ -359,7 +357,6 @@
|
||||||
5BDB7A3B28D4824A001AC277 /* FolderMonitor in Frameworks */,
|
5BDB7A3B28D4824A001AC277 /* FolderMonitor in Frameworks */,
|
||||||
5BA8C30328DF0360004C5CC4 /* CandidateWindow in Frameworks */,
|
5BA8C30328DF0360004C5CC4 /* CandidateWindow in Frameworks */,
|
||||||
5BC5E01E28DDE4770094E427 /* NotifierUI in Frameworks */,
|
5BC5E01E28DDE4770094E427 /* NotifierUI in Frameworks */,
|
||||||
5BA8C30528DF0364004C5CC4 /* Voltaire in Frameworks */,
|
|
||||||
5B40113C28D71C0100A9D4CB /* Uninstaller in Frameworks */,
|
5B40113C28D71C0100A9D4CB /* Uninstaller in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -565,7 +562,6 @@
|
||||||
5BDB7A3128D47587001AC277 /* DanielGalasko_FolderMonitor */,
|
5BDB7A3128D47587001AC277 /* DanielGalasko_FolderMonitor */,
|
||||||
5BFC63C428D487AE004A77B7 /* Fuziki_NSAttributedTextView */,
|
5BFC63C428D487AE004A77B7 /* Fuziki_NSAttributedTextView */,
|
||||||
5BDB7A3028D47587001AC277 /* Jad_BookmarkManager */,
|
5BDB7A3028D47587001AC277 /* Jad_BookmarkManager */,
|
||||||
5BA8C30028DEFE4F004C5CC4 /* OpenVanilla_Voltaire */,
|
|
||||||
5BDB7A3428D47587001AC277 /* Qwertyyb_ShiftKeyUpChecker */,
|
5BDB7A3428D47587001AC277 /* Qwertyyb_ShiftKeyUpChecker */,
|
||||||
5BDB7A3528D47587001AC277 /* RMJay_LineReader */,
|
5BDB7A3528D47587001AC277 /* RMJay_LineReader */,
|
||||||
5BDB7A2F28D47587001AC277 /* Sindresorhus_Preferences */,
|
5BDB7A2F28D47587001AC277 /* Sindresorhus_Preferences */,
|
||||||
|
@ -783,7 +779,6 @@
|
||||||
5BC5E02028DDEFE00094E427 /* TooltipUI */,
|
5BC5E02028DDEFE00094E427 /* TooltipUI */,
|
||||||
5BC5E02328DE07860094E427 /* PopupCompositionBuffer */,
|
5BC5E02328DE07860094E427 /* PopupCompositionBuffer */,
|
||||||
5BA8C30228DF0360004C5CC4 /* CandidateWindow */,
|
5BA8C30228DF0360004C5CC4 /* CandidateWindow */,
|
||||||
5BA8C30428DF0364004C5CC4 /* Voltaire */,
|
|
||||||
);
|
);
|
||||||
productName = vChewing;
|
productName = vChewing;
|
||||||
productReference = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */;
|
productReference = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */;
|
||||||
|
@ -1780,10 +1775,6 @@
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = CandidateWindow;
|
productName = CandidateWindow;
|
||||||
};
|
};
|
||||||
5BA8C30428DF0364004C5CC4 /* Voltaire */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
productName = Voltaire;
|
|
||||||
};
|
|
||||||
5BC5E01D28DDE4770094E427 /* NotifierUI */ = {
|
5BC5E01D28DDE4770094E427 /* NotifierUI */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = NotifierUI;
|
productName = NotifierUI;
|
||||||
|
|
Loading…
Reference in New Issue