Merge pull request #219 from zonble/dev/half_size_punctuation

Various new functions and UI components
This commit is contained in:
Weizhong Yang a.k.a zonble 2022-01-14 14:40:20 +08:00 committed by GitHub
commit 915693bc28
27 changed files with 710 additions and 111 deletions

View File

@ -39,15 +39,16 @@
D41355D8278D74B5005E5CBD /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = D41355D7278D7409005E5CBD /* LanguageModelManager.mm */; }; D41355D8278D74B5005E5CBD /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = D41355D7278D7409005E5CBD /* LanguageModelManager.mm */; };
D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D41355D9278E6D17005E5CBD /* McBopomofoLM.cpp */; }; D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D41355D9278E6D17005E5CBD /* McBopomofoLM.cpp */; };
D41355DE278EA3ED005E5CBD /* UserPhrasesLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D41355DC278EA3ED005E5CBD /* UserPhrasesLM.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 */; }; D427F76A278C9E29004A2160 /* CandidateUI in Frameworks */ = {isa = PBXBuildFile; productRef = D427F769278C9E29004A2160 /* CandidateUI */; };
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427F76B278CA1BA004A2160 /* AppDelegate.swift */; }; 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 */; }; D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; };
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; }; D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; };
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; }; 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 */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -165,14 +166,16 @@
D41355DC278EA3ED005E5CBD /* UserPhrasesLM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UserPhrasesLM.cpp; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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 */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -180,9 +183,12 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D427F7B4279086DC004A2160 /* InputSourceHelper in Frameworks */,
D427F7C127908EFC004A2160 /* OpenCCBridge in Frameworks */,
6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */, 6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */,
D48550A325EBE689006A204C /* OpenCC in Frameworks */, D427F7A927905E90004A2160 /* TooltipUI in Frameworks */,
D427F76A278C9E29004A2160 /* CandidateUI in Frameworks */, D427F76A278C9E29004A2160 /* CandidateUI in Frameworks */,
D427F7AE27907B8A004A2160 /* NotifierUI in Frameworks */,
6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */, 6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -191,6 +197,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D427F7B6279086F6004A2160 /* InputSourceHelper in Frameworks */,
6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */, 6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -241,10 +248,8 @@
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */, D41355D7278D7409005E5CBD /* LanguageModelManager.mm */,
6A0D4EC815FC0D6400ABF4B3 /* main.m */, 6A0D4EC815FC0D6400ABF4B3 /* main.m */,
D427F76B278CA1BA004A2160 /* AppDelegate.swift */, D427F76B278CA1BA004A2160 /* AppDelegate.swift */,
D47F7DD4278C25A0002F9DD7 /* InputSourceHelper.swift */,
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */, D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */,
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */, D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */,
D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */,
6A0D4EF615FC0DA600ABF4B3 /* McBopomofo-Prefix.pch */, 6A0D4EF615FC0DA600ABF4B3 /* McBopomofo-Prefix.pch */,
D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */, D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */,
); );
@ -393,6 +398,10 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D427F768278C9D0D004A2160 /* CandidateUI */, D427F768278C9D0D004A2160 /* CandidateUI */,
D427F7A727905E43004A2160 /* TooltipUI */,
D427F7AC27907B7E004A2160 /* NotifierUI */,
D427F7B2279086B5004A2160 /* InputSourceHelper */,
D427F7BF27908EAC004A2160 /* OpenCCBridge */,
); );
name = Packages; name = Packages;
sourceTree = "<group>"; sourceTree = "<group>";
@ -432,8 +441,11 @@
); );
name = McBopomofo; name = McBopomofo;
packageProductDependencies = ( packageProductDependencies = (
D48550A225EBE689006A204C /* OpenCC */,
D427F769278C9E29004A2160 /* CandidateUI */, D427F769278C9E29004A2160 /* CandidateUI */,
D427F7A827905E90004A2160 /* TooltipUI */,
D427F7AD27907B8A004A2160 /* NotifierUI */,
D427F7B3279086DC004A2160 /* InputSourceHelper */,
D427F7C027908EFC004A2160 /* OpenCCBridge */,
); );
productName = McBopomofo; productName = McBopomofo;
productReference = 6A0D4EA215FC0D2D00ABF4B3 /* McBopomofo.app */; productReference = 6A0D4EA215FC0D2D00ABF4B3 /* McBopomofo.app */;
@ -454,6 +466,9 @@
6ACA420115FC1DCC00935EF6 /* PBXTargetDependency */, 6ACA420115FC1DCC00935EF6 /* PBXTargetDependency */,
); );
name = McBopomofoInstaller; name = McBopomofoInstaller;
packageProductDependencies = (
D427F7B5279086F6004A2160 /* InputSourceHelper */,
);
productName = McBopomofoInstaller; productName = McBopomofoInstaller;
productReference = 6ACA41CB15FC1D7500935EF6 /* McBopomofoInstaller.app */; productReference = 6ACA41CB15FC1D7500935EF6 /* McBopomofoInstaller.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
@ -482,7 +497,6 @@
); );
mainGroup = 6A0D4E9215FC0CFA00ABF4B3; mainGroup = 6A0D4E9215FC0CFA00ABF4B3;
packageReferences = ( packageReferences = (
D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */,
); );
productRefGroup = 6A0D4EA315FC0D2D00ABF4B3 /* Products */; productRefGroup = 6A0D4EA315FC0D2D00ABF4B3 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -558,9 +572,7 @@
files = ( files = (
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */, D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */,
6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */, 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */,
D47F7DD5278C25A0002F9DD7 /* InputSourceHelper.swift in Sources */,
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */, D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */,
D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */,
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */, D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */,
6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */,
D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */, D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */,
@ -579,7 +591,6 @@
6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */, 6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */,
6A225A232367A1D700F685C6 /* ArchiveUtil.m in Sources */, 6A225A232367A1D700F685C6 /* ArchiveUtil.m in Sources */,
6ACA41FF15FC1D9000935EF6 /* main.m in Sources */, 6ACA41FF15FC1D9000935EF6 /* main.m in Sources */,
D47F7DD6278C3075002F9DD7 /* InputSourceHelper.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -1043,26 +1054,30 @@
}; };
/* End XCConfigurationList section */ /* 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 */ /* Begin XCSwiftPackageProductDependency section */
D427F769278C9E29004A2160 /* CandidateUI */ = { D427F769278C9E29004A2160 /* CandidateUI */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = CandidateUI; productName = CandidateUI;
}; };
D48550A225EBE689006A204C /* OpenCC */ = { D427F7A827905E90004A2160 /* TooltipUI */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */; productName = TooltipUI;
productName = OpenCC; };
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 */ /* End XCSwiftPackageProductDependency section */
}; };

