Merge pull request #212 from zonble/dev/swiftify

Converts most of the Objective-C classes into Swift
This commit is contained in:
Weizhong Yang a.k.a zonble 2022-01-11 02:53:32 +08:00 committed by GitHub
commit 11d33c0b42
41 changed files with 1776 additions and 2468 deletions

1
.gitignore vendored
View File

@ -11,3 +11,4 @@ Credits.rtf
# that can be built by make -C Source/Data/bin/C_Version
# C_count.occ.exe
.idea
.swiftpm

View File

@ -9,17 +9,8 @@
/* Begin PBXBuildFile section */
6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0421A615FEF3F50061ED63 /* FastLM.cpp */; };
6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; };
6A0D4ED015FC0D6400ABF4B3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */; };
6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */; };
6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC815FC0D6400ABF4B3 /* main.m */; };
6A0D4ED415FC0D6400ABF4B3 /* OVInputSourceHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */; };
6A0D4ED515FC0D6400ABF4B3 /* PreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */; };
6A0D4EFE15FC0DA600ABF4B3 /* VTCandidateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EDA15FC0DA600ABF4B3 /* VTCandidateController.m */; };
6A0D4EFF15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EDC15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m */; };
6A0D4F0015FC0DA600ABF4B3 /* VTHorizontalCandidateView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EDE15FC0DA600ABF4B3 /* VTHorizontalCandidateView.m */; };
6A0D4F0115FC0DA600ABF4B3 /* VTVerticalCandidateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EE015FC0DA600ABF4B3 /* VTVerticalCandidateController.m */; };
6A0D4F0215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EE215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m */; };
6A0D4F0315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EE415FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m */; };
6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */; };
6A0D4F0915FC0DA600ABF4B3 /* Bopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */; };
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */; };
@ -33,7 +24,6 @@
6A2E40F9253A6AA000D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; };
6A38BC1515FC117A00A8A51F /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; };
6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */; };
6AB3620D274CA50700AC7547 /* OVInputSourceHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */; };
6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; };
6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACA41E915FC1D9000935EF6 /* AppDelegate.m */; };
6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */; };
@ -45,10 +35,15 @@
6AD7CBC815FE555000691B5B /* data-plain-bpmf.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6AD7CBC715FE555000691B5B /* data-plain-bpmf.txt */; };
6AE210B215FC63CC003659FE /* PlainBopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6AE210B015FC63CC003659FE /* PlainBopomofo.tiff */; };
6AE210B315FC63CC003659FE /* PlainBopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6AE210B115FC63CC003659FE /* PlainBopomofo@2x.tiff */; };
6AE30A491F7F40B7008735BD /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6AE30A471F7F40B7008735BD /* UserOverrideModel.cpp */; };
6AFF97F2253B299E007F1C49 /* OVNonModalAlertWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF97F0253B299E007F1C49 /* OVNonModalAlertWindowController.xib */; };
6AFF97F3253B299E007F1C49 /* OVNonModalAlertWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */; };
6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */; };
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 */; };
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 */
@ -76,27 +71,9 @@
6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
6A0D4EA915FC0D2D00ABF4B3 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
6A0D4EAB15FC0D2D00ABF4B3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
6A0D4EC315FC0D6400ABF4B3 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputMethodController.h; sourceTree = "<group>"; };
6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = InputMethodController.mm; sourceTree = "<group>"; };
6A0D4EC815FC0D6400ABF4B3 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
6A0D4EC915FC0D6400ABF4B3 /* OVInputSourceHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVInputSourceHelper.h; sourceTree = "<group>"; };
6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OVInputSourceHelper.m; sourceTree = "<group>"; };
6A0D4ECB15FC0D6400ABF4B3 /* PreferencesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesWindowController.h; sourceTree = "<group>"; };
6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesWindowController.m; sourceTree = "<group>"; };
6A0D4ED915FC0DA600ABF4B3 /* VTCandidateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTCandidateController.h; sourceTree = "<group>"; };
6A0D4EDA15FC0DA600ABF4B3 /* VTCandidateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTCandidateController.m; sourceTree = "<group>"; };
6A0D4EDB15FC0DA600ABF4B3 /* VTHorizontalCandidateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTHorizontalCandidateController.h; sourceTree = "<group>"; };
6A0D4EDC15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTHorizontalCandidateController.m; sourceTree = "<group>"; };
6A0D4EDD15FC0DA600ABF4B3 /* VTHorizontalCandidateView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTHorizontalCandidateView.h; sourceTree = "<group>"; };
6A0D4EDE15FC0DA600ABF4B3 /* VTHorizontalCandidateView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTHorizontalCandidateView.m; sourceTree = "<group>"; };
6A0D4EDF15FC0DA600ABF4B3 /* VTVerticalCandidateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTVerticalCandidateController.h; sourceTree = "<group>"; };
6A0D4EE015FC0DA600ABF4B3 /* VTVerticalCandidateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTVerticalCandidateController.m; sourceTree = "<group>"; };
6A0D4EE115FC0DA600ABF4B3 /* VTVerticalCandidateTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTVerticalCandidateTableView.h; sourceTree = "<group>"; };
6A0D4EE215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTVerticalCandidateTableView.m; sourceTree = "<group>"; };
6A0D4EE315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTVerticalKeyLabelStripView.h; sourceTree = "<group>"; };
6A0D4EE415FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTVerticalKeyLabelStripView.m; sourceTree = "<group>"; };
6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Bopomofo.tiff; sourceTree = "<group>"; };
6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "Bopomofo@2x.tiff"; sourceTree = "<group>"; };
6A0D4EF515FC0DA600ABF4B3 /* McBopomofo-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "McBopomofo-Info.plist"; sourceTree = "<group>"; };
@ -177,13 +154,16 @@
6AD7CBC715FE555000691B5B /* data-plain-bpmf.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-plain-bpmf.txt"; sourceTree = "<group>"; };
6AE210B015FC63CC003659FE /* PlainBopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = PlainBopomofo.tiff; sourceTree = "<group>"; };
6AE210B115FC63CC003659FE /* PlainBopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "PlainBopomofo@2x.tiff"; sourceTree = "<group>"; };
6AE30A471F7F40B7008735BD /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = "<group>"; };
6AE30A481F7F40B7008735BD /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = "<group>"; };
6AFF97EF253B299E007F1C49 /* OVNonModalAlertWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVNonModalAlertWindowController.h; sourceTree = "<group>"; };
6AFF97F0253B299E007F1C49 /* OVNonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OVNonModalAlertWindowController.xib; sourceTree = "<group>"; };
6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OVNonModalAlertWindowController.m; sourceTree = "<group>"; };
6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NonModalAlertWindowController.xib; 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>"; };
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 */
@ -193,6 +173,7 @@
files = (
6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */,
D48550A325EBE689006A204C /* OpenCC in Frameworks */,
D427F76A278C9E29004A2160 /* CandidateUI in Frameworks */,
6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -211,6 +192,7 @@
6A0D4E9215FC0CFA00ABF4B3 = {
isa = PBXGroup;
children = (
D427F766278C9CBD004A2160 /* Packages */,
6A0D4EC215FC0D3C00ABF4B3 /* Source */,
6A0D4EA515FC0D2D00ABF4B3 /* Frameworks */,
6A0D4EA315FC0D2D00ABF4B3 /* Products */,
@ -240,50 +222,24 @@
6A0D4EC215FC0D3C00ABF4B3 /* Source */ = {
isa = PBXGroup;
children = (
6A0D4ED815FC0DA600ABF4B3 /* CandidateUI */,
6A38BBDD15FC115800A8A51F /* Data */,
6A0D4F1215FC0EB100ABF4B3 /* Engine */,
6ACA41E715FC1D9000935EF6 /* Installer */,
6A0D4F4715FC0EB900ABF4B3 /* Resources */,
6A0D4EC315FC0D6400ABF4B3 /* AppDelegate.h */,
6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */,
6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */,
6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */,
6A0D4EC815FC0D6400ABF4B3 /* main.m */,
6A0D4EF615FC0DA600ABF4B3 /* McBopomofo-Prefix.pch */,
6AFF97EF253B299E007F1C49 /* OVNonModalAlertWindowController.h */,
6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */,
6A0D4EC915FC0D6400ABF4B3 /* OVInputSourceHelper.h */,
6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */,
6A0D4ECB15FC0D6400ABF4B3 /* PreferencesWindowController.h */,
6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.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 */,
6AE30A471F7F40B7008735BD /* UserOverrideModel.cpp */,
6AE30A481F7F40B7008735BD /* UserOverrideModel.h */,
);
path = Source;
sourceTree = "<group>";
};
6A0D4ED815FC0DA600ABF4B3 /* CandidateUI */ = {
isa = PBXGroup;
children = (
6A0D4ED915FC0DA600ABF4B3 /* VTCandidateController.h */,
6A0D4EDA15FC0DA600ABF4B3 /* VTCandidateController.m */,
6A0D4EDB15FC0DA600ABF4B3 /* VTHorizontalCandidateController.h */,
6A0D4EDC15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m */,
6A0D4EDD15FC0DA600ABF4B3 /* VTHorizontalCandidateView.h */,
6A0D4EDE15FC0DA600ABF4B3 /* VTHorizontalCandidateView.m */,
6A0D4EDF15FC0DA600ABF4B3 /* VTVerticalCandidateController.h */,
6A0D4EE015FC0DA600ABF4B3 /* VTVerticalCandidateController.m */,
6A0D4EE115FC0DA600ABF4B3 /* VTVerticalCandidateTableView.h */,
6A0D4EE215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m */,
6A0D4EE315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.h */,
6A0D4EE415FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m */,
);
path = CandidateUI;
sourceTree = "<group>";
};
6A0D4EEE15FC0DA600ABF4B3 /* Images */ = {
isa = PBXGroup;
children = (
@ -304,6 +260,8 @@
6A0D4F2215FC0EB100ABF4B3 /* OpenVanilla */,
6A0421A615FEF3F50061ED63 /* FastLM.cpp */,
6A0421A715FEF3F50061ED63 /* FastLM.h */,
D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */,
D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */,
);
path = Engine;
sourceTree = "<group>";
@ -377,7 +335,7 @@
6A0D4F4715FC0EB900ABF4B3 /* Resources */ = {
isa = PBXGroup;
children = (
6AFF97F0253B299E007F1C49 /* OVNonModalAlertWindowController.xib */,
6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */,
6A0D4EEE15FC0DA600ABF4B3 /* Images */,
6A0D4EF515FC0DA600ABF4B3 /* McBopomofo-Info.plist */,
6A0D4F4815FC0EE100ABF4B3 /* InfoPlist.strings */,
@ -416,6 +374,14 @@
path = Installer;
sourceTree = "<group>";
};
D427F766278C9CBD004A2160 /* Packages */ = {
isa = PBXGroup;
children = (
D427F768278C9D0D004A2160 /* CandidateUI */,
);
name = Packages;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXLegacyTarget section */
@ -452,6 +418,7 @@
name = McBopomofo;
packageProductDependencies = (
D48550A225EBE689006A204C /* OpenCC */,
D427F769278C9E29004A2160 /* CandidateUI */,
);
productName = McBopomofo;
productReference = 6A0D4EA215FC0D2D00ABF4B3 /* McBopomofo.app */;
@ -525,7 +492,7 @@
6A0D4F5815FC0EF900ABF4B3 /* Localizable.strings in Resources */,
6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */,
6A38BC1515FC117A00A8A51F /* data.txt in Resources */,
6AFF97F2253B299E007F1C49 /* OVNonModalAlertWindowController.xib in Resources */,
6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */,
6AE210B215FC63CC003659FE /* PlainBopomofo.tiff in Resources */,
6AE210B315FC63CC003659FE /* PlainBopomofo@2x.tiff in Resources */,
6AD7CBC815FE555000691B5B /* data-plain-bpmf.txt in Resources */,
@ -574,20 +541,14 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6A0D4ED015FC0D6400ABF4B3 /* AppDelegate.m in Sources */,
6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */,
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */,
6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */,
6A0D4ED415FC0D6400ABF4B3 /* OVInputSourceHelper.m in Sources */,
6A0D4ED515FC0D6400ABF4B3 /* PreferencesWindowController.m in Sources */,
6A0D4EFE15FC0DA600ABF4B3 /* VTCandidateController.m in Sources */,
6A0D4EFF15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m in Sources */,
6A0D4F0015FC0DA600ABF4B3 /* VTHorizontalCandidateView.m in Sources */,
6AFF97F3253B299E007F1C49 /* OVNonModalAlertWindowController.m in Sources */,
6A0D4F0115FC0DA600ABF4B3 /* VTVerticalCandidateController.m in Sources */,
6AE30A491F7F40B7008735BD /* UserOverrideModel.cpp in Sources */,
6A0D4F0215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m in Sources */,
6A0D4F0315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.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 */,
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */,
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */,
6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */,
);
@ -599,8 +560,8 @@
files = (
6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */,
6A225A232367A1D700F685C6 /* ArchiveUtil.m in Sources */,
6AB3620D274CA50700AC7547 /* OVInputSourceHelper.m in Sources */,
6ACA41FF15FC1D9000935EF6 /* main.m in Sources */,
D47F7DD6278C3075002F9DD7 /* InputSourceHelper.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -985,6 +946,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "org.openvanilla.McBopomofo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_VERSION = 5.0;
WRAPPER_EXTENSION = app;
};
name = Debug;
@ -1017,6 +979,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "org.openvanilla.McBopomofo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_VERSION = 5.0;
WRAPPER_EXTENSION = app;
};
name = Release;
@ -1074,6 +1037,10 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
D427F769278C9E29004A2160 /* CandidateUI */ = {
isa = XCSwiftPackageProductDependency;
productName = CandidateUI;
};
D48550A225EBE689006A204C /* OpenCC */ = {
isa = XCSwiftPackageProductDependency;
package = D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */;

7
Packages/CandidateUI/.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: "CandidateUI",
platforms: [.macOS(.v10_10)],
products: [
.library(
name: "CandidateUI",
targets: ["CandidateUI"]),
],
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: "CandidateUI",
dependencies: []),
]
)

View File

@ -0,0 +1,3 @@
# CandidateUI
The candidate window for McBopomofo.

View File

@ -0,0 +1,150 @@
//
// CandidateController.swift
//
// Copyright (c) 2011 The McBopomofo Project.
//
// Contributors:
// Mengjuei Hsieh (@mjhsieh)
// Weizhong Yang (@zonble)
//
// Based on the Syrup Project and the Formosana Library
// by Lukhnos Liu (@lukhnos).
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
import Cocoa
@objc(VTCandidateControllerDelegate)
public protocol CandidateControllerDelegate: AnyObject {
func candidateCountForController(_ controller: CandidateController) -> UInt
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt)
}
@objc(VTCandidateController)
public class CandidateController: NSWindowController {
@objc public weak var delegate: CandidateControllerDelegate?
@objc public var selectedCandidateIndex: UInt = UInt.max
@objc public var visible: Bool = false {
didSet {
if visible {
window?.perform(#selector(NSWindow.orderFront(_:)), with: self, afterDelay: 0.0)
} else {
window?.perform(#selector(NSWindow.orderOut(_:)), with: self, afterDelay: 0.0)
}
}
}
@objc public var windowTopLeftPoint: NSPoint {
get {
guard let frameRect = window?.frame else {
return NSPoint.zero
}
return NSPoint(x: frameRect.minX, y: frameRect.maxY)
}
set {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
self.set(windowTopLeftPoint: newValue, bottomOutOfScreenAdjustmentHeight: 0)
}
}
}
@objc public var keyLabels: [String] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
@objc public var keyLabelFont: NSFont = NSFont.systemFont(ofSize: 14)
@objc public var candidateFont: NSFont = NSFont.systemFont(ofSize: 18)
@objc public func reloadData() {
}
@objc public func showNextPage() -> Bool {
false
}
@objc public func showPreviousPage() -> Bool {
false
}
@objc public func highlightNextCandidate() -> Bool {
false
}
@objc public func highlightPreviousCandidate() -> Bool {
false
}
@objc public func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt {
UInt.max
}
@objc(setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:)
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
self.doSet(windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height)
}
}
func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) {
var adjustedPoint = windowTopLeftPoint
var adjustedHeight = height
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero
for screen in NSScreen.screens {
let frame = screen.visibleFrame
if windowTopLeftPoint.x >= frame.minX &&
windowTopLeftPoint.x <= frame.maxX &&
windowTopLeftPoint.y >= frame.minY &&
windowTopLeftPoint.y <= frame.maxY {
screenFrame = frame
break
}
}
if adjustedHeight > screenFrame.size.height / 2.0 {
adjustedHeight = 0.0
}
let windowSize = window?.frame.size ?? NSSize.zero
// bottom beneath the screen?
if adjustedPoint.y - windowSize.height < screenFrame.minY {
adjustedPoint.y = windowTopLeftPoint.y + adjustedHeight + windowSize.height
}
// top over the screen?
if adjustedPoint.y >= screenFrame.maxY {
adjustedPoint.y = screenFrame.maxY - 1.0
}
// right
if adjustedPoint.x + windowSize.width >= screenFrame.maxX {
adjustedPoint.x = screenFrame.maxX - windowSize.width
}
// left
if adjustedPoint.x < screenFrame.minX {
adjustedPoint.x = screenFrame.minX
}
window?.setFrameTopLeftPoint(adjustedPoint)
}
}

View File

@ -0,0 +1,418 @@
//
// HorizontalCandidateController.swift
//
// Copyright (c) 2011 The McBopomofo Project.
//
// Contributors:
// Mengjuei Hsieh (@mjhsieh)
// Weizhong Yang (@zonble)
//
// Based on the Syrup Project and the Formosana Library
// by Lukhnos Liu (@lukhnos).
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
import Cocoa
fileprivate class HorizontalCandidateView: NSView {
var highlightedIndex: UInt = 0
var action: Selector?
weak var target: AnyObject?
private var keyLabels: [String] = []
private var displayedCandidates: [String] = []
private var keyLabelHeight: CGFloat = 0
private var candidateTextHeight: CGFloat = 0
private var cellPadding: CGFloat = 0
private var keyLabelAttrDict: [NSAttributedString.Key:AnyObject] = [:]
private var candidateAttrDict: [NSAttributedString.Key:AnyObject] = [:]
private var elementWidths: [CGFloat] = []
private var trackingHighlightedIndex: UInt = UInt.max
override var isFlipped: Bool {
true
}
var sizeForView: NSSize {
var result = NSSize.zero
if !elementWidths.isEmpty {
result.width = elementWidths.reduce(0, +)
result.width += CGFloat(elementWidths.count)
result.height = keyLabelHeight + candidateTextHeight + 1.0
}
return result
}
@objc(setKeyLabels:displayedCandidates:)
func set(keyLabels labels: [String], displayedCandidates candidates: [String]) {
let count = min(labels.count, candidates.count)
keyLabels = Array(labels[0..<count])
displayedCandidates = Array(candidates[0..<count])
var newWidths = [CGFloat]()
let baseSize = NSSize(width: 10240.0, height: 10240.0)
for index in 0..<count {
let labelRect = (keyLabels[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: keyLabelAttrDict)
let candidateRect = (displayedCandidates[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: candidateAttrDict)
let cellWidth = max(labelRect.size.width, candidateRect.size.width) + cellPadding;
newWidths.append(cellWidth)
}
elementWidths = newWidths
}
@objc(setKeyLabelFont:candidateFont:)
func set(keyLabelFont labelFont: NSFont, candidateFont: NSFont) {
let paraStyle = NSMutableParagraphStyle()
paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.alignment = .center
keyLabelAttrDict = [.font: labelFont,
.paragraphStyle: paraStyle,
.foregroundColor: NSColor.black]
candidateAttrDict = [.font: candidateFont,
.paragraphStyle: paraStyle,
.foregroundColor: NSColor.textColor]
let labelFontSize = labelFont.pointSize
let candidateFontSize = candidateFont.pointSize
let biggestSize = max(labelFontSize, candidateFontSize)
keyLabelHeight = ceil(labelFontSize * 1.20)
candidateTextHeight = ceil(candidateFontSize * 1.20)
cellPadding = ceil(biggestSize / 2.0)
}
override func draw(_ dirtyRect: NSRect) {
let backgroundColor = NSColor.controlBackgroundColor
let darkGray = NSColor(deviceWhite: 0.7, alpha: 1.0)
let lightGray = NSColor(deviceWhite: 0.8, alpha: 1.0)
let bounds = self.bounds
backgroundColor.setFill()
NSBezierPath.fill(bounds)
if #available(macOS 10.14, *) {
NSColor.separatorColor.setStroke()
} else {
NSColor.darkGray.setStroke()
}
NSBezierPath.strokeLine(from: NSPoint(x: bounds.size.width, y: 0.0), to: NSPoint(x: bounds.size.width, y: bounds.size.height))
var accuWidth: CGFloat = 0
for index in 0..<elementWidths.count {
let currentWidth = elementWidths[index]
let labelRect = NSRect(x: accuWidth, y: 0.0, width: currentWidth, height: keyLabelHeight)
let candidateRect = NSRect(x: accuWidth, y: keyLabelHeight + 1.0, width: currentWidth, height: candidateTextHeight)
(index == highlightedIndex ? darkGray : lightGray).setFill()
NSBezierPath.fill(labelRect)
(keyLabels[index] as NSString).draw(in: labelRect, withAttributes: keyLabelAttrDict)
var activeCandidateAttr = candidateAttrDict
if index == highlightedIndex {
NSColor.selectedTextBackgroundColor.setFill()
activeCandidateAttr = candidateAttrDict
activeCandidateAttr[.foregroundColor] = NSColor.selectedTextColor
} else {
backgroundColor.setFill()
}
NSBezierPath.fill(candidateRect)
(displayedCandidates[index] as NSString).draw(in: candidateRect, withAttributes: activeCandidateAttr)
accuWidth += currentWidth + 1.0
}
}
private func findHitIndex(event: NSEvent) -> UInt? {
let location = convert(event.locationInWindow, to: nil)
if !NSPointInRect(location, self.bounds) {
return nil
}
var accuWidth: CGFloat = 0.0
for index in 0..<elementWidths.count {
let currentWidth = elementWidths[index]
if location.x >= accuWidth && location.x <= accuWidth + currentWidth {
return UInt(index)
}
accuWidth += currentWidth + 1.0
}
return nil
}
override func mouseUp(with event: NSEvent) {
trackingHighlightedIndex = highlightedIndex
guard let newIndex = findHitIndex(event: event) else {
return
}
highlightedIndex = newIndex
self.setNeedsDisplay(self.bounds)
}
override func mouseDown(with event: NSEvent) {
guard let newIndex = findHitIndex(event: event) else {
return
}
var triggerAction = false
if newIndex == highlightedIndex {
triggerAction = true
} else {
highlightedIndex = trackingHighlightedIndex
}
trackingHighlightedIndex = 0
self.setNeedsDisplay(self.bounds)
if triggerAction {
if let target = target as? NSObject, let action = action {
target.perform(action, with: self)
}
}
}
}
@objc(VTHorizontalCandidateController)
public class HorizontalCandidateController : CandidateController {
private var candidateView: HorizontalCandidateView
private var prevPageButton: NSButton
private var nextPageButton: NSButton
private var currentPage: UInt = 0
public init() {
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.hasShadow = true
contentRect.origin = NSPoint.zero
candidateView = HorizontalCandidateView(frame: contentRect)
panel.contentView?.addSubview(candidateView)
contentRect.size = NSSize(width: 36.0, height: 20.0)
nextPageButton = NSButton(frame: contentRect)
nextPageButton.setButtonType(.momentaryLight)
nextPageButton.bezelStyle = .smallSquare
nextPageButton.title = "»"
prevPageButton = NSButton(frame: contentRect)
prevPageButton.setButtonType(.momentaryLight)
prevPageButton.bezelStyle = .smallSquare
prevPageButton.title = "«"
panel.contentView?.addSubview(nextPageButton)
panel.contentView?.addSubview(prevPageButton)
super.init(window: panel)
candidateView.target = self
candidateView.action = #selector(candidateViewMouseDidClick(_:))
nextPageButton.target = self
nextPageButton.action = #selector(pageButtonAction(_:))
prevPageButton.target = self
prevPageButton.action = #selector(pageButtonAction(_:))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func reloadData() {
candidateView.highlightedIndex = 0
currentPage = 0
layoutCandidateView()
}
public override func showNextPage() -> Bool {
guard delegate != nil else {
return false
}
if currentPage + 1 >= pageCount {
return false
}
currentPage += 1
candidateView.highlightedIndex = 0
layoutCandidateView()
return true
}
public override func showPreviousPage() -> Bool {
guard delegate != nil else {
return false
}
if currentPage == 0 {
return false
}
currentPage -= 1
candidateView.highlightedIndex = 0
layoutCandidateView()
return true
}
public override func highlightNextCandidate() -> Bool {
guard let delegate = delegate else {
return false
}
let currentIndex = selectedCandidateIndex
if currentIndex + 1 >= delegate.candidateCountForController(self) {
return false
}
selectedCandidateIndex = currentIndex + 1
return true
}
public override func highlightPreviousCandidate() -> Bool {
guard delegate != nil else {
return false
}
let currentIndex = selectedCandidateIndex
if currentIndex == 0 {
return false
}
selectedCandidateIndex = currentIndex - 1
return true
}
public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt {
guard let delegate = delegate else {
return UInt.max
}
let result = currentPage * UInt(keyLabels.count) + index
return result < delegate.candidateCountForController(self) ? result : UInt.max
}
@objc public override var selectedCandidateIndex: UInt {
get {
return currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex
}
set {
guard let delegate = delegate else {
return
}
let keyLabelCount = UInt(keyLabels.count)
if newValue < delegate.candidateCountForController(self) {
currentPage = newValue / keyLabelCount
candidateView.highlightedIndex = newValue % keyLabelCount
layoutCandidateView()
}
}
}
}
extension HorizontalCandidateController {
private var pageCount: UInt {
guard let delegate = delegate else {
return 0
}
let totalCount = delegate.candidateCountForController(self)
let keyLabelCount = UInt(keyLabels.count)
return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0)
}
private func layoutCandidateView() {
guard let delegate = delegate else {
return
}
candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont)
var candidates = [String]()
let count = delegate.candidateCountForController(self)
let keyLabelCount = UInt(keyLabels.count)
let begin = currentPage * keyLabelCount
for index in begin..<min(begin + keyLabelCount, count) {
let candidate = delegate.candidateController(self, candidateAtIndex: index)
candidates.append(candidate)
}
candidateView.set(keyLabels: keyLabels, displayedCandidates: candidates)
var newSize = candidateView.sizeForView
var frameRect = candidateView.frame
frameRect.size = newSize
candidateView.frame = frameRect
if self.pageCount > 1 {
var buttonRect = nextPageButton.frame
var spacing = 0.0
if newSize.height < 40.0 {
buttonRect.size.height = floor(newSize.height / 2)
} else {
buttonRect.size.height = 20.0
}
if newSize.height >= 60.0 {
spacing = ceil(newSize.height * 0.1)
}
let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0
buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY)
nextPageButton.frame = buttonRect
buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY + buttonRect.size.height + spacing)
prevPageButton.frame = buttonRect
newSize.width += 52.0
nextPageButton.isHidden = false
prevPageButton.isHidden = false
} else {
nextPageButton.isHidden = true
prevPageButton.isHidden = true
}
frameRect = window?.frame ?? NSRect.zero
let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height)
frameRect.size = newSize
frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height)
self.window?.setFrame(frameRect, display: false)
self.candidateView.setNeedsDisplay(candidateView.bounds)
}
@objc fileprivate func pageButtonAction(_ sender: Any) {
guard let sender = sender as? NSButton else {
return
}
if sender == nextPageButton {
_ = showNextPage()
} else if sender == prevPageButton {
_ = showPreviousPage()
}
}
@objc fileprivate func candidateViewMouseDidClick(_ sender: Any) {
delegate?.candidateController(self, didSelectCandidateAtIndex: self.selectedCandidateIndex)
}
}

