Repo // Introducing reverse-lookup window.

This commit is contained in:
ShikiSuen 2022-12-10 18:56:39 +08:00
parent 9ec8428af6
commit 892a574378
7 changed files with 217 additions and 0 deletions

View File

@ -0,0 +1,193 @@
// (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 LangModelAssembly
class CtlRevLookupWindow: NSWindowController, NSWindowDelegate {
static var shared: CtlRevLookupWindow?
static func show() {
if shared == nil { Self.shared = .init(window: FrmRevLookupWindow()) }
guard let shared = Self.shared, let window = shared.window as? FrmRevLookupWindow else { return }
shared.window = window
window.delegate = shared
window.setPosition(vertical: .bottom, horizontal: .right, padding: 20)
window.orderFrontRegardless() //
window.level = .statusBar
window.titlebarAppearsTransparent = true
shared.showWindow(shared)
NSApp.activate(ignoringOtherApps: true)
}
}
class FrmRevLookupWindow: NSWindow {
typealias LMRevLookup = vChewingLM.LMRevLookup
static let lmRevLookupCore = LMRevLookup(path: LMMgr.getBundleDataPath("data-bpmf-reverse-lookup"))
// Dictionary
static let lmRevLookupCNS1 = LMRevLookup(path: LMMgr.getBundleDataPath("data-bpmf-reverse-lookup-CNS1"))
static let lmRevLookupCNS2 = LMRevLookup(path: LMMgr.getBundleDataPath("data-bpmf-reverse-lookup-CNS2"))
static let lmRevLookupCNS3 = LMRevLookup(path: LMMgr.getBundleDataPath("data-bpmf-reverse-lookup-CNS3"))
static let lmRevLookupCNS4 = LMRevLookup(path: LMMgr.getBundleDataPath("data-bpmf-reverse-lookup-CNS4"))
static let lmRevLookupCNS5 = LMRevLookup(path: LMMgr.getBundleDataPath("data-bpmf-reverse-lookup-CNS5"))
static let lmRevLookupCNS6 = LMRevLookup(path: LMMgr.getBundleDataPath("data-bpmf-reverse-lookup-CNS6"))
public lazy var inputField = NSTextField()
public lazy var resultView = NSTextView()
private lazy var clipView = NSClipView()
private lazy var scrollView = NSScrollView()
private lazy var button = NSButton()
private lazy var view = NSView()
init() {
super.init(
contentRect: CGRect(x: 196, y: 240, width: 480, height: 340),
styleMask: [.titled, .closable],
backing: .buffered, defer: true
)
setupUI()
setupLayout()
}
private func setupUI() {
contentView = view
allowsToolTipsWhenApplicationIsInactive = false
autorecalculatesKeyViewLoop = false
isReleasedWhenClosed = false
title = "Reverse Lookup (Phonabets)".localized
view.addSubview(inputField)
view.addSubview(scrollView)
view.addSubview(button)
view.wantsLayer = true
button.setContentHuggingPriority(.defaultHigh, for: .vertical)
button.translatesAutoresizingMaskIntoConstraints = false
button.alignment = .center
button.bezelStyle = .recessed
button.font = NSFont.systemFont(ofSize: 12, weight: .bold)
button.imageScaling = .scaleProportionallyDown
button.title = "👓"
button.cell.map { $0 as? NSButtonCell }??.isBordered = true
button.target = self
button.action = #selector(keyboardConfirmed(_:))
button.keyEquivalent = String(utf16CodeUnits: [unichar(NSEvent.SpecialKey.enter.rawValue)], count: 1) as String
scrollView.borderType = .noBorder
scrollView.hasHorizontalScroller = false
scrollView.horizontalLineScroll = 10
scrollView.horizontalPageScroll = 10
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.verticalLineScroll = 10
scrollView.verticalPageScroll = 10
clipView.documentView = resultView
clipView.autoresizingMask = [.width, .height]
clipView.drawsBackground = false
clipView.frame = CGRect(x: 0, y: 0, width: 480, height: 320)
resultView.autoresizingMask = [.width, .height]
resultView.backgroundColor = NSColor.textBackgroundColor
resultView.frame = CGRect(x: 0, y: 0, width: 480, height: 320)
resultView.importsGraphics = false
resultView.insertionPointColor = NSColor.textColor
resultView.isEditable = false
resultView.isRichText = false
resultView.isVerticallyResizable = true
resultView.maxSize = CGSize(width: 774, height: 10_000_000)
resultView.minSize = CGSize(width: 480, height: 320)
resultView.smartInsertDeleteEnabled = true
resultView.textColor = NSColor.textColor
resultView.wantsLayer = true
resultView.font = NSFont.systemFont(ofSize: 13)
resultView.string = "Maximum 15 results returnable.".localized
scrollView.contentView = clipView
inputField.setContentHuggingPriority(.defaultHigh, for: .vertical)
inputField.translatesAutoresizingMaskIntoConstraints = false
inputField.backgroundColor = NSColor.textBackgroundColor
inputField.drawsBackground = true
inputField.font = NSFont.systemFont(ofSize: 13)
inputField.isBezeled = true
inputField.isEditable = true
inputField.isSelectable = true
inputField.lineBreakMode = .byClipping
inputField.textColor = NSColor.controlTextColor
inputField.cell.map { $0 as? NSTextFieldCell }??.isScrollable = true
inputField.cell.map { $0 as? NSTextFieldCell }??.sendsActionOnEndEditing = true
inputField.cell.map { $0 as? NSTextFieldCell }??.usesSingleLineMode = true
inputField.action = #selector(keyboardConfirmed(_:))
inputField.toolTip =
"Maximum 15 results returnable.".localized
}
@objc func keyboardConfirmed(_: Any?) {
if inputField.stringValue.isEmpty { return }
resultView.string = "\n" + "Loading…".localized
DispatchQueue.main.async { [self] in
self.updateResult(with: self.inputField.stringValue)
}
}
private func updateResult(with input: String) {
guard !input.isEmpty else { return }
button.isEnabled = false
inputField.isEnabled = false
let strBuilder = NSMutableString()
strBuilder.append("\n")
strBuilder.append("Char\tReading(s)\n".localized)
strBuilder.append("==\t====\n")
var i = 0
theLoop: for char in input.charComponents {
if i == 15 {
strBuilder.append("Maximum 15 results returnable.".localized + "\n")
break theLoop
}
var arrResult = Self.lmRevLookupCore.query(with: char) ?? []
//
arrResult +=
Self.lmRevLookupCNS1.query(with: char)
?? Self.lmRevLookupCNS2.query(with: char)
?? Self.lmRevLookupCNS3.query(with: char)
?? Self.lmRevLookupCNS4.query(with: char)
?? Self.lmRevLookupCNS5.query(with: char)
?? Self.lmRevLookupCNS6.query(with: char)
?? []
arrResult = arrResult.deduplicated
if !arrResult.isEmpty {
strBuilder.append(char + "\t")
strBuilder.append(arrResult.joined(separator: ", "))
strBuilder.append("\n")
i += 1
}
}
resultView.string = strBuilder.description
button.isEnabled = true
inputField.isEnabled = true
}
private func setupLayout() {
inputField.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
button.leadingAnchor.constraint(equalTo: inputField.trailingAnchor, constant: 5).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
inputField.lastBaselineAnchor.constraint(equalTo: button.lastBaselineAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: button.bottomAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: button.trailingAnchor, constant: 3).isActive = true
inputField.topAnchor.constraint(equalTo: button.topAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
inputField.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
inputField.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
}
}

View File

@ -16,6 +16,10 @@
"Loading…" = "Loading…"; "Loading…" = "Loading…";
"Consolidate" = "Consolidate"; "Consolidate" = "Consolidate";
"Reload" = "Reload"; "Reload" = "Reload";
"Loading complete." = "Loading complete.";
"Char\tReading(s)\n" = "Char\tReading(s)\n";
"Reverse Lookup (Phonabets)" = "Reverse Lookup (Phonabets)";
"Maximum 15 results returnable." = "Maximum 15 results returnable.";
"Example:\nCandidate Reading-Reading #Comment" = "Example:\nCandidate Reading-Reading #Comment"; "Example:\nCandidate Reading-Reading #Comment" = "Example:\nCandidate Reading-Reading #Comment";
"Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment" = "Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment"; "Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment" = "Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment";
"Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3..." = "Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3..."; "Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3..." = "Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3...";

View File

@ -16,6 +16,10 @@
"Loading…" = "Loading…"; "Loading…" = "Loading…";
"Consolidate" = "Consolidate"; "Consolidate" = "Consolidate";
"Reload" = "Reload"; "Reload" = "Reload";
"Loading complete." = "Loading complete.";
"Char\tReading(s)\n" = "Char\tReading(s)\n";
"Reverse Lookup (Phonabets)" = "Reverse Lookup (Phonabets)";
"Maximum 15 results returnable." = "Maximum 15 results returnable.";
"Example:\nCandidate Reading-Reading #Comment" = "Example:\nCandidate Reading-Reading #Comment"; "Example:\nCandidate Reading-Reading #Comment" = "Example:\nCandidate Reading-Reading #Comment";
"Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment" = "Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment"; "Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment" = "Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment";
"Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3..." = "Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3..."; "Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3..." = "Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3...";

View File

@ -16,6 +16,10 @@
"Loading…" = "読み込む中…"; "Loading…" = "読み込む中…";
"Consolidate" = "整理"; "Consolidate" = "整理";
"Reload" = "再読込"; "Reload" = "再読込";
"Loading complete." = "読込完了。";
"Char\tReading(s)\n" = "漢字\t音読\n";
"Reverse Lookup (Phonabets)" = "注音音読逆引参照";
"Maximum 15 results returnable." = "参照結果は最初の15件のみ表示可能。";
"Example:\nCandidate Reading-Reading #Comment" = "【模範例】\n候補 音読-音読 #メモ"; "Example:\nCandidate Reading-Reading #Comment" = "【模範例】\n候補 音読-音読 #メモ";
"Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment" = "【模範例】\n候補 音読-音読 優先度 #メモ\n候補 音読-音読 #メモ"; "Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment" = "【模範例】\n候補 音読-音読 優先度 #メモ\n候補 音読-音読 #メモ";
"Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3..." = "【模範例】\n頭文字 残候補\n頭文字 残候補1 残候補2 残候補3..."; "Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3..." = "【模範例】\n頭文字 残候補\n頭文字 残候補1 残候補2 残候補3...";

View File

@ -16,6 +16,10 @@
"Loading…" = "正在载入…"; "Loading…" = "正在载入…";
"Consolidate" = "整理"; "Consolidate" = "整理";
"Reload" = "重新载入"; "Reload" = "重新载入";
"Loading complete." = "载入完毕。";
"Char\tReading(s)\n" = "汉字\t读音\n";
"Reverse Lookup (Phonabets)" = "注音反查";
"Maximum 15 results returnable." = "仅能给出前 15 笔结果。";
"Example:\nCandidate Reading-Reading #Comment" = "【范例】\n候选字词 读音-读音 #注解"; "Example:\nCandidate Reading-Reading #Comment" = "【范例】\n候选字词 读音-读音 #注解";
"Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment" = "【范例】\n候选字词 读音-读音 权重 #注解\n候选字词 读音-读音 #注解"; "Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment" = "【范例】\n候选字词 读音-读音 权重 #注解\n候选字词 读音-读音 #注解";
"Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3..." = "【范例】\n首字 候选\n首字 候选1 候选2 候选3..."; "Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3..." = "【范例】\n首字 候选\n首字 候选1 候选2 候选3...";

View File

@ -16,6 +16,10 @@
"Loading…" = "正在載入…"; "Loading…" = "正在載入…";
"Consolidate" = "整理"; "Consolidate" = "整理";
"Reload" = "重新載入"; "Reload" = "重新載入";
"Loading complete." = "載入完畢。";
"Char\tReading(s)\n" = "漢字\t讀音\n";
"Reverse Lookup (Phonabets)" = "注音反查";
"Maximum 15 results returnable." = "僅能給出前 15 筆結果。";
"Example:\nCandidate Reading-Reading #Comment" = "【範例】\n候選字詞 讀音-讀音 #註解"; "Example:\nCandidate Reading-Reading #Comment" = "【範例】\n候選字詞 讀音-讀音 #註解";
"Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment" = "【範例】\n候選字詞 讀音-讀音 權重 #註解\n候選字詞 讀音-讀音 #註解"; "Example:\nCandidate Reading-Reading Weight #Comment\nCandidate Reading-Reading #Comment" = "【範例】\n候選字詞 讀音-讀音 權重 #註解\n候選字詞 讀音-讀音 #註解";
"Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3..." = "【範例】\n首字 候選\n首字 候選1 候選2 候選3..."; "Example:\nInitial RestPhrase\nInitial RestPhrase1 RestPhrase2 RestPhrase3..." = "【範例】\n首字 候選\n首字 候選1 候選2 候選3...";

View File

@ -27,6 +27,7 @@
5B253E822945AF6700680C67 /* data-bpmf-reverse-lookup-CNS4.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5B253E7C2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS4.plist */; }; 5B253E822945AF6700680C67 /* data-bpmf-reverse-lookup-CNS4.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5B253E7C2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS4.plist */; };
5B253E832945AF6700680C67 /* data-bpmf-reverse-lookup-CNS5.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5B253E7D2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS5.plist */; }; 5B253E832945AF6700680C67 /* data-bpmf-reverse-lookup-CNS5.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5B253E7D2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS5.plist */; };
5B2E009428FD1E8100E78D6E /* VwrPrefPaneCassette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2E009328FD1E8100E78D6E /* VwrPrefPaneCassette.swift */; }; 5B2E009428FD1E8100E78D6E /* VwrPrefPaneCassette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2E009328FD1E8100E78D6E /* VwrPrefPaneCassette.swift */; };
5B30BF282944867800BD87A9 /* CtlRevLookupWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B30BF272944867800BD87A9 /* CtlRevLookupWindow.swift */; };
5B3133BF280B229700A4A505 /* InputHandler_HandleStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3133BE280B229700A4A505 /* InputHandler_HandleStates.swift */; }; 5B3133BF280B229700A4A505 /* InputHandler_HandleStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3133BE280B229700A4A505 /* InputHandler_HandleStates.swift */; };
5B40113928D7050D00A9D4CB /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113828D7050D00A9D4CB /* Shared */; }; 5B40113928D7050D00A9D4CB /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113828D7050D00A9D4CB /* Shared */; };
5B40113C28D71C0100A9D4CB /* Uninstaller in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113B28D71C0100A9D4CB /* Uninstaller */; }; 5B40113C28D71C0100A9D4CB /* Uninstaller in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113B28D71C0100A9D4CB /* Uninstaller */; };
@ -215,6 +216,7 @@
5B2DB17127AF8771006D874E /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = Data/Makefile; sourceTree = "<group>"; }; 5B2DB17127AF8771006D874E /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = Data/Makefile; sourceTree = "<group>"; };
5B2E009328FD1E8100E78D6E /* VwrPrefPaneCassette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VwrPrefPaneCassette.swift; sourceTree = "<group>"; }; 5B2E009328FD1E8100E78D6E /* VwrPrefPaneCassette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VwrPrefPaneCassette.swift; sourceTree = "<group>"; };
5B2F2BB3286216A500B8557B /* vChewingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = vChewingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5B2F2BB3286216A500B8557B /* vChewingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = vChewingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
5B30BF272944867800BD87A9 /* CtlRevLookupWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CtlRevLookupWindow.swift; sourceTree = "<group>"; };
5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = vChewingKeyLayout.bundle; sourceTree = "<group>"; }; 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = vChewingKeyLayout.bundle; sourceTree = "<group>"; };
5B312684287800DE001AA720 /* FAQ.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = FAQ.md; sourceTree = "<group>"; }; 5B312684287800DE001AA720 /* FAQ.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = FAQ.md; sourceTree = "<group>"; };
5B3133BE280B229700A4A505 /* InputHandler_HandleStates.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = InputHandler_HandleStates.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; }; 5B3133BE280B229700A4A505 /* InputHandler_HandleStates.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = InputHandler_HandleStates.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
@ -470,6 +472,7 @@
5B0EF55E28CDBF8E00F8F7CE /* CtlClientListMgr.swift */, 5B0EF55E28CDBF8E00F8F7CE /* CtlClientListMgr.swift */,
D47F7DCD278BFB57002F9DD7 /* CtlPrefWindow.swift */, D47F7DCD278BFB57002F9DD7 /* CtlPrefWindow.swift */,
5BCC631529407BBB00A2D84F /* CtlPrefWindow_PhraseEditor.swift */, 5BCC631529407BBB00A2D84F /* CtlPrefWindow_PhraseEditor.swift */,
5B30BF272944867800BD87A9 /* CtlRevLookupWindow.swift */,
); );
path = WindowControllers; path = WindowControllers;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1090,6 +1093,7 @@
5B78EE0D28A562B4009456C1 /* VwrPrefPaneDevZone.swift in Sources */, 5B78EE0D28A562B4009456C1 /* VwrPrefPaneDevZone.swift in Sources */,
5B6C141228A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift in Sources */, 5B6C141228A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift in Sources */,
D47F7DCE278BFB57002F9DD7 /* CtlPrefWindow.swift in Sources */, D47F7DCE278BFB57002F9DD7 /* CtlPrefWindow.swift in Sources */,
5B30BF282944867800BD87A9 /* CtlRevLookupWindow.swift in Sources */,
5BD0113D2818543900609769 /* InputHandler_Core.swift in Sources */, 5BD0113D2818543900609769 /* InputHandler_Core.swift in Sources */,
5BF56F9A28C39D1800DD6839 /* IMEStateData.swift in Sources */, 5BF56F9A28C39D1800DD6839 /* IMEStateData.swift in Sources */,
5B21176C287539BB000443A9 /* SessionCtl_HandleStates.swift in Sources */, 5B21176C287539BB000443A9 /* SessionCtl_HandleStates.swift in Sources */,