View File

@ -203,7 +203,7 @@ public class HorizontalCandidateController: CandidateController {
var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel]
let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) 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 panel.hasShadow = true
contentRect.origin = NSPoint.zero contentRect.origin = NSPoint.zero

7
Packages/InputSourceHelper/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View File

@ -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: []),
]
)

View File

@ -0,0 +1,3 @@
# InputSourceHelper
A description of this package.

7
Packages/NotifierUI/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View File

@ -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: []),
]
)

View File

@ -0,0 +1,3 @@
# NotifierUI
A description of this package.

View File

@ -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()
}
}

7
Packages/OpenCCBridge/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View File

@ -0,0 +1,16 @@
{
"object": {
"pins": [
{
"package": "SwiftyOpenCC",
"repositoryURL": "https://github.com/ddddxxx/SwiftyOpenCC.git",
"state": {
"branch": null,
"revision": "a802c02cbf1c6fcd529575f11a9876d94fc813f4",
"version": null
}
}
]
},
"version": 1
}

View File

@ -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")
]),
]
)

View File

@ -0,0 +1,3 @@
# OpenCCBridge
A description of this package.

View File

@ -3,20 +3,16 @@ import OpenCC
// Since SwiftyOpenCC only provide Swift classes, we create an NSObject subclass // 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. // 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 static let shared = OpenCCBridge()
private var converter: ChineseConverter? private var converter: ChineseConverter?
override init() { private override init() {
try? converter = ChineseConverter(options: .simplify) try? converter = ChineseConverter(options: .simplify)
super.init() super.init()
} }
@objc static func convert(_ string: String) -> String? { @objc public static func convert(_ string: String) -> String? {
shared.converter?.convert(string) shared.converter?.convert(string)
} }
private func convert(_ string: String) -> String? {
converter?.convert(string)
}
} }

