Merge pull request #212 from zonble/dev/swiftify
Converts most of the Objective-C classes into Swift
This commit is contained in:
commit
11d33c0b42
|
@ -11,3 +11,4 @@ Credits.rtf
|
|||
# that can be built by make -C Source/Data/bin/C_Version
|
||||
# C_count.occ.exe
|
||||
.idea
|
||||
.swiftpm
|
||||
|
|
|
@ -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" */;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
|
@ -0,0 +1,25 @@
|
|||
// swift-tools-version:5.5
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "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: []),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
# CandidateUI
|
||||
|
||||
The candidate window for McBopomofo.
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"/>
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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"/>
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue