Adds notifier UI to notify user Chinese conversion on/off.
This commit is contained in:
parent
a7e38b5b2d
commit
d4772ffa99
|
@ -43,6 +43,7 @@
|
||||||
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 */; };
|
D427F7A927905E90004A2160 /* TooltipUI in Frameworks */ = {isa = PBXBuildFile; productRef = D427F7A827905E90004A2160 /* TooltipUI */; };
|
||||||
|
D427F7AE27907B8A004A2160 /* NotifierUI in Frameworks */ = {isa = PBXBuildFile; productRef = D427F7AD27907B8A004A2160 /* NotifierUI */; };
|
||||||
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 */; };
|
||||||
|
@ -170,6 +171,7 @@
|
||||||
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>"; };
|
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>"; };
|
||||||
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>"; };
|
||||||
|
@ -186,6 +188,7 @@
|
||||||
D48550A325EBE689006A204C /* OpenCC in Frameworks */,
|
D48550A325EBE689006A204C /* OpenCC in Frameworks */,
|
||||||
D427F7A927905E90004A2160 /* TooltipUI 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;
|
||||||
|
@ -397,6 +400,7 @@
|
||||||
children = (
|
children = (
|
||||||
D427F768278C9D0D004A2160 /* CandidateUI */,
|
D427F768278C9D0D004A2160 /* CandidateUI */,
|
||||||
D427F7A727905E43004A2160 /* TooltipUI */,
|
D427F7A727905E43004A2160 /* TooltipUI */,
|
||||||
|
D427F7AC27907B7E004A2160 /* NotifierUI */,
|
||||||
);
|
);
|
||||||
name = Packages;
|
name = Packages;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -439,6 +443,7 @@
|
||||||
D48550A225EBE689006A204C /* OpenCC */,
|
D48550A225EBE689006A204C /* OpenCC */,
|
||||||
D427F769278C9E29004A2160 /* CandidateUI */,
|
D427F769278C9E29004A2160 /* CandidateUI */,
|
||||||
D427F7A827905E90004A2160 /* TooltipUI */,
|
D427F7A827905E90004A2160 /* TooltipUI */,
|
||||||
|
D427F7AD27907B8A004A2160 /* NotifierUI */,
|
||||||
);
|
);
|
||||||
productName = McBopomofo;
|
productName = McBopomofo;
|
||||||
productReference = 6A0D4EA215FC0D2D00ABF4B3 /* McBopomofo.app */;
|
productReference = 6A0D4EA215FC0D2D00ABF4B3 /* McBopomofo.app */;
|
||||||
|
@ -1068,6 +1073,10 @@
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = TooltipUI;
|
productName = TooltipUI;
|
||||||
};
|
};
|
||||||
|
D427F7AD27907B8A004A2160 /* NotifierUI */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = NotifierUI;
|
||||||
|
};
|
||||||
D48550A225EBE689006A204C /* OpenCC */ = {
|
D48550A225EBE689006A204C /* OpenCC */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */;
|
package = D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */;
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
protocol NotifierWindowDelegate: AnyObject {
|
||||||
|
func windowDidBecomeClicked(_ window: NotifierWindow)
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotifierWindow: NSWindow {
|
||||||
|
weak var clickDelegate: NotifierWindowDelegate?
|
||||||
|
|
||||||
|
override func mouseDown(with event: NSEvent) {
|
||||||
|
clickDelegate?.windowDidBecomeClicked(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let kWindowWidth: CGFloat = 160.0
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
static func increaseInstanceCount() {
|
||||||
|
instanceCount += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
static func decreaseInstanceCount() {
|
||||||
|
instanceCount -= 1
|
||||||
|
if instanceCount < 0 {
|
||||||
|
instanceCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func show() {
|
||||||
|
setStartLocation()
|
||||||
|
moveIn()
|
||||||
|
NotifierController.increaseInstanceCount()
|
||||||
|
waitTimer = Timer.scheduledTimer(timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut), userInfo: nil, repeats: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func doFadeOut(_ timer: Timer) {
|
||||||
|
let opacity = self.window?.alphaValue ?? 0
|
||||||
|
if opacity <= 0 {
|
||||||
|
self.close()
|
||||||
|
} else {
|
||||||
|
self.window?.alphaValue = opacity - 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func windowDidBecomeClicked(_ window: NotifierWindow) {
|
||||||
|
self.fadeOut()
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ public class TooltipController: NSWindowController {
|
||||||
let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0)
|
let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.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
|
||||||
|
|
||||||
messageTextField = NSTextField()
|
messageTextField = NSTextField()
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
#import "McBopomofo-Swift.h"
|
#import "McBopomofo-Swift.h"
|
||||||
|
|
||||||
@import CandidateUI;
|
@import CandidateUI;
|
||||||
|
@import NotifierUI;
|
||||||
@import TooltipUI;
|
@import TooltipUI;
|
||||||
@import OpenCC;
|
@import OpenCC;
|
||||||
|
|
||||||
|
@ -1692,6 +1693,11 @@ 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
|
- (void)toggleHalfWidthPunctuation:(id)sender
|
||||||
|
|
|
@ -68,3 +68,7 @@
|
||||||
"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 \"%@\". 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.";
|
"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";
|
||||||
|
|
|
@ -68,3 +68,7 @@
|
||||||
"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." = "您目前選擇了 \"%@\"。按下 Enter 就可以加入到使用者詞彙中。";
|
"You are now selecting \"%@\". Press enter to add a new phrase." = "您目前選擇了 \"%@\"。按下 Enter 就可以加入到使用者詞彙中。";
|
||||||
|
|
||||||
|
"Chinese conversion on" = "已經切換到簡體中文模式";
|
||||||
|
|
||||||
|
"Chinese conversion off" = "已經切換到繁體中文模式";
|
||||||
|
|
Loading…
Reference in New Issue