View File

@ -0,0 +1,457 @@
//
// VerticalCandidateController.swift
//
// Copyright (c) 2011 The McBopomofo Project.
//
// Contributors:
// Mengjuei Hsieh (@mjhsieh)
// Weizhong Yang (@zonble)
//
// Based on the Syrup Project and the Formosana Library
// by Lukhnos Liu (@lukhnos).
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
import Cocoa
fileprivate class VerticalKeyLabelStripView: NSView {
var keyLabelFont: NSFont = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
var labelOffsetY: CGFloat = 0
var keyLabels: [String] = []
var highlightedIndex: UInt = UInt.max
override var isFlipped: Bool {
true
}
override func draw(_ dirtyRect: NSRect) {
let bounds = self.bounds
NSColor.white.setFill()
NSBezierPath.fill(bounds)
let count = UInt(keyLabels.count)
if count == 0 {
return
}
let cellHeight: CGFloat = bounds.size.height / CGFloat(count)
let black = NSColor.black
let darkGray = NSColor(deviceWhite: 0.7, alpha: 1.0)
let lightGray = NSColor(deviceWhite: 0.8, alpha: 1.0)
let paraStyle = NSMutableParagraphStyle()
paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.alignment = .center
let textAttr: [NSAttributedString.Key:AnyObject] = [
.font: keyLabelFont,
.foregroundColor: black,
.paragraphStyle: paraStyle]
for index in 0..<count {
let textRect = NSRect(x: 0.0, y: CGFloat(index) * cellHeight + labelOffsetY, width: bounds.size.width, height: cellHeight - labelOffsetY)
var cellRect = NSRect(x: 0.0, y: CGFloat(index) * cellHeight, width: bounds.size.width, height: cellHeight - 1)
if index + 1 >= count {
cellRect.size.height += 1.0
}
(index == highlightedIndex ? darkGray : lightGray).setFill()
NSBezierPath.fill(cellRect)
let text = keyLabels[Int(index)]
(text as NSString).draw(in: textRect, withAttributes: textAttr)
}
}
}
fileprivate class VerticalCandidateTableView: NSTableView {
override func adjustScroll(_ newVisible: NSRect) -> NSRect {
var scrollRect = newVisible
let rowHeightPlusSpacing = rowHeight + intercellSpacing.height
scrollRect.origin.y = (scrollRect.origin.y / rowHeightPlusSpacing) * rowHeightPlusSpacing
return scrollRect
}
}
private let kCandidateTextPadding = 24.0
private let kCandidateTextLeftMargin = 8.0
private let kCandidateTextPaddingWithMandatedTableViewPadding = 18.0
private let kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0
@objc(VTVerticalCandidateController)
public class VerticalCandidateController: CandidateController {
private var keyLabelStripView: VerticalKeyLabelStripView
private var scrollView: NSScrollView
private var tableView: NSTableView
private var candidateTextParagraphStyle: NSMutableParagraphStyle
private var candidateTextPadding: CGFloat = kCandidateTextPadding
private var candidateTextLeftMargin: CGFloat = kCandidateTextLeftMargin
private var maxCandidateAttrStringWidth: CGFloat = 0
public init() {
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) + 1)
panel.hasShadow = true
contentRect.origin = NSPoint.zero
var stripRect = contentRect
stripRect.size.width = 10.0
keyLabelStripView = VerticalKeyLabelStripView(frame: stripRect)
panel.contentView?.addSubview(keyLabelStripView)
var scrollViewRect = contentRect
scrollViewRect.origin.x = stripRect.size.width
scrollViewRect.size.width -= stripRect.size.width
scrollView = NSScrollView(frame: scrollViewRect)
scrollView.verticalScrollElasticity = .none
tableView = NSTableView(frame: contentRect)
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "candidate"))
column.dataCell = NSTextFieldCell()
column.isEditable = false
candidateTextPadding = kCandidateTextPadding
candidateTextLeftMargin = kCandidateTextLeftMargin
tableView.addTableColumn(column)
tableView.intercellSpacing = NSSize(width: 0.0, height: 1.0)
tableView.headerView = nil
tableView.allowsMultipleSelection = false
tableView.allowsEmptySelection = false
scrollView.documentView = tableView
panel.contentView?.addSubview(scrollView)
let paraStyle = NSMutableParagraphStyle()
paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.firstLineHeadIndent = candidateTextLeftMargin
paraStyle.lineBreakMode = .byClipping
candidateTextParagraphStyle = paraStyle
if #available(macOS 10.16, *) {
tableView.style = .fullWidth
candidateTextPadding = kCandidateTextPaddingWithMandatedTableViewPadding
candidateTextLeftMargin = kCandidateTextLeftMarginWithMandatedTableViewPadding
}
super.init(window: panel)
tableView.dataSource = self
tableView.delegate = self
tableView.doubleAction = #selector(rowDoubleClicked(_:))
tableView.target = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func reloadData() {
maxCandidateAttrStringWidth = ceil(candidateFont.pointSize * 2.0 + candidateTextPadding)
tableView.reloadData()
self.layoutCandidateView()
if delegate?.candidateCountForController(self) ?? 0 > 0 {
selectedCandidateIndex = 0
}
}
public override func showNextPage() -> Bool {
scrollPageByOne(true)
}
public override func showPreviousPage() -> Bool {
scrollPageByOne(false)
}
public override func highlightNextCandidate() -> Bool {
moveSelectionByOne(true)
}
public override func highlightPreviousCandidate() -> Bool {
moveSelectionByOne(false)
}
public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt {
guard let delegate = delegate else {
return UInt.max
}
let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin)
if firstVisibleRow != -1 {
let result = UInt(firstVisibleRow) + index
if result < delegate.candidateCountForController(self) {
return result
}
}
return UInt.max
}
@objc public override var selectedCandidateIndex: UInt {
get {
let selectedRow = tableView.selectedRow
return selectedRow == -1 ? UInt.max : UInt(selectedRow)
}
set {
guard let delegate = delegate else {
return
}
var newIndex = newValue
let selectedRow = tableView.selectedRow
let labelCount = keyLabels.count
let itemCount = delegate.candidateCountForController(self)
if newIndex == UInt.max {
if itemCount == 0 {
tableView.deselectAll(self)
return
}
newIndex = 0
}
var lastVisibleRow = newValue
if selectedRow != -1 && itemCount > 0 && itemCount > labelCount {
if newIndex > selectedRow && (Int(newIndex) - selectedRow) > 1 {
lastVisibleRow = min(newIndex + UInt(labelCount) - 1, itemCount - 1)
}
// no need to handle the backward case: (newIndex < selectedRow && selectedRow - newIndex > 1)
}
if itemCount > labelCount {
tableView.scrollRowToVisible(Int(lastVisibleRow))
}
tableView.selectRowIndexes(IndexSet(integer: Int(newIndex)), byExtendingSelection: false)
}
}
}
extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegate {
public func numberOfRows(in tableView: NSTableView) -> Int {
Int(delegate?.candidateCountForController(self) ?? 0)
}
public func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
guard let delegate = delegate else {
return nil
}
var candidate = ""
if row < delegate.candidateCountForController(self) {
candidate = delegate.candidateController(self, candidateAtIndex: UInt(row))
}
let attrString = NSAttributedString(string: candidate, attributes: [
.font: candidateFont,
.paragraphStyle: candidateTextParagraphStyle
])
// we do more work than what this method is expected to; normally not a good practice, but for the amount of data (9 to 10 rows max), we can afford the overhead
// expand the window width if text overflows
let boundingRect = attrString.boundingRect(with: NSSize(width: 10240.0, height: 10240.0) , options: .usesLineFragmentOrigin)
let textWidth = boundingRect.size.width + candidateTextPadding
if textWidth > maxCandidateAttrStringWidth {
maxCandidateAttrStringWidth = textWidth
layoutCandidateView()
}
// keep track of the highlighted index in the key label strip
let count = UInt(keyLabels.count)
let selectedRow = tableView.selectedRow
if selectedRow != -1 {
var newHilightIndex = 0
if keyLabelStripView.highlightedIndex != -1 &&
(row >= selectedRow + Int(count) || (selectedRow > count && row <= selectedRow - Int(count))) {
newHilightIndex = -1
} else {
let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin)
newHilightIndex = selectedRow - firstVisibleRow
if newHilightIndex < -1 {
newHilightIndex = -1
}
}
if newHilightIndex != keyLabelStripView.highlightedIndex && newHilightIndex >= 0 {
keyLabelStripView.highlightedIndex = UInt(newHilightIndex)
keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame)
}
}
return attrString
}
public func tableViewSelectionDidChange(_ notification: Notification) {
let selectedRow = tableView.selectedRow
if selectedRow != -1 {
// keep track of the highlighted index in the key label strip
let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin)
keyLabelStripView.highlightedIndex = UInt(selectedRow - firstVisibleRow)
keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame)
// fix a subtle OS X "bug" that, since we force the scroller to appear,
// scrolling sometimes shows a temporarily "broken" scroll bar
// (but quickly disappears)
if scrollView.hasVerticalScroller {
scrollView.verticalScroller?.setNeedsDisplay()
}
}
}
@objc func rowDoubleClicked(_ sender: Any) {
let clickedRow = tableView.clickedRow
if clickedRow != -1 {
delegate?.candidateController(self, didSelectCandidateAtIndex: UInt(clickedRow))
}
}
func scrollPageByOne(_ forward: Bool) -> Bool {
guard let delegate = delegate else {
return false
}
let labelCount = UInt(keyLabels.count)
let itemCount = delegate.candidateCountForController(self)
if 0 == itemCount {
return false
}
if itemCount <= labelCount {
return false
}
var newIndex = selectedCandidateIndex
if forward {
if newIndex == itemCount - 1 {
return false
}
newIndex = min(newIndex + labelCount, itemCount - 1)
} else {
if newIndex == 0 {
return false
}
if newIndex < labelCount {
newIndex = 0
} else {
newIndex -= labelCount
}
}
selectedCandidateIndex = newIndex
return true
}
private func moveSelectionByOne(_ forward: Bool) -> Bool {
guard let delegate = delegate else {
return false
}
let itemCount = delegate.candidateCountForController(self)
if 0 == itemCount {
return false
}
var newIndex = selectedCandidateIndex
if forward {
if newIndex == itemCount - 1 {
return false
}
newIndex += 1
} else {
if 0 == newIndex {
return false
}
newIndex -= 1
}
selectedCandidateIndex = newIndex
return true
}
private func layoutCandidateView() {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { [self] in
self.doLayoutCanaditeView()
}
}
private func doLayoutCanaditeView() {
guard let delegate = delegate else {
return
}
let count = delegate.candidateCountForController(self)
if 0 == count {
return
}
let candidateFontSize = ceil(candidateFont.pointSize)
let keyLabelFontSize = ceil(keyLabelFont.pointSize)
let fontSize = max(candidateFontSize, keyLabelFontSize)
let controlSize: NSControl.ControlSize = fontSize > 36.0 ? .regular : .small
var keyLabelCount = UInt(keyLabels.count)
var scrollerWidth: CGFloat = 0.0
if count <= keyLabelCount {
keyLabelCount = count
scrollView.hasVerticalScroller = false
} else {
scrollView.hasVerticalScroller = true
let verticalScroller = scrollView.verticalScroller
verticalScroller?.controlSize = controlSize
verticalScroller?.scrollerStyle = .legacy
scrollerWidth = NSScroller.scrollerWidth(for: controlSize, scrollerStyle: .legacy)
}
keyLabelStripView.keyLabelFont = keyLabelFont
keyLabelStripView.keyLabels = Array(keyLabels[0..<Int(keyLabelCount)])
keyLabelStripView.labelOffsetY = (keyLabelFontSize >= candidateFontSize) ? 0.0 : floor((candidateFontSize - keyLabelFontSize) / 2.0)
let rowHeight = ceil(fontSize * 1.25)
tableView.rowHeight = rowHeight
var maxKeyLabelWidth = keyLabelFontSize
let textAttr: [NSAttributedString.Key:AnyObject] = [.font: keyLabelFont]
let boundingBox = NSSize(width: 1600.0, height: 1600.0)
for label in keyLabels {
let rect = (label as NSString).boundingRect(with: boundingBox, options: .usesLineFragmentOrigin, attributes: textAttr)
maxKeyLabelWidth = max(rect.size.width, maxKeyLabelWidth)
}
let rowSpacing = tableView.intercellSpacing.height
let stripWidth = ceil(maxKeyLabelWidth * 1.20)
let tableViewStartWidth = ceil(maxCandidateAttrStringWidth + scrollerWidth)
let windowWidth = stripWidth + 1.0 + tableViewStartWidth
let windowHeight = CGFloat(keyLabelCount) * (rowHeight + rowSpacing)
var frameRect = self.window?.frame ?? NSRect.zero
let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height)
frameRect.size = NSMakeSize(windowWidth, windowHeight)
frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height)
keyLabelStripView.frame = NSRect(x: 0.0, y: 0.0, width: stripWidth, height: windowHeight)
scrollView.frame = NSRect(x: stripWidth + 1.0, y: 0.0, width: tableViewStartWidth, height: windowHeight)
self.window?.setFrame(frameRect, display: false)
}
}