View File

@ -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!")
}
}

7
Packages/TooltipUI/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View File

@ -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: []),
]
)

View File

@ -0,0 +1,3 @@
# TooltipUI
A description of this package.

View File

@ -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)
}
}

View File

@ -2,6 +2,8 @@
## 系統需求 ## 系統需求
小麥注音輸入法可以在 macOS 10.10 以上版本運作。如果您要自行編譯小麥注音輸入法,或參與開發,您需要:
- macOS 10.15 Catalina 以上版本 - macOS 10.15 Catalina 以上版本
- Xcode 12.0 以上版本 - Xcode 12.0 以上版本
@ -18,4 +20,3 @@
## 軟體授權 ## 軟體授權
本專案採用 MIT License 釋出,使用者可自由使用、散播本軟體,惟散播時必須完整保留版權聲明及軟體授權([詳全文](https://github.com/openvanilla/McBopomofo/blob/master/LICENSE.txt))。 本專案採用 MIT License 釋出,使用者可自由使用、散播本軟體,惟散播時必須完整保留版權聲明及軟體授權([詳全文](https://github.com/openvanilla/McBopomofo/blob/master/LICENSE.txt))。

View File

@ -75,5 +75,6 @@
// if Chinese conversion is enabled // if Chinese conversion is enabled
BOOL _chineseConversionEnabled; BOOL _chineseConversionEnabled;
BOOL _halfWidthPunctuationEnabled;
} }
@end @end

View File

