2.7.5 // Further renovation. Merge PR#147 from upd/2.7.5
6
AUTHORS
|
@ -17,13 +17,9 @@ $ Contributors and volunteers of the upstream repo, having no responsibility in
|
|||
- Zonble Yang:
|
||||
- McBopomofo for macOS 2.x architect.
|
||||
- Voltaire candidate window MK2 (massively modified as MK3 in vChewing by Shiki Suen).
|
||||
- Notifier window.
|
||||
- App-style installer (only preserved for developer purposes).
|
||||
- PrefMgr (userdefaults manager).
|
||||
- Mengjuei Hsieh:
|
||||
- McBopomofo for macOS 1.x main developer and architect.
|
||||
- The original C++ version of the User Override Module.
|
||||
- Shiki Suen is trying to rewrite this module in Swift (and CSharp) with further development.
|
||||
|
||||
Although there is no Lukhnos's codes left in the current repository, we still credit him for his previous work:
|
||||
|
||||
|
@ -32,6 +28,8 @@ Although there is no Lukhnos's codes left in the current repository, we still cr
|
|||
- Shiki Suen's Megrez engine (MIT License) is basically a Swift-rewritten version of Gramambular 2 with further development.
|
||||
- Developer of Mandarin syllable composer (removed since vChewing 1.5.7).
|
||||
- Shiki Suen's Tekkon engine is made from scratch and has no relationship to Mandarin syllable composer.
|
||||
- Developer of the original C++ version of the User Override Module MK2.
|
||||
- Shiki Suen is trying to rewrite this module in Swift (and CSharp) with further development.
|
||||
|
||||
$ Special thanks to:
|
||||
|
||||
|
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 75 KiB |
|
@ -48,7 +48,7 @@ public class NSAttributedTextView: NSView {
|
|||
}
|
||||
|
||||
public var direction: writingDirection = .horizontal
|
||||
public var fontSize: CGFloat = NSFont.systemFontSize {
|
||||
public var fontSize: Double = NSFont.systemFontSize {
|
||||
didSet {
|
||||
attributes[.font] = NSFont.systemFont(ofSize: fontSize)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,28 @@
|
|||
// 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"),
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,48 @@
|
|||
# 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.
|
||||
```
|
|
@ -10,6 +10,10 @@
|
|||
|
||||
// 將之前 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) }
|
||||
|
@ -17,22 +21,23 @@ private class vwrCandidateUniversal: NSView {
|
|||
|
||||
var action: Selector?
|
||||
weak var target: AnyObject?
|
||||
weak var controller: AnyObject?
|
||||
var isVerticalLayout = false
|
||||
var fractionFontSize: CGFloat = 12.0
|
||||
var fractionFontSize: Double = 12.0
|
||||
|
||||
private var keyLabels: [String] = []
|
||||
private var displayedCandidates: [String] = []
|
||||
private var dispCandidatesWithLabels: [String] = []
|
||||
private var keyLabelHeight: CGFloat = 0
|
||||
private var keyLabelWidth: CGFloat = 0
|
||||
private var candidateTextHeight: CGFloat = 0
|
||||
private var cellPadding: CGFloat = 0
|
||||
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: CGFloat = 0 // 縱排專用
|
||||
private var elementWidths: [CGFloat] = []
|
||||
private var elementHeights: [CGFloat] = [] // 縱排專用
|
||||
private var windowWidth: Double = 0 // 縱排專用
|
||||
private var elementWidths: [Double] = []
|
||||
private var elementHeights: [Double] = [] // 縱排專用
|
||||
private var trackingHighlightedIndex: Int = .max {
|
||||
didSet { trackingHighlightedIndex = max(trackingHighlightedIndex, 0) }
|
||||
}
|
||||
|
@ -50,17 +55,17 @@ private class vwrCandidateUniversal: NSView {
|
|||
result.width = windowWidth
|
||||
result.height = elementHeights.reduce(0, +)
|
||||
case false:
|
||||
result.width = elementWidths.reduce(0, +) + CGFloat(elementWidths.count)
|
||||
result.width = elementWidths.reduce(0, +) + Double(elementWidths.count)
|
||||
result.height = candidateTextHeight + cellPadding
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@objc(setKeyLabels:displayedCandidates:)
|
||||
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 = ChineseConverter.kanjiConversionIfRequired(theCandidate)
|
||||
let theConverted = delegate.kanjiConversionIfRequired(theCandidate)
|
||||
return (theCandidate == theConverted) ? theCandidate : "\(theConverted)(\(theCandidate))"
|
||||
}
|
||||
|
||||
|
@ -69,9 +74,9 @@ private class vwrCandidateUniversal: NSView {
|
|||
displayedCandidates = Array(candidates[0..<count])
|
||||
dispCandidatesWithLabels = zip(keyLabels, displayedCandidates).map { $0 + $1 }
|
||||
|
||||
var newWidths = [CGFloat]()
|
||||
var calculatedWindowWidth = CGFloat()
|
||||
var newHeights = [CGFloat]()
|
||||
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(
|
||||
|
@ -133,55 +138,15 @@ private class vwrCandidateUniversal: NSView {
|
|||
cellPadding = ceil(biggestSize / 4.0) * 2
|
||||
}
|
||||
|
||||
func ensureLangIdentifier(for attr: inout [NSAttributedString.Key: AnyObject]) {
|
||||
if PrefMgr.shared.handleDefaultCandidateFontsByLangIdentifier {
|
||||
switch IMEApp.currentInputMode {
|
||||
case .imeModeCHS:
|
||||
if #available(macOS 12.0, *) {
|
||||
attr[.languageIdentifier] = "zh-Hans" as AnyObject
|
||||
}
|
||||
case .imeModeCHT:
|
||||
if #available(macOS 12.0, *) {
|
||||
attr[.languageIdentifier] =
|
||||
(PrefMgr.shared.shiftJISShinjitaiOutputEnabled || PrefMgr.shared.chineseConversionEnabled)
|
||||
? "ja" as AnyObject : "zh-Hant" as AnyObject
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var highlightedColor: NSColor {
|
||||
var result = NSColor.alternateSelectedControlColor
|
||||
var colorBlendAmount: CGFloat = NSApplication.isDarkMode ? 0.3 : 0.0
|
||||
if #available(macOS 10.14, *), !NSApplication.isDarkMode, IMEApp.currentInputMode == .imeModeCHT {
|
||||
colorBlendAmount = 0.15
|
||||
}
|
||||
// The background color of the highlightened candidate
|
||||
switch IMEApp.currentInputMode {
|
||||
case .imeModeCHS:
|
||||
result = NSColor.systemRed
|
||||
case .imeModeCHT:
|
||||
result = NSColor.systemBlue
|
||||
default: break
|
||||
}
|
||||
var blendingAgainstTarget: NSColor = NSApplication.isDarkMode ? NSColor.black : NSColor.white
|
||||
if #unavailable(macOS 10.14) {
|
||||
colorBlendAmount = 0.3
|
||||
blendingAgainstTarget = NSColor.white
|
||||
}
|
||||
return result.blended(withFraction: colorBlendAmount, of: blendingAgainstTarget)!
|
||||
}
|
||||
|
||||
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: CGFloat = 0
|
||||
var accuHeight: Double = 0
|
||||
for (index, elementHeight) in elementHeights.enumerated() {
|
||||
let currentHeight = elementHeight
|
||||
let rctCandidateArea = NSRect(
|
||||
|
@ -200,7 +165,7 @@ private class vwrCandidateUniversal: NSView {
|
|||
var activeCandidateIndexAttr = keyLabelAttrDict
|
||||
var activeCandidateAttr = candidateAttrDict
|
||||
if index == highlightedIndex {
|
||||
highlightedColor.setFill()
|
||||
controller.highlightedColor().setFill()
|
||||
// Highlightened index text color
|
||||
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
|
||||
.withAlphaComponent(0.84)
|
||||
|
@ -209,7 +174,11 @@ private class vwrCandidateUniversal: NSView {
|
|||
let path: NSBezierPath = .init(roundedRect: rctCandidateArea, xRadius: 6, yRadius: 6)
|
||||
path.fill()
|
||||
}
|
||||
ensureLangIdentifier(for: &activeCandidateAttr)
|
||||
if #available(macOS 12, *) {
|
||||
if controller.useLangIdentifier {
|
||||
activeCandidateAttr[.languageIdentifier] = controller.locale as AnyObject
|
||||
}
|
||||
}
|
||||
(keyLabels[index] as NSString).draw(
|
||||
in: rctLabel, withAttributes: activeCandidateIndexAttr
|
||||
)
|
||||
|
@ -219,7 +188,7 @@ private class vwrCandidateUniversal: NSView {
|
|||
accuHeight += currentHeight
|
||||
}
|
||||
case false:
|
||||
var accuWidth: CGFloat = 0
|
||||
var accuWidth: Double = 0
|
||||
for (index, elementWidth) in elementWidths.enumerated() {
|
||||
let currentWidth = elementWidth
|
||||
let rctCandidateArea = NSRect(
|
||||
|
@ -239,7 +208,7 @@ private class vwrCandidateUniversal: NSView {
|
|||
var activeCandidateIndexAttr = keyLabelAttrDict
|
||||
var activeCandidateAttr = candidateAttrDict
|
||||
if index == highlightedIndex {
|
||||
highlightedColor.setFill()
|
||||
controller.highlightedColor().setFill()
|
||||
// Highlightened index text color
|
||||
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
|
||||
.withAlphaComponent(0.84)
|
||||
|
@ -248,7 +217,11 @@ private class vwrCandidateUniversal: NSView {
|
|||
let path: NSBezierPath = .init(roundedRect: rctCandidateArea, xRadius: 6, yRadius: 6)
|
||||
path.fill()
|
||||
}
|
||||
ensureLangIdentifier(for: &activeCandidateAttr)
|
||||
if #available(macOS 12, *) {
|
||||
if controller.useLangIdentifier {
|
||||
activeCandidateAttr[.languageIdentifier] = controller.locale as AnyObject
|
||||
}
|
||||
}
|
||||
(keyLabels[index] as NSString).draw(
|
||||
in: rctLabel, withAttributes: activeCandidateIndexAttr
|
||||
)
|
||||
|
@ -267,7 +240,7 @@ private class vwrCandidateUniversal: NSView {
|
|||
}
|
||||
switch isVerticalLayout {
|
||||
case true:
|
||||
var accuHeight: CGFloat = 0.0
|
||||
var accuHeight = 0.0
|
||||
for (index, elementHeight) in elementHeights.enumerated() {
|
||||
let currentHeight = elementHeight
|
||||
|
||||
|
@ -277,7 +250,7 @@ private class vwrCandidateUniversal: NSView {
|
|||
accuHeight += currentHeight
|
||||
}
|
||||
case false:
|
||||
var accuWidth: CGFloat = 0.0
|
||||
var accuWidth = 0.0
|
||||
for (index, elementWidth) in elementWidths.enumerated() {
|
||||
let currentWidth = elementWidth
|
||||
|
||||
|
@ -322,7 +295,7 @@ private class vwrCandidateUniversal: NSView {
|
|||
}
|
||||
}
|
||||
|
||||
public class ctlCandidateUniversal: ctlCandidate {
|
||||
public class CtlCandidateUniversal: CtlCandidate {
|
||||
private var candidateView: vwrCandidateUniversal
|
||||
private var prevPageButton: NSButton
|
||||
private var nextPageButton: NSButton
|
||||
|
@ -408,6 +381,7 @@ public class ctlCandidateUniversal: ctlCandidate {
|
|||
super.init(layout)
|
||||
window = panel
|
||||
currentLayout = layout
|
||||
candidateView.controller = self
|
||||
|
||||
candidateView.target = self
|
||||
candidateView.action = #selector(candidateViewMouseDidClick(_:))
|
||||
|
@ -431,9 +405,9 @@ public class ctlCandidateUniversal: ctlCandidate {
|
|||
}
|
||||
|
||||
@discardableResult override public func showNextPage() -> Bool {
|
||||
guard delegate != nil else { return false }
|
||||
guard let delegate = delegate else { return false }
|
||||
if pageCount == 1 { return highlightNextCandidate() }
|
||||
if currentPageIndex + 1 >= pageCount { IMEApp.buzz() }
|
||||
if currentPageIndex + 1 >= pageCount { delegate.buzz() }
|
||||
currentPageIndex = (currentPageIndex + 1 >= pageCount) ? 0 : currentPageIndex + 1
|
||||
if currentPageIndex == pageCount - 1 {
|
||||
candidateView.highlightedIndex = min(lastPageContentCount - 1, candidateView.highlightedIndex)
|
||||
|
@ -444,9 +418,9 @@ public class ctlCandidateUniversal: ctlCandidate {
|
|||
}
|
||||
|
||||
@discardableResult override public func showPreviousPage() -> Bool {
|
||||
guard delegate != nil else { return false }
|
||||
guard let delegate = delegate else { return false }
|
||||
if pageCount == 1 { return highlightPreviousCandidate() }
|
||||
if currentPageIndex == 0 { IMEApp.buzz() }
|
||||
if currentPageIndex == 0 { delegate.buzz() }
|
||||
currentPageIndex = (currentPageIndex == 0) ? pageCount - 1 : currentPageIndex - 1
|
||||
if currentPageIndex == pageCount - 1 {
|
||||
candidateView.highlightedIndex = min(lastPageContentCount - 1, candidateView.highlightedIndex)
|
||||
|
@ -459,7 +433,7 @@ public class ctlCandidateUniversal: ctlCandidate {
|
|||
@discardableResult override public func highlightNextCandidate() -> Bool {
|
||||
guard let delegate = delegate else { return false }
|
||||
selectedCandidateIndex =
|
||||
(selectedCandidateIndex + 1 >= delegate.candidateCountForController(self))
|
||||
(selectedCandidateIndex + 1 >= delegate.candidatePairs().count)
|
||||
? 0 : selectedCandidateIndex + 1
|
||||
return true
|
||||
}
|
||||
|
@ -468,7 +442,7 @@ public class ctlCandidateUniversal: ctlCandidate {
|
|||
guard let delegate = delegate else { return false }
|
||||
selectedCandidateIndex =
|
||||
(selectedCandidateIndex == 0)
|
||||
? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1
|
||||
? delegate.candidatePairs().count - 1 : selectedCandidateIndex - 1
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -478,7 +452,7 @@ public class ctlCandidateUniversal: ctlCandidate {
|
|||
}
|
||||
|
||||
let result = currentPageIndex * keyLabels.count + index
|
||||
return result < delegate.candidateCountForController(self) ? result : Int.max
|
||||
return result < delegate.candidatePairs().count ? result : Int.max
|
||||
}
|
||||
|
||||
override public var selectedCandidateIndex: Int {
|
||||
|
@ -490,7 +464,7 @@ public class ctlCandidateUniversal: ctlCandidate {
|
|||
return
|
||||
}
|
||||
let keyLabelCount = keyLabels.count
|
||||
if newValue < delegate.candidateCountForController(self) {
|
||||
if newValue < delegate.candidatePairs().count {
|
||||
currentPageIndex = newValue / keyLabelCount
|
||||
candidateView.highlightedIndex = newValue % keyLabelCount
|
||||
layoutCandidateView()
|
||||
|
@ -499,12 +473,12 @@ public class ctlCandidateUniversal: ctlCandidate {
|
|||
}
|
||||
}
|
||||
|
||||
extension ctlCandidateUniversal {
|
||||
extension CtlCandidateUniversal {
|
||||
private var pageCount: Int {
|
||||
guard let delegate = delegate else {
|
||||
return 0
|
||||
}
|
||||
let totalCount = delegate.candidateCountForController(self)
|
||||
let totalCount = delegate.candidatePairs().count
|
||||
let keyLabelCount = keyLabels.count
|
||||
return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0)
|
||||
}
|
||||
|
@ -513,24 +487,22 @@ extension ctlCandidateUniversal {
|
|||
guard let delegate = delegate else {
|
||||
return 0
|
||||
}
|
||||
let totalCount = delegate.candidateCountForController(self)
|
||||
let totalCount = delegate.candidatePairs().count
|
||||
let keyLabelCount = keyLabels.count
|
||||
return totalCount % keyLabelCount
|
||||
}
|
||||
|
||||
private func layoutCandidateView() {
|
||||
guard let delegate = delegate else {
|
||||
return
|
||||
}
|
||||
guard let delegate = delegate, let window = window else { return }
|
||||
|
||||
candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont)
|
||||
var candidates = [(String, String)]()
|
||||
let count = delegate.candidateCountForController(self)
|
||||
let count = delegate.candidatePairs().count
|
||||
let keyLabelCount = keyLabels.count
|
||||
|
||||
let begin = currentPageIndex * keyLabelCount
|
||||
for index in begin..<min(begin + keyLabelCount, count) {
|
||||
let candidate = delegate.ctlCandidate(self, candidateAtIndex: index)
|
||||
let candidate = delegate.candidatePairAt(index)
|
||||
candidates.append(candidate)
|
||||
}
|
||||
candidateView.set(
|
||||
|
@ -540,14 +512,14 @@ extension ctlCandidateUniversal {
|
|||
var frameRect = candidateView.frame
|
||||
frameRect.size = newSize
|
||||
candidateView.frame = frameRect
|
||||
let counterHeight: CGFloat = newSize.height - 24
|
||||
let counterHeight: Double = newSize.height - 24.0
|
||||
|
||||
if pageCount > 1, PrefMgr.shared.showPageButtonsInCandidateWindow {
|
||||
if pageCount > 1, showPageButtons {
|
||||
var buttonRect = nextPageButton.frame
|
||||
let spacing: CGFloat = 0.0
|
||||
let spacing = 0.0
|
||||
|
||||
if currentLayout == .horizontal { buttonRect.size.height = floor(newSize.height / 2) }
|
||||
let buttonOriginY: CGFloat = {
|
||||
let buttonOriginY: Double = {
|
||||
if currentLayout == .vertical {
|
||||
return counterHeight
|
||||
}
|
||||
|
@ -582,12 +554,12 @@ extension ctlCandidateUniversal {
|
|||
|
||||
rect.size.height += 3
|
||||
rect.size.width += 4
|
||||
let rectOriginY: CGFloat =
|
||||
let rectOriginY: Double =
|
||||
(currentLayout == .horizontal)
|
||||
? (newSize.height - rect.height) / 2
|
||||
: counterHeight
|
||||
let rectOriginX: CGFloat =
|
||||
PrefMgr.shared.showPageButtonsInCandidateWindow
|
||||
let rectOriginX: Double =
|
||||
showPageButtons
|
||||
? newSize.width
|
||||
: newSize.width + 4
|
||||
rect.origin = NSPoint(x: rectOriginX, y: rectOriginY)
|
||||
|
@ -598,12 +570,12 @@ extension ctlCandidateUniversal {
|
|||
pageCounterLabel.isHidden = true
|
||||
}
|
||||
|
||||
frameRect = window?.frame ?? NSRect.seniorTheBeast
|
||||
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)
|
||||
window.setFrame(frameRect, display: false)
|
||||
candidateView.setNeedsDisplay(candidateView.bounds)
|
||||
}
|
||||
|
||||
|
@ -619,6 +591,6 @@ extension ctlCandidateUniversal {
|
|||
}
|
||||
|
||||
@objc private func candidateViewMouseDidClick(_: Any) {
|
||||
delegate?.candidateSelected(at: selectedCandidateIndex)
|
||||
delegate?.candidatePairSelected(at: selectedCandidateIndex)
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@ extension Preferences {
|
|||
}
|
||||
}
|
||||
.modifier(Section.LabelWidthModifier(maximumWidth: $maximumLabelWidth))
|
||||
.frame(width: CGFloat(contentWidth), alignment: .leading)
|
||||
.frame(width: Double(contentWidth), alignment: .leading)
|
||||
.padding(.vertical, 20)
|
||||
.padding(.horizontal, 30)
|
||||
}
|
||||
|
@ -65,9 +65,9 @@ extension Preferences {
|
|||
if index != sections.count - 1, sections[index].bottomDivider {
|
||||
Divider()
|
||||
// Strangely doesn't work without width being specified. Probably because of custom alignment.
|
||||
.frame(width: CGFloat(contentWidth), height: 20)
|
||||
.frame(width: Double(contentWidth), height: 20)
|
||||
.alignmentGuide(.preferenceSectionLabel) {
|
||||
$0[.leading] + CGFloat(max(minimumLabelWidth, maximumLabelWidth))
|
||||
$0[.leading] + Double(max(minimumLabelWidth, maximumLabelWidth))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,8 +77,8 @@ final class SegmentedControlStyleViewController: NSViewController, PreferencesSt
|
|||
)
|
||||
}()
|
||||
|
||||
let segmentBorderWidth = CGFloat(preferencePanes.count) + 1
|
||||
let segmentWidth = segmentSize.width * CGFloat(preferencePanes.count) + segmentBorderWidth
|
||||
let segmentBorderWidth = Double(preferencePanes.count) + 1
|
||||
let segmentWidth = segmentSize.width * Double(preferencePanes.count) + segmentBorderWidth
|
||||
let segmentHeight = segmentSize.height
|
||||
segmentedControl.frame = CGRect(x: 0, y: 0, width: segmentWidth, height: segmentHeight)
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,26 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "CandidateWindow",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "CandidateWindow",
|
||||
targets: ["CandidateWindow"]
|
||||
)
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../vChewing_Shared")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "CandidateWindow",
|
||||
dependencies: [
|
||||
.product(name: "Shared", package: "vChewing_Shared")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
# CandidateWindow
|
||||
|
||||
用以定義與威注音的選字窗有關的基礎內容,目前尚未完工。
|
||||
|
||||
```
|
||||
// (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.
|
||||
```
|
|
@ -0,0 +1,147 @@
|
|||
// (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.
|
||||
|
||||
import Cocoa
|
||||
import Shared
|
||||
|
||||
open class CtlCandidate: NSWindowController, CtlCandidateProtocol {
|
||||
open var showPageButtons: Bool = false
|
||||
open var currentLayout: CandidateLayout = .horizontal
|
||||
open var locale: String = ""
|
||||
open var useLangIdentifier: Bool = false
|
||||
|
||||
open func highlightedColor() -> NSColor {
|
||||
var result = NSColor.alternateSelectedControlColor
|
||||
var colorBlendAmount: Double = NSApplication.isDarkMode ? 0.3 : 0.0
|
||||
if #available(macOS 10.14, *), !NSApplication.isDarkMode, locale == "zh-Hant" {
|
||||
colorBlendAmount = 0.15
|
||||
}
|
||||
// The background color of the highlightened candidate
|
||||
switch locale {
|
||||
case "zh-Hans":
|
||||
result = NSColor.systemRed
|
||||
case "zh-Hant":
|
||||
result = NSColor.systemBlue
|
||||
case "ja":
|
||||
result = NSColor.systemBrown
|
||||
default: break
|
||||
}
|
||||
var blendingAgainstTarget: NSColor = NSApplication.isDarkMode ? NSColor.black : NSColor.white
|
||||
if #unavailable(macOS 10.14) {
|
||||
colorBlendAmount = 0.3
|
||||
blendingAgainstTarget = NSColor.white
|
||||
}
|
||||
return result.blended(withFraction: colorBlendAmount, of: blendingAgainstTarget)!
|
||||
}
|
||||
|
||||
open weak var delegate: CtlCandidateDelegate? {
|
||||
didSet {
|
||||
reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
open var windowTopLeftPoint: NSPoint {
|
||||
get {
|
||||
guard let frameRect = window?.frame else { return NSPoint.zero }
|
||||
return NSPoint(x: frameRect.minX, y: frameRect.maxY)
|
||||
}
|
||||
set {
|
||||
DispatchQueue.main.async {
|
||||
self.set(windowTopLeftPoint: newValue, bottomOutOfScreenAdjustmentHeight: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open var selectedCandidateIndex: Int = .max
|
||||
open var visible = false {
|
||||
didSet {
|
||||
NSObject.cancelPreviousPerformRequests(withTarget: self)
|
||||
DispatchQueue.main.async { [self] in
|
||||
_ = visible ? window?.orderFront(self) : window?.orderOut(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public required init(_: CandidateLayout = .horizontal) {
|
||||
super.init(window: .init())
|
||||
visible = false
|
||||
}
|
||||
|
||||
/// Sets the location of the candidate window.
|
||||
///
|
||||
/// Please note that the method has side effects that modifies
|
||||
/// `windowTopLeftPoint` to make the candidate window to stay in at least
|
||||
/// in a screen.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - windowTopLeftPoint: The given location.
|
||||
/// - height: The height that helps the window not to be out of the bottom
|
||||
/// of a screen.
|
||||
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: Double) {
|
||||
DispatchQueue.main.async { [self] in
|
||||
guard let window = window, var screenFrame = NSScreen.main?.visibleFrame else { return }
|
||||
let windowSize = window.frame.size
|
||||
|
||||
var adjustedPoint = windowTopLeftPoint
|
||||
var delta = heightDelta
|
||||
for frame in NSScreen.screens.map(\.visibleFrame).filter({ $0.contains(windowTopLeftPoint) }) {
|
||||
screenFrame = frame
|
||||
break
|
||||
}
|
||||
|
||||
if delta > screenFrame.size.height / 2.0 { delta = 0.0 }
|
||||
|
||||
if adjustedPoint.y < screenFrame.minY + windowSize.height {
|
||||
adjustedPoint.y = windowTopLeftPoint.y + windowSize.height + delta
|
||||
}
|
||||
adjustedPoint.y = min(adjustedPoint.y, screenFrame.maxY - 1.0)
|
||||
adjustedPoint.x = min(max(adjustedPoint.x, screenFrame.minX), screenFrame.maxX - windowSize.width - 1.0)
|
||||
|
||||
window.setFrameTopLeftPoint(adjustedPoint)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Contents that are not needed to be implemented here.
|
||||
|
||||
@available(*, unavailable)
|
||||
public required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
open var keyLabels: [CandidateKeyLabel] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
|
||||
.map {
|
||||
CandidateKeyLabel(key: $0, displayedText: $0)
|
||||
}
|
||||
|
||||
open var candidateFont = NSFont.systemFont(ofSize: 18)
|
||||
open var keyLabelFont = NSFont.monospacedDigitSystemFont(ofSize: 14, weight: .medium)
|
||||
|
||||
open var tooltip: String = ""
|
||||
|
||||
@discardableResult open func highlightNextCandidate() -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
@discardableResult open func highlightPreviousCandidate() -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
@discardableResult open func showNextPage() -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
@discardableResult open func showPreviousPage() -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
open func candidateIndexAtKeyLabelIndex(_: Int) -> Int {
|
||||
Int.max
|
||||
}
|
||||
|
||||
open func reloadData() {}
|
||||
}
|
|
@ -221,6 +221,15 @@ extension vChewingLM {
|
|||
return !unigramsFor(key: key).isEmpty
|
||||
}
|
||||
|
||||
/// 根據給定的索引鍵和資料值,確認是否有該具體的資料值在庫。
|
||||
/// - Parameters:
|
||||
/// - key: 索引鍵。
|
||||
/// - value: 資料值。
|
||||
/// - Returns: 是否在庫。
|
||||
public func hasKeyValuePairFor(key: String, value: String) -> Bool {
|
||||
unigramsFor(key: key).map(\.value).contains(value)
|
||||
}
|
||||
|
||||
/// 給定讀音字串,讓 LMI 給出對應的經過處理的單元圖陣列。
|
||||
/// - Parameter key: 給定的讀音字串。
|
||||
/// - Returns: 對應的經過處理的單元圖陣列。
|
||||
|
|
|
@ -13,7 +13,7 @@ Megrez Engine is a module made for processing lingual data of an input method. T
|
|||
|
||||
### §1. 初期化
|
||||
|
||||
在你的 ctlInputMethod (InputMethodController) 或者 KeyHandler 內初期化一份 Megrez.Compositor 組字器副本(這裡將該副本命名為「`compositor`」)。由於 Megrez.Compositor 的型別是 Struct 型別(為了讓 Compositor 可以 deep copy),所以其副本可以用 var 來宣告。
|
||||
在你的 IMKInputController 或者 KeyHandler 內初期化一份 Megrez.Compositor 組字器副本(這裡將該副本命名為「`compositor`」)。由於 Megrez.Compositor 的型別是 Struct 型別(為了讓 Compositor 可以 deep copy),所以其副本可以用 var 來宣告。
|
||||
|
||||
以 KeyHandler 為例:
|
||||
```swift
|
||||
|
@ -24,17 +24,17 @@ class KeyHandler {
|
|||
}
|
||||
```
|
||||
|
||||
以 ctlInputMethod 為例:
|
||||
以 IMKInputController 為例:
|
||||
```swift
|
||||
@objc(ctlInputMethod) // 根據 info.plist 內的情況來確定型別的命名
|
||||
class ctlInputMethod: IMKInputController {
|
||||
@objc(IMKMyInputController) // 根據 info.plist 內的情況來確定型別的命名
|
||||
class IMKMyInputController: IMKInputController {
|
||||
// 先設定好變數
|
||||
var compositor: Megrez.Compositor = .init()
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
由於 Swift 會在某個大副本(KeyHandler 或者 ctlInputMethod 副本)被銷毀的時候自動銷毀其中的全部副本,所以 Megrez.Compositor 的副本初期化沒必要寫在 init() 當中。但你很可能會想在 init() 時指定 Tekkon.Composer 所對接的語言模組型別、以及其可以允許的最大詞長。
|
||||
由於 Swift 會在某個大副本(KeyHandler 或者 IMKInputController 副本)被銷毀的時候自動銷毀其中的全部副本,所以 Megrez.Compositor 的副本初期化沒必要寫在 init() 當中。但你很可能會想在 init() 時指定 Tekkon.Composer 所對接的語言模組型別、以及其可以允許的最大詞長。
|
||||
|
||||
這裡就需要在 init() 時使用參數:
|
||||
```swift
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,22 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "NotifierUI",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "NotifierUI",
|
||||
targets: ["NotifierUI"]
|
||||
)
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "NotifierUI",
|
||||
dependencies: []
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
# NotifierUI
|
||||
|
||||
威注音的飄雲通知視窗。
|
||||
|
||||
```
|
||||
// (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.
|
||||
```
|
|
@ -0,0 +1,155 @@
|
|||
// (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.
|
||||
|
||||
import Cocoa
|
||||
|
||||
public class Notifier: NSWindowController {
|
||||
public static func notify(message: String) {
|
||||
Self.message = message
|
||||
}
|
||||
|
||||
static var message: String = "" {
|
||||
didSet {
|
||||
if !Self.message.isEmpty {
|
||||
Self.message = Notifier(message).blankValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private Declarations
|
||||
|
||||
private static var instanceStack: [Notifier] = []
|
||||
private let blankValue = ""
|
||||
|
||||
@discardableResult private init(_ message: String) {
|
||||
let rawMessage = message.replacingOccurrences(of: "\n", with: "")
|
||||
guard let screenRect = NSScreen.main?.visibleFrame, !rawMessage.isEmpty else {
|
||||
super.init(window: nil)
|
||||
return
|
||||
}
|
||||
let kLargeFontSize: Double = 17
|
||||
let kSmallFontSize: Double = 15
|
||||
let messageArray = message.components(separatedBy: "\n")
|
||||
|
||||
let paraStyle = NSMutableParagraphStyle()
|
||||
paraStyle.setParagraphStyle(NSParagraphStyle.default)
|
||||
paraStyle.alignment = .center
|
||||
let attrTitle: [NSAttributedString.Key: AnyObject] = [
|
||||
.foregroundColor: NSColor.controlTextColor,
|
||||
.font: NSFont.boldSystemFont(ofSize: kLargeFontSize),
|
||||
.paragraphStyle: paraStyle,
|
||||
]
|
||||
let attrString = NSMutableAttributedString(string: messageArray[0], attributes: attrTitle)
|
||||
let attrAlt: [NSAttributedString.Key: AnyObject] = [
|
||||
.foregroundColor: NSColor.secondaryLabelColor,
|
||||
.font: NSFont.systemFont(ofSize: kSmallFontSize),
|
||||
.paragraphStyle: paraStyle,
|
||||
]
|
||||
let additionalString = messageArray.count > 1 ? "\n\(messageArray[1])" : ""
|
||||
let attrStringAlt = NSMutableAttributedString(string: additionalString, attributes: attrAlt)
|
||||
attrString.insert(attrStringAlt, at: attrString.length)
|
||||
|
||||
let textRect: NSRect = attrString.boundingRect(
|
||||
with: NSSize(width: 1600.0, height: 1600.0), options: [.usesLineFragmentOrigin]
|
||||
)
|
||||
let windowWidth = Double(4) * kLargeFontSize + textRect.width
|
||||
let contentRect = NSRect(x: 0, y: 0, width: windowWidth, height: 60.0)
|
||||
var windowRect = contentRect
|
||||
windowRect.origin.x = screenRect.maxX - windowRect.width - 10
|
||||
windowRect.origin.y = screenRect.maxY - windowRect.height - 10
|
||||
let styleMask: NSWindow.StyleMask = [.borderless]
|
||||
|
||||
let transparentVisualEffect = NSVisualEffectView()
|
||||
transparentVisualEffect.blendingMode = .behindWindow
|
||||
transparentVisualEffect.state = .active
|
||||
|
||||
let theWindow = NSWindow(
|
||||
contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false
|
||||
)
|
||||
theWindow.contentView = transparentVisualEffect
|
||||
theWindow.isMovableByWindowBackground = true
|
||||
theWindow.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel))
|
||||
theWindow.hasShadow = true
|
||||
theWindow.backgroundColor = .textBackgroundColor
|
||||
theWindow.title = ""
|
||||
theWindow.titlebarAppearsTransparent = true
|
||||
theWindow.titleVisibility = .hidden
|
||||
theWindow.showsToolbarButton = false
|
||||
theWindow.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true
|
||||
theWindow.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isHidden = true
|
||||
theWindow.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true
|
||||
theWindow.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true
|
||||
theWindow.isReleasedWhenClosed = true
|
||||
theWindow.isMovable = false
|
||||
|
||||
let lblMessage = NSTextField()
|
||||
lblMessage.attributedStringValue = attrString
|
||||
lblMessage.drawsBackground = false
|
||||
lblMessage.font = .boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular))
|
||||
lblMessage.frame = contentRect
|
||||
lblMessage.isBezeled = false
|
||||
lblMessage.isEditable = false
|
||||
lblMessage.isSelectable = false
|
||||
lblMessage.textColor = .controlTextColor
|
||||
theWindow.contentView?.addSubview(lblMessage)
|
||||
|
||||
let x = lblMessage.frame.origin.x
|
||||
let y = ((theWindow.frame.height) - textRect.height) / 1.9
|
||||
let newFrame = NSRect(x: x, y: y, width: theWindow.frame.width, height: textRect.height)
|
||||
lblMessage.frame = newFrame
|
||||
|
||||
super.init(window: theWindow)
|
||||
display()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func close() {
|
||||
super.close()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private Functions
|
||||
|
||||
extension Notifier {
|
||||
private func shiftExistingWindowPositions() {
|
||||
guard let window = window, !Self.instanceStack.isEmpty else { return }
|
||||
for theInstanceWindow in Self.instanceStack.compactMap(\.window) {
|
||||
var theOrigin = theInstanceWindow.frame
|
||||
theOrigin.origin.y -= (10 + window.frame.height)
|
||||
theInstanceWindow.setFrame(theOrigin, display: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func fadeIn() {
|
||||
guard let window = window else { return }
|
||||
let afterRect = window.frame
|
||||
var beforeRect = afterRect
|
||||
beforeRect.origin.x -= 20
|
||||
window.setFrame(beforeRect, display: true)
|
||||
window.orderFront(self)
|
||||
window.setFrame(afterRect, display: true, animate: true)
|
||||
}
|
||||
|
||||
private func display() {
|
||||
let existingInstanceArray = Self.instanceStack.compactMap(\.window)
|
||||
if !existingInstanceArray.isEmpty {
|
||||
existingInstanceArray.forEach { $0.alphaValue *= 0.5 }
|
||||
}
|
||||
shiftExistingWindowPositions()
|
||||
fadeIn()
|
||||
Self.instanceStack.insert(self, at: 0)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
self.close()
|
||||
Self.instanceStack.removeAll(where: { $0.window == nil })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,26 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "PopupCompositionBuffer",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "PopupCompositionBuffer",
|
||||
targets: ["PopupCompositionBuffer"]
|
||||
)
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../vChewing_Shared")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "PopupCompositionBuffer",
|
||||
dependencies: [
|
||||
.product(name: "Shared", package: "vChewing_Shared")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
# NotifierUI
|
||||
|
||||
威注音的浮動組字窗。
|
||||
|
||||
```
|
||||
// (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.
|
||||
```
|
|
@ -6,7 +6,10 @@
|
|||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
public class ctlPopupCompositionBuffer: NSWindowController {
|
||||
import Cocoa
|
||||
import Shared
|
||||
|
||||
public class PopupCompositionBuffer: NSWindowController {
|
||||
public var isTypingDirectionVertical = false {
|
||||
didSet {
|
||||
if #unavailable(macOS 10.14) {
|
||||
|
@ -33,7 +36,7 @@ public class ctlPopupCompositionBuffer: NSWindowController {
|
|||
panel.hasShadow = true
|
||||
panel.backgroundColor = NSColor.controlBackgroundColor
|
||||
panel.styleMask = .fullSizeContentView
|
||||
|
||||
panel.isMovable = false
|
||||
messageTextField = NSTextField()
|
||||
messageTextField.isEditable = false
|
||||
messageTextField.isSelectable = false
|
||||
|
@ -58,7 +61,7 @@ public class ctlPopupCompositionBuffer: NSWindowController {
|
|||
return
|
||||
}
|
||||
|
||||
let attrString: NSMutableAttributedString = .init(string: state.data.displayedTextConverted)
|
||||
let attrString: NSMutableAttributedString = .init(string: state.displayedTextConverted)
|
||||
let verticalAttributes: [NSAttributedString.Key: Any] = [
|
||||
.verticalGlyphForm: true,
|
||||
.paragraphStyle: {
|
||||
|
@ -75,7 +78,7 @@ public class ctlPopupCompositionBuffer: NSWindowController {
|
|||
|
||||
if isTypingDirectionVertical {
|
||||
attrString.setAttributes(
|
||||
verticalAttributes, range: NSRange(location: 0, length: attrString.string.utf16.count)
|
||||
verticalAttributes, range: NSRange(location: 0, length: attrString.length)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -95,8 +98,8 @@ public class ctlPopupCompositionBuffer: NSWindowController {
|
|||
attrString.setAttributes(
|
||||
markerAttributes,
|
||||
range: NSRange(
|
||||
location: state.data.u16MarkedRange.lowerBound,
|
||||
length: state.data.u16MarkedRange.upperBound - state.data.u16MarkedRange.lowerBound
|
||||
location: state.u16MarkedRange.lowerBound,
|
||||
length: state.u16MarkedRange.upperBound - state.u16MarkedRange.lowerBound
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -123,12 +126,12 @@ public class ctlPopupCompositionBuffer: NSWindowController {
|
|||
isTypingDirectionVertical
|
||||
? NSMutableAttributedString(string: "▔", attributes: cursorAttributes)
|
||||
: NSMutableAttributedString(string: "_", attributes: cursorAttributes)
|
||||
attrString.insert(attrCursor, at: state.data.u16Cursor)
|
||||
attrString.insert(attrCursor, at: state.u16Cursor)
|
||||
|
||||
textShown = attrString
|
||||
messageTextField.maximumNumberOfLines = 1
|
||||
if let editor = messageTextField.currentEditor() {
|
||||
editor.selectedRange = NSRange(state.data.u16MarkedRange)
|
||||
editor.selectedRange = NSRange(state.u16MarkedRange)
|
||||
}
|
||||
window?.orderFront(nil)
|
||||
set(windowOrigin: point)
|
||||
|
@ -165,7 +168,7 @@ public class ctlPopupCompositionBuffer: NSWindowController {
|
|||
with: NSSize(width: 1600.0, height: 1600.0),
|
||||
options: [.usesLineFragmentOrigin, .usesFontLeading]
|
||||
)
|
||||
rect.size.width = max(rect.size.width, 20 * CGFloat(attrString.string.count)) + 2
|
||||
rect.size.width = max(rect.size.width, 20 * Double(attrString.string.count)) + 2
|
||||
rect.size.height *= 1.2
|
||||
rect.size.height = max(22, rect.size.height)
|
||||
if isTypingDirectionVertical {
|
|
@ -0,0 +1,41 @@
|
|||
// (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.
|
||||
|
||||
import Cocoa
|
||||
|
||||
public protocol CtlCandidateDelegate: AnyObject {
|
||||
func candidatePairs() -> [(String, String)]
|
||||
func candidatePairAt(_ index: Int) -> (String, String)
|
||||
func candidatePairSelected(at index: Int)
|
||||
func buzz()
|
||||
func kanjiConversionIfRequired(_ target: String) -> String
|
||||
}
|
||||
|
||||
public protocol CtlCandidateProtocol {
|
||||
var locale: String { get set }
|
||||
var currentLayout: CandidateLayout { get set }
|
||||
var delegate: CtlCandidateDelegate? { get set }
|
||||
var selectedCandidateIndex: Int { get set }
|
||||
var visible: Bool { get set }
|
||||
var windowTopLeftPoint: NSPoint { get set }
|
||||
var keyLabels: [CandidateKeyLabel] { get set }
|
||||
var keyLabelFont: NSFont { get set }
|
||||
var candidateFont: NSFont { get set }
|
||||
var tooltip: String { get set }
|
||||
var useLangIdentifier: Bool { get set }
|
||||
var showPageButtons: Bool { get set }
|
||||
|
||||
init(_ layout: CandidateLayout)
|
||||
func reloadData()
|
||||
func showNextPage() -> Bool
|
||||
func showPreviousPage() -> Bool
|
||||
func highlightNextCandidate() -> Bool
|
||||
func highlightPreviousCandidate() -> Bool
|
||||
func candidateIndexAtKeyLabelIndex(_: Int) -> Int
|
||||
func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: Double)
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// (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.
|
||||
|
||||
import Cocoa
|
||||
|
||||
// 所有 IMEState 均遵守該協定:
|
||||
public protocol IMEStateProtocol {
|
||||
var type: StateType { get }
|
||||
var data: StateDataProtocol { get }
|
||||
var isASCIIMode: Bool { get set }
|
||||
var isVerticalTyping: Bool { get set }
|
||||
var isVerticalCandidateWindow: Bool { get set }
|
||||
var candidates: [(String, String)] { get set }
|
||||
var hasComposition: Bool { get }
|
||||
var isCandidateContainer: Bool { get }
|
||||
var displayedText: String { get }
|
||||
var displayedTextConverted: String { get }
|
||||
var textToCommit: String { get set }
|
||||
var tooltip: String { get set }
|
||||
var attributedString: NSAttributedString { get }
|
||||
var convertedToInputting: IMEStateProtocol { get }
|
||||
var isFilterable: Bool { get }
|
||||
var isMarkedLengthValid: Bool { get }
|
||||
var node: CandidateNode { get set }
|
||||
var displayTextSegments: [String] { get }
|
||||
var tooltipBackupForInputting: String { get set }
|
||||
var markedRange: Range<Int> { get }
|
||||
var u16MarkedRange: Range<Int> { get }
|
||||
var u16Cursor: Int { get }
|
||||
var cursor: Int { get set }
|
||||
var marker: Int { get set }
|
||||
}
|
||||
|
||||
public protocol StateDataProtocol {
|
||||
var cursor: Int { get set }
|
||||
var marker: Int { get set }
|
||||
var markedRange: Range<Int> { get }
|
||||
var u16MarkedRange: Range<Int> { get }
|
||||
var u16Cursor: Int { get }
|
||||
var textToCommit: String { get set }
|
||||
var markedReadings: [String] { get set }
|
||||
var displayTextSegments: [String] { get set }
|
||||
var isFilterable: Bool { get }
|
||||
var isVerticalTyping: Bool { get set }
|
||||
var isMarkedLengthValid: Bool { get }
|
||||
var candidates: [(String, String)] { get set }
|
||||
var displayedText: String { get set }
|
||||
var displayedTextConverted: String { get }
|
||||
var tooltipBackupForInputting: String { get set }
|
||||
var tooltip: String { get set }
|
||||
var attributedStringNormal: NSAttributedString { get }
|
||||
var attributedStringMarking: NSAttributedString { get }
|
||||
var attributedStringPlaceholder: NSAttributedString { get }
|
||||
var userPhraseDumped: String { get }
|
||||
var userPhraseDumpedConverted: String { get }
|
||||
var doesUserPhraseExist: Bool { get }
|
||||
var tooltipColorState: TooltipColorState { get set }
|
||||
mutating func updateTooltipForMarking()
|
||||
}
|
|
@ -103,6 +103,50 @@ public enum UserDef: String, CaseIterable {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Enums and Structs used by Candidate Window
|
||||
|
||||
public enum CandidateLayout {
|
||||
case horizontal
|
||||
case vertical
|
||||
}
|
||||
|
||||
public struct CandidateKeyLabel {
|
||||
public private(set) var key: String
|
||||
public private(set) var displayedText: String
|
||||
|
||||
public init(key: String, displayedText: String) {
|
||||
self.key = key
|
||||
self.displayedText = displayedText
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Tooltip Color States
|
||||
|
||||
public enum TooltipColorState {
|
||||
case normal
|
||||
case redAlert
|
||||
case warning
|
||||
case denialOverflow
|
||||
case denialInsufficiency
|
||||
case prompt
|
||||
}
|
||||
|
||||
// MARK: - IMEState types.
|
||||
|
||||
// 用以讓每個狀態自描述的 enum。
|
||||
public enum StateType: String {
|
||||
case ofDeactivated = "Deactivated"
|
||||
case ofEmpty = "Empty"
|
||||
case ofAbortion = "Abortion" // 該狀態會自動轉為 Empty
|
||||
case ofCommitting = "Committing"
|
||||
case ofAssociates = "Associates"
|
||||
case ofNotEmpty = "NotEmpty"
|
||||
case ofInputting = "Inputting"
|
||||
case ofMarking = "Marking"
|
||||
case ofCandidates = "Candidates"
|
||||
case ofSymbolTable = "SymbolTable"
|
||||
}
|
||||
|
||||
// MARK: - Parser for Syllable composer
|
||||
|
||||
public enum KeyboardParser: Int, CaseIterable {
|
||||
|
|
|
@ -23,7 +23,7 @@ Regarding pinyin input support, we only support: Hanyu Pinyin, Secondary Pinyin,
|
|||
|
||||
### §1. 初期化
|
||||
|
||||
在你的 ctlInputMethod (InputMethodController) 或者 KeyHandler 內初期化一份 Tekkon.Composer 注拼槽副本(這裡將該副本命名為「`_composer`」)。由於 Tekkon.Composer 的型別是 Struct 型別,所以其副本必須為變數(var),否則無法自我 mutate。
|
||||
在你的 IMKInputController (InputMethodController) 或者 KeyHandler 內初期化一份 Tekkon.Composer 注拼槽副本(這裡將該副本命名為「`_composer`」)。由於 Tekkon.Composer 的型別是 Struct 型別,所以其副本必須為變數(var),否則無法自我 mutate。
|
||||
|
||||
以 KeyHandler 為例:
|
||||
```swift
|
||||
|
@ -34,10 +34,10 @@ class KeyHandler: NSObject {
|
|||
}
|
||||
```
|
||||
|
||||
以 ctlInputMethod 為例:
|
||||
以 IMKInputController 為例:
|
||||
```swift
|
||||
@objc(ctlInputMethod) // 根據 info.plist 內的情況來確定型別的命名
|
||||
class ctlInputMethod: IMKInputController {
|
||||
@objc(IMKMyInputController) // 根據 info.plist 內的情況來確定型別的命名
|
||||
class IMKMyInputController: IMKInputController {
|
||||
// 先設定好變數
|
||||
var _composer: Tekkon.Composer = .init()
|
||||
...
|
||||
|
@ -45,7 +45,7 @@ class ctlInputMethod: IMKInputController {
|
|||
```
|
||||
|
||||
|
||||
由於 Swift 會在某個大副本(KeyHandler 或者 ctlInputMethod 副本)被銷毀的時候自動銷毀其中的全部副本,所以 Tekkon.Composer 的副本初期化沒必要寫在 init() 當中。但你很可能會想在 init() 時指定 Tekkon.Composer 所使用的注音排列(是大千?還是倚天傳統?還是神通?等)。
|
||||
由於 Swift 會在某個大副本(KeyHandler 或者 IMKInputController 副本)被銷毀的時候自動銷毀其中的全部副本,所以 Tekkon.Composer 的副本初期化沒必要寫在 init() 當中。但你很可能會想在 init() 時指定 Tekkon.Composer 所使用的注音排列(是大千?還是倚天傳統?還是神通?等)。
|
||||
|
||||
這裡就需要在 _composer 這個副本所在的型別當中額外寫一個過程函式。
|
||||
|
||||
|
@ -159,9 +159,9 @@ final class TekkonTests: XCTestCase {
|
|||
|
||||
#### // 2. 訊號處理
|
||||
|
||||
無論是 KeyHandler 還是 ctlInputMethod 都得要處理被傳入的 NSEvent 當中的 charCode 訊號。
|
||||
無論是 KeyHandler 還是 IMKInputController 都得要處理被傳入的 NSEvent 當中的 charCode 訊號。
|
||||
|
||||
比如 ctlInputMethod 內:
|
||||
比如 IMKInputController 內:
|
||||
```swift
|
||||
func handleInputText(_ inputText: String?, key keyCode: Int, modifiers flags: Int, client: Any?) -> Bool {
|
||||
...
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,30 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "TooltipUI",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "TooltipUI",
|
||||
targets: ["TooltipUI"]
|
||||
)
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../Fuziki_NSAttributedTextView"),
|
||||
.package(path: "../vChewing_CocoaExtension"),
|
||||
.package(path: "../vChewing_Shared"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "TooltipUI",
|
||||
dependencies: [
|
||||
.product(name: "NSAttributedTextView", package: "Fuziki_NSAttributedTextView"),
|
||||
.product(name: "CocoaExtension", package: "vChewing_CocoaExtension"),
|
||||
.product(name: "Shared", package: "vChewing_Shared"),
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
# NotifierUI
|
||||
|
||||
威注音的工具提示視窗。
|
||||
|
||||
```
|
||||
// (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.
|
||||
```
|
|
@ -6,18 +6,12 @@
|
|||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import Cocoa
|
||||
import CocoaExtension
|
||||
import NSAttributedTextView
|
||||
import Shared
|
||||
|
||||
public class ctlTooltip: NSWindowController {
|
||||
public enum ColorStates {
|
||||
case normal
|
||||
case redAlert
|
||||
case warning
|
||||
case denialOverflow
|
||||
case denialInsufficiency
|
||||
case prompt
|
||||
}
|
||||
|
||||
public class TooltipUI: NSWindowController {
|
||||
private var messageText: NSAttributedTooltipTextView
|
||||
private var tooltip: String = "" {
|
||||
didSet {
|
||||
|
@ -43,6 +37,7 @@ public class ctlTooltip: NSWindowController {
|
|||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 2)
|
||||
panel.hasShadow = true
|
||||
panel.backgroundColor = NSColor.controlBackgroundColor
|
||||
panel.isMovable = false
|
||||
messageText = NSAttributedTooltipTextView()
|
||||
messageText.backgroundColor = NSColor.controlBackgroundColor
|
||||
messageText.textColor = NSColor.textColor
|
||||
|
@ -57,7 +52,7 @@ public class ctlTooltip: NSWindowController {
|
|||
|
||||
public func show(
|
||||
tooltip: String = "", at point: NSPoint,
|
||||
bottomOutOfScreenAdjustmentHeight heightDelta: CGFloat,
|
||||
bottomOutOfScreenAdjustmentHeight heightDelta: Double,
|
||||
direction: NSAttributedTooltipTextView.writingDirection = .horizontal
|
||||
) {
|
||||
self.direction = direction
|
||||
|
@ -66,7 +61,7 @@ public class ctlTooltip: NSWindowController {
|
|||
set(windowTopLeftPoint: point, bottomOutOfScreenAdjustmentHeight: heightDelta)
|
||||
}
|
||||
|
||||
public func setColor(state: ColorStates) {
|
||||
public func setColor(state: TooltipColorState) {
|
||||
var backgroundColor = NSColor.controlBackgroundColor
|
||||
var textColor = NSColor.textColor
|
||||
switch state {
|
||||
|
@ -123,7 +118,7 @@ public class ctlTooltip: NSWindowController {
|
|||
window?.orderOut(nil)
|
||||
}
|
||||
|
||||
private func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: CGFloat) {
|
||||
private func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: Double) {
|
||||
guard let window = window else { return }
|
||||
let windowSize = window.frame.size
|
||||
|
|
@ -91,7 +91,7 @@ P.S.: 威注音输入法的 Shift 按键监测功能仅借由对 NSEvent 讯号
|
|||
威注音专案目前仅用到小麦注音的下述程式组件(MIT License):
|
||||
|
||||
- 仅供研发人员调试方便而使用的 App 版安装程式 (by Zonble Yang),不对公众使用。
|
||||
- Voltaire MK2 选字窗、飘云通知视窗 (by Zonble Yang),有大幅度修改。
|
||||
- Voltaire MK2 选字窗 (by Zonble Yang),有大幅度修改。
|
||||
|
||||
威注音专案目前还用到如下的来自 Lukhnos Liu 的算法:
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ P.S.: 威注音輸入法的 Shift 按鍵監測功能僅藉由對 NSEvent 訊號
|
|||
威注音專案目前僅用到小麥注音的下述程式組件(MIT License):
|
||||
|
||||
- 僅供研發人員調試方便而使用的 App 版安裝程式 (by Zonble Yang),不對公眾使用。
|
||||
- Voltaire MK2 選字窗、飄雲通知視窗 (by Zonble Yang),有大幅度修改。
|
||||
- Voltaire MK2 選字窗 (by Zonble Yang),有大幅度修改。
|
||||
|
||||
威注音專案目前還用到如下的來自 Lukhnos Liu 的算法:
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 33487f554d2306f43d100c9c5b8ce99c2109ca52
|
||||
Subproject commit 6f71610f126eee8efcce4a4ff832bec03af7c79b
|
|
@ -73,7 +73,7 @@ public enum ChineseConverter {
|
|||
///
|
||||
/// - Parameter string: Text in Original Script.
|
||||
/// - Returns: Text converted to Different Script.
|
||||
public static func crossConvert(_ string: String) -> String? {
|
||||
public static func crossConvert(_ string: String) -> String {
|
||||
switch IMEApp.currentInputMode {
|
||||
case .imeModeCHS:
|
||||
return shared.convert(string, to: .zhHantTW)
|
||||
|
|
|
@ -7,46 +7,9 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import LangModelAssembly
|
||||
import Shared
|
||||
|
||||
// 用以讓每個狀態自描述的 enum。
|
||||
public enum StateType: String {
|
||||
case ofDeactivated = "Deactivated"
|
||||
case ofEmpty = "Empty"
|
||||
case ofAbortion = "Abortion" // 該狀態會自動轉為 Empty
|
||||
case ofCommitting = "Committing"
|
||||
case ofAssociates = "Associates"
|
||||
case ofNotEmpty = "NotEmpty"
|
||||
case ofInputting = "Inputting"
|
||||
case ofMarking = "Marking"
|
||||
case ofCandidates = "Candidates"
|
||||
case ofSymbolTable = "SymbolTable"
|
||||
}
|
||||
|
||||
// 所有 IMEState 均遵守該協定:
|
||||
public protocol IMEStateProtocol {
|
||||
var type: StateType { get }
|
||||
var data: StateData { get }
|
||||
var isASCIIMode: Bool { get set }
|
||||
var isVerticalTyping: Bool { get set }
|
||||
var isVerticalCandidateWindow: Bool { get set }
|
||||
var candidates: [(String, String)] { get }
|
||||
var hasComposition: Bool { get }
|
||||
var isCandidateContainer: Bool { get }
|
||||
var displayedText: String { get }
|
||||
var textToCommit: String { get set }
|
||||
var tooltip: String { get set }
|
||||
var attributedString: NSAttributedString { get }
|
||||
var convertedToInputting: IMEState { get }
|
||||
var isFilterable: Bool { get }
|
||||
var isMarkedLengthValid: Bool { get }
|
||||
var node: CandidateNode { get set }
|
||||
var cursor: Int { get }
|
||||
var displayTextSegments: [String] { get }
|
||||
var tooltipBackupForInputting: String { get set }
|
||||
var markedRange: Range<Int> { get }
|
||||
}
|
||||
|
||||
/// 用以呈現輸入法控制器(ctlInputMethod)的各種狀態。
|
||||
/// 用以呈現輸入法控制器(SessionCtl)的各種狀態。
|
||||
///
|
||||
/// 從實際角度來看,輸入法屬於有限態械(Finite State Machine)。其藉由滑鼠/鍵盤
|
||||
/// 等輸入裝置接收輸入訊號,據此切換至對應的狀態,再根據狀態更新使用者介面內容,
|
||||
|
@ -79,17 +42,17 @@ public protocol IMEStateProtocol {
|
|||
/// - .SymbolTable: 波浪鍵符號選單專用的狀態,有自身的特殊處理。
|
||||
public struct IMEState: IMEStateProtocol {
|
||||
public var type: StateType = .ofEmpty
|
||||
public var data: StateData = .init()
|
||||
public var data: StateDataProtocol = StateData() as StateDataProtocol
|
||||
public var node: CandidateNode = .init(name: "")
|
||||
public var isASCIIMode = false
|
||||
public var isVerticalCandidateWindow = false
|
||||
init(_ data: StateData = .init(), type: StateType = .ofEmpty) {
|
||||
init(_ data: StateDataProtocol = StateData() as StateDataProtocol, type: StateType = .ofEmpty) {
|
||||
self.data = data
|
||||
self.type = type
|
||||
isVerticalTyping = ctlInputMethod.isVerticalTyping
|
||||
isVerticalTyping = SessionCtl.isVerticalTyping
|
||||
}
|
||||
|
||||
init(_ data: StateData = .init(), type: StateType = .ofEmpty, node: CandidateNode) {
|
||||
init(_ data: StateDataProtocol = StateData() as StateDataProtocol, type: StateType = .ofEmpty, node: CandidateNode) {
|
||||
self.data = data
|
||||
self.type = type
|
||||
self.node = node
|
||||
|
@ -105,14 +68,14 @@ extension IMEState {
|
|||
public static func ofAbortion() -> IMEState { .init(type: .ofAbortion) }
|
||||
public static func ofCommitting(textToCommit: String) -> IMEState {
|
||||
var result = IMEState(type: .ofCommitting)
|
||||
result.data.textToCommit = textToCommit
|
||||
result.textToCommit = textToCommit
|
||||
ChineseConverter.ensureCurrencyNumerals(target: &result.data.textToCommit)
|
||||
return result
|
||||
}
|
||||
|
||||
public static func ofAssociates(candidates: [(String, String)]) -> IMEState {
|
||||
var result = IMEState(type: .ofAssociates)
|
||||
result.data.candidates = candidates
|
||||
result.candidates = candidates
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -120,10 +83,10 @@ extension IMEState {
|
|||
var result = IMEState(type: .ofNotEmpty)
|
||||
// 注意資料的設定順序,一定得先設定 displayTextSegments。
|
||||
result.data.displayTextSegments = displayTextSegments.map {
|
||||
if !ctlInputMethod.isVerticalTyping { return $0 }
|
||||
if !SessionCtl.isVerticalTyping { return $0 }
|
||||
guard PrefMgr.shared.hardenVerticalPunctuations else { return $0 }
|
||||
var neta = $0
|
||||
ChineseConverter.hardenVerticalPunctuations(target: &neta, convert: ctlInputMethod.isVerticalTyping)
|
||||
ChineseConverter.hardenVerticalPunctuations(target: &neta, convert: SessionCtl.isVerticalTyping)
|
||||
return neta
|
||||
}
|
||||
|
||||
|
@ -133,7 +96,7 @@ extension IMEState {
|
|||
}
|
||||
|
||||
public static func ofInputting(displayTextSegments: [String], cursor: Int) -> IMEState {
|
||||
var result = IMEState.ofNotEmpty(displayTextSegments: displayTextSegments, cursor: cursor)
|
||||
var result = Self.ofNotEmpty(displayTextSegments: displayTextSegments, cursor: cursor)
|
||||
result.type = .ofInputting
|
||||
return result
|
||||
}
|
||||
|
@ -143,7 +106,7 @@ extension IMEState {
|
|||
)
|
||||
-> IMEState
|
||||
{
|
||||
var result = IMEState.ofNotEmpty(displayTextSegments: displayTextSegments, cursor: cursor)
|
||||
var result = Self.ofNotEmpty(displayTextSegments: displayTextSegments, cursor: cursor)
|
||||
result.type = .ofMarking
|
||||
result.data.marker = marker
|
||||
result.data.markedReadings = markedReadings
|
||||
|
@ -154,7 +117,7 @@ extension IMEState {
|
|||
public static func ofCandidates(candidates: [(String, String)], displayTextSegments: [String], cursor: Int)
|
||||
-> IMEState
|
||||
{
|
||||
var result = IMEState.ofNotEmpty(displayTextSegments: displayTextSegments, cursor: cursor)
|
||||
var result = Self.ofNotEmpty(displayTextSegments: displayTextSegments, cursor: cursor)
|
||||
result.type = .ofCandidates
|
||||
result.data.candidates = candidates
|
||||
return result
|
||||
|
@ -172,35 +135,44 @@ extension IMEState {
|
|||
extension IMEState {
|
||||
public var isFilterable: Bool { data.isFilterable }
|
||||
public var isMarkedLengthValid: Bool { data.isMarkedLengthValid }
|
||||
public var candidates: [(String, String)] { data.candidates }
|
||||
public var displayedText: String { data.displayedText }
|
||||
public var cursor: Int { data.cursor }
|
||||
public var displayedTextConverted: String { data.displayedTextConverted }
|
||||
public var displayTextSegments: [String] { data.displayTextSegments }
|
||||
public var markedRange: Range<Int> { data.markedRange }
|
||||
public var convertedToInputting: IMEState {
|
||||
public var u16MarkedRange: Range<Int> { data.u16MarkedRange }
|
||||
public var u16Cursor: Int { data.u16Cursor }
|
||||
|
||||
public var cursor: Int {
|
||||
get { data.cursor }
|
||||
set { data.cursor = newValue }
|
||||
}
|
||||
|
||||
public var marker: Int {
|
||||
get { data.marker }
|
||||
set { data.marker = newValue }
|
||||
}
|
||||
|
||||
public var convertedToInputting: IMEStateProtocol {
|
||||
if type == .ofInputting { return self }
|
||||
var result = IMEState.ofInputting(displayTextSegments: data.displayTextSegments, cursor: data.cursor)
|
||||
var result = Self.ofInputting(displayTextSegments: data.displayTextSegments, cursor: data.cursor)
|
||||
result.tooltip = data.tooltipBackupForInputting
|
||||
result.isVerticalTyping = isVerticalTyping
|
||||
return result
|
||||
}
|
||||
|
||||
public var candidates: [(String, String)] {
|
||||
get { data.candidates }
|
||||
set { data.candidates = newValue }
|
||||
}
|
||||
|
||||
public var textToCommit: String {
|
||||
get {
|
||||
data.textToCommit
|
||||
}
|
||||
set {
|
||||
data.textToCommit = newValue
|
||||
}
|
||||
get { data.textToCommit }
|
||||
set { data.textToCommit = newValue }
|
||||
}
|
||||
|
||||
public var tooltip: String {
|
||||
get {
|
||||
data.tooltip
|
||||
}
|
||||
set {
|
||||
data.tooltip = newValue
|
||||
}
|
||||
get { data.tooltip }
|
||||
set { data.tooltip = newValue }
|
||||
}
|
||||
|
||||
public var attributedString: NSAttributedString {
|
||||
|
@ -231,11 +203,7 @@ extension IMEState {
|
|||
}
|
||||
|
||||
public var tooltipBackupForInputting: String {
|
||||
get {
|
||||
data.tooltipBackupForInputting
|
||||
}
|
||||
set {
|
||||
data.tooltipBackupForInputting = newValue
|
||||
}
|
||||
get { data.tooltipBackupForInputting }
|
||||
set { data.tooltipBackupForInputting = newValue }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import Shared
|
||||
import Tekkon
|
||||
import TooltipUI
|
||||
|
||||
public struct StateData {
|
||||
public struct StateData: StateDataProtocol {
|
||||
private static var minCandidateLength: Int {
|
||||
PrefMgr.shared.allowBoostingSingleKanjiAsUserPhrase ? 1 : 2
|
||||
}
|
||||
|
@ -17,8 +19,8 @@ public struct StateData {
|
|||
Self.minCandidateLength...PrefMgr.shared.maxCandidateLength
|
||||
}
|
||||
|
||||
var displayedText: String = ""
|
||||
var displayedTextConverted: String {
|
||||
public var displayedText: String = ""
|
||||
public var displayedTextConverted: String {
|
||||
/// 先做繁簡轉換
|
||||
var result = ChineseConverter.kanjiConversionIfRequired(displayedText)
|
||||
if result.utf16.count != displayedText.utf16.count
|
||||
|
@ -31,19 +33,19 @@ public struct StateData {
|
|||
|
||||
// MARK: Cursor & Marker & Range for UTF8
|
||||
|
||||
var cursor: Int = 0 {
|
||||
public var cursor: Int = 0 {
|
||||
didSet {
|
||||
cursor = min(max(cursor, 0), displayedText.count)
|
||||
}
|
||||
}
|
||||
|
||||
var marker: Int = 0 {
|
||||
public var marker: Int = 0 {
|
||||
didSet {
|
||||
marker = min(max(marker, 0), displayedText.count)
|
||||
}
|
||||
}
|
||||
|
||||
var markedRange: Range<Int> {
|
||||
public var markedRange: Range<Int> {
|
||||
min(cursor, marker)..<max(cursor, marker)
|
||||
}
|
||||
|
||||
|
@ -52,58 +54,58 @@ public struct StateData {
|
|||
/// IMK 協定的內文組字區的游標長度與游標位置無法正確統計 UTF8 高萬字(比如 emoji)的長度,
|
||||
/// 所以在這裡必須做糾偏處理。因為在用 Swift,所以可以用「.utf16」取代「NSString.length()」。
|
||||
/// 這樣就可以免除不必要的類型轉換。
|
||||
var u16Cursor: Int {
|
||||
public var u16Cursor: Int {
|
||||
displayedText.charComponents[0..<cursor].joined().utf16.count
|
||||
}
|
||||
|
||||
var u16Marker: Int {
|
||||
public var u16Marker: Int {
|
||||
displayedText.charComponents[0..<marker].joined().utf16.count
|
||||
}
|
||||
|
||||
var u16MarkedRange: Range<Int> {
|
||||
public var u16MarkedRange: Range<Int> {
|
||||
min(u16Cursor, u16Marker)..<max(u16Cursor, u16Marker)
|
||||
}
|
||||
|
||||
// MARK: Other data for non-empty states.
|
||||
|
||||
var isVerticalTyping = false
|
||||
var markedTargetExists: Bool {
|
||||
public var isVerticalTyping = false
|
||||
public var markedTargetExists: Bool {
|
||||
let pair = userPhraseKVPair
|
||||
return LMMgr.checkIfUserPhraseExist(
|
||||
userPhrase: pair.1, mode: IMEApp.currentInputMode, key: pair.0
|
||||
)
|
||||
}
|
||||
|
||||
var displayTextSegments = [String]() {
|
||||
public var displayTextSegments = [String]() {
|
||||
didSet {
|
||||
displayedText = displayTextSegments.joined()
|
||||
}
|
||||
}
|
||||
|
||||
var reading: String = ""
|
||||
var markedReadings = [String]()
|
||||
var candidates = [(String, String)]()
|
||||
var textToCommit: String = ""
|
||||
var tooltip: String = ""
|
||||
var tooltipBackupForInputting: String = ""
|
||||
var attributedStringPlaceholder: NSAttributedString = .init(
|
||||
public var reading: String = ""
|
||||
public var markedReadings = [String]()
|
||||
public var candidates = [(String, String)]()
|
||||
public var textToCommit: String = ""
|
||||
public var tooltip: String = ""
|
||||
public var tooltipBackupForInputting: String = ""
|
||||
public var attributedStringPlaceholder: NSAttributedString = .init(
|
||||
string: " ",
|
||||
attributes: [
|
||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||
.markedClauseSegment: 0,
|
||||
]
|
||||
)
|
||||
var isFilterable: Bool {
|
||||
public var isFilterable: Bool {
|
||||
markedTargetExists ? isMarkedLengthValid : false
|
||||
}
|
||||
|
||||
var isMarkedLengthValid: Bool {
|
||||
public var isMarkedLengthValid: Bool {
|
||||
Self.allowedMarkLengthRange.contains(markedRange.count)
|
||||
}
|
||||
|
||||
var tooltipColorState: ctlTooltip.ColorStates = .normal
|
||||
public var tooltipColorState: TooltipColorState = .normal
|
||||
|
||||
var attributedStringNormal: NSAttributedString {
|
||||
public var attributedStringNormal: NSAttributedString {
|
||||
/// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況,
|
||||
/// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。
|
||||
let attributedString = NSMutableAttributedString(string: displayedTextConverted)
|
||||
|
@ -121,7 +123,7 @@ public struct StateData {
|
|||
return attributedString
|
||||
}
|
||||
|
||||
var attributedStringMarking: NSAttributedString {
|
||||
public var attributedStringMarking: NSAttributedString {
|
||||
/// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況,
|
||||
/// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。
|
||||
let attributedString = NSMutableAttributedString(string: displayedTextConverted)
|
||||
|
@ -160,7 +162,7 @@ public struct StateData {
|
|||
// MARK: - IMEState 工具函式
|
||||
|
||||
extension StateData {
|
||||
var chkIfUserPhraseExists: Bool {
|
||||
public var doesUserPhraseExist: Bool {
|
||||
let text = displayedText.charComponents[markedRange].joined()
|
||||
let joined = markedReadings.joined(separator: "-")
|
||||
return LMMgr.checkIfUserPhraseExist(
|
||||
|
@ -168,7 +170,7 @@ extension StateData {
|
|||
)
|
||||
}
|
||||
|
||||
var readingThreadForDisplay: String {
|
||||
public var readingThreadForDisplay: String {
|
||||
var arrOutput = [String]()
|
||||
for neta in markedReadings {
|
||||
var neta = neta
|
||||
|
@ -192,27 +194,27 @@ extension StateData {
|
|||
return arrOutput.joined(separator: "\u{A0}")
|
||||
}
|
||||
|
||||
var userPhraseKVPair: (String, String) {
|
||||
public var userPhraseKVPair: (String, String) {
|
||||
let key = markedReadings.joined(separator: "-")
|
||||
let value = displayedText.charComponents[markedRange].joined()
|
||||
return (key, value)
|
||||
}
|
||||
|
||||
var userPhraseDumped: String {
|
||||
public var userPhraseDumped: String {
|
||||
let pair = userPhraseKVPair
|
||||
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
|
||||
let nerfedScore = SessionCtl.areWeNerfing && markedTargetExists ? " -114.514" : ""
|
||||
return "\(pair.1) \(pair.0)\(nerfedScore)"
|
||||
}
|
||||
|
||||
var userPhraseDumpedConverted: String {
|
||||
public var userPhraseDumpedConverted: String {
|
||||
let pair = userPhraseKVPair
|
||||
let text = ChineseConverter.crossConvert(pair.1) ?? ""
|
||||
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
|
||||
let text = ChineseConverter.crossConvert(pair.1)
|
||||
let nerfedScore = SessionCtl.areWeNerfing && markedTargetExists ? " -114.514" : ""
|
||||
let convertedMark = "#𝙃𝙪𝙢𝙖𝙣𝘾𝙝𝙚𝙘𝙠𝙍𝙚𝙦𝙪𝙞𝙧𝙚𝙙"
|
||||
return "\(text) \(pair.0)\(nerfedScore)\t\(convertedMark)"
|
||||
}
|
||||
|
||||
mutating func updateTooltipForMarking() {
|
||||
public mutating func updateTooltipForMarking() {
|
||||
var tooltipForMarking: String {
|
||||
let pair = userPhraseKVPair
|
||||
if PrefMgr.shared.phraseReplacementEnabled {
|
||||
|
|
|
@ -20,7 +20,7 @@ import Tekkon
|
|||
/// KeyHandler 委任協定
|
||||
public protocol KeyHandlerDelegate {
|
||||
var clientBundleIdentifier: String { get }
|
||||
func ctlCandidate() -> ctlCandidateProtocol
|
||||
func candidateController() -> CtlCandidateProtocol
|
||||
func candidateSelectionCalledByKeyHandler(at index: Int)
|
||||
func performUserPhraseOperation(with state: IMEStateProtocol, addToFilter: Bool)
|
||||
-> Bool
|
||||
|
@ -30,7 +30,7 @@ public protocol KeyHandlerDelegate {
|
|||
|
||||
/// KeyHandler 按鍵調度模組。
|
||||
public class KeyHandler {
|
||||
/// 委任物件 (ctlInputMethod),以便呼叫其中的函式。
|
||||
/// 委任物件 (SessionCtl),以便呼叫其中的函式。
|
||||
public var delegate: KeyHandlerDelegate?
|
||||
public var prefs: PrefMgrProtocol
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ extension KeyHandler {
|
|||
stateCallback: @escaping (IMEStateProtocol) -> Void,
|
||||
errorCallback: @escaping (String) -> Void
|
||||
) -> Bool {
|
||||
guard var ctlCandidate = delegate?.ctlCandidate() else {
|
||||
guard var ctlCandidate = delegate?.candidateController() else {
|
||||
errorCallback("06661F6E")
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ extension KeyHandler {
|
|||
stateCallback(candidateState)
|
||||
}
|
||||
}
|
||||
// 將「這個按鍵訊號已經被輸入法攔截處理了」的結果藉由 ctlInputMethod 回報給 IMK。
|
||||
// 將「這個按鍵訊號已經被輸入法攔截處理了」的結果藉由 SessionCtl 回報給 IMK。
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -400,7 +400,7 @@ extension KeyHandler {
|
|||
|
||||
// MARK: - 終末處理 (Still Nothing)
|
||||
|
||||
/// 對剩下的漏網之魚做攔截處理、直接將當前狀態繼續回呼給 ctlInputMethod。
|
||||
/// 對剩下的漏網之魚做攔截處理、直接將當前狀態繼續回呼給 SessionCtl。
|
||||
/// 否則的話,可能會導致輸入法行為異常:部分應用會阻止輸入法完全攔截某些按鍵訊號。
|
||||
/// 砍掉這一段會導致「F1-F12 按鍵干擾組字區」的問題。
|
||||
/// 暫時只能先恢復這段,且補上偵錯彙報機制,方便今後排查故障。
|
||||
|
|
|
@ -114,7 +114,6 @@ extension KeyHandler {
|
|||
func buildAssociatePhraseState(
|
||||
withPair pair: Megrez.Compositor.KeyValuePaired
|
||||
) -> IMEState {
|
||||
// 上一行必須要用驚嘆號,否則 Xcode 會誤導你砍掉某些實際上必需的語句。
|
||||
IMEState.ofAssociates(
|
||||
candidates: buildAssociatePhraseArray(withPair: pair))
|
||||
}
|
||||
|
@ -127,7 +126,7 @@ extension KeyHandler {
|
|||
/// - input: 輸入按鍵訊號。
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - errorCallback: 錯誤回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handleMarkingState(
|
||||
_ state: IMEStateProtocol,
|
||||
input: InputSignalProtocol,
|
||||
|
@ -236,7 +235,7 @@ extension KeyHandler {
|
|||
/// - isTypingVertical: 是否縱排輸入?
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - errorCallback: 錯誤回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handlePunctuation(
|
||||
_ customPunctuation: String,
|
||||
state: IMEStateProtocol,
|
||||
|
@ -286,7 +285,7 @@ extension KeyHandler {
|
|||
/// - Parameters:
|
||||
/// - state: 當前狀態。
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handleEnter(
|
||||
state: IMEStateProtocol,
|
||||
stateCallback: @escaping (IMEStateProtocol) -> Void
|
||||
|
@ -304,7 +303,7 @@ extension KeyHandler {
|
|||
/// - Parameters:
|
||||
/// - state: 當前狀態。
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handleCtrlCommandEnter(
|
||||
state: IMEStateProtocol,
|
||||
stateCallback: @escaping (IMEStateProtocol) -> Void
|
||||
|
@ -332,7 +331,7 @@ extension KeyHandler {
|
|||
/// - Parameters:
|
||||
/// - state: 當前狀態。
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handleCtrlOptionCommandEnter(
|
||||
state: IMEStateProtocol,
|
||||
stateCallback: @escaping (IMEStateProtocol) -> Void
|
||||
|
@ -370,7 +369,7 @@ extension KeyHandler {
|
|||
/// - input: 輸入按鍵訊號。
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - errorCallback: 錯誤回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handleBackSpace(
|
||||
state: IMEStateProtocol,
|
||||
input: InputSignalProtocol,
|
||||
|
@ -432,7 +431,7 @@ extension KeyHandler {
|
|||
/// - input: 輸入按鍵訊號。
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - errorCallback: 錯誤回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handleDelete(
|
||||
state: IMEStateProtocol,
|
||||
input: InputSignalProtocol,
|
||||
|
@ -476,7 +475,7 @@ extension KeyHandler {
|
|||
/// - state: 當前狀態。
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - errorCallback: 錯誤回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handleClockKey(
|
||||
state: IMEStateProtocol,
|
||||
stateCallback: @escaping (IMEStateProtocol) -> Void,
|
||||
|
@ -497,7 +496,7 @@ extension KeyHandler {
|
|||
/// - state: 當前狀態。
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - errorCallback: 錯誤回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handleHome(
|
||||
state: IMEStateProtocol,
|
||||
stateCallback: @escaping (IMEStateProtocol) -> Void,
|
||||
|
@ -529,7 +528,7 @@ extension KeyHandler {
|
|||
/// - state: 當前狀態。
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - errorCallback: 錯誤回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handleEnd(
|
||||
state: IMEStateProtocol,
|
||||
stateCallback: @escaping (IMEStateProtocol) -> Void,
|
||||
|
@ -560,7 +559,7 @@ extension KeyHandler {
|
|||
/// - Parameters:
|
||||
/// - state: 當前狀態。
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handleEsc(
|
||||
state: IMEStateProtocol,
|
||||
stateCallback: @escaping (IMEStateProtocol) -> Void
|
||||
|
@ -592,7 +591,7 @@ extension KeyHandler {
|
|||
/// - input: 輸入按鍵訊號。
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - errorCallback: 錯誤回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handleForward(
|
||||
state: IMEStateProtocol,
|
||||
input: InputSignalProtocol,
|
||||
|
@ -661,7 +660,7 @@ extension KeyHandler {
|
|||
/// - input: 輸入按鍵訊號。
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - errorCallback: 錯誤回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handleBackward(
|
||||
state: IMEStateProtocol,
|
||||
input: InputSignalProtocol,
|
||||
|
@ -730,7 +729,7 @@ extension KeyHandler {
|
|||
/// - reverseModifier: 是否有控制輪替方向的修飾鍵輸入。
|
||||
/// - stateCallback: 狀態回呼。
|
||||
/// - errorCallback: 錯誤回呼。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 ctlInputMethod 回報給 IMK。
|
||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||
func handleInlineCandidateRotation(
|
||||
state: IMEStateProtocol,
|
||||
reverseModifier: Bool,
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import BookmarkManager
|
||||
import LangModelAssembly
|
||||
import Megrez
|
||||
import NotifierUI
|
||||
import Shared
|
||||
|
||||
/// 使用者辭典資料預設範例檔案名稱。
|
||||
|
@ -98,7 +98,7 @@ public enum LMMgr {
|
|||
}
|
||||
if !Self.lmCHT.isLanguageModelLoaded {
|
||||
showFinishNotification = true
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Loading CHT Core Dict...", comment: "")
|
||||
)
|
||||
group.enter()
|
||||
|
@ -109,7 +109,7 @@ public enum LMMgr {
|
|||
}
|
||||
if !Self.lmCHS.isLanguageModelLoaded {
|
||||
showFinishNotification = true
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Loading CHS Core Dict...", comment: "")
|
||||
)
|
||||
group.enter()
|
||||
|
@ -120,7 +120,7 @@ public enum LMMgr {
|
|||
}
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
if showFinishNotification {
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Core Dict loading complete.", comment: "")
|
||||
)
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ public enum LMMgr {
|
|||
case .imeModeCHS:
|
||||
if !Self.lmCHS.isLanguageModelLoaded {
|
||||
showFinishNotification = true
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Loading CHS Core Dict...", comment: "")
|
||||
)
|
||||
group.enter()
|
||||
|
@ -174,7 +174,7 @@ public enum LMMgr {
|
|||
case .imeModeCHT:
|
||||
if !Self.lmCHT.isLanguageModelLoaded {
|
||||
showFinishNotification = true
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Loading CHT Core Dict...", comment: "")
|
||||
)
|
||||
group.enter()
|
||||
|
@ -187,7 +187,7 @@ public enum LMMgr {
|
|||
}
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
if showFinishNotification {
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Core Dict loading complete.", comment: "")
|
||||
)
|
||||
}
|
||||
|
@ -244,16 +244,12 @@ public enum LMMgr {
|
|||
mode: Shared.InputMode,
|
||||
key unigramKey: String
|
||||
) -> Bool {
|
||||
let unigrams: [Megrez.Unigram] =
|
||||
(mode == .imeModeCHT)
|
||||
? Self.lmCHT.unigramsFor(key: unigramKey) : Self.lmCHS.unigramsFor(key: unigramKey)
|
||||
for unigram in unigrams {
|
||||
if unigram.value == userPhrase {
|
||||
return true
|
||||
switch mode {
|
||||
case .imeModeCHS: return lmCHS.hasKeyValuePairFor(key: unigramKey, value: userPhrase)
|
||||
case .imeModeCHT: return lmCHT.hasKeyValuePairFor(key: unigramKey, value: userPhrase)
|
||||
case .imeModeNULL: return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public static func setPhraseReplacementEnabled(_ state: Bool) {
|
||||
Self.lmCHT.isPhraseReplacementEnabled = state
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Shared
|
||||
|
||||
// MARK: Auto parameter fix procedures, executed everytime on ctlInputMethod.activateServer().
|
||||
// MARK: Auto parameter fix procedures, executed everytime on SessionCtl.activateServer().
|
||||
|
||||
extension PrefMgr {
|
||||
public func fixOddPreferences() {
|
||||
|
|
|
@ -9,9 +9,11 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import IMKUtils
|
||||
import PopupCompositionBuffer
|
||||
import Shared
|
||||
import ShiftKeyUpChecker
|
||||
import Tekkon
|
||||
import TooltipUI
|
||||
import Voltaire
|
||||
|
||||
/// 輸入法控制模組,乃在輸入法端用以控制輸入行為的基礎型別。
|
||||
///
|
||||
|
@ -21,24 +23,24 @@ import Tekkon
|
|||
/// 檢查委任物件是否實現了方法:若存在的話,就調用委任物件內的版本。
|
||||
/// - Remark: 在輸入法的主函式中分配的 IMKServer 型別為客體應用程式創建的每個
|
||||
/// 輸入會話創建一個控制器型別。因此,對於每個輸入會話,都有一個對應的 IMKInputController。
|
||||
@objc(ctlInputMethod) // 必須加上 ObjC,因為 IMK 是用 ObjC 寫的。
|
||||
class ctlInputMethod: IMKInputController {
|
||||
@objc(SessionCtl) // 必須加上 ObjC,因為 IMK 是用 ObjC 寫的。
|
||||
class SessionCtl: IMKInputController {
|
||||
/// 標記狀態來聲明目前新增的詞彙是否需要賦以非常低的權重。
|
||||
static var areWeNerfing = false
|
||||
|
||||
/// 目前在用的的選字窗副本。
|
||||
static var ctlCandidateCurrent: ctlCandidateProtocol =
|
||||
PrefMgr.shared.useIMKCandidateWindow ? ctlCandidateIMK.init(.horizontal) : ctlCandidateUniversal.init(.horizontal)
|
||||
static var ctlCandidateCurrent: CtlCandidateProtocol =
|
||||
PrefMgr.shared.useIMKCandidateWindow ? CtlCandidateIMK(.horizontal) : CtlCandidateUniversal(.horizontal)
|
||||
|
||||
/// 工具提示視窗的共用副本。
|
||||
static var tooltipInstance = ctlTooltip()
|
||||
static var tooltipInstance = TooltipUI()
|
||||
|
||||
/// 浮動組字窗的共用副本。
|
||||
static var popupCompositionBuffer = ctlPopupCompositionBuffer()
|
||||
static var popupCompositionBuffer = PopupCompositionBuffer()
|
||||
|
||||
// MARK: -
|
||||
|
||||
/// 當前這個 ctlInputMethod 副本是否處於英數輸入模式。
|
||||
/// 當前這個 SessionCtl 副本是否處於英數輸入模式。
|
||||
var isASCIIMode = false {
|
||||
didSet {
|
||||
resetKeyHandler()
|
||||
|
@ -75,8 +77,46 @@ class ctlInputMethod: IMKInputController {
|
|||
return result
|
||||
}
|
||||
|
||||
/// InputMode 需要在每次出現內容變更的時候都連帶重設組字器與各項語言模組,
|
||||
/// 順帶更新 IME 模組及 UserPrefs 當中對於當前語言模式的記載。
|
||||
var inputMode: Shared.InputMode = IMEApp.currentInputMode {
|
||||
willSet {
|
||||
/// 將新的簡繁輸入模式提報給 Prefs 與 IME 模組。
|
||||
IMEApp.currentInputMode = newValue
|
||||
PrefMgr.shared.mostRecentInputMode = IMEApp.currentInputMode.rawValue
|
||||
}
|
||||
didSet {
|
||||
/// 重設所有語言模組。這裡不需要做按需重設,因為對運算量沒有影響。
|
||||
keyHandler.currentLM = LMMgr.currentLM() // 會自動更新組字引擎內的模組。
|
||||
keyHandler.currentUOM = LMMgr.currentUOM()
|
||||
/// 清空注拼槽+同步最新的注拼槽排列設定。
|
||||
keyHandler.ensureKeyboardParser()
|
||||
/// 將輸入法偏好設定同步至語言模組內。
|
||||
syncBaseLMPrefs()
|
||||
}
|
||||
}
|
||||
|
||||
/// 對用以設定委任物件的控制器型別進行初期化處理。
|
||||
///
|
||||
/// inputClient 參數是客體應用側存在的用以藉由 IMKServer 伺服器向輸入法傳訊的物件。該物件始終遵守 IMKTextInput 協定。
|
||||
/// - Remark: 所有由委任物件實裝的「被協定要求實裝的方法」都會有一個用來接受客體物件的參數。在 IMKInputController 內部的型別不需要接受這個參數,因為已經有「client()」這個參數存在了。
|
||||
/// - Parameters:
|
||||
/// - server: IMKServer
|
||||
/// - delegate: 客體物件
|
||||
/// - inputClient: 用以接受輸入的客體應用物件
|
||||
override init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) {
|
||||
super.init(server: server, delegate: delegate, client: inputClient)
|
||||
keyHandler.delegate = self
|
||||
syncBaseLMPrefs()
|
||||
// 下述兩行很有必要,否則輸入法會在手動重啟之後無法立刻生效。
|
||||
resetKeyHandler()
|
||||
activateServer(inputClient)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 工具函式
|
||||
|
||||
extension SessionCtl {
|
||||
/// 指定鍵盤佈局。
|
||||
func setKeyLayout() {
|
||||
guard let client = client() else { return }
|
||||
|
@ -101,28 +141,11 @@ class ctlInputMethod: IMKInputController {
|
|||
}
|
||||
handle(state: isSecureMode ? IMEState.ofAbortion() : IMEState.ofEmpty())
|
||||
}
|
||||
|
||||
// MARK: - IMKInputController 方法
|
||||
|
||||
/// 對用以設定委任物件的控制器型別進行初期化處理。
|
||||
///
|
||||
/// inputClient 參數是客體應用側存在的用以藉由 IMKServer 伺服器向輸入法傳訊的物件。該物件始終遵守 IMKTextInput 協定。
|
||||
/// - Remark: 所有由委任物件實裝的「被協定要求實裝的方法」都會有一個用來接受客體物件的參數。在 IMKInputController 內部的型別不需要接受這個參數,因為已經有「client()」這個參數存在了。
|
||||
/// - Parameters:
|
||||
/// - server: IMKServer
|
||||
/// - delegate: 客體物件
|
||||
/// - inputClient: 用以接受輸入的客體應用物件
|
||||
override init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) {
|
||||
super.init(server: server, delegate: delegate, client: inputClient)
|
||||
keyHandler.delegate = self
|
||||
syncBaseLMPrefs()
|
||||
// 下述兩行很有必要,否則輸入法會在手動重啟之後無法立刻生效。
|
||||
resetKeyHandler()
|
||||
activateServer(inputClient)
|
||||
}
|
||||
|
||||
// MARK: - IMKStateSetting 協定規定的方法
|
||||
|
||||
extension SessionCtl {
|
||||
/// 啟用輸入法時,會觸發該函式。
|
||||
/// - Parameter sender: 呼叫了該函式的客體(無須使用)。
|
||||
override func activateServer(_ sender: Any!) {
|
||||
|
@ -192,25 +215,6 @@ class ctlInputMethod: IMKInputController {
|
|||
}
|
||||
}
|
||||
|
||||
/// InputMode 需要在每次出現內容變更的時候都連帶重設組字器與各項語言模組,
|
||||
/// 順帶更新 IME 模組及 UserPrefs 當中對於當前語言模式的記載。
|
||||
var inputMode: Shared.InputMode = IMEApp.currentInputMode {
|
||||
willSet {
|
||||
/// 將新的簡繁輸入模式提報給 Prefs 與 IME 模組。
|
||||
IMEApp.currentInputMode = newValue
|
||||
PrefMgr.shared.mostRecentInputMode = IMEApp.currentInputMode.rawValue
|
||||
}
|
||||
didSet {
|
||||
/// 重設所有語言模組。這裡不需要做按需重設,因為對運算量沒有影響。
|
||||
keyHandler.currentLM = LMMgr.currentLM() // 會自動更新組字引擎內的模組。
|
||||
keyHandler.currentUOM = LMMgr.currentUOM()
|
||||
/// 清空注拼槽+同步最新的注拼槽排列設定。
|
||||
keyHandler.ensureKeyboardParser()
|
||||
/// 將輸入法偏好設定同步至語言模組內。
|
||||
syncBaseLMPrefs()
|
||||
}
|
||||
}
|
||||
|
||||
/// 將輸入法偏好設定同步至語言模組內。
|
||||
func syncBaseLMPrefs() {
|
||||
LMMgr.currentLM().isPhraseReplacementEnabled = PrefMgr.shared.phraseReplacementEnabled
|
||||
|
@ -219,9 +223,13 @@ class ctlInputMethod: IMKInputController {
|
|||
LMMgr.currentLM().isSCPCEnabled = PrefMgr.shared.useSCPCTypingMode
|
||||
LMMgr.currentLM().deltaOfCalendarYears = PrefMgr.shared.deltaOfCalendarYears
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - IMKServerInput 協定規定的方法
|
||||
// MARK: - IMKServerInput 協定規定的方法(僅部分)
|
||||
|
||||
// 註:handle(_ event:) 位於 SessionCtl_HandleEvent.swift。
|
||||
|
||||
extension SessionCtl {
|
||||
/// 該函式的回饋結果決定了輸入法會攔截且捕捉哪些類型的輸入裝置操作事件。
|
||||
///
|
||||
/// 一個客體應用會與輸入法共同確認某個輸入裝置操作事件是否可以觸發輸入法內的某個方法。預設情況下,
|
||||
|
@ -237,103 +245,6 @@ class ctlInputMethod: IMKInputController {
|
|||
return Int(events.rawValue)
|
||||
}
|
||||
|
||||
/// 接受所有鍵鼠事件為 NSEvent,讓輸入法判斷是否要處理、該怎樣處理。
|
||||
/// - Parameters:
|
||||
/// - event: 裝置操作輸入事件,可能會是 nil。
|
||||
/// - sender: 呼叫了該函式的客體(無須使用)。
|
||||
/// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。
|
||||
@objc(handleEvent:client:) override func handle(_ event: NSEvent!, client sender: Any!) -> Bool {
|
||||
_ = sender // 防止格式整理工具毀掉與此對應的參數。
|
||||
|
||||
// MARK: 前置處理
|
||||
|
||||
// 更新此時的靜態狀態標記。
|
||||
state.isASCIIMode = isASCIIMode
|
||||
state.isVerticalTyping = isVerticalTyping
|
||||
|
||||
// 就這傳入的 NSEvent 都還有可能是 nil,Apple InputMethodKit 團隊到底在搞三小。
|
||||
// 只針對特定類型的 client() 進行處理。
|
||||
guard let event = event, sender is IMKTextInput else {
|
||||
resetKeyHandler()
|
||||
return false
|
||||
}
|
||||
|
||||
// 用 Shift 開關半形英數模式,僅對 macOS 10.15 及之後的 macOS 有效。
|
||||
let shouldUseShiftToggleHandle: Bool = {
|
||||
switch PrefMgr.shared.shiftKeyAccommodationBehavior {
|
||||
case 0: return false
|
||||
case 1: return Shared.arrClientShiftHandlingExceptionList.contains(clientBundleIdentifier)
|
||||
case 2: return true
|
||||
default: return false
|
||||
}
|
||||
}()
|
||||
|
||||
/// 警告:這裡的 event 必須是原始 event 且不能被 var,否則會影響 Shift 中英模式判定。
|
||||
if #available(macOS 10.15, *) {
|
||||
if Self.theShiftKeyDetector.check(event), !PrefMgr.shared.disableShiftTogglingAlphanumericalMode {
|
||||
if !shouldUseShiftToggleHandle || (!rencentKeyHandledByKeyHandlerEtc && shouldUseShiftToggleHandle) {
|
||||
NotifierController.notify(
|
||||
message: isASCIIMode.toggled()
|
||||
? NSLocalizedString("Alphanumerical Input Mode", comment: "")
|
||||
: NSLocalizedString("Chinese Input Mode", comment: "")
|
||||
)
|
||||
}
|
||||
if shouldUseShiftToggleHandle {
|
||||
rencentKeyHandledByKeyHandlerEtc = false
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: 針對客體的具體處理
|
||||
|
||||
// 不再讓威注音處理由 Shift 切換到的英文模式的按鍵輸入。
|
||||
if isASCIIMode { return false }
|
||||
|
||||
/// 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。
|
||||
/// 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。
|
||||
/// 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false,
|
||||
/// 否則,每次處理這種判斷時都會觸發 NSInternalInconsistencyException。
|
||||
if event.type == .flagsChanged { return false }
|
||||
|
||||
/// 沒有文字輸入客體的話,就不要再往下處理了。
|
||||
guard client() != nil else { return false }
|
||||
|
||||
var eventToDeal = event
|
||||
|
||||
// 如果是方向鍵輸入的話,就想辦法帶上標記資訊、來說明當前是縱排還是橫排。
|
||||
if event.isUp || event.isDown || event.isLeft || event.isRight {
|
||||
eventToDeal = event.reinitiate(charactersIgnoringModifiers: isVerticalTyping ? "Vertical" : "Horizontal") ?? event
|
||||
}
|
||||
|
||||
// 使 NSEvent 自翻譯,這樣可以讓 Emacs NSEvent 變成標準 NSEvent。
|
||||
if eventToDeal.isEmacsKey {
|
||||
let verticalProcessing =
|
||||
(state.isCandidateContainer)
|
||||
? state.isVerticalCandidateWindow : state.isVerticalTyping
|
||||
eventToDeal = eventToDeal.convertFromEmacKeyEvent(isVerticalContext: verticalProcessing)
|
||||
}
|
||||
|
||||
// 準備修飾鍵,用來判定要新增的詞彙是否需要賦以非常低的權重。
|
||||
Self.areWeNerfing = eventToDeal.modifierFlags.contains([.shift, .command])
|
||||
|
||||
// IMK 選字窗處理,當且僅當啟用了 IMK 選字窗的時候才會生效。
|
||||
if let result = imkCandidatesEventHandler(event: eventToDeal) {
|
||||
if shouldUseShiftToggleHandle {
|
||||
rencentKeyHandledByKeyHandlerEtc = result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/// 剩下的 NSEvent 直接交給 commonEventHandler 來處理。
|
||||
/// 這樣可以與 IMK 選字窗共用按鍵處理資源,維護起來也比較方便。
|
||||
let result = commonEventHandler(eventToDeal)
|
||||
if shouldUseShiftToggleHandle {
|
||||
rencentKeyHandledByKeyHandlerEtc = result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/// 有時會出現某些 App 攔截輸入法的 Ctrl+Enter / Shift+Enter 熱鍵的情況。
|
||||
/// 也就是說 handle(event:) 完全抓不到這個 Event。
|
||||
/// 這時需要在 commitComposition 這一關做一些收尾處理。
|
||||
|
@ -344,13 +255,13 @@ class ctlInputMethod: IMKInputController {
|
|||
// super.commitComposition(sender) // 這句不要引入,否則每次切出輸入法時都會死當。
|
||||
}
|
||||
|
||||
/// 指定輸入法要遞交出去的內容(雖然威注音可能並未用到這個函式)。
|
||||
/// 指定輸入法要遞交出去的內容(雖然 InputMethodKit 可能並不會真的用到這個函式)。
|
||||
/// - Parameter sender: 呼叫了該函式的客體(無須使用)。
|
||||
/// - Returns: 字串內容,或者 nil。
|
||||
override func composedString(_ sender: Any!) -> Any! {
|
||||
_ = sender // 防止格式整理工具毀掉與此對應的參數。
|
||||
guard state.hasComposition else { return "" }
|
||||
return state.displayedText
|
||||
return state.displayedTextConverted
|
||||
}
|
||||
|
||||
/// 輸入法要被換掉或關掉的時候,要做的事情。
|
||||
|
@ -360,121 +271,4 @@ class ctlInputMethod: IMKInputController {
|
|||
resetKeyHandler()
|
||||
super.inputControllerWillClose()
|
||||
}
|
||||
|
||||
// MARK: - IMKCandidates 功能擴充
|
||||
|
||||
/// 生成 IMK 選字窗專用的候選字串陣列。
|
||||
/// - Parameter sender: 呼叫了該函式的客體(無須使用)。
|
||||
/// - Returns: IMK 選字窗專用的候選字串陣列。
|
||||
override func candidates(_ sender: Any!) -> [Any]! {
|
||||
_ = sender // 防止格式整理工具毀掉與此對應的參數。
|
||||
var arrResult = [String]()
|
||||
|
||||
// 注意:下文中的不可列印字元是用來方便在 IMEState 當中用來分割資料的。
|
||||
func handleIMKCandidatesPrepared(_ candidates: [(String, String)], prefix: String = "") {
|
||||
for theCandidate in candidates {
|
||||
let theConverted = ChineseConverter.kanjiConversionIfRequired(theCandidate.1)
|
||||
var result = (theCandidate.1 == theConverted) ? theCandidate.1 : "\(theConverted)\u{1A}(\(theCandidate.1))"
|
||||
if arrResult.contains(result) {
|
||||
let reading: String =
|
||||
PrefMgr.shared.showHanyuPinyinInCompositionBuffer
|
||||
? Tekkon.cnvPhonaToHanyuPinyin(target: Tekkon.restoreToneOneInZhuyinKey(target: theCandidate.0))
|
||||
: theCandidate.0
|
||||
result = "\(result)\u{17}(\(reading))"
|
||||
}
|
||||
arrResult.append(prefix + result)
|
||||
}
|
||||
}
|
||||
|
||||
if state.type == .ofAssociates {
|
||||
handleIMKCandidatesPrepared(state.candidates, prefix: "⇧")
|
||||
} else if state.type == .ofSymbolTable {
|
||||
// 分類符號選單不會出現同符異音項、不需要康熙 / JIS 轉換,所以使用簡化過的處理方式。
|
||||
arrResult = state.candidates.map(\.1)
|
||||
} else if state.type == .ofCandidates {
|
||||
guard !state.candidates.isEmpty else { return .init() }
|
||||
if state.candidates[0].0.contains("_punctuation") {
|
||||
arrResult = state.candidates.map(\.1) // 標點符號選單處理。
|
||||
} else {
|
||||
handleIMKCandidatesPrepared(state.candidates)
|
||||
}
|
||||
}
|
||||
|
||||
return arrResult
|
||||
}
|
||||
|
||||
/// IMK 選字窗限定函式,只要選字窗內的高亮內容選擇出現變化了、就會呼叫這個函式。
|
||||
/// - Parameter _: 已經高亮選中的候選字詞內容。
|
||||
override open func candidateSelectionChanged(_: NSAttributedString!) {
|
||||
// 警告:不要考慮用實作這個函式的方式來更新內文組字區的顯示。
|
||||
// 因為這樣會導致 IMKServer.commitCompositionWithReply() 呼叫你本來不想呼叫的 commitComposition(),
|
||||
// 然後 keyHandler 會被重設,屆時輸入法會在狀態處理等方面崩潰掉。
|
||||
|
||||
// 這個函式的實作其實很容易誘發各種崩潰,所以最好不要輕易實作。
|
||||
|
||||
// 有些幹話還是要講的:
|
||||
// 在這個函式當中試圖(無論是否拿著傳入的參數)從 ctlCandidateIMK 找 identifier 的話,
|
||||
// 只會找出 NSNotFound。你想 NSLog 列印看 identifier 是多少,輸入法直接崩潰。
|
||||
// 而且會他媽的崩得連 console 內的 ips 錯誤報告都沒有。
|
||||
// 在下文的 candidateSelected() 試圖看每個候選字的 identifier 的話,永遠都只能拿到 NSNotFound。
|
||||
// 衰洨 IMK 真的看上去就像是沒有做過單元測試的東西,賈伯斯有檢查過的話會被氣得從棺材裡爬出來。
|
||||
}
|
||||
|
||||
/// IMK 選字窗限定函式,只要選字窗確認了某個候選字詞的選擇、就會呼叫這個函式。
|
||||
/// - Parameter candidateString: 已經確認的候選字詞內容。
|
||||
override open func candidateSelected(_ candidateString: NSAttributedString!) {
|
||||
let candidateString: String = candidateString?.string ?? ""
|
||||
if state.type == .ofAssociates {
|
||||
if !PrefMgr.shared.alsoConfirmAssociatedCandidatesByEnter {
|
||||
handle(state: IMEState.ofAbortion())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var indexDeducted = 0
|
||||
|
||||
// 注意:下文中的不可列印字元是用來方便在 IMEState 當中用來分割資料的。
|
||||
func handleIMKCandidatesSelected(_ candidates: [(String, String)], prefix: String = "") {
|
||||
for (i, neta) in candidates.enumerated() {
|
||||
let theConverted = ChineseConverter.kanjiConversionIfRequired(neta.1)
|
||||
let netaShown = (neta.1 == theConverted) ? neta.1 : "\(theConverted)\u{1A}(\(neta.1))"
|
||||
let reading: String =
|
||||
PrefMgr.shared.showHanyuPinyinInCompositionBuffer
|
||||
? Tekkon.cnvPhonaToHanyuPinyin(target: Tekkon.restoreToneOneInZhuyinKey(target: neta.0)) : neta.0
|
||||
let netaShownWithPronunciation = "\(netaShown)\u{17}(\(reading))"
|
||||
if candidateString == prefix + netaShownWithPronunciation {
|
||||
indexDeducted = i
|
||||
break
|
||||
}
|
||||
if candidateString == prefix + netaShown {
|
||||
indexDeducted = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分類符號選單不會出現同符異音項、不需要康熙 / JIS 轉換,所以使用簡化過的處理方式。
|
||||
func handleSymbolCandidatesSelected(_ candidates: [(String, String)]) {
|
||||
for (i, neta) in candidates.enumerated() {
|
||||
if candidateString == neta.1 {
|
||||
indexDeducted = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if state.type == .ofAssociates {
|
||||
handleIMKCandidatesSelected(state.candidates, prefix: "⇧")
|
||||
} else if state.type == .ofSymbolTable {
|
||||
handleSymbolCandidatesSelected(state.candidates)
|
||||
} else if state.type == .ofCandidates {
|
||||
guard !state.candidates.isEmpty else { return }
|
||||
if state.candidates[0].0.contains("_punctuation") {
|
||||
handleSymbolCandidatesSelected(state.candidates) // 標點符號選單處理。
|
||||
} else {
|
||||
handleIMKCandidatesSelected(state.candidates)
|
||||
}
|
||||
}
|
||||
candidateSelected(at: indexDeducted)
|
||||
}
|
||||
}
|
|
@ -12,31 +12,29 @@ import Shared
|
|||
|
||||
// MARK: - KeyHandler Delegate
|
||||
|
||||
extension ctlInputMethod: KeyHandlerDelegate {
|
||||
extension SessionCtl: KeyHandlerDelegate {
|
||||
var clientBundleIdentifier: String {
|
||||
guard let client = client() else { return "" }
|
||||
return client.bundleIdentifier() ?? ""
|
||||
}
|
||||
|
||||
func ctlCandidate() -> ctlCandidateProtocol { ctlInputMethod.ctlCandidateCurrent }
|
||||
func candidateController() -> CtlCandidateProtocol { Self.ctlCandidateCurrent }
|
||||
|
||||
func candidateSelectionCalledByKeyHandler(at index: Int) {
|
||||
candidateSelected(at: index)
|
||||
candidatePairSelected(at: index)
|
||||
}
|
||||
|
||||
func performUserPhraseOperation(with state: IMEStateProtocol, addToFilter: Bool)
|
||||
-> Bool
|
||||
{
|
||||
guard state.type == .ofMarking else { return false }
|
||||
let refInputModeReversed: Shared.InputMode =
|
||||
(inputMode == .imeModeCHT) ? .imeModeCHS : .imeModeCHT
|
||||
if !LMMgr.writeUserPhrase(
|
||||
state.data.userPhraseDumped, inputMode: inputMode,
|
||||
areWeDuplicating: state.data.chkIfUserPhraseExists,
|
||||
areWeDuplicating: state.data.doesUserPhraseExist,
|
||||
areWeDeleting: addToFilter
|
||||
)
|
||||
|| !LMMgr.writeUserPhrase(
|
||||
state.data.userPhraseDumpedConverted, inputMode: refInputModeReversed,
|
||||
state.data.userPhraseDumpedConverted, inputMode: inputMode.reversed,
|
||||
areWeDuplicating: false,
|
||||
areWeDeleting: addToFilter
|
||||
)
|
||||
|
@ -49,48 +47,27 @@ extension ctlInputMethod: KeyHandlerDelegate {
|
|||
|
||||
// MARK: - Candidate Controller Delegate
|
||||
|
||||
extension ctlInputMethod: ctlCandidateDelegate {
|
||||
var isAssociatedPhrasesState: Bool { state.type == .ofAssociates }
|
||||
|
||||
/// 完成 handle() 函式本該完成的內容,但去掉了與 IMK 選字窗有關的判斷語句。
|
||||
/// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。
|
||||
/// 該函式僅由 IMK 選字窗來存取,且對接給 commonEventHandler()。
|
||||
/// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。
|
||||
/// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。
|
||||
@discardableResult func sharedEventHandler(_ event: NSEvent) -> Bool {
|
||||
commonEventHandler(event)
|
||||
extension SessionCtl: CtlCandidateDelegate {
|
||||
func buzz() {
|
||||
IMEApp.buzz()
|
||||
}
|
||||
|
||||
func candidateCountForController(_ controller: ctlCandidateProtocol) -> Int {
|
||||
_ = controller // 防止格式整理工具毀掉與此對應的參數。
|
||||
if state.isCandidateContainer {
|
||||
return state.candidates.count
|
||||
}
|
||||
return 0
|
||||
func kanjiConversionIfRequired(_ target: String) -> String {
|
||||
ChineseConverter.kanjiConversionIfRequired(target)
|
||||
}
|
||||
|
||||
/// 直接給出全部的候選字詞的字音配對陣列
|
||||
/// - Parameter controller: 對應的控制器。因為有唯一解,所以填錯了也不會有影響。
|
||||
/// - Returns: 候選字詞陣列(字音配對)。
|
||||
func candidatesForController(_ controller: ctlCandidateProtocol) -> [(String, String)] {
|
||||
_ = controller // 防止格式整理工具毀掉與此對應的參數。
|
||||
if state.isCandidateContainer {
|
||||
return state.candidates
|
||||
}
|
||||
return .init()
|
||||
func candidatePairs() -> [(String, String)] {
|
||||
state.isCandidateContainer ? state.candidates : []
|
||||
}
|
||||
|
||||
func ctlCandidate(_ controller: ctlCandidateProtocol, candidateAtIndex index: Int)
|
||||
-> (String, String)
|
||||
{
|
||||
_ = controller // 防止格式整理工具毀掉與此對應的參數。
|
||||
if state.isCandidateContainer {
|
||||
func candidatePairAt(_ index: Int) -> (String, String) {
|
||||
if state.isCandidateContainer, state.candidates.count > index {
|
||||
return state.candidates[index]
|
||||
}
|
||||
return ("", "")
|
||||
}
|
||||
|
||||
func candidateSelected(at index: Int) {
|
||||
func candidatePairSelected(at index: Int) {
|
||||
if state.type == .ofSymbolTable, (0..<state.node.members.count).contains(index) {
|
||||
let node = state.node.members[index]
|
||||
if !node.members.isEmpty {
|
|
@ -10,17 +10,18 @@
|
|||
|
||||
import NSAttributedTextView
|
||||
import Shared
|
||||
import Voltaire
|
||||
|
||||
// MARK: - Tooltip Display and Candidate Display Methods
|
||||
|
||||
extension ctlInputMethod {
|
||||
extension SessionCtl {
|
||||
// 有些 App 會濫用內文組字區的內容來預測使用者的輸入行為。
|
||||
// 對此類 App 有疑慮者,可以將這類 App 登記到客體管理員當中。
|
||||
// 這樣,不但強制使用(限制讀音 20 個的)浮動組字窗,而且內文組字區只會顯示一個空格。
|
||||
var attributedStringSecured: (NSAttributedString, NSRange) {
|
||||
PrefMgr.shared.clientsIMKTextInputIncapable.contains(clientBundleIdentifier)
|
||||
? (state.data.attributedStringPlaceholder, NSRange(location: 0, length: 0))
|
||||
: (state.attributedString, NSRange(state.data.u16MarkedRange))
|
||||
: (state.attributedString, NSRange(state.u16MarkedRange))
|
||||
}
|
||||
|
||||
func lineHeightRect(zeroCursor: Bool = false) -> NSRect {
|
||||
|
@ -28,14 +29,10 @@ extension ctlInputMethod {
|
|||
guard let client = client() else {
|
||||
return lineHeightRect
|
||||
}
|
||||
var u16Cursor: Int = {
|
||||
// iMessage 在 cursor == 0 時的計算會有一些偏差,所以例外處理。
|
||||
if clientBundleIdentifier == "com.apple.MobileSMS" { return state.data.u16Cursor }
|
||||
if state.data.marker >= state.data.cursor { return state.data.u16Cursor }
|
||||
return state.data.u16Marker // 這樣可以讓工具提示視窗始終盡量往書寫方向的後方顯示。
|
||||
}()
|
||||
u16Cursor = max(min(state.data.displayedTextConverted.utf16.count, u16Cursor), 0)
|
||||
var u16Cursor: Int = state.u16MarkedRange.lowerBound
|
||||
u16Cursor = max(min(state.displayedTextConverted.utf16.count, u16Cursor), 0)
|
||||
if zeroCursor { u16Cursor = 0 }
|
||||
// iMessage 的話,據此算出來的 lineHeightRect 結果的橫向座標起始點不準確。目前無解。
|
||||
while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, u16Cursor >= 0 {
|
||||
client.attributes(
|
||||
forCharacterIndex: u16Cursor, lineHeightRectangle: &lineHeightRect
|
||||
|
@ -49,7 +46,7 @@ extension ctlInputMethod {
|
|||
guard client() != nil else { return }
|
||||
let lineHeightRect = lineHeightRect()
|
||||
var finalOrigin: NSPoint = lineHeightRect.origin
|
||||
let delta: CGFloat = lineHeightRect.size.height + 4.0 // bottomOutOfScreenAdjustmentHeight
|
||||
let delta: Double = lineHeightRect.size.height + 4.0 // bottomOutOfScreenAdjustmentHeight
|
||||
if isVerticalTyping {
|
||||
finalOrigin = NSPoint(
|
||||
x: lineHeightRect.origin.x + lineHeightRect.size.width + 5, y: lineHeightRect.origin.y
|
||||
|
@ -61,14 +58,14 @@ extension ctlInputMethod {
|
|||
}()
|
||||
// 強制重新初期化,因為 NSAttributedTextView 有顯示滯後性。
|
||||
do {
|
||||
ctlInputMethod.tooltipInstance.hide()
|
||||
ctlInputMethod.tooltipInstance = .init()
|
||||
Self.tooltipInstance.hide()
|
||||
Self.tooltipInstance = .init()
|
||||
if state.type == .ofMarking {
|
||||
ctlInputMethod.tooltipInstance.setColor(state: state.data.tooltipColorState)
|
||||
Self.tooltipInstance.setColor(state: state.data.tooltipColorState)
|
||||
}
|
||||
}
|
||||
// 再設定其文字顯示內容並顯示。
|
||||
ctlInputMethod.tooltipInstance.show(
|
||||
Self.tooltipInstance.show(
|
||||
tooltip: tooltip, at: finalOrigin,
|
||||
bottomOutOfScreenAdjustmentHeight: delta, direction: tooltipContentDirection
|
||||
)
|
||||
|
@ -83,7 +80,7 @@ extension ctlInputMethod {
|
|||
}
|
||||
if isVerticalTyping { return true }
|
||||
// 接下來的判斷並非適用於 IMK 選字窗,所以先插入排除語句。
|
||||
guard ctlInputMethod.ctlCandidateCurrent is ctlCandidateUniversal else { return false }
|
||||
guard Self.ctlCandidateCurrent is CtlCandidateUniversal else { return false }
|
||||
// 以上是通用情形。接下來決定橫排輸入時是否使用縱排選字窗。
|
||||
// 因為在拿候選字陣列時已經排序過了,所以這裡不用再多排序。
|
||||
// 測量每頁顯示候選字的累計總長度。如果太長的話就強制使用縱排候選字窗。
|
||||
|
@ -96,7 +93,7 @@ extension ctlInputMethod {
|
|||
|
||||
state.isVerticalCandidateWindow = (isCandidateWindowVertical || !PrefMgr.shared.useHorizontalCandidateList)
|
||||
|
||||
ctlInputMethod.ctlCandidateCurrent.delegate = nil
|
||||
Self.ctlCandidateCurrent.delegate = nil
|
||||
|
||||
/// 下面這一段本可直接指定 currentLayout,但這樣的話翻頁按鈕位置無法精準地重新繪製。
|
||||
/// 所以只能重新初期化。壞處就是得在 ctlCandidate() 當中與 SymbolTable 控制有關的地方
|
||||
|
@ -109,26 +106,26 @@ extension ctlInputMethod {
|
|||
? CandidateLayout.vertical
|
||||
: CandidateLayout.horizontal)
|
||||
|
||||
ctlInputMethod.ctlCandidateCurrent =
|
||||
Self.ctlCandidateCurrent =
|
||||
PrefMgr.shared.useIMKCandidateWindow
|
||||
? ctlCandidateIMK.init(candidateLayout) : ctlCandidateUniversal.init(candidateLayout)
|
||||
? CtlCandidateIMK(candidateLayout) : CtlCandidateUniversal(candidateLayout)
|
||||
|
||||
// set the attributes for the candidate panel (which uses NSAttributedString)
|
||||
let textSize = PrefMgr.shared.candidateListTextSize
|
||||
let minimumKeyLabelSize: Double = 10
|
||||
let keyLabelSize = max(textSize / 2, minimumKeyLabelSize)
|
||||
|
||||
func labelFont(name: String?, size: CGFloat) -> NSFont {
|
||||
func labelFont(name: String?, size: Double) -> NSFont {
|
||||
if let name = name {
|
||||
return NSFont(name: name, size: size) ?? NSFont.systemFont(ofSize: size)
|
||||
}
|
||||
return NSFont.systemFont(ofSize: size)
|
||||
}
|
||||
|
||||
ctlInputMethod.ctlCandidateCurrent.keyLabelFont = labelFont(
|
||||
Self.ctlCandidateCurrent.keyLabelFont = labelFont(
|
||||
name: PrefMgr.shared.candidateKeyLabelFontName, size: keyLabelSize
|
||||
)
|
||||
ctlInputMethod.ctlCandidateCurrent.candidateFont = ctlInputMethod.candidateFont(
|
||||
Self.ctlCandidateCurrent.candidateFont = Self.candidateFont(
|
||||
name: PrefMgr.shared.candidateTextFontName, size: textSize
|
||||
)
|
||||
|
||||
|
@ -136,33 +133,46 @@ extension ctlInputMethod {
|
|||
let keyLabels =
|
||||
candidateKeys.count > 4 ? Array(candidateKeys) : Array(CandidateKey.defaultKeys)
|
||||
let keyLabelSuffix = state.type == .ofAssociates ? "^" : ""
|
||||
ctlInputMethod.ctlCandidateCurrent.keyLabels = keyLabels.map {
|
||||
Self.ctlCandidateCurrent.keyLabels = keyLabels.map {
|
||||
CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix)
|
||||
}
|
||||
|
||||
ctlInputMethod.ctlCandidateCurrent.delegate = self
|
||||
ctlInputMethod.ctlCandidateCurrent.reloadData()
|
||||
Self.ctlCandidateCurrent.delegate = self
|
||||
Self.ctlCandidateCurrent.showPageButtons = PrefMgr.shared.showPageButtonsInCandidateWindow
|
||||
Self.ctlCandidateCurrent.useLangIdentifier = PrefMgr.shared.handleDefaultCandidateFontsByLangIdentifier
|
||||
Self.ctlCandidateCurrent.locale = {
|
||||
switch inputMode {
|
||||
case .imeModeCHS: return "zh-Hans"
|
||||
case .imeModeCHT:
|
||||
if !PrefMgr.shared.shiftJISShinjitaiOutputEnabled, !PrefMgr.shared.chineseConversionEnabled {
|
||||
return "zh-Hant"
|
||||
}
|
||||
return "ja"
|
||||
default: return ""
|
||||
}
|
||||
}()
|
||||
Self.ctlCandidateCurrent.reloadData()
|
||||
|
||||
if #available(macOS 10.14, *) {
|
||||
// Spotlight 視窗會擋住 IMK 選字窗,所以需要特殊處理。
|
||||
if let ctlCandidateCurrent = ctlInputMethod.ctlCandidateCurrent as? ctlCandidateIMK {
|
||||
if let ctlCandidateCurrent = Self.ctlCandidateCurrent as? CtlCandidateIMK {
|
||||
while ctlCandidateCurrent.windowLevel() <= client.windowLevel() {
|
||||
ctlCandidateCurrent.setWindowLevel(UInt64(max(0, client.windowLevel() + 1000)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctlInputMethod.ctlCandidateCurrent.visible = true
|
||||
Self.ctlCandidateCurrent.visible = true
|
||||
|
||||
if isVerticalTyping {
|
||||
ctlInputMethod.ctlCandidateCurrent.set(
|
||||
Self.ctlCandidateCurrent.set(
|
||||
windowTopLeftPoint: NSPoint(
|
||||
x: lineHeightRect().origin.x + lineHeightRect().size.width + 4.0, y: lineHeightRect().origin.y - 4.0
|
||||
),
|
||||
bottomOutOfScreenAdjustmentHeight: lineHeightRect().size.height + 4.0
|
||||
)
|
||||
} else {
|
||||
ctlInputMethod.ctlCandidateCurrent.set(
|
||||
Self.ctlCandidateCurrent.set(
|
||||
windowTopLeftPoint: NSPoint(x: lineHeightRect().origin.x, y: lineHeightRect().origin.y - 4.0),
|
||||
bottomOutOfScreenAdjustmentHeight: lineHeightRect().size.height + 4.0
|
||||
)
|
||||
|
@ -178,7 +188,7 @@ extension ctlInputMethod {
|
|||
/// **REASON**: IMKCandidates has bug that it does not respect font attributes attached to the
|
||||
/// results generated from `candidiates() -> [Any]!` function. IMKCandidates is plagued with
|
||||
/// bugs which are not dealt in the recent decade, regardless Radar complaints from input method developers.
|
||||
/// 1) Remove the usage of ".languageIdentifier" from ctlCandidateUniversal.swift (already done).
|
||||
/// 1) Make sure the usage of ".languageIdentifier" is disabled in the Dev Zone of the vChewing Preferences.
|
||||
/// 2) Run "make update" in the project folder to download the latest git-submodule of dictionary file.
|
||||
/// 3) Compile the target "vChewingInstaller", run it. It will install the input method into
|
||||
/// "~/Library/Input Methods/" folder. Remember to ENABLE BOTH "vChewing-CHS"
|
||||
|
@ -188,7 +198,7 @@ extension ctlInputMethod {
|
|||
/// 5) Do NOT enable either KangXi conversion mode nor JIS conversion mode. They are disabled by default.
|
||||
/// 6) Expecting the glyph differences of the candidate "骨" between PingFang SC and PingFang TC when rendering
|
||||
/// the candidate window in different "vChewing-CHS" and "vChewing-CHT" input modes.
|
||||
static func candidateFont(name: String? = nil, size: CGFloat) -> NSFont {
|
||||
static func candidateFont(name: String? = nil, size: Double) -> NSFont {
|
||||
let finalReturnFont: NSFont =
|
||||
{
|
||||
switch IMEApp.currentInputMode {
|
|
@ -0,0 +1,237 @@
|
|||
// (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.
|
||||
|
||||
import InputMethodKit
|
||||
import NotifierUI
|
||||
import Shared
|
||||
|
||||
// MARK: - Facade
|
||||
|
||||
extension SessionCtl {
|
||||
/// 接受所有鍵鼠事件為 NSEvent,讓輸入法判斷是否要處理、該怎樣處理。
|
||||
/// - Parameters:
|
||||
/// - event: 裝置操作輸入事件,可能會是 nil。
|
||||
/// - sender: 呼叫了該函式的客體(無須使用)。
|
||||
/// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。
|
||||
@objc(handleEvent:client:) override func handle(_ event: NSEvent!, client sender: Any!) -> Bool {
|
||||
_ = sender // 防止格式整理工具毀掉與此對應的參數。
|
||||
|
||||
// MARK: 前置處理
|
||||
|
||||
// 更新此時的靜態狀態標記。
|
||||
state.isASCIIMode = isASCIIMode
|
||||
state.isVerticalTyping = isVerticalTyping
|
||||
|
||||
// 就這傳入的 NSEvent 都還有可能是 nil,Apple InputMethodKit 團隊到底在搞三小。
|
||||
// 只針對特定類型的 client() 進行處理。
|
||||
guard let event = event, sender is IMKTextInput else {
|
||||
resetKeyHandler()
|
||||
return false
|
||||
}
|
||||
|
||||
// 用 Shift 開關半形英數模式,僅對 macOS 10.15 及之後的 macOS 有效。
|
||||
let shouldUseShiftToggleHandle: Bool = {
|
||||
switch PrefMgr.shared.shiftKeyAccommodationBehavior {
|
||||
case 0: return false
|
||||
case 1: return Shared.arrClientShiftHandlingExceptionList.contains(clientBundleIdentifier)
|
||||
case 2: return true
|
||||
default: return false
|
||||
}
|
||||
}()
|
||||
|
||||
/// 警告:這裡的 event 必須是原始 event 且不能被 var,否則會影響 Shift 中英模式判定。
|
||||
if #available(macOS 10.15, *) {
|
||||
if Self.theShiftKeyDetector.check(event), !PrefMgr.shared.disableShiftTogglingAlphanumericalMode {
|
||||
if !shouldUseShiftToggleHandle || (!rencentKeyHandledByKeyHandlerEtc && shouldUseShiftToggleHandle) {
|
||||
let status = NSLocalizedString("NotificationSwitchShift", comment: "")
|
||||
Notifier.notify(
|
||||
message: isASCIIMode.toggled()
|
||||
? NSLocalizedString("Alphanumerical Input Mode", comment: "") + "\n" + status
|
||||
: NSLocalizedString("Chinese Input Mode", comment: "") + "\n" + status
|
||||
)
|
||||
}
|
||||
if shouldUseShiftToggleHandle {
|
||||
rencentKeyHandledByKeyHandlerEtc = false
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: 針對客體的具體處理
|
||||
|
||||
// 不再讓威注音處理由 Shift 切換到的英文模式的按鍵輸入。
|
||||
if isASCIIMode { return false }
|
||||
|
||||
/// 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。
|
||||
/// 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。
|
||||
/// 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false,
|
||||
/// 否則,每次處理這種判斷時都會觸發 NSInternalInconsistencyException。
|
||||
if event.type == .flagsChanged { return false }
|
||||
|
||||
/// 沒有文字輸入客體的話,就不要再往下處理了。
|
||||
guard client() != nil else { return false }
|
||||
|
||||
var eventToDeal = event
|
||||
|
||||
// 如果是方向鍵輸入的話,就想辦法帶上標記資訊、來說明當前是縱排還是橫排。
|
||||
if event.isUp || event.isDown || event.isLeft || event.isRight {
|
||||
eventToDeal = event.reinitiate(charactersIgnoringModifiers: isVerticalTyping ? "Vertical" : "Horizontal") ?? event
|
||||
}
|
||||
|
||||
// 使 NSEvent 自翻譯,這樣可以讓 Emacs NSEvent 變成標準 NSEvent。
|
||||
if eventToDeal.isEmacsKey {
|
||||
let verticalProcessing = (state.isCandidateContainer) ? state.isVerticalCandidateWindow : state.isVerticalTyping
|
||||
eventToDeal = eventToDeal.convertFromEmacKeyEvent(isVerticalContext: verticalProcessing)
|
||||
}
|
||||
|
||||
// 準備修飾鍵,用來判定要新增的詞彙是否需要賦以非常低的權重。
|
||||
Self.areWeNerfing = eventToDeal.modifierFlags.contains([.shift, .command])
|
||||
|
||||
// IMK 選字窗處理,當且僅當啟用了 IMK 選字窗的時候才會生效。
|
||||
if let result = imkCandidatesEventPreHandler(event: eventToDeal) {
|
||||
if shouldUseShiftToggleHandle { rencentKeyHandledByKeyHandlerEtc = result }
|
||||
return result
|
||||
}
|
||||
|
||||
/// 剩下的 NSEvent 直接交給 commonEventHandler 來處理。
|
||||
/// 這樣可以與 IMK 選字窗共用按鍵處理資源,維護起來也比較方便。
|
||||
let result = commonEventHandler(eventToDeal)
|
||||
if shouldUseShiftToggleHandle {
|
||||
rencentKeyHandledByKeyHandlerEtc = result
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private functions
|
||||
|
||||
extension SessionCtl {
|
||||
/// 完成 handle() 函式本該完成的內容,但去掉了與 IMK 選字窗有關的判斷語句。
|
||||
/// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。
|
||||
/// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。
|
||||
/// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。
|
||||
private func commonEventHandler(_ event: NSEvent) -> Bool {
|
||||
// 無法列印的訊號輸入,一概不作處理。
|
||||
// 這個過程不能放在 KeyHandler 內,否則不會起作用。
|
||||
if !event.charCode.isPrintable { return false }
|
||||
|
||||
/// 將按鍵行為與當前輸入法狀態結合起來、交給按鍵調度模組來處理。
|
||||
/// 再根據返回的 result bool 數值來告知 IMK「這個按鍵事件是被處理了還是被放行了」。
|
||||
/// 這裡不用 keyHandler.handleCandidate() 是因為需要針對聯想詞輸入狀態做額外處理。
|
||||
let result = keyHandler.handle(input: event, state: state) { newState in
|
||||
self.handle(state: newState)
|
||||
} errorCallback: { errorString in
|
||||
vCLog(errorString)
|
||||
IMEApp.buzz()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/// 完成 handle() 函式本該完成的內容,但專門處理與 IMK 選字窗有關的判斷語句。
|
||||
/// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。
|
||||
/// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。
|
||||
/// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。
|
||||
private func imkCandidatesEventPreHandler(event eventToDeal: NSEvent) -> Bool? {
|
||||
// IMK 選字窗處理,當且僅當啟用了 IMK 選字窗的時候才會生效。
|
||||
// 這樣可以讓 interpretKeyEvents() 函式自行判斷:
|
||||
// - 是就地交給 imkCandidates.interpretKeyEvents() 處理?
|
||||
// - 還是藉由 delegate 扔回 SessionCtl 給 KeyHandler 處理?
|
||||
if let imkCandidates = Self.ctlCandidateCurrent as? CtlCandidateIMK, imkCandidates.visible {
|
||||
let event: NSEvent = CtlCandidateIMK.replaceNumPadKeyCodes(target: eventToDeal) ?? eventToDeal
|
||||
|
||||
// Shift+Enter 是個特殊情形,不提前攔截處理的話、會有垃圾參數傳給 delegate 的 keyHandler 從而崩潰。
|
||||
// 所以這裡直接將 Shift Flags 清空。
|
||||
if event.isShiftHold, event.isEnter {
|
||||
guard let newEvent = event.reinitiate(modifierFlags: []) else {
|
||||
NSSound.beep()
|
||||
return true
|
||||
}
|
||||
|
||||
return imkCandidatesEventSubHandler(event: newEvent)
|
||||
}
|
||||
|
||||
// 聯想詞選字。
|
||||
if let newChar = CtlCandidateIMK.defaultIMKSelectionKey[event.keyCode],
|
||||
event.isShiftHold, state.type == .ofAssociates,
|
||||
let newEvent = event.reinitiate(modifierFlags: [], characters: newChar)
|
||||
{
|
||||
if #available(macOS 10.14, *) {
|
||||
imkCandidates.handleKeyboardEvent(newEvent)
|
||||
} else {
|
||||
imkCandidates.interpretKeyEvents([newEvent])
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return imkCandidatesEventSubHandler(event: event)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func imkCandidatesEventSubHandler(event: NSEvent) -> Bool {
|
||||
let eventArray = [event]
|
||||
guard let imkC = Self.ctlCandidateCurrent as? CtlCandidateIMK else { return false }
|
||||
if event.isEsc || event.isBackSpace || event.isDelete || (event.isShiftHold && !event.isSpace) {
|
||||
return commonEventHandler(event)
|
||||
} else if event.isSymbolMenuPhysicalKey {
|
||||
// 符號鍵的行為是固定的,不受偏好設定影響。
|
||||
switch imkC.currentLayout {
|
||||
case .horizontal: _ = event.isShiftHold ? imkC.moveUp(self) : imkC.moveDown(self)
|
||||
case .vertical: _ = event.isShiftHold ? imkC.moveLeft(self) : imkC.moveRight(self)
|
||||
}
|
||||
return true
|
||||
} else if event.isSpace {
|
||||
switch PrefMgr.shared.specifyShiftSpaceKeyBehavior {
|
||||
case true: _ = event.isShiftHold ? imkC.highlightNextCandidate() : imkC.showNextPage()
|
||||
case false: _ = event.isShiftHold ? imkC.showNextPage() : imkC.highlightNextCandidate()
|
||||
}
|
||||
return true
|
||||
} else if event.isTab {
|
||||
switch PrefMgr.shared.specifyShiftTabKeyBehavior {
|
||||
case true: _ = event.isShiftHold ? imkC.showPreviousPage() : imkC.showNextPage()
|
||||
case false: _ = event.isShiftHold ? imkC.highlightPreviousCandidate() : imkC.highlightNextCandidate()
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
if let newChar = CtlCandidateIMK.defaultIMKSelectionKey[event.keyCode] {
|
||||
/// 根據 KeyCode 重新換算一下選字鍵的 NSEvent,糾正其 Character 數值。
|
||||
/// 反正 IMK 選字窗目前也沒辦法修改選字鍵。
|
||||
let newEvent = event.reinitiate(characters: newChar)
|
||||
if let newEvent = newEvent {
|
||||
if PrefMgr.shared.useSCPCTypingMode, state.type == .ofAssociates {
|
||||
// 註:input.isShiftHold 已經在 Self.handle() 內處理,因為在那邊處理才有效。
|
||||
return event.isShiftHold ? true : commonEventHandler(event)
|
||||
} else {
|
||||
if #available(macOS 10.14, *) {
|
||||
imkC.handleKeyboardEvent(newEvent)
|
||||
} else {
|
||||
imkC.interpretKeyEvents([newEvent])
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if PrefMgr.shared.useSCPCTypingMode, !event.isReservedKey {
|
||||
return commonEventHandler(event)
|
||||
}
|
||||
|
||||
if state.type == .ofAssociates,
|
||||
!event.isPageUp, !event.isPageDown, !event.isCursorForward, !event.isCursorBackward,
|
||||
!event.isCursorClockLeft, !event.isCursorClockRight, !event.isSpace,
|
||||
!event.isEnter || !PrefMgr.shared.alsoConfirmAssociatedCandidatesByEnter
|
||||
{
|
||||
return commonEventHandler(event)
|
||||
}
|
||||
imkC.interpretKeyEvents(eventArray)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,9 +8,12 @@
|
|||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import PopupCompositionBuffer
|
||||
import Shared
|
||||
|
||||
// MARK: - 狀態調度 (State Handling)
|
||||
|
||||
extension ctlInputMethod {
|
||||
extension SessionCtl {
|
||||
/// 針對傳入的新狀態進行調度。
|
||||
///
|
||||
/// 先將舊狀態單獨記錄起來,再將新舊狀態作為參數,
|
||||
|
@ -21,9 +24,9 @@ extension ctlInputMethod {
|
|||
state = newState
|
||||
switch state.type {
|
||||
case .ofDeactivated:
|
||||
ctlInputMethod.ctlCandidateCurrent.delegate = nil
|
||||
ctlInputMethod.ctlCandidateCurrent.visible = false
|
||||
ctlInputMethod.tooltipInstance.hide()
|
||||
Self.ctlCandidateCurrent.delegate = nil
|
||||
Self.ctlCandidateCurrent.visible = false
|
||||
Self.tooltipInstance.hide()
|
||||
if previous.hasComposition {
|
||||
commit(text: previous.displayedText)
|
||||
}
|
||||
|
@ -36,29 +39,29 @@ extension ctlInputMethod {
|
|||
state = IMEState.ofEmpty()
|
||||
previous = state
|
||||
}
|
||||
ctlInputMethod.ctlCandidateCurrent.visible = false
|
||||
ctlInputMethod.tooltipInstance.hide()
|
||||
Self.ctlCandidateCurrent.visible = false
|
||||
Self.tooltipInstance.hide()
|
||||
// 全專案用以判斷「.Abortion」的地方僅此一處。
|
||||
if previous.hasComposition, state.type != .ofAbortion {
|
||||
commit(text: previous.displayedText)
|
||||
}
|
||||
// 在這裡手動再取消一次選字窗與工具提示的顯示,可謂雙重保險。
|
||||
ctlInputMethod.ctlCandidateCurrent.visible = false
|
||||
ctlInputMethod.tooltipInstance.hide()
|
||||
Self.ctlCandidateCurrent.visible = false
|
||||
Self.tooltipInstance.hide()
|
||||
clearInlineDisplay()
|
||||
// 最後一道保險
|
||||
keyHandler.clear()
|
||||
case .ofCommitting:
|
||||
ctlInputMethod.ctlCandidateCurrent.visible = false
|
||||
ctlInputMethod.tooltipInstance.hide()
|
||||
Self.ctlCandidateCurrent.visible = false
|
||||
Self.tooltipInstance.hide()
|
||||
let textToCommit = state.textToCommit
|
||||
if !textToCommit.isEmpty { commit(text: textToCommit) }
|
||||
clearInlineDisplay()
|
||||
// 最後一道保險
|
||||
keyHandler.clear()
|
||||
case .ofInputting:
|
||||
ctlInputMethod.ctlCandidateCurrent.visible = false
|
||||
ctlInputMethod.tooltipInstance.hide()
|
||||
Self.ctlCandidateCurrent.visible = false
|
||||
Self.tooltipInstance.hide()
|
||||
let textToCommit = state.textToCommit
|
||||
if !textToCommit.isEmpty { commit(text: textToCommit) }
|
||||
setInlineDisplayWithCursor()
|
||||
|
@ -66,27 +69,27 @@ extension ctlInputMethod {
|
|||
show(tooltip: state.tooltip)
|
||||
}
|
||||
case .ofMarking:
|
||||
ctlInputMethod.ctlCandidateCurrent.visible = false
|
||||
Self.ctlCandidateCurrent.visible = false
|
||||
setInlineDisplayWithCursor()
|
||||
if state.tooltip.isEmpty {
|
||||
ctlInputMethod.tooltipInstance.hide()
|
||||
Self.tooltipInstance.hide()
|
||||
} else {
|
||||
show(tooltip: state.tooltip)
|
||||
}
|
||||
case .ofCandidates, .ofAssociates, .ofSymbolTable:
|
||||
ctlInputMethod.tooltipInstance.hide()
|
||||
Self.tooltipInstance.hide()
|
||||
setInlineDisplayWithCursor()
|
||||
showCandidates()
|
||||
default: break
|
||||
}
|
||||
// 浮動組字窗的顯示判定
|
||||
if state.hasComposition, PrefMgr.shared.clientsIMKTextInputIncapable.contains(clientBundleIdentifier) {
|
||||
ctlInputMethod.popupCompositionBuffer.isTypingDirectionVertical = isVerticalTyping
|
||||
ctlInputMethod.popupCompositionBuffer.show(
|
||||
Self.popupCompositionBuffer.isTypingDirectionVertical = isVerticalTyping
|
||||
Self.popupCompositionBuffer.show(
|
||||
state: state, at: lineHeightRect(zeroCursor: true).origin
|
||||
)
|
||||
} else {
|
||||
ctlInputMethod.popupCompositionBuffer.hide()
|
||||
Self.popupCompositionBuffer.hide()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
// (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.
|
||||
|
||||
import Foundation
|
||||
import Tekkon
|
||||
|
||||
// MARK: - IMKCandidates 功能擴充
|
||||
|
||||
extension SessionCtl {
|
||||
/// 生成 IMK 選字窗專用的候選字串陣列。
|
||||
/// - Parameter sender: 呼叫了該函式的客體(無須使用)。
|
||||
/// - Returns: IMK 選字窗專用的候選字串陣列。
|
||||
override func candidates(_ sender: Any!) -> [Any]! {
|
||||
_ = sender // 防止格式整理工具毀掉與此對應的參數。
|
||||
var arrResult = [String]()
|
||||
|
||||
// 注意:下文中的不可列印字元是用來方便在 IMEState 當中用來分割資料的。
|
||||
func handleIMKCandidatesPrepared(_ candidates: [(String, String)], prefix: String = "") {
|
||||
for theCandidate in candidates {
|
||||
let theConverted = ChineseConverter.kanjiConversionIfRequired(theCandidate.1)
|
||||
var result = (theCandidate.1 == theConverted) ? theCandidate.1 : "\(theConverted)\u{1A}(\(theCandidate.1))"
|
||||
if arrResult.contains(result) {
|
||||
let reading: String =
|
||||
PrefMgr.shared.showHanyuPinyinInCompositionBuffer
|
||||
? Tekkon.cnvPhonaToHanyuPinyin(target: Tekkon.restoreToneOneInZhuyinKey(target: theCandidate.0))
|
||||
: theCandidate.0
|
||||
result = "\(result)\u{17}(\(reading))"
|
||||
}
|
||||
arrResult.append(prefix + result)
|
||||
}
|
||||
}
|
||||
|
||||
if state.type == .ofAssociates {
|
||||
handleIMKCandidatesPrepared(state.candidates, prefix: "⇧")
|
||||
} else if state.type == .ofSymbolTable {
|
||||
// 分類符號選單不會出現同符異音項、不需要康熙 / JIS 轉換,所以使用簡化過的處理方式。
|
||||
arrResult = state.candidates.map(\.1)
|
||||
} else if state.type == .ofCandidates {
|
||||
guard !state.candidates.isEmpty else { return .init() }
|
||||
if state.candidates[0].0.contains("_punctuation") {
|
||||
arrResult = state.candidates.map(\.1) // 標點符號選單處理。
|
||||
} else {
|
||||
handleIMKCandidatesPrepared(state.candidates)
|
||||
}
|
||||
}
|
||||
|
||||
return arrResult
|
||||
}
|
||||
|
||||
/// IMK 選字窗限定函式,只要選字窗內的高亮內容選擇出現變化了、就會呼叫這個函式。
|
||||
/// - Parameter _: 已經高亮選中的候選字詞內容。
|
||||
override open func candidateSelectionChanged(_: NSAttributedString!) {
|
||||
// 警告:不要考慮用實作這個函式的方式來更新內文組字區的顯示。
|
||||
// 因為這樣會導致 IMKServer.commitCompositionWithReply() 呼叫你本來不想呼叫的 commitComposition(),
|
||||
// 然後 keyHandler 會被重設,屆時輸入法會在狀態處理等方面崩潰掉。
|
||||
|
||||
// 這個函式的實作其實很容易誘發各種崩潰,所以最好不要輕易實作。
|
||||
|
||||
// 有些幹話還是要講的:
|
||||
// 在這個函式當中試圖(無論是否拿著傳入的參數)從 ctlCandidateIMK 找 identifier 的話,
|
||||
// 只會找出 NSNotFound。你想 NSLog 列印看 identifier 是多少,輸入法直接崩潰。
|
||||
// 而且會他媽的崩得連 console 內的 ips 錯誤報告都沒有。
|
||||
// 在下文的 candidateSelected() 試圖看每個候選字的 identifier 的話,永遠都只能拿到 NSNotFound。
|
||||
// 衰洨 IMK 真的看上去就像是沒有做過單元測試的東西,賈伯斯有檢查過的話會被氣得從棺材裡爬出來。
|
||||
}
|
||||
|
||||
/// IMK 選字窗限定函式,只要選字窗確認了某個候選字詞的選擇、就會呼叫這個函式。
|
||||
/// - Parameter candidateString: 已經確認的候選字詞內容。
|
||||
override open func candidateSelected(_ candidateString: NSAttributedString!) {
|
||||
let candidateString: String = candidateString?.string ?? ""
|
||||
if state.type == .ofAssociates {
|
||||
if !PrefMgr.shared.alsoConfirmAssociatedCandidatesByEnter {
|
||||
handle(state: IMEState.ofAbortion())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var indexDeducted = 0
|
||||
|
||||
// 注意:下文中的不可列印字元是用來方便在 IMEState 當中用來分割資料的。
|
||||
func handleIMKCandidatesSelected(_ candidates: [(String, String)], prefix: String = "") {
|
||||
for (i, neta) in candidates.enumerated() {
|
||||
let theConverted = ChineseConverter.kanjiConversionIfRequired(neta.1)
|
||||
let netaShown = (neta.1 == theConverted) ? neta.1 : "\(theConverted)\u{1A}(\(neta.1))"
|
||||
let reading: String =
|
||||
PrefMgr.shared.showHanyuPinyinInCompositionBuffer
|
||||
? Tekkon.cnvPhonaToHanyuPinyin(target: Tekkon.restoreToneOneInZhuyinKey(target: neta.0)) : neta.0
|
||||
let netaShownWithPronunciation = "\(netaShown)\u{17}(\(reading))"
|
||||
if candidateString == prefix + netaShownWithPronunciation {
|
||||
indexDeducted = i
|
||||
break
|
||||
}
|
||||
if candidateString == prefix + netaShown {
|
||||
indexDeducted = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分類符號選單不會出現同符異音項、不需要康熙 / JIS 轉換,所以使用簡化過的處理方式。
|
||||
func handleSymbolCandidatesSelected(_ candidates: [(String, String)]) {
|
||||
for (i, neta) in candidates.enumerated() {
|
||||
if candidateString == neta.1 {
|
||||
indexDeducted = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if state.type == .ofAssociates {
|
||||
handleIMKCandidatesSelected(state.candidates, prefix: "⇧")
|
||||
} else if state.type == .ofSymbolTable {
|
||||
handleSymbolCandidatesSelected(state.candidates)
|
||||
} else if state.type == .ofCandidates {
|
||||
guard !state.candidates.isEmpty else { return }
|
||||
if state.candidates[0].0.contains("_punctuation") {
|
||||
handleSymbolCandidatesSelected(state.candidates) // 標點符號選單處理。
|
||||
} else {
|
||||
handleIMKCandidatesSelected(state.candidates)
|
||||
}
|
||||
}
|
||||
candidatePairSelected(at: indexDeducted)
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import NotifierUI
|
||||
import Preferences
|
||||
|
||||
extension Bool {
|
||||
|
@ -20,7 +21,7 @@ extension Bool {
|
|||
|
||||
// 因為選單部分的內容又臭又長,所以就單獨拉到一個檔案內管理了。
|
||||
|
||||
extension ctlInputMethod {
|
||||
extension SessionCtl {
|
||||
override func menu() -> NSMenu! {
|
||||
let optionKeyPressed = NSEvent.modifierFlags.contains(.option)
|
||||
|
||||
|
@ -191,7 +192,7 @@ extension ctlInputMethod {
|
|||
|
||||
// MARK: - IME Menu Items
|
||||
|
||||
extension ctlInputMethod {
|
||||
extension SessionCtl {
|
||||
@objc override func showPreferences(_: Any?) {
|
||||
if #unavailable(macOS 10.15) {
|
||||
showLegacyPreferences()
|
||||
|
@ -223,7 +224,7 @@ extension ctlInputMethod {
|
|||
|
||||
@objc func toggleSCPCTypingMode(_: Any? = nil) {
|
||||
resetKeyHandler()
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Per-Char Select Mode", comment: "") + "\n"
|
||||
+ (PrefMgr.shared.useSCPCTypingMode.toggled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
|
@ -233,7 +234,7 @@ extension ctlInputMethod {
|
|||
|
||||
@objc func toggleChineseConverter(_: Any?) {
|
||||
resetKeyHandler()
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Force KangXi Writing", comment: "") + "\n"
|
||||
+ (PrefMgr.shared.chineseConversionEnabled.toggled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
|
@ -243,7 +244,7 @@ extension ctlInputMethod {
|
|||
|
||||
@objc func toggleShiftJISShinjitaiOutput(_: Any?) {
|
||||
resetKeyHandler()
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("JIS Shinjitai Output", comment: "") + "\n"
|
||||
+ (PrefMgr.shared.shiftJISShinjitaiOutputEnabled.toggled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
|
@ -253,7 +254,7 @@ extension ctlInputMethod {
|
|||
|
||||
@objc func toggleCurrencyNumerals(_: Any?) {
|
||||
resetKeyHandler()
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Currency Numeral Output", comment: "") + "\n"
|
||||
+ (PrefMgr.shared.currencyNumeralsEnabled.toggled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
|
@ -263,7 +264,7 @@ extension ctlInputMethod {
|
|||
|
||||
@objc func toggleHalfWidthPunctuation(_: Any?) {
|
||||
resetKeyHandler()
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Half-Width Punctuation Mode", comment: "") + "\n"
|
||||
+ (PrefMgr.shared.halfWidthPunctuationEnabled.toggled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
|
@ -273,7 +274,7 @@ extension ctlInputMethod {
|
|||
|
||||
@objc func toggleCNS11643Enabled(_: Any?) {
|
||||
resetKeyHandler()
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("CNS11643 Mode", comment: "") + "\n"
|
||||
+ (PrefMgr.shared.cns11643Enabled.toggled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
|
@ -283,7 +284,7 @@ extension ctlInputMethod {
|
|||
|
||||
@objc func toggleSymbolEnabled(_: Any?) {
|
||||
resetKeyHandler()
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Symbol & Emoji Input", comment: "") + "\n"
|
||||
+ (PrefMgr.shared.symbolInputEnabled.toggled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
|
@ -293,7 +294,7 @@ extension ctlInputMethod {
|
|||
|
||||
@objc func toggleAssociatedPhrasesEnabled(_: Any?) {
|
||||
resetKeyHandler()
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Per-Char Associated Phrases", comment: "") + "\n"
|
||||
+ (PrefMgr.shared.associatedPhrasesEnabled.toggled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||
|
@ -303,7 +304,7 @@ extension ctlInputMethod {
|
|||
|
||||
@objc func togglePhraseReplacement(_: Any?) {
|
||||
resetKeyHandler()
|
||||
NotifierController.notify(
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Use Phrase Replacement", comment: "") + "\n"
|
||||
+ (PrefMgr.shared.phraseReplacementEnabled.toggled()
|
||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
|
@ -6,12 +6,19 @@
|
|||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol {
|
||||
import CandidateWindow
|
||||
import Shared
|
||||
|
||||
/// 威注音自用的 IMKCandidates 型別。因為有用到 bridging header,所以無法弄成 Swift Package。
|
||||
public class CtlCandidateIMK: IMKCandidates, CtlCandidateProtocol {
|
||||
public var showPageButtons: Bool = false
|
||||
public var locale: String = ""
|
||||
public var useLangIdentifier: Bool = false
|
||||
public var currentLayout: CandidateLayout = .horizontal
|
||||
public static let defaultIMKSelectionKey: [UInt16: String] = [
|
||||
18: "1", 19: "2", 20: "3", 21: "4", 23: "5", 22: "6", 26: "7", 28: "8", 25: "9",
|
||||
]
|
||||
public weak var delegate: ctlCandidateDelegate? {
|
||||
public weak var delegate: CtlCandidateDelegate? {
|
||||
didSet {
|
||||
reloadData()
|
||||
}
|
||||
|
@ -40,26 +47,15 @@ public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol {
|
|||
ofSize: 14, weight: .medium
|
||||
)
|
||||
|
||||
public var candidateFont = NSFont.systemFont(ofSize: PrefMgr.shared.candidateListTextSize) {
|
||||
public var candidateFont = NSFont.systemFont(ofSize: 16) {
|
||||
didSet {
|
||||
if #available(macOS 10.14, *) { setFontSize(candidateFont.pointSize) }
|
||||
var attributes = attributes()
|
||||
// FB11300759: Set "NSAttributedString.Key.font" doesn't work.
|
||||
attributes?[NSAttributedString.Key.font] = candidateFont
|
||||
if PrefMgr.shared.handleDefaultCandidateFontsByLangIdentifier {
|
||||
switch IMEApp.currentInputMode {
|
||||
case .imeModeCHS:
|
||||
if #available(macOS 12.0, *) {
|
||||
attributes?[NSAttributedString.Key.languageIdentifier] = "zh-Hans" as AnyObject
|
||||
}
|
||||
case .imeModeCHT:
|
||||
if #available(macOS 12.0, *) {
|
||||
attributes?[NSAttributedString.Key.languageIdentifier] =
|
||||
(PrefMgr.shared.shiftJISShinjitaiOutputEnabled || PrefMgr.shared.chineseConversionEnabled)
|
||||
? "ja" as AnyObject : "zh-Hant" as AnyObject
|
||||
}
|
||||
default:
|
||||
break
|
||||
if useLangIdentifier {
|
||||
attributes?[NSAttributedString.Key.languageIdentifier] = locale as AnyObject
|
||||
}
|
||||
}
|
||||
setAttributes(attributes)
|
||||
|
@ -119,7 +115,7 @@ public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol {
|
|||
|
||||
private var pageCount: Int {
|
||||
guard let delegate = delegate else { return 0 }
|
||||
let totalCount = delegate.candidateCountForController(self)
|
||||
let totalCount = delegate.candidatePairs().count
|
||||
let keyLabelCount = keyLabels.count
|
||||
return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0)
|
||||
}
|
||||
|
@ -151,7 +147,7 @@ public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol {
|
|||
public func candidateIndexAtKeyLabelIndex(_ index: Int) -> Int {
|
||||
guard let delegate = delegate else { return Int.max }
|
||||
let result = currentPageIndex * keyLabels.count + index
|
||||
return result < delegate.candidateCountForController(self) ? result : Int.max
|
||||
return result < delegate.candidatePairs().count ? result : Int.max
|
||||
}
|
||||
|
||||
public var selectedCandidateIndex: Int {
|
||||
|
@ -159,17 +155,17 @@ public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol {
|
|||
set { selectCandidate(withIdentifier: newValue) }
|
||||
}
|
||||
|
||||
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) {
|
||||
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: Double) {
|
||||
DispatchQueue.main.async {
|
||||
self.doSet(windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height)
|
||||
}
|
||||
}
|
||||
|
||||
func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: CGFloat) {
|
||||
func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: Double) {
|
||||
guard var screenFrame = NSScreen.main?.visibleFrame else { return }
|
||||
var adjustedPoint = windowTopLeftPoint
|
||||
let windowSize = candidateFrame().size
|
||||
var delta = heightDelta
|
||||
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.seniorTheBeast
|
||||
for frame in NSScreen.screens.map(\.visibleFrame).filter({ $0.contains(windowTopLeftPoint) }) {
|
||||
screenFrame = frame
|
||||
break
|
||||
|
@ -185,74 +181,6 @@ public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol {
|
|||
|
||||
setCandidateFrameTopLeft(adjustedPoint)
|
||||
}
|
||||
|
||||
override public func interpretKeyEvents(_ eventArray: [NSEvent]) {
|
||||
// 鬼知道為什麼這個函式接收的參數是陣列,但經過測試卻發現這個函式收到的陣列往往內容只有一個。
|
||||
// 這也可能是 Objective-C 當中允許接收內容為 nil 的一種方式。
|
||||
guard !eventArray.isEmpty else { return }
|
||||
let event = eventArray[0]
|
||||
guard let delegate = delegate else { return }
|
||||
if event.isEsc || event.isBackSpace || event.isDelete || (event.isShiftHold && !event.isSpace) {
|
||||
_ = delegate.sharedEventHandler(event)
|
||||
} else if event.isSymbolMenuPhysicalKey {
|
||||
// 符號鍵的行為是固定的,不受偏好設定影響。
|
||||
switch currentLayout {
|
||||
case .horizontal: event.isShiftHold ? moveUp(self) : moveDown(self)
|
||||
case .vertical: event.isShiftHold ? moveLeft(self) : moveRight(self)
|
||||
}
|
||||
} else if event.isSpace {
|
||||
switch PrefMgr.shared.specifyShiftSpaceKeyBehavior {
|
||||
case true: _ = event.isShiftHold ? highlightNextCandidate() : showNextPage()
|
||||
case false: _ = event.isShiftHold ? showNextPage() : highlightNextCandidate()
|
||||
}
|
||||
} else if event.isTab {
|
||||
switch PrefMgr.shared.specifyShiftTabKeyBehavior {
|
||||
case true: _ = event.isShiftHold ? showPreviousPage() : showNextPage()
|
||||
case false: _ = event.isShiftHold ? highlightPreviousCandidate() : highlightNextCandidate()
|
||||
}
|
||||
} else {
|
||||
if let newChar = Self.defaultIMKSelectionKey[event.keyCode] {
|
||||
/// 根據 KeyCode 重新換算一下選字鍵的 NSEvent,糾正其 Character 數值。
|
||||
/// 反正 IMK 選字窗目前也沒辦法修改選字鍵。
|
||||
let newEvent = event.reinitiate(characters: newChar)
|
||||
if let newEvent = newEvent {
|
||||
if PrefMgr.shared.useSCPCTypingMode, delegate.isAssociatedPhrasesState {
|
||||
// 註:input.isShiftHold 已經在 ctlInputMethod.handle() 內處理,因為在那邊處理才有效。
|
||||
if !event.isShiftHold {
|
||||
_ = delegate.sharedEventHandler(event)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if #available(macOS 10.14, *) {
|
||||
handleKeyboardEvent(newEvent)
|
||||
} else {
|
||||
super.interpretKeyEvents([newEvent])
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if PrefMgr.shared.useSCPCTypingMode, !event.isReservedKey {
|
||||
_ = delegate.sharedEventHandler(event)
|
||||
return
|
||||
}
|
||||
|
||||
if delegate.isAssociatedPhrasesState,
|
||||
!event.isPageUp, !event.isPageDown, !event.isCursorForward, !event.isCursorBackward,
|
||||
!event.isCursorClockLeft, !event.isCursorClockRight, !event.isSpace,
|
||||
!event.isEnter || !PrefMgr.shared.alsoConfirmAssociatedCandidatesByEnter
|
||||
{
|
||||
_ = delegate.sharedEventHandler(event)
|
||||
return
|
||||
}
|
||||
super.interpretKeyEvents(eventArray)
|
||||
}
|
||||
}
|
||||
|
||||
public func superInterpretKeyEvents(_ eventArray: [NSEvent]) {
|
||||
super.interpretKeyEvents(eventArray)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Generate TISInputSource Object
|
||||
|
@ -277,7 +205,7 @@ var currentTISInputSource: TISInputSource? {
|
|||
|
||||
// MARK: - Translating NumPad KeyCodes to Default IMK Candidate Selection KeyCodes.
|
||||
|
||||
extension ctlCandidateIMK {
|
||||
extension CtlCandidateIMK {
|
||||
public static func replaceNumPadKeyCodes(target event: NSEvent) -> NSEvent? {
|
||||
let mapNumPadKeyCodeTranslation: [UInt16: UInt16] = [
|
||||
83: 18, 84: 19, 85: 20, 86: 21, 87: 23, 88: 22, 89: 26, 91: 28, 92: 25,
|
|
@ -1,175 +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.
|
||||
|
||||
public enum CandidateLayout {
|
||||
case horizontal
|
||||
case vertical
|
||||
}
|
||||
|
||||
public class CandidateKeyLabel: NSObject {
|
||||
public private(set) var key: String
|
||||
public private(set) var displayedText: String
|
||||
|
||||
public init(key: String, displayedText: String) {
|
||||
self.key = key
|
||||
self.displayedText = displayedText
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ctlCandidateDelegate: AnyObject {
|
||||
var isAssociatedPhrasesState: Bool { get }
|
||||
func sharedEventHandler(_ event: NSEvent) -> Bool
|
||||
func candidateCountForController(_ controller: ctlCandidateProtocol) -> Int
|
||||
func candidatesForController(_ controller: ctlCandidateProtocol) -> [(String, String)]
|
||||
func ctlCandidate(_ controller: ctlCandidateProtocol, candidateAtIndex index: Int)
|
||||
-> (String, String)
|
||||
func candidateSelected(at index: Int)
|
||||
}
|
||||
|
||||
public protocol ctlCandidateProtocol {
|
||||
var currentLayout: CandidateLayout { get set }
|
||||
var delegate: ctlCandidateDelegate? { get set }
|
||||
var selectedCandidateIndex: Int { get set }
|
||||
var visible: Bool { get set }
|
||||
var windowTopLeftPoint: NSPoint { get set }
|
||||
var keyLabels: [CandidateKeyLabel] { get set }
|
||||
var keyLabelFont: NSFont { get set }
|
||||
var candidateFont: NSFont { get set }
|
||||
var tooltip: String { get set }
|
||||
|
||||
init(_ layout: CandidateLayout)
|
||||
func reloadData()
|
||||
func showNextPage() -> Bool
|
||||
func showPreviousPage() -> Bool
|
||||
func highlightNextCandidate() -> Bool
|
||||
func highlightPreviousCandidate() -> Bool
|
||||
func candidateIndexAtKeyLabelIndex(_: Int) -> Int
|
||||
func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat)
|
||||
}
|
||||
|
||||
public class ctlCandidate: NSWindowController, ctlCandidateProtocol {
|
||||
public var currentLayout: CandidateLayout = .horizontal
|
||||
public weak var delegate: ctlCandidateDelegate? {
|
||||
didSet {
|
||||
reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
public var selectedCandidateIndex: Int = .max
|
||||
public var visible = false {
|
||||
didSet {
|
||||
NSObject.cancelPreviousPerformRequests(withTarget: self)
|
||||
if visible {
|
||||
window?.perform(#selector(NSWindow.orderFront(_:)), with: self, afterDelay: 0.0)
|
||||
} else {
|
||||
window?.perform(#selector(NSWindow.orderOut(_:)), with: self, afterDelay: 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var windowTopLeftPoint: NSPoint {
|
||||
get {
|
||||
guard let frameRect = window?.frame else {
|
||||
return NSPoint.zero
|
||||
}
|
||||
return NSPoint(x: frameRect.minX, y: frameRect.maxY)
|
||||
}
|
||||
set {
|
||||
DispatchQueue.main.async {
|
||||
self.set(windowTopLeftPoint: newValue, bottomOutOfScreenAdjustmentHeight: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public required init(_: CandidateLayout = .horizontal) {
|
||||
super.init(window: .init())
|
||||
visible = false
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public var keyLabels: [CandidateKeyLabel] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
|
||||
.map {
|
||||
CandidateKeyLabel(key: $0, displayedText: $0)
|
||||
}
|
||||
|
||||
public var keyLabelFont = NSFont.monospacedDigitSystemFont(
|
||||
ofSize: 14, weight: .medium
|
||||
)
|
||||
public var candidateFont = NSFont.systemFont(ofSize: 18)
|
||||
public var tooltip: String = ""
|
||||
|
||||
public func reloadData() {}
|
||||
|
||||
@discardableResult public func showNextPage() -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
@discardableResult public func showPreviousPage() -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
@discardableResult public func highlightNextCandidate() -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
@discardableResult public func highlightPreviousCandidate() -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
public func candidateIndexAtKeyLabelIndex(_: Int) -> Int {
|
||||
Int.max
|
||||
}
|
||||
|
||||
/// Sets the location of the candidate window.
|
||||
///
|
||||
/// Please note that the method has side effects that modifies
|
||||
/// `windowTopLeftPoint` to make the candidate window to stay in at least
|
||||
/// in a screen.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - windowTopLeftPoint: The given location.
|
||||
/// - height: The height that helps the window not to be out of the bottom
|
||||
/// of a screen.
|
||||
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) {
|
||||
DispatchQueue.main.async {
|
||||
self.doSet(
|
||||
windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight heightDelta: CGFloat) {
|
||||
guard let window = window else { return }
|
||||
let windowSize = window.frame.size
|
||||
|
||||
var adjustedPoint = windowTopLeftPoint
|
||||
var delta = heightDelta
|
||||
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.seniorTheBeast
|
||||
for frame in NSScreen.screens.map(\.visibleFrame).filter({ $0.contains(windowTopLeftPoint) }) {
|
||||
screenFrame = frame
|
||||
break
|
||||
}
|
||||
|
||||
if delta > screenFrame.size.height / 2.0 { delta = 0.0 }
|
||||
|
||||
if adjustedPoint.y < screenFrame.minY + windowSize.height {
|
||||
adjustedPoint.y = windowTopLeftPoint.y + windowSize.height + delta
|
||||
}
|
||||
adjustedPoint.y = min(adjustedPoint.y, screenFrame.maxY - 1.0)
|
||||
adjustedPoint.x = min(max(adjustedPoint.x, screenFrame.minX), screenFrame.maxX - windowSize.width - 1.0)
|
||||
|
||||
window.setFrameTopLeftPoint(adjustedPoint)
|
||||
}
|
||||
}
|
|
@ -1,226 +0,0 @@
|
|||
// (c) 2021 and onwards Weizhong Yang (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.
|
||||
|
||||
private protocol NotifierWindowDelegate: AnyObject {
|
||||
func windowDidBecomeClicked(_ window: NotifierWindow)
|
||||
}
|
||||
|
||||
private class NotifierWindow: NSWindow {
|
||||
weak var clickDelegate: NotifierWindowDelegate?
|
||||
|
||||
override func mouseDown(with _: NSEvent) {
|
||||
clickDelegate?.windowDidBecomeClicked(self)
|
||||
}
|
||||
}
|
||||
|
||||
private let kWindowWidth: CGFloat = 213.0
|
||||
private let kWindowHeight: CGFloat = 60.0
|
||||
|
||||
public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
||||
static var message: String = "" {
|
||||
didSet {
|
||||
if !Self.message.isEmpty {
|
||||
NotifierController.initiateWithNoStay(message: message)
|
||||
Self.message = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var messageTextField: NSTextField
|
||||
|
||||
private var message: String = "" {
|
||||
didSet {
|
||||
let paraStyle = NSMutableParagraphStyle()
|
||||
paraStyle.setParagraphStyle(NSParagraphStyle.default)
|
||||
paraStyle.alignment = .center
|
||||
let attr: [NSAttributedString.Key: AnyObject] = [
|
||||
.foregroundColor: foregroundColor,
|
||||
.font: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)),
|
||||
.paragraphStyle: paraStyle,
|
||||
]
|
||||
let attrString = NSAttributedString(string: message, attributes: attr)
|
||||
messageTextField.attributedStringValue = attrString
|
||||
let width = window?.frame.width ?? kWindowWidth
|
||||
let rect = attrString.boundingRect(
|
||||
with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin
|
||||
)
|
||||
let height = rect.height
|
||||
let x = messageTextField.frame.origin.x
|
||||
let y = ((window?.frame.height ?? kWindowHeight) - height) / 2
|
||||
let newFrame = NSRect(x: x, y: y, width: width, height: height)
|
||||
messageTextField.frame = newFrame
|
||||
}
|
||||
}
|
||||
|
||||
private var shouldStay = false
|
||||
private var backgroundColor: NSColor = .textBackgroundColor {
|
||||
didSet {
|
||||
window?.backgroundColor = backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
private var foregroundColor: NSColor = .controlTextColor {
|
||||
didSet {
|
||||
messageTextField.textColor = foregroundColor
|
||||
}
|
||||
}
|
||||
|
||||
private var waitTimer: Timer?
|
||||
private var fadeTimer: Timer?
|
||||
|
||||
private static var instanceCount = 0
|
||||
private static var lastLocation = NSPoint.zero
|
||||
|
||||
private static func initiateWithNoStay(message: String) {
|
||||
let controller = NotifierController()
|
||||
controller.message = message
|
||||
controller.show()
|
||||
}
|
||||
|
||||
public static func notify(message: String) {
|
||||
Self.message = message
|
||||
}
|
||||
|
||||
public static func notify(message: String, stay: Bool) {
|
||||
let controller = NotifierController()
|
||||
controller.message = message
|
||||
controller.shouldStay = stay
|
||||
controller.show()
|
||||
}
|
||||
|
||||
private static func increaseInstanceCount() {
|
||||
instanceCount += 1
|
||||
}
|
||||
|
||||
private static func decreaseInstanceCount() {
|
||||
instanceCount -= 1
|
||||
if instanceCount < 0 {
|
||||
instanceCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
private init() {
|
||||
let screenRect = NSScreen.main?.visibleFrame ?? NSRect.seniorTheBeast
|
||||
let contentRect = NSRect(x: 0, y: 0, width: kWindowWidth, height: kWindowHeight)
|
||||
var windowRect = contentRect
|
||||
windowRect.origin.x = screenRect.maxX - windowRect.width - 10
|
||||
windowRect.origin.y = screenRect.maxY - windowRect.height - 10
|
||||
let styleMask: NSWindow.StyleMask = [.fullSizeContentView, .titled]
|
||||
|
||||
let transparentVisualEffect = NSVisualEffectView()
|
||||
transparentVisualEffect.blendingMode = .behindWindow
|
||||
transparentVisualEffect.state = .active
|
||||
|
||||
let panel = NotifierWindow(
|
||||
contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false
|
||||
)
|
||||
panel.contentView = transparentVisualEffect
|
||||
panel.isMovableByWindowBackground = true
|
||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel))
|
||||
panel.hasShadow = true
|
||||
panel.backgroundColor = backgroundColor
|
||||
panel.title = ""
|
||||
panel.titlebarAppearsTransparent = true
|
||||
panel.titleVisibility = .hidden
|
||||
panel.showsToolbarButton = false
|
||||
panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true
|
||||
panel.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isHidden = true
|
||||
panel.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true
|
||||
panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true
|
||||
|
||||
messageTextField = NSTextField()
|
||||
messageTextField.frame = contentRect
|
||||
messageTextField.isEditable = false
|
||||
messageTextField.isSelectable = false
|
||||
messageTextField.isBezeled = false
|
||||
messageTextField.textColor = foregroundColor
|
||||
messageTextField.drawsBackground = false
|
||||
messageTextField.font = .boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular))
|
||||
panel.contentView?.addSubview(messageTextField)
|
||||
|
||||
super.init(window: panel)
|
||||
|
||||
panel.clickDelegate = self
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func show() {
|
||||
func setStartLocation() {
|
||||
if Self.instanceCount == 0 {
|
||||
return
|
||||
}
|
||||
let lastLocation = Self.lastLocation
|
||||
let screenRect = NSScreen.main?.visibleFrame ?? NSRect.seniorTheBeast
|
||||
var windowRect = window?.frame ?? NSRect.seniorTheBeast
|
||||
windowRect.origin.x = lastLocation.x
|
||||
windowRect.origin.y = lastLocation.y - 10 - windowRect.height
|
||||
|
||||
if windowRect.origin.y < screenRect.minY {
|
||||
return
|
||||
}
|
||||
|
||||
window?.setFrame(windowRect, display: true)
|
||||
}
|
||||
|
||||
func moveIn() {
|
||||
let afterRect = window?.frame ?? NSRect.seniorTheBeast
|
||||
Self.lastLocation = afterRect.origin
|
||||
var beforeRect = afterRect
|
||||
beforeRect.origin.y += 10
|
||||
window?.setFrame(beforeRect, display: true)
|
||||
window?.orderFront(self)
|
||||
window?.setFrame(afterRect, display: true, animate: true)
|
||||
}
|
||||
|
||||
setStartLocation()
|
||||
moveIn()
|
||||
Self.increaseInstanceCount()
|
||||
waitTimer = Timer.scheduledTimer(
|
||||
timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut),
|
||||
userInfo: nil,
|
||||
repeats: false
|
||||
)
|
||||
}
|
||||
|
||||
@objc private func doFadeOut(_: Timer) {
|
||||
let opacity = window?.alphaValue ?? 0
|
||||
if opacity <= 0 {
|
||||
close()
|
||||
} else {
|
||||
window?.alphaValue = opacity - 0.2
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func fadeOut() {
|
||||
waitTimer?.invalidate()
|
||||
waitTimer = nil
|
||||
Self.decreaseInstanceCount()
|
||||
fadeTimer = Timer.scheduledTimer(
|
||||
timeInterval: 0.01, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil,
|
||||
repeats: true
|
||||
)
|
||||
}
|
||||
|
||||
override public func close() {
|
||||
waitTimer?.invalidate()
|
||||
waitTimer = nil
|
||||
fadeTimer?.invalidate()
|
||||
fadeTimer = nil
|
||||
super.close()
|
||||
}
|
||||
|
||||
fileprivate func windowDidBecomeClicked(_: NotifierWindow) {
|
||||
fadeOut()
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ struct suiPrefPaneDevZone: View {
|
|||
@State private var selShiftKeyAccommodationBehavior: Int = UserDefaults.standard.integer(
|
||||
forKey: UserDef.kShiftKeyAccommodationBehavior.rawValue)
|
||||
|
||||
private let contentMaxHeight: Double = 432
|
||||
private let contentMaxHeight: Double = 440
|
||||
private let contentWidth: Double = {
|
||||
switch PrefMgr.shared.appleLanguages[0] {
|
||||
case "ja":
|
||||
|
|
|
@ -37,7 +37,7 @@ struct suiPrefPaneDictionary: View {
|
|||
|
||||
private static let dlgOpenPath = NSOpenPanel()
|
||||
|
||||
private let contentMaxHeight: Double = 432
|
||||
private let contentMaxHeight: Double = 440
|
||||
private let contentWidth: Double = {
|
||||
switch PrefMgr.shared.appleLanguages[0] {
|
||||
case "ja":
|
||||
|
|
|
@ -48,7 +48,7 @@ struct suiPrefPaneExperience: View {
|
|||
@State private var selAlwaysShowTooltipTextsHorizontally = UserDefaults.standard.bool(
|
||||
forKey: UserDef.kAlwaysShowTooltipTextsHorizontally.rawValue)
|
||||
|
||||
private let contentMaxHeight: Double = 432
|
||||
private let contentMaxHeight: Double = 440
|
||||
private let contentWidth: Double = {
|
||||
switch PrefMgr.shared.appleLanguages[0] {
|
||||
case "ja":
|
||||
|
|
|
@ -39,7 +39,7 @@ struct suiPrefPaneGeneral: View {
|
|||
forKey: UserDef.kCheckUpdateAutomatically.rawValue)
|
||||
@State private var selEnableDebugMode = UserDefaults.standard.bool(forKey: UserDef.kIsDebugModeEnabled.rawValue)
|
||||
|
||||
private let contentMaxHeight: Double = 432
|
||||
private let contentMaxHeight: Double = 440
|
||||
private let contentWidth: Double = {
|
||||
switch PrefMgr.shared.appleLanguages[0] {
|
||||
case "ja":
|
||||
|
@ -60,7 +60,7 @@ struct suiPrefPaneGeneral: View {
|
|||
Picker(
|
||||
"",
|
||||
selection: $selCandidateUIFontSize.onChange {
|
||||
PrefMgr.shared.candidateListTextSize = CGFloat(selCandidateUIFontSize)
|
||||
PrefMgr.shared.candidateListTextSize = Double(selCandidateUIFontSize)
|
||||
}
|
||||
) {
|
||||
Group {
|
||||
|
|
|
@ -34,7 +34,7 @@ struct suiPrefPaneKeyboard: View {
|
|||
@State private var selUsingHotKeyCurrencyNumerals = UserDefaults.standard.bool(
|
||||
forKey: UserDef.kUsingHotKeyCurrencyNumerals.rawValue)
|
||||
|
||||
private let contentMaxHeight: Double = 432
|
||||
private let contentMaxHeight: Double = 440
|
||||
private let contentWidth: Double = {
|
||||
switch PrefMgr.shared.appleLanguages[0] {
|
||||
case "ja":
|
||||
|
|
|
@ -53,7 +53,7 @@ extension ctlClientListMgr {
|
|||
alert.addButton(withTitle: NSLocalizedString("Just Select", comment: "") + "…")
|
||||
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
|
||||
|
||||
let maxFloat = CGFloat(Float.greatestFiniteMagnitude)
|
||||
let maxFloat = Double(Float.greatestFiniteMagnitude)
|
||||
let scrollview = NSScrollView(frame: NSRect(x: 0, y: 0, width: 370, height: 380))
|
||||
let contentSize = scrollview.contentSize
|
||||
scrollview.borderType = .noBorder
|
||||
|
|
|
@ -12,7 +12,7 @@ import BookmarkManager
|
|||
import IMKUtils
|
||||
import Shared
|
||||
|
||||
private let kWindowTitleHeight: CGFloat = 78
|
||||
private let kWindowTitleHeight: Double = 78
|
||||
|
||||
extension NSToolbarItem.Identifier {
|
||||
fileprivate static let ofGeneral = NSToolbarItem.Identifier(rawValue: "tabGeneral")
|
||||
|
|
|
@ -1,76 +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.
|
||||
|
||||
import InputMethodKit
|
||||
import Shared
|
||||
|
||||
extension ctlInputMethod {
|
||||
/// 完成 handle() 函式本該完成的內容,但去掉了與 IMK 選字窗有關的判斷語句。
|
||||
/// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。
|
||||
/// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。
|
||||
/// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。
|
||||
func commonEventHandler(_ event: NSEvent) -> Bool {
|
||||
// 無法列印的訊號輸入,一概不作處理。
|
||||
// 這個過程不能放在 KeyHandler 內,否則不會起作用。
|
||||
if !event.charCode.isPrintable { return false }
|
||||
|
||||
/// 將按鍵行為與當前輸入法狀態結合起來、交給按鍵調度模組來處理。
|
||||
/// 再根據返回的 result bool 數值來告知 IMK「這個按鍵事件是被處理了還是被放行了」。
|
||||
/// 這裡不用 keyHandler.handleCandidate() 是因為需要針對聯想詞輸入狀態做額外處理。
|
||||
let result = keyHandler.handle(input: event, state: state) { newState in
|
||||
self.handle(state: newState)
|
||||
} errorCallback: { errorString in
|
||||
vCLog(errorString)
|
||||
IMEApp.buzz()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/// 完成 handle() 函式本該完成的內容,但專門處理與 IMK 選字窗有關的判斷語句。
|
||||
/// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。
|
||||
/// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。
|
||||
/// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。
|
||||
func imkCandidatesEventHandler(event eventToDeal: NSEvent) -> Bool? {
|
||||
// IMK 選字窗處理,當且僅當啟用了 IMK 選字窗的時候才會生效。
|
||||
// 這樣可以讓 interpretKeyEvents() 函式自行判斷:
|
||||
// - 是就地交給 super.interpretKeyEvents() 處理?
|
||||
// - 還是藉由 delegate 扔回 ctlInputMethod 給 KeyHandler 處理?
|
||||
if let imkCandidates = ctlInputMethod.ctlCandidateCurrent as? ctlCandidateIMK, imkCandidates.visible {
|
||||
let event: NSEvent = ctlCandidateIMK.replaceNumPadKeyCodes(target: eventToDeal) ?? eventToDeal
|
||||
|
||||
// Shift+Enter 是個特殊情形,不提前攔截處理的話、會有垃圾參數傳給 delegate 的 keyHandler 從而崩潰。
|
||||
// 所以這裡直接將 Shift Flags 清空。
|
||||
if event.isShiftHold, event.isEnter {
|
||||
guard let newEvent = event.reinitiate(modifierFlags: []) else {
|
||||
NSSound.beep()
|
||||
return true
|
||||
}
|
||||
imkCandidates.interpretKeyEvents([newEvent])
|
||||
return true
|
||||
}
|
||||
|
||||
// 聯想詞選字。
|
||||
if let newChar = ctlCandidateIMK.defaultIMKSelectionKey[event.keyCode],
|
||||
event.isShiftHold, isAssociatedPhrasesState,
|
||||
let newEvent = event.reinitiate(modifierFlags: [], characters: newChar)
|
||||
{
|
||||
if #available(macOS 10.14, *) {
|
||||
imkCandidates.handleKeyboardEvent(newEvent)
|
||||
} else {
|
||||
imkCandidates.superInterpretKeyEvents([newEvent])
|
||||
}
|
||||
}
|
||||
|
||||
imkCandidates.interpretKeyEvents([event])
|
||||
return true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@
|
|||
"Force KangXi Writing" = "Force KangXi Writing";
|
||||
"NotificationSwitchON" = "✔ ON";
|
||||
"NotificationSwitchOFF" = "✘ OFF";
|
||||
"NotificationSwitchShift" = "↺ Switched";
|
||||
"Edit User Phrases…" = "Edit User Phrases…";
|
||||
"Reload User Phrases" = "Reload User Phrases";
|
||||
"Unable to create the user phrase file." = "Unable to create the user phrase file.";
|
||||
|
|
|
@ -92,13 +92,13 @@
|
|||
<key>InputMethodConnectionName</key>
|
||||
<string>org_atelierInmu_inputmethod_vChewing_Connection</string>
|
||||
<key>InputMethodServerControllerClass</key>
|
||||
<string>ctlInputMethod</string>
|
||||
<string>SessionCtl</string>
|
||||
<key>InputMethodServerDelegateClass</key>
|
||||
<string>ctlInputMethod</string>
|
||||
<string>SessionCtl</string>
|
||||
<key>InputMethodServerPreferencesWindowControllerClass</key>
|
||||
<string>ctlPrefWindow</string>
|
||||
<key>InputMethodSessionController</key>
|
||||
<string>ctlInputMethod</string>
|
||||
<string>SessionCtl</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
<key>LSHasLocalizedDisplayName</key>
|
||||
|
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 2.7 KiB |
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon_1024.png",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_16.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_64.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_128.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_256.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_256.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_512.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_512.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "icon_1024.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 957 B |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 809 B |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 402 KiB |
|
@ -1,31 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,0.176229,0.510845)">
|
||||
<g id="v" transform="matrix(-0.0111111,-1.36072e-18,1.36072e-18,-0.0111111,13.8235,12.9891)">
|
||||
<g id="Shadow" transform="matrix(-1.02102,-1.25039e-16,1.28682e-16,-1.05077,1169.98,1160.72)">
|
||||
<path d="M1322.21,34L452.612,34C202.809,34 0,231.067 0,473.799L0,1318.78L1322.21,1318.78L1322.21,34Z" style="fill-opacity:0.63;"/>
|
||||
<g id="CHS">
|
||||
<path d="M16,3.774C16,1.691 14.309,0 12.226,0L3.774,0C1.691,0 0,1.691 0,3.774L0,12.226C0,14.309 1.691,16 3.774,16L12.226,16C14.309,16 16,14.309 16,12.226L16,3.774Z" style="fill:white;"/>
|
||||
<path d="M16,8.465L6.413,8.465L5,16L12.226,16C14.309,16 16,14.309 16,12.226L16,8.465Z" style="fill:rgb(64,134,191);"/>
|
||||
<path d="M16,3.774C16,1.691 14.309,0 12.226,0L8,0L6.413,8.465L16,8.465L16,3.774Z" style="fill:rgb(83,191,64);"/>
|
||||
<path d="M0,8.465L6.413,8.465L8,0L3.774,0C1.691,0 0,1.691 0,3.774L0,8.465Z" style="fill:rgb(255,200,0);"/>
|
||||
<path d="M5,16L6.413,8.465L0,8.465L0,12.226C0,14.309 1.691,16 3.774,16L5,16Z" style="fill:rgb(255,81,0);"/>
|
||||
</g>
|
||||
<g transform="matrix(-1.01166,-1.23893e-16,1.27502e-16,-1.04113,1259.98,1250.4)">
|
||||
<g id="Outline">
|
||||
<path d="M1334.45,34L452.612,34C202.809,34 0,231.067 0,473.799L0,1330.67L1334.45,1330.67L1334.45,34Z" style="fill:rgb(11,46,99);"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Core" transform="matrix(-0.861782,-1.05538e-16,1.08613e-16,-0.886889,1169.98,1155.15)">
|
||||
<path d="M1357.66,34L452.612,34C202.809,34 0,231.067 0,473.799L0,1353.23L1357.66,1353.23L1357.66,34Z" style="fill:url(#_Radial1);"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="TextElement" transform="matrix(0.0115939,0,0,0.0115939,0.253417,-0.559324)">
|
||||
<g id="TextShadow" transform="matrix(76.3556,0,0,67.9081,-21039.4,-60964.1)">
|
||||
<path d="M279.547,902.926C280.327,903.628 280.535,903.602 281.653,903.29C282.849,902.952 285.241,902.718 286.905,902.666C287.763,902.614 287.841,902.718 287.581,903.082C286.723,904.304 285.033,906.124 284.149,906.462C283.109,906.826 283.057,906.878 282.979,907.71C282.875,908.828 283.629,909.01 284.383,908.698C285.683,908.126 288.543,907.918 289.635,908.126C290.233,908.23 290.285,908.49 290.129,909.036C289.531,911.22 288.283,913.508 287.139,914.99C286.411,915.926 286.359,915.9 285.475,915.406C284.981,915.146 284.045,914.574 283.551,914.366C282.667,913.95 282.251,914.288 283.213,915.458C283.785,916.134 284.981,917.304 285.397,917.928C286.281,919.254 286.385,919.28 287.633,918.058C290.675,915.094 292.079,909.972 292.573,909.53C293.223,908.958 293.405,908.776 293.041,908.152C292.703,907.58 291.663,906.93 291.195,906.618C290.857,906.384 290.441,906.046 289.661,906.306C288.881,906.54 288.413,906.254 286.931,906.462C288.023,905.266 289.895,903.472 290.571,903.186C291.611,902.718 291.897,902.432 291.481,902.042C290.935,901.496 290.571,900.95 290.103,900.612C289.583,900.196 289.167,900.456 288.179,900.742C287.347,900.976 281.393,901.678 280.405,901.496C278.065,901.08 277.987,901.47 279.547,902.926Z" style="fill-opacity:0.8;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g id="Text" transform="matrix(64.7661,0,0,60.0776,-17811.9,-53913.5)">
|
||||
<path d="M279.547,902.926C280.327,903.628 280.535,903.602 281.653,903.29C282.849,902.952 285.241,902.718 286.905,902.666C287.763,902.614 287.841,902.718 287.581,903.082C286.723,904.304 285.033,906.124 284.149,906.462C283.109,906.826 283.057,906.878 282.979,907.71C282.875,908.828 283.629,909.01 284.383,908.698C285.683,908.126 288.543,907.918 289.635,908.126C290.233,908.23 290.285,908.49 290.129,909.036C289.531,911.22 288.283,913.508 287.139,914.99C286.411,915.926 286.359,915.9 285.475,915.406C284.981,915.146 284.045,914.574 283.551,914.366C282.667,913.95 282.251,914.288 283.213,915.458C283.785,916.134 284.981,917.304 285.397,917.928C286.281,919.254 286.385,919.28 287.633,918.058C290.675,915.094 292.079,909.972 292.573,909.53C293.223,908.958 293.405,908.776 293.041,908.152C292.703,907.58 291.663,906.93 291.195,906.618C290.857,906.384 290.441,906.046 289.661,906.306C288.881,906.54 288.413,906.254 286.931,906.462C288.023,905.266 289.895,903.472 290.571,903.186C291.611,902.718 291.897,902.432 291.481,902.042C290.935,901.496 290.571,900.95 290.103,900.612C289.583,900.196 289.167,900.456 288.179,900.742C287.347,900.976 281.393,901.678 280.405,901.496C278.065,901.08 277.987,901.47 279.547,902.926Z" style="fill:rgb(16,42,103);fill-rule:nonzero;"/>
|
||||
<g transform="matrix(0.750892,0,0,0.696534,-206.162,-625.152)">
|
||||
<g id="TextElement">
|
||||
<g id="Text">
|
||||
<path d="M279.547,902.926C280.327,903.628 280.535,903.602 281.653,903.29C282.849,902.952 285.241,902.718 286.905,902.666C287.763,902.614 287.841,902.718 287.581,903.082C286.723,904.304 285.033,906.124 284.149,906.462C283.109,906.826 283.057,906.878 282.979,907.71C282.875,908.828 283.629,909.01 284.383,908.698C285.683,908.126 288.543,907.918 289.635,908.126C290.233,908.23 290.285,908.49 290.129,909.036C289.531,911.22 288.283,913.508 287.139,914.99C286.411,915.926 286.359,915.9 285.475,915.406C284.981,915.146 284.045,914.574 283.551,914.366C282.667,913.95 282.251,914.288 283.213,915.458C283.785,916.134 284.981,917.304 285.397,917.928C286.281,919.254 286.385,919.28 287.633,918.058C290.675,915.094 292.079,909.972 292.573,909.53C293.223,908.958 293.405,908.776 293.041,908.152C292.703,907.58 291.663,906.93 291.195,906.618C290.857,906.384 290.441,906.046 289.661,906.306C288.881,906.54 288.413,906.254 286.931,906.462C288.023,905.266 289.895,903.472 290.571,903.186C291.611,902.718 291.897,902.432 291.481,902.042C290.935,901.496 290.571,900.95 290.103,900.612C289.583,900.196 289.167,900.456 288.179,900.742C287.347,900.976 281.393,901.678 280.405,901.496C278.065,901.08 277.987,901.47 279.547,902.926Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M284.778,904.241C284.724,904.294 284.67,904.346 284.617,904.396C284.276,904.716 283.97,904.996 283.728,905.099C282.618,905.49 282.228,905.851 281.992,906.261C281.825,906.553 281.713,906.932 281.654,907.566C281.654,907.566 281.654,907.566 281.654,907.567C281.556,908.615 281.894,909.276 282.311,909.689C282.896,910.267 283.87,910.448 284.86,910.038C284.869,910.035 284.878,910.031 284.886,910.027C285.757,909.644 287.442,909.454 288.588,909.474C287.986,911.198 287.016,912.905 286.117,914.07L286.074,914.124L286.059,914.116C285.548,913.847 284.584,913.265 284.059,913.039C283.4,912.733 282.843,912.726 282.507,912.818C282.043,912.945 281.729,913.235 281.532,913.608C281.373,913.91 281.272,914.304 281.364,914.809C281.428,915.161 281.649,915.72 282.216,916.41C282.221,916.415 282.225,916.42 282.229,916.426C282.773,917.069 283.92,918.172 284.316,918.766C284.662,919.286 284.931,919.641 285.162,919.857C285.543,920.213 285.918,920.356 286.296,920.381C286.638,920.404 287.015,920.329 287.445,920.055C287.718,919.881 288.063,919.577 288.527,919.122C291.374,916.347 292.893,911.795 293.545,910.525C294.21,909.912 294.469,909.418 294.535,908.97C294.599,908.546 294.551,908.044 294.168,907.388C294.167,907.385 294.166,907.383 294.164,907.381C293.743,906.668 292.496,905.798 291.905,905.404C291.562,905.166 291.169,904.89 290.628,904.796C290.799,904.665 290.946,904.57 291.058,904.522C291.066,904.519 291.075,904.515 291.084,904.511C291.842,904.17 292.303,903.842 292.532,903.581C292.868,903.197 292.985,902.795 292.996,902.416C293.008,901.978 292.881,901.462 292.375,900.978C291.787,900.388 291.376,899.813 290.872,899.44C290.291,898.984 289.768,898.886 289.113,898.995C288.786,899.049 288.391,899.194 287.839,899.354C287.054,899.572 281.545,900.25 280.629,900.081C280.627,900.08 280.624,900.08 280.622,900.079C279.567,899.892 278.866,899.878 278.491,899.959C277.855,900.096 277.527,900.466 277.349,900.813C277.141,901.216 277.043,901.752 277.342,902.409C277.502,902.763 277.932,903.318 278.675,904.011C278.681,904.017 278.687,904.023 278.694,904.028C279.185,904.47 279.55,904.697 279.903,904.816C280.419,904.992 280.955,904.968 281.987,904.68C281.988,904.679 281.989,904.679 281.991,904.679C282.653,904.492 283.705,904.342 284.778,904.241ZM279.547,902.926C280.327,903.628 280.535,903.602 281.653,903.29C282.849,902.952 285.241,902.718 286.905,902.666C287.763,902.614 287.841,902.718 287.581,903.082C286.723,904.304 285.033,906.124 284.149,906.462C283.109,906.826 283.057,906.878 282.979,907.71C282.875,908.828 283.629,909.01 284.383,908.698C285.683,908.126 288.543,907.918 289.635,908.126C290.233,908.23 290.285,908.49 290.129,909.036C289.531,911.22 288.283,913.508 287.139,914.99C286.411,915.926 286.359,915.9 285.475,915.406C284.981,915.146 284.045,914.574 283.551,914.366C282.667,913.95 282.251,914.288 283.213,915.458C283.785,916.134 284.981,917.304 285.397,917.928C286.281,919.254 286.385,919.28 287.633,918.058C290.675,915.094 292.079,909.972 292.573,909.53C293.223,908.958 293.405,908.776 293.041,908.152C292.703,907.58 291.663,906.93 291.195,906.618C290.857,906.384 290.441,906.046 289.661,906.306C288.881,906.54 288.413,906.254 286.931,906.462C288.023,905.266 289.895,903.472 290.571,903.186C291.611,902.718 291.897,902.432 291.481,902.042C290.935,901.496 290.571,900.95 290.103,900.612C289.583,900.196 289.167,900.456 288.179,900.742C287.347,900.976 281.393,901.678 280.405,901.496C278.065,901.08 277.987,901.47 279.547,902.926Z" style="fill:white;"/>
|
||||
<path d="M279.547,902.926C280.327,903.628 280.535,903.602 281.653,903.29C282.849,902.952 285.241,902.718 286.905,902.666C287.763,902.614 287.841,902.718 287.581,903.082C286.723,904.304 285.033,906.124 284.149,906.462C283.109,906.826 283.057,906.878 282.979,907.71C282.875,908.828 283.629,909.01 284.383,908.698C285.683,908.126 288.543,907.918 289.635,908.126C290.233,908.23 290.285,908.49 290.129,909.036C289.531,911.22 288.283,913.508 287.139,914.99C286.411,915.926 286.359,915.9 285.475,915.406C284.981,915.146 284.045,914.574 283.551,914.366C282.667,913.95 282.251,914.288 283.213,915.458C283.785,916.134 284.981,917.304 285.397,917.928C286.281,919.254 286.385,919.28 287.633,918.058C290.675,915.094 292.079,909.972 292.573,909.53C293.223,908.958 293.405,908.776 293.041,908.152C292.703,907.58 291.663,906.93 291.195,906.618C290.857,906.384 290.441,906.046 289.661,906.306C288.881,906.54 288.413,906.254 286.931,906.462C288.023,905.266 289.895,903.472 290.571,903.186C291.611,902.718 291.897,902.432 291.481,902.042C290.935,901.496 290.571,900.95 290.103,900.612C289.583,900.196 289.167,900.456 288.179,900.742C287.347,900.976 281.393,901.678 280.405,901.496C278.065,901.08 277.987,901.47 279.547,902.926Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M287.346,902.94C286.511,904.121 284.91,905.864 284.062,906.19C283.738,906.304 283.507,906.389 283.338,906.475C283.119,906.588 282.988,906.709 282.898,906.865C282.795,907.046 282.751,907.288 282.714,907.681C282.714,907.681 282.714,907.681 282.714,907.681C282.661,908.249 282.805,908.619 283.031,908.842C283.362,909.169 283.919,909.198 284.478,908.966C284.48,908.965 284.482,908.965 284.484,908.964C285.747,908.408 288.527,908.207 289.589,908.409C289.59,908.409 289.591,908.409 289.593,908.409C289.689,908.426 289.768,908.447 289.831,908.474C289.876,908.494 289.911,908.511 289.927,908.544C289.941,908.575 289.939,908.612 289.936,908.655C289.93,908.74 289.907,908.838 289.874,908.951C289.874,908.953 289.874,908.954 289.874,908.955C289.286,911.101 288.059,913.349 286.935,914.806C286.762,915.028 286.63,915.194 286.515,915.309C286.438,915.386 286.378,915.438 286.311,915.453C286.247,915.468 286.183,915.446 286.1,915.412C285.967,915.358 285.807,915.268 285.598,915.151C285.596,915.15 285.594,915.149 285.592,915.148C285.094,914.886 284.153,914.312 283.653,914.101C283.338,913.953 283.072,913.896 282.886,913.911C282.675,913.929 282.535,914.031 282.458,914.175C282.392,914.301 282.367,914.483 282.443,914.721C282.515,914.947 282.693,915.258 283.014,915.648C283.015,915.649 283.015,915.65 283.016,915.652C283.583,916.321 284.769,917.478 285.181,918.096C285.462,918.518 285.672,918.813 285.859,918.989C286.03,919.148 286.193,919.224 286.362,919.235C286.524,919.246 286.699,919.196 286.902,919.067C287.133,918.919 287.419,918.655 287.812,918.271C289.971,916.167 291.321,912.995 292.106,911.125C292.302,910.658 292.462,910.274 292.59,910.014C292.654,909.884 292.699,909.79 292.743,909.751C292.994,909.53 293.178,909.361 293.296,909.207C293.402,909.066 293.462,908.93 293.484,908.788C293.516,908.574 293.459,908.33 293.266,907.999C293.266,907.999 293.266,907.998 293.266,907.998C292.911,907.398 291.83,906.703 291.337,906.375C290.952,906.108 290.475,905.736 289.588,906.03C289.228,906.138 288.939,906.122 288.569,906.107C288.312,906.096 288.018,906.085 287.647,906.104C288.707,905 290.107,903.691 290.668,903.453C290.67,903.453 290.672,903.452 290.674,903.451C291.118,903.251 291.432,903.079 291.623,902.921C291.84,902.741 291.926,902.554 291.931,902.381C291.935,902.209 291.861,902.02 291.661,901.83C291.106,901.275 290.732,900.723 290.257,900.377C289.933,900.12 289.641,900.069 289.276,900.13C288.976,900.18 288.617,900.318 288.111,900.464C287.288,900.695 281.423,901.392 280.45,901.213C280.449,901.213 280.449,901.213 280.448,901.213C279.582,901.059 279.009,901.018 278.7,901.085C278.473,901.134 278.344,901.246 278.281,901.37C278.211,901.505 278.198,901.684 278.299,901.904C278.425,902.182 278.787,902.597 279.373,903.143C279.374,903.144 279.375,903.145 279.376,903.146C279.724,903.459 279.974,903.636 280.223,903.721C280.595,903.848 280.977,903.775 281.72,903.568C281.72,903.568 281.72,903.568 281.721,903.568C282.903,903.234 285.268,903.004 286.913,902.953L287.346,902.94ZM279.547,902.926C280.327,903.628 280.535,903.602 281.653,903.29C282.849,902.952 285.241,902.718 286.905,902.666C287.763,902.614 287.841,902.718 287.581,903.082C286.723,904.304 285.033,906.124 284.149,906.462C283.109,906.826 283.057,906.878 282.979,907.71C282.875,908.828 283.629,909.01 284.383,908.698C285.683,908.126 288.543,907.918 289.635,908.126C290.233,908.23 290.285,908.49 290.129,909.036C289.531,911.22 288.283,913.508 287.139,914.99C286.411,915.926 286.359,915.9 285.475,915.406C284.981,915.146 284.045,914.574 283.551,914.366C282.667,913.95 282.251,914.288 283.213,915.458C283.785,916.134 284.981,917.304 285.397,917.928C286.281,919.254 286.385,919.28 287.633,918.058C290.675,915.094 292.079,909.972 292.573,909.53C293.223,908.958 293.405,908.776 293.041,908.152C292.703,907.58 291.663,906.93 291.195,906.618C290.857,906.384 290.441,906.046 289.661,906.306C288.881,906.54 288.413,906.254 286.931,906.462C288.023,905.266 289.895,903.472 290.571,903.186C291.611,902.718 291.897,902.432 291.481,902.042C290.935,901.496 290.571,900.95 290.103,900.612C289.583,900.196 289.167,900.456 288.179,900.742C287.347,900.976 281.393,901.678 280.405,901.496C278.065,901.08 277.987,901.47 279.547,902.926Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<radialGradient id="_Radial1" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1087.57,-3794.91,3905.47,-1056.78,1725.88,3633.33)"><stop offset="0" style="stop-color:rgb(218,240,255);stop-opacity:1"/><stop offset="0.28" style="stop-color:rgb(200,225,246);stop-opacity:1"/><stop offset="0.51" style="stop-color:rgb(193,219,243);stop-opacity:1"/><stop offset="0.73" style="stop-color:rgb(127,167,220);stop-opacity:1"/><stop offset="0.87" style="stop-color:rgb(14,87,170);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(9,53,104);stop-opacity:1"/></radialGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="CHT">
|
||||
<path d="M16,3.774C16,1.691 14.309,0 12.226,0L3.774,0C1.691,0 0,1.691 0,3.774L0,12.226C0,14.309 1.691,16 3.774,16L12.226,16C14.309,16 16,14.309 16,12.226L16,3.774Z" style="fill:white;"/>
|
||||
<path d="M16,8.465L6.413,8.465L5,16L12.226,16C14.309,16 16,14.309 16,12.226L16,8.465Z" style="fill:rgb(255,81,0);"/>
|
||||
<path d="M16,3.774C16,1.691 14.309,0 12.226,0L8,0L6.413,8.465L16,8.465L16,3.774Z" style="fill:rgb(255,200,0);"/>
|
||||
<path d="M0,8.465L6.413,8.465L8,0L3.774,0C1.691,0 0,1.691 0,3.774L0,8.465Z" style="fill:rgb(83,191,64);"/>
|
||||
<path d="M5,16L6.413,8.465L0,8.465L0,12.226C0,14.309 1.691,16 3.774,16L5,16Z" style="fill:rgb(64,134,191);"/>
|
||||
</g>
|
||||
<g transform="matrix(0.750892,0,0,0.696534,-206.162,-625.152)">
|
||||
<g id="TextElement">
|
||||
<g id="Text">
|
||||
<path d="M279.547,902.926C280.327,903.628 280.535,903.602 281.653,903.29C282.849,902.952 285.241,902.718 286.905,902.666C287.763,902.614 287.841,902.718 287.581,903.082C286.723,904.304 285.033,906.124 284.149,906.462C283.109,906.826 283.057,906.878 282.979,907.71C282.875,908.828 283.629,909.01 284.383,908.698C285.683,908.126 288.543,907.918 289.635,908.126C290.233,908.23 290.285,908.49 290.129,909.036C289.531,911.22 288.283,913.508 287.139,914.99C286.411,915.926 286.359,915.9 285.475,915.406C284.981,915.146 284.045,914.574 283.551,914.366C282.667,913.95 282.251,914.288 283.213,915.458C283.785,916.134 284.981,917.304 285.397,917.928C286.281,919.254 286.385,919.28 287.633,918.058C290.675,915.094 292.079,909.972 292.573,909.53C293.223,908.958 293.405,908.776 293.041,908.152C292.703,907.58 291.663,906.93 291.195,906.618C290.857,906.384 290.441,906.046 289.661,906.306C288.881,906.54 288.413,906.254 286.931,906.462C288.023,905.266 289.895,903.472 290.571,903.186C291.611,902.718 291.897,902.432 291.481,902.042C290.935,901.496 290.571,900.95 290.103,900.612C289.583,900.196 289.167,900.456 288.179,900.742C287.347,900.976 281.393,901.678 280.405,901.496C278.065,901.08 277.987,901.47 279.547,902.926Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M284.778,904.241C284.724,904.294 284.67,904.346 284.617,904.396C284.276,904.716 283.97,904.996 283.728,905.099C282.618,905.49 282.228,905.851 281.992,906.261C281.825,906.553 281.713,906.932 281.654,907.566C281.654,907.566 281.654,907.566 281.654,907.567C281.556,908.615 281.894,909.276 282.311,909.689C282.896,910.267 283.87,910.448 284.86,910.038C284.869,910.035 284.878,910.031 284.886,910.027C285.757,909.644 287.442,909.454 288.588,909.474C287.986,911.198 287.016,912.905 286.117,914.07L286.074,914.124L286.059,914.116C285.548,913.847 284.584,913.265 284.059,913.039C283.4,912.733 282.843,912.726 282.507,912.818C282.043,912.945 281.729,913.235 281.532,913.608C281.373,913.91 281.272,914.304 281.364,914.809C281.428,915.161 281.649,915.72 282.216,916.41C282.221,916.415 282.225,916.42 282.229,916.426C282.773,917.069 283.92,918.172 284.316,918.766C284.662,919.286 284.931,919.641 285.162,919.857C285.543,920.213 285.918,920.356 286.296,920.381C286.638,920.404 287.015,920.329 287.445,920.055C287.718,919.881 288.063,919.577 288.527,919.122C291.374,916.347 292.893,911.795 293.545,910.525C294.21,909.912 294.469,909.418 294.535,908.97C294.599,908.546 294.551,908.044 294.168,907.388C294.167,907.385 294.166,907.383 294.164,907.381C293.743,906.668 292.496,905.798 291.905,905.404C291.562,905.166 291.169,904.89 290.628,904.796C290.799,904.665 290.946,904.57 291.058,904.522C291.066,904.519 291.075,904.515 291.084,904.511C291.842,904.17 292.303,903.842 292.532,903.581C292.868,903.197 292.985,902.795 292.996,902.416C293.008,901.978 292.881,901.462 292.375,900.978C291.787,900.388 291.376,899.813 290.872,899.44C290.291,898.984 289.768,898.886 289.113,898.995C288.786,899.049 288.391,899.194 287.839,899.354C287.054,899.572 281.545,900.25 280.629,900.081C280.627,900.08 280.624,900.08 280.622,900.079C279.567,899.892 278.866,899.878 278.491,899.959C277.855,900.096 277.527,900.466 277.349,900.813C277.141,901.216 277.043,901.752 277.342,902.409C277.502,902.763 277.932,903.318 278.675,904.011C278.681,904.017 278.687,904.023 278.694,904.028C279.185,904.47 279.55,904.697 279.903,904.816C280.419,904.992 280.955,904.968 281.987,904.68C281.988,904.679 281.989,904.679 281.991,904.679C282.653,904.492 283.705,904.342 284.778,904.241ZM279.547,902.926C280.327,903.628 280.535,903.602 281.653,903.29C282.849,902.952 285.241,902.718 286.905,902.666C287.763,902.614 287.841,902.718 287.581,903.082C286.723,904.304 285.033,906.124 284.149,906.462C283.109,906.826 283.057,906.878 282.979,907.71C282.875,908.828 283.629,909.01 284.383,908.698C285.683,908.126 288.543,907.918 289.635,908.126C290.233,908.23 290.285,908.49 290.129,909.036C289.531,911.22 288.283,913.508 287.139,914.99C286.411,915.926 286.359,915.9 285.475,915.406C284.981,915.146 284.045,914.574 283.551,914.366C282.667,913.95 282.251,914.288 283.213,915.458C283.785,916.134 284.981,917.304 285.397,917.928C286.281,919.254 286.385,919.28 287.633,918.058C290.675,915.094 292.079,909.972 292.573,909.53C293.223,908.958 293.405,908.776 293.041,908.152C292.703,907.58 291.663,906.93 291.195,906.618C290.857,906.384 290.441,906.046 289.661,906.306C288.881,906.54 288.413,906.254 286.931,906.462C288.023,905.266 289.895,903.472 290.571,903.186C291.611,902.718 291.897,902.432 291.481,902.042C290.935,901.496 290.571,900.95 290.103,900.612C289.583,900.196 289.167,900.456 288.179,900.742C287.347,900.976 281.393,901.678 280.405,901.496C278.065,901.08 277.987,901.47 279.547,902.926Z" style="fill:white;"/>
|
||||
<path d="M279.547,902.926C280.327,903.628 280.535,903.602 281.653,903.29C282.849,902.952 285.241,902.718 286.905,902.666C287.763,902.614 287.841,902.718 287.581,903.082C286.723,904.304 285.033,906.124 284.149,906.462C283.109,906.826 283.057,906.878 282.979,907.71C282.875,908.828 283.629,909.01 284.383,908.698C285.683,908.126 288.543,907.918 289.635,908.126C290.233,908.23 290.285,908.49 290.129,909.036C289.531,911.22 288.283,913.508 287.139,914.99C286.411,915.926 286.359,915.9 285.475,915.406C284.981,915.146 284.045,914.574 283.551,914.366C282.667,913.95 282.251,914.288 283.213,915.458C283.785,916.134 284.981,917.304 285.397,917.928C286.281,919.254 286.385,919.28 287.633,918.058C290.675,915.094 292.079,909.972 292.573,909.53C293.223,908.958 293.405,908.776 293.041,908.152C292.703,907.58 291.663,906.93 291.195,906.618C290.857,906.384 290.441,906.046 289.661,906.306C288.881,906.54 288.413,906.254 286.931,906.462C288.023,905.266 289.895,903.472 290.571,903.186C291.611,902.718 291.897,902.432 291.481,902.042C290.935,901.496 290.571,900.95 290.103,900.612C289.583,900.196 289.167,900.456 288.179,900.742C287.347,900.976 281.393,901.678 280.405,901.496C278.065,901.08 277.987,901.47 279.547,902.926Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M287.346,902.94C286.511,904.121 284.91,905.864 284.062,906.19C283.738,906.304 283.507,906.389 283.338,906.475C283.119,906.588 282.988,906.709 282.898,906.865C282.795,907.046 282.751,907.288 282.714,907.681C282.714,907.681 282.714,907.681 282.714,907.681C282.661,908.249 282.805,908.619 283.031,908.842C283.362,909.169 283.919,909.198 284.478,908.966C284.48,908.965 284.482,908.965 284.484,908.964C285.747,908.408 288.527,908.207 289.589,908.409C289.59,908.409 289.591,908.409 289.593,908.409C289.689,908.426 289.768,908.447 289.831,908.474C289.876,908.494 289.911,908.511 289.927,908.544C289.941,908.575 289.939,908.612 289.936,908.655C289.93,908.74 289.907,908.838 289.874,908.951C289.874,908.953 289.874,908.954 289.874,908.955C289.286,911.101 288.059,913.349 286.935,914.806C286.762,915.028 286.63,915.194 286.515,915.309C286.438,915.386 286.378,915.438 286.311,915.453C286.247,915.468 286.183,915.446 286.1,915.412C285.967,915.358 285.807,915.268 285.598,915.151C285.596,915.15 285.594,915.149 285.592,915.148C285.094,914.886 284.153,914.312 283.653,914.101C283.338,913.953 283.072,913.896 282.886,913.911C282.675,913.929 282.535,914.031 282.458,914.175C282.392,914.301 282.367,914.483 282.443,914.721C282.515,914.947 282.693,915.258 283.014,915.648C283.015,915.649 283.015,915.65 283.016,915.652C283.583,916.321 284.769,917.478 285.181,918.096C285.462,918.518 285.672,918.813 285.859,918.989C286.03,919.148 286.193,919.224 286.362,919.235C286.524,919.246 286.699,919.196 286.902,919.067C287.133,918.919 287.419,918.655 287.812,918.271C289.971,916.167 291.321,912.995 292.106,911.125C292.302,910.658 292.462,910.274 292.59,910.014C292.654,909.884 292.699,909.79 292.743,909.751C292.994,909.53 293.178,909.361 293.296,909.207C293.402,909.066 293.462,908.93 293.484,908.788C293.516,908.574 293.459,908.33 293.266,907.999C293.266,907.999 293.266,907.998 293.266,907.998C292.911,907.398 291.83,906.703 291.337,906.375C290.952,906.108 290.475,905.736 289.588,906.03C289.228,906.138 288.939,906.122 288.569,906.107C288.312,906.096 288.018,906.085 287.647,906.104C288.707,905 290.107,903.691 290.668,903.453C290.67,903.453 290.672,903.452 290.674,903.451C291.118,903.251 291.432,903.079 291.623,902.921C291.84,902.741 291.926,902.554 291.931,902.381C291.935,902.209 291.861,902.02 291.661,901.83C291.106,901.275 290.732,900.723 290.257,900.377C289.933,900.12 289.641,900.069 289.276,900.13C288.976,900.18 288.617,900.318 288.111,900.464C287.288,900.695 281.423,901.392 280.45,901.213C280.449,901.213 280.449,901.213 280.448,901.213C279.582,901.059 279.009,901.018 278.7,901.085C278.473,901.134 278.344,901.246 278.281,901.37C278.211,901.505 278.198,901.684 278.299,901.904C278.425,902.182 278.787,902.597 279.373,903.143C279.374,903.144 279.375,903.145 279.376,903.146C279.724,903.459 279.974,903.636 280.223,903.721C280.595,903.848 280.977,903.775 281.72,903.568C281.72,903.568 281.72,903.568 281.721,903.568C282.903,903.234 285.268,903.004 286.913,902.953L287.346,902.94ZM279.547,902.926C280.327,903.628 280.535,903.602 281.653,903.29C282.849,902.952 285.241,902.718 286.905,902.666C287.763,902.614 287.841,902.718 287.581,903.082C286.723,904.304 285.033,906.124 284.149,906.462C283.109,906.826 283.057,906.878 282.979,907.71C282.875,908.828 283.629,909.01 284.383,908.698C285.683,908.126 288.543,907.918 289.635,908.126C290.233,908.23 290.285,908.49 290.129,909.036C289.531,911.22 288.283,913.508 287.139,914.99C286.411,915.926 286.359,915.9 285.475,915.406C284.981,915.146 284.045,914.574 283.551,914.366C282.667,913.95 282.251,914.288 283.213,915.458C283.785,916.134 284.981,917.304 285.397,917.928C286.281,919.254 286.385,919.28 287.633,918.058C290.675,915.094 292.079,909.972 292.573,909.53C293.223,908.958 293.405,908.776 293.041,908.152C292.703,907.58 291.663,906.93 291.195,906.618C290.857,906.384 290.441,906.046 289.661,906.306C288.881,906.54 288.413,906.254 286.931,906.462C288.023,905.266 289.895,903.472 290.571,903.186C291.611,902.718 291.897,902.432 291.481,902.042C290.935,901.496 290.571,900.95 290.103,900.612C289.583,900.196 289.167,900.456 288.179,900.742C287.347,900.976 281.393,901.678 280.405,901.496C278.065,901.08 277.987,901.47 279.547,902.926Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 19 KiB |
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated by Pixelmator Pro 3.0 -->
|
||||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="NewIMELogo-CHT-">
|
||||
<g id="CHT">
|
||||
<path id="-" fill="#ffffff" fill-rule="evenodd" stroke="none" d="M -122 1178.28125 L 1254 1178.28125 L 1254 -191 L -122 -191 Z"/>
|
||||
<path id="path1" fill="#ff8346" fill-rule="evenodd" stroke="none" d="M 1024 536.989563 L 429 537 L 338 1024 L 1024 1024 Z"/>
|
||||
<path id="path2" fill="#f9ce29" fill-rule="evenodd" stroke="none" d="M 1024 0 L 530 0 L 429 537 L 1024 537 Z"/>
|
||||
<path id="path3" fill="#4ec03b" fill-rule="evenodd" stroke="none" d="M 0 537 L 429.517975 536.989624 L 530 0 C 530 0 -3e-06 0 -3e-06 0 Z"/>
|
||||
<path id="path4" fill="#3d7db3" fill-rule="evenodd" stroke="none" d="M 338 1024 L 429.518005 536.989563 L 0 536.989563 L 0 1024 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="NewIMELogo-CHT--">
|
||||
<g id="g1">
|
||||
<g id="TextElement">
|
||||
<g id="Text">
|
||||
<path id="path5" fill="#002833" stroke="none" d="M 290.875397 291.867126 C 321.331543 317.292175 329.453491 316.350464 373.107788 305.049438 C 419.806641 292.80896 513.205505 284.333191 578.178711 282.449646 C 611.680603 280.566162 614.726379 284.333191 604.573914 297.515442 C 571.072021 341.77771 505.083557 407.697876 470.566437 419.938477 C 429.959076 433.122925 427.928589 435.00647 424.882843 465.142395 C 420.821869 505.635437 450.262756 512.227661 479.703644 500.926636 C 530.464661 480.208191 642.13678 472.676392 684.775818 480.208191 C 708.125244 483.97522 710.155701 493.3927 704.06427 513.169434 C 680.714844 592.271973 631.984314 675.143677 587.315918 728.821167 C 558.890259 762.724121 556.859802 761.78241 522.342712 743.88916 C 503.05307 734.47168 466.506683 713.753174 447.217041 706.221436 C 412.699921 691.153381 396.457214 703.396118 434.02005 745.772705 C 456.354248 770.25592 503.05307 812.634644 519.296997 835.234375 C 553.814087 883.263672 557.875061 884.205383 606.60437 839.943176 C 725.38324 732.588196 780.2052 547.07019 799.493652 531.062622 C 824.873535 510.344177 831.980225 503.751892 817.766846 481.149963 C 804.569885 460.433655 763.961304 436.889893 745.688049 425.588989 C 732.489929 417.11322 716.247192 404.87262 685.791016 414.290161 C 655.334961 422.763733 637.060547 412.406677 579.194031 419.938477 C 621.833069 376.620178 694.927063 311.641663 721.323364 301.282471 C 761.930786 284.333191 773.098511 273.973877 756.854614 259.847717 C 735.535645 240.070984 721.323364 220.296448 703.049011 208.053711 C 682.7453 192.985718 666.501404 202.403198 627.92334 212.76239 C 595.436707 221.238159 362.955353 246.663208 324.377258 240.070984 C 233.008865 225.005127 229.963135 239.129211 290.875397 291.867126 Z"/>
|
||||
<path id="path6" fill="#ffffff" fill-rule="evenodd" stroke="none" d="M 495.127747 339.496216 C 493.018585 341.4151 490.910645 343.298584 488.840851 345.109131 C 475.525909 356.699707 463.577728 366.840088 454.128326 370.571716 C 410.787384 384.733337 395.558746 397.809509 386.344086 412.658691 C 379.823639 423.234558 375.45047 436.962891 373.145905 459.925232 C 373.145905 459.925232 373.145905 459.925232 373.145905 459.96283 C 369.319702 497.920166 382.517853 521.861816 398.799896 536.821472 C 421.641693 557.756592 459.672852 564.311279 498.328369 549.462158 C 498.679901 549.351563 499.031403 549.207886 499.343628 549.062012 C 533.353149 535.189941 599.146179 528.308105 643.894409 529.033203 C 620.387695 591.476135 582.512573 653.304382 547.4104 695.499634 L 545.731445 697.456055 L 545.145203 697.166504 C 525.192993 687.421753 487.552734 666.342957 467.052429 658.15686 C 441.320984 647.07251 419.571869 646.820435 406.452362 650.151917 C 388.335266 654.752319 376.07486 665.255371 368.383087 678.764771 C 362.173645 689.703247 358.230652 703.975342 361.823334 722.266479 C 364.322113 735.015503 372.9505 755.260864 395.090424 780.252686 C 395.285858 780.433899 395.441956 780.615173 395.598083 780.834045 C 416.839539 804.121338 461.625854 844.072754 477.088104 865.587036 C 490.59726 884.422058 501.101227 897.279297 510.120453 905.102966 C 524.99762 917.997803 539.639954 923.17749 554.39917 924.081604 C 567.753479 924.915039 582.474487 922.19812 599.26416 912.274353 C 609.923035 905.971741 623.394104 894.962524 641.51239 878.481934 C 752.676819 777.971191 811.98877 613.098755 837.447327 567.101135 C 863.412292 544.897095 873.525391 527.006104 876.102844 510.777466 C 878.601624 495.422119 876.727234 477.239258 861.772644 453.478882 C 861.733337 453.370544 861.694031 453.297546 861.616577 453.224609 C 845.178406 427.401733 796.487244 395.888489 773.410645 381.61853 C 760.01825 372.999084 744.672852 363.00238 723.548157 359.597961 C 730.225891 354.851624 735.96582 351.411804 740.338989 349.671997 C 740.651184 349.56366 741.002686 349.419983 741.354187 349.274048 C 770.951172 336.923035 788.951538 325.042847 797.893311 315.589966 C 811.012817 301.682556 815.580261 287.12085 816.010437 273.394714 C 816.478699 257.530884 811.520447 238.8396 791.762512 221.311096 C 768.802795 199.940491 752.755432 179.113708 733.076172 165.604248 C 710.389282 149.088257 689.968811 145.537903 664.393494 149.486145 C 651.625488 151.442627 636.201355 156.692993 614.647705 162.48938 C 583.996155 170.385925 368.890717 194.942139 333.123627 188.820801 C 333.046143 188.7854 332.928192 188.7854 332.850739 188.747864 C 291.655884 181.976501 264.284821 181.468079 249.64241 184.401611 C 224.809448 189.364624 212.002136 202.765747 205.051514 215.333435 C 196.929565 229.930542 193.103333 249.344727 204.778641 273.140503 C 211.026199 285.962463 227.815857 306.064148 256.826599 331.164185 C 257.06134 331.383057 257.296082 331.599731 257.568939 331.780945 C 276.740631 347.788513 290.992157 356.012268 304.776611 360.320801 C 324.924194 366.696472 345.852295 365.827637 386.148651 355.395447 C 386.187988 355.360107 386.227325 355.360107 386.304779 355.360107 C 412.154175 348.586548 453.229858 343.15271 495.127747 339.496216 Z M 290.875397 291.867126 C 321.331543 317.292175 329.453491 316.350464 373.107788 305.049438 C 419.806641 292.80896 513.205505 284.333191 578.178711 282.449646 C 611.680603 280.566162 614.726379 284.333191 604.573914 297.515442 C 571.072021 341.77771 505.083557 407.697876 470.566437 419.938477 C 429.959076 433.122925 427.928589 435.00647 424.882843 465.142395 C 420.821869 505.635437 450.262756 512.227661 479.703644 500.926636 C 530.464661 480.208191 642.13678 472.676392 684.775818 480.208191 C 708.125244 483.97522 710.155701 493.3927 704.06427 513.169434 C 680.714844 592.271973 631.984314 675.143677 587.315918 728.821167 C 558.890259 762.724121 556.859802 761.78241 522.342712 743.88916 C 503.05307 734.47168 466.506683 713.753174 447.217041 706.221436 C 412.699921 691.153381 396.457214 703.396118 434.02005 745.772705 C 456.354248 770.25592 503.05307 812.634644 519.296997 835.234375 C 553.814087 883.263672 557.875061 884.205383 606.60437 839.943176 C 725.38324 732.588196 780.2052 547.07019 799.493652 531.062622 C 824.873535 510.344177 831.980225 503.751892 817.766846 481.149963 C 804.569885 460.433655 763.961304 436.889893 745.688049 425.588989 C 732.489929 417.11322 716.247192 404.87262 685.791016 414.290161 C 655.334961 422.763733 637.060547 412.406677 579.194031 419.938477 C 621.833069 376.620178 694.927063 311.641663 721.323364 301.282471 C 761.930786 284.333191 773.098511 273.973877 756.854614 259.847717 C 735.535645 240.070984 721.323364 220.296448 703.049011 208.053711 C 682.7453 192.985718 666.501404 202.403198 627.92334 212.76239 C 595.436707 221.238159 362.955353 246.663208 324.377258 240.070984 C 233.008865 225.005127 229.963135 239.129211 290.875397 291.867126 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 22 KiB |
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated by Pixelmator Pro 3.0 -->
|
||||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="NewIMELogo-CHT-">
|
||||
<g id="CHT">
|
||||
<path id="-" fill="#ffffff" fill-rule="evenodd" stroke="none" d="M 0 1019 L 1024 1019 L 1024 0 L 0 0 Z"/>
|
||||
<path id="path1" fill="#ff8346" fill-rule="evenodd" stroke="none" d="M 1024 541.759705 L 410.431976 541.759705 L 320 1024 L 1024 1024 Z"/>
|
||||
<path id="path2" fill="#f9ce29" fill-rule="evenodd" stroke="none" d="M 1024 0 L 508.359161 0 L 406.06897 541.759705 L 1024 541.759705 Z"/>
|
||||
<path id="path3" fill="#4ec03b" fill-rule="evenodd" stroke="none" d="M 0 541.759705 L 410.431976 541.759705 L 512 0 C 512 0 0 0 0 0 Z"/>
|
||||
<path id="path4" fill="#3d7db3" fill-rule="evenodd" stroke="none" d="M 320 1024 L 410.431976 541.759705 L -0 541.759705 L -0 1024 Z"/>
|
||||
</g>
|
||||
<g id="g1">
|
||||
<g id="TextElement">
|
||||
<g id="Text">
|
||||
<path id="path5" fill="#002833" stroke="none" d="M 239.846634 241.067261 C 277.331116 272.359619 287.327362 271.200562 341.055725 257.291626 C 398.53125 242.226379 513.483704 231.794678 593.450745 229.476501 C 634.683838 227.158386 638.432434 231.794678 625.937134 248.018982 C 584.704041 302.495605 503.487457 383.628174 461.004852 398.693481 C 411.02655 414.920532 408.527496 417.238708 404.7789 454.329102 C 399.780762 504.166687 436.015717 512.280212 472.250641 498.371277 C 534.725769 472.871643 672.168335 463.601746 724.647156 472.871643 C 753.384888 477.507935 755.883972 489.098694 748.38678 513.43927 C 719.649048 610.796265 659.672974 712.792236 604.696533 778.856812 C 569.711121 820.583557 567.212097 819.4245 524.729492 797.402039 C 500.988403 785.811279 456.008209 760.311646 432.26712 751.041748 C 389.784515 732.49646 369.793488 747.564453 416.024689 799.720215 C 443.512909 829.853455 500.988403 882.011841 520.980896 909.826965 C 563.463501 968.93988 568.461609 970.098938 628.436157 915.622375 C 774.625488 783.493164 842.098694 555.16333 865.838318 535.461731 C 897.075134 509.962036 905.821838 501.848511 888.32843 474.030701 C 872.085999 448.533752 822.106201 419.556824 799.616089 405.647949 C 783.372192 395.216248 763.381165 380.15094 725.896667 391.741699 C 688.412231 402.170715 665.920654 389.423584 594.700317 398.693481 C 647.179138 345.378662 737.140991 265.405151 769.628784 252.655334 C 819.607117 231.794678 833.35199 219.0448 813.359497 201.658691 C 787.120789 177.318115 769.628784 152.980225 747.137268 137.912231 C 722.148071 119.367004 702.155579 130.957764 654.674866 143.707581 C 614.691345 154.139282 328.560425 185.431641 281.079712 177.318115 C 168.626297 158.775574 164.877701 176.158997 239.846634 241.067261 Z"/>
|
||||
<path id="path6" fill="#ffffff" fill-rule="evenodd" stroke="none" d="M 491.234131 299.687683 C 488.638275 302.049377 486.043884 304.367493 483.496429 306.595886 C 467.108795 320.861206 452.403351 333.341675 440.773315 337.934448 C 387.430634 355.364136 368.687683 371.457886 357.346558 389.733765 C 349.321411 402.750244 343.939056 419.646606 341.102661 447.907959 C 341.102661 447.907959 341.102661 447.907959 341.102661 447.954224 C 336.393463 494.670959 352.63736 524.137573 372.676788 542.5495 C 400.789764 568.315796 447.597351 576.383057 495.17337 558.107239 C 495.606018 557.971191 496.038666 557.794312 496.422913 557.614746 C 538.280762 540.541504 619.256836 532.071533 674.331543 532.963989 C 645.400208 609.816772 598.784729 685.913086 555.582031 737.845703 L 553.515625 740.253601 L 552.794067 739.897217 C 528.237549 727.903687 481.911041 701.960571 456.679901 691.885315 C 425.010437 678.243042 398.24231 677.932861 382.095215 682.033142 C 359.797241 687.69519 344.70752 700.621948 335.240723 717.248901 C 327.598328 730.71167 322.745422 748.277344 327.167175 770.789551 C 330.242584 786.480591 340.862152 811.39801 368.111298 842.157104 C 368.351837 842.380188 368.543945 842.603271 368.736084 842.872681 C 394.879425 871.533936 450.001068 920.704895 469.031494 947.184082 C 485.658173 970.365601 498.586121 986.189941 509.686707 995.819031 C 527.99707 1011.689636 546.018433 1018.064575 564.183594 1019.177368 C 580.619629 1020.203125 598.737793 1016.859253 619.402039 1004.645386 C 632.52063 996.888306 649.100403 983.338501 671.399841 963.054688 C 808.21759 839.349182 881.216919 636.429199 912.550537 579.816772 C 944.507446 552.488708 956.954346 530.468994 960.126587 510.495361 C 963.202026 491.596436 960.895081 469.217529 942.489441 439.973999 C 942.44104 439.840637 942.392639 439.750854 942.297302 439.661072 C 922.065735 407.879028 862.138123 369.093506 833.736206 351.530518 C 817.253235 340.921936 798.366577 328.618347 772.366943 324.428223 C 780.585693 318.586609 787.650208 314.352966 793.032593 312.21167 C 793.416809 312.078369 793.849487 311.901489 794.282104 311.721924 C 830.709167 296.52063 852.863403 281.898865 863.868713 270.264587 C 880.015808 253.147766 885.637207 235.225647 886.166687 218.33197 C 886.743042 198.807251 880.640564 175.802612 856.32312 154.229065 C 828.064941 127.926758 808.314392 102.293762 784.09375 85.666748 C 756.171387 65.339355 731.038513 60.969727 699.561218 65.829102 C 683.846741 68.237061 664.86322 74.699036 638.335632 81.833069 C 600.610657 91.55188 335.865479 121.774963 291.844452 114.240967 C 291.749115 114.197388 291.603912 114.197388 291.508606 114.151184 C 240.807251 105.817261 207.119766 105.191467 189.098358 108.802002 C 158.534714 114.910278 142.771851 131.403992 134.217255 146.871948 C 124.221001 164.837585 119.511795 188.731995 133.881409 218.019104 C 141.570709 233.799927 162.234909 258.540466 197.940414 289.432861 C 198.229324 289.702209 198.51825 289.968872 198.854095 290.191956 C 222.449997 309.893555 239.990356 320.015076 256.955841 325.317932 C 281.752869 333.164856 307.510529 332.095581 357.106049 319.25592 C 357.154449 319.212402 357.20285 319.212402 357.298187 319.212402 C 389.112823 310.875732 439.667511 304.187927 491.234131 299.687683 Z M 239.846634 241.067261 C 277.331116 272.359619 287.327362 271.200562 341.055725 257.291626 C 398.53125 242.226379 513.483704 231.794678 593.450745 229.476501 C 634.683838 227.158386 638.432434 231.794678 625.937134 248.018982 C 584.704041 302.495605 503.487457 383.628174 461.004852 398.693481 C 411.02655 414.920532 408.527496 417.238708 404.7789 454.329102 C 399.780762 504.166687 436.015717 512.280212 472.250641 498.371277 C 534.725769 472.871643 672.168335 463.601746 724.647156 472.871643 C 753.384888 477.507935 755.883972 489.098694 748.38678 513.43927 C 719.649048 610.796265 659.672974 712.792236 604.696533 778.856812 C 569.711121 820.583557 567.212097 819.4245 524.729492 797.402039 C 500.988403 785.811279 456.008209 760.311646 432.26712 751.041748 C 389.784515 732.49646 369.793488 747.564453 416.024689 799.720215 C 443.512909 829.853455 500.988403 882.011841 520.980896 909.826965 C 563.463501 968.93988 568.461609 970.098938 628.436157 915.622375 C 774.625488 783.493164 842.098694 555.16333 865.838318 535.461731 C 897.075134 509.962036 905.821838 501.848511 888.32843 474.030701 C 872.085999 448.533752 822.106201 419.556824 799.616089 405.647949 C 783.372192 395.216248 763.381165 380.15094 725.896667 391.741699 C 688.412231 402.170715 665.920654 389.423584 594.700317 398.693481 C 647.179138 345.378662 737.140991 265.405151 769.628784 252.655334 C 819.607117 231.794678 833.35199 219.0448 813.359497 201.658691 C 787.120789 177.318115 769.628784 152.980225 747.137268 137.912231 C 722.148071 119.367004 702.155579 130.957764 654.674866 143.707581 C 614.691345 154.139282 328.560425 185.431641 281.079712 177.318115 C 168.626297 158.775574 164.877701 176.158997 239.846634 241.067261 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.7 KiB |
|
@ -1,32 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,0.176229,0.510845)">
|
||||
<g id="v" transform="matrix(-0.0111111,-1.36072e-18,1.36072e-18,-0.0111111,13.8235,12.9891)">
|
||||
<g id="Shadow" transform="matrix(-1.02102,-1.25039e-16,1.28682e-16,-1.05077,1169.98,1160.72)">
|
||||
<path d="M1322.21,34L452.612,34C202.809,34 0,231.067 0,473.799L0,1318.78L1322.21,1318.78L1322.21,34Z" style="fill-opacity:0.63;"/>
|
||||
</g>
|
||||
<g transform="matrix(-1.01166,-1.23893e-16,1.27502e-16,-1.04113,1259.98,1250.4)">
|
||||
<g id="Outline">
|
||||
<path d="M1334.45,34L452.612,34C202.809,34 0,231.067 0,473.799L0,1330.67L1334.45,1330.67L1334.45,34Z" style="fill:rgb(11,46,99);"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Core" transform="matrix(-0.861782,-1.05538e-16,1.08613e-16,-0.886889,1169.98,1155.15)">
|
||||
<path d="M1357.66,34L452.612,34C202.809,34 0,231.067 0,473.799L0,1353.23L1357.66,1353.23L1357.66,34Z" style="fill:url(#_Radial1);"/>
|
||||
<path d="M1409.88,1403.97L1409.88,-16.739L452.612,-16.739C173.989,-16.739 -52.217,203.063 -52.217,473.799C-52.217,473.799 -52.217,1403.97 -52.217,1403.97L1409.88,1403.97ZM1357.66,34L452.612,34C202.809,34 0,231.067 0,473.799L0,1353.23L1357.66,1353.23L1357.66,34Z" style="fill:white;"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="TextElement" transform="matrix(0.0115939,0,0,0.0115939,0.253417,-0.559324)">
|
||||
<g id="TextShadow" transform="matrix(76.3556,0,0,67.9081,-21039.4,-60964.1)">
|
||||
<path d="M279.547,902.926C280.327,903.628 280.535,903.602 281.653,903.29C282.849,902.952 285.241,902.718 286.905,902.666C287.763,902.614 287.841,902.718 287.581,903.082C286.723,904.304 285.033,906.124 284.149,906.462C283.109,906.826 283.057,906.878 282.979,907.71C282.875,908.828 283.629,909.01 284.383,908.698C285.683,908.126 288.543,907.918 289.635,908.126C290.233,908.23 290.285,908.49 290.129,909.036C289.531,911.22 288.283,913.508 287.139,914.99C286.411,915.926 286.359,915.9 285.475,915.406C284.981,915.146 284.045,914.574 283.551,914.366C282.667,913.95 282.251,914.288 283.213,915.458C283.785,916.134 284.981,917.304 285.397,917.928C286.281,919.254 286.385,919.28 287.633,918.058C290.675,915.094 292.079,909.972 292.573,909.53C293.223,908.958 293.405,908.776 293.041,908.152C292.703,907.58 291.663,906.93 291.195,906.618C290.857,906.384 290.441,906.046 289.661,906.306C288.881,906.54 288.413,906.254 286.931,906.462C288.023,905.266 289.895,903.472 290.571,903.186C291.611,902.718 291.897,902.432 291.481,902.042C290.935,901.496 290.571,900.95 290.103,900.612C289.583,900.196 289.167,900.456 288.179,900.742C287.347,900.976 281.393,901.678 280.405,901.496C278.065,901.08 277.987,901.47 279.547,902.926Z" style="fill-opacity:0.8;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g id="Text" transform="matrix(64.7661,0,0,60.0776,-17811.9,-53913.5)">
|
||||
<path d="M279.547,902.926C280.327,903.628 280.535,903.602 281.653,903.29C282.849,902.952 285.241,902.718 286.905,902.666C287.763,902.614 287.841,902.718 287.581,903.082C286.723,904.304 285.033,906.124 284.149,906.462C283.109,906.826 283.057,906.878 282.979,907.71C282.875,908.828 283.629,909.01 284.383,908.698C285.683,908.126 288.543,907.918 289.635,908.126C290.233,908.23 290.285,908.49 290.129,909.036C289.531,911.22 288.283,913.508 287.139,914.99C286.411,915.926 286.359,915.9 285.475,915.406C284.981,915.146 284.045,914.574 283.551,914.366C282.667,913.95 282.251,914.288 283.213,915.458C283.785,916.134 284.981,917.304 285.397,917.928C286.281,919.254 286.385,919.28 287.633,918.058C290.675,915.094 292.079,909.972 292.573,909.53C293.223,908.958 293.405,908.776 293.041,908.152C292.703,907.58 291.663,906.93 291.195,906.618C290.857,906.384 290.441,906.046 289.661,906.306C288.881,906.54 288.413,906.254 286.931,906.462C288.023,905.266 289.895,903.472 290.571,903.186C291.611,902.718 291.897,902.432 291.481,902.042C290.935,901.496 290.571,900.95 290.103,900.612C289.583,900.196 289.167,900.456 288.179,900.742C287.347,900.976 281.393,901.678 280.405,901.496C278.065,901.08 277.987,901.47 279.547,902.926Z" style="fill:rgb(16,42,103);fill-rule:nonzero;"/>
|
||||
<path d="M284.778,904.241C284.724,904.294 284.67,904.346 284.617,904.396C284.276,904.716 283.97,904.996 283.728,905.099C282.618,905.49 282.228,905.851 281.992,906.261C281.825,906.553 281.713,906.932 281.654,907.566L281.654,907.567C281.556,908.615 281.894,909.276 282.311,909.689C282.896,910.267 283.87,910.448 284.86,910.038C284.869,910.035 284.878,910.031 284.886,910.027C285.757,909.644 287.442,909.454 288.588,909.474C287.986,911.198 287.016,912.905 286.117,914.07L286.074,914.124L286.059,914.116C285.548,913.847 284.584,913.265 284.059,913.039C283.4,912.733 282.843,912.726 282.507,912.818C282.043,912.945 281.729,913.235 281.532,913.608C281.373,913.91 281.272,914.304 281.364,914.809C281.428,915.161 281.649,915.72 282.216,916.41C282.221,916.415 282.225,916.42 282.229,916.426C282.773,917.069 283.92,918.172 284.316,918.766C284.662,919.286 284.931,919.641 285.162,919.857C285.543,920.213 285.918,920.356 286.296,920.381C286.638,920.404 287.015,920.329 287.445,920.055C287.718,919.881 288.063,919.577 288.527,919.122C291.374,916.347 292.893,911.795 293.545,910.525C294.21,909.912 294.469,909.418 294.535,908.97C294.599,908.546 294.551,908.044 294.168,907.388C294.167,907.385 294.166,907.383 294.164,907.381C293.743,906.668 292.496,905.798 291.905,905.404C291.562,905.166 291.169,904.89 290.628,904.796C290.799,904.665 290.946,904.57 291.058,904.522C291.066,904.519 291.075,904.515 291.084,904.511C291.842,904.17 292.303,903.842 292.532,903.581C292.868,903.197 292.985,902.795 292.996,902.416C293.008,901.978 292.881,901.462 292.375,900.978C291.787,900.388 291.376,899.813 290.872,899.44C290.291,898.984 289.768,898.886 289.113,898.995C288.786,899.049 288.391,899.194 287.839,899.354C287.054,899.572 281.545,900.25 280.629,900.081C280.627,900.08 280.624,900.08 280.622,900.079C279.567,899.892 278.866,899.878 278.491,899.959C277.855,900.096 277.527,900.466 277.349,900.813C277.141,901.216 277.043,901.752 277.342,902.409C277.502,902.763 277.932,903.318 278.675,904.011C278.681,904.017 278.687,904.023 278.694,904.028C279.185,904.47 279.55,904.697 279.903,904.816C280.419,904.992 280.955,904.968 281.987,904.68C281.988,904.679 281.989,904.679 281.991,904.679C282.653,904.492 283.705,904.342 284.778,904.241ZM279.547,902.926C280.327,903.628 280.535,903.602 281.653,903.29C282.849,902.952 285.241,902.718 286.905,902.666C287.763,902.614 287.841,902.718 287.581,903.082C286.723,904.304 285.033,906.124 284.149,906.462C283.109,906.826 283.057,906.878 282.979,907.71C282.875,908.828 283.629,909.01 284.383,908.698C285.683,908.126 288.543,907.918 289.635,908.126C290.233,908.23 290.285,908.49 290.129,909.036C289.531,911.22 288.283,913.508 287.139,914.99C286.411,915.926 286.359,915.9 285.475,915.406C284.981,915.146 284.045,914.574 283.551,914.366C282.667,913.95 282.251,914.288 283.213,915.458C283.785,916.134 284.981,917.304 285.397,917.928C286.281,919.254 286.385,919.28 287.633,918.058C290.675,915.094 292.079,909.972 292.573,909.53C293.223,908.958 293.405,908.776 293.041,908.152C292.703,907.58 291.663,906.93 291.195,906.618C290.857,906.384 290.441,906.046 289.661,906.306C288.881,906.54 288.413,906.254 286.931,906.462C288.023,905.266 289.895,903.472 290.571,903.186C291.611,902.718 291.897,902.432 291.481,902.042C290.935,901.496 290.571,900.95 290.103,900.612C289.583,900.196 289.167,900.456 288.179,900.742C287.347,900.976 281.393,901.678 280.405,901.496C278.065,901.08 277.987,901.47 279.547,902.926Z" style="fill:white;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<radialGradient id="_Radial1" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1087.57,-3794.91,3905.47,-1056.78,1725.88,3633.33)"><stop offset="0" style="stop-color:rgb(218,240,255);stop-opacity:1"/><stop offset="0.28" style="stop-color:rgb(200,225,246);stop-opacity:1"/><stop offset="0.51" style="stop-color:rgb(193,219,243);stop-opacity:1"/><stop offset="0.73" style="stop-color:rgb(127,167,220);stop-opacity:1"/><stop offset="0.87" style="stop-color:rgb(14,87,170);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(9,53,104);stop-opacity:1"/></radialGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 8.5 KiB |