View File

@ -1,54 +0,0 @@
//
// AppDelegate.h
//
// Copyright (c) 2011 The McBopomofo Project.
//
// Contributors:
// Mengjuei Hsieh (@mjhsieh)
// Weizhong Yang (@zonble)
//
// Based on the Syrup Project and the Formosana Library
// by Lukhnos Liu (@lukhnos).
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <Cocoa/Cocoa.h>
@class PreferencesWindowController;
@interface AppDelegate : NSObject <NSApplicationDelegate>
{
@private
NSURLConnection *_updateCheckConnection;
BOOL _currentUpdateCheckIsForced;
NSMutableData *_receivingData;
NSURL *_updateNextStepURL;
PreferencesWindowController *_preferencesWindowController;
}
- (void)checkForUpdate;
- (void)checkForUpdateForced:(BOOL)forced;
- (void)showPreferences;
@property (weak, nonatomic) IBOutlet NSWindow *window;
@end

View File

@ -1,273 +0,0 @@
//
// AppDelegate.m
//
// Copyright (c) 2011 The McBopomofo Project.
//
// Contributors:
// Mengjuei Hsieh (@mjhsieh)
// Weizhong Yang (@zonble)
//
// Based on the Syrup Project and the Formosana Library
// by Lukhnos Liu (@lukhnos).
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "AppDelegate.h"
#import "OVNonModalAlertWindowController.h"
#import "PreferencesWindowController.h"
extern void LTLoadLanguageModel(void);
extern void LTLoadUserLanguageModelFile(void);
static NSString *kCheckUpdateAutomatically = @"CheckUpdateAutomatically";
static NSString *kNextUpdateCheckDateKey = @"NextUpdateCheckDate";
static NSString *kUpdateInfoEndpointKey = @"UpdateInfoEndpoint";
static NSString *kUpdateInfoSiteKey = @"UpdateInfoSite";
static const NSTimeInterval kNextCheckInterval = 86400.0;
static const NSTimeInterval kTimeoutInterval = 60.0;
@interface AppDelegate () <NSURLConnectionDataDelegate, OVNonModalAlertWindowControllerDelegate>
@end
@implementation AppDelegate
@synthesize window = _window;
- (void)dealloc
{
_preferencesWindowController = nil;
_updateCheckConnection = nil;
}
- (void)applicationDidFinishLaunching:(NSNotification *)inNotification
{
LTLoadLanguageModel();
LTLoadUserLanguageModelFile();
if (![[NSUserDefaults standardUserDefaults] objectForKey:kCheckUpdateAutomatically]) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kCheckUpdateAutomatically];
[[NSUserDefaults standardUserDefaults] synchronize];
}
[self checkForUpdate];
}
- (void)checkForUpdate
{
[self checkForUpdateForced:NO];
}
- (void)checkForUpdateForced:(BOOL)forced
{
if (_updateCheckConnection) {
// busy
return;
}
_currentUpdateCheckIsForced = forced;
// time for update?
if (!forced) {
if (![[NSUserDefaults standardUserDefaults] boolForKey:kCheckUpdateAutomatically]) {
return;
}
NSDate *now = [NSDate date];
NSDate *date = [[NSUserDefaults standardUserDefaults] objectForKey:kNextUpdateCheckDateKey];
if (![date isKindOfClass:[NSDate class]]) {
date = now;
}
if ([now compare:date] == NSOrderedAscending) {
return;
}
}
NSDate *nextUpdateDate = [NSDate dateWithTimeInterval:kNextCheckInterval sinceDate:[NSDate date]];
[[NSUserDefaults standardUserDefaults] setObject:nextUpdateDate forKey:kNextUpdateCheckDateKey];
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
NSString *updateInfoURLString = [infoDict objectForKey:kUpdateInfoEndpointKey];
if (![updateInfoURLString length]) {
return;
}
NSURL *updateInfoURL = [NSURL URLWithString:updateInfoURLString];
if (!updateInfoURL) {
return;
}
NSURLRequest *request = [NSURLRequest requestWithURL:updateInfoURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:kTimeoutInterval];
if (!request) {
return;
}
#if DEBUG
NSLog(@"about to request update url %@ ",updateInfoURL);
#endif
if (_receivingData) {
_receivingData = nil;
}
// create a new data buffer and connection
_receivingData = [[NSMutableData alloc] init];
_updateCheckConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[_updateCheckConnection start];
}
- (void)showPreferences
{
if (!_preferencesWindowController) {
_preferencesWindowController = [[PreferencesWindowController alloc] initWithWindowNibName:@"preferences"];
}
[[_preferencesWindowController window] center];
[[_preferencesWindowController window] orderFront:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
BOOL isForcedCheck = _currentUpdateCheckIsForced;
_receivingData = nil;
_updateCheckConnection = nil;
_currentUpdateCheckIsForced = NO;
if (isForcedCheck) {
[[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Update Check Failed", nil) content:[NSString stringWithFormat:NSLocalizedString(@"There may be no internet connection or the server failed to respond.\n\nError message: %@", nil), [error localizedDescription]] confirmButtonTitle:NSLocalizedString(@"Dismiss", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil];
}
}
- (void)showNoUpdateAvailableAlert
{
[[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Check for Update Completed", nil) content:NSLocalizedString(@"You are already using the latest version of McBopomofo.", nil) confirmButtonTitle:NSLocalizedString(@"OK", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
id plist = [NSPropertyListSerialization propertyListWithData:_receivingData options:NSPropertyListImmutable format:NULL error:NULL];
#if DEBUG
NSLog(@"plist %@",plist);
#endif
BOOL isForcedCheck = _currentUpdateCheckIsForced;
_receivingData = nil;
_updateCheckConnection = nil;
_currentUpdateCheckIsForced = NO;
if (!plist) {
if (isForcedCheck) {
[self showNoUpdateAvailableAlert];
}
return;
}
NSString *remoteVersion = [plist objectForKey:(id)kCFBundleVersionKey];
#if DEBUG
NSLog(@"the remoteversion is %@",remoteVersion);
#endif
if (!remoteVersion) {
if (isForcedCheck) {
[self showNoUpdateAvailableAlert];
}
return;
}
// TODO: Validate info (e.g. bundle identifier)
// TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
NSString *currentVersion = [infoDict objectForKey:(id)kCFBundleVersionKey];
NSComparisonResult result = [currentVersion compare:remoteVersion options:NSNumericSearch];
if (result != NSOrderedAscending) {
if (isForcedCheck) {
[self showNoUpdateAvailableAlert];
}
return;
}
NSString *siteInfoURLString = [plist objectForKey:kUpdateInfoSiteKey];
if (!siteInfoURLString) {
if (isForcedCheck) {
[self showNoUpdateAvailableAlert];
}
return;
}
NSURL *siteInfoURL = [NSURL URLWithString:siteInfoURLString];
if (!siteInfoURL) {
if (isForcedCheck) {
[self showNoUpdateAvailableAlert];
}
return;
}
_updateNextStepURL = siteInfoURL;
NSDictionary *versionDescriptions = [plist objectForKey:@"Description"];
NSString *versionDescription = @"";
if ([versionDescriptions isKindOfClass:[NSDictionary class]]) {
NSString *locale = @"en";
NSArray *supportedLocales = [NSArray arrayWithObjects:@"en", @"zh-Hant", @"zh-Hans", nil];
NSArray *preferredTags = [NSBundle preferredLocalizationsFromArray:supportedLocales];
if ([preferredTags count]) {
locale = [preferredTags objectAtIndex:0];
}
versionDescription = [versionDescriptions objectForKey:locale];
if (!versionDescription) {
versionDescription = [versionDescriptions objectForKey:@"en"];
}
if (!versionDescription) {
versionDescription = @"";
}
else {
versionDescription = [@"\n\n" stringByAppendingString:versionDescription];
}
}
NSString *content = [NSString stringWithFormat:NSLocalizedString(@"You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@", nil), [infoDict objectForKey:@"CFBundleShortVersionString"], currentVersion, [plist objectForKey:@"CFBundleShortVersionString"], remoteVersion, versionDescription];
[[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"New Version Available", nil) content:content confirmButtonTitle:NSLocalizedString(@"Visit Website", nil) cancelButtonTitle:NSLocalizedString(@"Not Now", nil) cancelAsDefault:NO delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_receivingData appendData:data];
}
- (void)nonModalAlertWindowControllerDidConfirm:(OVNonModalAlertWindowController *)controller
{
if (_updateNextStepURL) {
[[NSWorkspace sharedWorkspace] openURL:_updateNextStepURL];
}
_updateNextStepURL = nil;
}
- (void)nonModalAlertWindowControllerDidCancel:(OVNonModalAlertWindowController *)controller
{
_updateNextStepURL = nil;
}
@end

217
Source/AppDelegate.swift Normal file
View File

@ -0,0 +1,217 @@
//
// AppDelegate.swift
//
// Copyright (c) 2011 The McBopomofo Project.
//
// Contributors:
// Mengjuei Hsieh (@mjhsieh)
// Weizhong Yang (@zonble)
//
// Based on the Syrup Project and the Formosana Library
// by Lukhnos Liu (@lukhnos).
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
import Cocoa
import InputMethodKit
private let kCheckUpdateAutomatically = "CheckUpdateAutomatically"
private let kNextUpdateCheckDateKey = "NextUpdateCheckDate"
private let kUpdateInfoEndpointKey = "UpdateInfoEndpoint"
private let kUpdateInfoSiteKey = "UpdateInfoSite"
private let kNextCheckInterval: TimeInterval = 86400.0
private let kTimeoutInterval: TimeInterval = 60.0
@objc(AppDelegate)
class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControllerDelegate {
@IBOutlet weak var window: NSWindow?
private var preferencesWindowController: PreferencesWindowController?
private var checkTask: URLSessionTask?
private var updateNextStepURL: URL?
func applicationDidFinishLaunching(_ notification: Notification) {
LTLoadLanguageModel()
LTLoadUserLanguageModelFile()
if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil {
UserDefaults.standard.set(true, forKey: kCheckUpdateAutomatically)
UserDefaults.standard.synchronize()
}
checkForUpdate()
}
@objc func showPreferences() {
if preferencesWindowController == nil {
preferencesWindowController = PreferencesWindowController(windowNibName: "preferences")
}
preferencesWindowController?.window?.center()
preferencesWindowController?.window?.orderFront(self)
}
@objc(checkForUpdate)
func checkForUpdate() {
checkForUpdate(forced: false)
}
@objc(checkForUpdateForced:)
func checkForUpdate(forced: Bool) {
if checkTask != nil {
// busy
return
}
// time for update?
if !forced {
if UserDefaults.standard.bool(forKey: kCheckUpdateAutomatically) == false {
return
}
let now = Date()
let date = UserDefaults.standard.object(forKey: kNextUpdateCheckDateKey) as? Date ?? now
if now.compare(date) == .orderedAscending {
return
}
}
let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date())
UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey)
guard let infoDict = Bundle.main.infoDictionary,
let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String,
let updateInfoURL = URL(string: updateInfoURLString) else {
return
}
let request = URLRequest(url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: kTimeoutInterval)
func showNoUpdateAvailableAlert() {
NonModalAlertWindowController.shared.show(title: NSLocalizedString("Check for Update Completed", comment: ""), content: NSLocalizedString("You are already using the latest version of McBopomofo.", comment: ""), confirmButtonTitle: NSLocalizedString("OK", comment: "") , cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
}
let task = URLSession.shared.dataTask(with: request) { data, response, error in
defer {
self.checkTask = nil
}
if let error = error {
if forced {
let title = NSLocalizedString("Update Check Failed", comment: "")
let content = String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), error.localizedDescription)
let buttonTitle = NSLocalizedString("Dismiss", comment: "")
DispatchQueue.main.async {
NonModalAlertWindowController.shared.show(title:title , content: content, confirmButtonTitle: buttonTitle, cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
}
}
return
}
do {
guard let plist = try PropertyListSerialization.propertyList(from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any],
let remoteVersion = plist[kCFBundleVersionKey] as? String,
let infoDict = Bundle.main.infoDictionary
else {
if forced {
DispatchQueue.main.async {
showNoUpdateAvailableAlert()
}
}
return
}
// TODO: Validate info (e.g. bundle identifier)
// TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this
let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? ""
let result = currentVersion.compare(remoteVersion, options: .numeric, range: nil, locale: nil)
if result != .orderedAscending {
if forced {
DispatchQueue.main.async {
showNoUpdateAvailableAlert()
}
}
return
}
guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String,
let siteInfoURL = URL(string: siteInfoURLString) else {
if forced {
DispatchQueue.main.async {
showNoUpdateAvailableAlert()
}
}
return
}
self.updateNextStepURL = siteInfoURL
var versionDescription = ""
let versionDescriptions = plist["Description"] as? [AnyHashable: Any]
if let versionDescriptions = versionDescriptions {
var locale = "en"
let supportedLocales = ["en", "zh-Hant", "zh-Hans"]
let preferredTags = Bundle.preferredLocalizations(from: supportedLocales)
if let first = preferredTags.first {
locale = first
}
versionDescription = versionDescriptions[locale] as? String ?? versionDescriptions["en"] as? String ?? ""
if !versionDescription.isEmpty {
versionDescription = "\n\n" + versionDescription
}
}
let content = String(format: NSLocalizedString("You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@", comment: ""),
infoDict["CFBundleShortVersionString"] as? String ?? "",
currentVersion,
plist["CFBundleShortVersionString"] as? String ?? "",
remoteVersion,
versionDescription)
DispatchQueue.main.async {
NonModalAlertWindowController.shared.show(title: NSLocalizedString("New Version Available", comment: "") , content: content, confirmButtonTitle: NSLocalizedString("Visit Website", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self)
}
} catch {
if forced {
DispatchQueue.main.async {
showNoUpdateAvailableAlert()
}
}
}
}
checkTask = task
task.resume()
}
func nonModalAlertWindowControllerDidConfirm(_ controller: NonModalAlertWindowController) {
if let updateNextStepURL = updateNextStepURL {
NSWorkspace.shared.open(updateNextStepURL)
}
updateNextStepURL = nil
}
func nonModalAlertWindowControllerDidCancel(_ controller: NonModalAlertWindowController) {
updateNextStepURL = nil
}
}

View File

@ -8,10 +8,10 @@
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="PreferencesWindowController">
<connections>
<outlet property="basisKeyboardLayoutButton" destination="124" id="135"/>
<outlet property="basisKeyboardLayoutButton" destination="124" id="1zl-3C-sPD"/>
<outlet property="fontSizePopUpButton" destination="90" id="108"/>
<outlet property="selectionKeyComboBox" destination="uHU-aL-du7" id="cEx-Ui-Phc"/>
<outlet property="window" destination="1" id="30"/>
<outlet property="selectionKeyComboBox" destination="uHU-aL-du7" id="bcd-bW-W5Q"/>
<outlet property="window" destination="1" id="03n-F5-D0u"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>

View File

@ -1,68 +0,0 @@
//
// VTCandidateController.h
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <Cocoa/Cocoa.h>
@class VTCandidateController;
@protocol VTCandidateControllerDelegate
- (NSUInteger)candidateCountForController:(VTCandidateController *)controller;
- (NSString *)candidateController:(VTCandidateController *)controller candidateAtIndex:(NSUInteger)index;
- (void)candidateController:(VTCandidateController *)controller didSelectCandidateAtIndex:(NSUInteger)index;
@end
@interface VTCandidateController : NSWindowController
{
@protected
__weak id<VTCandidateControllerDelegate> _delegate;
NSArray *_keyLabels;
NSFont *_keyLabelFont;
NSFont *_candidateFont;
BOOL _visible;
}
- (void)reloadData;
- (BOOL)showNextPage;
- (BOOL)showPreviousPage;
- (BOOL)highlightNextCandidate;
- (BOOL)highlightPreviousCandidate;
- (void)setWindowTopLeftPoint:(NSPoint)topLeftPoint bottomOutOfScreenAdjustmentHeight:(CGFloat)height;
- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index;
@property (weak, nonatomic) id<VTCandidateControllerDelegate> delegate;
@property (assign, nonatomic) NSUInteger selectedCandidateIndex;
@property (assign, nonatomic) BOOL visible;
@property (assign, nonatomic) NSPoint windowTopLeftPoint;
@property (copy, nonatomic) NSArray *keyLabels;
@property (copy, nonatomic) NSFont *keyLabelFont;
@property (copy, nonatomic) NSFont *candidateFont;
@end

View File

@ -1,178 +0,0 @@
//
// VTCandidateController.m
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTCandidateController.h"
@implementation VTCandidateController
@synthesize delegate = _delegate;
@synthesize keyLabels = _keyLabels;
@synthesize keyLabelFont = _keyLabelFont;
@synthesize candidateFont = _candidateFont;
- (void)dealloc
{
_keyLabels = nil;
_keyLabelFont = nil;
_candidateFont = nil;
}
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if (self) {
// populate the default values
_keyLabels = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"];
_keyLabelFont = [NSFont systemFontOfSize:14.0];
_candidateFont = [NSFont systemFontOfSize:18.0];
}
return self;
}
- (void)reloadData
{
}
- (BOOL)showNextPage
{
return NO;
}
- (BOOL)showPreviousPage
{
return NO;
}
- (BOOL)highlightNextCandidate
{
return NO;
}
- (BOOL)highlightPreviousCandidate
{
return NO;
}
- (void)setWindowTopLeftPoint:(NSPoint)topLeftPoint bottomOutOfScreenAdjustmentHeight:(CGFloat)height
{
// Since layout is now deferred, the origin setting should also be deferred so that
// the correct visible frame dimensions are used.
NSArray *params = [NSArray arrayWithObjects:[NSValue valueWithPoint:topLeftPoint], [NSNumber numberWithDouble:height], nil];
[self performSelector:@selector(deferredSetWindowTopLeftPoint:) withObject:params afterDelay:0.0];
}
- (void)deferredSetWindowTopLeftPoint:(NSArray *)params
{
NSPoint topLeftPoint = [[params objectAtIndex:0] pointValue];
CGFloat height = [[params objectAtIndex:1] doubleValue];
NSPoint adjustedPoint = topLeftPoint;
CGFloat adjustedHeight = height;
// first, locate the screen the point is in
NSRect screenFrame = [[NSScreen mainScreen] visibleFrame];
for (NSScreen *screen in [NSScreen screens]) {
NSRect frame = [screen visibleFrame];
if (topLeftPoint.x >= NSMinX(frame) && topLeftPoint.x <= NSMaxX(frame) && topLeftPoint.y >= NSMinY(frame) && topLeftPoint.y <= NSMaxY(frame)) {
screenFrame = frame;
break;
}
}
// make sure we don't have any erratic value
if (adjustedHeight > screenFrame.size.height / 2.0) {
adjustedHeight = 0.0;
}
NSSize windowSize = [[self window] frame].size;
// bottom beneath the screen?
if (adjustedPoint.y - windowSize.height < NSMinY(screenFrame)) {
adjustedPoint.y = topLeftPoint.y + adjustedHeight + windowSize.height;
}
// top over the screen?
if (adjustedPoint.y >= NSMaxY(screenFrame)) {
adjustedPoint.y = NSMaxY(screenFrame) - 1.0;
}
// right
if (adjustedPoint.x + windowSize.width >= NSMaxX(screenFrame)) {
adjustedPoint.x = NSMaxX(screenFrame) - windowSize.width;
}
// left
if (adjustedPoint.x < NSMinX(screenFrame)) {
adjustedPoint.x = NSMinX(screenFrame);
}
[[self window] setFrameTopLeftPoint:adjustedPoint];
}
- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index
{
return NSUIntegerMax;
}
- (BOOL)visible
{
// Because setVisible: defers its action, we need to use our own visible. Do not use [[self window] isVisible].
return _visible;
}
- (void)setVisible:(BOOL)visible
{
_visible = visible;
if (visible) {
[[self window] performSelector:@selector(orderFront:) withObject:self afterDelay:0.0];
}
else {
[[self window] performSelector:@selector(orderOut:) withObject:self afterDelay:0.0];
}
}
- (NSPoint)windowTopLeftPoint
{
NSRect frameRect = [[self window] frame];
return NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height);
}
- (void)setWindowTopLeftPoint:(NSPoint)topLeftPoint
{
[self setWindowTopLeftPoint:topLeftPoint bottomOutOfScreenAdjustmentHeight:0.0];
}
- (NSUInteger)selectedCandidateIndex
{
return NSUIntegerMax;
}
- (void)setSelectedCandidateIndex:(NSUInteger)newIndex
{
}
@end

View File

@ -1,40 +0,0 @@
//
// VTHorizontalCandidateController.h
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTCandidateController.h"
@class VTHorizontalCandidateView;
@interface VTHorizontalCandidateController : VTCandidateController
{
@protected
VTHorizontalCandidateView *_candidateView;
NSButton *_prevPageButton;
NSButton *_nextPageButton;
NSUInteger _currentPage;
}
@end

View File

@ -1,250 +0,0 @@
//
// VTHorizontalCandidateController.m
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTHorizontalCandidateController.h"
#import "VTHorizontalCandidateView.h"
@interface VTHorizontalCandidateController (Private)
- (NSUInteger)pageCount;
- (void)layoutCandidateView;
- (void)pageButtonAction:(id)sender;
- (void)candidateViewMouseDidClick:(id)sender;
@end
@implementation VTHorizontalCandidateController
- (void)dealloc
{
_candidateView = nil;
_prevPageButton = nil;
_nextPageButton = nil;
}
- (id)init
{
NSRect contentRect = NSMakeRect(128.0, 128.0, 0.0, 0.0);
NSUInteger styleMask = NSBorderlessWindowMask | NSNonactivatingPanelMask;
NSPanel *panel = [[NSPanel alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO];
[panel setLevel:kCGPopUpMenuWindowLevel];
[panel setHasShadow:YES];
self = [self initWithWindow:panel];
if (self) {
contentRect.origin = NSMakePoint(0.0, 0.0);
_candidateView = [[VTHorizontalCandidateView alloc] initWithFrame:contentRect];
_candidateView.target = self;
_candidateView.action = @selector(candidateViewMouseDidClick:);
[[panel contentView] addSubview:_candidateView];
contentRect.size = NSMakeSize(36.0, 20.0);
_nextPageButton = [[NSButton alloc] initWithFrame:contentRect];
_prevPageButton = [[NSButton alloc] initWithFrame:contentRect];
[_nextPageButton setButtonType:NSMomentaryLightButton];
[_nextPageButton setBezelStyle:NSSmallSquareBezelStyle];
[_nextPageButton setTitle:@"»"];
[_nextPageButton setTarget:self];
[_nextPageButton setAction:@selector(pageButtonAction:)];
[_prevPageButton setButtonType:NSMomentaryLightButton];
[_prevPageButton setBezelStyle:NSSmallSquareBezelStyle];
[_prevPageButton setTitle:@"«"];
[_prevPageButton setTarget:self];
[_prevPageButton setAction:@selector(pageButtonAction:)];
[[panel contentView] addSubview:_nextPageButton];
[[panel contentView] addSubview:_prevPageButton];
}
return self;
}
- (void)reloadData
{
_candidateView.highlightedIndex = 0;
_currentPage = 0;
[self layoutCandidateView];
}
- (BOOL)showNextPage
{
if (_currentPage + 1 >= [self pageCount]) {
return NO;
}
_currentPage++;
_candidateView.highlightedIndex = 0;
[self layoutCandidateView];
return YES;
}
- (BOOL)showPreviousPage
{
if (_currentPage == 0) {
return NO;
}
_currentPage--;
_candidateView.highlightedIndex = 0;
[self layoutCandidateView];
return YES;
}
- (BOOL)highlightNextCandidate
{
NSUInteger currentIndex = self.selectedCandidateIndex;
if (currentIndex + 1 >= [_delegate candidateCountForController:self]) {
return NO;
}
self.selectedCandidateIndex = currentIndex + 1;
return YES;
}
- (BOOL)highlightPreviousCandidate
{
NSUInteger currentIndex = self.selectedCandidateIndex;
if (currentIndex == 0) {
return NO;
}
self.selectedCandidateIndex = currentIndex - 1;
return YES;
}
- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index
{
NSUInteger result = _currentPage * [_keyLabels count] + index;
return result < [_delegate candidateCountForController:self] ? result : NSUIntegerMax;
}
- (NSUInteger)selectedCandidateIndex
{
return _currentPage * [_keyLabels count] + _candidateView.highlightedIndex;
}
- (void)setSelectedCandidateIndex:(NSUInteger)newIndex
{
NSUInteger keyLabelCount = [_keyLabels count];
if (newIndex < [_delegate candidateCountForController:self]) {
_currentPage = newIndex / keyLabelCount;
_candidateView.highlightedIndex = newIndex % keyLabelCount;
[self layoutCandidateView];
}
}
@end
@implementation VTHorizontalCandidateController (Private)
- (NSUInteger)pageCount
{
NSUInteger totalCount = [_delegate candidateCountForController:self];
NSUInteger keyLabelCount = [_keyLabels count];
return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0);
}
- (void)layoutCandidateView
{
[_candidateView setKeyLabelFont:_keyLabelFont candidateFont:_candidateFont];
NSMutableArray *candidates = [NSMutableArray array];
NSUInteger count = [_delegate candidateCountForController:self];
NSUInteger keyLabelCount = [_keyLabels count];
for (NSUInteger index = _currentPage * keyLabelCount, j = 0; index < count && j < keyLabelCount; index++, j++) {
[candidates addObject:[_delegate candidateController:self candidateAtIndex:index]];
}
[_candidateView setKeyLabels:_keyLabels displayedCandidates:candidates];
NSSize newSize = _candidateView.sizeForView;
NSRect frameRect = [_candidateView frame];
frameRect.size = newSize;
[_candidateView setFrame:frameRect];
if ([self pageCount] > 1) {
NSRect buttonRect = [_nextPageButton frame];
CGFloat spacing = 0.0;
if (newSize.height < 40.0) {
buttonRect.size.height = floor(newSize.height / 2);
}
else {
buttonRect.size.height = 20.0;
}
if (newSize.height >= 60.0) {
spacing = ceil(newSize.height * 0.1);
}
CGFloat buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0;
buttonRect.origin = NSMakePoint(newSize.width + 8.0, buttonOriginY);
[_nextPageButton setFrame:buttonRect];
buttonRect.origin = NSMakePoint(newSize.width + 8.0, buttonOriginY + buttonRect.size.height + spacing);
[_prevPageButton setFrame:buttonRect];
[_nextPageButton setEnabled:(_currentPage + 1 < [self pageCount])];
[_prevPageButton setEnabled:(_currentPage != 0)];
newSize.width += 52.0;
[_nextPageButton setHidden:NO];
[_prevPageButton setHidden:NO];
}
else {
[_nextPageButton setHidden:YES];
[_prevPageButton setHidden:YES];
}
frameRect = [[self window] frame];
NSPoint topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height);
frameRect.size = newSize;
frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height);
[[self window] setFrame:frameRect display:NO];
[_candidateView setNeedsDisplay:YES];
}
- (void)pageButtonAction:(id)sender
{
if (sender == _nextPageButton) {
[self showNextPage];
}
else if (sender == _prevPageButton) {
[self showPreviousPage];
}
}
- (void)candidateViewMouseDidClick:(id)sender
{
[_delegate candidateController:self didSelectCandidateAtIndex:self.selectedCandidateIndex];
}
@end