@ -42,7 +42,9 @@
#import "McBopomofo-Swift.h" #import "McBopomofo-Swift.h"
@import CandidateUI; @import CandidateUI;
@import OpenCC; @import NotifierUI;
@import TooltipUI;
@import OpenCCBridge;
// C++ namespace usages // C++ namespace usages
using namespace std; using namespace std;
@ -78,6 +80,7 @@ static NSString *const kUseHorizontalCandidateListPreferenceKey = @"UseHorizonta
static NSString *const kComposingBufferSizePreferenceKey = @"ComposingBufferSize"; static NSString *const kComposingBufferSizePreferenceKey = @"ComposingBufferSize";
static NSString *const kChooseCandidateUsingSpaceKey = @"ChooseCandidateUsingSpaceKey"; static NSString *const kChooseCandidateUsingSpaceKey = @"ChooseCandidateUsingSpaceKey";
static NSString *const kChineseConversionEnabledKey = @"ChineseConversionEnabledKey"; static NSString *const kChineseConversionEnabledKey = @"ChineseConversionEnabledKey";
static NSString *const kHalfWidthPunctuationEnabledKey = @"HalfWidthPunctuationEnabledKey";
static NSString *const kEscToCleanInputBufferKey = @"EscToCleanInputBufferKey"; static NSString *const kEscToCleanInputBufferKey = @"EscToCleanInputBufferKey";
// advanced (usually optional) settings // advanced (usually optional) settings
@ -103,6 +106,16 @@ enum {
kDeleteKeyCode = 117 kDeleteKeyCode = 117
}; };
typedef NS_ENUM(NSUInteger, McBpomofoEmacsKey) {
McBpomofoEmacsKeyNone,
McBpomofoEmacsKeyForward,
McBpomofoEmacsKeyBackward,
McBpomofoEmacsKeyHome,
McBpomofoEmacsKeyEnd,
McBpomofoEmacsKeyDelete,
McBpomofoEmacsKeyNextPage,
};
VTCandidateController *gCurrentCandidateController = nil; VTCandidateController *gCurrentCandidateController = nil;
// if DEBUG is defined, a DOT file (GraphViz format) will be written to the // 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; _inputMode = kBopomofoModeIdentifier;
_chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey]; _chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey];
_halfWidthPunctuationEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kHalfWidthPunctuationEnabledKey];
} }
return self; 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:@""]; NSMenuItem *preferenceMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"McBopomofo Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""];
[menu addItem:preferenceMenuItem]; [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.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl;
chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff;
[menu addItem:chineseConversionMenuItem]; [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 addItem:[NSMenuItem separatorItem]];
[menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""]; [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.delegate = nil;
gCurrentCandidateController.visible = NO; gCurrentCandidateController.visible = NO;
[_candidates removeAllObjects]; [_candidates removeAllObjects];
[self _hideTooltip];
} }
- (void)setValue:(id)value forTag:(long)tag client:(id)sender - (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. // Chinese conversion.
NSString *buffer = _composingBuffer; NSString *buffer = _composingBuffer;
if (_chineseConversionEnabled) { BOOL chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey];
if (chineseConversionEnabled) {
buffer = [OpenCCBridge convert:_composingBuffer]; buffer = [OpenCCBridge convert:_composingBuffer];
} }
@ -393,6 +414,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
[_composingBuffer setString:@""]; [_composingBuffer setString:@""];
gCurrentCandidateController.visible = NO; gCurrentCandidateController.visible = NO;
[_candidates removeAllObjects]; [_candidates removeAllObjects];
[self _hideTooltip];
} }
NS_INLINE size_t min(size_t a, size_t b) { return a < b ? a : b; } 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 // 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)]; [client setMarkedText:attrString selectionRange:NSMakeRange((NSInteger)_builder->markerCursorIndex(), 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
_latestReadingCursor = (NSInteger)_builder->markerCursorIndex(); _latestReadingCursor = (NSInteger)_builder->markerCursorIndex();
[self _showCurrentMarkedTextTooltipWithClient:client];
} }
else { else {
// we must use NSAttributedString so that the cursor is visible -- // 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 // 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)]; [client setMarkedText:attrString selectionRange:NSMakeRange(cursorIndex, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
_latestReadingCursor = cursorIndex; _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]; NodeAnchor &anchor = _walkedNodes[0];
NSString *popedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()]; NSString *popedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()];
// Chinese conversion. // Chinese conversion.
if (_chineseConversionEnabled) { BOOL chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey];
if (chineseConversionEnabled) {
popedText = [OpenCCBridge convert:popedText]; popedText = [OpenCCBridge convert:popedText];
} }
[client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; [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(); NSBeep();
} }
- (string)currentLayout - (string)_currentLayout
{ {
string layout = string("Standard_");; string layout = string("Standard_");;
NSInteger keyboardLayout = [[NSUserDefaults standardUserDefaults] integerForKey:kKeyboardLayoutPreferenceKey]; 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; return layout;
} }
- (NSString *)_currentMarkedText - (McBpomofoEmacsKey)_detectEmacsKeyFromCharCode:(UniChar)charCode modifiers:(NSUInteger)flags
{ {
if (_builder->markerCursorIndex() < 0) { if (flags & NSControlKeyMask) {
return @""; char c = charCode + 'a' - 1;
if (c == 'a') {
return McBpomofoEmacsKeyHome;
} }
if (!_bpmfReadingBuffer->isEmpty()) { else if (c == 'e') {
return @""; return McBpomofoEmacsKeyEnd;
} }
else if (c == 'f') {
size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex()); return McBpomofoEmacsKeyForward;
size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex());
// A phrase should contian at least two characters.
if (end - begin < 2) {
return @"";
} }
else if (c == 'b') {
NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin)); return McBpomofoEmacsKeyBackward;
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:@"-"]]; else if (c == 'd') {
return string; return McBpomofoEmacsKeyDelete;
}
else if (c == 'v') {
return McBpomofoEmacsKeyNextPage;
}
}
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 - (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 // get the unicode character code
UniChar charCode = [inputText length] ? [inputText characterAtIndex:0] : 0; 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"]) { if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && [NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) {
// special handling for com.apple.Terminal // special handling for com.apple.Terminal
_currentDeferredClient = client; _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 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; 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 we have candidate, it means we need to pass the event to the candidate handler
if ([_candidates count]) { 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. // 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; return YES;
} }
// Shift + left // Shift + left
if (keyCode == cursorBackwardKey && (flags & NSShiftKeyMask)) { if ((keyCode == cursorBackwardKey || emacsKey == McBpomofoEmacsKeyBackward)
&& (flags & NSShiftKeyMask)) {
if (_builder->markerCursorIndex() > 0) { if (_builder->markerCursorIndex() > 0) {
_builder->setMarkerCursorIndex(_builder->markerCursorIndex() - 1); _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; return YES;
} }
// Shift + Right // Shift + Right
if (keyCode == cursorForwardKey && (flags & NSShiftKeyMask)) { if ((keyCode == cursorForwardKey || emacsKey == McBpomofoEmacsKeyForward)
&& (flags & NSShiftKeyMask)) {
if (_builder->markerCursorIndex() < _builder->length()) { if (_builder->markerCursorIndex() < _builder->length()) {
_builder->setMarkerCursorIndex(_builder->markerCursorIndex() + 1); _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 // handle cursor backward
if (keyCode == cursorBackwardKey) { if (keyCode == cursorBackwardKey || emacsKey == McBpomofoEmacsKeyBackward) {
if (!_bpmfReadingBuffer->isEmpty()) { if (!_bpmfReadingBuffer->isEmpty()) {
[self beep]; [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 // handle cursor forward
if (keyCode == cursorForwardKey) { if (keyCode == cursorForwardKey || emacsKey == McBpomofoEmacsKeyForward) {
if (!_bpmfReadingBuffer->isEmpty()) { if (!_bpmfReadingBuffer->isEmpty()) {
[self beep]; [self beep];
} }
@ -937,7 +955,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
return YES; return YES;
} }
if (keyCode == kHomeKeyCode) { if (keyCode == kHomeKeyCode || emacsKey == McBpomofoEmacsKeyHome) {
if (!_bpmfReadingBuffer->isEmpty()) { if (!_bpmfReadingBuffer->isEmpty()) {
[self beep]; [self beep];
} }
@ -958,7 +976,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
return YES; return YES;
} }
if (keyCode == kEndKeyCode) { if (keyCode == kEndKeyCode || emacsKey == McBpomofoEmacsKeyEnd) {
if (!_bpmfReadingBuffer->isEmpty()) { if (!_bpmfReadingBuffer->isEmpty()) {
[self beep]; [self beep];
} }
@ -1011,7 +1029,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
} }
// Delete // Delete
if (keyCode == kDeleteKeyCode) { if (keyCode == kDeleteKeyCode || emacsKey == McBpomofoEmacsKeyDelete) {
if (_bpmfReadingBuffer->isEmpty()) { if (_bpmfReadingBuffer->isEmpty()) {
if (![_composingBuffer length]) { if (![_composingBuffer length]) {
return NO; return NO;
@ -1033,7 +1051,6 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
return YES; return YES;
} }
// Enter // Enter
if (charCode == 13) { if (charCode == 13) {
if (![_composingBuffer length]) { 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. // if nothing is matched, see if it's a punctuation key for current layout.
string layout = [self currentLayout]; string layout = [self _currentLayout];
string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode); string punctuationNamePrefix = (_halfWidthPunctuationEnabled ? string("_half_punctuation_"): string("_punctuation_"));
if ([self handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) { string customPunctuation = punctuationNamePrefix + layout + string(1, (char)charCode);
if ([self _handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) {
return YES; 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); string punctuation = punctuationNamePrefix + string(1, (char)charCode);
if ([self handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) { if ([self _handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) {
return YES; return YES;
} }
@ -1085,7 +1103,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
return NO; 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 (_languageModel->hasUnigramsForKey(customPunctuation)) {
if (_bpmfReadingBuffer->isEmpty()) { 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; 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 = BOOL cancelCandidateKey =
(charCode == 27) || (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]; [self candidateController:gCurrentCandidateController didSelectCandidateAtIndex:gCurrentCandidateController.selectedCandidateIndex];
return YES; return YES;
} }
else if (charCode == 32 || keyCode == kPageDownKeyCode) { else if (charCode == 32 || keyCode == kPageDownKeyCode || emacsKey == McBpomofoEmacsKeyNextPage) {
BOOL updated = [gCurrentCandidateController showNextPage]; BOOL updated = [gCurrentCandidateController showNextPage];
if (!updated) { if (!updated) {
[self beep]; [self beep];
@ -1168,6 +1186,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
return YES; return YES;
} }
} }
else if (emacsKey == McBpomofoEmacsKeyBackward) {
BOOL updated = [gCurrentCandidateController highlightPreviousCandidate];
if (!updated) {
[self beep];
}
[self updateClientComposingBuffer:_currentCandidateClient];
return YES;
}
else if (keyCode == kRightKeyCode) { else if (keyCode == kRightKeyCode) {
if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) {
BOOL updated = [gCurrentCandidateController highlightNextCandidate]; 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; return YES;
} }
} }
else if (emacsKey == McBpomofoEmacsKeyForward) {
BOOL updated = [gCurrentCandidateController highlightNextCandidate];
if (!updated) {
[self beep];
}
[self updateClientComposingBuffer:_currentCandidateClient];
return YES;
}
else if (keyCode == kUpKeyCode) { else if (keyCode == kUpKeyCode) {
if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) {
BOOL updated = [gCurrentCandidateController showPreviousPage]; 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; return YES;
} }
} }
else if (keyCode == kHomeKeyCode) { else if (keyCode == kHomeKeyCode || emacsKey == McBpomofoEmacsKeyHome) {
if (gCurrentCandidateController.selectedCandidateIndex == 0) { if (gCurrentCandidateController.selectedCandidateIndex == 0) {
[self beep]; [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]; [self updateClientComposingBuffer:_currentCandidateClient];
return YES; return YES;
} }
else if (keyCode == kEndKeyCode && [_candidates count] > 0) { else if ((keyCode == kEndKeyCode || emacsKey == McBpomofoEmacsKeyEnd) && [_candidates count] > 0) {
if (gCurrentCandidateController.selectedCandidateIndex == [_candidates count] - 1) { if (gCurrentCandidateController.selectedCandidateIndex == [_candidates count] - 1) {
[self beep]; [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) { if (_inputMode == kPlainBopomofoModeIdentifier) {
string layout = [self currentLayout]; string layout = [self _currentLayout];
string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode); string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode);
string punctuation = string("_punctuation_") + 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 + (VTHorizontalCandidateController *)horizontalCandidateController
{ {
static VTHorizontalCandidateController *instance = nil; static VTHorizontalCandidateController *instance = nil;
@synchronized(self) { static dispatch_once_t onceToken;
if (!instance) { dispatch_once(&onceToken, ^{
instance = [[VTHorizontalCandidateController alloc] init]; instance = [[VTHorizontalCandidateController alloc] init];
} });
}
return instance; return instance;
} }
+ (VTVerticalCandidateController *)verticalCandidateController + (VTVerticalCandidateController *)verticalCandidateController
{ {
static VTVerticalCandidateController *instance = nil; static VTVerticalCandidateController *instance = nil;
@synchronized(self) { static dispatch_once_t onceToken;
if (!instance) { dispatch_once(&onceToken, ^{
instance = [[VTVerticalCandidateController alloc] init]; 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; 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; 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 #pragma mark - Misc menu items
- (void)showPreferences:(id)sender - (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; _chineseConversionEnabled = !_chineseConversionEnabled;
[[NSUserDefaults standardUserDefaults] setBool:_chineseConversionEnabled forKey:kChineseConversionEnabledKey]; [[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 @end
#pragma mark - #pragma mark -

View File

@ -27,7 +27,8 @@
#import "AppDelegate.h" #import "AppDelegate.h"
#import <sys/mount.h> #import <sys/mount.h>
#import "McBopomofoInstaller-Swift.h" //#import "McBopomofoInstaller-Swift.h"
@import InputSourceHelper;
static NSString *const kTargetBin = @"McBopomofo"; static NSString *const kTargetBin = @"McBopomofo";
static NSString *const kTargetType = @"app"; static NSString *const kTargetType = @"app";

View File

@ -62,3 +62,13 @@
"Please check the permission of at \"%@\"." = "Please check the permission of at \"%@\"."; "Please check the permission of at \"%@\"." = "Please check the permission of at \"%@\".";
"Edit Excluded Phrases" = "Edit Excluded Phrases"; "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";

View File

@ -34,7 +34,7 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import <InputMethodKit/InputMethodKit.h> #import <InputMethodKit/InputMethodKit.h>
#import "McBopomofo-Swift.h" @import InputSourceHelper;
static NSString *const kConnectionName = @"McBopomofo_1_Connection"; static NSString *const kConnectionName = @"McBopomofo_1_Connection";

View File

@ -63,3 +63,12 @@
"Edit Excluded Phrases" = "編輯要排除的詞彙"; "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" = "已經切換到繁體中文模式";