Merge pull request #219 from zonble/dev/half_size_punctuation
Various new functions and UI components
This commit is contained in:
commit
915693bc28
|
@ -39,15 +39,16 @@
|
|||
D41355D8278D74B5005E5CBD /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = D41355D7278D7409005E5CBD /* LanguageModelManager.mm */; };
|
||||
D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D41355D9278E6D17005E5CBD /* McBopomofoLM.cpp */; };
|
||||
D41355DE278EA3ED005E5CBD /* UserPhrasesLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D41355DC278EA3ED005E5CBD /* UserPhrasesLM.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 */; };
|
||||
D427F7A927905E90004A2160 /* TooltipUI in Frameworks */ = {isa = PBXBuildFile; productRef = D427F7A827905E90004A2160 /* TooltipUI */; };
|
||||
D427F7AE27907B8A004A2160 /* NotifierUI in Frameworks */ = {isa = PBXBuildFile; productRef = D427F7AD27907B8A004A2160 /* NotifierUI */; };
|
||||
D427F7B4279086DC004A2160 /* InputSourceHelper in Frameworks */ = {isa = PBXBuildFile; productRef = D427F7B3279086DC004A2160 /* InputSourceHelper */; };
|
||||
D427F7B6279086F6004A2160 /* InputSourceHelper in Frameworks */ = {isa = PBXBuildFile; productRef = D427F7B5279086F6004A2160 /* InputSourceHelper */; };
|
||||
D427F7C127908EFC004A2160 /* OpenCCBridge in Frameworks */ = {isa = PBXBuildFile; productRef = D427F7C027908EFC004A2160 /* OpenCCBridge */; };
|
||||
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; };
|
||||
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; };
|
||||
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; };
|
||||
D47F7DD5278C25A0002F9DD7 /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD4278C25A0002F9DD7 /* InputSourceHelper.swift */; };
|
||||
D47F7DD6278C3075002F9DD7 /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD4278C25A0002F9DD7 /* InputSourceHelper.swift */; };
|
||||
D48550A325EBE689006A204C /* OpenCC in Frameworks */ = {isa = PBXBuildFile; productRef = D48550A225EBE689006A204C /* OpenCC */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -165,14 +166,16 @@
|
|||
D41355DC278EA3ED005E5CBD /* UserPhrasesLM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UserPhrasesLM.cpp; sourceTree = "<group>"; };
|
||||
D41355DD278EA3ED005E5CBD /* UserPhrasesLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserPhrasesLM.h; sourceTree = "<group>"; };
|
||||
D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "McBopomofo-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenCCBridge.swift; sourceTree = "<group>"; };
|
||||
D427F768278C9D0D004A2160 /* CandidateUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = CandidateUI; path = Packages/CandidateUI; sourceTree = "<group>"; };
|
||||
D427F76B278CA1BA004A2160 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
D427F7A727905E43004A2160 /* TooltipUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = TooltipUI; path = Packages/TooltipUI; sourceTree = "<group>"; };
|
||||
D427F7AC27907B7E004A2160 /* NotifierUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = NotifierUI; path = Packages/NotifierUI; sourceTree = "<group>"; };
|
||||
D427F7B2279086B5004A2160 /* InputSourceHelper */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = InputSourceHelper; path = Packages/InputSourceHelper; sourceTree = "<group>"; };
|
||||
D427F7BF27908EAC004A2160 /* OpenCCBridge */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = OpenCCBridge; path = Packages/OpenCCBridge; sourceTree = "<group>"; };
|
||||
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
|
||||
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; };
|
||||
D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = "<group>"; };
|
||||
D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = "<group>"; };
|
||||
D47F7DD4278C25A0002F9DD7 /* InputSourceHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceHelper.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -180,9 +183,12 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D427F7B4279086DC004A2160 /* InputSourceHelper in Frameworks */,
|
||||
D427F7C127908EFC004A2160 /* OpenCCBridge in Frameworks */,
|
||||
6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */,
|
||||
D48550A325EBE689006A204C /* OpenCC in Frameworks */,
|
||||
D427F7A927905E90004A2160 /* TooltipUI in Frameworks */,
|
||||
D427F76A278C9E29004A2160 /* CandidateUI in Frameworks */,
|
||||
D427F7AE27907B8A004A2160 /* NotifierUI in Frameworks */,
|
||||
6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -191,6 +197,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D427F7B6279086F6004A2160 /* InputSourceHelper in Frameworks */,
|
||||
6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -241,10 +248,8 @@
|
|||
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */,
|
||||
6A0D4EC815FC0D6400ABF4B3 /* main.m */,
|
||||
D427F76B278CA1BA004A2160 /* AppDelegate.swift */,
|
||||
D47F7DD4278C25A0002F9DD7 /* InputSourceHelper.swift */,
|
||||
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */,
|
||||
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */,
|
||||
D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */,
|
||||
6A0D4EF615FC0DA600ABF4B3 /* McBopomofo-Prefix.pch */,
|
||||
D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */,
|
||||
);
|
||||
|
@ -393,6 +398,10 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D427F768278C9D0D004A2160 /* CandidateUI */,
|
||||
D427F7A727905E43004A2160 /* TooltipUI */,
|
||||
D427F7AC27907B7E004A2160 /* NotifierUI */,
|
||||
D427F7B2279086B5004A2160 /* InputSourceHelper */,
|
||||
D427F7BF27908EAC004A2160 /* OpenCCBridge */,
|
||||
);
|
||||
name = Packages;
|
||||
sourceTree = "<group>";
|
||||
|
@ -432,8 +441,11 @@
|
|||
);
|
||||
name = McBopomofo;
|
||||
packageProductDependencies = (
|
||||
D48550A225EBE689006A204C /* OpenCC */,
|
||||
D427F769278C9E29004A2160 /* CandidateUI */,
|
||||
D427F7A827905E90004A2160 /* TooltipUI */,
|
||||
D427F7AD27907B8A004A2160 /* NotifierUI */,
|
||||
D427F7B3279086DC004A2160 /* InputSourceHelper */,
|
||||
D427F7C027908EFC004A2160 /* OpenCCBridge */,
|
||||
);
|
||||
productName = McBopomofo;
|
||||
productReference = 6A0D4EA215FC0D2D00ABF4B3 /* McBopomofo.app */;
|
||||
|
@ -454,6 +466,9 @@
|
|||
6ACA420115FC1DCC00935EF6 /* PBXTargetDependency */,
|
||||
);
|
||||
name = McBopomofoInstaller;
|
||||
packageProductDependencies = (
|
||||
D427F7B5279086F6004A2160 /* InputSourceHelper */,
|
||||
);
|
||||
productName = McBopomofoInstaller;
|
||||
productReference = 6ACA41CB15FC1D7500935EF6 /* McBopomofoInstaller.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
|
@ -482,7 +497,6 @@
|
|||
);
|
||||
mainGroup = 6A0D4E9215FC0CFA00ABF4B3;
|
||||
packageReferences = (
|
||||
D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */,
|
||||
);
|
||||
productRefGroup = 6A0D4EA315FC0D2D00ABF4B3 /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -558,9 +572,7 @@
|
|||
files = (
|
||||
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */,
|
||||
6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */,
|
||||
D47F7DD5278C25A0002F9DD7 /* InputSourceHelper.swift in Sources */,
|
||||
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */,
|
||||
D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */,
|
||||
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */,
|
||||
6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */,
|
||||
D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */,
|
||||
|
@ -579,7 +591,6 @@
|
|||
6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */,
|
||||
6A225A232367A1D700F685C6 /* ArchiveUtil.m in Sources */,
|
||||
6ACA41FF15FC1D9000935EF6 /* main.m in Sources */,
|
||||
D47F7DD6278C3075002F9DD7 /* InputSourceHelper.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1043,26 +1054,30 @@
|
|||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/ddddxxx/SwiftyOpenCC.git";
|
||||
requirement = {
|
||||
kind = revision;
|
||||
revision = 1d8105a0f7199c90af722bff62728050c858e777;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
D427F769278C9E29004A2160 /* CandidateUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = CandidateUI;
|
||||
};
|
||||
D48550A225EBE689006A204C /* OpenCC */ = {
|
||||
D427F7A827905E90004A2160 /* TooltipUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */;
|
||||
productName = OpenCC;
|
||||
productName = TooltipUI;
|
||||
};
|
||||
D427F7AD27907B8A004A2160 /* NotifierUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = NotifierUI;
|
||||
};
|
||||
D427F7B3279086DC004A2160 /* InputSourceHelper */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = InputSourceHelper;
|
||||
};
|
||||
D427F7B5279086F6004A2160 /* InputSourceHelper */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = InputSourceHelper;
|
||||
};
|
||||
D427F7C027908EFC004A2160 /* OpenCCBridge */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = OpenCCBridge;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
|
|
|
@ -203,7 +203,7 @@ public class HorizontalCandidateController: CandidateController {
|
|||
var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
|
||||
let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel]
|
||||
let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
|
||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel))
|
||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
|
||||
panel.hasShadow = true
|
||||
|
||||
contentRect.origin = NSPoint.zero
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
|
@ -0,0 +1,25 @@
|
|||
// swift-tools-version:5.5
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "InputSourceHelper",
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "InputSourceHelper",
|
||||
targets: ["InputSourceHelper"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "InputSourceHelper",
|
||||
dependencies: []),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
# InputSourceHelper
|
||||
|
||||
A description of this package.
|
|
@ -0,0 +1,7 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
|
@ -0,0 +1,25 @@
|
|||
// swift-tools-version:5.5
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "NotifierUI",
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "NotifierUI",
|
||||
targets: ["NotifierUI"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "NotifierUI",
|
||||
dependencies: []),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
# NotifierUI
|
||||
|
||||
A description of this package.
|
|
@ -0,0 +1,171 @@
|
|||
import Cocoa
|
||||
|
||||
private protocol NotifierWindowDelegate: AnyObject {
|
||||
func windowDidBecomeClicked(_ window: NotifierWindow)
|
||||
}
|
||||
|
||||
private class NotifierWindow: NSWindow {
|
||||
weak var clickDelegate: NotifierWindowDelegate?
|
||||
|
||||
override func mouseDown(with event: NSEvent) {
|
||||
clickDelegate?.windowDidBecomeClicked(self)
|
||||
}
|
||||
}
|
||||
|
||||
private let kWindowWidth: CGFloat = 160.0
|
||||
private let kWindowHeight: CGFloat = 80.0
|
||||
|
||||
public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
||||
private var messageTextField: NSTextField
|
||||
|
||||
private var message: String = "" {
|
||||
didSet {
|
||||
let paraStyle = NSMutableParagraphStyle()
|
||||
paraStyle.setParagraphStyle(NSParagraphStyle.default)
|
||||
paraStyle.alignment = .center
|
||||
let attr: [NSAttributedString.Key: AnyObject] = [
|
||||
.foregroundColor: foregroundColor,
|
||||
.font: NSFont.systemFont(ofSize: NSFont.systemFontSize(for: .regular)),
|
||||
.paragraphStyle: paraStyle
|
||||
]
|
||||
let attrString = NSAttributedString(string: message, attributes: attr)
|
||||
messageTextField.attributedStringValue = attrString
|
||||
let width = window?.frame.width ?? kWindowWidth
|
||||
let rect = attrString.boundingRect(with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin)
|
||||
let height = rect.height
|
||||
let x = messageTextField.frame.origin.x
|
||||
let y = ((window?.frame.height ?? kWindowHeight) - height) / 2
|
||||
let newFrame = NSRect(x: x, y: y, width: width, height: height)
|
||||
messageTextField.frame = newFrame
|
||||
}
|
||||
}
|
||||
private var shouldStay: Bool = false
|
||||
private var backgroundColor: NSColor = .black {
|
||||
didSet {
|
||||
self.window?.backgroundColor = backgroundColor
|
||||
self.messageTextField.backgroundColor = backgroundColor
|
||||
}
|
||||
}
|
||||
private var foregroundColor: NSColor = .white {
|
||||
didSet {
|
||||
self.messageTextField.textColor = foregroundColor
|
||||
}
|
||||
}
|
||||
private var waitTimer: Timer?
|
||||
private var fadeTimer: Timer?
|
||||
|
||||
private static var instanceCount = 0
|
||||
private static var lastLocation = NSPoint.zero
|
||||
|
||||
@objc public static func notify(message: String, stay: Bool = false) {
|
||||
let controller = NotifierController()
|
||||
controller.message = message
|
||||
controller.shouldStay = stay
|
||||
controller.show()
|
||||
}
|
||||
|
||||
private static func increaseInstanceCount() {
|
||||
instanceCount += 1
|
||||
}
|
||||
|
||||
private static func decreaseInstanceCount() {
|
||||
instanceCount -= 1
|
||||
if instanceCount < 0 {
|
||||
instanceCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
private init() {
|
||||
let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero
|
||||
let contentRect = NSRect(x: 0, y: 0, width: kWindowWidth, height: kWindowHeight)
|
||||
var windowRect = contentRect
|
||||
windowRect.origin.x = screenRect.maxX - windowRect.width - 10
|
||||
windowRect.origin.y = screenRect.maxY - windowRect.height - 10
|
||||
let styleMask: NSWindow.StyleMask = [.borderless]
|
||||
let panel = NotifierWindow(contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false)
|
||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel))
|
||||
panel.hasShadow = true
|
||||
panel.backgroundColor = backgroundColor
|
||||
|
||||
messageTextField = NSTextField()
|
||||
messageTextField.frame = contentRect
|
||||
messageTextField.isEditable = false
|
||||
messageTextField.isSelectable = false
|
||||
messageTextField.isBezeled = false
|
||||
messageTextField.textColor = foregroundColor
|
||||
messageTextField.drawsBackground = true
|
||||
messageTextField.backgroundColor = backgroundColor
|
||||
messageTextField.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small))
|
||||
panel.contentView?.addSubview(messageTextField)
|
||||
|
||||
super.init(window: panel)
|
||||
|
||||
panel.clickDelegate = self
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func show() {
|
||||
func setStartLocation() {
|
||||
if NotifierController.instanceCount == 0 {
|
||||
return
|
||||
}
|
||||
let lastLocation = NotifierController.lastLocation
|
||||
let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero
|
||||
var windowRect = self.window?.frame ?? NSRect.zero
|
||||
windowRect.origin.x = lastLocation.x
|
||||
windowRect.origin.y = lastLocation.y - 10 - windowRect.height
|
||||
|
||||
if windowRect.origin.y < screenRect.minY {
|
||||
return
|
||||
}
|
||||
|
||||
self.window?.setFrame(windowRect, display: true)
|
||||
}
|
||||
|
||||
func moveIn() {
|
||||
let afterRect = self.window?.frame ?? NSRect.zero
|
||||
NotifierController.lastLocation = afterRect.origin
|
||||
var beforeRect = afterRect
|
||||
beforeRect.origin.y += 10
|
||||
window?.setFrame(beforeRect, display: true)
|
||||
window?.orderFront(self)
|
||||
window?.setFrame(afterRect, display: true, animate: true)
|
||||
}
|
||||
|
||||
setStartLocation()
|
||||
moveIn()
|
||||
NotifierController.increaseInstanceCount()
|
||||
waitTimer = Timer.scheduledTimer(timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut), userInfo: nil, repeats: false)
|
||||
}
|
||||
|
||||
@objc private func doFadeOut(_ timer: Timer) {
|
||||
let opacity = self.window?.alphaValue ?? 0
|
||||
if opacity <= 0 {
|
||||
self.close()
|
||||
} else {
|
||||
self.window?.alphaValue = opacity - 0.2
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func fadeOut() {
|
||||
waitTimer?.invalidate()
|
||||
waitTimer = nil
|
||||
NotifierController.decreaseInstanceCount()
|
||||
fadeTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil, repeats: true)
|
||||
}
|
||||
|
||||
public override func close() {
|
||||
waitTimer?.invalidate()
|
||||
waitTimer = nil
|
||||
fadeTimer?.invalidate()
|
||||
fadeTimer = nil
|
||||
super.close()
|
||||
}
|
||||
|
||||
fileprivate func windowDidBecomeClicked(_ window: NotifierWindow) {
|
||||
self.fadeOut()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "SwiftyOpenCC",
|
||||
"repositoryURL": "https://github.com/ddddxxx/SwiftyOpenCC.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "a802c02cbf1c6fcd529575f11a9876d94fc813f4",
|
||||
"version": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// swift-tools-version:5.5
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "OpenCCBridge",
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "OpenCCBridge",
|
||||
targets: ["OpenCCBridge"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
.package(name: "SwiftyOpenCC", url: "https://github.com/ddddxxx/SwiftyOpenCC.git", revision: "a802c02cbf1c6fcd529575f11a9876d94fc813f4")
|
||||
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "OpenCCBridge",
|
||||
dependencies: [
|
||||
.product(name: "OpenCC", package: "SwiftyOpenCC")
|
||||
]),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
# OpenCCBridge
|
||||
|
||||
A description of this package.
|
|
@ -3,20 +3,16 @@ import OpenCC
|
|||
|
||||
// Since SwiftyOpenCC only provide Swift classes, we create an NSObject subclass
|
||||
// in Swift in order to bridge the Swift classes into our Objective-C++ project.
|
||||
class OpenCCBridge: NSObject {
|
||||
public class OpenCCBridge: NSObject {
|
||||
private static let shared = OpenCCBridge()
|
||||
private var converter: ChineseConverter?
|
||||
|
||||
override init() {
|
||||
private override init() {
|
||||
try? converter = ChineseConverter(options: .simplify)
|
||||
super.init()
|
||||
}
|
||||
|
||||
@objc static func convert(_ string: String) -> String? {
|
||||
@objc public static func convert(_ string: String) -> String? {
|
||||
shared.converter?.convert(string)
|
||||
}
|
||||
|
||||
private func convert(_ string: String) -> String? {
|
||||
converter?.convert(string)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import XCTest
|
||||
@testable import OpenCCBridge
|
||||
|
||||
final class OpenCCBridgeTests: XCTestCase {
|
||||
func testExample() throws {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
XCTAssertEqual(OpenCCBridge().text, "Hello, World!")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
|
@ -0,0 +1,25 @@
|
|||
// swift-tools-version:5.5
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "TooltipUI",
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "TooltipUI",
|
||||
targets: ["TooltipUI"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "TooltipUI",
|
||||
dependencies: []),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
# TooltipUI
|
||||
|
||||
A description of this package.
|
|
@ -0,0 +1,65 @@
|
|||
import Cocoa
|
||||
|
||||
public class TooltipController: NSWindowController {
|
||||
private let backgroundColor = NSColor(calibratedHue: 0.16, saturation: 0.22, brightness: 0.97, alpha: 1.0)
|
||||
private var messageTextField: NSTextField
|
||||
private var tooltip: String = "" {
|
||||
didSet {
|
||||
messageTextField.stringValue = tooltip
|
||||
adjustSize()
|
||||
}
|
||||
}
|
||||
|
||||
public init() {
|
||||
let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0)
|
||||
let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel]
|
||||
let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
|
||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
|
||||
panel.hasShadow = true
|
||||
|
||||
messageTextField = NSTextField()
|
||||
messageTextField.isEditable = false
|
||||
messageTextField.isSelectable = false
|
||||
messageTextField.isBezeled = false
|
||||
messageTextField.textColor = .black
|
||||
messageTextField.drawsBackground = true
|
||||
messageTextField.backgroundColor = backgroundColor
|
||||
messageTextField.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small))
|
||||
panel.contentView?.addSubview(messageTextField)
|
||||
|
||||
super.init(window: panel)
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc(showTooltip:atPoint:)
|
||||
public func show(tooltip: String, at point: NSPoint) {
|
||||
self.tooltip = tooltip
|
||||
window?.orderFront(nil)
|
||||
set(windowLocation: point)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func hide() {
|
||||
window?.orderOut(nil)
|
||||
}
|
||||
|
||||
private func set(windowLocation location: NSPoint) {
|
||||
var newPoint = location
|
||||
if location.y > 5 {
|
||||
newPoint.y -= 5
|
||||
}
|
||||
window?.setFrameTopLeftPoint(newPoint)
|
||||
}
|
||||
|
||||
private func adjustSize() {
|
||||
let attrString = messageTextField.attributedStringValue;
|
||||
var rect = attrString.boundingRect(with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin)
|
||||
rect.size.width += 10
|
||||
messageTextField.frame = rect
|
||||
window?.setFrame(rect, display: true)
|
||||
}
|
||||
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
## 系統需求
|
||||
|
||||
小麥注音輸入法可以在 macOS 10.10 以上版本運作。如果您要自行編譯小麥注音輸入法,或參與開發,您需要:
|
||||
|
||||
- macOS 10.15 Catalina 以上版本
|
||||
- Xcode 12.0 以上版本
|
||||
|
||||
|
@ -18,4 +20,3 @@
|
|||
## 軟體授權
|
||||
|
||||
本專案採用 MIT License 釋出,使用者可自由使用、散播本軟體,惟散播時必須完整保留版權聲明及軟體授權([詳全文](https://github.com/openvanilla/McBopomofo/blob/master/LICENSE.txt))。
|
||||
|
||||
|
|
|
@ -75,5 +75,6 @@
|
|||
|
||||
// if Chinese conversion is enabled
|
||||
BOOL _chineseConversionEnabled;
|
||||
BOOL _halfWidthPunctuationEnabled;
|
||||
}
|
||||
@end
|
||||
|
|
|
@ -42,7 +42,9 @@
|
|||
#import "McBopomofo-Swift.h"
|
||||
|
||||
@import CandidateUI;
|
||||
@import OpenCC;
|
||||
@import NotifierUI;
|
||||
@import TooltipUI;
|
||||
@import OpenCCBridge;
|
||||
|
||||
// C++ namespace usages
|
||||
using namespace std;
|
||||
|
@ -78,6 +80,7 @@ static NSString *const kUseHorizontalCandidateListPreferenceKey = @"UseHorizonta
|
|||
static NSString *const kComposingBufferSizePreferenceKey = @"ComposingBufferSize";
|
||||
static NSString *const kChooseCandidateUsingSpaceKey = @"ChooseCandidateUsingSpaceKey";
|
||||
static NSString *const kChineseConversionEnabledKey = @"ChineseConversionEnabledKey";
|
||||
static NSString *const kHalfWidthPunctuationEnabledKey = @"HalfWidthPunctuationEnabledKey";
|
||||
static NSString *const kEscToCleanInputBufferKey = @"EscToCleanInputBufferKey";
|
||||
|
||||
// advanced (usually optional) settings
|
||||
|
@ -103,6 +106,16 @@ enum {
|
|||
kDeleteKeyCode = 117
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, McBpomofoEmacsKey) {
|
||||
McBpomofoEmacsKeyNone,
|
||||
McBpomofoEmacsKeyForward,
|
||||
McBpomofoEmacsKeyBackward,
|
||||
McBpomofoEmacsKeyHome,
|
||||
McBpomofoEmacsKeyEnd,
|
||||
McBpomofoEmacsKeyDelete,
|
||||
McBpomofoEmacsKeyNextPage,
|
||||
};
|
||||
|
||||
VTCandidateController *gCurrentCandidateController = nil;
|
||||
|
||||
// if DEBUG is defined, a DOT file (GraphViz format) will be written to the
|
||||
|
@ -189,6 +202,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
|
||||
_inputMode = kBopomofoModeIdentifier;
|
||||
_chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey];
|
||||
_halfWidthPunctuationEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kHalfWidthPunctuationEnabledKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
|
@ -201,11 +215,15 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
NSMenuItem *preferenceMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"McBopomofo Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""];
|
||||
[menu addItem:preferenceMenuItem];
|
||||
|
||||
NSMenuItem *chineseConversionMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"G"];
|
||||
NSMenuItem *chineseConversionMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"g"];
|
||||
chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl;
|
||||
chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff;
|
||||
[menu addItem:chineseConversionMenuItem];
|
||||
|
||||
NSMenuItem *halfWidthPunctuationMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@""];
|
||||
halfWidthPunctuationMenuItem.state = _halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff;
|
||||
[menu addItem:halfWidthPunctuationMenuItem];
|
||||
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
[menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""];
|
||||
|
||||
|
@ -319,6 +337,8 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
gCurrentCandidateController.delegate = nil;
|
||||
gCurrentCandidateController.visible = NO;
|
||||
[_candidates removeAllObjects];
|
||||
|
||||
[self _hideTooltip];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)value forTag:(long)tag client:(id)sender
|
||||
|
@ -382,7 +402,8 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
|
||||
// Chinese conversion.
|
||||
NSString *buffer = _composingBuffer;
|
||||
if (_chineseConversionEnabled) {
|
||||
BOOL chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey];
|
||||
if (chineseConversionEnabled) {
|
||||
buffer = [OpenCCBridge convert:_composingBuffer];
|
||||
}
|
||||
|
||||
|
@ -393,6 +414,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
[_composingBuffer setString:@""];
|
||||
gCurrentCandidateController.visible = NO;
|
||||
[_candidates removeAllObjects];
|
||||
[self _hideTooltip];
|
||||
}
|
||||
|
||||
NS_INLINE size_t min(size_t a, size_t b) { return a < b ? a : b; }
|
||||
|
@ -472,6 +494,7 @@ 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();
|
||||
[self _showCurrentMarkedTextTooltipWithClient:client];
|
||||
}
|
||||
else {
|
||||
// we must use NSAttributedString so that the cursor is visible --
|
||||
|
@ -484,6 +507,7 @@ 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(cursorIndex, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
_latestReadingCursor = cursorIndex;
|
||||
[self _hideTooltip];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -542,7 +566,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
NodeAnchor &anchor = _walkedNodes[0];
|
||||
NSString *popedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()];
|
||||
// Chinese conversion.
|
||||
if (_chineseConversionEnabled) {
|
||||
BOOL chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey];
|
||||
if (chineseConversionEnabled) {
|
||||
popedText = [OpenCCBridge convert:popedText];
|
||||
}
|
||||
[client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
|
@ -559,7 +584,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
NSBeep();
|
||||
}
|
||||
|
||||
- (string)currentLayout
|
||||
- (string)_currentLayout
|
||||
{
|
||||
string layout = string("Standard_");;
|
||||
NSInteger keyboardLayout = [[NSUserDefaults standardUserDefaults] integerForKey:kKeyboardLayoutPreferenceKey];
|
||||
|
@ -588,45 +613,32 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
return layout;
|
||||
}
|
||||
|
||||
- (NSString *)_currentMarkedText
|
||||
- (McBpomofoEmacsKey)_detectEmacsKeyFromCharCode:(UniChar)charCode modifiers:(NSUInteger)flags
|
||||
{
|
||||
if (_builder->markerCursorIndex() < 0) {
|
||||
return @"";
|
||||
if (flags & NSControlKeyMask) {
|
||||
char c = charCode + 'a' - 1;
|
||||
if (c == 'a') {
|
||||
return McBpomofoEmacsKeyHome;
|
||||
}
|
||||
else if (c == 'e') {
|
||||
return McBpomofoEmacsKeyEnd;
|
||||
}
|
||||
else if (c == 'f') {
|
||||
return McBpomofoEmacsKeyForward;
|
||||
}
|
||||
else if (c == 'b') {
|
||||
return McBpomofoEmacsKeyBackward;
|
||||
}
|
||||
else if (c == 'd') {
|
||||
return McBpomofoEmacsKeyDelete;
|
||||
}
|
||||
else if (c == 'v') {
|
||||
return McBpomofoEmacsKeyNextPage;
|
||||
}
|
||||
}
|
||||
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex());
|
||||
size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex());
|
||||
// A phrase should contian at least two characters.
|
||||
if (end - begin < 2) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin));
|
||||
NSString *reading = [_composingBuffer substringWithRange:range];
|
||||
NSMutableString *string = [[NSMutableString alloc] init];
|
||||
[string appendString:reading];
|
||||
[string appendString:@" "];
|
||||
NSMutableArray *readingsArray = [[NSMutableArray alloc] init];
|
||||
vector<std::string> v = _builder->readingsAtRange(begin, end);
|
||||
for(vector<std::string>::iterator it_i=v.begin(); it_i!=v.end(); ++it_i) {
|
||||
[readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]];
|
||||
}
|
||||
[string appendString:[readingsArray componentsJoinedByString:@"-"]];
|
||||
return string;
|
||||
return McBpomofoEmacsKeyNone;
|
||||
}
|
||||
|
||||
- (BOOL)_writeUserPhrase
|
||||
{
|
||||
NSString *currentMarkedPhrase = [self _currentMarkedText];
|
||||
if (![currentMarkedPhrase length]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [LanguageModelManager writeUserPhrase:currentMarkedPhrase];
|
||||
}
|
||||
|
||||
- (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client
|
||||
{
|
||||
|
@ -653,6 +665,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
// get the unicode character code
|
||||
UniChar charCode = [inputText length] ? [inputText characterAtIndex:0] : 0;
|
||||
|
||||
McBpomofoEmacsKey emacsKey = [self _detectEmacsKeyFromCharCode:charCode modifiers:flags];
|
||||
|
||||
if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && [NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) {
|
||||
// special handling for com.apple.Terminal
|
||||
_currentDeferredClient = client;
|
||||
|
@ -664,7 +678,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
|
||||
// if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it
|
||||
if (![_composingBuffer length] && _bpmfReadingBuffer->isEmpty() && ((flags & NSCommandKeyMask) || (flags & NSControlKeyMask) || (flags & NSAlternateKeyMask) || (flags & NSNumericPadKeyMask))) {
|
||||
if (![_composingBuffer length] &&
|
||||
_bpmfReadingBuffer->isEmpty() &&
|
||||
((flags & NSCommandKeyMask) || (flags & NSControlKeyMask) || (flags & NSAlternateKeyMask) || (flags & NSNumericPadKeyMask))) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
@ -708,7 +724,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
|
||||
// if we have candidate, it means we need to pass the event to the candidate handler
|
||||
if ([_candidates count]) {
|
||||
return [self handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode];
|
||||
return [self _handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode emacsKey:(McBpomofoEmacsKey)emacsKey];
|
||||
}
|
||||
|
||||
// If we have marker index.
|
||||
|
@ -731,7 +747,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
return YES;
|
||||
}
|
||||
// Shift + left
|
||||
if (keyCode == cursorBackwardKey && (flags & NSShiftKeyMask)) {
|
||||
if ((keyCode == cursorBackwardKey || emacsKey == McBpomofoEmacsKeyBackward)
|
||||
&& (flags & NSShiftKeyMask)) {
|
||||
if (_builder->markerCursorIndex() > 0) {
|
||||
_builder->setMarkerCursorIndex(_builder->markerCursorIndex() - 1);
|
||||
}
|
||||
|
@ -742,7 +759,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
return YES;
|
||||
}
|
||||
// Shift + Right
|
||||
if (keyCode == cursorForwardKey && (flags & NSShiftKeyMask)) {
|
||||
if ((keyCode == cursorForwardKey || emacsKey == McBpomofoEmacsKeyForward)
|
||||
&& (flags & NSShiftKeyMask)) {
|
||||
if (_builder->markerCursorIndex() < _builder->length()) {
|
||||
_builder->setMarkerCursorIndex(_builder->markerCursorIndex() + 1);
|
||||
}
|
||||
|
@ -875,7 +893,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
|
||||
// handle cursor backward
|
||||
if (keyCode == cursorBackwardKey) {
|
||||
if (keyCode == cursorBackwardKey || emacsKey == McBpomofoEmacsKeyBackward) {
|
||||
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||
[self beep];
|
||||
}
|
||||
|
@ -907,7 +925,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
|
||||
// handle cursor forward
|
||||
if (keyCode == cursorForwardKey) {
|
||||
if (keyCode == cursorForwardKey || emacsKey == McBpomofoEmacsKeyForward) {
|
||||
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||
[self beep];
|
||||
}
|
||||
|
@ -937,7 +955,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
return YES;
|
||||
}
|
||||
|
||||
if (keyCode == kHomeKeyCode) {
|
||||
if (keyCode == kHomeKeyCode || emacsKey == McBpomofoEmacsKeyHome) {
|
||||
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||
[self beep];
|
||||
}
|
||||
|
@ -958,7 +976,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
return YES;
|
||||
}
|
||||
|
||||
if (keyCode == kEndKeyCode) {
|
||||
if (keyCode == kEndKeyCode || emacsKey == McBpomofoEmacsKeyEnd) {
|
||||
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||
[self beep];
|
||||
}
|
||||
|
@ -1011,7 +1029,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
|
||||
// Delete
|
||||
if (keyCode == kDeleteKeyCode) {
|
||||
if (keyCode == kDeleteKeyCode || emacsKey == McBpomofoEmacsKeyDelete) {
|
||||
if (_bpmfReadingBuffer->isEmpty()) {
|
||||
if (![_composingBuffer length]) {
|
||||
return NO;
|
||||
|
@ -1033,7 +1051,6 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
return YES;
|
||||
}
|
||||
|
||||
|
||||
// Enter
|
||||
if (charCode == 13) {
|
||||
if (![_composingBuffer length]) {
|
||||
|
@ -1061,15 +1078,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 ([self handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) {
|
||||
string layout = [self _currentLayout];
|
||||
string punctuationNamePrefix = (_halfWidthPunctuationEnabled ? string("_half_punctuation_"): string("_punctuation_"));
|
||||
string customPunctuation = punctuationNamePrefix + layout + string(1, (char)charCode);
|
||||
if ([self _handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// if nothing is matched, see if it's a punctuation key.
|
||||
string punctuation = string("_punctuation_") + string(1, (char)charCode);
|
||||
if ([self handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) {
|
||||
string punctuation = punctuationNamePrefix + string(1, (char)charCode);
|
||||
if ([self _handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
@ -1085,7 +1103,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client
|
||||
- (BOOL)_handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client
|
||||
{
|
||||
if (_languageModel->hasUnigramsForKey(customPunctuation)) {
|
||||
if (_bpmfReadingBuffer->isEmpty()) {
|
||||
|
@ -1111,7 +1129,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode
|
||||
- (BOOL)_handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode emacsKey:(McBpomofoEmacsKey)emacsKey
|
||||
{
|
||||
BOOL cancelCandidateKey =
|
||||
(charCode == 27) ||
|
||||
|
@ -1134,7 +1152,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
[self candidateController:gCurrentCandidateController didSelectCandidateAtIndex:gCurrentCandidateController.selectedCandidateIndex];
|
||||
return YES;
|
||||
}
|
||||
else if (charCode == 32 || keyCode == kPageDownKeyCode) {
|
||||
else if (charCode == 32 || keyCode == kPageDownKeyCode || emacsKey == McBpomofoEmacsKeyNextPage) {
|
||||
BOOL updated = [gCurrentCandidateController showNextPage];
|
||||
if (!updated) {
|
||||
[self beep];
|
||||
|
@ -1168,6 +1186,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
return YES;
|
||||
}
|
||||
}
|
||||
else if (emacsKey == McBpomofoEmacsKeyBackward) {
|
||||
BOOL updated = [gCurrentCandidateController highlightPreviousCandidate];
|
||||
if (!updated) {
|
||||
[self beep];
|
||||
}
|
||||
[self updateClientComposingBuffer:_currentCandidateClient];
|
||||
return YES;
|
||||
}
|
||||
else if (keyCode == kRightKeyCode) {
|
||||
if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) {
|
||||
BOOL updated = [gCurrentCandidateController highlightNextCandidate];
|
||||
|
@ -1186,6 +1212,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
return YES;
|
||||
}
|
||||
}
|
||||
else if (emacsKey == McBpomofoEmacsKeyForward) {
|
||||
BOOL updated = [gCurrentCandidateController highlightNextCandidate];
|
||||
if (!updated) {
|
||||
[self beep];
|
||||
}
|
||||
[self updateClientComposingBuffer:_currentCandidateClient];
|
||||
return YES;
|
||||
}
|
||||
else if (keyCode == kUpKeyCode) {
|
||||
if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) {
|
||||
BOOL updated = [gCurrentCandidateController showPreviousPage];
|
||||
|
@ -1222,7 +1256,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
return YES;
|
||||
}
|
||||
}
|
||||
else if (keyCode == kHomeKeyCode) {
|
||||
else if (keyCode == kHomeKeyCode || emacsKey == McBpomofoEmacsKeyHome) {
|
||||
if (gCurrentCandidateController.selectedCandidateIndex == 0) {
|
||||
[self beep];
|
||||
|
||||
|
@ -1234,7 +1268,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
[self updateClientComposingBuffer:_currentCandidateClient];
|
||||
return YES;
|
||||
}
|
||||
else if (keyCode == kEndKeyCode && [_candidates count] > 0) {
|
||||
else if ((keyCode == kEndKeyCode || emacsKey == McBpomofoEmacsKeyEnd) && [_candidates count] > 0) {
|
||||
if (gCurrentCandidateController.selectedCandidateIndex == [_candidates count] - 1) {
|
||||
[self beep];
|
||||
}
|
||||
|
@ -1264,7 +1298,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
|
||||
if (_inputMode == kPlainBopomofoModeIdentifier) {
|
||||
string layout = [self currentLayout];
|
||||
string layout = [self _currentLayout];
|
||||
string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode);
|
||||
string punctuation = string("_punctuation_") + string(1, (char)charCode);
|
||||
|
||||
|
@ -1333,24 +1367,30 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
+ (VTHorizontalCandidateController *)horizontalCandidateController
|
||||
{
|
||||
static VTHorizontalCandidateController *instance = nil;
|
||||
@synchronized(self) {
|
||||
if (!instance) {
|
||||
instance = [[VTHorizontalCandidateController alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[VTHorizontalCandidateController alloc] init];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (VTVerticalCandidateController *)verticalCandidateController
|
||||
{
|
||||
static VTVerticalCandidateController *instance = nil;
|
||||
@synchronized(self) {
|
||||
if (!instance) {
|
||||
instance = [[VTVerticalCandidateController alloc] init];
|
||||
}
|
||||
}
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[VTVerticalCandidateController alloc] init];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (TooltipController *)tooltipController
|
||||
{
|
||||
static TooltipController *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[TooltipController alloc] init];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
@ -1474,6 +1514,113 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
gCurrentCandidateController.visible = YES;
|
||||
}
|
||||
|
||||
#pragma mark - User phrases
|
||||
|
||||
- (NSString *)_currentMarkedText
|
||||
{
|
||||
if (_builder->markerCursorIndex() < 0) {
|
||||
return @"";
|
||||
}
|
||||
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex());
|
||||
size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex());
|
||||
// A phrase should contian at least two characters.
|
||||
if (end - begin < 1) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin));
|
||||
NSString *selectedText = [_composingBuffer substringWithRange:range];
|
||||
return selectedText;
|
||||
}
|
||||
|
||||
- (NSString *)_currentMarkedTextAndReadings
|
||||
{
|
||||
if (_builder->markerCursorIndex() < 0) {
|
||||
return @"";
|
||||
}
|
||||
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex());
|
||||
size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex());
|
||||
// A phrase should contian at least two characters.
|
||||
if (end - begin < 2) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin));
|
||||
NSString *selectedText = [_composingBuffer substringWithRange:range];
|
||||
NSMutableString *string = [[NSMutableString alloc] init];
|
||||
[string appendString:selectedText];
|
||||
[string appendString:@" "];
|
||||
NSMutableArray *readingsArray = [[NSMutableArray alloc] init];
|
||||
vector<std::string> v = _builder->readingsAtRange(begin, end);
|
||||
for(vector<std::string>::iterator it_i=v.begin(); it_i!=v.end(); ++it_i) {
|
||||
[readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]];
|
||||
}
|
||||
[string appendString:[readingsArray componentsJoinedByString:@"-"]];
|
||||
return string;
|
||||
}
|
||||
|
||||
- (BOOL)_writeUserPhrase
|
||||
{
|
||||
NSString *currentMarkedPhrase = [self _currentMarkedTextAndReadings];
|
||||
if (![currentMarkedPhrase length]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [LanguageModelManager writeUserPhrase:currentMarkedPhrase];
|
||||
}
|
||||
|
||||
- (void)_showCurrentMarkedTextTooltipWithClient:(id)client
|
||||
{
|
||||
NSString *text = [self _currentMarkedText];
|
||||
NSInteger length = text.length;
|
||||
if (!length) {
|
||||
[self _hideTooltip];
|
||||
}
|
||||
else if (length == 1) {
|
||||
NSString *messsage = [NSString stringWithFormat:NSLocalizedString(@"You are now selecting \"%@\". You can add a phrase with two or more characters.", @""), text];
|
||||
[self _showTooltip:messsage client:client];
|
||||
}
|
||||
else {
|
||||
NSString *messsage = [NSString stringWithFormat:NSLocalizedString(@"You are now selecting \"%@\". Press enter to add a new phrase.", @""), text];
|
||||
[self _showTooltip:messsage client:client];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_showTooltip:(NSString *)tooltip client:(id)client
|
||||
{
|
||||
NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0);
|
||||
|
||||
NSInteger cursor = _latestReadingCursor;
|
||||
if (cursor == [_composingBuffer length] && cursor != 0) {
|
||||
cursor--;
|
||||
}
|
||||
|
||||
// some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch
|
||||
@try {
|
||||
[client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSLog(@"%@", exception);
|
||||
}
|
||||
|
||||
[[McBopomofoInputMethodController tooltipController] showTooltip:tooltip atPoint:lineHeightRect.origin];
|
||||
}
|
||||
|
||||
- (void)_hideTooltip
|
||||
{
|
||||
if ([McBopomofoInputMethodController tooltipController].window.isVisible) {
|
||||
[[McBopomofoInputMethodController tooltipController] hide];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Misc menu items
|
||||
|
||||
- (void)showPreferences:(id)sender
|
||||
|
@ -1546,8 +1693,20 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
{
|
||||
_chineseConversionEnabled = !_chineseConversionEnabled;
|
||||
[[NSUserDefaults standardUserDefaults] setBool:_chineseConversionEnabled forKey:kChineseConversionEnabledKey];
|
||||
|
||||
[NotifierController notifyWithMessage:
|
||||
_chineseConversionEnabled ?
|
||||
NSLocalizedString(@"Chinese conversion on", @"") :
|
||||
NSLocalizedString(@"Chinese conversion off", @"") stay:NO];
|
||||
}
|
||||
|
||||
- (void)toggleHalfWidthPunctuation:(id)sender
|
||||
{
|
||||
_halfWidthPunctuationEnabled = !_halfWidthPunctuationEnabled;
|
||||
[[NSUserDefaults standardUserDefaults] setBool:_halfWidthPunctuationEnabled forKey:kHalfWidthPunctuationEnabledKey];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
|
||||
#import "AppDelegate.h"
|
||||
#import <sys/mount.h>
|
||||
#import "McBopomofoInstaller-Swift.h"
|
||||
//#import "McBopomofoInstaller-Swift.h"
|
||||
@import InputSourceHelper;
|
||||
|
||||
static NSString *const kTargetBin = @"McBopomofo";
|
||||
static NSString *const kTargetType = @"app";
|
||||
|
|
|
@ -62,3 +62,13 @@
|
|||
"Please check the permission of at \"%@\"." = "Please check the permission of at \"%@\".";
|
||||
|
||||
"Edit Excluded Phrases" = "Edit Excluded Phrases";
|
||||
|
||||
"Use Half-Width Punctuations" = "Use Half-Width Punctuations";
|
||||
|
||||
"You are now selecting \"%@\". You can add a phrase with two or more characters." = "You are now selecting \"%@\". You can add a phrase with two or more characters.";
|
||||
|
||||
"You are now selecting \"%@\". Press enter to add a new phrase." = "You are now selecting \"%@\". Press enter to add a new phrase.";
|
||||
|
||||
"Chinese conversion on" = "Chinese conversion on";
|
||||
|
||||
"Chinese conversion off" = "Chinese conversion off";
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <InputMethodKit/InputMethodKit.h>
|
||||
#import "McBopomofo-Swift.h"
|
||||
@import InputSourceHelper;
|
||||
|
||||
static NSString *const kConnectionName = @"McBopomofo_1_Connection";
|
||||
|
||||
|
|
|
@ -63,3 +63,12 @@
|
|||
|
||||
"Edit Excluded Phrases" = "編輯要排除的詞彙";
|
||||
|
||||
"Use Half-Width Punctuations" = "使用半型標點符號";
|
||||
|
||||
"You are now selecting \"%@\". You can add a phrase with two or more characters." = "您目前選擇了 \"%@\"。請選擇兩個字以上,才能加入使用者詞彙。";
|
||||
|
||||
"You are now selecting \"%@\". Press enter to add a new phrase." = "您目前選擇了 \"%@\"。按下 Enter 就可以加入到使用者詞彙中。";
|
||||
|
||||
"Chinese conversion on" = "已經切換到簡體中文模式";
|
||||
|
||||
"Chinese conversion off" = "已經切換到繁體中文模式";
|
||||
|
|
Loading…
Reference in New Issue