View File

@ -1,54 +0,0 @@
//
// VTHorizontalCandidateView.h
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <Cocoa/Cocoa.h>
@interface VTHorizontalCandidateView : NSView
{
@protected
NSArray *_keyLabels;
NSArray *_displayedCandidates;
CGFloat _keyLabelHeight;
CGFloat _candidateTextHeight;
CGFloat _cellPadding;
NSDictionary *_keyLabelAttrDict;
NSDictionary *_candidateAttrDict;
NSArray *_elementWidths;
NSUInteger _highlightedIndex;
NSUInteger _trackingHighlightedIndex;
SEL _action;
__weak id _target;
}
- (void)setKeyLabels:(NSArray *)labels displayedCandidates:(NSArray *)candidates;
- (void)setKeyLabelFont:(NSFont *)labelFont candidateFont:(NSFont *)candidateFont;
@property (readonly, nonatomic) NSSize sizeForView;
@property (assign, nonatomic) NSUInteger highlightedIndex;
@property (assign, nonatomic) SEL action;
@property (weak, nonatomic) id target;
@end

View File

@ -1,225 +0,0 @@
//
// VTHorizontalCandidateView.m
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTHorizontalCandidateView.h"
// use these instead of MIN/MAX macro to keep compilers happy with pedantic warnings on
NS_INLINE CGFloat min(CGFloat a, CGFloat b) { return a < b ? a : b; }
NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; }
@implementation VTHorizontalCandidateView
@synthesize highlightedIndex = _highlightedIndex;
@synthesize action = _action;
@synthesize target = _target;
- (void)dealloc
{
_keyLabels = nil;
_displayedCandidates = nil;
_keyLabelAttrDict = nil;
_candidateAttrDict = nil;
_elementWidths = nil;
}
- (void)setKeyLabels:(NSArray *)labels displayedCandidates:(NSArray *)candidates
{
NSUInteger count = min([labels count], [candidates count]);
_keyLabels = [labels subarrayWithRange:NSMakeRange(0, count)];
_displayedCandidates = [candidates subarrayWithRange:NSMakeRange(0, count)];
NSMutableArray *newWidths = [NSMutableArray array];
NSSize baseSize = NSMakeSize(10240.0, 10240.0);
for (NSUInteger index = 0; index < count; index++) {
NSRect labelRect = [[_keyLabels objectAtIndex:index] boundingRectWithSize:baseSize options:NSStringDrawingUsesLineFragmentOrigin attributes:_keyLabelAttrDict];
NSRect candidateRect = [[_displayedCandidates objectAtIndex:index] boundingRectWithSize:baseSize options:NSStringDrawingUsesLineFragmentOrigin attributes:_candidateAttrDict];
CGFloat width = max(labelRect.size.width, candidateRect.size.width) + _cellPadding;
[newWidths addObject:[NSNumber numberWithDouble:width]];
}
_elementWidths = newWidths;
}
- (void)setKeyLabelFont:(NSFont *)labelFont candidateFont:(NSFont *)candidateFont
{
NSMutableParagraphStyle *paraStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[paraStyle setAlignment:NSCenterTextAlignment];
_keyLabelAttrDict = [NSDictionary dictionaryWithObjectsAndKeys:
labelFont, NSFontAttributeName,
paraStyle, NSParagraphStyleAttributeName,
[NSColor blackColor], NSForegroundColorAttributeName,
nil];
_candidateAttrDict = [NSDictionary dictionaryWithObjectsAndKeys:
candidateFont, NSFontAttributeName,
paraStyle, NSParagraphStyleAttributeName,
[NSColor textColor], NSForegroundColorAttributeName,
nil];
CGFloat labelFontSize = [labelFont pointSize];
CGFloat candidateFontSize = [candidateFont pointSize];
CGFloat biggestSize = max(labelFontSize, candidateFontSize);
_keyLabelHeight = ceil(labelFontSize * 1.20);
_candidateTextHeight = ceil(candidateFontSize * 1.20);
_cellPadding = ceil(biggestSize / 2.0);
}
- (NSSize)sizeForView
{
NSSize result = NSMakeSize(0.0, 0.0);
if ([_elementWidths count]) {
for (NSNumber *w in _elementWidths) {
result.width += [w doubleValue];
}
result.width += [_elementWidths count];
result.height = _keyLabelHeight + _candidateTextHeight + 1.0;
}
return result;
}
- (BOOL)isFlipped
{
return YES;
}
- (void)drawRect:(NSRect)dirtyRect
{
NSColor *backgroundColor = [NSColor controlBackgroundColor];
NSColor *darkGray = [NSColor colorWithDeviceWhite:0.7 alpha:1.0];
NSColor *lightGray = [NSColor colorWithDeviceWhite:0.8 alpha:1.0];
NSRect bounds = [self bounds];
[backgroundColor setFill];
[NSBezierPath fillRect:bounds];
[[NSColor darkGrayColor] setStroke];
[NSBezierPath strokeLineFromPoint:NSMakePoint(bounds.size.width, 0.0) toPoint:NSMakePoint(bounds.size.width, bounds.size.height)];
NSUInteger count = [_elementWidths count];
CGFloat accuWidth = 0.0;
for (NSUInteger index = 0; index < count; index++) {
NSDictionary *activeCandidateAttr = _candidateAttrDict;
CGFloat currentWidth = [[_elementWidths objectAtIndex:index] doubleValue];
NSRect labelRect = NSMakeRect(accuWidth, 0.0, currentWidth, _keyLabelHeight);
NSRect candidateRect = NSMakeRect(accuWidth, _keyLabelHeight + 1.0, currentWidth, _candidateTextHeight);
if (index == _highlightedIndex) {
[darkGray setFill];
}
else {
[lightGray setFill];
}
[NSBezierPath fillRect:labelRect];
[[_keyLabels objectAtIndex:index] drawInRect:labelRect withAttributes:_keyLabelAttrDict];
if (index == _highlightedIndex) {
[[NSColor selectedTextBackgroundColor] setFill];
activeCandidateAttr = [_candidateAttrDict mutableCopy];
[(NSMutableDictionary *)activeCandidateAttr setObject:[NSColor selectedTextColor] forKey:NSForegroundColorAttributeName];
}
else {
[backgroundColor setFill];
}
[NSBezierPath fillRect:candidateRect];
[[_displayedCandidates objectAtIndex:index] drawInRect:candidateRect withAttributes:activeCandidateAttr];
accuWidth += currentWidth + 1.0;
}
}
- (NSUInteger)findHitIndex:(NSEvent *)theEvent
{
NSUInteger result = NSUIntegerMax;
NSPoint location = [self convertPoint:[theEvent locationInWindow] toView:nil];
if (!NSPointInRect(location, [self bounds])) {
return result;
}
NSUInteger count = [_elementWidths count];
CGFloat accuWidth = 0.0;
for (NSUInteger index = 0; index < count; index++) {
CGFloat currentWidth = [[_elementWidths objectAtIndex:index] doubleValue];
if (location.x >= accuWidth && location.x <= accuWidth + currentWidth) {
result = index;
break;
}
accuWidth += currentWidth + 1.0;
}
return result;
}
- (void)mouseDown:(NSEvent *)theEvent
{
NSUInteger newIndex = [self findHitIndex:theEvent];
_trackingHighlightedIndex = _highlightedIndex;
if (newIndex != NSUIntegerMax) {
_highlightedIndex = newIndex;
[self setNeedsDisplay:YES];
}
}
- (void)mouseUp:(NSEvent *)theEvent
{
NSUInteger newIndex = [self findHitIndex:theEvent];
BOOL triggerAction = NO;
if (newIndex == _highlightedIndex) {
triggerAction = YES;
}
else {
_highlightedIndex = _trackingHighlightedIndex;
}
_trackingHighlightedIndex = 0;
[self setNeedsDisplay:YES];
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if (triggerAction && _target && _action) {
[_target performSelector:_action withObject:self];
}
# pragma clang diagnostic pop
}
@end

