Merge pull request #216 from zonble/dev/lm_management
Introduces custom excluded phrases
This commit is contained in:
commit
fb513f51b0
|
@ -1,4 +1,5 @@
|
|||
build
|
||||
.build
|
||||
*.pbxuser
|
||||
*.mode1v3
|
||||
*.tm_build_errors
|
||||
|
|
|
@ -36,6 +36,8 @@
|
|||
6AE210B215FC63CC003659FE /* PlainBopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6AE210B015FC63CC003659FE /* PlainBopomofo.tiff */; };
|
||||
6AE210B315FC63CC003659FE /* PlainBopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6AE210B115FC63CC003659FE /* PlainBopomofo@2x.tiff */; };
|
||||
6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */; };
|
||||
D41355D8278D74B5005E5CBD /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = D41355D7278D7409005E5CBD /* LanguageModelManager.mm */; };
|
||||
D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D41355D9278E6D17005E5CBD /* McBopomofoLM.cpp */; };
|
||||
D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */; };
|
||||
D427F76A278C9E29004A2160 /* CandidateUI in Frameworks */ = {isa = PBXBuildFile; productRef = D427F769278C9E29004A2160 /* CandidateUI */; };
|
||||
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427F76B278CA1BA004A2160 /* AppDelegate.swift */; };
|
||||
|
@ -155,6 +157,10 @@
|
|||
6AE210B015FC63CC003659FE /* PlainBopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = PlainBopomofo.tiff; sourceTree = "<group>"; };
|
||||
6AE210B115FC63CC003659FE /* PlainBopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "PlainBopomofo@2x.tiff"; sourceTree = "<group>"; };
|
||||
6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NonModalAlertWindowController.xib; sourceTree = "<group>"; };
|
||||
D41355D6278D7409005E5CBD /* LanguageModelManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LanguageModelManager.h; sourceTree = "<group>"; };
|
||||
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LanguageModelManager.mm; sourceTree = "<group>"; };
|
||||
D41355D9278E6D17005E5CBD /* McBopomofoLM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = McBopomofoLM.cpp; sourceTree = "<group>"; };
|
||||
D41355DA278E6D17005E5CBD /* McBopomofoLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = McBopomofoLM.h; sourceTree = "<group>"; };
|
||||
D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "McBopomofo-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenCCBridge.swift; sourceTree = "<group>"; };
|
||||
D427F768278C9D0D004A2160 /* CandidateUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = CandidateUI; path = Packages/CandidateUI; sourceTree = "<group>"; };
|
||||
|
@ -228,6 +234,8 @@
|
|||
6A0D4F4715FC0EB900ABF4B3 /* Resources */,
|
||||
6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */,
|
||||
6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */,
|
||||
D41355D6278D7409005E5CBD /* LanguageModelManager.h */,
|
||||
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */,
|
||||
6A0D4EC815FC0D6400ABF4B3 /* main.m */,
|
||||
D427F76B278CA1BA004A2160 /* AppDelegate.swift */,
|
||||
D47F7DD4278C25A0002F9DD7 /* InputSourceHelper.swift */,
|
||||
|
@ -262,6 +270,8 @@
|
|||
6A0421A715FEF3F50061ED63 /* FastLM.h */,
|
||||
D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */,
|
||||
D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */,
|
||||
D41355D9278E6D17005E5CBD /* McBopomofoLM.cpp */,
|
||||
D41355DA278E6D17005E5CBD /* McBopomofoLM.h */,
|
||||
);
|
||||
path = Engine;
|
||||
sourceTree = "<group>";
|
||||
|
@ -548,9 +558,11 @@
|
|||
D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */,
|
||||
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */,
|
||||
6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */,
|
||||
D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */,
|
||||
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */,
|
||||
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */,
|
||||
6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */,
|
||||
D41355D8278D74B5005E5CBD /* LanguageModelManager.mm in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -34,14 +34,14 @@
|
|||
|
||||
import Cocoa
|
||||
|
||||
@objc(VTCandidateControllerDelegate)
|
||||
@objc (VTCandidateControllerDelegate)
|
||||
public protocol CandidateControllerDelegate: AnyObject {
|
||||
func candidateCountForController(_ controller: CandidateController) -> UInt
|
||||
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String
|
||||
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt)
|
||||
}
|
||||
|
||||
@objc(VTCandidateController)
|
||||
@objc (VTCandidateController)
|
||||
public class CandidateController: NSWindowController {
|
||||
@objc public weak var delegate: CandidateControllerDelegate?
|
||||
@objc public var selectedCandidateIndex: UInt = UInt.max
|
||||
|
@ -95,7 +95,7 @@ public class CandidateController: NSWindowController {
|
|||
UInt.max
|
||||
}
|
||||
|
||||
@objc(setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:)
|
||||
@objc (setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:)
|
||||
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) {
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
|
||||
self.doSet(windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height)
|
||||
|
@ -110,9 +110,9 @@ public class CandidateController: NSWindowController {
|
|||
for screen in NSScreen.screens {
|
||||
let frame = screen.visibleFrame
|
||||
if windowTopLeftPoint.x >= frame.minX &&
|
||||
windowTopLeftPoint.x <= frame.maxX &&
|
||||
windowTopLeftPoint.y >= frame.minY &&
|
||||
windowTopLeftPoint.y <= frame.maxY {
|
||||
windowTopLeftPoint.x <= frame.maxX &&
|
||||
windowTopLeftPoint.y >= frame.minY &&
|
||||
windowTopLeftPoint.y <= frame.maxY {
|
||||
screenFrame = frame
|
||||
break
|
||||
}
|
||||
|
|
|
@ -44,8 +44,8 @@ fileprivate class HorizontalCandidateView: NSView {
|
|||
private var keyLabelHeight: CGFloat = 0
|
||||
private var candidateTextHeight: CGFloat = 0
|
||||
private var cellPadding: CGFloat = 0
|
||||
private var keyLabelAttrDict: [NSAttributedString.Key:AnyObject] = [:]
|
||||
private var candidateAttrDict: [NSAttributedString.Key:AnyObject] = [:]
|
||||
private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:]
|
||||
private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:]
|
||||
private var elementWidths: [CGFloat] = []
|
||||
private var trackingHighlightedIndex: UInt = UInt.max
|
||||
|
||||
|
@ -64,7 +64,7 @@ fileprivate class HorizontalCandidateView: NSView {
|
|||
return result
|
||||
}
|
||||
|
||||
@objc(setKeyLabels:displayedCandidates:)
|
||||
@objc (setKeyLabels:displayedCandidates:)
|
||||
func set(keyLabels labels: [String], displayedCandidates candidates: [String]) {
|
||||
let count = min(labels.count, candidates.count)
|
||||
keyLabels = Array(labels[0..<count])
|
||||
|
@ -81,7 +81,7 @@ fileprivate class HorizontalCandidateView: NSView {
|
|||
elementWidths = newWidths
|
||||
}
|
||||
|
||||
@objc(setKeyLabelFont:candidateFont:)
|
||||
@objc (setKeyLabelFont:candidateFont:)
|
||||
func set(keyLabelFont labelFont: NSFont, candidateFont: NSFont) {
|
||||
let paraStyle = NSMutableParagraphStyle()
|
||||
paraStyle.setParagraphStyle(NSParagraphStyle.default)
|
||||
|
@ -192,8 +192,8 @@ fileprivate class HorizontalCandidateView: NSView {
|
|||
}
|
||||
}
|
||||
|
||||
@objc(VTHorizontalCandidateController)
|
||||
public class HorizontalCandidateController : CandidateController {
|
||||
@objc (VTHorizontalCandidateController)
|
||||
public class HorizontalCandidateController: CandidateController {
|
||||
private var candidateView: HorizontalCandidateView
|
||||
private var prevPageButton: NSButton
|
||||
private var nextPageButton: NSButton
|
||||
|
@ -312,9 +312,9 @@ public class HorizontalCandidateController : CandidateController {
|
|||
return result < delegate.candidateCountForController(self) ? result : UInt.max
|
||||
}
|
||||
|
||||
@objc public override var selectedCandidateIndex: UInt {
|
||||
public override var selectedCandidateIndex: UInt {
|
||||
get {
|
||||
return currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex
|
||||
currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex
|
||||
}
|
||||
set {
|
||||
guard let delegate = delegate else {
|
||||
|
@ -362,7 +362,7 @@ extension HorizontalCandidateController {
|
|||
frameRect.size = newSize
|
||||
candidateView.frame = frameRect
|
||||
|
||||
if self.pageCount > 1 {
|
||||
if pageCount > 1 {
|
||||
var buttonRect = nextPageButton.frame
|
||||
var spacing = 0.0
|
||||
|
||||
|
@ -397,7 +397,7 @@ extension HorizontalCandidateController {
|
|||
frameRect.size = newSize
|
||||
frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height)
|
||||
self.window?.setFrame(frameRect, display: false)
|
||||
self.candidateView.setNeedsDisplay(candidateView.bounds)
|
||||
candidateView.setNeedsDisplay(candidateView.bounds)
|
||||
}
|
||||
|
||||
@objc fileprivate func pageButtonAction(_ sender: Any) {
|
||||
|
@ -407,12 +407,12 @@ extension HorizontalCandidateController {
|
|||
if sender == nextPageButton {
|
||||
_ = showNextPage()
|
||||
} else if sender == prevPageButton {
|
||||
_ = showPreviousPage()
|
||||
_ = showPreviousPage()
|
||||
}
|
||||
}
|
||||
|
||||
@objc fileprivate func candidateViewMouseDidClick(_ sender: Any) {
|
||||
delegate?.candidateController(self, didSelectCandidateAtIndex: self.selectedCandidateIndex)
|
||||
delegate?.candidateController(self, didSelectCandidateAtIndex: selectedCandidateIndex)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ fileprivate class VerticalKeyLabelStripView: NSView {
|
|||
paraStyle.setParagraphStyle(NSParagraphStyle.default)
|
||||
paraStyle.alignment = .center
|
||||
|
||||
let textAttr: [NSAttributedString.Key:AnyObject] = [
|
||||
let textAttr: [NSAttributedString.Key: AnyObject] = [
|
||||
.font: keyLabelFont,
|
||||
.foregroundColor: black,
|
||||
.paragraphStyle: paraStyle]
|
||||
|
@ -97,7 +97,7 @@ private let kCandidateTextPaddingWithMandatedTableViewPadding = 18.0
|
|||
private let kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0
|
||||
|
||||
|
||||
@objc(VTVerticalCandidateController)
|
||||
@objc (VTVerticalCandidateController)
|
||||
public class VerticalCandidateController: CandidateController {
|
||||
private var keyLabelStripView: VerticalKeyLabelStripView
|
||||
private var scrollView: NSScrollView
|
||||
|
@ -170,7 +170,7 @@ public class VerticalCandidateController: CandidateController {
|
|||
public override func reloadData() {
|
||||
maxCandidateAttrStringWidth = ceil(candidateFont.pointSize * 2.0 + candidateTextPadding)
|
||||
tableView.reloadData()
|
||||
self.layoutCandidateView()
|
||||
layoutCandidateView()
|
||||
if delegate?.candidateCountForController(self) ?? 0 > 0 {
|
||||
selectedCandidateIndex = 0
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ public class VerticalCandidateController: CandidateController {
|
|||
return UInt.max
|
||||
}
|
||||
|
||||
@objc public override var selectedCandidateIndex: UInt {
|
||||
public override var selectedCandidateIndex: UInt {
|
||||
get {
|
||||
let selectedRow = tableView.selectedRow
|
||||
return selectedRow == -1 ? UInt.max : UInt(selectedRow)
|
||||
|
@ -270,7 +270,7 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat
|
|||
// we do more work than what this method is expected to; normally not a good practice, but for the amount of data (9 to 10 rows max), we can afford the overhead
|
||||
|
||||
// expand the window width if text overflows
|
||||
let boundingRect = attrString.boundingRect(with: NSSize(width: 10240.0, height: 10240.0) , options: .usesLineFragmentOrigin)
|
||||
let boundingRect = attrString.boundingRect(with: NSSize(width: 10240.0, height: 10240.0), options: .usesLineFragmentOrigin)
|
||||
let textWidth = boundingRect.size.width + candidateTextPadding
|
||||
if textWidth > maxCandidateAttrStringWidth {
|
||||
maxCandidateAttrStringWidth = textWidth
|
||||
|
@ -285,7 +285,7 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat
|
|||
var newHilightIndex = 0
|
||||
|
||||
if keyLabelStripView.highlightedIndex != -1 &&
|
||||
(row >= selectedRow + Int(count) || (selectedRow > count && row <= selectedRow - Int(count))) {
|
||||
(row >= selectedRow + Int(count) || (selectedRow > count && row <= selectedRow - Int(count))) {
|
||||
newHilightIndex = -1
|
||||
} else {
|
||||
let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin)
|
||||
|
@ -388,11 +388,11 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat
|
|||
|
||||
private func layoutCandidateView() {
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { [self] in
|
||||
self.doLayoutCanaditeView()
|
||||
doLayoutCandidateView()
|
||||
}
|
||||
}
|
||||
|
||||
private func doLayoutCanaditeView() {
|
||||
private func doLayoutCandidateView() {
|
||||
guard let delegate = delegate else {
|
||||
return
|
||||
}
|
||||
|
@ -428,7 +428,7 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat
|
|||
tableView.rowHeight = rowHeight
|
||||
|
||||
var maxKeyLabelWidth = keyLabelFontSize
|
||||
let textAttr: [NSAttributedString.Key:AnyObject] = [.font: keyLabelFont]
|
||||
let textAttr: [NSAttributedString.Key: AnyObject] = [.font: keyLabelFont]
|
||||
let boundingBox = NSSize(width: 1600.0, height: 1600.0)
|
||||
|
||||
for label in keyLabels {
|
||||
|
|
|
@ -42,7 +42,7 @@ private let kUpdateInfoSiteKey = "UpdateInfoSite"
|
|||
private let kNextCheckInterval: TimeInterval = 86400.0
|
||||
private let kTimeoutInterval: TimeInterval = 60.0
|
||||
|
||||
@objc(AppDelegate)
|
||||
@objc (AppDelegate)
|
||||
class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControllerDelegate {
|
||||
|
||||
@IBOutlet weak var window: NSWindow?
|
||||
|
@ -51,8 +51,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
|
|||
private var updateNextStepURL: URL?
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
LTLoadLanguageModel()
|
||||
LTLoadUserLanguageModelFile()
|
||||
LanguageModelManager.loadDataModels()
|
||||
LanguageModelManager.loadUserPhrasesModel()
|
||||
|
||||
if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil {
|
||||
UserDefaults.standard.set(true, forKey: kCheckUpdateAutomatically)
|
||||
|
@ -69,12 +69,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
|
|||
preferencesWindowController?.window?.orderFront(self)
|
||||
}
|
||||
|
||||
@objc(checkForUpdate)
|
||||
@objc (checkForUpdate)
|
||||
func checkForUpdate() {
|
||||
checkForUpdate(forced: false)
|
||||
}
|
||||
|
||||
@objc(checkForUpdateForced:)
|
||||
@objc (checkForUpdateForced:)
|
||||
func checkForUpdate(forced: Bool) {
|
||||
|
||||
if checkTask != nil {
|
||||
|
@ -98,15 +98,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
|
|||
UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey)
|
||||
|
||||
guard let infoDict = Bundle.main.infoDictionary,
|
||||
let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String,
|
||||
let updateInfoURL = URL(string: updateInfoURLString) else {
|
||||
let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String,
|
||||
let updateInfoURL = URL(string: updateInfoURLString) else {
|
||||
return
|
||||
}
|
||||
|
||||
let request = URLRequest(url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: kTimeoutInterval)
|
||||
|
||||
func showNoUpdateAvailableAlert() {
|
||||
NonModalAlertWindowController.shared.show(title: NSLocalizedString("Check for Update Completed", comment: ""), content: NSLocalizedString("You are already using the latest version of McBopomofo.", comment: ""), confirmButtonTitle: NSLocalizedString("OK", comment: "") , cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
|
||||
NonModalAlertWindowController.shared.show(title: NSLocalizedString("Check for Update Completed", comment: ""), content: NSLocalizedString("You are already using the latest version of McBopomofo.", comment: ""), confirmButtonTitle: NSLocalizedString("OK", comment: ""), cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
|
||||
}
|
||||
|
||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
|
@ -121,7 +121,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
|
|||
let buttonTitle = NSLocalizedString("Dismiss", comment: "")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NonModalAlertWindowController.shared.show(title:title , content: content, confirmButtonTitle: buttonTitle, cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
|
||||
NonModalAlertWindowController.shared.show(title: title, content: content, confirmButtonTitle: buttonTitle, cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
@ -131,7 +131,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
|
|||
guard let plist = try PropertyListSerialization.propertyList(from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any],
|
||||
let remoteVersion = plist[kCFBundleVersionKey] as? String,
|
||||
let infoDict = Bundle.main.infoDictionary
|
||||
else {
|
||||
else {
|
||||
if forced {
|
||||
DispatchQueue.main.async {
|
||||
showNoUpdateAvailableAlert()
|
||||
|
@ -156,7 +156,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
|
|||
}
|
||||
|
||||
guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String,
|
||||
let siteInfoURL = URL(string: siteInfoURLString) else {
|
||||
let siteInfoURL = URL(string: siteInfoURLString) else {
|
||||
if forced {
|
||||
DispatchQueue.main.async {
|
||||
showNoUpdateAvailableAlert()
|
||||
|
@ -189,7 +189,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
|
|||
remoteVersion,
|
||||
versionDescription)
|
||||
DispatchQueue.main.async {
|
||||
NonModalAlertWindowController.shared.show(title: NSLocalizedString("New Version Available", comment: "") , content: content, confirmButtonTitle: NSLocalizedString("Visit Website", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self)
|
||||
NonModalAlertWindowController.shared.show(title: NSLocalizedString("New Version Available", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("Visit Website", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self)
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
|
|
@ -285,7 +285,7 @@ const vector<Bigram> FastLM::bigramsForKeys(const string& preceedingKey, const s
|
|||
return vector<Bigram>();
|
||||
}
|
||||
|
||||
const vector<Unigram> FastLM::unigramsForKeys(const string& key)
|
||||
const vector<Unigram> FastLM::unigramsForKey(const string& key)
|
||||
{
|
||||
vector<Unigram> v;
|
||||
map<const char *, vector<Row> >::const_iterator i = keyRowMap.find(key.c_str());
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace Formosa {
|
|||
void dump();
|
||||
|
||||
virtual const vector<Bigram> bigramsForKeys(const string& preceedingKey, const string& key);
|
||||
virtual const vector<Unigram> unigramsForKeys(const string& key);
|
||||
virtual const vector<Unigram> unigramsForKey(const string& key);
|
||||
virtual bool hasUnigramsForKey(const string& key);
|
||||
|
||||
protected:
|
||||
|
|
|
@ -38,7 +38,7 @@ namespace Formosa {
|
|||
|
||||
class BlockReadingBuilder {
|
||||
public:
|
||||
BlockReadingBuilder(LanguageModel *inLM, LanguageModel *inUserPhraseLM);
|
||||
BlockReadingBuilder(LanguageModel *inLM);
|
||||
void clear();
|
||||
|
||||
size_t length() const;
|
||||
|
@ -73,13 +73,11 @@ namespace Formosa {
|
|||
|
||||
Grid m_grid;
|
||||
LanguageModel *m_LM;
|
||||
LanguageModel *m_UserPhraseLM;
|
||||
string m_joinSeparator;
|
||||
};
|
||||
|
||||
inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM, LanguageModel *inUserPhraseLM)
|
||||
inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM)
|
||||
: m_LM(inLM)
|
||||
, m_UserPhraseLM(inUserPhraseLM)
|
||||
, m_cursorIndex(0)
|
||||
, m_markerCursorIndex(SIZE_MAX)
|
||||
{
|
||||
|
@ -197,7 +195,7 @@ namespace Formosa {
|
|||
{
|
||||
return m_grid;
|
||||
}
|
||||
|
||||
|
||||
inline void BlockReadingBuilder::build()
|
||||
{
|
||||
if (!m_LM) {
|
||||
|
@ -222,19 +220,7 @@ namespace Formosa {
|
|||
for (size_t q = 1 ; q <= MaximumBuildSpanLength && p+q <= end ; q++) {
|
||||
string combinedReading = Join(m_readings.begin() + p, m_readings.begin() + p + q, m_joinSeparator);
|
||||
if (!m_grid.hasNodeAtLocationSpanningLengthMatchingKey(p, q, combinedReading)) {
|
||||
vector<Unigram> unigrams;
|
||||
|
||||
if (m_UserPhraseLM != NULL) {
|
||||
if (m_UserPhraseLM->hasUnigramsForKey(combinedReading)) {
|
||||
vector<Unigram> userUnigrams = m_UserPhraseLM->unigramsForKeys(combinedReading);
|
||||
unigrams.insert(unigrams.end(), userUnigrams.begin(), userUnigrams.end());
|
||||
}
|
||||
}
|
||||
|
||||
if (m_LM->hasUnigramsForKey(combinedReading)) {
|
||||
vector<Unigram> globalUnigrams = m_LM->unigramsForKeys(combinedReading);
|
||||
unigrams.insert(unigrams.end(), globalUnigrams.begin(), globalUnigrams.end());
|
||||
}
|
||||
vector<Unigram> unigrams = m_LM->unigramsForKey(combinedReading);
|
||||
|
||||
if (unigrams.size() > 0) {
|
||||
Node n(combinedReading, unigrams, vector<Bigram>());
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace Formosa {
|
|||
virtual ~LanguageModel() {}
|
||||
|
||||
virtual const vector<Bigram> bigramsForKeys(const string &preceedingKey, const string& key) = 0;
|
||||
virtual const vector<Unigram> unigramsForKeys(const string &key) = 0;
|
||||
virtual const vector<Unigram> unigramsForKey(const string &key) = 0;
|
||||
virtual bool hasUnigramsForKey(const string& key) = 0;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
#include "McBopomofoLM.h"
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace McBopomofo;
|
||||
|
||||
McBopomofoLM::McBopomofoLM()
|
||||
{
|
||||
}
|
||||
|
||||
McBopomofoLM::~McBopomofoLM()
|
||||
{
|
||||
m_languageModel.close();
|
||||
m_userPhrases.close();
|
||||
m_excludedPhrases.close();
|
||||
}
|
||||
|
||||
void McBopomofoLM::loadLanguageModel(const char* languageModelDataPath)
|
||||
{
|
||||
if (languageModelDataPath) {
|
||||
m_languageModel.close();
|
||||
m_languageModel.open(languageModelDataPath);
|
||||
}
|
||||
}
|
||||
|
||||
void McBopomofoLM::loadUserPhrases(const char* userPhrasesDataPath,
|
||||
const char* excludedPhrasesDataPath)
|
||||
{
|
||||
if (userPhrasesDataPath) {
|
||||
m_userPhrases.close();
|
||||
m_userPhrases.open(userPhrasesDataPath);
|
||||
}
|
||||
if (excludedPhrasesDataPath) {
|
||||
m_excludedPhrases.close();
|
||||
m_excludedPhrases.open(excludedPhrasesDataPath);
|
||||
}
|
||||
}
|
||||
|
||||
const vector<Bigram> McBopomofoLM::bigramsForKeys(const string& preceedingKey, const string& key)
|
||||
{
|
||||
return vector<Bigram>();
|
||||
}
|
||||
|
||||
const vector<Unigram> McBopomofoLM::unigramsForKey(const string& key)
|
||||
{
|
||||
vector<Unigram> unigrams;
|
||||
vector<Unigram> userUnigrams;
|
||||
|
||||
// Use unordered_set so that you don't have to do O(n*m)
|
||||
unordered_set<string> excludedValues;
|
||||
unordered_set<string> userValues;
|
||||
|
||||
if (m_excludedPhrases.hasUnigramsForKey(key)) {
|
||||
vector<Unigram> excludedUnigrams = m_excludedPhrases.unigramsForKey(key);
|
||||
transform(excludedUnigrams.begin(), excludedUnigrams.end(),
|
||||
inserter(excludedValues, excludedValues.end()),
|
||||
[](const Unigram &u) { return u.keyValue.value; });
|
||||
}
|
||||
|
||||
if (m_userPhrases.hasUnigramsForKey(key)) {
|
||||
vector<Unigram> rawUserUnigrams = m_userPhrases.unigramsForKey(key);
|
||||
|
||||
for (auto&& unigram : rawUserUnigrams) {
|
||||
if (excludedValues.find(unigram.keyValue.value) == excludedValues.end()) {
|
||||
userUnigrams.push_back(unigram);
|
||||
}
|
||||
}
|
||||
|
||||
transform(userUnigrams.begin(), userUnigrams.end(),
|
||||
inserter(userValues, userValues.end()),
|
||||
[](const Unigram &u) { return u.keyValue.value; });
|
||||
}
|
||||
|
||||
if (m_languageModel.hasUnigramsForKey(key)) {
|
||||
vector<Unigram> globalUnigrams = m_languageModel.unigramsForKey(key);
|
||||
|
||||
for (auto&& unigram : globalUnigrams) {
|
||||
if (excludedValues.find(unigram.keyValue.value) == excludedValues.end() &&
|
||||
userValues.find(unigram.keyValue.value) == userValues.end()) {
|
||||
unigrams.push_back(unigram);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unigrams.insert(unigrams.begin(), userUnigrams.begin(), userUnigrams.end());
|
||||
return unigrams;
|
||||
}
|
||||
|
||||
bool McBopomofoLM::hasUnigramsForKey(const string& key)
|
||||
{
|
||||
if (!m_excludedPhrases.hasUnigramsForKey(key)) {
|
||||
return m_userPhrases.hasUnigramsForKey(key) ||
|
||||
m_languageModel.hasUnigramsForKey(key);
|
||||
}
|
||||
|
||||
return unigramsForKey(key).size() > 0;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef MCBOPOMOFOLM_H
|
||||
#define MCBOPOMOFOLM_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include "FastLM.h"
|
||||
|
||||
namespace McBopomofo {
|
||||
|
||||
using namespace Formosa::Gramambular;
|
||||
|
||||
class McBopomofoLM : public LanguageModel {
|
||||
public:
|
||||
McBopomofoLM();
|
||||
~McBopomofoLM();
|
||||
|
||||
void loadLanguageModel(const char* languageModelDataPath);
|
||||
void loadUserPhrases(const char* m_userPhrasesDataPath,
|
||||
const char* m_excludedPhrasesDataPath);
|
||||
|
||||
const vector<Bigram> bigramsForKeys(const string& preceedingKey, const string& key);
|
||||
const vector<Unigram> unigramsForKey(const string& key);
|
||||
bool hasUnigramsForKey(const string& key);
|
||||
|
||||
protected:
|
||||
FastLM m_languageModel;
|
||||
FastLM m_userPhrases;
|
||||
FastLM m_excludedPhrases;
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
|
@ -36,7 +36,7 @@
|
|||
#import <InputMethodKit/InputMethodKit.h>
|
||||
#import "Mandarin.h"
|
||||
#import "Gramambular.h"
|
||||
#import "FastLM.h"
|
||||
#import "McBopomofoLM.h"
|
||||
#import "UserOverrideModel.h"
|
||||
|
||||
@interface McBopomofoInputMethodController : IMKInputController
|
||||
|
@ -46,8 +46,10 @@
|
|||
Formosa::Mandarin::BopomofoReadingBuffer* _bpmfReadingBuffer;
|
||||
|
||||
// language model
|
||||
Formosa::Gramambular::FastLM *_languageModel;
|
||||
Formosa::Gramambular::FastLM *_userPhrasesModel;
|
||||
McBopomofo::McBopomofoLM *_languageModel;
|
||||
|
||||
// user override model
|
||||
McBopomofo::UserOverrideModel *_userOverrideModel;
|
||||
|
||||
// the grid (lattice) builder for the unigrams (and bigrams)
|
||||
Formosa::Gramambular::BlockReadingBuilder* _builder;
|
||||
|
@ -55,9 +57,6 @@
|
|||
// latest walked path (trellis) using the Viterbi algorithm
|
||||
std::vector<Formosa::Gramambular::NodeAnchor> _walkedNodes;
|
||||
|
||||
// user override model
|
||||
McBopomofo::UserOverrideModel *_uom;
|
||||
|
||||
// the latest composing buffer that is updated to the foreground app
|
||||
NSMutableString *_composingBuffer;
|
||||
NSInteger _latestReadingCursor;
|
||||
|
@ -68,7 +67,7 @@
|
|||
// a special deferred client for Terminal.app fix
|
||||
id _currentDeferredClient;
|
||||
|
||||
// currently available candidates
|
||||
// current available candidates
|
||||
NSMutableArray *_candidates;
|
||||
|
||||
// current input mode
|
||||
|
@ -78,7 +77,3 @@
|
|||
BOOL _chineseConversionEnabled;
|
||||
}
|
||||
@end
|
||||
|
||||
// the shared language model object
|
||||
extern "C" void LTLoadLanguageModel();
|
||||
extern "C" void LTLoadUserLanguageModelFile();
|
||||
|
|
|
@ -38,17 +38,17 @@
|
|||
#import <set>
|
||||
#import "OVStringHelper.h"
|
||||
#import "OVUTF8Helper.h"
|
||||
#import "LanguageModelManager.h"
|
||||
#import "McBopomofo-Swift.h"
|
||||
|
||||
@import CandidateUI;
|
||||
@import OpenCC;
|
||||
|
||||
//@import SwiftUI;
|
||||
|
||||
// C++ namespace usages
|
||||
using namespace std;
|
||||
using namespace Formosa::Mandarin;
|
||||
using namespace Formosa::Gramambular;
|
||||
using namespace McBopomofo;
|
||||
using namespace OpenVanilla;
|
||||
|
||||
// default, min and max candidate list text size
|
||||
|
@ -111,62 +111,6 @@ VTCandidateController *gCurrentCandidateController = nil;
|
|||
static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot";
|
||||
#endif
|
||||
|
||||
// shared language model object that stores our phrase-term probability database
|
||||
FastLM gLanguageModel;
|
||||
FastLM gLanguageModelPlainBopomofo;
|
||||
FastLM gUserPhraseLanguageModel;
|
||||
|
||||
static const int kUserOverrideModelCapacity = 500;
|
||||
static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr.
|
||||
McBopomofo::UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife);
|
||||
|
||||
static NSString *LTUserDataFolderPath()
|
||||
{
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES);
|
||||
NSString *appSupportPath = [paths objectAtIndex:0];
|
||||
NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"McBopomofo"];
|
||||
return userDictPath;
|
||||
}
|
||||
|
||||
static NSString *LTUserPhrasesDataPath()
|
||||
{
|
||||
return [LTUserDataFolderPath() stringByAppendingPathComponent:@"data.txt"];
|
||||
}
|
||||
|
||||
static BOOL LTCheckIfUserLanguageModelFileExists() {
|
||||
|
||||
NSString *folderPath = LTUserDataFolderPath();
|
||||
BOOL isFolder = NO;
|
||||
BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder];
|
||||
if (folderExist && !isFolder) {
|
||||
NSError *error = nil;
|
||||
[[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Failed to remove folder %@", error);
|
||||
return NO;
|
||||
}
|
||||
folderExist = NO;
|
||||
}
|
||||
if (!folderExist) {
|
||||
NSError *error = nil;
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Failed to create folder %@", error);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *filePath = LTUserPhrasesDataPath();
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
|
||||
BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:YES];
|
||||
if (!result) {
|
||||
NSLog(@"Failed to write file");
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
// https://clang-analyzer.llvm.org/faq.html
|
||||
__attribute__((annotate("returns_localized_nsstring")))
|
||||
static inline NSString *LocalizationNotNeeded(NSString *s) {
|
||||
|
@ -174,17 +118,12 @@ static inline NSString *LocalizationNotNeeded(NSString *s) {
|
|||
}
|
||||
|
||||
// private methods
|
||||
@interface McBopomofoInputMethodController () <VTCandidateControllerDelegate>
|
||||
@interface McBopomofoInputMethodController ()
|
||||
+ (VTHorizontalCandidateController *)horizontalCandidateController;
|
||||
+ (VTVerticalCandidateController *)verticalCandidateController;
|
||||
@end
|
||||
|
||||
- (void)collectCandidates;
|
||||
- (size_t)actualCandidateCursorIndex;
|
||||
- (void)_showCandidateWindowUsingVerticalMode:(BOOL)useVerticalMode client:(id)client;
|
||||
|
||||
- (void)beep;
|
||||
- (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client;
|
||||
- (BOOL)handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode;
|
||||
@interface McBopomofoInputMethodController (VTCandidateController) <VTCandidateControllerDelegate>
|
||||
@end
|
||||
|
||||
// sort helper
|
||||
|
@ -237,10 +176,10 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
_bpmfReadingBuffer = new BopomofoReadingBuffer(BopomofoKeyboardLayout::StandardLayout());
|
||||
|
||||
// create the lattice builder
|
||||
_languageModel = &gLanguageModel;
|
||||
_userPhrasesModel = &gUserPhraseLanguageModel;
|
||||
_builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel);
|
||||
_uom = &gUserOverrideModel;
|
||||
_languageModel = [LanguageModelManager languageModelMcBopomofo];
|
||||
_userOverrideModel = [LanguageModelManager userOverrideModel];
|
||||
|
||||
_builder = new BlockReadingBuilder(_languageModel);
|
||||
|
||||
// each Mandarin syllable is separated by a hyphen
|
||||
_builder->setJoinSeparator("-");
|
||||
|
@ -267,18 +206,24 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff;
|
||||
[menu addItem:chineseConversionMenuItem];
|
||||
|
||||
if (_inputMode != kPlainBopomofoModeIdentifier) {
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
[menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""];
|
||||
NSMenuItem *editUserPheaseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""];
|
||||
[editUserPheaseItem setIndentationLevel:2];
|
||||
[menu addItem:editUserPheaseItem];
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
[menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""];
|
||||
|
||||
NSMenuItem *reloadUserPheaseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""];
|
||||
[reloadUserPheaseItem setIndentationLevel:2];
|
||||
[menu addItem:reloadUserPheaseItem];
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
if (_inputMode == kPlainBopomofoModeIdentifier) {
|
||||
NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesPlainBopomofo:) keyEquivalent:@""];
|
||||
[menu addItem:editExcludedPhrasesItem];
|
||||
}
|
||||
else {
|
||||
NSMenuItem *editUserPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""];
|
||||
[menu addItem:editUserPhrasesItem];
|
||||
|
||||
NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesMcBopomofo:) keyEquivalent:@""];
|
||||
[menu addItem:editExcludedPhrasesItem];
|
||||
}
|
||||
|
||||
NSMenuItem *reloadUserPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""];
|
||||
[menu addItem:reloadUserPhrasesItem];
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
NSMenuItem *updateCheckItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""];
|
||||
[menu addItem:updateCheckItem];
|
||||
|
@ -379,18 +324,15 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
- (void)setValue:(id)value forTag:(long)tag client:(id)sender
|
||||
{
|
||||
NSString *newInputMode;
|
||||
Formosa::Gramambular::FastLM *newLanguageModel;
|
||||
Formosa::Gramambular::FastLM *userPhraseModel;
|
||||
McBopomofoLM *newLanguageModel;
|
||||
|
||||
if ([value isKindOfClass:[NSString class]] && [value isEqual:kPlainBopomofoModeIdentifier]) {
|
||||
newInputMode = kPlainBopomofoModeIdentifier;
|
||||
newLanguageModel = &gLanguageModelPlainBopomofo;
|
||||
userPhraseModel = NULL;
|
||||
newLanguageModel = [LanguageModelManager languageModelPlainBopomofo];
|
||||
}
|
||||
else {
|
||||
newInputMode = kBopomofoModeIdentifier;
|
||||
newLanguageModel = &gLanguageModel;
|
||||
userPhraseModel = &gUserPhraseLanguageModel;
|
||||
newLanguageModel = [LanguageModelManager languageModelMcBopomofo];
|
||||
}
|
||||
|
||||
// Only apply the changes if the value is changed
|
||||
|
@ -406,7 +348,6 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
|
||||
_inputMode = newInputMode;
|
||||
_languageModel = newLanguageModel;
|
||||
_userPhrasesModel = userPhraseModel;
|
||||
|
||||
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||
_bpmfReadingBuffer->clear();
|
||||
|
@ -419,7 +360,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
|
||||
if (_builder) {
|
||||
delete _builder;
|
||||
_builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel);
|
||||
_builder = new BlockReadingBuilder(_languageModel);
|
||||
_builder->setJoinSeparator("-");
|
||||
}
|
||||
}
|
||||
|
@ -432,8 +373,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
// if it's Terminal, we don't commit at the first call (the client of which will not be IPMDServerClientWrapper)
|
||||
// then we defer the update in the next runloop round -- so that the composing buffer is not
|
||||
// meaninglessly flushed, an annoying bug in Terminal.app since Mac OS X 10.5
|
||||
if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && ![NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"])
|
||||
{
|
||||
if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && ![NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) {
|
||||
if (_currentDeferredClient) {
|
||||
[self performSelector:@selector(updateClientComposingBuffer:) withObject:_currentDeferredClient afterDelay:0.0];
|
||||
}
|
||||
|
@ -532,7 +472,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
// i.e. the client app needs to take care of where to put ths composing buffer
|
||||
[client setMarkedText:attrString selectionRange:NSMakeRange((NSInteger)_builder->markerCursorIndex(), 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
_latestReadingCursor = (NSInteger)_builder->markerCursorIndex();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// we must use NSAttributedString so that the cursor is visible --
|
||||
// can't just use NSString
|
||||
NSDictionary *attrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
|
||||
|
@ -560,13 +501,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
reverse(_walkedNodes.begin(), _walkedNodes.end());
|
||||
|
||||
// if DEBUG is defined, a GraphViz file is written to kGraphVizOutputfile
|
||||
#if DEBUG
|
||||
#if DEBUG
|
||||
string dotDump = _builder->grid().dumpDOT();
|
||||
NSString *dotStr = [NSString stringWithUTF8String:dotDump.c_str()];
|
||||
NSError *error = nil;
|
||||
|
||||
BOOL __unused success = [dotStr writeToFile:kGraphVizOutputfile atomically:YES encoding:NSUTF8StringEncoding error:&error];
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)popOverflowComposingTextAndWalk:(id)client
|
||||
|
@ -681,29 +622,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
|
||||
- (BOOL)_writeUserPhrase
|
||||
{
|
||||
if (!LTCheckIfUserLanguageModelFileExists()) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *currentMarkedPhrase = [self _currentMarkedText];
|
||||
if (![currentMarkedPhrase length]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
currentMarkedPhrase = [currentMarkedPhrase stringByAppendingString:@"\n"];
|
||||
|
||||
NSString *path = LTUserPhrasesDataPath();
|
||||
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path];
|
||||
if (!file) {
|
||||
return NO;
|
||||
}
|
||||
[file seekToEndOfFile];
|
||||
NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding];
|
||||
[file writeData:data];
|
||||
[file closeFile];
|
||||
|
||||
LTLoadUserLanguageModelFile();
|
||||
return YES;
|
||||
return [LanguageModelManager writeUserPhrase:currentMarkedPhrase];
|
||||
}
|
||||
|
||||
- (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client
|
||||
|
@ -801,7 +725,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
if (charCode == 13) {
|
||||
if ([self _writeUserPhrase]) {
|
||||
_builder->setMarkerCursorIndex(SIZE_MAX);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
[self beep];
|
||||
}
|
||||
[self updateClientComposingBuffer:client];
|
||||
|
@ -868,9 +793,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
[self popOverflowComposingTextAndWalk:client];
|
||||
|
||||
// get user override model suggestion
|
||||
string overrideValue =
|
||||
(_inputMode == kPlainBopomofoModeIdentifier) ? "" :
|
||||
_uom->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]);
|
||||
string overrideValue = (_inputMode == kPlainBopomofoModeIdentifier) ? "" :
|
||||
_userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]);
|
||||
|
||||
if (!overrideValue.empty()) {
|
||||
size_t cursorIndex = [self actualCandidateCursorIndex];
|
||||
vector<NodeAnchor> nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex);
|
||||
|
@ -1137,8 +1062,33 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
}
|
||||
|
||||
// if nothing is matched, see if it's a punctuation key for current layout.
|
||||
string layout = [self currentLayout];
|
||||
string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode);
|
||||
if ([self handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// if nothing is matched, see if it's a punctuation key.
|
||||
string punctuation = string("_punctuation_") + string(1, (char)charCode);
|
||||
if ([self handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// still nothing, then we update the composing buffer (some app has
|
||||
// strange behavior if we don't do this, "thinking" the key is not
|
||||
// actually consumed)
|
||||
if ([_composingBuffer length] || !_bpmfReadingBuffer->isEmpty()) {
|
||||
[self beep];
|
||||
[self updateClientComposingBuffer:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client
|
||||
{
|
||||
if (_languageModel->hasUnigramsForKey(customPunctuation)) {
|
||||
if (_bpmfReadingBuffer->isEmpty()) {
|
||||
_builder->insertReadingAtCursor(customPunctuation);
|
||||
|
@ -1158,53 +1108,17 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
[self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client];
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
// if nothing is matched, see if it's a punctuation key
|
||||
string punctuation = string("_punctuation_") + string(1, (char)charCode);
|
||||
if (_languageModel->hasUnigramsForKey(punctuation)) {
|
||||
if (_bpmfReadingBuffer->isEmpty()) {
|
||||
_builder->insertReadingAtCursor(punctuation);
|
||||
[self popOverflowComposingTextAndWalk:client];
|
||||
}
|
||||
else { // If there is still unfinished bpmf reading, ignore the punctuation
|
||||
[self beep];
|
||||
}
|
||||
[self updateClientComposingBuffer:client];
|
||||
|
||||
if (_inputMode == kPlainBopomofoModeIdentifier && _bpmfReadingBuffer->isEmpty()) {
|
||||
[self collectCandidates];
|
||||
if ([_candidates count] == 1) {
|
||||
[self commitComposition:client];
|
||||
}
|
||||
else {
|
||||
[self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client];
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
// still nothing, then we update the composing buffer (some app has
|
||||
// strange behavior if we don't do this, "thinking" the key is not
|
||||
// actually consumed)
|
||||
if ([_composingBuffer length] || !_bpmfReadingBuffer->isEmpty()) {
|
||||
[self beep];
|
||||
[self updateClientComposingBuffer:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode
|
||||
{
|
||||
BOOL cancelCandidateKey =
|
||||
(charCode == 27) ||
|
||||
((_inputMode == kPlainBopomofoModeIdentifier) &&
|
||||
(charCode == 8 || keyCode == kDeleteKeyCode));
|
||||
(charCode == 27) ||
|
||||
((_inputMode == kPlainBopomofoModeIdentifier) &&
|
||||
(charCode == 8 || keyCode == kDeleteKeyCode));
|
||||
|
||||
if (cancelCandidateKey) {
|
||||
gCurrentCandidateController.visible = NO;
|
||||
|
@ -1357,7 +1271,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
string punctuation = string("_punctuation_") + string(1, (char)charCode);
|
||||
|
||||
BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((char)charCode) || _languageModel->hasUnigramsForKey(customPunctuation) ||
|
||||
_languageModel->hasUnigramsForKey(punctuation);
|
||||
_languageModel->hasUnigramsForKey(punctuation);
|
||||
|
||||
if (shouldAutoSelectCandidate) {
|
||||
NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:0];
|
||||
|
@ -1580,28 +1494,48 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
[(AppDelegate *)[[NSApplication sharedApplication] delegate] checkForUpdateForced:YES];
|
||||
}
|
||||
|
||||
- (void)openUserPhrases:(id)sender
|
||||
- (BOOL)_checkUserFiles
|
||||
{
|
||||
NSLog(@"openUserPhrases called");
|
||||
if (!LTCheckIfUserLanguageModelFileExists()) {
|
||||
NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), LTUserDataFolderPath()];
|
||||
if (![LanguageModelManager checkIfUserLanguageModelFilesExist] ) {
|
||||
NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), [LanguageModelManager dataFolderPath]];
|
||||
[[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil];
|
||||
return;
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *path = LTUserPhrasesDataPath();
|
||||
NSLog(@"Open %@", path);
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
||||
[[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:path atomically:YES];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)_openUserFile:(NSString *)path
|
||||
{
|
||||
if (![self _checkUserFiles]) {
|
||||
return;
|
||||
}
|
||||
NSURL *url = [NSURL fileURLWithPath:path];
|
||||
[[NSWorkspace sharedWorkspace] openURL:url];
|
||||
}
|
||||
|
||||
- (void)openUserPhrases:(id)sender
|
||||
{
|
||||
NSLog(@"openUserPhrases called");
|
||||
[self _openUserFile:[LanguageModelManager userPhrasesDataPathMcBopomofo]];
|
||||
}
|
||||
|
||||
- (void)openExcludedPhrasesPlainBopomofo:(id)sender
|
||||
{
|
||||
NSLog(@"openExcludedPhrasesPlainBopomofo called");
|
||||
[self _openUserFile:[LanguageModelManager excludedPhrasesDataPathPlainBopomofo]];
|
||||
}
|
||||
|
||||
- (void)openExcludedPhrasesMcBopomofo:(id)sender
|
||||
{
|
||||
NSLog(@"openExcludedPhrasesMcBopomofo called");
|
||||
[self _openUserFile:[LanguageModelManager excludedPhrasesDataPathMcBopomofo]];
|
||||
}
|
||||
|
||||
- (void)reloadUserPhrases:(id)sender
|
||||
{
|
||||
NSLog(@"reloadUserPhrases called");
|
||||
LTLoadUserLanguageModelFile();
|
||||
[LanguageModelManager loadUserPhrasesModel];
|
||||
}
|
||||
|
||||
- (void)showAbout:(id)sender
|
||||
|
@ -1616,6 +1550,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
[[NSUserDefaults standardUserDefaults] setBool:_chineseConversionEnabled forKey:kChineseConversionEnabledKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation McBopomofoInputMethodController (VTCandidateController)
|
||||
|
||||
- (NSUInteger)candidateCountForController:(VTCandidateController *)controller
|
||||
{
|
||||
return [_candidates count];
|
||||
|
@ -1636,7 +1576,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
size_t cursorIndex = [self actualCandidateCursorIndex];
|
||||
_builder->grid().fixNodeSelectedCandidate(cursorIndex, selectedValue);
|
||||
if (_inputMode != kPlainBopomofoModeIdentifier) {
|
||||
_uom->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]);
|
||||
_userOverrideModel->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]);
|
||||
}
|
||||
|
||||
[_candidates removeAllObjects];
|
||||
|
@ -1651,28 +1591,3 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
|
||||
@end
|
||||
|
||||
static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM &lm)
|
||||
{
|
||||
NSString *dataPath = [[NSBundle bundleForClass:[McBopomofoInputMethodController class]] pathForResource:filenameWithoutExtension ofType:@"txt"];
|
||||
bool result = lm.open([dataPath UTF8String]);
|
||||
if (!result) {
|
||||
NSLog(@"Failed opening language model: %@", dataPath);
|
||||
}
|
||||
}
|
||||
|
||||
void LTLoadLanguageModel()
|
||||
{
|
||||
LTLoadLanguageModelFile(@"data", gLanguageModel);
|
||||
LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo);
|
||||
}
|
||||
|
||||
|
||||
void LTLoadUserLanguageModelFile()
|
||||
{
|
||||
gUserPhraseLanguageModel.close();
|
||||
bool result = gUserPhraseLanguageModel.open([LTUserPhrasesDataPath() UTF8String]);
|
||||
if (!result) {
|
||||
NSLog(@"Failed opening language model for user phrases.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,12 +46,12 @@ public class InputSourceHelper: NSObject {
|
|||
TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource]
|
||||
}
|
||||
|
||||
@objc(inputSourceForProperty:stringValue:)
|
||||
@objc (inputSourceForProperty:stringValue:)
|
||||
public static func inputSource(for propertyKey: CFString, stringValue: String) -> TISInputSource? {
|
||||
let stringID = CFStringGetTypeID()
|
||||
for source in allInstalledInputSources() {
|
||||
if let proprtyPtr = TISGetInputSourceProperty(source, propertyKey) {
|
||||
let property = Unmanaged<CFTypeRef>.fromOpaque(proprtyPtr).takeUnretainedValue()
|
||||
if let propertyPtr = TISGetInputSourceProperty(source, propertyKey) {
|
||||
let property = Unmanaged<CFTypeRef>.fromOpaque(propertyPtr).takeUnretainedValue()
|
||||
let typeID = CFGetTypeID(property)
|
||||
if typeID != stringID {
|
||||
continue
|
||||
|
@ -64,12 +64,12 @@ public class InputSourceHelper: NSObject {
|
|||
return nil
|
||||
}
|
||||
|
||||
@objc(inputSourceForInputSourceID:)
|
||||
@objc (inputSourceForInputSourceID:)
|
||||
public static func inputSource(for sourceID: String) -> TISInputSource? {
|
||||
inputSource(for: kTISPropertyInputSourceID, stringValue: sourceID)
|
||||
}
|
||||
|
||||
@objc(inputSourceEnabled:)
|
||||
@objc (inputSourceEnabled:)
|
||||
public static func inputSourceEnabled(for source: TISInputSource) -> Bool {
|
||||
if let valuePts = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsEnabled) {
|
||||
let value = Unmanaged<CFBoolean>.fromOpaque(valuePts).takeUnretainedValue()
|
||||
|
@ -78,20 +78,20 @@ public class InputSourceHelper: NSObject {
|
|||
return false
|
||||
}
|
||||
|
||||
@objc(enableInputSource:)
|
||||
@objc (enableInputSource:)
|
||||
public static func enable(inputSource: TISInputSource) -> Bool {
|
||||
let status = TISEnableInputSource(inputSource)
|
||||
return status == noErr
|
||||
}
|
||||
|
||||
@objc(enableAllInputModesForInputSourceBundleID:)
|
||||
@objc (enableAllInputModesForInputSourceBundleID:)
|
||||
public static func enableAllInputMode(for inputSourceBundleD: String) -> Bool {
|
||||
var enabled = false
|
||||
for source in allInstalledInputSources() {
|
||||
guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID),
|
||||
let _ = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
let bundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue()
|
||||
if String(bundleID) == inputSourceBundleD {
|
||||
let modeEnabled = self.enable(inputSource: source)
|
||||
|
@ -105,13 +105,13 @@ public class InputSourceHelper: NSObject {
|
|||
return enabled
|
||||
}
|
||||
|
||||
@objc(enableInputMode:forInputSourceBundleID:)
|
||||
@objc (enableInputMode:forInputSourceBundleID:)
|
||||
public static func enable(inputMode modeID: String, for bundleID: String) -> Bool {
|
||||
for source in allInstalledInputSources() {
|
||||
guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID),
|
||||
let modePtr = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
let inputsSourceBundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue()
|
||||
let inputsSourceModeID = Unmanaged<CFString>.fromOpaque(modePtr).takeUnretainedValue()
|
||||
if modeID == String(inputsSourceModeID) && bundleID == String(inputsSourceBundleID) {
|
||||
|
@ -126,13 +126,13 @@ public class InputSourceHelper: NSObject {
|
|||
|
||||
}
|
||||
|
||||
@objc(disableInputSource:)
|
||||
@objc (disableInputSource:)
|
||||
public static func disable(inputSource: TISInputSource) -> Bool {
|
||||
let status = TISDisableInputSource(inputSource)
|
||||
return status == noErr
|
||||
}
|
||||
|
||||
@objc(registerInputSource:)
|
||||
@objc (registerInputSource:)
|
||||
public static func registerTnputSource(at url: URL) -> Bool {
|
||||
let status = TISRegisterInputSource(url as CFURL)
|
||||
return status == noErr
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import "FastLM.h"
|
||||
#import "UserOverrideModel.h"
|
||||
#import "McBopomofoLM.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface LanguageModelManager : NSObject
|
||||
|
||||
+ (void)loadDataModels;
|
||||
+ (void)loadUserPhrasesModel;
|
||||
+ (BOOL)checkIfUserLanguageModelFilesExist;
|
||||
+ (BOOL)writeUserPhrase:(NSString *)userPhrase;
|
||||
|
||||
@property (class, readonly, nonatomic) NSString *dataFolderPath;
|
||||
@property (class, readonly, nonatomic) NSString *userPhrasesDataPathMcBopomofo;
|
||||
@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathMcBopomofo;
|
||||
@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathPlainBopomofo;
|
||||
@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelMcBopomofo;
|
||||
@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelPlainBopomofo;
|
||||
@property (class, readonly, nonatomic) McBopomofo::UserOverrideModel *userOverrideModel;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,155 @@
|
|||
#import "LanguageModelManager.h"
|
||||
#import <fstream>
|
||||
#import <iostream>
|
||||
#import <set>
|
||||
#import "OVStringHelper.h"
|
||||
#import "OVUTF8Helper.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace Formosa::Gramambular;
|
||||
using namespace McBopomofo;
|
||||
using namespace OpenVanilla;
|
||||
|
||||
static const int kUserOverrideModelCapacity = 500;
|
||||
static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr.
|
||||
|
||||
McBopomofoLM gLanguageModelMcBopomofo;
|
||||
McBopomofoLM gLanguageModelPlainBopomofo;
|
||||
UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife);
|
||||
|
||||
@implementation LanguageModelManager
|
||||
|
||||
static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomofoLM &lm)
|
||||
{
|
||||
Class cls = NSClassFromString(@"McBopomofoInputMethodController");
|
||||
NSString *dataPath = [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"];
|
||||
lm.loadLanguageModel([dataPath UTF8String]);
|
||||
}
|
||||
|
||||
+ (void)loadDataModels
|
||||
{
|
||||
LTLoadLanguageModelFile(@"data", gLanguageModelMcBopomofo);
|
||||
LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo);
|
||||
}
|
||||
|
||||
+ (void)loadUserPhrasesModel
|
||||
{
|
||||
gLanguageModelMcBopomofo.loadUserPhrases([[self userPhrasesDataPathMcBopomofo] UTF8String], [[self excludedPhrasesDataPathMcBopomofo] UTF8String]);
|
||||
gLanguageModelPlainBopomofo.loadUserPhrases(NULL, [[self excludedPhrasesDataPathPlainBopomofo] UTF8String]);
|
||||
}
|
||||
|
||||
+ (BOOL)checkIfUserDataFolderExists
|
||||
{
|
||||
NSString *folderPath = [self dataFolderPath];
|
||||
BOOL isFolder = NO;
|
||||
BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder];
|
||||
if (folderExist && !isFolder) {
|
||||
NSError *error = nil;
|
||||
[[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Failed to remove folder %@", error);
|
||||
return NO;
|
||||
}
|
||||
folderExist = NO;
|
||||
}
|
||||
if (!folderExist) {
|
||||
NSError *error = nil;
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Failed to create folder %@", error);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)checkIfFileExist:(NSString *)filePath
|
||||
{
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
|
||||
BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:YES];
|
||||
if (!result) {
|
||||
NSLog(@"Failed to write file");
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)checkIfUserLanguageModelFilesExist
|
||||
{
|
||||
if (![self checkIfUserDataFolderExists]) {
|
||||
return NO;
|
||||
}
|
||||
if (![self checkIfFileExist:[self userPhrasesDataPathMcBopomofo]]) {
|
||||
return NO;
|
||||
}
|
||||
if (![self checkIfFileExist:[self excludedPhrasesDataPathMcBopomofo]]) {
|
||||
return NO;
|
||||
}
|
||||
if (![self checkIfFileExist:[self excludedPhrasesDataPathPlainBopomofo]]) {
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)writeUserPhrase:(NSString *)userPhrase
|
||||
{
|
||||
if (![self checkIfUserLanguageModelFilesExist]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *currentMarkedPhrase = [userPhrase stringByAppendingString:@"\n"];
|
||||
|
||||
NSString *path = [self userPhrasesDataPathMcBopomofo];
|
||||
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path];
|
||||
if (!file) {
|
||||
return NO;
|
||||
}
|
||||
[file seekToEndOfFile];
|
||||
NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding];
|
||||
[file writeData:data];
|
||||
[file closeFile];
|
||||
|
||||
[self loadUserPhrasesModel];
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSString *)dataFolderPath
|
||||
{
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES);
|
||||
NSString *appSupportPath = [paths objectAtIndex:0];
|
||||
NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"McBopomofo"];
|
||||
return userDictPath;
|
||||
}
|
||||
|
||||
+ (NSString *)userPhrasesDataPathMcBopomofo
|
||||
{
|
||||
return [[self dataFolderPath] stringByAppendingPathComponent:@"data.txt"];
|
||||
}
|
||||
|
||||
+ (NSString *)excludedPhrasesDataPathMcBopomofo
|
||||
{
|
||||
return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases.txt"];
|
||||
}
|
||||
|
||||
+ (NSString *)excludedPhrasesDataPathPlainBopomofo
|
||||
{
|
||||
return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases-plain-bpmf.txt"];
|
||||
}
|
||||
|
||||
+ (McBopomofoLM *)languageModelMcBopomofo
|
||||
{
|
||||
return &gLanguageModelMcBopomofo;
|
||||
}
|
||||
|
||||
+ (McBopomofoLM *)languageModelPlainBopomofo
|
||||
{
|
||||
return &gLanguageModelPlainBopomofo;
|
||||
}
|
||||
|
||||
+ (McBopomofo::UserOverrideModel *)userOverrideModel
|
||||
{
|
||||
return &gUserOverrideModel;
|
||||
}
|
||||
|
||||
@end
|
|
@ -2,7 +2,10 @@
|
|||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
extern void LTLoadLanguageModel(void);
|
||||
extern void LTLoadUserLanguageModelFile(void);
|
||||
|
||||
@import Foundation;
|
||||
|
||||
@interface LanguageModelManager : NSObject
|
||||
+ (void)loadDataModels;
|
||||
+ (void)loadUserPhrasesModel;
|
||||
+ (BOOL)checkIfUserLanguageModelFilesExist;
|
||||
@end
|
||||
|
|
|
@ -40,45 +40,46 @@ import Cocoa
|
|||
}
|
||||
|
||||
class NonModalAlertWindowController: NSWindowController {
|
||||
@objc(sharedInstance) static let shared = NonModalAlertWindowController(windowNibName: "NonModalAlertWindowController")
|
||||
|
||||
@objc (sharedInstance)
|
||||
static let shared = NonModalAlertWindowController(windowNibName: "NonModalAlertWindowController")
|
||||
|
||||
@IBOutlet weak var titleTextField: NSTextField!
|
||||
@IBOutlet weak var contentTextField: NSTextField!
|
||||
@IBOutlet weak var confirmButton: NSButton!
|
||||
@IBOutlet weak var cancelButton: NSButton!
|
||||
weak var delegate: NonModalAlertWindowControllerDelegate?
|
||||
|
||||
|
||||
@objc func show(title: String, content: String, confirmButtonTitle: String, cancelButtonTitle: String?, cancelAsDefault: Bool, delegate: NonModalAlertWindowControllerDelegate?) {
|
||||
if window?.isVisible == true {
|
||||
self.delegate?.nonModalAlertWindowControllerDidCancel(self)
|
||||
}
|
||||
|
||||
|
||||
self.delegate = delegate
|
||||
|
||||
|
||||
var oldFrame = confirmButton.frame
|
||||
confirmButton.title = confirmButtonTitle
|
||||
confirmButton.sizeToFit()
|
||||
|
||||
|
||||
var newFrame = confirmButton.frame
|
||||
newFrame.size.width = max(90, newFrame.size.width + 10)
|
||||
newFrame.origin.x += oldFrame.size.width - newFrame.size.width
|
||||
self.confirmButton.frame = newFrame
|
||||
|
||||
confirmButton.frame = newFrame
|
||||
|
||||
if let cancelButtonTitle = cancelButtonTitle {
|
||||
cancelButton.title = cancelButtonTitle
|
||||
cancelButton.sizeToFit()
|
||||
var adjustFrame = cancelButton.frame
|
||||
adjustFrame.size.width = max(90, adjustFrame.size.width + 10)
|
||||
adjustFrame.origin.x = newFrame.origin.x - adjustFrame.size.width
|
||||
self.confirmButton.frame = adjustFrame
|
||||
self.cancelButton.isHidden = false
|
||||
confirmButton.frame = adjustFrame
|
||||
cancelButton.isHidden = false
|
||||
} else {
|
||||
self.cancelButton.isHidden = true
|
||||
cancelButton.isHidden = true
|
||||
}
|
||||
|
||||
|
||||
cancelButton.nextKeyView = confirmButton
|
||||
confirmButton.nextKeyView = cancelButton
|
||||
|
||||
|
||||
if cancelButtonTitle != nil {
|
||||
if cancelAsDefault {
|
||||
window?.defaultButtonCell = cancelButton.cell as? NSButtonCell
|
||||
|
@ -89,12 +90,12 @@ class NonModalAlertWindowController: NSWindowController {
|
|||
} else {
|
||||
window?.defaultButtonCell = confirmButton.cell as? NSButtonCell
|
||||
}
|
||||
|
||||
|
||||
titleTextField.stringValue = title
|
||||
|
||||
|
||||
oldFrame = contentTextField.frame
|
||||
contentTextField.stringValue = content
|
||||
|
||||
|
||||
var infiniteHeightFrame = oldFrame
|
||||
infiniteHeightFrame.size.width -= 4.0
|
||||
infiniteHeightFrame.size.height = 10240
|
||||
|
@ -104,7 +105,7 @@ class NonModalAlertWindowController: NSWindowController {
|
|||
newFrame.origin = oldFrame.origin
|
||||
newFrame.origin.y -= (newFrame.size.height - oldFrame.size.height)
|
||||
contentTextField.frame = newFrame
|
||||
|
||||
|
||||
var windowFrame = window?.frame ?? NSRect.zero
|
||||
windowFrame.size.height += (newFrame.size.height - oldFrame.size.height)
|
||||
window?.level = NSWindow.Level(Int(CGShieldingWindowLevel()) + 1)
|
||||
|
@ -113,20 +114,20 @@ class NonModalAlertWindowController: NSWindowController {
|
|||
window?.makeKeyAndOrderFront(self)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
|
||||
@IBAction func confirmButtonAction(_ sender: Any) {
|
||||
delegate?.nonModalAlertWindowControllerDidConfirm(self)
|
||||
window?.orderOut(self)
|
||||
}
|
||||
|
||||
|
||||
@IBAction func cancelButtonAction(_ sender: Any) {
|
||||
cancel(sender)
|
||||
}
|
||||
|
||||
|
||||
func cancel(_ sender: Any) {
|
||||
delegate?.nonModalAlertWindowControllerDidCancel(self)
|
||||
delegate = nil
|
||||
window?.orderOut(self)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -12,11 +12,11 @@ class OpenCCBridge: NSObject {
|
|||
super.init()
|
||||
}
|
||||
|
||||
@objc static func convert(_ string:String) -> String? {
|
||||
return shared.converter?.convert(string)
|
||||
@objc static func convert(_ string: String) -> String? {
|
||||
shared.converter?.convert(string)
|
||||
}
|
||||
|
||||
private func convert(_ string:String) -> String? {
|
||||
return converter?.convert(string)
|
||||
private func convert(_ string: String) -> String? {
|
||||
converter?.convert(string)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ private let kDefaultKeys = "123456789"
|
|||
// Please note that the class should be exposed as "PreferencesWindowController"
|
||||
// in Objective-C in order to let IMK to see the same class name as
|
||||
// the "InputMethodServerPreferencesWindowControllerClass" in Info.plist.
|
||||
@objc(PreferencesWindowController) class PreferencesWindowController: NSWindowController {
|
||||
@objc (PreferencesWindowController) class PreferencesWindowController: NSWindowController {
|
||||
@IBOutlet weak var fontSizePopUpButton: NSPopUpButton!
|
||||
@IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton!
|
||||
@IBOutlet weak var selectionKeyComboBox: NSComboBox!
|
||||
|
@ -85,8 +85,8 @@ private let kDefaultKeys = "123456789"
|
|||
|
||||
guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID),
|
||||
let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) else {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
let sourceID = String(Unmanaged<CFString>.fromOpaque(sourceIDPtr).takeUnretainedValue())
|
||||
let localizedName = String(Unmanaged<CFString>.fromOpaque(localizedNamePtr).takeUnretainedValue())
|
||||
|
@ -109,7 +109,7 @@ private let kDefaultKeys = "123456789"
|
|||
selectionKeyComboBox.addItems(withObjectValues: [kDefaultKeys, "asdfghjkl", "asdfzxcvb"])
|
||||
|
||||
var candidateSelectionKeys = (UserDefaults.standard.string(forKey: kCandidateKeys) ?? kDefaultKeys)
|
||||
.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
if candidateSelectionKeys.isEmpty {
|
||||
candidateSelectionKeys = kDefaultKeys
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ private let kDefaultKeys = "123456789"
|
|||
selectionKeyComboBox.stringValue = candidateSelectionKeys
|
||||
}
|
||||
|
||||
@IBAction func updateBasisKeyboardLayoutAction(_ sender:Any) {
|
||||
@IBAction func updateBasisKeyboardLayoutAction(_ sender: Any) {
|
||||
if let sourceID = basisKeyboardLayoutButton.selectedItem?.representedObject {
|
||||
UserDefaults.standard.set(sourceID, forKey: kBasisKeyboardLayoutPreferenceKey)
|
||||
}
|
||||
|
|
|
@ -60,3 +60,5 @@
|
|||
"Unable to create the user phrase file." = "Unable to create the user phrase file.";
|
||||
|
||||
"Please check the permission of at \"%@\"." = "Please check the permission of at \"%@\".";
|
||||
|
||||
"Edit Excluded Phrases" = "Edit Excluded Phrases";
|
||||
|
|
|
@ -60,3 +60,6 @@
|
|||
"Unable to create the user phrase file." = "無法建立使用者詞彙檔案";
|
||||
|
||||
"Please check the permission of at \"%@\"." = "請檢查以下路徑的寫入權限 \"%@\"。";
|
||||
|
||||
"Edit Excluded Phrases" = "編輯要排除的詞彙";
|
||||
|
||||
|
|
Loading…
Reference in New Issue