From f339948219fed103e2b98c6a336b015248d53885 Mon Sep 17 00:00:00 2001 From: zonble Date: Tue, 11 Jan 2022 13:46:29 +0800 Subject: [PATCH 1/9] Fixes duplicated code and typos. --- .gitignore | 1 + .../CandidateUI/CandidateController.swift | 12 ++-- .../HorizontalCandidateController.swift | 24 +++---- .../VerticalCandidateController.swift | 18 +++--- Source/AppDelegate.swift | 20 +++--- Source/InputMethodController.h | 2 +- Source/InputMethodController.mm | 62 +++++++------------ Source/InputSourceHelper.swift | 28 ++++----- Source/NonModalAlertWindowController.swift | 43 ++++++------- Source/OpenCCBridge.swift | 8 +-- Source/PreferencesWindowController.swift | 10 +-- 11 files changed, 108 insertions(+), 120 deletions(-) diff --git a/.gitignore b/.gitignore index 1e054964..242ad78a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build +.build *.pbxuser *.mode1v3 *.tm_build_errors diff --git a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift index dfa83d19..073eef4a 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/CandidateController.swift @@ -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 } diff --git a/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift index 8f6b9b7a..c7d7d505 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/HorizontalCandidateController.swift @@ -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.. 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) } } diff --git a/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift b/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift index 495da285..746b32d8 100644 --- a/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift +++ b/Packages/CandidateUI/Sources/CandidateUI/VerticalCandidateController.swift @@ -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 @@ -172,7 +172,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 } @@ -210,7 +210,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) @@ -272,7 +272,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 @@ -287,7 +287,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) @@ -390,11 +390,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 } @@ -430,7 +430,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 { diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index 620fbbfe..ac58d0e8 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -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? @@ -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 { diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index 3fe1fdcc..97b745e7 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -68,7 +68,7 @@ // a special deferred client for Terminal.app fix id _currentDeferredClient; - // currently available candidates + // current available candidates NSMutableArray *_candidates; // current input mode diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 0b075cf9..3442c853 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -1140,50 +1140,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } string layout = [self currentLayout]; string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode); if (_languageModel->hasUnigramsForKey(customPunctuation)) { - if (_bpmfReadingBuffer->isEmpty()) { - _builder->insertReadingAtCursor(customPunctuation); - [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]; - } - } - + [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 (_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]; - } - } - + [self handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]; return YES; } @@ -1199,6 +1163,28 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return NO; } +- (void)handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client +{ + if (_bpmfReadingBuffer->isEmpty()) { + _builder->insertReadingAtCursor(customPunctuation); + [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]; + } + } +} + - (BOOL)handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode { BOOL cancelCandidateKey = diff --git a/Source/InputSourceHelper.swift b/Source/InputSourceHelper.swift index e5c1bf83..a9fc979b 100644 --- a/Source/InputSourceHelper.swift +++ b/Source/InputSourceHelper.swift @@ -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.fromOpaque(proprtyPtr).takeUnretainedValue() + if let propertyPtr = TISGetInputSourceProperty(source, propertyKey) { + let property = Unmanaged.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.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.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.fromOpaque(bundleIDPtr).takeUnretainedValue() let inputsSourceModeID = Unmanaged.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 diff --git a/Source/NonModalAlertWindowController.swift b/Source/NonModalAlertWindowController.swift index 4f1b60eb..2a227bee 100644 --- a/Source/NonModalAlertWindowController.swift +++ b/Source/NonModalAlertWindowController.swift @@ -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) } - + } diff --git a/Source/OpenCCBridge.swift b/Source/OpenCCBridge.swift index 582ec8e0..f63a2726 100644 --- a/Source/OpenCCBridge.swift +++ b/Source/OpenCCBridge.swift @@ -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) } } diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index 59d490dd..0282d02b 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -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.fromOpaque(sourceIDPtr).takeUnretainedValue()) let localizedName = String(Unmanaged.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) } From 144d13346350e1d795a8dca644e77d28aada96f6 Mon Sep 17 00:00:00 2001 From: zonble Date: Tue, 11 Jan 2022 17:12:58 +0800 Subject: [PATCH 2/9] Adds Language Model Manager. The reference of the global language models were stored in the class InputMethodController, however, the global models are global but not a part of the input method controller, and the input method controller only use one of the models (McBopomofo/Plain Bopomofo). I guess it somehow violates SRP and there should be a better place for the global models. --- McBopomofo.xcodeproj/project.pbxproj | 6 + Source/AppDelegate.swift | 6 +- Source/InputMethodController.h | 10 +- Source/InputMethodController.mm | 176 ++++++--------------------- Source/LanguageModelManager.h | 23 ++++ Source/LanguageModelManager.mm | 140 +++++++++++++++++++++ Source/McBopomofo-Bridging-Header.h | 10 +- 7 files changed, 224 insertions(+), 147 deletions(-) create mode 100644 Source/LanguageModelManager.h create mode 100644 Source/LanguageModelManager.mm diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index ab3928ab..502946a1 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ 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 */; }; 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 +156,8 @@ 6AE210B015FC63CC003659FE /* PlainBopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = PlainBopomofo.tiff; sourceTree = ""; }; 6AE210B115FC63CC003659FE /* PlainBopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "PlainBopomofo@2x.tiff"; sourceTree = ""; }; 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NonModalAlertWindowController.xib; sourceTree = ""; }; + D41355D6278D7409005E5CBD /* LanguageModelManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LanguageModelManager.h; sourceTree = ""; }; + D41355D7278D7409005E5CBD /* LanguageModelManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LanguageModelManager.mm; sourceTree = ""; }; D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "McBopomofo-Bridging-Header.h"; sourceTree = ""; }; D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenCCBridge.swift; sourceTree = ""; }; D427F768278C9D0D004A2160 /* CandidateUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = CandidateUI; path = Packages/CandidateUI; sourceTree = ""; }; @@ -228,6 +231,8 @@ 6A0D4F4715FC0EB900ABF4B3 /* Resources */, 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */, 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */, + D41355D6278D7409005E5CBD /* LanguageModelManager.h */, + D41355D7278D7409005E5CBD /* LanguageModelManager.mm */, 6A0D4EC815FC0D6400ABF4B3 /* main.m */, D427F76B278CA1BA004A2160 /* AppDelegate.swift */, D47F7DD4278C25A0002F9DD7 /* InputSourceHelper.swift */, @@ -551,6 +556,7 @@ D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */, + D41355D8278D74B5005E5CBD /* LanguageModelManager.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index ac58d0e8..794e1fe7 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -51,8 +51,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle private var updateNextStepURL: URL? func applicationDidFinishLaunching(_ notification: Notification) { - LTLoadLanguageModel() - LTLoadUserLanguageModelFile() + LanguageModelManager.loadDataModels() + LanguageModelManager.loadUserPhrasesModel() +// LTLoadLanguageModel() +// LTLoadUserLanguageModelFile() if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil { UserDefaults.standard.set(true, forKey: kCheckUpdateAutomatically) diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index 97b745e7..a9813def 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -49,15 +49,15 @@ Formosa::Gramambular::FastLM *_languageModel; Formosa::Gramambular::FastLM *_userPhrasesModel; + // user override model + McBopomofo::UserOverrideModel *_userOverrideModel; + // the grid (lattice) builder for the unigrams (and bigrams) Formosa::Gramambular::BlockReadingBuilder* _builder; // latest walked path (trellis) using the Viterbi algorithm std::vector _walkedNodes; - // user override model - McBopomofo::UserOverrideModel *_uom; - // the latest composing buffer that is updated to the foreground app NSMutableString *_composingBuffer; NSInteger _latestReadingCursor; @@ -78,7 +78,3 @@ BOOL _chineseConversionEnabled; } @end - -// the shared language model object -extern "C" void LTLoadLanguageModel(); -extern "C" void LTLoadUserLanguageModelFile(); diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 3442c853..2111b963 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -38,13 +38,12 @@ #import #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; @@ -111,62 +110,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 +117,12 @@ static inline NSString *LocalizationNotNeeded(NSString *s) { } // private methods -@interface McBopomofoInputMethodController () +@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) @end // sort helper @@ -237,10 +175,11 @@ static double FindHighestScore(const vector& nodes, double epsilon) _bpmfReadingBuffer = new BopomofoReadingBuffer(BopomofoKeyboardLayout::StandardLayout()); // create the lattice builder - _languageModel = &gLanguageModel; - _userPhrasesModel = &gUserPhraseLanguageModel; + _languageModel = [LanguageModelManager languageModelMcBopomofo]; + _userPhrasesModel = [LanguageModelManager userPhraseLanguageModel]; + _userOverrideModel = [LanguageModelManager userOverrideModel]; + _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); - _uom = &gUserOverrideModel; // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); @@ -380,17 +319,17 @@ static double FindHighestScore(const vector& nodes, double epsilon) { NSString *newInputMode; Formosa::Gramambular::FastLM *newLanguageModel; - Formosa::Gramambular::FastLM *userPhraseModel; + Formosa::Gramambular::FastLM *newUserPhraseModel; if ([value isKindOfClass:[NSString class]] && [value isEqual:kPlainBopomofoModeIdentifier]) { newInputMode = kPlainBopomofoModeIdentifier; - newLanguageModel = &gLanguageModelPlainBopomofo; - userPhraseModel = NULL; + newLanguageModel = [LanguageModelManager languageModelPlainBopomofo]; + newUserPhraseModel = NULL; } else { newInputMode = kBopomofoModeIdentifier; - newLanguageModel = &gLanguageModel; - userPhraseModel = &gUserPhraseLanguageModel; + newLanguageModel = [LanguageModelManager languageModelMcBopomofo]; + newUserPhraseModel = [LanguageModelManager userPhraseLanguageModel]; } // Only apply the changes if the value is changed @@ -406,7 +345,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) _inputMode = newInputMode; _languageModel = newLanguageModel; - _userPhrasesModel = userPhraseModel; + _userPhrasesModel = newUserPhraseModel; if (!_bpmfReadingBuffer->isEmpty()) { _bpmfReadingBuffer->clear(); @@ -432,8 +371,7 @@ static double FindHighestScore(const vector& 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 +470,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 +499,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 +620,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 +723,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 +791,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 nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); @@ -1188,9 +1111,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (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; @@ -1343,7 +1266,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]; @@ -1569,13 +1492,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)openUserPhrases:(id)sender { NSLog(@"openUserPhrases called"); - if (!LTCheckIfUserLanguageModelFileExists()) { - NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), LTUserDataFolderPath()]; + if (![LanguageModelManager checkIfUserLanguageModelFileExists] ) { + 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; } - NSString *path = LTUserPhrasesDataPath(); + NSString *path = [LanguageModelManager userPhrasesDataPath]; NSLog(@"Open %@", path); if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:path atomically:YES]; @@ -1587,7 +1510,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)reloadUserPhrases:(id)sender { NSLog(@"reloadUserPhrases called"); - LTLoadUserLanguageModelFile(); + [LanguageModelManager loadUserPhrasesModel]; } - (void)showAbout:(id)sender @@ -1602,6 +1525,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]; @@ -1622,7 +1551,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]; @@ -1637,28 +1566,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."); - } -} diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h new file mode 100644 index 00000000..0c199cc0 --- /dev/null +++ b/Source/LanguageModelManager.h @@ -0,0 +1,23 @@ +#import +#import "FastLM.h" +#import "UserOverrideModel.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface LanguageModelManager : NSObject + ++ (void)loadDataModels; ++ (void)loadUserPhrasesModel; ++ (BOOL)checkIfUserLanguageModelFileExists; ++ (BOOL)writeUserPhrase:(NSString *)userPhrase; + +@property (class, readonly, nonatomic) NSString *dataFolderPath; +@property (class, readonly, nonatomic) NSString *userPhrasesDataPath; +@property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *languageModelMcBopomofo; +@property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *languageModelPlainBopomofo; +@property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *userPhraseLanguageModel; +@property (class, readonly, nonatomic) McBopomofo::UserOverrideModel *userOverrideModel; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm new file mode 100644 index 00000000..f2ec7493 --- /dev/null +++ b/Source/LanguageModelManager.mm @@ -0,0 +1,140 @@ +#import "LanguageModelManager.h" +#import +#import +#import +#import "OVStringHelper.h" +#import "OVUTF8Helper.h" + +using namespace std; +using namespace Formosa::Gramambular; +using namespace OpenVanilla; + +static const int kUserOverrideModelCapacity = 500; +static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. + +FastLM globalLanguageModel; +FastLM globalLanguageModelPlainBopomofo; +FastLM globalUserPhraseLanguageModel; +McBopomofo::UserOverrideModel globalUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); + +@implementation LanguageModelManager + +static bool LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM &lm) +{ + Class cls = NSClassFromString(@"McBopomofoInputMethodController"); + NSString *dataPath = [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"]; + bool result = lm.open([dataPath UTF8String]); + return (BOOL)result; +} + ++ (void)loadDataModels +{ + bool dataOpenResult = LTLoadLanguageModelFile(@"data", globalLanguageModel); + if (!dataOpenResult) { + NSLog(@"Failed to open language model."); + } + bool plainBpmfOpenResult = LTLoadLanguageModelFile(@"data-plain-bpmf", globalLanguageModelPlainBopomofo); + if (!plainBpmfOpenResult) { + NSLog(@"Failed to open language model for plain bpmf."); + } +} + ++ (void)loadUserPhrasesModel +{ + globalUserPhraseLanguageModel.close(); + bool result = globalUserPhraseLanguageModel.open([[self userPhrasesDataPath] UTF8String]); + if (!result) { + NSLog(@"Failed to open user phrases."); + } +} + ++ (BOOL)checkIfUserLanguageModelFileExists +{ + 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; + } + } + + NSString *filePath = [self userPhrasesDataPath]; + 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)writeUserPhrase:(NSString *)userPhrase +{ + if (![self checkIfUserLanguageModelFileExists]) { + return NO; + } + + NSString *currentMarkedPhrase = [userPhrase stringByAppendingString:@"\n"]; + + NSString *path = [self userPhrasesDataPath]; + 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 *)userPhrasesDataPath +{ + return [[self dataFolderPath] stringByAppendingPathComponent:@"data.txt"]; +} + + + (Formosa::Gramambular::FastLM *)languageModelMcBopomofo +{ + return &globalLanguageModel; +} + ++ (Formosa::Gramambular::FastLM *)languageModelPlainBopomofo +{ + return &globalLanguageModelPlainBopomofo; +} + ++ (Formosa::Gramambular::FastLM *)userPhraseLanguageModel +{ + return &globalUserPhraseLanguageModel; +} + ++ (McBopomofo::UserOverrideModel *)userOverrideModel +{ + return &globalUserOverrideModel; +} + +@end diff --git a/Source/McBopomofo-Bridging-Header.h b/Source/McBopomofo-Bridging-Header.h index 032f1391..0b8d8d44 100644 --- a/Source/McBopomofo-Bridging-Header.h +++ b/Source/McBopomofo-Bridging-Header.h @@ -2,7 +2,13 @@ // 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); +//extern void LTLoadLanguageModel(void); +//extern void LTLoadUserLanguageModelFile(void); +@import Foundation; +@interface LanguageModelManager : NSObject ++ (void)loadDataModels; ++ (void)loadUserPhrasesModel; ++ (BOOL)checkIfUserLanguageModelFileExists; +@end From 9b485b799ca1c66fe69c4f66f19e63267e400db9 Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 12 Jan 2022 00:16:55 +0800 Subject: [PATCH 3/9] Implements excluding phrases. --- .../Engine/Gramambular/BlockReadingBuilder.h | 52 +++++++++--- Source/InputMethodController.h | 1 + Source/InputMethodController.mm | 82 +++++++++++++------ Source/LanguageModelManager.h | 9 +- Source/LanguageModelManager.mm | 76 ++++++++++++++--- Source/McBopomofo-Bridging-Header.h | 2 +- 6 files changed, 171 insertions(+), 51 deletions(-) diff --git a/Source/Engine/Gramambular/BlockReadingBuilder.h b/Source/Engine/Gramambular/BlockReadingBuilder.h index 08508b55..bd3dc2d0 100644 --- a/Source/Engine/Gramambular/BlockReadingBuilder.h +++ b/Source/Engine/Gramambular/BlockReadingBuilder.h @@ -38,7 +38,7 @@ namespace Formosa { class BlockReadingBuilder { public: - BlockReadingBuilder(LanguageModel *inLM, LanguageModel *inUserPhraseLM); + BlockReadingBuilder(LanguageModel *inLM, LanguageModel *inUserPhraseLM, LanguageModel *inExcludedPhrasesLM); void clear(); size_t length() const; @@ -58,6 +58,8 @@ namespace Formosa { vector readingsAtRange(size_t begin, size_t end) const; Grid& grid(); + + bool checkIfUnigramExistInVector(Unigram& unigram, vectorvector); protected: void build(); @@ -73,13 +75,17 @@ namespace Formosa { Grid m_grid; LanguageModel *m_LM; - LanguageModel *m_UserPhraseLM; + LanguageModel *m_userPhraseLM; + LanguageModel *m_excludedPhrasesLM; string m_joinSeparator; }; - inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM, LanguageModel *inUserPhraseLM) + inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM, + LanguageModel *inUserPhraseLM, + LanguageModel *inExcludedPhrasesLM) : m_LM(inLM) - , m_UserPhraseLM(inUserPhraseLM) + , m_userPhraseLM(inUserPhraseLM) + , m_excludedPhrasesLM(inExcludedPhrasesLM) , m_cursorIndex(0) , m_markerCursorIndex(SIZE_MAX) { @@ -197,7 +203,17 @@ namespace Formosa { { return m_grid; } - + + inline bool BlockReadingBuilder::checkIfUnigramExistInVector(Unigram& unigram, vectorvector) + { + for (std::vector::iterator it=vector.begin(); it!=vector.end(); ++it) { + if (it->keyValue.value == unigram.keyValue.value) { + return true; + } + } + return false; + } + inline void BlockReadingBuilder::build() { if (!m_LM) { @@ -223,17 +239,31 @@ namespace Formosa { string combinedReading = Join(m_readings.begin() + p, m_readings.begin() + p + q, m_joinSeparator); if (!m_grid.hasNodeAtLocationSpanningLengthMatchingKey(p, q, combinedReading)) { vector unigrams; + vector userUnigrams; - if (m_UserPhraseLM != NULL) { - if (m_UserPhraseLM->hasUnigramsForKey(combinedReading)) { - vector userUnigrams = m_UserPhraseLM->unigramsForKeys(combinedReading); - unigrams.insert(unigrams.end(), userUnigrams.begin(), userUnigrams.end()); - } + if (m_userPhraseLM != NULL && m_userPhraseLM->hasUnigramsForKey(combinedReading)) { + userUnigrams = m_userPhraseLM->unigramsForKeys(combinedReading); } if (m_LM->hasUnigramsForKey(combinedReading)) { vector globalUnigrams = m_LM->unigramsForKeys(combinedReading); - unigrams.insert(unigrams.end(), globalUnigrams.begin(), globalUnigrams.end()); + for (std::vector::iterator it=globalUnigrams.begin(); it!=globalUnigrams.end(); ++it) { + if (!checkIfUnigramExistInVector(*it, unigrams)) { + unigrams.push_back(*it); + } + } + } + unigrams.insert(unigrams.begin(), userUnigrams.begin(), userUnigrams.end()); + + if (m_excludedPhrasesLM != NULL && m_excludedPhrasesLM->hasUnigramsForKey(combinedReading)) { + vector excludedUnigrams = m_excludedPhrasesLM->unigramsForKeys(combinedReading); + vector newUnigram; + for (std::vector::iterator it=unigrams.begin(); it!=unigrams.end(); ++it) { + if (!checkIfUnigramExistInVector(*it, excludedUnigrams)) { + newUnigram.push_back(*it); + } + } + unigrams = newUnigram; } if (unigrams.size() > 0) { diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index a9813def..a99e248e 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -48,6 +48,7 @@ // language model Formosa::Gramambular::FastLM *_languageModel; Formosa::Gramambular::FastLM *_userPhrasesModel; + Formosa::Gramambular::FastLM *_excludedPhraseModel; // user override model McBopomofo::UserOverrideModel *_userOverrideModel; diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 2111b963..2a36ad49 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -178,8 +178,9 @@ static double FindHighestScore(const vector& nodes, double epsilon) _languageModel = [LanguageModelManager languageModelMcBopomofo]; _userPhrasesModel = [LanguageModelManager userPhraseLanguageModel]; _userOverrideModel = [LanguageModelManager userOverrideModel]; + _excludedPhraseModel = [LanguageModelManager excludedPhrasesLanguageModelMcBopomofo]; - _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); + _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel, _excludedPhraseModel); // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); @@ -206,18 +207,23 @@ static double FindHighestScore(const vector& 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]; - - NSMenuItem *reloadUserPheaseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; - [reloadUserPheaseItem setIndentationLevel:2]; - [menu addItem:reloadUserPheaseItem]; - [menu addItem:[NSMenuItem separatorItem]]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""]; + 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]; @@ -318,18 +324,21 @@ static double FindHighestScore(const vector& nodes, double epsilon) - (void)setValue:(id)value forTag:(long)tag client:(id)sender { NSString *newInputMode; - Formosa::Gramambular::FastLM *newLanguageModel; - Formosa::Gramambular::FastLM *newUserPhraseModel; + FastLM *newLanguageModel; + FastLM *newUserPhrasesModel; + FastLM *newExcludedPhraseModel; if ([value isKindOfClass:[NSString class]] && [value isEqual:kPlainBopomofoModeIdentifier]) { newInputMode = kPlainBopomofoModeIdentifier; newLanguageModel = [LanguageModelManager languageModelPlainBopomofo]; - newUserPhraseModel = NULL; + newUserPhrasesModel = NULL; + newExcludedPhraseModel = [LanguageModelManager excludedPhrasesLanguageModelPlainBopomofo]; } else { newInputMode = kBopomofoModeIdentifier; newLanguageModel = [LanguageModelManager languageModelMcBopomofo]; - newUserPhraseModel = [LanguageModelManager userPhraseLanguageModel]; + newUserPhrasesModel = [LanguageModelManager userPhraseLanguageModel]; + newExcludedPhraseModel = [LanguageModelManager excludedPhrasesLanguageModelMcBopomofo]; } // Only apply the changes if the value is changed @@ -345,7 +354,8 @@ static double FindHighestScore(const vector& nodes, double epsilon) _inputMode = newInputMode; _languageModel = newLanguageModel; - _userPhrasesModel = newUserPhraseModel; + _userPhrasesModel = newUserPhrasesModel; + _excludedPhraseModel = newExcludedPhraseModel; if (!_bpmfReadingBuffer->isEmpty()) { _bpmfReadingBuffer->clear(); @@ -358,7 +368,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) if (_builder) { delete _builder; - _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); + _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel, _excludedPhraseModel); _builder->setJoinSeparator("-"); } } @@ -1489,24 +1499,44 @@ 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 (![LanguageModelManager checkIfUserLanguageModelFileExists] ) { + 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 = [LanguageModelManager userPhrasesDataPath]; - 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"); diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h index 0c199cc0..9ba88bb0 100644 --- a/Source/LanguageModelManager.h +++ b/Source/LanguageModelManager.h @@ -8,16 +8,19 @@ NS_ASSUME_NONNULL_BEGIN + (void)loadDataModels; + (void)loadUserPhrasesModel; -+ (BOOL)checkIfUserLanguageModelFileExists; ++ (BOOL)checkIfUserLanguageModelFilesExist; + (BOOL)writeUserPhrase:(NSString *)userPhrase; @property (class, readonly, nonatomic) NSString *dataFolderPath; -@property (class, readonly, nonatomic) NSString *userPhrasesDataPath; +@property (class, readonly, nonatomic) NSString *userPhrasesDataPathMcBopomofo; +@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathMcBopomofo; +@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathPlainBopomofo; @property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *languageModelMcBopomofo; @property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *languageModelPlainBopomofo; @property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *userPhraseLanguageModel; +@property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *excludedPhrasesLanguageModelMcBopomofo; +@property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *excludedPhrasesLanguageModelPlainBopomofo; @property (class, readonly, nonatomic) McBopomofo::UserOverrideModel *userOverrideModel; - @end NS_ASSUME_NONNULL_END diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index f2ec7493..68924f54 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -15,6 +15,8 @@ static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. FastLM globalLanguageModel; FastLM globalLanguageModelPlainBopomofo; FastLM globalUserPhraseLanguageModel; +FastLM globalUserExcludedPhrasesMcBopomofo; +FastLM globalUserExcludedPhrasesPlainBopomofo; McBopomofo::UserOverrideModel globalUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); @implementation LanguageModelManager @@ -42,13 +44,27 @@ static bool LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM & + (void)loadUserPhrasesModel { globalUserPhraseLanguageModel.close(); - bool result = globalUserPhraseLanguageModel.open([[self userPhrasesDataPath] UTF8String]); + globalUserExcludedPhrasesMcBopomofo.close(); + globalUserExcludedPhrasesPlainBopomofo.close(); + + bool result = false; + + result = globalUserPhraseLanguageModel.open([[self userPhrasesDataPathMcBopomofo] UTF8String]); if (!result) { - NSLog(@"Failed to open user phrases."); + NSLog(@"Failed to open user phrases. %@", [self userPhrasesDataPathMcBopomofo]); + } + result = globalUserExcludedPhrasesMcBopomofo.open([[self excludedPhrasesDataPathMcBopomofo] UTF8String]); + if (!result) { + NSLog(@"Failed to open excluded phrases McBopomofo. %@", [self excludedPhrasesDataPathMcBopomofo]); + } + + result = globalUserExcludedPhrasesPlainBopomofo.open([[self excludedPhrasesDataPathPlainBopomofo] UTF8String]); + if (!result) { + NSLog(@"Failed to open excluded phrases Plain Bopomofo. %@", [self excludedPhrasesDataPathPlainBopomofo]); } } -+ (BOOL)checkIfUserLanguageModelFileExists ++ (BOOL)checkIfUserDataFolderExists { NSString *folderPath = [self dataFolderPath]; BOOL isFolder = NO; @@ -70,8 +86,11 @@ static bool LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM & return NO; } } + return YES; +} - NSString *filePath = [self userPhrasesDataPath]; ++ (BOOL)checkIfFileExist:(NSString *)filePath +{ if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) { BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:YES]; if (!result) { @@ -82,15 +101,32 @@ static bool LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM & 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 checkIfUserLanguageModelFileExists]) { + if (![self checkIfUserLanguageModelFilesExist]) { return NO; } NSString *currentMarkedPhrase = [userPhrase stringByAppendingString:@"\n"]; - NSString *path = [self userPhrasesDataPath]; + NSString *path = [self userPhrasesDataPathMcBopomofo]; NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path]; if (!file) { return NO; @@ -112,26 +148,46 @@ static bool LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM & return userDictPath; } -+ (NSString *)userPhrasesDataPath ++ (NSString *)userPhrasesDataPathMcBopomofo { return [[self dataFolderPath] stringByAppendingPathComponent:@"data.txt"]; } - + (Formosa::Gramambular::FastLM *)languageModelMcBopomofo ++ (NSString *)excludedPhrasesDataPathMcBopomofo +{ + return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases.txt"]; +} + ++ (NSString *)excludedPhrasesDataPathPlainBopomofo +{ + return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases-plain-bpmf.txt"]; +} + + + (FastLM *)languageModelMcBopomofo { return &globalLanguageModel; } -+ (Formosa::Gramambular::FastLM *)languageModelPlainBopomofo ++ (FastLM *)languageModelPlainBopomofo { return &globalLanguageModelPlainBopomofo; } -+ (Formosa::Gramambular::FastLM *)userPhraseLanguageModel ++ (FastLM *)userPhraseLanguageModel { return &globalUserPhraseLanguageModel; } ++ (FastLM *)excludedPhrasesLanguageModelMcBopomofo +{ + return &globalUserExcludedPhrasesMcBopomofo; +} + ++ (FastLM *)excludedPhrasesLanguageModelPlainBopomofo +{ + return &globalUserExcludedPhrasesPlainBopomofo; +} + + (McBopomofo::UserOverrideModel *)userOverrideModel { return &globalUserOverrideModel; diff --git a/Source/McBopomofo-Bridging-Header.h b/Source/McBopomofo-Bridging-Header.h index 0b8d8d44..6e2a1a13 100644 --- a/Source/McBopomofo-Bridging-Header.h +++ b/Source/McBopomofo-Bridging-Header.h @@ -10,5 +10,5 @@ @interface LanguageModelManager : NSObject + (void)loadDataModels; + (void)loadUserPhrasesModel; -+ (BOOL)checkIfUserLanguageModelFileExists; ++ (BOOL)checkIfUserLanguageModelFilesExist; @end From ea36061a411d241f4d7351ea01e0ddaaba85ef35 Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 12 Jan 2022 00:36:55 +0800 Subject: [PATCH 4/9] Implements excluding punctuations. --- Source/InputMethodController.mm | 50 +++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 2a36ad49..5c6db914 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -1070,17 +1070,16 @@ 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 (_languageModel->hasUnigramsForKey(customPunctuation)) { - [self handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]; + if ([self handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) { return YES; } - // if nothing is matched, see if it's a punctuation key + // if nothing is matched, see if it's a punctuation key. string punctuation = string("_punctuation_") + string(1, (char)charCode); - if (_languageModel->hasUnigramsForKey(punctuation)) { - [self handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]; + if ([self handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) { return YES; } @@ -1096,8 +1095,46 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return NO; } -- (void)handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client +- (vector)_collectUnigrams:(string)string { + vector unigrams; + vector userUnigrams; + + if (_userPhrasesModel != NULL && _userPhrasesModel->hasUnigramsForKey(string)) { + userUnigrams = _userPhrasesModel->unigramsForKeys(string); + } + + if (_languageModel->hasUnigramsForKey(string)) { + vector globalUnigrams = _languageModel->unigramsForKeys(string); + for (std::vector::iterator it=globalUnigrams.begin(); it!=globalUnigrams.end(); ++it) { + if (!_builder->checkIfUnigramExistInVector(*it, unigrams)) { + unigrams.push_back(*it); + } + } + } + unigrams.insert(unigrams.begin(), userUnigrams.begin(), userUnigrams.end()); + + if (_excludedPhraseModel != NULL && _excludedPhraseModel->hasUnigramsForKey(string)) { + vector excludedUnigrams = _excludedPhraseModel->unigramsForKeys(string); + vector newUnigram; + for (std::vector::iterator it=unigrams.begin(); it!=unigrams.end(); ++it) { + if (!_builder->checkIfUnigramExistInVector(*it, excludedUnigrams)) { + newUnigram.push_back(*it); + } + } + unigrams = newUnigram; + } + return unigrams; +} + + +- (BOOL)handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client +{ + vector collected = [self _collectUnigrams:customPunctuation]; + if (!collected.size()) { + return NO; + } + if (_bpmfReadingBuffer->isEmpty()) { _builder->insertReadingAtCursor(customPunctuation); [self popOverflowComposingTextAndWalk:client]; @@ -1116,6 +1153,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; } } + return YES; } - (BOOL)handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode From cbd21cbe1d27b19605d43704629ef93c122c76f1 Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 12 Jan 2022 01:10:39 +0800 Subject: [PATCH 5/9] Updates localization. --- Source/InputMethodController.mm | 1 + Source/en.lproj/Localizable.strings | 2 ++ Source/zh-Hant.lproj/Localizable.strings | 3 +++ 3 files changed, 6 insertions(+) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 5c6db914..32b71d8c 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -209,6 +209,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) [menu addItem:[NSMenuItem separatorItem]]; [menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""]; + if (_inputMode == kPlainBopomofoModeIdentifier) { NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesPlainBopomofo:) keyEquivalent:@""]; [menu addItem:editExcludedPhrasesItem]; diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index eb268a24..a9b5048e 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -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"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 118264db..cdd2b6d2 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -60,3 +60,6 @@ "Unable to create the user phrase file." = "無法建立使用者詞彙檔案"; "Please check the permission of at \"%@\"." = "請檢查以下路徑的寫入權限 \"%@\"。"; + +"Edit Excluded Phrases" = "編輯要排除的詞彙"; + From 56896625e3d6bbeb36e61fc25df02d6be03b9216 Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 12 Jan 2022 01:17:39 +0800 Subject: [PATCH 6/9] Removes unused comments. --- Source/AppDelegate.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index 794e1fe7..94e553ec 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -53,8 +53,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle func applicationDidFinishLaunching(_ notification: Notification) { LanguageModelManager.loadDataModels() LanguageModelManager.loadUserPhrasesModel() -// LTLoadLanguageModel() -// LTLoadUserLanguageModelFile() if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil { UserDefaults.standard.set(true, forKey: kCheckUpdateAutomatically) From abdf97f65270c23cceea78af348b895e3b8ce0ab Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 12 Jan 2022 12:26:24 +0800 Subject: [PATCH 7/9] Adds McBopomofoLM as the facade of three language models. - main language model - user phrases - user excluded phrases --- McBopomofo.xcodeproj/project.pbxproj | 6 ++ Source/Engine/FastLM.cpp | 2 +- Source/Engine/FastLM.h | 2 +- .../Engine/Gramambular/BlockReadingBuilder.h | 38 +------- Source/Engine/Gramambular/LanguageModel.h | 2 +- Source/Engine/McBopomofoLM.cpp | 92 +++++++++++++++++++ Source/Engine/McBopomofoLM.h | 31 +++++++ Source/InputMethodController.h | 6 +- Source/InputMethodController.mm | 90 +++++------------- Source/LanguageModelManager.h | 8 +- Source/LanguageModelManager.mm | 71 +++----------- 11 files changed, 178 insertions(+), 170 deletions(-) create mode 100644 Source/Engine/McBopomofoLM.cpp create mode 100644 Source/Engine/McBopomofoLM.h diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index 502946a1..4d0bf37e 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 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 */; }; @@ -158,6 +159,8 @@ 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NonModalAlertWindowController.xib; sourceTree = ""; }; D41355D6278D7409005E5CBD /* LanguageModelManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LanguageModelManager.h; sourceTree = ""; }; D41355D7278D7409005E5CBD /* LanguageModelManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LanguageModelManager.mm; sourceTree = ""; }; + D41355D9278E6D17005E5CBD /* McBopomofoLM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = McBopomofoLM.cpp; sourceTree = ""; }; + D41355DA278E6D17005E5CBD /* McBopomofoLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = McBopomofoLM.h; sourceTree = ""; }; D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "McBopomofo-Bridging-Header.h"; sourceTree = ""; }; D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenCCBridge.swift; sourceTree = ""; }; D427F768278C9D0D004A2160 /* CandidateUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = CandidateUI; path = Packages/CandidateUI; sourceTree = ""; }; @@ -267,6 +270,8 @@ 6A0421A715FEF3F50061ED63 /* FastLM.h */, D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */, D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */, + D41355D9278E6D17005E5CBD /* McBopomofoLM.cpp */, + D41355DA278E6D17005E5CBD /* McBopomofoLM.h */, ); path = Engine; sourceTree = ""; @@ -553,6 +558,7 @@ 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 */, diff --git a/Source/Engine/FastLM.cpp b/Source/Engine/FastLM.cpp index 48569c65..9808142f 100644 --- a/Source/Engine/FastLM.cpp +++ b/Source/Engine/FastLM.cpp @@ -285,7 +285,7 @@ const vector FastLM::bigramsForKeys(const string& preceedingKey, const s return vector(); } -const vector FastLM::unigramsForKeys(const string& key) +const vector FastLM::unigramsForKey(const string& key) { vector v; map >::const_iterator i = keyRowMap.find(key.c_str()); diff --git a/Source/Engine/FastLM.h b/Source/Engine/FastLM.h index 77aac397..69fb8bd9 100644 --- a/Source/Engine/FastLM.h +++ b/Source/Engine/FastLM.h @@ -50,7 +50,7 @@ namespace Formosa { void dump(); virtual const vector bigramsForKeys(const string& preceedingKey, const string& key); - virtual const vector unigramsForKeys(const string& key); + virtual const vector unigramsForKey(const string& key); virtual bool hasUnigramsForKey(const string& key); protected: diff --git a/Source/Engine/Gramambular/BlockReadingBuilder.h b/Source/Engine/Gramambular/BlockReadingBuilder.h index bd3dc2d0..145a0185 100644 --- a/Source/Engine/Gramambular/BlockReadingBuilder.h +++ b/Source/Engine/Gramambular/BlockReadingBuilder.h @@ -38,7 +38,7 @@ namespace Formosa { class BlockReadingBuilder { public: - BlockReadingBuilder(LanguageModel *inLM, LanguageModel *inUserPhraseLM, LanguageModel *inExcludedPhrasesLM); + BlockReadingBuilder(LanguageModel *inLM); void clear(); size_t length() const; @@ -75,17 +75,11 @@ namespace Formosa { Grid m_grid; LanguageModel *m_LM; - LanguageModel *m_userPhraseLM; - LanguageModel *m_excludedPhrasesLM; string m_joinSeparator; }; - inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM, - LanguageModel *inUserPhraseLM, - LanguageModel *inExcludedPhrasesLM) + inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM) : m_LM(inLM) - , m_userPhraseLM(inUserPhraseLM) - , m_excludedPhrasesLM(inExcludedPhrasesLM) , m_cursorIndex(0) , m_markerCursorIndex(SIZE_MAX) { @@ -238,33 +232,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 unigrams; - vector userUnigrams; - - if (m_userPhraseLM != NULL && m_userPhraseLM->hasUnigramsForKey(combinedReading)) { - userUnigrams = m_userPhraseLM->unigramsForKeys(combinedReading); - } - - if (m_LM->hasUnigramsForKey(combinedReading)) { - vector globalUnigrams = m_LM->unigramsForKeys(combinedReading); - for (std::vector::iterator it=globalUnigrams.begin(); it!=globalUnigrams.end(); ++it) { - if (!checkIfUnigramExistInVector(*it, unigrams)) { - unigrams.push_back(*it); - } - } - } - unigrams.insert(unigrams.begin(), userUnigrams.begin(), userUnigrams.end()); - - if (m_excludedPhrasesLM != NULL && m_excludedPhrasesLM->hasUnigramsForKey(combinedReading)) { - vector excludedUnigrams = m_excludedPhrasesLM->unigramsForKeys(combinedReading); - vector newUnigram; - for (std::vector::iterator it=unigrams.begin(); it!=unigrams.end(); ++it) { - if (!checkIfUnigramExistInVector(*it, excludedUnigrams)) { - newUnigram.push_back(*it); - } - } - unigrams = newUnigram; - } + vector unigrams = m_LM->unigramsForKey(combinedReading); if (unigrams.size() > 0) { Node n(combinedReading, unigrams, vector()); diff --git a/Source/Engine/Gramambular/LanguageModel.h b/Source/Engine/Gramambular/LanguageModel.h index 46c2e1d4..65331b37 100644 --- a/Source/Engine/Gramambular/LanguageModel.h +++ b/Source/Engine/Gramambular/LanguageModel.h @@ -42,7 +42,7 @@ namespace Formosa { virtual ~LanguageModel() {} virtual const vector bigramsForKeys(const string &preceedingKey, const string& key) = 0; - virtual const vector unigramsForKeys(const string &key) = 0; + virtual const vector unigramsForKey(const string &key) = 0; virtual bool hasUnigramsForKey(const string& key) = 0; }; } diff --git a/Source/Engine/McBopomofoLM.cpp b/Source/Engine/McBopomofoLM.cpp new file mode 100644 index 00000000..e5bc741f --- /dev/null +++ b/Source/Engine/McBopomofoLM.cpp @@ -0,0 +1,92 @@ +#include "McBopomofoLM.h" +#include +#include +#include + +using namespace McBopomofo; + +McBopomofoLM::McBopomofoLM() +{ +} + +McBopomofoLM::~McBopomofoLM() +{ + m_languageModel.close(); + m_userPhrases.close(); + m_excluddePhrases.close(); +} + +void McBopomofoLM::loadLanguageModel(const char* languageModelDataPath) +{ + m_languageModel.close(); + m_languageModel.open(languageModelDataPath); +} + +void McBopomofoLM::loadUserPhrases(const char* userPhrasesDataPath, + const char* excludedPhrasesDataPath) +{ + m_userPhrases.close(); + m_userPhrases.open(userPhrasesDataPath); + m_excluddePhrases.close(); + m_excluddePhrases.open(excludedPhrasesDataPath); +} + +const vector McBopomofoLM::bigramsForKeys(const string& preceedingKey, const string& key) +{ + return vector(); +} + +const vector McBopomofoLM::unigramsForKey(const string& key) +{ + vector unigrams; + vector userUnigrams; + + // Use unordered_set so that you don't have to do O(n*m) + unordered_set excludedValues; + unordered_set userValues; + + if (m_excluddePhrases.hasUnigramsForKey(key)) { + vector excludedUnigrams = m_excluddePhrases.unigramsForKey(key); + transform(excludedUnigrams.begin(), excludedUnigrams.end(), + inserter(excludedValues, excludedValues.end()), + [](const Unigram &u) { return u.keyValue.value; }); + } + + if (m_userPhrases.hasUnigramsForKey(key)) { + vector 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 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_excluddePhrases.hasUnigramsForKey(key)) { + return m_userPhrases.hasUnigramsForKey(key) || + m_languageModel.hasUnigramsForKey(key); + } + + return unigramsForKey(key).size() > 0; +} diff --git a/Source/Engine/McBopomofoLM.h b/Source/Engine/McBopomofoLM.h new file mode 100644 index 00000000..19ad8246 --- /dev/null +++ b/Source/Engine/McBopomofoLM.h @@ -0,0 +1,31 @@ +#ifndef MCBOPOMOFOLM_H +#define MCBOPOMOFOLM_H + +#include +#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 bigramsForKeys(const string& preceedingKey, const string& key); + const vector unigramsForKey(const string& key); + bool hasUnigramsForKey(const string& key); + +protected: + FastLM m_languageModel; + FastLM m_userPhrases; + FastLM m_excluddePhrases; +}; +}; + +#endif diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index a99e248e..404c0ab4 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -36,7 +36,7 @@ #import #import "Mandarin.h" #import "Gramambular.h" -#import "FastLM.h" +#import "McBopomofoLM.h" #import "UserOverrideModel.h" @interface McBopomofoInputMethodController : IMKInputController @@ -46,9 +46,7 @@ Formosa::Mandarin::BopomofoReadingBuffer* _bpmfReadingBuffer; // language model - Formosa::Gramambular::FastLM *_languageModel; - Formosa::Gramambular::FastLM *_userPhrasesModel; - Formosa::Gramambular::FastLM *_excludedPhraseModel; + McBopomofo::McBopomofoLM *_languageModel; // user override model McBopomofo::UserOverrideModel *_userOverrideModel; diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 32b71d8c..d17ad3e6 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -48,6 +48,7 @@ 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 @@ -176,11 +177,9 @@ static double FindHighestScore(const vector& nodes, double epsilon) // create the lattice builder _languageModel = [LanguageModelManager languageModelMcBopomofo]; - _userPhrasesModel = [LanguageModelManager userPhraseLanguageModel]; _userOverrideModel = [LanguageModelManager userOverrideModel]; - _excludedPhraseModel = [LanguageModelManager excludedPhrasesLanguageModelMcBopomofo]; - _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel, _excludedPhraseModel); + _builder = new BlockReadingBuilder(_languageModel); // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); @@ -325,21 +324,15 @@ static double FindHighestScore(const vector& nodes, double epsilon) - (void)setValue:(id)value forTag:(long)tag client:(id)sender { NSString *newInputMode; - FastLM *newLanguageModel; - FastLM *newUserPhrasesModel; - FastLM *newExcludedPhraseModel; + McBopomofoLM *newLanguageModel; if ([value isKindOfClass:[NSString class]] && [value isEqual:kPlainBopomofoModeIdentifier]) { newInputMode = kPlainBopomofoModeIdentifier; newLanguageModel = [LanguageModelManager languageModelPlainBopomofo]; - newUserPhrasesModel = NULL; - newExcludedPhraseModel = [LanguageModelManager excludedPhrasesLanguageModelPlainBopomofo]; } else { newInputMode = kBopomofoModeIdentifier; newLanguageModel = [LanguageModelManager languageModelMcBopomofo]; - newUserPhrasesModel = [LanguageModelManager userPhraseLanguageModel]; - newExcludedPhraseModel = [LanguageModelManager excludedPhrasesLanguageModelMcBopomofo]; } // Only apply the changes if the value is changed @@ -355,8 +348,6 @@ static double FindHighestScore(const vector& nodes, double epsilon) _inputMode = newInputMode; _languageModel = newLanguageModel; - _userPhrasesModel = newUserPhrasesModel; - _excludedPhraseModel = newExcludedPhraseModel; if (!_bpmfReadingBuffer->isEmpty()) { _bpmfReadingBuffer->clear(); @@ -369,7 +360,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) if (_builder) { delete _builder; - _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel, _excludedPhraseModel); + _builder = new BlockReadingBuilder(_languageModel); _builder->setJoinSeparator("-"); } } @@ -1096,65 +1087,30 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return NO; } -- (vector)_collectUnigrams:(string)string -{ - vector unigrams; - vector userUnigrams; - - if (_userPhrasesModel != NULL && _userPhrasesModel->hasUnigramsForKey(string)) { - userUnigrams = _userPhrasesModel->unigramsForKeys(string); - } - - if (_languageModel->hasUnigramsForKey(string)) { - vector globalUnigrams = _languageModel->unigramsForKeys(string); - for (std::vector::iterator it=globalUnigrams.begin(); it!=globalUnigrams.end(); ++it) { - if (!_builder->checkIfUnigramExistInVector(*it, unigrams)) { - unigrams.push_back(*it); - } - } - } - unigrams.insert(unigrams.begin(), userUnigrams.begin(), userUnigrams.end()); - - if (_excludedPhraseModel != NULL && _excludedPhraseModel->hasUnigramsForKey(string)) { - vector excludedUnigrams = _excludedPhraseModel->unigramsForKeys(string); - vector newUnigram; - for (std::vector::iterator it=unigrams.begin(); it!=unigrams.end(); ++it) { - if (!_builder->checkIfUnigramExistInVector(*it, excludedUnigrams)) { - newUnigram.push_back(*it); - } - } - unigrams = newUnigram; - } - return unigrams; -} - - - (BOOL)handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client { - vector collected = [self _collectUnigrams:customPunctuation]; - if (!collected.size()) { - return NO; - } - - if (_bpmfReadingBuffer->isEmpty()) { - _builder->insertReadingAtCursor(customPunctuation); - [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]; + if (_languageModel->hasUnigramsForKey(customPunctuation)) { + if (_bpmfReadingBuffer->isEmpty()) { + _builder->insertReadingAtCursor(customPunctuation); + [self popOverflowComposingTextAndWalk:client]; } - else { - [self _showCandidateWindowUsingVerticalMode:useVerticalMode client: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; } - return YES; + return NO; } - (BOOL)handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h index 9ba88bb0..d819e067 100644 --- a/Source/LanguageModelManager.h +++ b/Source/LanguageModelManager.h @@ -1,6 +1,7 @@ #import #import "FastLM.h" #import "UserOverrideModel.h" +#import "McBopomofoLM.h" NS_ASSUME_NONNULL_BEGIN @@ -15,11 +16,8 @@ NS_ASSUME_NONNULL_BEGIN @property (class, readonly, nonatomic) NSString *userPhrasesDataPathMcBopomofo; @property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathMcBopomofo; @property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathPlainBopomofo; -@property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *languageModelMcBopomofo; -@property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *languageModelPlainBopomofo; -@property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *userPhraseLanguageModel; -@property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *excludedPhrasesLanguageModelMcBopomofo; -@property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *excludedPhrasesLanguageModelPlainBopomofo; +@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelMcBopomofo; +@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelPlainBopomofo; @property (class, readonly, nonatomic) McBopomofo::UserOverrideModel *userOverrideModel; @end diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 68924f54..ba632b67 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -7,61 +7,35 @@ 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. -FastLM globalLanguageModel; -FastLM globalLanguageModelPlainBopomofo; -FastLM globalUserPhraseLanguageModel; -FastLM globalUserExcludedPhrasesMcBopomofo; -FastLM globalUserExcludedPhrasesPlainBopomofo; -McBopomofo::UserOverrideModel globalUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); +McBopomofoLM gLanguageModelMcBopomofo; +McBopomofoLM gLanguageModelPlainBopomofo; +UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); @implementation LanguageModelManager -static bool LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM &lm) +static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomofoLM &lm) { Class cls = NSClassFromString(@"McBopomofoInputMethodController"); NSString *dataPath = [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"]; - bool result = lm.open([dataPath UTF8String]); - return (BOOL)result; + lm.loadLanguageModel([dataPath UTF8String]); } + (void)loadDataModels { - bool dataOpenResult = LTLoadLanguageModelFile(@"data", globalLanguageModel); - if (!dataOpenResult) { - NSLog(@"Failed to open language model."); - } - bool plainBpmfOpenResult = LTLoadLanguageModelFile(@"data-plain-bpmf", globalLanguageModelPlainBopomofo); - if (!plainBpmfOpenResult) { - NSLog(@"Failed to open language model for plain bpmf."); - } + LTLoadLanguageModelFile(@"data", gLanguageModelMcBopomofo); + LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo); } + (void)loadUserPhrasesModel { - globalUserPhraseLanguageModel.close(); - globalUserExcludedPhrasesMcBopomofo.close(); - globalUserExcludedPhrasesPlainBopomofo.close(); - - bool result = false; - - result = globalUserPhraseLanguageModel.open([[self userPhrasesDataPathMcBopomofo] UTF8String]); - if (!result) { - NSLog(@"Failed to open user phrases. %@", [self userPhrasesDataPathMcBopomofo]); - } - result = globalUserExcludedPhrasesMcBopomofo.open([[self excludedPhrasesDataPathMcBopomofo] UTF8String]); - if (!result) { - NSLog(@"Failed to open excluded phrases McBopomofo. %@", [self excludedPhrasesDataPathMcBopomofo]); - } - - result = globalUserExcludedPhrasesPlainBopomofo.open([[self excludedPhrasesDataPathPlainBopomofo] UTF8String]); - if (!result) { - NSLog(@"Failed to open excluded phrases Plain Bopomofo. %@", [self excludedPhrasesDataPathPlainBopomofo]); - } + gLanguageModelMcBopomofo.loadUserPhrases([[self userPhrasesDataPathMcBopomofo] UTF8String], [[self excludedPhrasesDataPathMcBopomofo] UTF8String]); + gLanguageModelPlainBopomofo.loadUserPhrases("", [[self excludedPhrasesDataPathPlainBopomofo] UTF8String]); } + (BOOL)checkIfUserDataFolderExists @@ -163,34 +137,19 @@ static bool LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM & return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases-plain-bpmf.txt"]; } - + (FastLM *)languageModelMcBopomofo + + (McBopomofoLM *)languageModelMcBopomofo { - return &globalLanguageModel; + return &gLanguageModelMcBopomofo; } -+ (FastLM *)languageModelPlainBopomofo ++ (McBopomofoLM *)languageModelPlainBopomofo { - return &globalLanguageModelPlainBopomofo; -} - -+ (FastLM *)userPhraseLanguageModel -{ - return &globalUserPhraseLanguageModel; -} - -+ (FastLM *)excludedPhrasesLanguageModelMcBopomofo -{ - return &globalUserExcludedPhrasesMcBopomofo; -} - -+ (FastLM *)excludedPhrasesLanguageModelPlainBopomofo -{ - return &globalUserExcludedPhrasesPlainBopomofo; + return &gLanguageModelPlainBopomofo; } + (McBopomofo::UserOverrideModel *)userOverrideModel { - return &globalUserOverrideModel; + return &gUserOverrideModel; } @end From 84fc2f068b0677babb13df16e7394d54676b872f Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 12 Jan 2022 13:16:10 +0800 Subject: [PATCH 8/9] Removes unused code and fixes a typo. --- .../Engine/Gramambular/BlockReadingBuilder.h | 12 -------- Source/Engine/McBopomofoLM.cpp | 30 +++++++++---------- Source/Engine/McBopomofoLM.h | 2 +- 3 files changed, 16 insertions(+), 28 deletions(-) diff --git a/Source/Engine/Gramambular/BlockReadingBuilder.h b/Source/Engine/Gramambular/BlockReadingBuilder.h index 145a0185..76d681a3 100644 --- a/Source/Engine/Gramambular/BlockReadingBuilder.h +++ b/Source/Engine/Gramambular/BlockReadingBuilder.h @@ -58,8 +58,6 @@ namespace Formosa { vector readingsAtRange(size_t begin, size_t end) const; Grid& grid(); - - bool checkIfUnigramExistInVector(Unigram& unigram, vectorvector); protected: void build(); @@ -198,16 +196,6 @@ namespace Formosa { return m_grid; } - inline bool BlockReadingBuilder::checkIfUnigramExistInVector(Unigram& unigram, vectorvector) - { - for (std::vector::iterator it=vector.begin(); it!=vector.end(); ++it) { - if (it->keyValue.value == unigram.keyValue.value) { - return true; - } - } - return false; - } - inline void BlockReadingBuilder::build() { if (!m_LM) { diff --git a/Source/Engine/McBopomofoLM.cpp b/Source/Engine/McBopomofoLM.cpp index e5bc741f..b7bfd1b5 100644 --- a/Source/Engine/McBopomofoLM.cpp +++ b/Source/Engine/McBopomofoLM.cpp @@ -13,7 +13,7 @@ McBopomofoLM::~McBopomofoLM() { m_languageModel.close(); m_userPhrases.close(); - m_excluddePhrases.close(); + m_excludedPhrases.close(); } void McBopomofoLM::loadLanguageModel(const char* languageModelDataPath) @@ -27,8 +27,8 @@ void McBopomofoLM::loadUserPhrases(const char* userPhrasesDataPath, { m_userPhrases.close(); m_userPhrases.open(userPhrasesDataPath); - m_excluddePhrases.close(); - m_excluddePhrases.open(excludedPhrasesDataPath); + m_excludedPhrases.close(); + m_excludedPhrases.open(excludedPhrasesDataPath); } const vector McBopomofoLM::bigramsForKeys(const string& preceedingKey, const string& key) @@ -40,35 +40,35 @@ const vector McBopomofoLM::unigramsForKey(const string& key) { vector unigrams; vector userUnigrams; - + // Use unordered_set so that you don't have to do O(n*m) unordered_set excludedValues; unordered_set userValues; - - if (m_excluddePhrases.hasUnigramsForKey(key)) { - vector excludedUnigrams = m_excluddePhrases.unigramsForKey(key); + + if (m_excludedPhrases.hasUnigramsForKey(key)) { + vector 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 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 globalUnigrams = m_languageModel.unigramsForKey(key); - + for (auto&& unigram : globalUnigrams) { if (excludedValues.find(unigram.keyValue.value) == excludedValues.end() && userValues.find(unigram.keyValue.value) == userValues.end()) { @@ -76,17 +76,17 @@ const vector McBopomofoLM::unigramsForKey(const string& key) } } } - + unigrams.insert(unigrams.begin(), userUnigrams.begin(), userUnigrams.end()); return unigrams; } bool McBopomofoLM::hasUnigramsForKey(const string& key) { - if (!m_excluddePhrases.hasUnigramsForKey(key)) { + if (!m_excludedPhrases.hasUnigramsForKey(key)) { return m_userPhrases.hasUnigramsForKey(key) || m_languageModel.hasUnigramsForKey(key); } - + return unigramsForKey(key).size() > 0; } diff --git a/Source/Engine/McBopomofoLM.h b/Source/Engine/McBopomofoLM.h index 19ad8246..292fbf61 100644 --- a/Source/Engine/McBopomofoLM.h +++ b/Source/Engine/McBopomofoLM.h @@ -24,7 +24,7 @@ public: protected: FastLM m_languageModel; FastLM m_userPhrases; - FastLM m_excluddePhrases; + FastLM m_excludedPhrases; }; }; From f1e56a7e01dd56516b72ca823fc10b786805fcdd Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 12 Jan 2022 13:17:41 +0800 Subject: [PATCH 9/9] Lets McBopomofoLM to accept NULL as the parameter in loadUserPhrases. --- Source/Engine/McBopomofoLM.cpp | 18 ++++++++++++------ Source/LanguageModelManager.mm | 2 +- Source/McBopomofo-Bridging-Header.h | 3 --- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Source/Engine/McBopomofoLM.cpp b/Source/Engine/McBopomofoLM.cpp index b7bfd1b5..0ffdaddd 100644 --- a/Source/Engine/McBopomofoLM.cpp +++ b/Source/Engine/McBopomofoLM.cpp @@ -18,17 +18,23 @@ McBopomofoLM::~McBopomofoLM() void McBopomofoLM::loadLanguageModel(const char* languageModelDataPath) { - m_languageModel.close(); - m_languageModel.open(languageModelDataPath); + if (languageModelDataPath) { + m_languageModel.close(); + m_languageModel.open(languageModelDataPath); + } } void McBopomofoLM::loadUserPhrases(const char* userPhrasesDataPath, const char* excludedPhrasesDataPath) { - m_userPhrases.close(); - m_userPhrases.open(userPhrasesDataPath); - m_excludedPhrases.close(); - m_excludedPhrases.open(excludedPhrasesDataPath); + if (userPhrasesDataPath) { + m_userPhrases.close(); + m_userPhrases.open(userPhrasesDataPath); + } + if (excludedPhrasesDataPath) { + m_excludedPhrases.close(); + m_excludedPhrases.open(excludedPhrasesDataPath); + } } const vector McBopomofoLM::bigramsForKeys(const string& preceedingKey, const string& key) diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index ba632b67..0af6d6eb 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -35,7 +35,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo + (void)loadUserPhrasesModel { gLanguageModelMcBopomofo.loadUserPhrases([[self userPhrasesDataPathMcBopomofo] UTF8String], [[self excludedPhrasesDataPathMcBopomofo] UTF8String]); - gLanguageModelPlainBopomofo.loadUserPhrases("", [[self excludedPhrasesDataPathPlainBopomofo] UTF8String]); + gLanguageModelPlainBopomofo.loadUserPhrases(NULL, [[self excludedPhrasesDataPathPlainBopomofo] UTF8String]); } + (BOOL)checkIfUserDataFolderExists diff --git a/Source/McBopomofo-Bridging-Header.h b/Source/McBopomofo-Bridging-Header.h index 6e2a1a13..51d288b9 100644 --- a/Source/McBopomofo-Bridging-Header.h +++ b/Source/McBopomofo-Bridging-Header.h @@ -2,9 +2,6 @@ // 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