View File

@ -1,41 +0,0 @@
//
// VTVerticalCandidateController.h
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTCandidateController.h"
@class VTVerticalKeyLabelStripView;
@interface VTVerticalCandidateController : VTCandidateController
{
@protected
VTVerticalKeyLabelStripView *_keyLabelStripView;
NSScrollView *_scrollView;
NSTableView *_tableView;
NSMutableParagraphStyle *_candidateTextParagraphStyle;
CGFloat _maxCandidateAttrStringWidth;
}
@end

View File

@ -1,434 +0,0 @@
//
// VTVerticalCandidateController.m
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTVerticalCandidateController.h"
#import "VTVerticalKeyLabelStripView.h"
#import "VTVerticalCandidateTableView.h"
// use these instead of MIN/MAX macro to keep compilers happy with pedantic warnings on
NS_INLINE CGFloat min(CGFloat a, CGFloat b) { return a < b ? a : b; }
NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; }
static const CGFloat kCandidateTextPadding = 24.0;
static const CGFloat kCandidateTextLeftMargin = 8.0;
#if defined(__MAC_10_16)
static const CGFloat kCandidateTextPaddingWithMandatedTableViewPadding = 18.0;
static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0;
#endif
@interface VTVerticalCandidateController (Private) <NSTableViewDataSource, NSTableViewDelegate>
- (void)rowDoubleClicked:(id)sender;
- (BOOL)scrollPageByOne:(BOOL)forward;
- (BOOL)moveSelectionByOne:(BOOL)forward;
- (void)layoutCandidateView;
@end
@implementation VTVerticalCandidateController
{
// Total padding added to the left and the right of the table view cell text.
CGFloat _candidateTextPadding;
// The indent of the table view cell text from the left.
CGFloat _candidateTextLeftMargin;
}
- (id)init
{
NSRect contentRect = NSMakeRect(128.0, 128.0, 0.0, 0.0);
NSUInteger styleMask = NSBorderlessWindowMask | NSNonactivatingPanelMask;
NSPanel *panel = [[NSPanel alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO];
[panel setLevel:kCGPopUpMenuWindowLevel];
[panel setHasShadow:YES];
self = [self initWithWindow:panel];
if (self) {
contentRect.origin = NSMakePoint(0.0, 0.0);
NSRect stripRect = contentRect;
stripRect.size.width = 10.0;
_keyLabelStripView = [[VTVerticalKeyLabelStripView alloc] initWithFrame:stripRect];
[[panel contentView] addSubview:_keyLabelStripView];
NSRect scrollViewRect = contentRect;
scrollViewRect.origin.x = stripRect.size.width;
scrollViewRect.size.width -= stripRect.size.width;
_scrollView = [[NSScrollView alloc] initWithFrame:scrollViewRect];
// >=10.7 only, elastic scroll causes some drawing issues with visible scroller, so we disable it
if ([_scrollView respondsToSelector:@selector(setVerticalScrollElasticity:)]) {
[_scrollView setVerticalScrollElasticity:NSScrollElasticityNone];
}
_tableView = [[VTVerticalCandidateTableView alloc] initWithFrame:contentRect];
[_tableView setDataSource:self];
[_tableView setDelegate:self];
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"candidate"];
[column setDataCell:[[NSTextFieldCell alloc] init]];
[column setEditable:NO];
_candidateTextPadding = kCandidateTextPadding;
_candidateTextLeftMargin = kCandidateTextLeftMargin;
[_tableView addTableColumn:column];
[_tableView setIntercellSpacing:NSMakeSize(0.0, 1.0)];
[_tableView setHeaderView:nil];
[_tableView setAllowsMultipleSelection:NO];
[_tableView setAllowsEmptySelection:YES];
[_tableView setDoubleAction:@selector(rowDoubleClicked:)];
[_tableView setTarget:self];
#if defined(__MAC_10_16)
if (@available(macOS 10.16, *)) {
[_tableView setStyle:NSTableViewStyleFullWidth];
_candidateTextPadding = kCandidateTextPaddingWithMandatedTableViewPadding;
_candidateTextLeftMargin = kCandidateTextLeftMarginWithMandatedTableViewPadding;
}
#endif
[_scrollView setDocumentView:_tableView];
[[panel contentView] addSubview:_scrollView];
_candidateTextParagraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[_candidateTextParagraphStyle setFirstLineHeadIndent:_candidateTextLeftMargin];
[_candidateTextParagraphStyle setLineBreakMode:NSLineBreakByClipping];
}
return self;
}
- (void)reloadData
{
_maxCandidateAttrStringWidth = ceil([_candidateFont pointSize] * 2.0 + _candidateTextPadding);
[_tableView reloadData];
[self layoutCandidateView];
if ([_delegate candidateCountForController:self]) {
self.selectedCandidateIndex = 0;
}
}
- (BOOL)showNextPage
{
return [self scrollPageByOne:YES];
}
- (BOOL)showPreviousPage
{
return [self scrollPageByOne:NO];
}
- (BOOL)highlightNextCandidate
{
return [self moveSelectionByOne:YES];
}
- (BOOL)highlightPreviousCandidate
{
return [self moveSelectionByOne:NO];
}
- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index
{
NSInteger firstVisibleRow = [_tableView rowAtPoint:[_scrollView documentVisibleRect].origin];
if (firstVisibleRow != -1) {
NSUInteger result = firstVisibleRow + index;
if (result < [_delegate candidateCountForController:self]) {
return result;
}
}
return NSUIntegerMax;
}
- (NSUInteger)selectedCandidateIndex
{
NSInteger selectedRow = [_tableView selectedRow];
return (selectedRow == -1) ? NSUIntegerMax : selectedRow;
}
- (void)setSelectedCandidateIndex:(NSUInteger)aNewIndex
{
NSUInteger newIndex = aNewIndex;
NSInteger selectedRow = [_tableView selectedRow];
NSUInteger labelCount = [_keyLabels count];
NSUInteger itemCount = [_delegate candidateCountForController:self];
if (newIndex == NSUIntegerMax) {
if (itemCount == 0) {
[_tableView deselectAll:self];
return;
}
newIndex = 0;
}
NSUInteger lastVisibleRow = newIndex;
if (selectedRow != -1 && itemCount > 0 && itemCount > labelCount) {
if (newIndex > selectedRow && (newIndex - selectedRow) > 1) {
lastVisibleRow = min(newIndex + labelCount - 1, itemCount - 1);
}
// no need to handle the backward case: (newIndex < selectedRow && selectedRow - newIndex > 1)
}
if (itemCount > labelCount) {
[_tableView scrollRowToVisible:lastVisibleRow];
}
[_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:newIndex] byExtendingSelection:NO];
}
@end
@implementation VTVerticalCandidateController (Private)
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [_delegate candidateCountForController:self];
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSString *candidate = @"";
// rendering can occur when the delegate is already gone or data goes stale; in that case we ignore it
if (row < [_delegate candidateCountForController:self]) {
candidate = [_delegate candidateController:self candidateAtIndex:row];
}
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:candidate attributes:[NSDictionary dictionaryWithObjectsAndKeys:_candidateFont, NSFontAttributeName, _candidateTextParagraphStyle, NSParagraphStyleAttributeName, nil]];
// we do more work than what this method is expected to; normally not a good practice, but for the amount of data (9 to 10 rows max), we can afford the overhead
// expand the window width if text overflows
NSRect boundingRect = [attrString boundingRectWithSize:NSMakeSize(10240.0, 10240.0) options:NSStringDrawingUsesLineFragmentOrigin];
CGFloat textWidth = boundingRect.size.width + _candidateTextPadding;
if (textWidth > _maxCandidateAttrStringWidth) {
_maxCandidateAttrStringWidth = textWidth;
[self layoutCandidateView];
}
// keep track of the highlighted index in the key label strip
NSUInteger count = [_keyLabels count];
NSInteger selectedRow = [_tableView selectedRow];
if (selectedRow != -1) {
// cast this into signed integer to make our life easier
NSInteger newHilightIndex;
if (_keyLabelStripView.highlightedIndex != -1 && (row >= selectedRow + count || (selectedRow > count && row <= selectedRow - count))) {
newHilightIndex = -1;
}
else {
NSInteger firstVisibleRow = [_tableView rowAtPoint:[_scrollView documentVisibleRect].origin];
newHilightIndex = selectedRow - firstVisibleRow;
if (newHilightIndex < -1) {
newHilightIndex = -1;
}
}
if (newHilightIndex != _keyLabelStripView.highlightedIndex && newHilightIndex >= 0) {
_keyLabelStripView.highlightedIndex = newHilightIndex;
[_keyLabelStripView setNeedsDisplay:YES];
}
}
return attrString;
}
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
{
NSInteger selectedRow = [_tableView selectedRow];
if (selectedRow != -1) {
// keep track of the highlighted index in the key label strip
NSInteger firstVisibleRow = [_tableView rowAtPoint:[_scrollView documentVisibleRect].origin];
_keyLabelStripView.highlightedIndex = selectedRow - firstVisibleRow;
[_keyLabelStripView setNeedsDisplay:YES];
// fix a subtle OS X "bug" that, since we force the scroller to appear,
// scrolling sometimes shows a temporarily "broken" scroll bar
// (but quickly disappears)
if ([_scrollView hasVerticalScroller]) {
[[_scrollView verticalScroller] setNeedsDisplay];
}
}
}
- (void)rowDoubleClicked:(id)sender
{
NSInteger clickedRow = [_tableView clickedRow];
if (clickedRow != -1) {
[_delegate candidateController:self didSelectCandidateAtIndex:clickedRow];
}
}
- (BOOL)scrollPageByOne:(BOOL)forward
{
NSUInteger labelCount = [_keyLabels count];
NSUInteger itemCount = [_delegate candidateCountForController:self];
if (0 == itemCount) {
return NO;
}
if (itemCount <= labelCount) {
return NO;
}
NSUInteger newIndex = self.selectedCandidateIndex;
if (forward) {
if (newIndex == itemCount - 1) {
return NO;
}
newIndex = min(newIndex + labelCount, itemCount - 1);
}
else {
if (newIndex == 0) {
return NO;
}
if (newIndex < labelCount) {
newIndex = 0;
}
else {
newIndex -= labelCount;
}
}
self.selectedCandidateIndex = newIndex;
return YES;
}
- (BOOL)moveSelectionByOne:(BOOL)forward
{
NSUInteger itemCount = [_delegate candidateCountForController:self];
if (0 == itemCount) {
return NO;
}
NSUInteger newIndex = self.selectedCandidateIndex;
if (forward) {
if (newIndex == itemCount - 1) {
return NO;
}
newIndex++;
}
else {
if (0 == newIndex) {
return NO;
}
newIndex--;
}
self.selectedCandidateIndex = newIndex;
return YES;
}
- (void)layoutCandidateView
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(doLayoutCanaditeView) object:nil];
[self performSelector:@selector(doLayoutCanaditeView) withObject:nil afterDelay:0.0];
}
- (void)doLayoutCanaditeView
{
NSUInteger count = [_delegate candidateCountForController:self];
if (!count) {
return;
}
CGFloat candidateFontSize = ceil([_candidateFont pointSize]);
CGFloat keyLabelFontSize = ceil([_keyLabelFont pointSize]);
CGFloat fontSize = max(candidateFontSize, keyLabelFontSize);
NSControlSize controlSize = (fontSize > 36.0) ? NSRegularControlSize : NSSmallControlSize;
NSUInteger keyLabelCount = [_keyLabels count];
CGFloat scrollerWidth = 0.0;
if (count <= keyLabelCount) {
keyLabelCount = count;
[_scrollView setHasVerticalScroller:NO];
}
else {
[_scrollView setHasVerticalScroller:YES];
NSScroller *verticalScroller = [_scrollView verticalScroller];
[verticalScroller setControlSize:controlSize];
[verticalScroller setScrollerStyle:NSScrollerStyleLegacy];
scrollerWidth = [NSScroller scrollerWidthForControlSize:controlSize scrollerStyle:NSScrollerStyleLegacy];
}
_keyLabelStripView.keyLabelFont = _keyLabelFont;
_keyLabelStripView.keyLabels = [_keyLabels subarrayWithRange:NSMakeRange(0, keyLabelCount)];
_keyLabelStripView.labelOffsetY = (keyLabelFontSize >= candidateFontSize) ? 0.0 : floor((candidateFontSize - keyLabelFontSize) / 2.0);
CGFloat rowHeight = ceil(fontSize * 1.25);
[_tableView setRowHeight:rowHeight];
CGFloat maxKeyLabelWidth = keyLabelFontSize;
NSDictionary *textAttr = [NSDictionary dictionaryWithObjectsAndKeys:
_keyLabelFont, NSFontAttributeName,
nil];
NSSize boundingBox = NSMakeSize(1600.0, 1600.0);
for (NSString *label in _keyLabels) {
NSRect rect = [label boundingRectWithSize:boundingBox options:NSStringDrawingUsesLineFragmentOrigin attributes:textAttr];
maxKeyLabelWidth = max(rect.size.width, maxKeyLabelWidth);
}
CGFloat rowSpacing = [_tableView intercellSpacing].height;
CGFloat stripWidth = ceil(maxKeyLabelWidth * 1.20);
CGFloat tableViewStartWidth = ceil(_maxCandidateAttrStringWidth + scrollerWidth);;
CGFloat windowWidth = stripWidth + 1.0 + tableViewStartWidth;
CGFloat windowHeight = keyLabelCount * (rowHeight + rowSpacing);
NSRect frameRect = [[self window] frame];
NSPoint topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height);
frameRect.size = NSMakeSize(windowWidth, windowHeight);
frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height);
[_keyLabelStripView setFrame:NSMakeRect(0.0, 0.0, stripWidth, windowHeight)];
[_scrollView setFrame:NSMakeRect(stripWidth + 1.0, 0.0, tableViewStartWidth, windowHeight)];
[[self window] setFrame:frameRect display:NO];
}
@end

View File

@ -1,31 +0,0 @@
//
// VTVerticalCandidateTableView.h
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <Cocoa/Cocoa.h>
@interface VTVerticalCandidateTableView : NSTableView
@end

View File

@ -1,38 +0,0 @@
//
// VTVerticalCandidateTableView.m
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTVerticalCandidateTableView.h"
@implementation VTVerticalCandidateTableView
- (NSRect)adjustScroll:(NSRect)newVisible
{
NSRect scrollRect = newVisible;
CGFloat rowHeightPlusSpacing = [self rowHeight] + [self intercellSpacing].height;
scrollRect.origin.y = (NSInteger)(scrollRect.origin.y / rowHeightPlusSpacing) * rowHeightPlusSpacing;
return scrollRect;
}
@end

View File

@ -1,43 +0,0 @@
//
// VTVerticalKeyLabelStripView.m
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <Cocoa/Cocoa.h>
@interface VTVerticalKeyLabelStripView : NSView
{
@protected
NSFont *_keyLabelFont;
CGFloat _labelOffsetY;
NSArray *_keyLabels;
NSInteger _highlightedIndex;
}
@property (retain, nonatomic) NSFont *keyLabelFont;
@property (assign, nonatomic) CGFloat labelOffsetY;
@property (retain, nonatomic) NSArray *keyLabels;
@property (assign, nonatomic) NSInteger highlightedIndex;
@end

View File

@ -1,104 +0,0 @@
//
// VTVerticalKeyLabelStripView.h
//
// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "VTVerticalKeyLabelStripView.h"
@implementation VTVerticalKeyLabelStripView
@synthesize keyLabelFont = _keyLabelFont;
@synthesize labelOffsetY = _labelOffsetY;
@synthesize keyLabels = _keyLabels;
@synthesize highlightedIndex = _highlightedIndex;
- (void)dealloc
{
_keyLabelFont = nil;
_keyLabels = nil;
}
- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if (self) {
_keyLabelFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
}
return self;
}
- (BOOL)isFlipped
{
return YES;
}
- (void)drawRect:(NSRect)dirtyRect
{
NSRect bounds = [self bounds];
[[NSColor whiteColor] setFill];
[NSBezierPath fillRect:bounds];
NSUInteger count = [_keyLabels count];
if (!count) {
return;
}
CGFloat cellHeight = bounds.size.height / count;
NSColor *black = [NSColor blackColor];
NSColor *darkGray = [NSColor colorWithDeviceWhite:0.7 alpha:1.0];
NSColor *lightGray = [NSColor colorWithDeviceWhite:0.8 alpha:1.0];
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setAlignment:NSCenterTextAlignment];
NSDictionary *textAttr = [NSDictionary dictionaryWithObjectsAndKeys:
_keyLabelFont, NSFontAttributeName,
black, NSForegroundColorAttributeName,
style, NSParagraphStyleAttributeName,
nil];
for (NSUInteger index = 0; index < count; index++) {
NSRect textRect = NSMakeRect(0.0, index * cellHeight + _labelOffsetY, bounds.size.width, cellHeight - _labelOffsetY);
NSRect cellRect = NSMakeRect(0.0, index * cellHeight, bounds.size.width, cellHeight - 1);
// fill in the last cell
if (index + 1 >= count) {
cellRect.size.height += 1.0;
}
if (index == _highlightedIndex) {
[darkGray setFill];
}
else {
[lightGray setFill];
}
[NSBezierPath fillRect:cellRect];
NSString *text = [_keyLabels objectAtIndex:index];
[text drawInRect:textRect withAttributes:textAttr];
}
}
@end

View File

@ -38,12 +38,11 @@
#import <set>
#import "OVStringHelper.h"
#import "OVUTF8Helper.h"
#import "AppDelegate.h"
#import "OVNonModalAlertWindowController.h"
#import "VTHorizontalCandidateController.h"
#import "VTVerticalCandidateController.h"
#import "McBopomofo-Swift.h"
@import CandidateUI;
@import OpenCC;
//@import SwiftUI;
// C++ namespace usages
@ -286,9 +285,6 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
NSMenuItem *aboutMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"About McBopomofo…", @"") action:@selector(showAbout:) keyEquivalent:@""];
[menu addItem:aboutMenuItem];
NSLog(@"menu %@", menu);
return menu;
}
@ -1589,7 +1585,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
NSLog(@"openUserPhrases called");
if (!LTCheckIfUserLanguageModelFileExists()) {
NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), LTUserDataFolderPath()];
[[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil];
[[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil];
return;
}

View File

@ -0,0 +1,142 @@
//
// OVInputSourceHelper.swift
//
// Copyright (c) 2011 The McBopomofo Project.
//
// Contributors:
// Mengjuei Hsieh (@mjhsieh)
// Weizhong Yang (@zonble)
//
// Based on the Syrup Project and the Formosana Library
// by Lukhnos Liu (@lukhnos).
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
import Cocoa
import Carbon
public class InputSourceHelper: NSObject {
@available(*, unavailable)
public override init() {
super.init()
}
public static func allInstalledInputSources() -> [TISInputSource] {
TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource]
}
@objc(inputSourceForProperty:stringValue:)
public static func inputSource(for propertyKey: CFString, stringValue: String) -> TISInputSource? {
let stringID = CFStringGetTypeID()
for source in allInstalledInputSources() {
if let proprtyPtr = TISGetInputSourceProperty(source, propertyKey) {
let property = Unmanaged<CFTypeRef>.fromOpaque(proprtyPtr).takeUnretainedValue()
let typeID = CFGetTypeID(property)
if typeID != stringID {
continue
}
if stringValue == property as? String {
return source
}
}
}
return nil
}
@objc(inputSourceForInputSourceID:)
public static func inputSource(for sourceID: String) -> TISInputSource? {
inputSource(for: kTISPropertyInputSourceID, stringValue: sourceID)
}
@objc(inputSourceEnabled:)
public static func inputSourceEnabled(for source: TISInputSource) -> Bool {
if let valuePts = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsEnabled) {
let value = Unmanaged<CFBoolean>.fromOpaque(valuePts).takeUnretainedValue()
return value == kCFBooleanTrue
}
return false
}
@objc(enableInputSource:)
public static func enable(inputSource: TISInputSource) -> Bool {
let status = TISEnableInputSource(inputSource)
return status == noErr
}
@objc(enableAllInputModesForInputSourceBundleID:)
public static func enableAllInputMode(for inputSourceBundleD: String) -> Bool {
var enabled = false
for source in allInstalledInputSources() {
guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID),
let _ = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else {
continue
}
let bundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue()
if String(bundleID) == inputSourceBundleD {
let modeEnabled = self.enable(inputSource: source)
if !modeEnabled {
return false
}
enabled = true
}
}
return enabled
}
@objc(enableInputMode:forInputSourceBundleID:)
public static func enable(inputMode modeID: String, for bundleID: String) -> Bool {
for source in allInstalledInputSources() {
guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID),
let modePtr = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else {
continue
}
let inputsSourceBundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue()
let inputsSourceModeID = Unmanaged<CFString>.fromOpaque(modePtr).takeUnretainedValue()
if modeID == String(inputsSourceModeID) && bundleID == String(inputsSourceBundleID) {
let enabled = enable(inputSource: source)
print("Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(enabled)")
return enabled
}
}
print("Failed to find any matching input source of mode: \(modeID), bundle ID: \(bundleID)")
return false
}
@objc(disableInputSource:)
public static func disable(inputSource: TISInputSource) -> Bool {
let status = TISDisableInputSource(inputSource)
return status == noErr
}
@objc(registerInputSource:)
public static func registerTnputSource(at url: URL) -> Bool {
let status = TISRegisterInputSource(url as CFURL)
return status == noErr
}
}

View File

@ -27,7 +27,7 @@
#import "AppDelegate.h"
#import <sys/mount.h>
#import "OVInputSourceHelper.h"
#import "McBopomofoInstaller-Swift.h"
static NSString *const kTargetBin = @"McBopomofo";
static NSString *const kTargetType = @"app";
@ -182,13 +182,13 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) {
NSURL *imeBundleURL = imeBundle.bundleURL;
NSString *imeIdentifier = imeBundle.bundleIdentifier;
TISInputSourceRef inputSource = [OVInputSourceHelper inputSourceForInputSourceID:imeIdentifier];
TISInputSourceRef inputSource = [InputSourceHelper inputSourceForInputSourceID:imeIdentifier];
// if this IME name is not found in the list of available IMEs
if (!inputSource) {
NSLog(@"Registering input source %@ at %@.", imeIdentifier, imeBundleURL.absoluteString);
// then register
BOOL status = [OVInputSourceHelper registerInputSource:imeBundleURL];
BOOL status = [InputSourceHelper registerInputSource:imeBundleURL];
if (!status) {
NSString *message = [NSString stringWithFormat:NSLocalizedString(@"Cannot register input source %@ at %@.", nil), imeIdentifier, imeBundleURL.absoluteString];
@ -197,7 +197,7 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) {
return;
}
inputSource = [OVInputSourceHelper inputSourceForInputSourceID:imeIdentifier];
inputSource = [InputSourceHelper inputSourceForInputSourceID:imeIdentifier];
// if it still doesn't register successfully, bail.
if (!inputSource) {
NSString *message = [NSString stringWithFormat:NSLocalizedString(@"Cannot find input source %@ after registration.", nil), imeIdentifier];
@ -219,10 +219,10 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) {
// as the kTISPropertyInputSourceIsEnabled can still be true even if the IME is *not*
// enabled in the user's current set of IMEs (which means the IME does not show up in
// the user's input menu).
BOOL mainInputSourceEnabled = [OVInputSourceHelper inputSourceEnabled:inputSource];
BOOL mainInputSourceEnabled = [InputSourceHelper inputSourceEnabled:inputSource];
if (!mainInputSourceEnabled || isMacOS12OrAbove) {
mainInputSourceEnabled = [OVInputSourceHelper enableInputSource:inputSource];
mainInputSourceEnabled = [InputSourceHelper enableInputSource:inputSource];
if (mainInputSourceEnabled) {
NSLog(@"Input method enabled: %@", imeIdentifier);
} else {

View File

@ -2,3 +2,7 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//
extern void LTLoadLanguageModel(void);
extern void LTLoadUserLanguageModelFile(void);

View File

@ -0,0 +1,132 @@
//
// NonModalAlertWindowController.swift
//
// Copyright (c) 2011 The McBopomofo Project.
//
// Contributors:
// Mengjuei Hsieh (@mjhsieh)
// Weizhong Yang (@zonble)
//
// Based on the Syrup Project and the Formosana Library
// by Lukhnos Liu (@lukhnos).
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
import Cocoa
@objc protocol NonModalAlertWindowControllerDelegate: AnyObject {
func nonModalAlertWindowControllerDidConfirm(_ controller: NonModalAlertWindowController)
func nonModalAlertWindowControllerDidCancel(_ controller: NonModalAlertWindowController)
}
class NonModalAlertWindowController: NSWindowController {
@objc(sharedInstance) static let shared = NonModalAlertWindowController(windowNibName: "NonModalAlertWindowController")
@IBOutlet weak var titleTextField: NSTextField!
@IBOutlet weak var contentTextField: NSTextField!
@IBOutlet weak var confirmButton: NSButton!
@IBOutlet weak var cancelButton: NSButton!
weak var delegate: NonModalAlertWindowControllerDelegate?
@objc func show(title: String, content: String, confirmButtonTitle: String, cancelButtonTitle: String?, cancelAsDefault: Bool, delegate: NonModalAlertWindowControllerDelegate?) {
if window?.isVisible == true {
self.delegate?.nonModalAlertWindowControllerDidCancel(self)
}
self.delegate = delegate
var oldFrame = confirmButton.frame
confirmButton.title = confirmButtonTitle
confirmButton.sizeToFit()
var newFrame = confirmButton.frame
newFrame.size.width = max(90, newFrame.size.width + 10)
newFrame.origin.x += oldFrame.size.width - newFrame.size.width
self.confirmButton.frame = newFrame
if let cancelButtonTitle = cancelButtonTitle {
cancelButton.title = cancelButtonTitle
cancelButton.sizeToFit()
var adjustFrame = cancelButton.frame
adjustFrame.size.width = max(90, adjustFrame.size.width + 10)
adjustFrame.origin.x = newFrame.origin.x - adjustFrame.size.width
self.confirmButton.frame = adjustFrame
self.cancelButton.isHidden = false
} else {
self.cancelButton.isHidden = true
}
cancelButton.nextKeyView = confirmButton
confirmButton.nextKeyView = cancelButton
if cancelButtonTitle != nil {
if cancelAsDefault {
window?.defaultButtonCell = cancelButton.cell as? NSButtonCell
} else {
cancelButton.keyEquivalent = " "
window?.defaultButtonCell = confirmButton.cell as? NSButtonCell
}
} else {
window?.defaultButtonCell = confirmButton.cell as? NSButtonCell
}
titleTextField.stringValue = title
oldFrame = contentTextField.frame
contentTextField.stringValue = content
var infiniteHeightFrame = oldFrame
infiniteHeightFrame.size.width -= 4.0
infiniteHeightFrame.size.height = 10240
newFrame = (content as NSString).boundingRect(with: infiniteHeightFrame.size, options: [.usesLineFragmentOrigin], attributes: [.font: contentTextField.font!])
newFrame.size.width = max(newFrame.size.width, oldFrame.size.width)
newFrame.size.height += 4.0
newFrame.origin = oldFrame.origin
newFrame.origin.y -= (newFrame.size.height - oldFrame.size.height)
contentTextField.frame = newFrame
var windowFrame = window?.frame ?? NSRect.zero
windowFrame.size.height += (newFrame.size.height - oldFrame.size.height)
window?.level = NSWindow.Level(Int(CGShieldingWindowLevel()) + 1)
window?.setFrame(windowFrame, display: true)
window?.center()
window?.makeKeyAndOrderFront(self)
NSApp.activate(ignoringOtherApps: true)
}
@IBAction func confirmButtonAction(_ sender: Any) {
delegate?.nonModalAlertWindowControllerDidConfirm(self)
window?.orderOut(self)
}
@IBAction func cancelButtonAction(_ sender: Any) {
cancel(sender)
}
func cancel(_ sender: Any) {
delegate?.nonModalAlertWindowControllerDidCancel(self)
delegate = nil
window?.orderOut(self)
}
}

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17156" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17156"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="OVNonModalAlertWindowController">
<customObject id="-2" userLabel="File's Owner" customClass="NonModalAlertWindowController" customModule="McBopomofo">
<connections>
<outlet property="cancelButton" destination="71" id="83"/>
<outlet property="confirmButton" destination="5" id="82"/>
@ -21,7 +21,7 @@
<windowStyleMask key="styleMask" titled="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="420" height="130"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
<view key="contentView" id="2">
<rect key="frame" x="0.0" y="0.0" width="420" height="130"/>
<autoresizingMask key="autoresizingMask"/>

View File

@ -1,50 +0,0 @@
//
// OVInputSourceHelper.h
//
// Copyright (c) 2010-2011 Lukhnos D. Liu (lukhnos at lukhnos dot org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <InputMethodKit/InputMethodKit.h>
@interface OVInputSourceHelper : NSObject
// list all installed input sources
+ (NSArray *)allInstalledInputSources;
// search for a certain input source
+ (TISInputSourceRef)inputSourceForProperty:(CFStringRef)inPropertyKey stringValue:(NSString *)inValue;
// shorthand for -inputSourceForProerty:kTISPropertyInputSourceID stringValue:<value>
+ (TISInputSourceRef)inputSourceForInputSourceID:(NSString *)inID;
// enable/disable an input source (along with all its input modes)
+ (BOOL)inputSourceEnabled:(TISInputSourceRef)inInputSource;
+ (BOOL)enableInputSource:(TISInputSourceRef)inInputSource;
+ (BOOL)enableAllInputModesForInputSourceBundleID:(NSString *)inID;
+ (BOOL)enableInputMode:(NSString *)modeID forInputSourceBundleID:(NSString *)bundleID;
+ (BOOL)disableInputSource:(TISInputSourceRef)inInputSource;
// register (i.e. make available to Input Source tab in Language & Text Preferences)
// an input source installed in (~)/Library/Input Methods or (~)/Library/Keyboard Layouts/
+ (BOOL)registerInputSource:(NSURL *)inBundleURL;
@end

View File

@ -1,122 +0,0 @@
//
// OVInputSourceHelper.m
//
// Copyright (c) 2010-2011 Lukhnos D. Liu (lukhnos at lukhnos dot org)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "OVInputSourceHelper.h"
@implementation OVInputSourceHelper
+ (NSArray *)allInstalledInputSources
{
CFArrayRef list = TISCreateInputSourceList(NULL, true);
return (__bridge NSArray *)list;
// return [NSMakeCollectable(list) autorelease];
}
+ (TISInputSourceRef)inputSourceForProperty:(CFStringRef)inPropertyKey stringValue:(NSString *)inValue
{
CFTypeID stringID = CFStringGetTypeID();
for (id source in [self allInstalledInputSources]) {
CFTypeRef property = TISGetInputSourceProperty((__bridge TISInputSourceRef)source, inPropertyKey);
if (!property || CFGetTypeID(property) != stringID) {
continue;
}
if (inValue && [inValue compare:(__bridge NSString *)property] == NSOrderedSame) {
return (__bridge TISInputSourceRef)source;
}
}
return NULL;
}
+ (TISInputSourceRef)inputSourceForInputSourceID:(NSString *)inID
{
return [self inputSourceForProperty:kTISPropertyInputSourceID stringValue:inID];
}
+ (BOOL)inputSourceEnabled:(TISInputSourceRef)inInputSource
{
CFBooleanRef value = TISGetInputSourceProperty(inInputSource, kTISPropertyInputSourceIsEnabled);
return value ? (BOOL)CFBooleanGetValue(value) : NO;
}
+ (BOOL)enableInputSource:(TISInputSourceRef)inInputSource
{
OSStatus status = TISEnableInputSource(inInputSource);
return status == noErr;
}
+ (BOOL)enableAllInputModesForInputSourceBundleID:(NSString *)inID
{
BOOL enabled = NO;
for (id source in [self allInstalledInputSources]) {
TISInputSourceRef inputSource = (__bridge TISInputSourceRef)source;
NSString *bundleID = (__bridge NSString *)TISGetInputSourceProperty(inputSource, kTISPropertyBundleID);
NSString *mode = (NSString *)CFBridgingRelease(TISGetInputSourceProperty(inputSource, kTISPropertyInputModeID));
if (mode && [bundleID isEqualToString:inID]) {
BOOL modeEnabled = [self enableInputSource:inputSource];
if (!modeEnabled) {
return NO;
}
enabled = YES;
}
}
return enabled;
}
+ (BOOL)enableInputMode:(NSString *)modeID forInputSourceBundleID:(NSString *)bundleID
{
for (id source in [self allInstalledInputSources]) {
TISInputSourceRef inputSource = (__bridge TISInputSourceRef)source;
NSString *inputSoureBundleID = (__bridge NSString *)TISGetInputSourceProperty(inputSource, kTISPropertyBundleID);
NSString *inputSourceModeID = (NSString *)CFBridgingRelease(TISGetInputSourceProperty(inputSource, kTISPropertyInputModeID));
if ([modeID isEqual:inputSourceModeID] && [bundleID isEqual:inputSoureBundleID]) {
BOOL enabled = [self enableInputSource:inputSource];
NSLog(@"Attempt to enable input source of mode: %@, bundle ID: %@, result: %d", modeID, bundleID, enabled);
return enabled;
}
}
NSLog(@"Failed to find any matching input source of mode: %@, bundle ID: %@", modeID, bundleID);
return NO;
}
+ (BOOL)disableInputSource:(TISInputSourceRef)inInputSource
{
OSStatus status = TISDisableInputSource(inInputSource);
return status == noErr;
}
+ (BOOL)registerInputSource:(NSURL *)inBundleURL
{
OSStatus status = TISRegisterInputSource((__bridge CFURLRef)inBundleURL);
return status == noErr;
}
@end

View File

@ -1,31 +0,0 @@
//
// OVNonModalAlertWindowController.h
// OpenVanilla
//
// Created by Lukhnos Liu on 10/17/12.
// Copyright (c) 2012 The OpenVanilla Project. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class OVNonModalAlertWindowController;
@protocol OVNonModalAlertWindowControllerDelegate <NSObject>
- (void)nonModalAlertWindowControllerDidConfirm:(OVNonModalAlertWindowController *)controller;
@optional
- (void)nonModalAlertWindowControllerDidCancel:(OVNonModalAlertWindowController *)controller;
@end
@interface OVNonModalAlertWindowController : NSWindowController
+ (OVNonModalAlertWindowController *)sharedInstance;
- (void)showWithTitle:(NSString *)title content:(NSString *)content confirmButtonTitle:(NSString *)confirmTitle cancelButtonTitle:(NSString *)cancelButtonTitle cancelAsDefault:(BOOL)cancelAsDefault delegate:(id<OVNonModalAlertWindowControllerDelegate>)delegate;
- (IBAction)confirmButtonAction:(id)sender;
- (IBAction)cancelButtonAction:(id)sender;
@property (assign, nonatomic) IBOutlet NSTextField *titleTextField;
@property (assign, nonatomic) IBOutlet NSTextField *contentTextField;
@property (assign, nonatomic) IBOutlet NSButton *confirmButton;
@property (assign, nonatomic) IBOutlet NSButton *cancelButton;
@property (assign, nonatomic) id<OVNonModalAlertWindowControllerDelegate> delegate;
@end

View File

@ -1,131 +0,0 @@
//
// OVNonModalAlertWindowController.m
// OpenVanilla
//
// Created by Lukhnos Liu on 10/17/12.
// Copyright (c) 2012 The OpenVanilla Project. All rights reserved.
//
#import "OVNonModalAlertWindowController.h"
@implementation OVNonModalAlertWindowController
@synthesize titleTextField = _titleTextField;
@synthesize contentTextField = _contentTextField;
@synthesize confirmButton = _confirmButton;
@synthesize cancelButton = _cancelButton;
@synthesize delegate = _delegate;
+ (OVNonModalAlertWindowController *)sharedInstance
{
static OVNonModalAlertWindowController *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[OVNonModalAlertWindowController alloc] initWithWindowNibName:@"OVNonModalAlertWindowController"];
[instance window];
});
return instance;
}
// Suppress the use of the MIN/MAX macros
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wgnu-statement-expression"
- (void)showWithTitle:(NSString *)title content:(NSString *)content confirmButtonTitle:(NSString *)confirmTitle cancelButtonTitle:(NSString *)cancelButtonTitle cancelAsDefault:(BOOL)cancelAsDefault delegate:(id <OVNonModalAlertWindowControllerDelegate>)delegate;
{
// cancel previous alert
if (self.window.visible) {
if ([_delegate respondsToSelector:@selector(nonModalAlertWindowControllerDidCancel:)]) {
[_delegate nonModalAlertWindowControllerDidCancel:self];
}
}
_delegate = delegate;
NSRect oldFrame = self.confirmButton.frame;
[self.confirmButton setTitle:confirmTitle];
[self.confirmButton sizeToFit];
NSRect newFrame = self.confirmButton.frame;
newFrame.size.width = MAX(90.0, (newFrame.size.width + 10.0));
newFrame.origin.x += (oldFrame.size.width - newFrame.size.width);
[self.confirmButton setFrame:newFrame];
if (cancelButtonTitle) {
[self.cancelButton setTitle:cancelButtonTitle];
[self.cancelButton sizeToFit];
NSRect adjustedFrame = self.cancelButton.frame;
adjustedFrame.size.width = MAX(90.0, (adjustedFrame.size.width + 10.0));
adjustedFrame.origin.x = newFrame.origin.x - adjustedFrame.size.width;
self.cancelButton.frame = adjustedFrame;
self.cancelButton.hidden = NO;
}
else {
self.cancelButton.hidden = YES;
}
self.cancelButton.nextKeyView = self.confirmButton;
self.confirmButton.nextKeyView = self.cancelButton;
if (cancelButtonTitle) {
if (cancelAsDefault) {
[self.window setDefaultButtonCell:self.cancelButton.cell];
}
else {
self.cancelButton.keyEquivalent = @" ";
[self.window setDefaultButtonCell:self.confirmButton.cell];
}
}
else {
[[self window] setDefaultButtonCell:self.confirmButton.cell];
}
self.titleTextField.stringValue = title;
oldFrame = [self.contentTextField frame];
self.contentTextField.stringValue = content;
NSRect infiniteHeightFrame = oldFrame;
infiniteHeightFrame.size.width -= 4.0;
infiniteHeightFrame.size.height = 10240;
newFrame = [content boundingRectWithSize:infiniteHeightFrame.size options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: self.contentTextField.font}];
newFrame.size.width = MAX(newFrame.size.width, oldFrame.size.width);
newFrame.size.height += 4.0;
newFrame.origin = oldFrame.origin;
newFrame.origin.y -= (newFrame.size.height - oldFrame.size.height);
[self.contentTextField setFrame:newFrame];
NSRect windowFrame = [[self window] frame];
windowFrame.size.height += (newFrame.size.height - oldFrame.size.height);
self.window.level = CGShieldingWindowLevel() + 1;
[self.window setFrame:windowFrame display:YES];
[self.window center];
[self.window makeKeyAndOrderFront:self];
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
}
#pragma GCC diagnostic pop
- (IBAction)confirmButtonAction:(id)sender
{
[_delegate nonModalAlertWindowControllerDidConfirm:self];
[self.window orderOut:self];
}
- (IBAction)cancelButtonAction:(id)sender
{
[self cancel:sender];
}
- (void)cancel:(id)sender
{
if ([_delegate respondsToSelector:@selector(nonModalAlertWindowControllerDidCancel:)]) {
[_delegate nonModalAlertWindowControllerDidCancel:self];
}
_delegate = nil;
[self.window orderOut:self];
}
@end

View File

@ -3,7 +3,7 @@ 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 {
class OpenCCBridge: NSObject {
private static let shared = OpenCCBridge()
private var converter: ChineseConverter?

View File

@ -1,50 +0,0 @@
//
// PreferencesWindowController.h
//
// Copyright (c) 2011 The McBopomofo Project.
//
// Contributors:
// Mengjuei Hsieh (@mjhsieh)
// Weizhong Yang (@zonble)
//
// Based on the Syrup Project and the Formosana Library
// by Lukhnos Liu (@lukhnos).
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <Cocoa/Cocoa.h>
@interface PreferencesWindowController : NSWindowController
{
@private
NSPopUpButton *__weak _fontSizePopUpButton;
NSPopUpButton *__weak _basisKeyboardLayoutButton;
NSComboBox *__weak _selectionKeyComboBox;
}
- (IBAction)updateBasisKeyboardLayoutAction:(id)sender;
- (IBAction)changeSelectionKeyAction:(id)sender;
@property (weak, nonatomic) IBOutlet NSPopUpButton *fontSizePopUpButton;
@property (weak, nonatomic) IBOutlet NSPopUpButton *basisKeyboardLayoutButton;
@property (weak, nonatomic) IBOutlet NSComboBox *selectionKeyComboBox;
@end

View File

@ -1,139 +0,0 @@
//
// PreferencesWindowController.m
//
// Copyright (c) 2011 The McBopomofo Project.
//
// Contributors:
// Mengjuei Hsieh (@mjhsieh)
// Weizhong Yang (@zonble)
//
// Based on the Syrup Project and the Formosana Library
// by Lukhnos Liu (@lukhnos).
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "PreferencesWindowController.h"
#import <Carbon/Carbon.h>
static NSString *const kBasisKeyboardLayoutPreferenceKey = @"BasisKeyboardLayout"; // alphanumeric ("ASCII") input basis
static NSString *const kCandidateKeys = @"CandidateKeys";
static NSString *const kDefaultKeys = @"123456789";
@implementation PreferencesWindowController
@synthesize fontSizePopUpButton = _fontSizePopUpButton;
@synthesize basisKeyboardLayoutButton = _basisKeyboardLayoutButton;
@synthesize selectionKeyComboBox = _selectionKeyComboBox;
- (void)awakeFromNib
{
CFArrayRef list = TISCreateInputSourceList(NULL, true);
NSMenuItem *usKeyboardLayoutItem = nil;
NSMenuItem *chosenItem = nil;
[self.basisKeyboardLayoutButton.menu removeAllItems];
NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey];
for (int i = 0; i < CFArrayGetCount(list); i++) {
TISInputSourceRef source = (TISInputSourceRef)CFArrayGetValueAtIndex(list, i);
CFStringRef category = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory);
if (CFStringCompare(category, kTISCategoryKeyboardInputSource, 0) != kCFCompareEqualTo) {
continue;
}
CFBooleanRef asciiCapable = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsASCIICapable);
if (!CFBooleanGetValue(asciiCapable)) {
continue;
}
CFStringRef sourceType = TISGetInputSourceProperty(source, kTISPropertyInputSourceType);
if (CFStringCompare(sourceType, kTISTypeKeyboardLayout, 0) != kCFCompareEqualTo) {
continue;
}
NSString *sourceID = (__bridge NSString *)TISGetInputSourceProperty(source, kTISPropertyInputSourceID);
NSString *localizedName = (__bridge NSString *)TISGetInputSourceProperty(source, kTISPropertyLocalizedName);
NSMenuItem *item = [[NSMenuItem alloc] init];
item.title = localizedName;
item.representedObject = sourceID;
if ([sourceID isEqualToString:@"com.apple.keylayout.US"]) {
usKeyboardLayoutItem = item;
}
// false if nil
if ([basisKeyboardLayoutID isEqualToString:sourceID]) {
chosenItem = item;
}
[self.basisKeyboardLayoutButton.menu addItem:item];
}
[self.basisKeyboardLayoutButton selectItem:(chosenItem ? chosenItem : usKeyboardLayoutItem)];
CFRelease(list);
self.selectionKeyComboBox.usesDataSource = NO;
[self.selectionKeyComboBox removeAllItems];
[self.selectionKeyComboBox addItemsWithObjectValues:@[
kDefaultKeys,
@"asdfghjkl",
@"asdfzxcvb"
]];
NSString *ckeys = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateKeys];
if (!ckeys || [ckeys stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length == 0) {
ckeys = kDefaultKeys;
}
[self.selectionKeyComboBox setStringValue:ckeys];
}
- (IBAction)updateBasisKeyboardLayoutAction:(id)sender
{
NSString *sourceID = [[self.basisKeyboardLayoutButton selectedItem] representedObject];
if (sourceID) {
[[NSUserDefaults standardUserDefaults] setObject:sourceID forKey:kBasisKeyboardLayoutPreferenceKey];
}
}
- (IBAction)changeSelectionKeyAction:(id)sender
{
NSString *keys = [[sender stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (keys.length != 9 ||
![keys canBeConvertedToEncoding:NSASCIIStringEncoding]) {
[self.selectionKeyComboBox setStringValue:kDefaultKeys];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kCandidateKeys];
NSBeep();
return;
}
[self.selectionKeyComboBox setStringValue:keys];
if ([keys isEqualToString:kDefaultKeys]) {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kCandidateKeys];
} else {
[[NSUserDefaults standardUserDefaults] setObject:keys forKey:kCandidateKeys];
}
}
@end

View File

@ -0,0 +1,144 @@
//
// PreferencesWindowController.swift
//
// Copyright (c) 2011 The McBopomofo Project.
//
// Contributors:
// Mengjuei Hsieh (@mjhsieh)
// Weizhong Yang (@zonble)
//
// Based on the Syrup Project and the Formosana Library
// by Lukhnos Liu (@lukhnos).
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
import Cocoa
import Carbon
private let kBasisKeyboardLayoutPreferenceKey = "BasisKeyboardLayout"
private let kCandidateKeys = "CandidateKeys"
private let kDefaultKeys = "123456789"
// Please note that the class should be exposed as "PreferencesWindowController"
// in Objective-C in order to let IMK to see the same class name as
// the "InputMethodServerPreferencesWindowControllerClass" in Info.plist.
@objc(PreferencesWindowController) class PreferencesWindowController: NSWindowController {
@IBOutlet weak var fontSizePopUpButton: NSPopUpButton!
@IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton!
@IBOutlet weak var selectionKeyComboBox: NSComboBox!
override func awakeFromNib() {
let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource]
var usKeyboardLayoutItem: NSMenuItem? = nil
var chosenItem: NSMenuItem? = nil
basisKeyboardLayoutButton.menu?.removeAllItems()
let basisKeyboardLayoutID = UserDefaults.standard.string(forKey: kBasisKeyboardLayoutPreferenceKey)
for source in list {
if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) {
let category = Unmanaged<CFString>.fromOpaque(categoryPtr).takeUnretainedValue()
if category != kTISCategoryKeyboardInputSource {
continue
}
} else {
continue
}
if let asciiCapablePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsASCIICapable) {
let asciiCapable = Unmanaged<CFBoolean>.fromOpaque(asciiCapablePtr).takeUnretainedValue()
if asciiCapable != kCFBooleanTrue {
continue
}
} else {
continue
}
if let sourceTypePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) {
let sourceType = Unmanaged<CFString>.fromOpaque(sourceTypePtr).takeUnretainedValue()
if sourceType != kTISTypeKeyboardLayout {
continue
}
} else {
continue
}
guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID),
let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) else {
continue
}
let sourceID = String(Unmanaged<CFString>.fromOpaque(sourceIDPtr).takeUnretainedValue())
let localizedName = String(Unmanaged<CFString>.fromOpaque(localizedNamePtr).takeUnretainedValue())
let menuItem = NSMenuItem()
menuItem.title = localizedName
menuItem.representedObject = sourceID
if sourceID == "com.apple.keylayout.US" {
usKeyboardLayoutItem = menuItem
}
if basisKeyboardLayoutID == sourceID {
chosenItem = menuItem
}
basisKeyboardLayoutButton.menu?.addItem(menuItem)
}
basisKeyboardLayoutButton.select(chosenItem ?? usKeyboardLayoutItem)
selectionKeyComboBox.usesDataSource = false
selectionKeyComboBox.addItems(withObjectValues: [kDefaultKeys, "asdfghjkl", "asdfzxcvb"])
var candidateSelectionKeys = (UserDefaults.standard.string(forKey: kCandidateKeys) ?? kDefaultKeys)
.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if candidateSelectionKeys.isEmpty {
candidateSelectionKeys = kDefaultKeys
}
selectionKeyComboBox.stringValue = candidateSelectionKeys
}
@IBAction func updateBasisKeyboardLayoutAction(_ sender:Any) {
if let sourceID = basisKeyboardLayoutButton.selectedItem?.representedObject {
UserDefaults.standard.set(sourceID, forKey: kBasisKeyboardLayoutPreferenceKey)
}
}
@IBAction func changeSelectionKeyAction(_ sender: Any) {
let keys = (sender as AnyObject).stringValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if keys.count != 9 || !keys.canBeConverted(to: .ascii) {
selectionKeyComboBox.stringValue = kDefaultKeys
UserDefaults.standard.removeObject(forKey: kCandidateKeys)
NSSound.beep()
return
}
selectionKeyComboBox.stringValue = keys
if keys == kDefaultKeys {
UserDefaults.standard.removeObject(forKey: kCandidateKeys)
} else {
UserDefaults.standard.set(keys, forKey: kCandidateKeys)
}
}
}

View File

@ -33,7 +33,8 @@
//
#import <Cocoa/Cocoa.h>
#import "OVInputSourceHelper.h"
#import <InputMethodKit/InputMethodKit.h>
#import "McBopomofo-Swift.h"
static NSString *const kConnectionName = @"McBopomofo_1_Connection";
@ -54,20 +55,20 @@ int main(int argc, char *argv[])
bundleURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
}
TISInputSourceRef inputSource = [OVInputSourceHelper inputSourceForInputSourceID:bundleID];
TISInputSourceRef inputSource = [InputSourceHelper inputSourceForInputSourceID:bundleID];
// if this IME name is not found in the list of available IMEs
if (!inputSource) {
NSLog(@"Registering input source %@ at %@.", bundleID, [bundleURL absoluteString]);
// then register
BOOL status = [OVInputSourceHelper registerInputSource:bundleURL];
BOOL status = [InputSourceHelper registerInputSource:bundleURL];
if (!status) {
NSLog(@"Fatal error: Cannot register input source %@ at %@.", bundleID, [bundleURL absoluteString]);
return -1;
}
inputSource = [OVInputSourceHelper inputSourceForInputSourceID:bundleID];
inputSource = [InputSourceHelper inputSourceForInputSourceID:bundleID];
// if it still doesn't register successfully, bail.
if (!inputSource) {
NSLog(@"Fatal error: Cannot find input source %@ after registration.", bundleID);
@ -76,22 +77,22 @@ int main(int argc, char *argv[])
}
// if it's not enabled, just enabled it
if (inputSource && ![OVInputSourceHelper inputSourceEnabled:inputSource]) {
if (inputSource && ![InputSourceHelper inputSourceEnabled:inputSource]) {
NSLog(@"Enabling input source %@ at %@.", bundleID, [bundleURL absoluteString]);
BOOL status = [OVInputSourceHelper enableInputSource:inputSource];
BOOL status = [InputSourceHelper enableInputSource:inputSource];
if (!status) {
NSLog(@"Fatal error: Cannot enable input source %@.", bundleID);
return -1;
}
if (![OVInputSourceHelper inputSourceEnabled:inputSource]){
if (![InputSourceHelper inputSourceEnabled:inputSource]){
NSLog(@"Fatal error: Cannot enable input source %@.", bundleID);
return -1;
}
}
if (argc > 2 && !strcmp(argv[2], "--all")) {
BOOL enabled = [OVInputSourceHelper enableAllInputModesForInputSourceBundleID:bundleID];
BOOL enabled = [InputSourceHelper enableAllInputModesForInputSourceBundleID:bundleID];
if (enabled) {
NSLog(@"All input sources enabled for %@", bundleID);
}