@ -0,0 +1,8 @@ |
|||||||
|
|
||||||
|
*.xcuserstate |
||||||
|
.DS_Store |
||||||
|
.DS_Store |
||||||
|
Clocker/Clocker.xcodeproj/project.xcworkspace/xcuserdata/abhishek_banthia.xcuserdatad/UserInterfaceState.xcuserstate |
||||||
|
Clocker/Clocker.xcodeproj/xcuserdata/abhishek_banthia.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist |
||||||
|
*.xcuserstate |
||||||
|
.DS_Store |
@ -0,0 +1,8 @@ |
|||||||
|
os: osx |
||||||
|
language: swift |
||||||
|
xcode_project: Clocker.xcodeproj # path to your xcodeproj folder |
||||||
|
osx_image: xcode10.2 |
||||||
|
script: |
||||||
|
- set -o pipefail |
||||||
|
- xcodebuild -project Clocker/Clocker.xcodeproj -scheme Clocker build analyze | xcpretty |
||||||
|
- xcodebuild -project Clocker/Clocker.xcodeproj -scheme Tests test-without-building | xcpretty |
After Width: | Height: | Size: 251 KiB |
After Width: | Height: | Size: 295 KiB |
After Width: | Height: | Size: 272 KiB |
After Width: | Height: | Size: 1022 KiB |
After Width: | Height: | Size: 481 KiB |
After Width: | Height: | Size: 711 KiB |
After Width: | Height: | Size: 258 KiB |
After Width: | Height: | Size: 812 KiB |
After Width: | Height: | Size: 144 KiB |
@ -0,0 +1,294 @@ |
|||||||
|
// Copyright © 2015 Abhishek Banthia |
||||||
|
|
||||||
|
import Cocoa |
||||||
|
|
||||||
|
open class AppDelegate : NSObject, NSApplicationDelegate { |
||||||
|
|
||||||
|
lazy private var floatingWindow: FloatingWindowController = FloatingWindowController.shared() |
||||||
|
lazy private var panelController: PanelController = PanelController.shared() |
||||||
|
private var statusBarHandler: StatusItemHandler! |
||||||
|
|
||||||
|
deinit { |
||||||
|
panelController.removeObserver(self, forKeyPath: "hasActivePanel") |
||||||
|
} |
||||||
|
|
||||||
|
private var kContextActivePanel = 0 |
||||||
|
|
||||||
|
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { |
||||||
|
|
||||||
|
if context == &kContextActivePanel { |
||||||
|
statusBarHandler.setHasActiveIcon(panelController.hasActivePanelGetter()) |
||||||
|
} else if let path = keyPath, path == "values.globalPing" { |
||||||
|
|
||||||
|
let hotKeyCenter = PTHotKeyCenter.shared() |
||||||
|
|
||||||
|
// Unregister old hot key |
||||||
|
let oldHotKey = hotKeyCenter?.hotKey(withIdentifier: path) |
||||||
|
hotKeyCenter?.unregisterHotKey(oldHotKey) |
||||||
|
|
||||||
|
// We don't register unless there's a valid key combination |
||||||
|
guard let newObject = object as? NSObject, let newShortcut = newObject.value(forKeyPath: path) as? [AnyHashable: Any] else { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Register new key |
||||||
|
let newHotKey: PTHotKey = PTHotKey(identifier: keyPath, |
||||||
|
keyCombo: newShortcut, |
||||||
|
target: self, |
||||||
|
action: #selector(ping(_:))) |
||||||
|
|
||||||
|
hotKeyCenter?.register(newHotKey) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public func applicationWillFinishLaunching(_ notification: Notification) { |
||||||
|
iVersion.sharedInstance().useAllAvailableLanguages = true |
||||||
|
iVersion.sharedInstance().verboseLogging = false |
||||||
|
} |
||||||
|
|
||||||
|
public func applicationDidFinishLaunching(_ notification: Notification) { |
||||||
|
|
||||||
|
// Initializing the event store takes really long |
||||||
|
EventCenter.sharedCenter() |
||||||
|
|
||||||
|
AppDefaults.initialize() |
||||||
|
|
||||||
|
// Check if we can show the onboarding flow! |
||||||
|
showOnboardingFlow() |
||||||
|
|
||||||
|
// Ratings Controller initialization |
||||||
|
RateController.applicationDidLaunch(UserDefaults.standard) |
||||||
|
|
||||||
|
#if RELEASE |
||||||
|
Crashlytics.sharedInstance().debugMode = true |
||||||
|
Fabric.with([Crashlytics.self]) |
||||||
|
checkIfRunFromApplicationsFolder() |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
public func applicationDockMenu(_ sender: NSApplication) -> NSMenu? { |
||||||
|
let menu = NSMenu(title: "Quick Access") |
||||||
|
|
||||||
|
Logger.log(object: ["Dock Menu Triggered": "YES"], for: "Dock Menu Triggered") |
||||||
|
|
||||||
|
let toggleMenuItem = NSMenuItem(title: "Toggle Panel", action: #selector(AppDelegate.togglePanel(_:)), keyEquivalent: "") |
||||||
|
let openPreferences = NSMenuItem(title: "Preferences", action: #selector(AppDelegate.openPreferencesWindow), keyEquivalent: ",") |
||||||
|
|
||||||
|
[toggleMenuItem, openPreferences].forEach { |
||||||
|
$0.isEnabled = true |
||||||
|
menu.addItem($0) |
||||||
|
} |
||||||
|
|
||||||
|
return menu |
||||||
|
} |
||||||
|
|
||||||
|
@objc private func openPreferencesWindow() { |
||||||
|
let displayMode = UserDefaults.standard.integer(forKey: CLShowAppInForeground) |
||||||
|
|
||||||
|
if displayMode == 1 { |
||||||
|
let floatingWindow = FloatingWindowController.shared() |
||||||
|
floatingWindow.openPreferences(NSButton()) |
||||||
|
} else { |
||||||
|
let panelController = PanelController.shared() |
||||||
|
panelController.openPreferences(NSButton()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private lazy var controller: OnboardingController? = { |
||||||
|
let s = NSStoryboard(name: NSStoryboard.Name("Onboarding"), bundle: nil) |
||||||
|
return s.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("onboardingFlow")) as? OnboardingController |
||||||
|
}() |
||||||
|
|
||||||
|
private func showOnboardingFlow() { |
||||||
|
let shouldLaunchOnboarding = (DataStore.shared().retrieve(key: CLShowOnboardingFlow) == nil && DataStore.shared().timezones().isEmpty) || (ProcessInfo.processInfo.arguments.contains(CLOnboaringTestsLaunchArgument)) |
||||||
|
|
||||||
|
shouldLaunchOnboarding ? controller?.launch() : continueUsually() |
||||||
|
} |
||||||
|
|
||||||
|
func continueUsually() { |
||||||
|
// Check if another instance of the app is already running. If so, then stop this one. |
||||||
|
checkIfAppIsAlreadyOpen() |
||||||
|
|
||||||
|
// Make sure the old models are not used anymore |
||||||
|
TimezoneData.convert() |
||||||
|
|
||||||
|
// Install the menubar item! |
||||||
|
statusBarHandler = StatusItemHandler() |
||||||
|
|
||||||
|
if UserDefaults.standard.object(forKey: CLInstallHomeIndicatorObject) == nil { |
||||||
|
fetchLocalTimezone() |
||||||
|
UserDefaults.standard.set(1, forKey: CLInstallHomeIndicatorObject) |
||||||
|
} |
||||||
|
|
||||||
|
if ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument) { |
||||||
|
RateController.setPreviewMode(true) |
||||||
|
} |
||||||
|
|
||||||
|
UserDefaults.standard.register(defaults: ["NSApplicationCrashOnExceptions": true]) |
||||||
|
|
||||||
|
assignShortcut() |
||||||
|
|
||||||
|
panelController.addObserver(self, |
||||||
|
forKeyPath: "hasActivePanel", |
||||||
|
options: [.new], |
||||||
|
context: &kContextActivePanel) |
||||||
|
|
||||||
|
let defaults = UserDefaults.standard |
||||||
|
|
||||||
|
setActivationPolicy() |
||||||
|
|
||||||
|
// Set the display mode default as panel! |
||||||
|
if let displayMode = defaults.object(forKey: CLShowAppInForeground) as? NSNumber, displayMode.intValue == 1 { |
||||||
|
showFloatingWindow() |
||||||
|
} else if let displayMode = defaults.object(forKey: CLShowAppInForeground) as? Int, displayMode == 1 { |
||||||
|
showFloatingWindow() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Should we have a dock icon or just stay in the menubar? |
||||||
|
private func setActivationPolicy() { |
||||||
|
let defaults = UserDefaults.standard |
||||||
|
|
||||||
|
let activationPolicy: NSApplication.ActivationPolicy = defaults.integer(forKey: CLAppDislayOptions) == 0 ? .accessory : .regular |
||||||
|
NSApp.setActivationPolicy(activationPolicy) |
||||||
|
} |
||||||
|
|
||||||
|
private func checkIfAppIsAlreadyOpen() { |
||||||
|
guard let bundleID = Bundle.main.bundleIdentifier else { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
let apps = NSRunningApplication.runningApplications(withBundleIdentifier: bundleID) |
||||||
|
|
||||||
|
if apps.count > 1 { |
||||||
|
let currentApplication = NSRunningApplication.current |
||||||
|
for app in apps where app != currentApplication { |
||||||
|
app.terminate() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private func showAppAlreadyOpenMessage() { |
||||||
|
showAlert(message: "An instance of Clocker is already open 😅", |
||||||
|
informativeText: "This instance of Clocker will terminate now.", |
||||||
|
buttonTitle: "Close") |
||||||
|
} |
||||||
|
|
||||||
|
private func showAlert(message: String, informativeText: String, buttonTitle: String) { |
||||||
|
NSApplication.shared.activate(ignoringOtherApps: true) |
||||||
|
let alert = NSAlert() |
||||||
|
alert.messageText = message |
||||||
|
alert.informativeText = informativeText |
||||||
|
alert.addButton(withTitle: buttonTitle) |
||||||
|
alert.runModal() |
||||||
|
} |
||||||
|
|
||||||
|
private func fetchLocalTimezone() { |
||||||
|
let identifier = TimeZone.autoupdatingCurrent.identifier |
||||||
|
|
||||||
|
let currentTimezone = TimezoneData() |
||||||
|
currentTimezone.timezoneID = identifier |
||||||
|
currentTimezone.setLabel(identifier) |
||||||
|
currentTimezone.formattedAddress = identifier |
||||||
|
currentTimezone.isSystemTimezone = true |
||||||
|
currentTimezone.placeID = "Home" |
||||||
|
|
||||||
|
let operations = TimezoneDataOperations(with: currentTimezone) |
||||||
|
operations.saveObject(at: 0) |
||||||
|
|
||||||
|
// Retrieve Location |
||||||
|
// retrieveLatestLocation() |
||||||
|
} |
||||||
|
|
||||||
|
@IBAction func ping(_ sender: Any) { |
||||||
|
togglePanel(sender) |
||||||
|
} |
||||||
|
|
||||||
|
private func retrieveLatestLocation() { |
||||||
|
let locationController = LocationController.sharedController() |
||||||
|
locationController.determineAndRequestLocationAuthorization() |
||||||
|
} |
||||||
|
|
||||||
|
private func showFloatingWindow() { |
||||||
|
// Display the Floating Window! |
||||||
|
floatingWindow.showWindow(nil) |
||||||
|
floatingWindow.updateTableContent() |
||||||
|
floatingWindow.startWindowTimer() |
||||||
|
|
||||||
|
NSApp.activate(ignoringOtherApps: true) |
||||||
|
} |
||||||
|
|
||||||
|
private func assignShortcut() { |
||||||
|
NSUserDefaultsController.shared.addObserver(self, |
||||||
|
forKeyPath: "values.globalPing", |
||||||
|
options: [.initial, .new], |
||||||
|
context: nil) |
||||||
|
} |
||||||
|
|
||||||
|
private func checkIfRunFromApplicationsFolder() { |
||||||
|
|
||||||
|
if let shortCircuit = UserDefaults.standard.object(forKey: "AllowOutsideApplicationsFolder") as? Bool, shortCircuit == true { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
let bundlePath = Bundle.main.bundlePath |
||||||
|
let applicationDirectory = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationDirectory, |
||||||
|
FileManager.SearchPathDomainMask.localDomainMask, |
||||||
|
true) |
||||||
|
for appDir in applicationDirectory { |
||||||
|
if bundlePath.hasPrefix(appDir) { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Clocker is installed out of Applications directory |
||||||
|
// This breaks start at login! Time to show an alert and terminate |
||||||
|
showAlert(message: "Move Clocker to the Applications folder", |
||||||
|
informativeText: "Clocker must be run from the Applications folder in order to work properly.\n\nPlease quit Clocker, move it to the Applications folder, and relaunch. Current folder: \(applicationDirectory)", |
||||||
|
buttonTitle: "Quit") |
||||||
|
|
||||||
|
// Terminate |
||||||
|
NSApp.terminate(nil) |
||||||
|
} |
||||||
|
|
||||||
|
@IBAction open func togglePanel(_ sender: Any) { |
||||||
|
|
||||||
|
let displayMode = UserDefaults.standard.integer(forKey: CLShowAppInForeground) |
||||||
|
|
||||||
|
if displayMode == 1 { |
||||||
|
floatingWindow.showWindow(nil) |
||||||
|
floatingWindow.updateTableContent() |
||||||
|
floatingWindow.startWindowTimer() |
||||||
|
} else { |
||||||
|
panelController.showWindow(nil) |
||||||
|
panelController.setActivePanel(newValue: !panelController.hasActivePanelGetter()) |
||||||
|
} |
||||||
|
|
||||||
|
NSApp.activate(ignoringOtherApps: true) |
||||||
|
} |
||||||
|
|
||||||
|
open func setupFloatingWindow() { |
||||||
|
showFloatingWindow() |
||||||
|
} |
||||||
|
|
||||||
|
open func closeFloatingWindow() { |
||||||
|
floatingWindow.window?.close() |
||||||
|
} |
||||||
|
|
||||||
|
func statusItemForPanel() -> StatusItemHandler { |
||||||
|
return statusBarHandler |
||||||
|
} |
||||||
|
|
||||||
|
open func setPanelDefaults() { |
||||||
|
panelController.updateDefaultPreferences() |
||||||
|
} |
||||||
|
|
||||||
|
open func setupMenubarTimer() { |
||||||
|
statusBarHandler.setupStatusItem() |
||||||
|
} |
||||||
|
|
||||||
|
open func invalidateMenubarTimer(_ showIcon: Bool) { |
||||||
|
statusBarHandler.invalidateTimer(showIcon: showIcon, isSyncing: true) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
//
|
||||||
|
// Clocker-Bridging-Header.h
|
||||||
|
// Clocker
|
||||||
|
//
|
||||||
|
// Created by Banthia, Abhishek on 12/22/17.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "CLTimezoneData.h" |
||||||
|
#import "iVersion.h" |
||||||
|
#import <ShortcutRecorder/ShortcutRecorder.h> |
||||||
|
#import <PTHotKey/PTHotKeyCenter.h> |
||||||
|
#import <PTHotKey/PTHotKey+ShortcutRecorder.h> |
||||||
|
#import <Fabric/Fabric.h> |
||||||
|
#import <Crashlytics/Crashlytics.h> |
@ -0,0 +1,7 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<Workspace |
||||||
|
version = "1.0"> |
||||||
|
<FileRef |
||||||
|
location = "self:/Users/abhishekbanthia/Downloads/Popup-master/Clocker.xcodeproj"> |
||||||
|
</FileRef> |
||||||
|
</Workspace> |
@ -0,0 +1,30 @@ |
|||||||
|
{ |
||||||
|
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "79BC31FA35C73FAE9D63749994DC7D1D9E35A66B", |
||||||
|
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { |
||||||
|
|
||||||
|
}, |
||||||
|
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { |
||||||
|
"79BC31FA35C73FAE9D63749994DC7D1D9E35A66B" : 0, |
||||||
|
"F2FE0AAE95F0B87896F2BEE0B176D4FC32D691A3" : 0 |
||||||
|
}, |
||||||
|
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "FE3C46F0-59C9-4F38-8281-63F46BD16224", |
||||||
|
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { |
||||||
|
"79BC31FA35C73FAE9D63749994DC7D1D9E35A66B" : "Clocker\/", |
||||||
|
"F2FE0AAE95F0B87896F2BEE0B176D4FC32D691A3" : "Clocker\/Clocker\/pop\/" |
||||||
|
}, |
||||||
|
"DVTSourceControlWorkspaceBlueprintNameKey" : "Clocker", |
||||||
|
"DVTSourceControlWorkspaceBlueprintVersion" : 204, |
||||||
|
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Clocker.xcodeproj", |
||||||
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ |
||||||
|
{ |
||||||
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Abhishaker17\/Clocker.git", |
||||||
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", |
||||||
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "79BC31FA35C73FAE9D63749994DC7D1D9E35A66B" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/facebook\/pop.git", |
||||||
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", |
||||||
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "F2FE0AAE95F0B87896F2BEE0B176D4FC32D691A3" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<dict> |
||||||
|
<key>IDEDidComputeMac32BitWarning</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
</plist> |
@ -0,0 +1,8 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<dict> |
||||||
|
<key>BuildSystemType</key> |
||||||
|
<string>Latest</string> |
||||||
|
</dict> |
||||||
|
</plist> |
@ -0,0 +1,18 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<dict> |
||||||
|
<key>BuildLocationStyle</key> |
||||||
|
<string>UseAppPreferences</string> |
||||||
|
<key>CustomBuildLocationType</key> |
||||||
|
<string>RelativeToDerivedData</string> |
||||||
|
<key>DerivedDataLocationStyle</key> |
||||||
|
<string>Default</string> |
||||||
|
<key>EnabledFullIndexStoreVisibility</key> |
||||||
|
<false/> |
||||||
|
<key>IssueFilterStyle</key> |
||||||
|
<string>ShowActiveSchemeOnly</string> |
||||||
|
<key>LiveSourceIssuesEnabled</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
</plist> |
@ -0,0 +1,5 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<array/> |
||||||
|
</plist> |
@ -0,0 +1,5 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<array/> |
||||||
|
</plist> |
@ -0,0 +1,8 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<dict> |
||||||
|
<key>FILEHEADER</key> |
||||||
|
<string> Copyright © 2015 Abhishek Banthia</string> |
||||||
|
</dict> |
||||||
|
</plist> |
@ -0,0 +1,135 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<Scheme |
||||||
|
LastUpgradeVersion = "1020" |
||||||
|
version = "1.7"> |
||||||
|
<BuildAction |
||||||
|
parallelizeBuildables = "YES" |
||||||
|
buildImplicitDependencies = "YES"> |
||||||
|
<BuildActionEntries> |
||||||
|
<BuildActionEntry |
||||||
|
buildForTesting = "YES" |
||||||
|
buildForRunning = "YES" |
||||||
|
buildForProfiling = "YES" |
||||||
|
buildForArchiving = "YES" |
||||||
|
buildForAnalyzing = "YES"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "DD4F7C0313C30F9F00825C6E" |
||||||
|
BuildableName = "Clocker.app" |
||||||
|
BlueprintName = "Clocker" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildActionEntry> |
||||||
|
</BuildActionEntries> |
||||||
|
</BuildAction> |
||||||
|
<TestAction |
||||||
|
buildConfiguration = "Debug" |
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||||
|
codeCoverageEnabled = "YES" |
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"> |
||||||
|
<Testables> |
||||||
|
<TestableReference |
||||||
|
skipped = "YES"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "9AC5EE2C1EDA17E400B4CE7B" |
||||||
|
BuildableName = "ClockerTests.xctest" |
||||||
|
BlueprintName = "ClockerTests" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</TestableReference> |
||||||
|
<TestableReference |
||||||
|
skipped = "NO"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "C2BFE3E22049F82300825BE5" |
||||||
|
BuildableName = "ClockerUITests.xctest" |
||||||
|
BlueprintName = "ClockerUITests" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</TestableReference> |
||||||
|
<TestableReference |
||||||
|
skipped = "NO"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "C20839C621515C1E00C86589" |
||||||
|
BuildableName = "ClockerUnitTests.xctest" |
||||||
|
BlueprintName = "ClockerUnitTests" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</TestableReference> |
||||||
|
</Testables> |
||||||
|
<MacroExpansion> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "DD4F7C0313C30F9F00825C6E" |
||||||
|
BuildableName = "Clocker.app" |
||||||
|
BlueprintName = "Clocker" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</MacroExpansion> |
||||||
|
<AdditionalOptions> |
||||||
|
</AdditionalOptions> |
||||||
|
</TestAction> |
||||||
|
<LaunchAction |
||||||
|
buildConfiguration = "Debug" |
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||||
|
region = "PH" |
||||||
|
launchStyle = "0" |
||||||
|
useCustomWorkingDirectory = "NO" |
||||||
|
ignoresPersistentStateOnLaunch = "NO" |
||||||
|
debugDocumentVersioning = "YES" |
||||||
|
debugServiceExtension = "internal" |
||||||
|
allowLocationSimulation = "YES" |
||||||
|
showNonLocalizedStrings = "YES"> |
||||||
|
<BuildableProductRunnable |
||||||
|
runnableDebuggingMode = "0"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "DD4F7C0313C30F9F00825C6E" |
||||||
|
BuildableName = "Clocker.app" |
||||||
|
BlueprintName = "Clocker" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildableProductRunnable> |
||||||
|
<EnvironmentVariables> |
||||||
|
<EnvironmentVariable |
||||||
|
key = "OS_ACTIVITY_MODE" |
||||||
|
value = "disable" |
||||||
|
isEnabled = "NO"> |
||||||
|
</EnvironmentVariable> |
||||||
|
</EnvironmentVariables> |
||||||
|
<AdditionalOptions> |
||||||
|
</AdditionalOptions> |
||||||
|
<LocationScenarioReference |
||||||
|
identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier" |
||||||
|
referenceType = "1"> |
||||||
|
</LocationScenarioReference> |
||||||
|
</LaunchAction> |
||||||
|
<ProfileAction |
||||||
|
buildConfiguration = "Release" |
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES" |
||||||
|
savedToolIdentifier = "" |
||||||
|
useCustomWorkingDirectory = "NO" |
||||||
|
debugDocumentVersioning = "YES"> |
||||||
|
<BuildableProductRunnable |
||||||
|
runnableDebuggingMode = "0"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "DD4F7C0313C30F9F00825C6E" |
||||||
|
BuildableName = "Clocker.app" |
||||||
|
BlueprintName = "Clocker" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildableProductRunnable> |
||||||
|
</ProfileAction> |
||||||
|
<AnalyzeAction |
||||||
|
buildConfiguration = "Debug"> |
||||||
|
</AnalyzeAction> |
||||||
|
<ArchiveAction |
||||||
|
buildConfiguration = "Release" |
||||||
|
revealArchiveInOrganizer = "YES"> |
||||||
|
</ArchiveAction> |
||||||
|
</Scheme> |
@ -0,0 +1,101 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<Scheme |
||||||
|
LastUpgradeVersion = "1020" |
||||||
|
version = "1.3"> |
||||||
|
<BuildAction |
||||||
|
parallelizeBuildables = "YES" |
||||||
|
buildImplicitDependencies = "YES"> |
||||||
|
<BuildActionEntries> |
||||||
|
<BuildActionEntry |
||||||
|
buildForTesting = "YES" |
||||||
|
buildForRunning = "YES" |
||||||
|
buildForProfiling = "YES" |
||||||
|
buildForArchiving = "YES" |
||||||
|
buildForAnalyzing = "YES"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "DD4F7C0313C30F9F00825C6E" |
||||||
|
BuildableName = "Clocker.app" |
||||||
|
BlueprintName = "Clocker" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildActionEntry> |
||||||
|
</BuildActionEntries> |
||||||
|
</BuildAction> |
||||||
|
<TestAction |
||||||
|
buildConfiguration = "Debug" |
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"> |
||||||
|
<Testables> |
||||||
|
<TestableReference |
||||||
|
skipped = "NO"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "C20839C621515C1E00C86589" |
||||||
|
BuildableName = "ClockerUnitTests.xctest" |
||||||
|
BlueprintName = "ClockerUnitTests" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</TestableReference> |
||||||
|
</Testables> |
||||||
|
<MacroExpansion> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "DD4F7C0313C30F9F00825C6E" |
||||||
|
BuildableName = "Clocker.app" |
||||||
|
BlueprintName = "Clocker" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</MacroExpansion> |
||||||
|
<AdditionalOptions> |
||||||
|
</AdditionalOptions> |
||||||
|
</TestAction> |
||||||
|
<LaunchAction |
||||||
|
buildConfiguration = "Debug" |
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||||
|
launchStyle = "0" |
||||||
|
useCustomWorkingDirectory = "NO" |
||||||
|
ignoresPersistentStateOnLaunch = "NO" |
||||||
|
debugDocumentVersioning = "YES" |
||||||
|
debugServiceExtension = "internal" |
||||||
|
allowLocationSimulation = "YES"> |
||||||
|
<BuildableProductRunnable |
||||||
|
runnableDebuggingMode = "0"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "DD4F7C0313C30F9F00825C6E" |
||||||
|
BuildableName = "Clocker.app" |
||||||
|
BlueprintName = "Clocker" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildableProductRunnable> |
||||||
|
<AdditionalOptions> |
||||||
|
</AdditionalOptions> |
||||||
|
</LaunchAction> |
||||||
|
<ProfileAction |
||||||
|
buildConfiguration = "Release" |
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES" |
||||||
|
savedToolIdentifier = "" |
||||||
|
useCustomWorkingDirectory = "NO" |
||||||
|
debugDocumentVersioning = "YES"> |
||||||
|
<BuildableProductRunnable |
||||||
|
runnableDebuggingMode = "0"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "DD4F7C0313C30F9F00825C6E" |
||||||
|
BuildableName = "Clocker.app" |
||||||
|
BlueprintName = "Clocker" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildableProductRunnable> |
||||||
|
</ProfileAction> |
||||||
|
<AnalyzeAction |
||||||
|
buildConfiguration = "Debug"> |
||||||
|
</AnalyzeAction> |
||||||
|
<ArchiveAction |
||||||
|
buildConfiguration = "Release" |
||||||
|
revealArchiveInOrganizer = "YES"> |
||||||
|
</ArchiveAction> |
||||||
|
</Scheme> |
@ -0,0 +1,152 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<Scheme |
||||||
|
LastUpgradeVersion = "0930" |
||||||
|
version = "1.3"> |
||||||
|
<BuildAction |
||||||
|
parallelizeBuildables = "YES" |
||||||
|
buildImplicitDependencies = "YES"> |
||||||
|
<BuildActionEntries> |
||||||
|
<BuildActionEntry |
||||||
|
buildForTesting = "YES" |
||||||
|
buildForRunning = "YES" |
||||||
|
buildForProfiling = "YES" |
||||||
|
buildForArchiving = "YES" |
||||||
|
buildForAnalyzing = "YES"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "DD4F7C0313C30F9F00825C6E" |
||||||
|
BuildableName = "Clocker.app" |
||||||
|
BlueprintName = "Clocker" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildActionEntry> |
||||||
|
</BuildActionEntries> |
||||||
|
</BuildAction> |
||||||
|
<TestAction |
||||||
|
buildConfiguration = "Debug" |
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||||
|
codeCoverageEnabled = "YES" |
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"> |
||||||
|
<Testables> |
||||||
|
<TestableReference |
||||||
|
skipped = "NO"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "9AC5EE2C1EDA17E400B4CE7B" |
||||||
|
BuildableName = "ClockerTests.xctest" |
||||||
|
BlueprintName = "ClockerTests" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</TestableReference> |
||||||
|
<TestableReference |
||||||
|
skipped = "NO"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "C2BFE3E22049F82300825BE5" |
||||||
|
BuildableName = "ClockerUITests.xctest" |
||||||
|
BlueprintName = "ClockerUITests" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</TestableReference> |
||||||
|
<TestableReference |
||||||
|
skipped = "NO"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "C25C2C7B20D18D6400BC9C81" |
||||||
|
BuildableName = "ClockerUnitTests.xctest" |
||||||
|
BlueprintName = "ClockerUnitTests" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</TestableReference> |
||||||
|
</Testables> |
||||||
|
<MacroExpansion> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "DD4F7C0313C30F9F00825C6E" |
||||||
|
BuildableName = "Clocker.app" |
||||||
|
BlueprintName = "Clocker" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</MacroExpansion> |
||||||
|
<AdditionalOptions> |
||||||
|
</AdditionalOptions> |
||||||
|
</TestAction> |
||||||
|
<LaunchAction |
||||||
|
buildConfiguration = "Debug" |
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||||
|
launchStyle = "0" |
||||||
|
useCustomWorkingDirectory = "NO" |
||||||
|
ignoresPersistentStateOnLaunch = "NO" |
||||||
|
debugDocumentVersioning = "YES" |
||||||
|
debugServiceExtension = "internal" |
||||||
|
allowLocationSimulation = "YES"> |
||||||
|
<BuildableProductRunnable |
||||||
|
runnableDebuggingMode = "0"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "DD4F7C0313C30F9F00825C6E" |
||||||
|
BuildableName = "Clocker.app" |
||||||
|
BlueprintName = "Clocker" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildableProductRunnable> |
||||||
|
<AdditionalOptions> |
||||||
|
<AdditionalOption |
||||||
|
key = "MallocStackLogging" |
||||||
|
value = "" |
||||||
|
isEnabled = "YES"> |
||||||
|
</AdditionalOption> |
||||||
|
<AdditionalOption |
||||||
|
key = "DYLD_INSERT_LIBRARIES" |
||||||
|
value = "/usr/lib/libgmalloc.dylib" |
||||||
|
isEnabled = "YES"> |
||||||
|
</AdditionalOption> |
||||||
|
<AdditionalOption |
||||||
|
key = "PrefersMallocStackLoggingLite" |
||||||
|
value = "" |
||||||
|
isEnabled = "YES"> |
||||||
|
</AdditionalOption> |
||||||
|
<AdditionalOption |
||||||
|
key = "NSZombieEnabled" |
||||||
|
value = "YES" |
||||||
|
isEnabled = "YES"> |
||||||
|
</AdditionalOption> |
||||||
|
<AdditionalOption |
||||||
|
key = "MallocGuardEdges" |
||||||
|
value = "" |
||||||
|
isEnabled = "YES"> |
||||||
|
</AdditionalOption> |
||||||
|
<AdditionalOption |
||||||
|
key = "MallocScribble" |
||||||
|
value = "" |
||||||
|
isEnabled = "YES"> |
||||||
|
</AdditionalOption> |
||||||
|
</AdditionalOptions> |
||||||
|
</LaunchAction> |
||||||
|
<ProfileAction |
||||||
|
buildConfiguration = "Release" |
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES" |
||||||
|
savedToolIdentifier = "" |
||||||
|
useCustomWorkingDirectory = "NO" |
||||||
|
debugDocumentVersioning = "YES"> |
||||||
|
<BuildableProductRunnable |
||||||
|
runnableDebuggingMode = "0"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "DD4F7C0313C30F9F00825C6E" |
||||||
|
BuildableName = "Clocker.app" |
||||||
|
BlueprintName = "Clocker" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildableProductRunnable> |
||||||
|
</ProfileAction> |
||||||
|
<AnalyzeAction |
||||||
|
buildConfiguration = "Debug"> |
||||||
|
</AnalyzeAction> |
||||||
|
<ArchiveAction |
||||||
|
buildConfiguration = "Release" |
||||||
|
revealArchiveInOrganizer = "YES"> |
||||||
|
</ArchiveAction> |
||||||
|
</Scheme> |
@ -0,0 +1,91 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<Scheme |
||||||
|
LastUpgradeVersion = "0930" |
||||||
|
version = "1.3"> |
||||||
|
<BuildAction |
||||||
|
parallelizeBuildables = "YES" |
||||||
|
buildImplicitDependencies = "YES"> |
||||||
|
<BuildActionEntries> |
||||||
|
<BuildActionEntry |
||||||
|
buildForTesting = "YES" |
||||||
|
buildForRunning = "YES" |
||||||
|
buildForProfiling = "YES" |
||||||
|
buildForArchiving = "YES" |
||||||
|
buildForAnalyzing = "YES"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "9A7547CF1F184DC3004705EF" |
||||||
|
BuildableName = "ClockerHelper.app" |
||||||
|
BlueprintName = "ClockerHelper" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildActionEntry> |
||||||
|
</BuildActionEntries> |
||||||
|
</BuildAction> |
||||||
|
<TestAction |
||||||
|
buildConfiguration = "Debug" |
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"> |
||||||
|
<Testables> |
||||||
|
</Testables> |
||||||
|
<MacroExpansion> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "9A7547CF1F184DC3004705EF" |
||||||
|
BuildableName = "ClockerHelper.app" |
||||||
|
BlueprintName = "ClockerHelper" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</MacroExpansion> |
||||||
|
<AdditionalOptions> |
||||||
|
</AdditionalOptions> |
||||||
|
</TestAction> |
||||||
|
<LaunchAction |
||||||
|
buildConfiguration = "Debug" |
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||||
|
launchStyle = "0" |
||||||
|
useCustomWorkingDirectory = "NO" |
||||||
|
ignoresPersistentStateOnLaunch = "NO" |
||||||
|
debugDocumentVersioning = "YES" |
||||||
|
debugServiceExtension = "internal" |
||||||
|
allowLocationSimulation = "YES"> |
||||||
|
<BuildableProductRunnable |
||||||
|
runnableDebuggingMode = "0"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "9A7547CF1F184DC3004705EF" |
||||||
|
BuildableName = "ClockerHelper.app" |
||||||
|
BlueprintName = "ClockerHelper" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildableProductRunnable> |
||||||
|
<AdditionalOptions> |
||||||
|
</AdditionalOptions> |
||||||
|
</LaunchAction> |
||||||
|
<ProfileAction |
||||||
|
buildConfiguration = "Release" |
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES" |
||||||
|
savedToolIdentifier = "" |
||||||
|
useCustomWorkingDirectory = "NO" |
||||||
|
debugDocumentVersioning = "YES"> |
||||||
|
<BuildableProductRunnable |
||||||
|
runnableDebuggingMode = "0"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "9A7547CF1F184DC3004705EF" |
||||||
|
BuildableName = "ClockerHelper.app" |
||||||
|
BlueprintName = "ClockerHelper" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildableProductRunnable> |
||||||
|
</ProfileAction> |
||||||
|
<AnalyzeAction |
||||||
|
buildConfiguration = "Debug"> |
||||||
|
</AnalyzeAction> |
||||||
|
<ArchiveAction |
||||||
|
buildConfiguration = "Release" |
||||||
|
revealArchiveInOrganizer = "YES"> |
||||||
|
</ArchiveAction> |
||||||
|
</Scheme> |
@ -0,0 +1,47 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<dict> |
||||||
|
<key>SchemeUserState</key> |
||||||
|
<dict> |
||||||
|
<key>Clocker.xcscheme</key> |
||||||
|
<dict> |
||||||
|
<key>orderHint</key> |
||||||
|
<integer>0</integer> |
||||||
|
</dict> |
||||||
|
<key>Clocker.xcscheme_^#shared#^_</key> |
||||||
|
<dict> |
||||||
|
<key>orderHint</key> |
||||||
|
<integer>4</integer> |
||||||
|
</dict> |
||||||
|
<key>ClockerHelper.xcscheme</key> |
||||||
|
<dict> |
||||||
|
<key>orderHint</key> |
||||||
|
<integer>2</integer> |
||||||
|
</dict> |
||||||
|
<key>ClockerUnitTest.xcscheme</key> |
||||||
|
<dict> |
||||||
|
<key>orderHint</key> |
||||||
|
<integer>6</integer> |
||||||
|
</dict> |
||||||
|
</dict> |
||||||
|
<key>SuppressBuildableAutocreation</key> |
||||||
|
<dict> |
||||||
|
<key>9A7547CF1F184DC3004705EF</key> |
||||||
|
<dict> |
||||||
|
<key>primary</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
<key>9AC5EE2C1EDA17E400B4CE7B</key> |
||||||
|
<dict> |
||||||
|
<key>primary</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
<key>DD4F7C0313C30F9F00825C6E</key> |
||||||
|
<dict> |
||||||
|
<key>primary</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
</dict> |
||||||
|
</dict> |
||||||
|
</plist> |
@ -0,0 +1,17 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<Bucket |
||||||
|
type = "1" |
||||||
|
version = "2.0"> |
||||||
|
<Breakpoints> |
||||||
|
<BreakpointProxy |
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.ExceptionBreakpoint"> |
||||||
|
<BreakpointContent |
||||||
|
shouldBeEnabled = "Yes" |
||||||
|
ignoreCount = "0" |
||||||
|
continueAfterRunningActions = "No" |
||||||
|
scope = "1" |
||||||
|
stopOnStyle = "0"> |
||||||
|
</BreakpointContent> |
||||||
|
</BreakpointProxy> |
||||||
|
</Breakpoints> |
||||||
|
</Bucket> |
@ -0,0 +1,101 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<Scheme |
||||||
|
LastUpgradeVersion = "1020" |
||||||
|
version = "1.3"> |
||||||
|
<BuildAction |
||||||
|
parallelizeBuildables = "YES" |
||||||
|
buildImplicitDependencies = "YES"> |
||||||
|
<BuildActionEntries> |
||||||
|
<BuildActionEntry |
||||||
|
buildForTesting = "YES" |
||||||
|
buildForRunning = "YES" |
||||||
|
buildForProfiling = "YES" |
||||||
|
buildForArchiving = "YES" |
||||||
|
buildForAnalyzing = "YES"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "9A7547CF1F184DC3004705EF" |
||||||
|
BuildableName = "ClockerHelper.app" |
||||||
|
BlueprintName = "ClockerHelper" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildActionEntry> |
||||||
|
</BuildActionEntries> |
||||||
|
</BuildAction> |
||||||
|
<TestAction |
||||||
|
buildConfiguration = "Debug" |
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"> |
||||||
|
<Testables> |
||||||
|
<TestableReference |
||||||
|
skipped = "NO"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "9A7547AA1F1834D9004705EF" |
||||||
|
BuildableName = "ClockerHelperTests.xctest" |
||||||
|
BlueprintName = "ClockerHelperTests" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</TestableReference> |
||||||
|
</Testables> |
||||||
|
<MacroExpansion> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "9A7547CF1F184DC3004705EF" |
||||||
|
BuildableName = "ClockerHelper.app" |
||||||
|
BlueprintName = "ClockerHelper" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</MacroExpansion> |
||||||
|
<AdditionalOptions> |
||||||
|
</AdditionalOptions> |
||||||
|
</TestAction> |
||||||
|
<LaunchAction |
||||||
|
buildConfiguration = "Debug" |
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||||
|
launchStyle = "0" |
||||||
|
useCustomWorkingDirectory = "NO" |
||||||
|
ignoresPersistentStateOnLaunch = "NO" |
||||||
|
debugDocumentVersioning = "YES" |
||||||
|
debugServiceExtension = "internal" |
||||||
|
allowLocationSimulation = "YES"> |
||||||
|
<BuildableProductRunnable |
||||||
|
runnableDebuggingMode = "0"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "9A7547CF1F184DC3004705EF" |
||||||
|
BuildableName = "ClockerHelper.app" |
||||||
|
BlueprintName = "ClockerHelper" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildableProductRunnable> |
||||||
|
<AdditionalOptions> |
||||||
|
</AdditionalOptions> |
||||||
|
</LaunchAction> |
||||||
|
<ProfileAction |
||||||
|
buildConfiguration = "Release" |
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES" |
||||||
|
savedToolIdentifier = "" |
||||||
|
useCustomWorkingDirectory = "NO" |
||||||
|
debugDocumentVersioning = "YES"> |
||||||
|
<BuildableProductRunnable |
||||||
|
runnableDebuggingMode = "0"> |
||||||
|
<BuildableReference |
||||||
|
BuildableIdentifier = "primary" |
||||||
|
BlueprintIdentifier = "9A7547CF1F184DC3004705EF" |
||||||
|
BuildableName = "ClockerHelper.app" |
||||||
|
BlueprintName = "ClockerHelper" |
||||||
|
ReferencedContainer = "container:Clocker.xcodeproj"> |
||||||
|
</BuildableReference> |
||||||
|
</BuildableProductRunnable> |
||||||
|
</ProfileAction> |
||||||
|
<AnalyzeAction |
||||||
|
buildConfiguration = "Debug"> |
||||||
|
</AnalyzeAction> |
||||||
|
<ArchiveAction |
||||||
|
buildConfiguration = "Release" |
||||||
|
revealArchiveInOrganizer = "YES"> |
||||||
|
</ArchiveAction> |
||||||
|
</Scheme> |
@ -0,0 +1,47 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<dict> |
||||||
|
<key>SchemeUserState</key> |
||||||
|
<dict> |
||||||
|
<key>Clocker.xcscheme_^#shared#^_</key> |
||||||
|
<dict> |
||||||
|
<key>orderHint</key> |
||||||
|
<integer>0</integer> |
||||||
|
</dict> |
||||||
|
<key>ClockerHelper.xcscheme</key> |
||||||
|
<dict> |
||||||
|
<key>orderHint</key> |
||||||
|
<integer>3</integer> |
||||||
|
</dict> |
||||||
|
</dict> |
||||||
|
<key>SuppressBuildableAutocreation</key> |
||||||
|
<dict> |
||||||
|
<key>9A7547951F1834D9004705EF</key> |
||||||
|
<dict> |
||||||
|
<key>primary</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
<key>9A7547AA1F1834D9004705EF</key> |
||||||
|
<dict> |
||||||
|
<key>primary</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
<key>9A7547CF1F184DC3004705EF</key> |
||||||
|
<dict> |
||||||
|
<key>primary</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
<key>9AC5EE2C1EDA17E400B4CE7B</key> |
||||||
|
<dict> |
||||||
|
<key>primary</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
<key>DD4F7C0313C30F9F00825C6E</key> |
||||||
|
<dict> |
||||||
|
<key>primary</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
</dict> |
||||||
|
</dict> |
||||||
|
</plist> |
@ -0,0 +1,32 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<dict> |
||||||
|
<key>SchemeUserState</key> |
||||||
|
<dict> |
||||||
|
<key>Clocker.xcscheme_^#shared#^_</key> |
||||||
|
<dict> |
||||||
|
<key>orderHint</key> |
||||||
|
<integer>0</integer> |
||||||
|
</dict> |
||||||
|
<key>ClockerHelper.xcscheme_^#shared#^_</key> |
||||||
|
<dict> |
||||||
|
<key>orderHint</key> |
||||||
|
<integer>4</integer> |
||||||
|
</dict> |
||||||
|
<key>Tests.xcscheme_^#shared#^_</key> |
||||||
|
<dict> |
||||||
|
<key>orderHint</key> |
||||||
|
<integer>3</integer> |
||||||
|
</dict> |
||||||
|
</dict> |
||||||
|
<key>SuppressBuildableAutocreation</key> |
||||||
|
<dict> |
||||||
|
<key>DD4F7C0313C30F9F00825C6E</key> |
||||||
|
<dict> |
||||||
|
<key>primary</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
</dict> |
||||||
|
</dict> |
||||||
|
</plist> |
@ -0,0 +1,65 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<dict> |
||||||
|
<key>CFBundleExecutable</key> |
||||||
|
<string>${EXECUTABLE_NAME}</string> |
||||||
|
<key>CFBundleIdentifier</key> |
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> |
||||||
|
<key>CFBundleInfoDictionaryVersion</key> |
||||||
|
<string>6.0</string> |
||||||
|
<key>CFBundleName</key> |
||||||
|
<string>${PRODUCT_NAME}</string> |
||||||
|
<key>CFBundlePackageType</key> |
||||||
|
<string>APPL</string> |
||||||
|
<key>CFBundleShortVersionString</key> |
||||||
|
<string>1.6.09</string> |
||||||
|
<key>CFBundleSignature</key> |
||||||
|
<string>????</string> |
||||||
|
<key>CFBundleVersion</key> |
||||||
|
<string>64</string> |
||||||
|
<key>Fabric</key> |
||||||
|
<dict> |
||||||
|
<key>APIKey</key> |
||||||
|
<string>94088f95c41979e8019b67d5795f52bbbe7104d4</string> |
||||||
|
<key>Kits</key> |
||||||
|
<array> |
||||||
|
<dict> |
||||||
|
<key>KitInfo</key> |
||||||
|
<dict/> |
||||||
|
<key>KitName</key> |
||||||
|
<string>Crashlytics</string> |
||||||
|
</dict> |
||||||
|
</array> |
||||||
|
</dict> |
||||||
|
<key>LSApplicationCategoryType</key> |
||||||
|
<string>public.app-category.utilities</string> |
||||||
|
<key>LSMinimumSystemVersion</key> |
||||||
|
<string>${MACOSX_DEPLOYMENT_TARGET}</string> |
||||||
|
<key>LSUIElement</key> |
||||||
|
<true/> |
||||||
|
<key>NSAppTransportSecurity</key> |
||||||
|
<dict> |
||||||
|
<key>NSAllowsArbitraryLoads</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
<key>NSCalendarsUsageDescription</key> |
||||||
|
<string>Clocker can be more useful when it can display upcoming events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy</string> |
||||||
|
<key>NSHumanReadableCopyright</key> |
||||||
|
<string>Copyright © 2016, Abhishek Banthia</string> |
||||||
|
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key> |
||||||
|
<string>Clocker can be more useful when it can use your location to determine your current timezone.</string> |
||||||
|
<key>NSLocationUsageDescription</key> |
||||||
|
<string>Clocker can be more useful when it can use your location to determine your current timezone.</string> |
||||||
|
<key>NSMainNibFile</key> |
||||||
|
<string>MainMenu</string> |
||||||
|
<key>NSPrincipalClass</key> |
||||||
|
<string>NSApplication</string> |
||||||
|
<key>NSRemindersUsageDescription</key> |
||||||
|
<string>Clocker can be more useful when it can set reminders for your selected timezone(s). You can change this setting in System Preferences › Security & Privacy › Privacy.</string> |
||||||
|
<key>NSSupportsAutomaticGraphicsSwitching</key> |
||||||
|
<true/> |
||||||
|
<key>RequestsOpenAccess</key> |
||||||
|
<string>YES</string> |
||||||
|
</dict> |
||||||
|
</plist> |
@ -0,0 +1,9 @@ |
|||||||
|
// |
||||||
|
// Prefix header for all source files of the 'Clocker' target in the 'Clocker' project |
||||||
|
// |
||||||
|
|
||||||
|
#ifdef __OBJC__ |
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h> |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,16 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<dict> |
||||||
|
<key>com.apple.security.app-sandbox</key> |
||||||
|
<true/> |
||||||
|
<key>com.apple.security.network.client</key> |
||||||
|
<true/> |
||||||
|
<key>com.apple.security.personal-information.calendars</key> |
||||||
|
<true/> |
||||||
|
<key>com.apple.security.temporary-exception.apple-events</key> |
||||||
|
<array> |
||||||
|
<string>com.apple.reminders</string> |
||||||
|
</array> |
||||||
|
</dict> |
||||||
|
</plist> |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 389 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 249 B |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 27 KiB |
@ -0,0 +1,119 @@ |
|||||||
|
// Copyright © 2015 Abhishek Banthia |
||||||
|
|
||||||
|
import Cocoa |
||||||
|
import CoreLocation |
||||||
|
|
||||||
|
class LocationController: NSObject { |
||||||
|
public static let sharedInstance = LocationController() |
||||||
|
|
||||||
|
private var locationManager: CLLocationManager = { |
||||||
|
let l = CLLocationManager() |
||||||
|
l.desiredAccuracy = kCLLocationAccuracyThreeKilometers |
||||||
|
return l |
||||||
|
}() |
||||||
|
|
||||||
|
@objc class func sharedController() -> LocationController { |
||||||
|
return sharedInstance |
||||||
|
} |
||||||
|
|
||||||
|
func authorizationStatus() -> CLAuthorizationStatus { |
||||||
|
return CLLocationManager.authorizationStatus() |
||||||
|
} |
||||||
|
|
||||||
|
@objc func locationAccessNotDetermined() -> Bool { |
||||||
|
return CLLocationManager.authorizationStatus() == .notDetermined |
||||||
|
} |
||||||
|
|
||||||
|
@objc func locationAccessGranted() -> Bool { |
||||||
|
return CLLocationManager.authorizationStatus() == .authorizedAlways || CLLocationManager.authorizationStatus() == .authorized |
||||||
|
} |
||||||
|
|
||||||
|
@objc func locationAccessDenied() -> Bool { |
||||||
|
return CLLocationManager.authorizationStatus() == .restricted || CLLocationManager.authorizationStatus() == .denied |
||||||
|
} |
||||||
|
|
||||||
|
@objc func setDelegate() { |
||||||
|
locationManager.delegate = self |
||||||
|
} |
||||||
|
|
||||||
|
@objc func determineAndRequestLocationAuthorization() { |
||||||
|
setDelegate() |
||||||
|
|
||||||
|
if CLLocationManager.locationServicesEnabled() { |
||||||
|
locationManager.startUpdatingLocation() |
||||||
|
} |
||||||
|
|
||||||
|
switch authorizationStatus() { |
||||||
|
case .authorizedAlways: |
||||||
|
locationManager.startUpdatingLocation() |
||||||
|
case .notDetermined: |
||||||
|
locationManager.startUpdatingLocation() |
||||||
|
case .denied, .restricted: |
||||||
|
locationManager.startUpdatingLocation() |
||||||
|
default: |
||||||
|
fatalError("Unexpected Authorization Status") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private func updateHomeObject(with customLabel: String, coordinates: CLLocationCoordinate2D?) { |
||||||
|
let timezones = DataStore.shared().timezones() |
||||||
|
|
||||||
|
var timezoneObjects: [TimezoneData] = [] |
||||||
|
|
||||||
|
for timezone in timezones { |
||||||
|
if let model = TimezoneData.customObject(from: timezone) { |
||||||
|
timezoneObjects.append(model) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for timezoneObject in timezoneObjects { |
||||||
|
if timezoneObject.isSystemTimezone == true { |
||||||
|
timezoneObject.setLabel(customLabel) |
||||||
|
if let latlong = coordinates { |
||||||
|
timezoneObject.longitude = latlong.longitude |
||||||
|
timezoneObject.latitude = latlong.latitude |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var datas: [Data] = [] |
||||||
|
|
||||||
|
for updatedObject in timezoneObjects { |
||||||
|
let dataObject = NSKeyedArchiver.archivedData(withRootObject: updatedObject) |
||||||
|
datas.append(dataObject) |
||||||
|
} |
||||||
|
|
||||||
|
DataStore.shared().setTimezones(datas) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
extension LocationController: CLLocationManagerDelegate { |
||||||
|
func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) { |
||||||
|
|
||||||
|
guard locations.count > 0, let coordinates = locations.first?.coordinate else { return } |
||||||
|
|
||||||
|
let reverseGeoCoder = CLGeocoder() |
||||||
|
|
||||||
|
reverseGeoCoder.reverseGeocodeLocation(locations[0]) { placemarks, _ in |
||||||
|
|
||||||
|
guard let customLabel = placemarks?.first?.locality else { return } |
||||||
|
|
||||||
|
self.updateHomeObject(with: customLabel, coordinates: coordinates) |
||||||
|
|
||||||
|
self.locationManager.stopUpdatingLocation() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func locationManager(_: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { |
||||||
|
if status == .denied || status == .restricted { |
||||||
|
updateHomeObject(with: TimeZone.autoupdatingCurrent.identifier, coordinates: nil) |
||||||
|
locationManager.stopUpdatingLocation() |
||||||
|
} else if status == .notDetermined || status == .authorized || status == .authorizedAlways { |
||||||
|
locationManager.startUpdatingLocation() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func locationManager(_: CLLocationManager, didFailWithError error: Error) { |
||||||
|
print(error) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,361 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> |
||||||
|
<dependencies> |
||||||
|
<deployment identifier="macosx"/> |
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14313.18"/> |
||||||
|
</dependencies> |
||||||
|
<objects> |
||||||
|
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> |
||||||
|
<connections> |
||||||
|
<outlet property="delegate" destination="494" id="495"/> |
||||||
|
</connections> |
||||||
|
</customObject> |
||||||
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> |
||||||
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/> |
||||||
|
<menu title="AMainMenu" systemMenu="main" id="29"> |
||||||
|
<items> |
||||||
|
<menuItem title="Clocker" id="56"> |
||||||
|
<menu key="submenu" title="Clocker" systemMenu="apple" id="57"> |
||||||
|
<items> |
||||||
|
<menuItem title="About Clocker" id="58"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
|
<connections> |
||||||
|
<action selector="orderFrontStandardAboutPanel:" target="-2" id="142"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem isSeparatorItem="YES" id="236"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" command="YES"/> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Preferences…" keyEquivalent="," id="129"/> |
||||||
|
<menuItem isSeparatorItem="YES" id="143"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" command="YES"/> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Services" id="131"> |
||||||
|
<menu key="submenu" title="Services" systemMenu="services" id="130"/> |
||||||
|
</menuItem> |
||||||
|
<menuItem isSeparatorItem="YES" id="144"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" command="YES"/> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Hide Clocker" keyEquivalent="h" id="134"> |
||||||
|
<connections> |
||||||
|
<action selector="hide:" target="-1" id="367"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Hide Others" keyEquivalent="h" id="145"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> |
||||||
|
<connections> |
||||||
|
<action selector="hideOtherApplications:" target="-1" id="368"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Show All" id="150"> |
||||||
|
<connections> |
||||||
|
<action selector="unhideAllApplications:" target="-1" id="370"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem isSeparatorItem="YES" id="149"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" command="YES"/> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Quit Clocker" keyEquivalent="q" id="136"> |
||||||
|
<connections> |
||||||
|
<action selector="terminate:" target="-3" id="449"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
</items> |
||||||
|
</menu> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="File" id="83"> |
||||||
|
<menu key="submenu" title="File" id="81"> |
||||||
|
<items> |
||||||
|
<menuItem title="New" keyEquivalent="n" id="82"> |
||||||
|
<connections> |
||||||
|
<action selector="newDocument:" target="-1" id="373"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Open…" keyEquivalent="o" id="72"> |
||||||
|
<connections> |
||||||
|
<action selector="openDocument:" target="-1" id="374"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Open Recent" id="124"> |
||||||
|
<menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="125"> |
||||||
|
<items> |
||||||
|
<menuItem title="Clear Menu" id="126"> |
||||||
|
<connections> |
||||||
|
<action selector="clearRecentDocuments:" target="-1" id="127"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
</items> |
||||||
|
</menu> |
||||||
|
</menuItem> |
||||||
|
<menuItem isSeparatorItem="YES" id="79"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" command="YES"/> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Close" keyEquivalent="w" id="73"> |
||||||
|
<connections> |
||||||
|
<action selector="performClose:" target="-1" id="193"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Save…" keyEquivalent="s" id="75"> |
||||||
|
<connections> |
||||||
|
<action selector="saveDocument:" target="-1" id="362"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Revert to Saved" id="112"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
|
<connections> |
||||||
|
<action selector="revertDocumentToSaved:" target="-1" id="364"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem isSeparatorItem="YES" id="74"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" command="YES"/> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Page Setup..." keyEquivalent="P" id="77"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/> |
||||||
|
<connections> |
||||||
|
<action selector="runPageLayout:" target="-1" id="87"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Print…" keyEquivalent="p" id="78"> |
||||||
|
<connections> |
||||||
|
<action selector="print:" target="-1" id="86"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
</items> |
||||||
|
</menu> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Edit" id="217"> |
||||||
|
<menu key="submenu" title="Edit" id="205"> |
||||||
|
<items> |
||||||
|
<menuItem title="Undo" keyEquivalent="z" id="207"> |
||||||
|
<connections> |
||||||
|
<action selector="undo:" target="-1" id="223"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Redo" keyEquivalent="Z" id="215"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/> |
||||||
|
<connections> |
||||||
|
<action selector="redo:" target="-1" id="231"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem isSeparatorItem="YES" id="206"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" command="YES"/> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Cut" keyEquivalent="x" id="199"> |
||||||
|
<connections> |
||||||
|
<action selector="cut:" target="-1" id="228"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Copy" keyEquivalent="c" id="197"> |
||||||
|
<connections> |
||||||
|
<action selector="copy:" target="-1" id="224"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Paste" keyEquivalent="v" id="203"> |
||||||
|
<connections> |
||||||
|
<action selector="paste:" target="-1" id="226"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Paste and Match Style" keyEquivalent="v" id="485"> |
||||||
|
<connections> |
||||||
|
<action selector="pasteAsPlainText:" target="-1" id="486"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Delete" id="202"> |
||||||
|
<connections> |
||||||
|
<action selector="delete:" target="-1" id="235"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Select All" keyEquivalent="a" id="198"> |
||||||
|
<connections> |
||||||
|
<action selector="selectAll:" target="-1" id="232"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem isSeparatorItem="YES" id="214"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" command="YES"/> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Find" id="218"> |
||||||
|
<menu key="submenu" title="Find" id="220"> |
||||||
|
<items> |
||||||
|
<menuItem title="Find…" tag="1" keyEquivalent="f" id="209"> |
||||||
|
<connections> |
||||||
|
<action selector="performFindPanelAction:" target="-1" id="241"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="534"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> |
||||||
|
<connections> |
||||||
|
<action selector="performFindPanelAction:" target="-1" id="535"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="208"> |
||||||
|
<connections> |
||||||
|
<action selector="performFindPanelAction:" target="-1" id="487"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="213"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/> |
||||||
|
<connections> |
||||||
|
<action selector="performFindPanelAction:" target="-1" id="488"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="221"> |
||||||
|
<connections> |
||||||
|
<action selector="performFindPanelAction:" target="-1" id="489"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Jump to Selection" keyEquivalent="j" id="210"> |
||||||
|
<connections> |
||||||
|
<action selector="centerSelectionInVisibleArea:" target="-1" id="245"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
</items> |
||||||
|
</menu> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Spelling and Grammar" id="216"> |
||||||
|
<menu key="submenu" title="Spelling and Grammar" id="200"> |
||||||
|
<items> |
||||||
|
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="204"> |
||||||
|
<connections> |
||||||
|
<action selector="showGuessPanel:" target="-1" id="230"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Check Document Now" keyEquivalent=";" id="201"> |
||||||
|
<connections> |
||||||
|
<action selector="checkSpelling:" target="-1" id="225"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem isSeparatorItem="YES" id="453"/> |
||||||
|
<menuItem title="Check Spelling While Typing" id="219"> |
||||||
|
<connections> |
||||||
|
<action selector="toggleContinuousSpellChecking:" target="-1" id="222"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Check Grammar With Spelling" id="346"> |
||||||
|
<connections> |
||||||
|
<action selector="toggleGrammarChecking:" target="-1" id="347"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Correct Spelling Automatically" id="454"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
|
<connections> |
||||||
|
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="456"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
</items> |
||||||
|
</menu> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Substitutions" id="348"> |
||||||
|
<menu key="submenu" title="Substitutions" id="349"> |
||||||
|
<items> |
||||||
|
<menuItem title="Show Substitutions" id="457"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
|
<connections> |
||||||
|
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="458"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem isSeparatorItem="YES" id="459"/> |
||||||
|
<menuItem title="Smart Copy/Paste" tag="1" keyEquivalent="f" id="350"> |
||||||
|
<connections> |
||||||
|
<action selector="toggleSmartInsertDelete:" target="-1" id="355"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Smart Quotes" tag="2" keyEquivalent="g" id="351"> |
||||||
|
<connections> |
||||||
|
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="356"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Smart Dashes" id="460"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
|
<connections> |
||||||
|
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="461"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Smart Links" tag="3" keyEquivalent="G" id="354"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/> |
||||||
|
<connections> |
||||||
|
<action selector="toggleAutomaticLinkDetection:" target="-1" id="357"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Text Replacement" id="462"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
|
<connections> |
||||||
|
<action selector="toggleAutomaticTextReplacement:" target="-1" id="463"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
</items> |
||||||
|
</menu> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Transformations" id="450"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
|
<menu key="submenu" title="Transformations" id="451"> |
||||||
|
<items> |
||||||
|
<menuItem title="Make Upper Case" id="452"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
|
<connections> |
||||||
|
<action selector="uppercaseWord:" target="-1" id="464"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Make Lower Case" id="465"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
|
<connections> |
||||||
|
<action selector="lowercaseWord:" target="-1" id="468"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Capitalize" id="466"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
|
<connections> |
||||||
|
<action selector="capitalizeWord:" target="-1" id="467"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
</items> |
||||||
|
</menu> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Speech" id="211"> |
||||||
|
<menu key="submenu" title="Speech" id="212"> |
||||||
|
<items> |
||||||
|
<menuItem title="Start Speaking" id="196"> |
||||||
|
<connections> |
||||||
|
<action selector="startSpeaking:" target="-1" id="233"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Stop Speaking" id="195"> |
||||||
|
<connections> |
||||||
|
<action selector="stopSpeaking:" target="-1" id="227"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
</items> |
||||||
|
</menu> |
||||||
|
</menuItem> |
||||||
|
</items> |
||||||
|
</menu> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Window" id="19"> |
||||||
|
<menu key="submenu" title="Window" systemMenu="window" id="24"> |
||||||
|
<items> |
||||||
|
<menuItem title="Minimize" keyEquivalent="m" id="23"> |
||||||
|
<connections> |
||||||
|
<action selector="performMiniaturize:" target="-1" id="37"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Zoom" id="239"> |
||||||
|
<connections> |
||||||
|
<action selector="performZoom:" target="-1" id="240"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
<menuItem isSeparatorItem="YES" id="92"> |
||||||
|
<modifierMask key="keyEquivalentModifierMask" command="YES"/> |
||||||
|
</menuItem> |
||||||
|
<menuItem title="Bring All to Front" id="5"> |
||||||
|
<connections> |
||||||
|
<action selector="arrangeInFront:" target="-1" id="39"/> |
||||||
|
</connections> |
||||||
|
</menuItem> |
||||||
|
</items> |
||||||
|
</menu> |
||||||
|
</menuItem> |
||||||
|
</items> |
||||||
|
</menu> |
||||||
|
<customObject id="494" customClass="AppDelegate" customModule="Clocker" customModuleProvider="target"/> |
||||||
|
<customObject id="420" customClass="NSFontManager"/> |
||||||
|
</objects> |
||||||
|
</document> |
@ -0,0 +1,18 @@ |
|||||||
|
# Xcode |
||||||
|
build/* |
||||||
|
*.pbxuser |
||||||
|
!default.pbxuser |
||||||
|
*.mode1v3 |
||||||
|
!default.mode1v3 |
||||||
|
*.mode2v3 |
||||||
|
!default.mode2v3 |
||||||
|
*.perspectivev3 |
||||||
|
!default.perspectivev3 |
||||||
|
*.xcworkspace |
||||||
|
!default.xcworkspace |
||||||
|
xcuserdata |
||||||
|
profile |
||||||
|
*.moved-aside |
||||||
|
|
||||||
|
## Ignore incredibly annoying .DS_Store files |
||||||
|
.DS_Store |
@ -0,0 +1,31 @@ |
|||||||
|
# ShortcutRecorder |
||||||
|
|
||||||
|
Copyright (c) 2006, Contributors |
||||||
|
All rights reserved. |
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without |
||||||
|
modification, are permitted provided that the following conditions are met: |
||||||
|
* Redistributions of source code must retain the above copyright |
||||||
|
notice, this list of conditions and the following disclaimer. |
||||||
|
* Redistributions in binary form must reproduce the above copyright |
||||||
|
notice, this list of conditions and the following disclaimer in the |
||||||
|
documentation and/or other materials provided with the distribution. |
||||||
|
* Neither the name of the organization nor the |
||||||
|
names of its contributors may be used to endorse or promote products |
||||||
|
derived from this software without specific prior written permission. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||||
|
DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY |
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
# PTHotKey |
||||||
|
|
||||||
|
Copyright (c) 2003 Quentin D. Carnicelli. |
||||||
|
All rights reserved. |
@ -0,0 +1,22 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<dict> |
||||||
|
<key>CFBundleDevelopmentRegion</key> |
||||||
|
<string>English</string> |
||||||
|
<key>CFBundleExecutable</key> |
||||||
|
<string>ShortcutRecorder</string> |
||||||
|
<key>CFBundleIdentifier</key> |
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> |
||||||
|
<key>CFBundleInfoDictionaryVersion</key> |
||||||
|
<string>6.0</string> |
||||||
|
<key>CFBundlePackageType</key> |
||||||
|
<string>FMWK</string> |
||||||
|
<key>CFBundleShortVersionString</key> |
||||||
|
<string>2.17</string> |
||||||
|
<key>CFBundleSignature</key> |
||||||
|
<string>????</string> |
||||||
|
<key>CFBundleVersion</key> |
||||||
|
<string>2.17</string> |
||||||
|
</dict> |
||||||
|
</plist> |
@ -0,0 +1,5 @@ |
|||||||
|
|
||||||
|
#ifdef __OBJC__ |
||||||
|
#import <Cocoa/Cocoa.h> |
||||||
|
#import <Carbon/Carbon.h> |
||||||
|
#endif |
@ -0,0 +1,116 @@ |
|||||||
|
//
|
||||||
|
// SRCommon.h
|
||||||
|
// ShortcutRecorder
|
||||||
|
//
|
||||||
|
// Copyright 2006-2012 Contributors. All rights reserved.
|
||||||
|
//
|
||||||
|
// License: BSD
|
||||||
|
//
|
||||||
|
// Contributors:
|
||||||
|
// David Dauer
|
||||||
|
// Jesper
|
||||||
|
// Jamie Kirkpatrick
|
||||||
|
// Andy Kim
|
||||||
|
// Ilya Kulakov
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h> |
||||||
|
#import <Carbon/Carbon.h> |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Mask representing subset of Cocoa modifier flags suitable for shortcuts. |
||||||
|
*/ |
||||||
|
static const NSEventModifierFlags SRCocoaModifierFlagsMask = NSEventModifierFlagCommand | NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagControl; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Mask representing subset of Carbon modifier flags suitable for shortcuts. |
||||||
|
*/ |
||||||
|
static const NSUInteger SRCarbonModifierFlagsMask = cmdKey | optionKey | shiftKey | controlKey; |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Converts carbon modifier flags to cocoa. |
||||||
|
*/ |
||||||
|
FOUNDATION_STATIC_INLINE NSEventModifierFlags SRCarbonToCocoaFlags(UInt32 aCarbonFlags) |
||||||
|
{ |
||||||
|
NSEventModifierFlags cocoaFlags = 0; |
||||||
|
|
||||||
|
if (aCarbonFlags & cmdKey) |
||||||
|
cocoaFlags |= NSEventModifierFlagCommand; |
||||||
|
|
||||||
|
if (aCarbonFlags & optionKey) |
||||||
|
cocoaFlags |= NSEventModifierFlagOption; |
||||||
|
|
||||||
|
if (aCarbonFlags & controlKey) |
||||||
|
cocoaFlags |= NSEventModifierFlagControl; |
||||||
|
|
||||||
|
if (aCarbonFlags & shiftKey) |
||||||
|
cocoaFlags |= NSEventModifierFlagShift; |
||||||
|
|
||||||
|
return cocoaFlags; |
||||||
|
} |
||||||
|
|
||||||
|
/*!
|
||||||
|
Converts cocoa modifier flags to carbon. |
||||||
|
*/ |
||||||
|
FOUNDATION_STATIC_INLINE UInt32 SRCocoaToCarbonFlags(NSEventModifierFlags aCocoaFlags) |
||||||
|
{ |
||||||
|
UInt32 carbonFlags = 0; |
||||||
|
|
||||||
|
if (aCocoaFlags & NSEventModifierFlagCommand) |
||||||
|
carbonFlags |= cmdKey; |
||||||
|
|
||||||
|
if (aCocoaFlags & NSEventModifierFlagOption) |
||||||
|
carbonFlags |= optionKey; |
||||||
|
|
||||||
|
if (aCocoaFlags & NSEventModifierFlagControl) |
||||||
|
carbonFlags |= controlKey; |
||||||
|
|
||||||
|
if (aCocoaFlags & NSEventModifierFlagShift) |
||||||
|
carbonFlags |= shiftKey; |
||||||
|
|
||||||
|
return carbonFlags; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Return Bundle where resources can be found. |
||||||
|
|
||||||
|
@discussion Throws NSInternalInconsistencyException if bundle cannot be found. |
||||||
|
*/ |
||||||
|
NSBundle *SRBundle(void); |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Convenient method to get localized string from the framework bundle. |
||||||
|
*/ |
||||||
|
NSString *SRLoc(NSString *aKey); |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Convenient method to get image from the framework bundle. |
||||||
|
*/ |
||||||
|
NSImage *SRImage(NSString *anImageName); |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns string representation of shortcut with modifier flags replaced with their localized |
||||||
|
readable equivalents (e.g. ? -> Option). |
||||||
|
*/ |
||||||
|
NSString *SRReadableStringForCocoaModifierFlagsAndKeyCode(NSEventModifierFlags aModifierFlags, unsigned short aKeyCode); |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns string representation of shortcut with modifier flags replaced with their localized |
||||||
|
readable equivalents (e.g. ? -> Option) and ASCII character for key code. |
||||||
|
*/ |
||||||
|
NSString *SRReadableASCIIStringForCocoaModifierFlagsAndKeyCode(NSEventModifierFlags aModifierFlags, unsigned short aKeyCode); |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines if given key code with flags is equal to key equivalent and flags |
||||||
|
(usually taken from NSButton or NSMenu). |
||||||
|
|
||||||
|
@discussion On Mac OS X some key combinations can have "alternates". E.g. option-A can be represented both as option-A and as Œ. |
||||||
|
*/ |
||||||
|
BOOL SRKeyCodeWithFlagsEqualToKeyEquivalentWithFlags(unsigned short aKeyCode, |
||||||
|
NSEventModifierFlags aKeyCodeFlags, |
||||||
|
NSString *aKeyEquivalent, |
||||||
|
NSEventModifierFlags aKeyEquivalentModifierFlags); |
@ -0,0 +1,175 @@ |
|||||||
|
// |
||||||
|
// SRCommon.m |
||||||
|
// ShortcutRecorder |
||||||
|
// |
||||||
|
// Copyright 2006-2012 Contributors. All rights reserved. |
||||||
|
// |
||||||
|
// License: BSD |
||||||
|
// |
||||||
|
// Contributors: |
||||||
|
// David Dauer |
||||||
|
// Jesper |
||||||
|
// Jamie Kirkpatrick |
||||||
|
// Andy Kim |
||||||
|
// Ilya Kulakov |
||||||
|
|
||||||
|
#import "SRCommon.h" |
||||||
|
#import "SRKeyCodeTransformer.h" |
||||||
|
|
||||||
|
|
||||||
|
NSBundle *SRBundle() |
||||||
|
{ |
||||||
|
static dispatch_once_t onceToken; |
||||||
|
static NSBundle *Bundle = nil; |
||||||
|
dispatch_once(&onceToken, ^{ |
||||||
|
Bundle = [NSBundle bundleWithIdentifier:@"com.kulakov.ShortcutRecorder"]; |
||||||
|
|
||||||
|
if (!Bundle) |
||||||
|
{ |
||||||
|
// Could be a CocoaPods framework with embedded resources bundle. |
||||||
|
// Look up "use_frameworks!" and "resources_bundle" in CocoaPods documentation. |
||||||
|
Bundle = [NSBundle bundleWithIdentifier:@"org.cocoapods.ShortcutRecorder"]; |
||||||
|
|
||||||
|
if (!Bundle) |
||||||
|
{ |
||||||
|
Class c = NSClassFromString(@"SRRecorderControl"); |
||||||
|
|
||||||
|
if (c) |
||||||
|
{ |
||||||
|
Bundle = [NSBundle bundleForClass:c]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (Bundle) |
||||||
|
{ |
||||||
|
Bundle = [NSBundle bundleWithPath:[Bundle pathForResource:@"ShortcutRecorder" ofType:@"bundle"]]; |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
if (!Bundle) |
||||||
|
{ |
||||||
|
@throw [NSException exceptionWithName:NSInternalInconsistencyException |
||||||
|
reason:@"Unable to find bundle with resources." |
||||||
|
userInfo:nil]; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
return Bundle; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
NSString *SRLoc(NSString *aKey) |
||||||
|
{ |
||||||
|
return NSLocalizedStringFromTableInBundle(aKey, @"ShortcutRecorder", SRBundle(), nil); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
NSImage *SRImage(NSString *anImageName) |
||||||
|
{ |
||||||
|
if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_6) |
||||||
|
return [[NSImage alloc] initByReferencingURL:[SRBundle() URLForImageResource:anImageName]]; |
||||||
|
else |
||||||
|
return [SRBundle() imageForResource:anImageName]; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
NSString *SRReadableStringForCocoaModifierFlagsAndKeyCode(NSEventModifierFlags aModifierFlags, unsigned short aKeyCode) |
||||||
|
{ |
||||||
|
SRKeyCodeTransformer *t = [SRKeyCodeTransformer sharedPlainTransformer]; |
||||||
|
NSString *c = [t transformedValue:@(aKeyCode)]; |
||||||
|
|
||||||
|
return [NSString stringWithFormat:@"%@%@%@%@%@", |
||||||
|
(aModifierFlags & NSCommandKeyMask ? SRLoc(@"Command-") : @""), |
||||||
|
(aModifierFlags & NSAlternateKeyMask ? SRLoc(@"Option-") : @""), |
||||||
|
(aModifierFlags & NSControlKeyMask ? SRLoc(@"Control-") : @""), |
||||||
|
(aModifierFlags & NSShiftKeyMask ? SRLoc(@"Shift-") : @""), |
||||||
|
c]; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
NSString *SRReadableASCIIStringForCocoaModifierFlagsAndKeyCode(NSEventModifierFlags aModifierFlags, unsigned short aKeyCode) |
||||||
|
{ |
||||||
|
SRKeyCodeTransformer *t = [SRKeyCodeTransformer sharedPlainASCIITransformer]; |
||||||
|
NSString *c = [t transformedValue:@(aKeyCode)]; |
||||||
|
|
||||||
|
return [NSString stringWithFormat:@"%@%@%@%@%@", |
||||||
|
(aModifierFlags & NSCommandKeyMask ? SRLoc(@"Command-") : @""), |
||||||
|
(aModifierFlags & NSAlternateKeyMask ? SRLoc(@"Option-") : @""), |
||||||
|
(aModifierFlags & NSControlKeyMask ? SRLoc(@"Control-") : @""), |
||||||
|
(aModifierFlags & NSShiftKeyMask ? SRLoc(@"Shift-") : @""), |
||||||
|
c]; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
static BOOL _SRKeyCodeWithFlagsEqualToKeyEquivalentWithFlags(unsigned short aKeyCode, |
||||||
|
NSEventModifierFlags aKeyCodeFlags, |
||||||
|
NSString *aKeyEquivalent, |
||||||
|
NSEventModifierFlags aKeyEquivalentModifierFlags, |
||||||
|
SRKeyCodeTransformer *aTransformer) |
||||||
|
{ |
||||||
|
if (!aKeyEquivalent) |
||||||
|
return NO; |
||||||
|
|
||||||
|
aKeyCodeFlags &= SRCocoaModifierFlagsMask; |
||||||
|
aKeyEquivalentModifierFlags &= SRCocoaModifierFlagsMask; |
||||||
|
|
||||||
|
if (aKeyCodeFlags == aKeyEquivalentModifierFlags) |
||||||
|
{ |
||||||
|
NSString *keyCodeRepresentation = [aTransformer transformedValue:@(aKeyCode) |
||||||
|
withImplicitModifierFlags:nil |
||||||
|
explicitModifierFlags:@(aKeyCodeFlags)]; |
||||||
|
return [keyCodeRepresentation isEqual:aKeyEquivalent]; |
||||||
|
} |
||||||
|
else if (!aKeyEquivalentModifierFlags || |
||||||
|
(aKeyCodeFlags & aKeyEquivalentModifierFlags) == aKeyEquivalentModifierFlags) |
||||||
|
{ |
||||||
|
// Some key equivalent modifier flags can be implicitly set by using special unicode characters. E.g. Œ insetead of opt-a. |
||||||
|
// However all modifier flags explictily set in key equivalent MUST be also set in key code flags. |
||||||
|
// E.g. ctrl-Œ/ctrl-opt-a and Œ/opt-a match this condition, but cmd-Œ/ctrl-opt-a doesn't. |
||||||
|
NSString *keyCodeRepresentation = [aTransformer transformedValue:@(aKeyCode) |
||||||
|
withImplicitModifierFlags:nil |
||||||
|
explicitModifierFlags:@(aKeyCodeFlags)]; |
||||||
|
|
||||||
|
if ([keyCodeRepresentation isEqual:aKeyEquivalent]) |
||||||
|
{ |
||||||
|
// Key code and key equivalent are not equal key code representation matches key equivalent, but modifier flags are not. |
||||||
|
return NO; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
NSEventModifierFlags possiblyImplicitFlags = aKeyCodeFlags & ~aKeyEquivalentModifierFlags; |
||||||
|
keyCodeRepresentation = [aTransformer transformedValue:@(aKeyCode) |
||||||
|
withImplicitModifierFlags:@(possiblyImplicitFlags) |
||||||
|
explicitModifierFlags:@(aKeyEquivalentModifierFlags)]; |
||||||
|
return [keyCodeRepresentation isEqual:aKeyEquivalent]; |
||||||
|
} |
||||||
|
} |
||||||
|
else |
||||||
|
return NO; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
BOOL SRKeyCodeWithFlagsEqualToKeyEquivalentWithFlags(unsigned short aKeyCode, |
||||||
|
NSEventModifierFlags aKeyCodeFlags, |
||||||
|
NSString *aKeyEquivalent, |
||||||
|
NSEventModifierFlags aKeyEquivalentModifierFlags) |
||||||
|
{ |
||||||
|
BOOL isEqual = _SRKeyCodeWithFlagsEqualToKeyEquivalentWithFlags(aKeyCode, |
||||||
|
aKeyCodeFlags, |
||||||
|
aKeyEquivalent, |
||||||
|
aKeyEquivalentModifierFlags, |
||||||
|
[SRKeyCodeTransformer sharedASCIITransformer]); |
||||||
|
|
||||||
|
if (!isEqual) |
||||||
|
{ |
||||||
|
isEqual = _SRKeyCodeWithFlagsEqualToKeyEquivalentWithFlags(aKeyCode, |
||||||
|
aKeyCodeFlags, |
||||||
|
aKeyEquivalent, |
||||||
|
aKeyEquivalentModifierFlags, |
||||||
|
[SRKeyCodeTransformer sharedTransformer]); |
||||||
|
} |
||||||
|
|
||||||
|
return isEqual; |
||||||
|
} |
@ -0,0 +1,139 @@ |
|||||||
|
//
|
||||||
|
// SRKeyCodeTransformer.h
|
||||||
|
// ShortcutRecorder
|
||||||
|
//
|
||||||
|
// Copyright 2006-2012 Contributors. All rights reserved.
|
||||||
|
//
|
||||||
|
// License: BSD
|
||||||
|
//
|
||||||
|
// Contributors:
|
||||||
|
// David Dauer
|
||||||
|
// Jesper
|
||||||
|
// Jamie Kirkpatrick
|
||||||
|
// Ilya Kulakov
|
||||||
|
// Silvio Rizzi
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h> |
||||||
|
#import <Carbon/Carbon.h> |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Transforms key code into unicode character or plain string. |
||||||
|
*/ |
||||||
|
@interface SRKeyCodeTransformer : NSValueTransformer |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns initialized key code transformer. |
||||||
|
|
||||||
|
@param aUsesASCII Determines whether transformer uses only ASCII capable keyboard input source. |
||||||
|
|
||||||
|
@param aUsesPlainStrings Determines whether key codes without readable glyphs (e.g. F1...F19) are transformed to |
||||||
|
to unicode characters (NSF1FunctionKey...NSF19FunctionKey) suitable for setting key equivalents |
||||||
|
of Cocoa controls or to plain strings (@"F1"...@"F19") suitable for drawing, logging and accessibility. |
||||||
|
|
||||||
|
@discussion This method is the designated initializer for SRKeyCodeTransformer. |
||||||
|
*/ |
||||||
|
- (instancetype)initWithASCIICapableKeyboardInputSource:(BOOL)aUsesASCII plainStrings:(BOOL)aUsesPlainStrings; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether transformer uses ASCII capable keyboard input source. |
||||||
|
*/ |
||||||
|
@property (readonly) BOOL usesASCIICapableKeyboardInputSource; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether key codes without readable glyphs are transformed to unicode characters |
||||||
|
suitable for setting keqEquivalents or to plain strings suitable for drawing, logging and accessibility. |
||||||
|
*/ |
||||||
|
@property (readonly) BOOL usesPlainStrings; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the shared transformer. |
||||||
|
*/ |
||||||
|
+ (instancetype)sharedTransformer; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the shared transformer configured to use only ASCII capable keyboard input source. |
||||||
|
*/ |
||||||
|
+ (instancetype)sharedASCIITransformer; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the shared transformer configured to transform key codes to plain strings. |
||||||
|
*/ |
||||||
|
+ (SRKeyCodeTransformer *)sharedPlainTransformer; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the shared transformer configured to use only ASCII capable keyboard input source |
||||||
|
and to transform key codes to plain strings. |
||||||
|
*/ |
||||||
|
+ (SRKeyCodeTransformer *)sharedPlainASCIITransformer; |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns mapping from special key codes to unicode characters. |
||||||
|
*/ |
||||||
|
+ (NSDictionary *)specialKeyCodesToUnicodeCharactersMapping; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns mapping from special key codes to plain strings. |
||||||
|
*/ |
||||||
|
+ (NSDictionary *)specialKeyCodesToPlainStringsMapping; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether key code is special. |
||||||
|
|
||||||
|
@param aKeyCode Key code to be checked. |
||||||
|
*/ |
||||||
|
- (BOOL)isKeyCodeSpecial:(unsigned short)aKeyCode; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Transforms given special key code into unicode character by taking into account modifier flags. |
||||||
|
|
||||||
|
@discussion E.g. the key code 0x30 is transformed to ⇥. But if shift is pressed, it is transformed to ⇤. |
||||||
|
|
||||||
|
@result Unicode character or plain string. nil if not a special key code. |
||||||
|
*/ |
||||||
|
- (NSString *)transformedSpecialKeyCode:(NSNumber *)aKeyCode withExplicitModifierFlags:(NSNumber *)aModifierFlags; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Shorcut to [self transformedValue:aValue withImplicitModifierFlags:aModifierFlags explicitModifierFlags:0] |
||||||
|
*/ |
||||||
|
- (NSString *)transformedValue:(NSNumber *)aValue withModifierFlags:(NSNumber *)aModifierFlags; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Transfroms given key code into unicode character by taking into account modifier flags. |
||||||
|
|
||||||
|
@param aValue An instance of NSNumber (unsigned short) that represents key code. |
||||||
|
|
||||||
|
@param anImplicitModifierFlags An instance of NSNumber (NSEventModifierFlags) that represents implicit modifier flags like opt in å. |
||||||
|
|
||||||
|
@param anExplicitModifierFlags An instance of NSNumber (NSEventModifierFlags) that represents explicit modifier flags like shift in shift-⇤. |
||||||
|
*/ |
||||||
|
- (NSString *)transformedValue:(NSNumber *)aValue withImplicitModifierFlags:(NSNumber *)anImplicitModifierFlags explicitModifierFlags:(NSNumber *)anExplicitModifierFlags; |
||||||
|
|
||||||
|
@end |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
These constants represents drawable unicode characters for key codes that do not have |
||||||
|
appropriate constants in Carbon and Cocoa. |
||||||
|
*/ |
||||||
|
typedef NS_ENUM(unichar, SRKeyCodeGlyph) |
||||||
|
{ |
||||||
|
SRKeyCodeGlyphTabRight = 0x21E5, // ⇥
|
||||||
|
SRKeyCodeGlyphTabLeft = 0x21E4, // ⇤
|
||||||
|
SRKeyCodeGlyphReturn = 0x2305, // ⌅
|
||||||
|
SRKeyCodeGlyphReturnR2L = 0x21A9, // ↩
|
||||||
|
SRKeyCodeGlyphDeleteLeft = 0x232B, // ⌫
|
||||||
|
SRKeyCodeGlyphDeleteRight = 0x2326, // ⌦
|
||||||
|
SRKeyCodeGlyphPadClear = 0x2327, // ⌧
|
||||||
|
SRKeyCodeGlyphLeftArrow = 0x2190, // ←
|
||||||
|
SRKeyCodeGlyphRightArrow = 0x2192, // →
|
||||||
|
SRKeyCodeGlyphUpArrow = 0x2191, // ↑
|
||||||
|
SRKeyCodeGlyphDownArrow = 0x2193, // ↓
|
||||||
|
SRKeyCodeGlyphPageDown = 0x21DF, // ⇟
|
||||||
|
SRKeyCodeGlyphPageUp = 0x21DE, // ⇞
|
||||||
|
SRKeyCodeGlyphNorthwestArrow = 0x2196, // ↖
|
||||||
|
SRKeyCodeGlyphSoutheastArrow = 0x2198, // ↘
|
||||||
|
SRKeyCodeGlyphEscape = 0x238B, // ⎋
|
||||||
|
SRKeyCodeGlyphSpace = 0x0020, // ' '
|
||||||
|
}; |
@ -0,0 +1,21 @@ |
|||||||
|
//
|
||||||
|
// SRKeyEquivalentModifierMaskTransformer.h
|
||||||
|
// ShortcutRecorder
|
||||||
|
//
|
||||||
|
// Copyright 2012 Contributors. All rights reserved.
|
||||||
|
//
|
||||||
|
// License: BSD
|
||||||
|
//
|
||||||
|
// Contributors to this file:
|
||||||
|
// Ilya Kulakov
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h> |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Transform dictionary representation of shortcut into string suitable |
||||||
|
for -setKeyEquivalentModifierMask: of NSButton and NSMenuItem. |
||||||
|
*/ |
||||||
|
@interface SRKeyEquivalentModifierMaskTransformer : NSValueTransformer |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,44 @@ |
|||||||
|
// |
||||||
|
// SRKeyEquivalentModifierMaskTransformer.m |
||||||
|
// ShortcutRecorder |
||||||
|
// |
||||||
|
// Copyright 2012 Contributors. All rights reserved. |
||||||
|
// |
||||||
|
// License: BSD |
||||||
|
// |
||||||
|
// Contributors: |
||||||
|
// Ilya Kulakov |
||||||
|
|
||||||
|
#import "SRKeyEquivalentModifierMaskTransformer.h" |
||||||
|
#import "SRKeyCodeTransformer.h" |
||||||
|
#import "SRRecorderControl.h" |
||||||
|
|
||||||
|
|
||||||
|
@implementation SRKeyEquivalentModifierMaskTransformer |
||||||
|
|
||||||
|
#pragma mark NSValueTransformer |
||||||
|
|
||||||
|
+ (BOOL)allowsReverseTransformation |
||||||
|
{ |
||||||
|
return NO; |
||||||
|
} |
||||||
|
|
||||||
|
+ (Class)transformedValueClass |
||||||
|
{ |
||||||
|
return [NSNumber class]; |
||||||
|
} |
||||||
|
|
||||||
|
- (NSNumber *)transformedValue:(NSDictionary *)aValue |
||||||
|
{ |
||||||
|
if (![aValue isKindOfClass:[NSDictionary class]]) |
||||||
|
return @(0); |
||||||
|
|
||||||
|
NSNumber *modifierFlags = aValue[SRShortcutModifierFlagsKey]; |
||||||
|
|
||||||
|
if (![modifierFlags isKindOfClass:[NSNumber class]]) |
||||||
|
return @(0); |
||||||
|
|
||||||
|
return modifierFlags; |
||||||
|
} |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,21 @@ |
|||||||
|
//
|
||||||
|
// SRKeyEquivalentTransformer.h
|
||||||
|
// ShortcutRecorder
|
||||||
|
//
|
||||||
|
// Copyright 2012 Contributors. All rights reserved.
|
||||||
|
//
|
||||||
|
// License: BSD
|
||||||
|
//
|
||||||
|
// Contributors to this file:
|
||||||
|
// Ilya Kulakov
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h> |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Transform dictionary representation of shortcut into string suitable |
||||||
|
for -setKeyEquivalent: of NSButton and NSMenuItem. |
||||||
|
*/ |
||||||
|
@interface SRKeyEquivalentTransformer : NSValueTransformer |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,53 @@ |
|||||||
|
// |
||||||
|
// SRKeyEquivalentTransformer.m |
||||||
|
// ShortcutRecorder |
||||||
|
// |
||||||
|
// Copyright 2012 Contributors. All rights reserved. |
||||||
|
// |
||||||
|
// License: BSD |
||||||
|
// |
||||||
|
// Contributors: |
||||||
|
// Ilya Kulakov |
||||||
|
|
||||||
|
#import "SRKeyEquivalentTransformer.h" |
||||||
|
#import "SRKeyCodeTransformer.h" |
||||||
|
#import "SRRecorderControl.h" |
||||||
|
|
||||||
|
|
||||||
|
@implementation SRKeyEquivalentTransformer |
||||||
|
|
||||||
|
#pragma mark NSValueTransformer |
||||||
|
|
||||||
|
+ (BOOL)allowsReverseTransformation |
||||||
|
{ |
||||||
|
return NO; |
||||||
|
} |
||||||
|
|
||||||
|
+ (Class)transformedValueClass |
||||||
|
{ |
||||||
|
return [NSString class]; |
||||||
|
} |
||||||
|
|
||||||
|
- (NSString *)transformedValue:(NSDictionary *)aValue |
||||||
|
{ |
||||||
|
if (![aValue isKindOfClass:[NSDictionary class]]) |
||||||
|
return @""; |
||||||
|
|
||||||
|
NSNumber *keyCode = aValue[SRShortcutKeyCode]; |
||||||
|
|
||||||
|
if (![keyCode isKindOfClass:[NSNumber class]]) |
||||||
|
return @""; |
||||||
|
|
||||||
|
NSNumber *modifierFlags = aValue[SRShortcutModifierFlagsKey]; |
||||||
|
|
||||||
|
if (![modifierFlags isKindOfClass:[NSNumber class]]) |
||||||
|
modifierFlags = @(0); |
||||||
|
|
||||||
|
SRKeyCodeTransformer *t = [SRKeyCodeTransformer sharedASCIITransformer]; |
||||||
|
|
||||||
|
return [t transformedValue:keyCode |
||||||
|
withImplicitModifierFlags:nil |
||||||
|
explicitModifierFlags:modifierFlags]; |
||||||
|
} |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,37 @@ |
|||||||
|
//
|
||||||
|
// SRModifierFlagsTransformer.h
|
||||||
|
// ShortcutRecorder
|
||||||
|
//
|
||||||
|
// Copyright 2006-2012 Contributors. All rights reserved.
|
||||||
|
//
|
||||||
|
// License: BSD
|
||||||
|
//
|
||||||
|
// Contributors:
|
||||||
|
// Ilya Kulakov
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h> |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Transforms mask of Cocoa modifier flags to string of unicode characters. |
||||||
|
*/ |
||||||
|
@interface SRModifierFlagsTransformer : NSValueTransformer |
||||||
|
|
||||||
|
- (instancetype)initWithPlainStrings:(BOOL)aUsesPlainStrings; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether modifier flags are transformed to unicode characters or to plain strings. |
||||||
|
*/ |
||||||
|
@property (readonly) BOOL usesPlainStrings; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the shared transformer. |
||||||
|
*/ |
||||||
|
+ (instancetype)sharedTransformer; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the shared plain transformer. |
||||||
|
*/ |
||||||
|
+ (instancetype)sharedPlainTransformer; |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,108 @@ |
|||||||
|
// |
||||||
|
// SRModifierFlagsTransformer.m |
||||||
|
// ShortcutRecorder |
||||||
|
// |
||||||
|
// Copyright 2006-2012 Contributors. All rights reserved. |
||||||
|
// |
||||||
|
// License: BSD |
||||||
|
// |
||||||
|
// Contributors: |
||||||
|
// Ilya Kulakov |
||||||
|
|
||||||
|
#import "SRModifierFlagsTransformer.h" |
||||||
|
#import "SRCommon.h" |
||||||
|
|
||||||
|
|
||||||
|
@implementation SRModifierFlagsTransformer |
||||||
|
|
||||||
|
- (instancetype)initWithPlainStrings:(BOOL)aUsesPlainStrings |
||||||
|
{ |
||||||
|
self = [super init]; |
||||||
|
|
||||||
|
if (self) |
||||||
|
{ |
||||||
|
_usesPlainStrings = aUsesPlainStrings; |
||||||
|
} |
||||||
|
|
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
- (instancetype)init |
||||||
|
{ |
||||||
|
return [self initWithPlainStrings:NO]; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#pragma mark Methods |
||||||
|
|
||||||
|
+ (instancetype)sharedTransformer |
||||||
|
{ |
||||||
|
static dispatch_once_t OnceToken; |
||||||
|
static SRModifierFlagsTransformer *Transformer = nil; |
||||||
|
dispatch_once(&OnceToken, ^{ |
||||||
|
Transformer = [[self alloc] initWithPlainStrings:NO]; |
||||||
|
}); |
||||||
|
return Transformer; |
||||||
|
} |
||||||
|
|
||||||
|
+ (instancetype)sharedPlainTransformer |
||||||
|
{ |
||||||
|
static dispatch_once_t OnceToken; |
||||||
|
static SRModifierFlagsTransformer *Transformer = nil; |
||||||
|
dispatch_once(&OnceToken, ^{ |
||||||
|
Transformer = [[self alloc] initWithPlainStrings:YES]; |
||||||
|
}); |
||||||
|
return Transformer; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#pragma mark NSValueTransformer |
||||||
|
|
||||||
|
+ (Class)transformedValueClass |
||||||
|
{ |
||||||
|
return [NSString class]; |
||||||
|
} |
||||||
|
|
||||||
|
+ (BOOL)allowsReverseTransformation |
||||||
|
{ |
||||||
|
return NO; |
||||||
|
} |
||||||
|
|
||||||
|
- (NSString *)transformedValue:(NSNumber *)aValue |
||||||
|
{ |
||||||
|
if (![aValue isKindOfClass:[NSNumber class]]) |
||||||
|
return nil; |
||||||
|
else if (self.usesPlainStrings) |
||||||
|
{ |
||||||
|
NSEventModifierFlags modifierFlags = [aValue unsignedIntegerValue]; |
||||||
|
NSMutableString *s = [NSMutableString string]; |
||||||
|
|
||||||
|
if (modifierFlags & NSControlKeyMask) |
||||||
|
[s appendString:SRLoc(@"Control-")]; |
||||||
|
|
||||||
|
if (modifierFlags & NSAlternateKeyMask) |
||||||
|
[s appendString:SRLoc(@"Option-")]; |
||||||
|
|
||||||
|
if (modifierFlags & NSShiftKeyMask) |
||||||
|
[s appendString:SRLoc(@"Shift-")]; |
||||||
|
|
||||||
|
if (modifierFlags & NSCommandKeyMask) |
||||||
|
[s appendString:SRLoc(@"Command-")]; |
||||||
|
|
||||||
|
if (s.length > 0) |
||||||
|
[s deleteCharactersInRange:NSMakeRange(s.length - 1, 1)]; |
||||||
|
|
||||||
|
return s; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
NSEventModifierFlags f = [aValue unsignedIntegerValue]; |
||||||
|
return [NSString stringWithFormat:@"%@%@%@%@", |
||||||
|
(f & NSControlKeyMask ? @"⌃" : @""), |
||||||
|
(f & NSAlternateKeyMask ? @"⌥" : @""), |
||||||
|
(f & NSShiftKeyMask ? @"⇧" : @""), |
||||||
|
(f & NSCommandKeyMask ? @"⌘" : @"")]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,436 @@ |
|||||||
|
//
|
||||||
|
// SRRecorderControl.h
|
||||||
|
// ShortcutRecorder
|
||||||
|
//
|
||||||
|
// Copyright 2006-2012 Contributors. All rights reserved.
|
||||||
|
//
|
||||||
|
// License: BSD
|
||||||
|
//
|
||||||
|
// Contributors:
|
||||||
|
// David Dauer
|
||||||
|
// Jesper
|
||||||
|
// Jamie Kirkpatrick
|
||||||
|
// Ilya Kulakov
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h> |
||||||
|
#import <ShortcutRecorder/SRCommon.h> |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Key code. |
||||||
|
|
||||||
|
@discussion NSNumber representation of unsigned short. |
||||||
|
Required key of SRRecorderControl's objectValue. |
||||||
|
*/ |
||||||
|
extern NSString *const SRShortcutKeyCode; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Modifier flags. |
||||||
|
|
||||||
|
@discussion NSNumber representation of NSEventModifierFlags. |
||||||
|
Optional key of SRRecorderControl's objectValue. |
||||||
|
*/ |
||||||
|
extern NSString *const SRShortcutModifierFlagsKey; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Interpretation of key code and modifier flags depending on system locale and input source |
||||||
|
used when shortcut was taken. |
||||||
|
|
||||||
|
@discussion NSString. |
||||||
|
Optional key of SRRecorderControl's objectValue. |
||||||
|
*/ |
||||||
|
extern NSString *const SRShortcutCharacters; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Interpretation of key code without modifier flags depending on system locale and input source |
||||||
|
used when shortcut was taken. |
||||||
|
|
||||||
|
@discussion NSString. |
||||||
|
Optional key of SRRecorderControl's objectValue. |
||||||
|
*/ |
||||||
|
extern NSString *const SRShortcutCharactersIgnoringModifiers; |
||||||
|
|
||||||
|
|
||||||
|
@protocol SRRecorderControlDelegate; |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
An SRRecorderControl object is a control (but not a subclass of NSControl) that allows you to record shortcuts. |
||||||
|
|
||||||
|
@discussion In addition to NSView bindings, exposes: |
||||||
|
NSValueBinding. This binding supports 2 options: |
||||||
|
- NSValueTransformerBindingOption |
||||||
|
- NSValueTransformerNameBindingOption |
||||||
|
NSEnabledBinding. This binding supports 2 options: |
||||||
|
- NSValueTransformerBindingOption |
||||||
|
- NSValueTransformerNameBindingOption |
||||||
|
Note that at that moment, this binding _is not_ multivalue. |
||||||
|
|
||||||
|
Required height: 25 points |
||||||
|
Recommended min width: 100 points |
||||||
|
*/ |
||||||
|
IB_DESIGNABLE |
||||||
|
@interface SRRecorderControl : NSView /* <NSAccessibility, NSKeyValueBindingCreation, NSToolTipOwner, NSNibAwaking> */ |
||||||
|
|
||||||
|
/*!
|
||||||
|
The receiver’s delegate. |
||||||
|
|
||||||
|
@discussion A recorder control delegate responds to editing-related messages. You can use to to prevent editing |
||||||
|
in some cases or to validate typed shortcuts. |
||||||
|
*/ |
||||||
|
@property (assign) IBOutlet NSObject<SRRecorderControlDelegate> *delegate; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns an integer bit field indicating allowed modifier flags. |
||||||
|
|
||||||
|
@discussion Defaults to SRCocoaModifierFlagsMask. |
||||||
|
*/ |
||||||
|
@property (readonly) IBInspectable NSEventModifierFlags allowedModifierFlags; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns an integer bit field indicating required modifier flags. |
||||||
|
|
||||||
|
@discussion Defaults to 0. |
||||||
|
*/ |
||||||
|
@property (readonly) IBInspectable NSEventModifierFlags requiredModifierFlags; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether shortcuts without modifier flags are allowed. |
||||||
|
|
||||||
|
@discussion Defaults to NO. |
||||||
|
*/ |
||||||
|
@property (readonly) IBInspectable BOOL allowsEmptyModifierFlags; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether the control reinterpret key code and modifier flags |
||||||
|
using ASCII capable input source. |
||||||
|
|
||||||
|
@discussion Defaults to YES. |
||||||
|
If not set, the same key code may be draw differently depending on current input source. |
||||||
|
E.g. with US English input source key code 0x0 is interpreted as "a", |
||||||
|
however with Russian input source, it's interpreted as "ф". |
||||||
|
*/ |
||||||
|
@property IBInspectable BOOL drawsASCIIEquivalentOfShortcut; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether Escape is used to cancel recording. |
||||||
|
|
||||||
|
@discussion Defaults to YES. |
||||||
|
If set, Escape without modifier flags cannot be recorded as shortcut. |
||||||
|
*/ |
||||||
|
@property IBInspectable BOOL allowsEscapeToCancelRecording; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether delete (or forward delete) is used to remove current shortcut and end recording. |
||||||
|
|
||||||
|
@discussion Defaults to YES. |
||||||
|
If set, neither Delete nor Forward Delete without modifier flags can be recorded as shortcut. |
||||||
|
*/ |
||||||
|
@property IBInspectable BOOL allowsDeleteToClearShortcutAndEndRecording; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether control enabled and can be edited or not. |
||||||
|
|
||||||
|
@discussion Defaults to YES. |
||||||
|
*/ |
||||||
|
@property (nonatomic, getter=isEnabled) IBInspectable BOOL enabled; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether recording is in process. |
||||||
|
*/ |
||||||
|
@property (nonatomic, readonly) BOOL isRecording; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns dictionary representation of receiver's shortcut. |
||||||
|
*/ |
||||||
|
@property (nonatomic, copy) NSDictionary *objectValue; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Configures recording behavior of the control. |
||||||
|
|
||||||
|
@param newAllowedModifierFlags New allowed modifier flags. |
||||||
|
|
||||||
|
@param newRequiredModifierFlags New required modifier flags. |
||||||
|
|
||||||
|
@param newAllowsEmptyModifierFlags Determines whether empty modifier flags are allowed. |
||||||
|
|
||||||
|
@discussion Flags are filtered using SRCocoaModifierFlagsMask. Flags does not affect object values set manually. |
||||||
|
|
||||||
|
These restrictions can be ignored if delegate implements shortcutRecorder:shouldUnconditionallyAllowModifierFlags:forKeyCode: and returns YES for given modifier flags and key code. |
||||||
|
|
||||||
|
Throws NSInvalidArgumentException if either required flags are not allowed |
||||||
|
or required flags are not empty and no modifier flags are allowed. |
||||||
|
|
||||||
|
@see SRRecorderControlDelegate |
||||||
|
*/ |
||||||
|
- (void)setAllowedModifierFlags:(NSEventModifierFlags)newAllowedModifierFlags |
||||||
|
requiredModifierFlags:(NSEventModifierFlags)newRequiredModifierFlags |
||||||
|
allowsEmptyModifierFlags:(BOOL)newAllowsEmptyModifierFlags; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Called to initialize internal state after either initWithFrame or awakeFromNib is called. |
||||||
|
*/ |
||||||
|
- (void)_initInternalState; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Turns on the recording mode. |
||||||
|
|
||||||
|
@discussion You SHOULD not call this method directly. |
||||||
|
*/ |
||||||
|
- (BOOL)beginRecording; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Turns off the recording mode. Current object value is preserved. |
||||||
|
|
||||||
|
@discussion You SHOULD not call this method directly. |
||||||
|
*/ |
||||||
|
- (void)endRecording; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Clears object value and turns off the recording mode. |
||||||
|
|
||||||
|
@discussion You SHOULD not call this method directly. |
||||||
|
*/ |
||||||
|
- (void)clearAndEndRecording; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Designated method to end recording. Sets a given object value, updates bindings and turns off the recording mode. |
||||||
|
|
||||||
|
@discussion You SHOULD not call this method directly. |
||||||
|
*/ |
||||||
|
- (void)endRecordingWithObjectValue:(NSDictionary *)anObjectValue; |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns shape of the control. |
||||||
|
|
||||||
|
@discussion Primarily used to draw appropriate focus ring. |
||||||
|
*/ |
||||||
|
- (NSBezierPath *)controlShape; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns rect for label with given attributes. |
||||||
|
|
||||||
|
@param aLabel Label for drawing. |
||||||
|
|
||||||
|
@param anAttributes A dictionary of NSAttributedString text attributes to be applied to the string. |
||||||
|
*/ |
||||||
|
- (NSRect)rectForLabel:(NSString *)aLabel withAttributes:(NSDictionary *)anAttributes; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns rect of the snap back button in the receiver coordinates. |
||||||
|
*/ |
||||||
|
- (NSRect)snapBackButtonRect; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns rect of the clear button in the receiver coordinates. |
||||||
|
|
||||||
|
@discussion Returned rect will have empty width (other values will be valid) if button should not be drawn. |
||||||
|
*/ |
||||||
|
- (NSRect)clearButtonRect; |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns label to be displayed by the receiver. |
||||||
|
|
||||||
|
@discussion Returned value depends on isRecording state objectValue and currenlty pressed keys and modifier flags. |
||||||
|
*/ |
||||||
|
- (NSString *)label; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns label for accessibility. |
||||||
|
|
||||||
|
@discussion Returned value depends on isRecording state objectValue and currenlty pressed keys and modifier flags. |
||||||
|
*/ |
||||||
|
- (NSString *)accessibilityLabel; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns string representation of object value. |
||||||
|
*/ |
||||||
|
- (NSString *)stringValue; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns string representation of object value for accessibility. |
||||||
|
*/ |
||||||
|
- (NSString *)accessibilityStringValue; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns attirbutes of label to be displayed by the receiver according to current state. |
||||||
|
|
||||||
|
@see normalLabelAttributes |
||||||
|
|
||||||
|
@see recordingLabelAttributes |
||||||
|
|
||||||
|
@see disabledLabelAttributes |
||||||
|
*/ |
||||||
|
- (NSDictionary *)labelAttributes; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns attributes of label to be displayed by the receiver in normal mode. |
||||||
|
*/ |
||||||
|
- (NSDictionary *)normalLabelAttributes; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns attributes of label to be displayed by the receiver in recording mode. |
||||||
|
*/ |
||||||
|
- (NSDictionary *)recordingLabelAttributes; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns attributes of label to be displayed by the receiver in disabled mode. |
||||||
|
*/ |
||||||
|
- (NSDictionary *)disabledLabelAttributes; |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Draws background of the receiver into current graphics context. |
||||||
|
*/ |
||||||
|
- (void)drawBackground:(NSRect)aDirtyRect; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Draws interior of the receiver into current graphics context. |
||||||
|
*/ |
||||||
|
- (void)drawInterior:(NSRect)aDirtyRect; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Draws label of the receiver into current graphics context. |
||||||
|
*/ |
||||||
|
- (void)drawLabel:(NSRect)aDirtyRect; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Draws snap back button of the receiver into current graphics context. |
||||||
|
*/ |
||||||
|
- (void)drawSnapBackButton:(NSRect)aDirtyRect; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Draws clear button of the receiver into current graphics context. |
||||||
|
*/ |
||||||
|
- (void)drawClearButton:(NSRect)aDirtyRect; |
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether main button (representation of the receiver in normal mode) is highlighted. |
||||||
|
*/ |
||||||
|
- (BOOL)isMainButtonHighlighted; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether snap back button is highlighted. |
||||||
|
*/ |
||||||
|
- (BOOL)isSnapBackButtonHighlighted; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whetehr clear button is highlighted. |
||||||
|
*/ |
||||||
|
- (BOOL)isClearButtonHighlighted; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether modifier flags are valid for key code according to the receiver settings. |
||||||
|
|
||||||
|
@param aModifierFlags Proposed modifier flags. |
||||||
|
|
||||||
|
@param aKeyCode Code of the pressed key. |
||||||
|
|
||||||
|
@see allowedModifierFlags |
||||||
|
|
||||||
|
@see allowsEmptyModifierFlags |
||||||
|
|
||||||
|
@see requiredModifierFlags |
||||||
|
*/ |
||||||
|
- (BOOL)areModifierFlagsValid:(NSEventModifierFlags)aModifierFlags forKeyCode:(unsigned short)aKeyCode; |
||||||
|
|
||||||
|
/*!
|
||||||
|
A helper method to propagate view-driven changes back to model. |
||||||
|
|
||||||
|
@discussion This method makes it easier to propagate changes from a view |
||||||
|
back to the model without overriding bind:toObject:withKeyPath:options: |
||||||
|
|
||||||
|
@see http://tomdalling.com/blog/cocoa/implementing-your-own-cocoa-bindings/
|
||||||
|
*/ |
||||||
|
- (void)propagateValue:(id)aValue forBinding:(NSString *)aBinding; |
||||||
|
|
||||||
|
@end |
||||||
|
|
||||||
|
|
||||||
|
@protocol SRRecorderControlDelegate <NSObject> |
||||||
|
|
||||||
|
@optional |
||||||
|
|
||||||
|
/*!
|
||||||
|
Asks the delegate if editing should begin in the specified shortcut recorder. |
||||||
|
|
||||||
|
@param aRecorder The shortcut recorder which editing is about to begin. |
||||||
|
|
||||||
|
@result YES if an editing session should be initiated; otherwise, NO to disallow editing. |
||||||
|
|
||||||
|
@discussion Implementation of this method by the delegate is optional. If it is not present, editing proceeds as if this method had returned YES. |
||||||
|
*/ |
||||||
|
- (BOOL)shortcutRecorderShouldBeginRecording:(SRRecorderControl *)aRecorder; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Gives a delegate opportunity to bypass rules specified by allowed and required modifier flags. |
||||||
|
|
||||||
|
@param aRecorder The shortcut recorder for which editing ended. |
||||||
|
|
||||||
|
@param aModifierFlags Proposed modifier flags. |
||||||
|
|
||||||
|
@param aKeyCode Code of the pressed key. |
||||||
|
|
||||||
|
@result YES if recorder should bypass key code with given modifier flags despite settings like required modifier flags, allowed modifier flags. |
||||||
|
|
||||||
|
@discussion Implementation of this method by the delegate is optional. |
||||||
|
Normally, you wouldn't allow a user to record shourcut without modifier flags set: disallow 'a', but allow cmd-'a'. |
||||||
|
However, some keys were designed to be key shortcuts by itself. E.g. Functional keys. By implementing this method a delegate can allow |
||||||
|
these special keys to be set without modifier flags even when the control is configured to disallow empty modifier flags. |
||||||
|
|
||||||
|
@see allowedModifierFlags |
||||||
|
|
||||||
|
@see allowsEmptyModifierFlags |
||||||
|
|
||||||
|
@see requiredModifierFlags |
||||||
|
*/ |
||||||
|
- (BOOL)shortcutRecorder:(SRRecorderControl *)aRecorder shouldUnconditionallyAllowModifierFlags:(NSEventModifierFlags)aModifierFlags forKeyCode:(unsigned short)aKeyCode; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Asks the delegate if the shortcut can be set by the specified shortcut recorder. |
||||||
|
|
||||||
|
@param aRecorder The shortcut recorder which shortcut is beign to be recordered. |
||||||
|
|
||||||
|
@param aShortcut The Shortcut user typed. |
||||||
|
|
||||||
|
@result YES if shortcut can be recordered. Otherwise NO. |
||||||
|
|
||||||
|
@discussion Implementation of this method by the delegate is optional. If it is not present, shortcut is recordered as if this method had returned YES. |
||||||
|
You may implement this method to filter shortcuts that were already set by other recorders. |
||||||
|
|
||||||
|
@see SRValidator |
||||||
|
*/ |
||||||
|
- (BOOL)shortcutRecorder:(SRRecorderControl *)aRecorder canRecordShortcut:(NSDictionary *)aShortcut; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Tells the delegate that editing stopped for the specified shortcut recorder. |
||||||
|
|
||||||
|
@param aRecorder The shortcut recorder for which editing ended. |
||||||
|
|
||||||
|
@discussion Implementation of this method by the delegate is optional. |
||||||
|
*/ |
||||||
|
- (void)shortcutRecorderDidEndRecording:(SRRecorderControl *)aRecorder; |
||||||
|
|
||||||
|
@end |
||||||
|
|
||||||
|
|
||||||
|
FOUNDATION_STATIC_INLINE BOOL SRShortcutEqualToShortcut(NSDictionary *a, NSDictionary *b) |
||||||
|
{ |
||||||
|
if (a == b) |
||||||
|
return YES; |
||||||
|
else if (a && !b) |
||||||
|
return NO; |
||||||
|
else if (!a && b) |
||||||
|
return NO; |
||||||
|
else |
||||||
|
return ([a[SRShortcutKeyCode] isEqual:b[SRShortcutKeyCode]] && [a[SRShortcutModifierFlagsKey] isEqual:b[SRShortcutModifierFlagsKey]]); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
FOUNDATION_STATIC_INLINE NSDictionary *SRShortcutWithCocoaModifierFlagsAndKeyCode(NSEventModifierFlags aModifierFlags, unsigned short aKeyCode) |
||||||
|
{ |
||||||
|
return @{SRShortcutKeyCode: @(aKeyCode), SRShortcutModifierFlagsKey: @(aModifierFlags)}; |
||||||
|
} |
@ -0,0 +1,131 @@ |
|||||||
|
//
|
||||||
|
// SRValidator.h
|
||||||
|
// ShortcutRecorder
|
||||||
|
//
|
||||||
|
// Copyright 2006-2012 Contributors. All rights reserved.
|
||||||
|
//
|
||||||
|
// License: BSD
|
||||||
|
//
|
||||||
|
// Contributors:
|
||||||
|
// David Dauer
|
||||||
|
// Jesper
|
||||||
|
// Jamie Kirkpatrick
|
||||||
|
// Andy Kim
|
||||||
|
// Silvio Rizzi
|
||||||
|
// Ilya Kulakov
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h> |
||||||
|
|
||||||
|
|
||||||
|
@protocol SRValidatorDelegate; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Implements logic to check whether shortcut is taken by other parts of the application and system. |
||||||
|
*/ |
||||||
|
@interface SRValidator : NSObject |
||||||
|
|
||||||
|
@property (assign) NSObject<SRValidatorDelegate> *delegate; |
||||||
|
|
||||||
|
- (instancetype)initWithDelegate:(NSObject<SRValidatorDelegate> *)aDelegate; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether shortcut is taken. |
||||||
|
|
||||||
|
@discussion Key is checked in the following order: |
||||||
|
1. If delegate implements shortcutValidator:isKeyCode:andFlagsTaken:reason: |
||||||
|
2. If delegate allows system-wide shortcuts are checked |
||||||
|
3. If delegate allows application menu it checked |
||||||
|
|
||||||
|
@see SRValidatorDelegate |
||||||
|
*/ |
||||||
|
- (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagsTaken:(NSEventModifierFlags)aFlags error:(NSError **)outError; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether shortcut is taken in delegate. |
||||||
|
|
||||||
|
@discussion If delegate does not implement appropriate method, returns immediately. |
||||||
|
*/ |
||||||
|
- (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagTakenInDelegate:(NSEventModifierFlags)aFlags error:(NSError **)outError; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether shortcut is taken by system-wide shortcuts. |
||||||
|
|
||||||
|
@discussion Does not check whether delegate allows or disallows checking in system shortcuts. |
||||||
|
*/ |
||||||
|
- (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagsTakenInSystemShortcuts:(NSEventModifierFlags)aFlags error:(NSError **)outError; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Determines whether shortcut is taken by application menu item. |
||||||
|
|
||||||
|
@discussion Does not check whether delegate allows or disallows checking in application menu. |
||||||
|
*/ |
||||||
|
- (BOOL)isKeyCode:(unsigned short)aKeyCode andFlags:(NSEventModifierFlags)aFlags takenInMenu:(NSMenu *)aMenu error:(NSError **)outError; |
||||||
|
|
||||||
|
@end |
||||||
|
|
||||||
|
|
||||||
|
@protocol SRValidatorDelegate |
||||||
|
|
||||||
|
@optional |
||||||
|
|
||||||
|
/*!
|
||||||
|
Asks the delegate if aKeyCode and aFlags are valid. |
||||||
|
|
||||||
|
@param aValidator The validator that validates key code and flags. |
||||||
|
|
||||||
|
@param aKeyCode Key code to validate. |
||||||
|
|
||||||
|
@param aFlags Flags to validate. |
||||||
|
|
||||||
|
@param outReason If delegate decides that shortcut is invalid, it may pass here an error message. |
||||||
|
|
||||||
|
@result YES if shortcut is valid. Otherwise NO. |
||||||
|
|
||||||
|
@discussion Implementation of this method by the delegate is optional. If it is not present, checking proceeds as if this method had returned YES. |
||||||
|
*/ |
||||||
|
- (BOOL)shortcutValidator:(SRValidator *)aValidator isKeyCode:(unsigned short)aKeyCode andFlagsTaken:(NSEventModifierFlags)aFlags reason:(NSString **)outReason; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Asks the delegate whether validator should check key equivalents of app's menu items. |
||||||
|
|
||||||
|
@param aValidator The validator that going to check app's menu items. |
||||||
|
|
||||||
|
@result YES if validator should check key equivalents of app's menu items. Otherwise NO. |
||||||
|
|
||||||
|
@discussion Implementation of this method by the delegate is optional. If it is not present, checking proceeds as if this method had returned YES. |
||||||
|
*/ |
||||||
|
- (BOOL)shortcutValidatorShouldCheckMenu:(SRValidator *)aValidator; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Asks the delegate whether it should check system shortcuts. |
||||||
|
|
||||||
|
@param aValidator The validator that going to check system shortcuts. |
||||||
|
|
||||||
|
@result YES if validator should check system shortcuts. Otherwise NO. |
||||||
|
|
||||||
|
@discussion Implementation of this method by the delegate is optional. If it is not present, checking proceeds as if this method had returned YES. |
||||||
|
*/ |
||||||
|
- (BOOL)shortcutValidatorShouldCheckSystemShortcuts:(SRValidator *)aValidator; |
||||||
|
|
||||||
|
/*!
|
||||||
|
Asks the delegate whether it should use ASCII representation of key code when making error messages. |
||||||
|
|
||||||
|
@param aValidator The validator that is about to make an error message. |
||||||
|
|
||||||
|
@result YES if validator should use ASCII representation. Otherwise NO. |
||||||
|
|
||||||
|
@discussion Implementation of this method by the delegate is optional. If it is not present, ASCII representation of key code is used. |
||||||
|
*/ |
||||||
|
- (BOOL)shortcutValidatorShouldUseASCIIStringForKeyCodes:(SRValidator *)aValidator; |
||||||
|
|
||||||
|
@end |
||||||
|
|
||||||
|
|
||||||
|
@interface NSMenuItem (SRValidator) |
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns full path to the menu item. E.g. "Window ➝ Zoom" |
||||||
|
*/ |
||||||
|
- (NSString *)SR_path; |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,233 @@ |
|||||||
|
// |
||||||
|
// SRValidator.h |
||||||
|
// ShortcutRecorder |
||||||
|
// |
||||||
|
// Copyright 2006-2012 Contributors. All rights reserved. |
||||||
|
// |
||||||
|
// License: BSD |
||||||
|
// |
||||||
|
// Contributors: |
||||||
|
// David Dauer |
||||||
|
// Jesper |
||||||
|
// Jamie Kirkpatrick |
||||||
|
// Andy Kim |
||||||
|
// Silvio Rizzi |
||||||
|
// Ilya Kulakov |
||||||
|
|
||||||
|
#import "SRValidator.h" |
||||||
|
#import "SRCommon.h" |
||||||
|
#import "SRKeyCodeTransformer.h" |
||||||
|
|
||||||
|
|
||||||
|
@implementation SRValidator |
||||||
|
|
||||||
|
- (instancetype)initWithDelegate:(NSObject<SRValidatorDelegate> *)aDelegate; |
||||||
|
{ |
||||||
|
self = [super init]; |
||||||
|
|
||||||
|
if (self) |
||||||
|
{ |
||||||
|
_delegate = aDelegate; |
||||||
|
} |
||||||
|
|
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
- (instancetype)init |
||||||
|
{ |
||||||
|
return [self initWithDelegate:nil]; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#pragma mark Methods |
||||||
|
|
||||||
|
- (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagsTaken:(NSEventModifierFlags)aFlags error:(NSError **)outError; |
||||||
|
{ |
||||||
|
if ([self isKeyCode:aKeyCode andFlagTakenInDelegate:aFlags error:outError]) |
||||||
|
return YES; |
||||||
|
|
||||||
|
if ((![self.delegate respondsToSelector:@selector(shortcutValidatorShouldCheckSystemShortcuts:)] || |
||||||
|
[self.delegate shortcutValidatorShouldCheckSystemShortcuts:self]) && |
||||||
|
[self isKeyCode:aKeyCode andFlagsTakenInSystemShortcuts:aFlags error:outError]) |
||||||
|
{ |
||||||
|
return YES; |
||||||
|
} |
||||||
|
|
||||||
|
if ((![self.delegate respondsToSelector:@selector(shortcutValidatorShouldCheckMenu:)] || |
||||||
|
[self.delegate shortcutValidatorShouldCheckMenu:self]) && |
||||||
|
[self isKeyCode:aKeyCode andFlags:aFlags takenInMenu:[NSApp mainMenu] error:outError]) |
||||||
|
{ |
||||||
|
return YES; |
||||||
|
} |
||||||
|
|
||||||
|
return NO; |
||||||
|
} |
||||||
|
|
||||||
|
- (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagTakenInDelegate:(NSEventModifierFlags)aFlags error:(NSError **)outError |
||||||
|
{ |
||||||
|
if (self.delegate) |
||||||
|
{ |
||||||
|
NSString *delegateReason = nil; |
||||||
|
if ([self.delegate respondsToSelector:@selector(shortcutValidator:isKeyCode:andFlagsTaken:reason:)] && |
||||||
|
[self.delegate shortcutValidator:self |
||||||
|
isKeyCode:aKeyCode |
||||||
|
andFlagsTaken:aFlags |
||||||
|
reason:&delegateReason]) |
||||||
|
{ |
||||||
|
if (outError) |
||||||
|
{ |
||||||
|
BOOL isASCIIOnly = YES; |
||||||
|
|
||||||
|
if ([self.delegate respondsToSelector:@selector(shortcutValidatorShouldUseASCIIStringForKeyCodes:)]) |
||||||
|
isASCIIOnly = [self.delegate shortcutValidatorShouldUseASCIIStringForKeyCodes:self]; |
||||||
|
|
||||||
|
NSString *shortcut = isASCIIOnly ? SRReadableASCIIStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode) : SRReadableStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode); |
||||||
|
NSString *failureReason = [NSString stringWithFormat: |
||||||
|
SRLoc(@"The key combination \"%@\" can't be used!"), |
||||||
|
shortcut]; |
||||||
|
NSString *description = [NSString stringWithFormat: |
||||||
|
SRLoc(@"The key combination \"%@\" can't be used because %@."), |
||||||
|
shortcut, |
||||||
|
[delegateReason length] ? delegateReason : @"it's already used"]; |
||||||
|
NSDictionary *userInfo = @{ |
||||||
|
NSLocalizedFailureReasonErrorKey : failureReason, |
||||||
|
NSLocalizedDescriptionKey: description |
||||||
|
}; |
||||||
|
*outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:userInfo]; |
||||||
|
} |
||||||
|
|
||||||
|
return YES; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return NO; |
||||||
|
} |
||||||
|
|
||||||
|
- (BOOL)isKeyCode:(unsigned short)aKeyCode andFlagsTakenInSystemShortcuts:(NSEventModifierFlags)aFlags error:(NSError **)outError |
||||||
|
{ |
||||||
|
CFArrayRef s = NULL; |
||||||
|
OSStatus err = CopySymbolicHotKeys(&s); |
||||||
|
|
||||||
|
if (err != noErr) |
||||||
|
return YES; |
||||||
|
|
||||||
|
NSArray *symbolicHotKeys = (NSArray *)CFBridgingRelease(s); |
||||||
|
aFlags &= SRCocoaModifierFlagsMask; |
||||||
|
|
||||||
|
for (NSDictionary *symbolicHotKey in symbolicHotKeys) |
||||||
|
{ |
||||||
|
if ((__bridge CFBooleanRef)symbolicHotKey[(__bridge NSString *)kHISymbolicHotKeyEnabled] != kCFBooleanTrue) |
||||||
|
continue; |
||||||
|
|
||||||
|
unsigned short symbolicHotKeyCode = [symbolicHotKey[(__bridge NSString *)kHISymbolicHotKeyCode] integerValue]; |
||||||
|
|
||||||
|
if (symbolicHotKeyCode == aKeyCode) |
||||||
|
{ |
||||||
|
UInt32 symbolicHotKeyFlags = [symbolicHotKey[(__bridge NSString *)kHISymbolicHotKeyModifiers] unsignedIntValue]; |
||||||
|
symbolicHotKeyFlags &= SRCarbonModifierFlagsMask; |
||||||
|
|
||||||
|
if (SRCarbonToCocoaFlags(symbolicHotKeyFlags) == aFlags) |
||||||
|
{ |
||||||
|
if (outError) |
||||||
|
{ |
||||||
|
BOOL isASCIIOnly = YES; |
||||||
|
|
||||||
|
if ([self.delegate respondsToSelector:@selector(shortcutValidatorShouldUseASCIIStringForKeyCodes:)]) |
||||||
|
isASCIIOnly = [self.delegate shortcutValidatorShouldUseASCIIStringForKeyCodes:self]; |
||||||
|
|
||||||
|
NSString *shortcut = isASCIIOnly ? SRReadableASCIIStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode) : SRReadableStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode); |
||||||
|
NSString *failureReason = [NSString stringWithFormat: |
||||||
|
SRLoc(@"The key combination \"%@\" can't be used!"), |
||||||
|
shortcut]; |
||||||
|
NSString *description = [NSString stringWithFormat: |
||||||
|
SRLoc(@"The key combination \"%@\" can't be used because it's already used by a system-wide keyboard shortcut. If you really want to use this key combination, most shortcuts can be changed in the Keyboard panel in System Preferences."), |
||||||
|
shortcut]; |
||||||
|
NSDictionary *userInfo = @{ |
||||||
|
NSLocalizedFailureReasonErrorKey: failureReason, |
||||||
|
NSLocalizedDescriptionKey: description |
||||||
|
}; |
||||||
|
*outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:userInfo]; |
||||||
|
} |
||||||
|
|
||||||
|
return YES; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return NO; |
||||||
|
} |
||||||
|
|
||||||
|
- (BOOL)isKeyCode:(unsigned short)aKeyCode andFlags:(NSEventModifierFlags)aFlags takenInMenu:(NSMenu *)aMenu error:(NSError **)outError |
||||||
|
{ |
||||||
|
aFlags &= SRCocoaModifierFlagsMask; |
||||||
|
|
||||||
|
for (NSMenuItem *menuItem in [aMenu itemArray]) |
||||||
|
{ |
||||||
|
if (menuItem.hasSubmenu && [self isKeyCode:aKeyCode andFlags:aFlags takenInMenu:menuItem.submenu error:outError]) |
||||||
|
return YES; |
||||||
|
|
||||||
|
NSString *keyEquivalent = menuItem.keyEquivalent; |
||||||
|
|
||||||
|
if (![keyEquivalent length]) |
||||||
|
continue; |
||||||
|
|
||||||
|
NSEventModifierFlags keyEquivalentModifierMask = menuItem.keyEquivalentModifierMask; |
||||||
|
|
||||||
|
if (SRKeyCodeWithFlagsEqualToKeyEquivalentWithFlags(aKeyCode, aFlags, keyEquivalent, keyEquivalentModifierMask)) |
||||||
|
{ |
||||||
|
if (outError) |
||||||
|
{ |
||||||
|
BOOL isASCIIOnly = YES; |
||||||
|
|
||||||
|
if ([self.delegate respondsToSelector:@selector(shortcutValidatorShouldUseASCIIStringForKeyCodes:)]) |
||||||
|
isASCIIOnly = [self.delegate shortcutValidatorShouldUseASCIIStringForKeyCodes:self]; |
||||||
|
|
||||||
|
NSString *shortcut = isASCIIOnly ? SRReadableASCIIStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode) : SRReadableStringForCocoaModifierFlagsAndKeyCode(aFlags, aKeyCode); |
||||||
|
NSString *failureReason = [NSString stringWithFormat:SRLoc(@"The key combination \"%@\" can't be used!"), shortcut]; |
||||||
|
NSString *description = [NSString stringWithFormat:SRLoc(@"The key combination \"%@\" can't be used because it's already used by the menu item \"%@\"."), shortcut, menuItem.SR_path]; |
||||||
|
NSDictionary *userInfo = @{ |
||||||
|
NSLocalizedFailureReasonErrorKey: failureReason, |
||||||
|
NSLocalizedDescriptionKey: description |
||||||
|
}; |
||||||
|
*outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:userInfo]; |
||||||
|
} |
||||||
|
|
||||||
|
return YES; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return NO; |
||||||
|
} |
||||||
|
|
||||||
|
@end |
||||||
|
|
||||||
|
|
||||||
|
@implementation NSMenuItem (SRValidator) |
||||||
|
|
||||||
|
- (NSString *)SR_path |
||||||
|
{ |
||||||
|
NSMutableArray *items = [NSMutableArray array]; |
||||||
|
static const NSUInteger Limit = 1000; |
||||||
|
NSMenuItem *currentMenuItem = self; |
||||||
|
NSUInteger i = 0; |
||||||
|
|
||||||
|
do |
||||||
|
{ |
||||||
|
[items insertObject:currentMenuItem atIndex:0]; |
||||||
|
currentMenuItem = currentMenuItem.parentItem; |
||||||
|
++i; |
||||||
|
} |
||||||
|
while (currentMenuItem && i < Limit); |
||||||
|
|
||||||
|
NSMutableString *path = [NSMutableString string]; |
||||||
|
|
||||||
|
for (NSMenuItem *menuItem in items) |
||||||
|
[path appendFormat:@"%@➝", menuItem.title]; |
||||||
|
|
||||||
|
if ([path length] > 1) |
||||||
|
[path deleteCharactersInRange:NSMakeRange([path length] - 1, 1)]; |
||||||
|
|
||||||
|
return path; |
||||||
|
} |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,54 @@ |
|||||||
|
//
|
||||||
|
// ShortcutRecorder.h
|
||||||
|
// ShortcutRecorder
|
||||||
|
// Copyright 2012 Contributors. All rights reserved.
|
||||||
|
//
|
||||||
|
// License: BSD
|
||||||
|
//
|
||||||
|
// Contributors to this file:
|
||||||
|
// Jesper
|
||||||
|
// Ilya Kulakov
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h> |
||||||
|
#import <ShortcutRecorder/SRCommon.h> |
||||||
|
#import <ShortcutRecorder/SRKeyCodeTransformer.h> |
||||||
|
#import <ShortcutRecorder/SRModifierFlagsTransformer.h> |
||||||
|
#import <ShortcutRecorder/SRKeyEquivalentTransformer.h> |
||||||
|
#import <ShortcutRecorder/SRKeyEquivalentModifierMaskTransformer.h> |
||||||
|
#import <ShortcutRecorder/SRValidator.h> |
||||||
|
#import <ShortcutRecorder/SRRecorderControl.h> |
||||||
|
|
||||||
|
|
||||||
|
#ifndef IBInspectable |
||||||
|
#define IBInspectable |
||||||
|
#endif |
||||||
|
|
||||||
|
#ifndef IB_DESIGNABLE |
||||||
|
#define IB_DESIGNABLE |
||||||
|
#endif |
||||||
|
|
||||||
|
#ifndef NSAppKitVersionNumber10_6 |
||||||
|
#define NSAppKitVersionNumber10_6 1038 |
||||||
|
#endif |
||||||
|
|
||||||
|
#ifndef NSAppKitVersionNumber10_9 |
||||||
|
#define NSAppKitVersionNumber10_9 1265 |
||||||
|
#endif |
||||||
|
|
||||||
|
#ifndef NSEDGEINSETS_DEFINED |
||||||
|
typedef struct NSEdgeInsets { |
||||||
|
CGFloat top; |
||||||
|
CGFloat left; |
||||||
|
CGFloat bottom; |
||||||
|
CGFloat right; |
||||||
|
} NSEdgeInsets; |
||||||
|
|
||||||
|
NS_INLINE NSEdgeInsets NSEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right) { |
||||||
|
NSEdgeInsets e; |
||||||
|
e.top = top; |
||||||
|
e.left = left; |
||||||
|
e.bottom = bottom; |
||||||
|
e.right = right; |
||||||
|
return e; |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,22 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<dict> |
||||||
|
<key>CFBundleDevelopmentRegion</key> |
||||||
|
<string>English</string> |
||||||
|
<key>CFBundleExecutable</key> |
||||||
|
<string>${EXECUTABLE_NAME}</string> |
||||||
|
<key>CFBundleIdentifier</key> |
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> |
||||||
|
<key>CFBundleInfoDictionaryVersion</key> |
||||||
|
<string>6.0</string> |
||||||
|
<key>CFBundlePackageType</key> |
||||||
|
<string>FMWK</string> |
||||||
|
<key>CFBundleShortVersionString</key> |
||||||
|
<string>1.5</string> |
||||||
|
<key>CFBundleSignature</key> |
||||||
|
<string>????</string> |
||||||
|
<key>CFBundleVersion</key> |
||||||
|
<string>1.5</string> |
||||||
|
</dict> |
||||||
|
</plist> |
@ -0,0 +1,31 @@ |
|||||||
|
//
|
||||||
|
// PTHotKey+ShortcutRecorder.h
|
||||||
|
// ShortcutRecorder
|
||||||
|
//
|
||||||
|
// Created by Ilya Kulakov on 27.02.11.
|
||||||
|
// Copyright 2011 Wireload. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h> |
||||||
|
#import "PTHotKey.h" |
||||||
|
|
||||||
|
|
||||||
|
@interface PTHotKey (ShortcutRecorder) |
||||||
|
|
||||||
|
+ (PTHotKey *)hotKeyWithIdentifier:(id)anIdentifier |
||||||
|
keyCombo:(NSDictionary *)aKeyCombo |
||||||
|
target:(id)aTarget |
||||||
|
action:(SEL)anAction; |
||||||
|
|
||||||
|
+ (PTHotKey *)hotKeyWithIdentifier:(id)anIdentifier |
||||||
|
keyCombo:(NSDictionary *)aKeyCombo |
||||||
|
target:(id)aTarget |
||||||
|
action:(SEL)anAction |
||||||
|
keyUpAction:(SEL)aKeyUpAction; |
||||||
|
|
||||||
|
+ (PTHotKey *)hotKeyWithIdentifier:(id)anIdentifier |
||||||
|
keyCombo:(NSDictionary *)aKeyCombo |
||||||
|
target:(id)aTarget |
||||||
|
action:(SEL)anAction |
||||||
|
withObject:(id)anObject; |
||||||
|
@end |
@ -0,0 +1,53 @@ |
|||||||
|
// |
||||||
|
// PTHotKey+ShortcutRecorder.m |
||||||
|
// ShortcutRecorder |
||||||
|
// |
||||||
|
// Created by Ilya Kulakov on 27.02.11. |
||||||
|
// Copyright 2011 Wireload. All rights reserved. |
||||||
|
// |
||||||
|
|
||||||
|
#import "PTHotKey+ShortcutRecorder.h" |
||||||
|
#import "SRRecorderControl.h" |
||||||
|
|
||||||
|
|
||||||
|
@implementation PTHotKey (ShortcutRecorder) |
||||||
|
|
||||||
|
+ (PTHotKey *)hotKeyWithIdentifier:(id)anIdentifier |
||||||
|
keyCombo:(NSDictionary *)aKeyCombo |
||||||
|
target:(id)aTarget |
||||||
|
action:(SEL)anAction |
||||||
|
{ |
||||||
|
return [PTHotKey hotKeyWithIdentifier:anIdentifier keyCombo:aKeyCombo target:aTarget action:anAction withObject:nil]; |
||||||
|
} |
||||||
|
|
||||||
|
+ (PTHotKey *)hotKeyWithIdentifier:(id)anIdentifier |
||||||
|
keyCombo:(NSDictionary *)aKeyCombo |
||||||
|
target:(id)aTarget |
||||||
|
action:(SEL)anAction |
||||||
|
withObject:(id)anObject |
||||||
|
{ |
||||||
|
NSInteger keyCode = [[aKeyCombo objectForKey:@"keyCode"] integerValue]; |
||||||
|
NSUInteger modifiers = SRCocoaToCarbonFlags([[aKeyCombo objectForKey:@"modifierFlags"] unsignedIntegerValue]); |
||||||
|
PTKeyCombo *newKeyCombo = [[PTKeyCombo alloc] initWithKeyCode:keyCode modifiers:modifiers]; |
||||||
|
PTHotKey *newHotKey = [[PTHotKey alloc] initWithIdentifier:anIdentifier keyCombo:newKeyCombo]; |
||||||
|
[newHotKey setTarget:aTarget]; |
||||||
|
[newHotKey setAction:anAction]; |
||||||
|
[newHotKey setObject:anObject]; |
||||||
|
return newHotKey; |
||||||
|
} |
||||||
|
|
||||||
|
+ (PTHotKey *)hotKeyWithIdentifier:(id)anIdentifier |
||||||
|
keyCombo:(NSDictionary *)aKeyCombo |
||||||
|
target:(id)aTarget |
||||||
|
action:(SEL)anAction |
||||||
|
keyUpAction:(SEL)aKeyUpAction |
||||||
|
{ |
||||||
|
PTHotKey *newHotKey = [PTHotKey hotKeyWithIdentifier:anIdentifier |
||||||
|
keyCombo:aKeyCombo |
||||||
|
target:aTarget |
||||||
|
action:anAction]; |
||||||
|
[newHotKey setKeyUpAction:aKeyUpAction]; |
||||||
|
return newHotKey; |
||||||
|
} |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,60 @@ |
|||||||
|
//
|
||||||
|
// PTHotKey.h
|
||||||
|
// Protein
|
||||||
|
//
|
||||||
|
// Created by Quentin Carnicelli on Sat Aug 02 2003.
|
||||||
|
// Copyright (c) 2003 Quentin D. Carnicelli. All rights reserved.
|
||||||
|
//
|
||||||
|
// Contributors:
|
||||||
|
// Andy Kim
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h> |
||||||
|
#import <Carbon/Carbon.h> |
||||||
|
#import "PTKeyCombo.h" |
||||||
|
|
||||||
|
@interface PTHotKey : NSObject |
||||||
|
{ |
||||||
|
NSString* mIdentifier; |
||||||
|
NSString* mName; |
||||||
|
PTKeyCombo* mKeyCombo; |
||||||
|
id mTarget; |
||||||
|
id mObject; |
||||||
|
SEL mAction; |
||||||
|
SEL mKeyUpAction; |
||||||
|
|
||||||
|
UInt32 mCarbonHotKeyID; |
||||||
|
EventHotKeyRef mCarbonEventHotKeyRef; |
||||||
|
} |
||||||
|
|
||||||
|
- (id)initWithIdentifier: (id)identifier keyCombo: (PTKeyCombo*)combo; |
||||||
|
- (id)initWithIdentifier: (id)identifier keyCombo: (PTKeyCombo*)combo withObject: (id)object; |
||||||
|
- (id)init; |
||||||
|
|
||||||
|
- (void)setIdentifier: (id)ident; |
||||||
|
- (id)identifier; |
||||||
|
|
||||||
|
- (void)setName: (NSString*)name; |
||||||
|
- (NSString*)name; |
||||||
|
|
||||||
|
- (void)setKeyCombo: (PTKeyCombo*)combo; |
||||||
|
- (PTKeyCombo*)keyCombo; |
||||||
|
|
||||||
|
- (void)setTarget: (id)target; |
||||||
|
- (id)target; |
||||||
|
- (void)setObject: (id)object; |
||||||
|
- (id)object; |
||||||
|
- (void)setAction: (SEL)action; |
||||||
|
- (SEL)action; |
||||||
|
- (void)setKeyUpAction: (SEL)action; |
||||||
|
- (SEL)keyUpAction; |
||||||
|
|
||||||
|
- (UInt32)carbonHotKeyID; |
||||||
|
- (void)setCarbonHotKeyID: (UInt32)hotKeyID; |
||||||
|
|
||||||
|
- (EventHotKeyRef)carbonEventHotKeyRef; |
||||||
|
- (void)setCarbonEventHotKeyRef:(EventHotKeyRef)hotKeyRef; |
||||||
|
|
||||||
|
- (void)invoke; |
||||||
|
- (void)uninvoke; |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,156 @@ |
|||||||
|
// |
||||||
|
// PTHotKey.m |
||||||
|
// Protein |
||||||
|
// |
||||||
|
// Created by Quentin Carnicelli on Sat Aug 02 2003. |
||||||
|
// Copyright (c) 2003 Quentin D. Carnicelli. All rights reserved. |
||||||
|
// |
||||||
|
|
||||||
|
#import "PTHotKey.h" |
||||||
|
|
||||||
|
#import "PTHotKeyCenter.h" |
||||||
|
#import "PTKeyCombo.h" |
||||||
|
|
||||||
|
@implementation PTHotKey |
||||||
|
|
||||||
|
- (id)init |
||||||
|
{ |
||||||
|
return [self initWithIdentifier: nil keyCombo: nil withObject:nil]; |
||||||
|
} |
||||||
|
|
||||||
|
- (id)initWithIdentifier: (id)identifier keyCombo: (PTKeyCombo*)combo |
||||||
|
{ |
||||||
|
return [self initWithIdentifier: identifier keyCombo: combo withObject:nil]; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
- (id)initWithIdentifier: (id)identifier keyCombo: (PTKeyCombo*)combo withObject: (id)object |
||||||
|
{ |
||||||
|
self = [super init]; |
||||||
|
|
||||||
|
if( self ) |
||||||
|
{ |
||||||
|
[self setIdentifier: identifier]; |
||||||
|
[self setKeyCombo: combo]; |
||||||
|
[self setObject: object]; |
||||||
|
} |
||||||
|
|
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
- (NSString*)description |
||||||
|
{ |
||||||
|
return [NSString stringWithFormat: @"<%@: %@, %@>", NSStringFromClass( [self class] ), [self identifier], [self keyCombo]]; |
||||||
|
} |
||||||
|
|
||||||
|
#pragma mark - |
||||||
|
|
||||||
|
- (void)setIdentifier: (id)ident |
||||||
|
{ |
||||||
|
mIdentifier = ident; |
||||||
|
} |
||||||
|
|
||||||
|
- (id)identifier |
||||||
|
{ |
||||||
|
return mIdentifier; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)setKeyCombo: (PTKeyCombo*)combo |
||||||
|
{ |
||||||
|
if( combo == nil ) |
||||||
|
combo = [PTKeyCombo clearKeyCombo]; |
||||||
|
|
||||||
|
mKeyCombo = combo; |
||||||
|
} |
||||||
|
|
||||||
|
- (PTKeyCombo*)keyCombo |
||||||
|
{ |
||||||
|
return mKeyCombo; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)setName: (NSString*)name |
||||||
|
{ |
||||||
|
mName = name; |
||||||
|
} |
||||||
|
|
||||||
|
- (NSString*)name |
||||||
|
{ |
||||||
|
return mName; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)setTarget: (id)target |
||||||
|
{ |
||||||
|
mTarget = target; |
||||||
|
} |
||||||
|
|
||||||
|
- (id)target |
||||||
|
{ |
||||||
|
return mTarget; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)setObject:(id)object |
||||||
|
{ |
||||||
|
mObject = object; |
||||||
|
} |
||||||
|
|
||||||
|
- (id)object |
||||||
|
{ |
||||||
|
return mObject; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)setAction: (SEL)action |
||||||
|
{ |
||||||
|
mAction = action; |
||||||
|
} |
||||||
|
|
||||||
|
- (SEL)action |
||||||
|
{ |
||||||
|
return mAction; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)setKeyUpAction: (SEL)action |
||||||
|
{ |
||||||
|
mKeyUpAction = action; |
||||||
|
} |
||||||
|
|
||||||
|
- (SEL)keyUpAction |
||||||
|
{ |
||||||
|
return mKeyUpAction; |
||||||
|
} |
||||||
|
|
||||||
|
- (UInt32)carbonHotKeyID |
||||||
|
{ |
||||||
|
return mCarbonHotKeyID; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)setCarbonHotKeyID: (UInt32)hotKeyID; |
||||||
|
{ |
||||||
|
mCarbonHotKeyID = hotKeyID; |
||||||
|
} |
||||||
|
|
||||||
|
- (EventHotKeyRef)carbonEventHotKeyRef |
||||||
|
{ |
||||||
|
return mCarbonEventHotKeyRef; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)setCarbonEventHotKeyRef: (EventHotKeyRef)hotKeyRef |
||||||
|
{ |
||||||
|
mCarbonEventHotKeyRef = hotKeyRef; |
||||||
|
} |
||||||
|
|
||||||
|
#pragma mark - |
||||||
|
|
||||||
|
- (void)invoke |
||||||
|
{ |
||||||
|
if(mAction) |
||||||
|
[NSApp sendAction:mAction to:mTarget from:self]; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)uninvoke |
||||||
|
{ |
||||||
|
if(mKeyUpAction) |
||||||
|
[NSApp sendAction:mKeyUpAction to:mTarget from:self]; |
||||||
|
} |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,44 @@ |
|||||||
|
//
|
||||||
|
// PTHotKeyCenter.h
|
||||||
|
// Protein
|
||||||
|
//
|
||||||
|
// Created by Quentin Carnicelli on Sat Aug 02 2003.
|
||||||
|
// Copyright (c) 2003 Quentin D. Carnicelli. All rights reserved.
|
||||||
|
//
|
||||||
|
// Contributors:
|
||||||
|
// Quentin D. Carnicelli
|
||||||
|
// Finlay Dobbie
|
||||||
|
// Vincent Pottier
|
||||||
|
// Andy Kim
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h> |
||||||
|
#import <Carbon/Carbon.h> |
||||||
|
|
||||||
|
@class PTHotKey; |
||||||
|
|
||||||
|
@interface PTHotKeyCenter : NSObject |
||||||
|
{ |
||||||
|
NSMutableDictionary* mHotKeys; //Keys are carbon hot key IDs
|
||||||
|
BOOL mEventHandlerInstalled; |
||||||
|
UInt32 mHotKeyCount; // Used to assign new hot key ID
|
||||||
|
BOOL mIsPaused; |
||||||
|
EventHandlerRef mEventHandler; |
||||||
|
} |
||||||
|
|
||||||
|
+ (PTHotKeyCenter *)sharedCenter; |
||||||
|
|
||||||
|
- (BOOL)registerHotKey: (PTHotKey*)hotKey; |
||||||
|
- (void)unregisterHotKey: (PTHotKey*)hotKey; |
||||||
|
|
||||||
|
- (NSArray*)allHotKeys; |
||||||
|
- (PTHotKey*)hotKeyWithIdentifier: (id)ident; |
||||||
|
|
||||||
|
- (void)sendEvent: (NSEvent*)event; |
||||||
|
|
||||||
|
- (void)pause; |
||||||
|
|
||||||
|
- (void)resume; |
||||||
|
|
||||||
|
- (BOOL)isPaused; |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,334 @@ |
|||||||
|
// |
||||||
|
// PTHotKeyCenter.m |
||||||
|
// Protein |
||||||
|
// |
||||||
|
// Created by Quentin Carnicelli on Sat Aug 02 2003. |
||||||
|
// Copyright (c) 2003 Quentin D. Carnicelli. All rights reserved. |
||||||
|
// |
||||||
|
|
||||||
|
#import "PTHotKeyCenter.h" |
||||||
|
#import "PTHotKey.h" |
||||||
|
#import "PTKeyCombo.h" |
||||||
|
|
||||||
|
@interface PTHotKeyCenter (Private) |
||||||
|
- (PTHotKey*)_hotKeyForCarbonHotKey: (EventHotKeyRef)carbonHotKey; |
||||||
|
- (PTHotKey*)_hotKeyForCarbonHotKeyID: (EventHotKeyID)hotKeyID; |
||||||
|
|
||||||
|
- (void)_updateEventHandler; |
||||||
|
- (void)_hotKeyDown: (PTHotKey*)hotKey; |
||||||
|
- (void)_hotKeyUp: (PTHotKey*)hotKey; |
||||||
|
static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* refCon ); |
||||||
|
@end |
||||||
|
|
||||||
|
@implementation PTHotKeyCenter |
||||||
|
|
||||||
|
static PTHotKeyCenter *_sharedHotKeyCenter = nil; |
||||||
|
|
||||||
|
+ (PTHotKeyCenter*)sharedCenter |
||||||
|
{ |
||||||
|
if( _sharedHotKeyCenter == nil ) |
||||||
|
{ |
||||||
|
_sharedHotKeyCenter = [[self alloc] init]; |
||||||
|
} |
||||||
|
|
||||||
|
return _sharedHotKeyCenter; |
||||||
|
} |
||||||
|
|
||||||
|
- (id)init |
||||||
|
{ |
||||||
|
self = [super init]; |
||||||
|
|
||||||
|
if( self ) |
||||||
|
{ |
||||||
|
mHotKeys = [[NSMutableDictionary alloc] init]; |
||||||
|
} |
||||||
|
|
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - |
||||||
|
|
||||||
|
- (BOOL)registerHotKey: (PTHotKey*)hotKey |
||||||
|
{ |
||||||
|
if ( mIsPaused == NO ) |
||||||
|
{ |
||||||
|
OSStatus err; |
||||||
|
EventHotKeyID hotKeyID; |
||||||
|
EventHotKeyRef carbonHotKey; |
||||||
|
|
||||||
|
if( [[self allHotKeys] containsObject: hotKey] == YES ) |
||||||
|
[self unregisterHotKey: hotKey]; |
||||||
|
|
||||||
|
if( [[hotKey keyCombo] isValidHotKeyCombo] == NO ) |
||||||
|
return YES; |
||||||
|
|
||||||
|
hotKeyID.signature = 'PTHk'; |
||||||
|
hotKeyID.id = ++mHotKeyCount; |
||||||
|
|
||||||
|
err = RegisterEventHotKey( (SInt32)[[hotKey keyCombo] keyCode], |
||||||
|
(UInt32)[[hotKey keyCombo] modifiers], |
||||||
|
hotKeyID, |
||||||
|
GetEventDispatcherTarget(), |
||||||
|
0, |
||||||
|
&carbonHotKey ); |
||||||
|
|
||||||
|
if( err ) |
||||||
|
return NO; |
||||||
|
|
||||||
|
[hotKey setCarbonHotKeyID:hotKeyID.id]; |
||||||
|
[hotKey setCarbonEventHotKeyRef:carbonHotKey]; |
||||||
|
|
||||||
|
if( hotKey ) |
||||||
|
[mHotKeys setObject: hotKey forKey: [NSNumber numberWithInteger:hotKeyID.id]]; |
||||||
|
|
||||||
|
[self _updateEventHandler]; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
EventHotKeyID hotKeyID = {'PTHk', ++mHotKeyCount}; |
||||||
|
[hotKey setCarbonHotKeyID:hotKeyID.id]; |
||||||
|
|
||||||
|
if( hotKey ) |
||||||
|
[mHotKeys setObject: hotKey forKey: [NSNumber numberWithInteger:hotKeyID.id]]; |
||||||
|
} |
||||||
|
return YES; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)unregisterHotKey: (PTHotKey*)hotKey |
||||||
|
{ |
||||||
|
if ( mIsPaused == NO ) |
||||||
|
{ |
||||||
|
EventHotKeyRef carbonHotKey; |
||||||
|
|
||||||
|
if( [[self allHotKeys] containsObject: hotKey] == NO ) |
||||||
|
return; |
||||||
|
|
||||||
|
carbonHotKey = [hotKey carbonEventHotKeyRef]; |
||||||
|
|
||||||
|
if( carbonHotKey ) |
||||||
|
{ |
||||||
|
UnregisterEventHotKey( carbonHotKey ); |
||||||
|
//Watch as we ignore 'err': |
||||||
|
|
||||||
|
[mHotKeys removeObjectForKey: [NSNumber numberWithInteger:[hotKey carbonHotKeyID]]]; |
||||||
|
|
||||||
|
[hotKey setCarbonHotKeyID:0]; |
||||||
|
[hotKey setCarbonEventHotKeyRef:NULL]; |
||||||
|
|
||||||
|
[self _updateEventHandler]; |
||||||
|
|
||||||
|
//See that? Completely ignored |
||||||
|
} |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
[mHotKeys removeObjectForKey: [NSNumber numberWithInteger:[hotKey carbonHotKeyID]]]; |
||||||
|
|
||||||
|
[hotKey setCarbonHotKeyID:0]; |
||||||
|
[hotKey setCarbonEventHotKeyRef:NULL]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
- (NSArray*)allHotKeys |
||||||
|
{ |
||||||
|
return [mHotKeys allValues]; |
||||||
|
} |
||||||
|
|
||||||
|
- (PTHotKey*)hotKeyWithIdentifier: (id)ident |
||||||
|
{ |
||||||
|
NSEnumerator* hotKeysEnum = [[self allHotKeys] objectEnumerator]; |
||||||
|
PTHotKey* hotKey; |
||||||
|
|
||||||
|
if( !ident ) |
||||||
|
return nil; |
||||||
|
|
||||||
|
while( (hotKey = [hotKeysEnum nextObject]) != nil ) |
||||||
|
{ |
||||||
|
if( [[hotKey identifier] isEqual: ident] ) |
||||||
|
return hotKey; |
||||||
|
} |
||||||
|
|
||||||
|
return nil; |
||||||
|
} |
||||||
|
|
||||||
|
#pragma mark - |
||||||
|
|
||||||
|
- (PTHotKey*)_hotKeyForCarbonHotKey: (EventHotKeyRef)carbonHotKeyRef |
||||||
|
{ |
||||||
|
NSEnumerator *e = [mHotKeys objectEnumerator]; |
||||||
|
PTHotKey *hotkey = nil; |
||||||
|
|
||||||
|
while( (hotkey = [e nextObject]) ) |
||||||
|
{ |
||||||
|
if( [hotkey carbonEventHotKeyRef] == carbonHotKeyRef ) |
||||||
|
return hotkey; |
||||||
|
} |
||||||
|
|
||||||
|
return nil; |
||||||
|
} |
||||||
|
|
||||||
|
- (PTHotKey*)_hotKeyForCarbonHotKeyID: (EventHotKeyID)hotKeyID |
||||||
|
{ |
||||||
|
return [mHotKeys objectForKey:[NSNumber numberWithInteger:hotKeyID.id]]; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)_updateEventHandler |
||||||
|
{ |
||||||
|
if( [mHotKeys count] && mEventHandlerInstalled == NO ) |
||||||
|
{ |
||||||
|
EventTypeSpec eventSpec[2] = { |
||||||
|
{ kEventClassKeyboard, kEventHotKeyPressed }, |
||||||
|
{ kEventClassKeyboard, kEventHotKeyReleased } |
||||||
|
}; |
||||||
|
|
||||||
|
InstallEventHandler( GetEventDispatcherTarget(), |
||||||
|
(EventHandlerProcPtr)hotKeyEventHandler, |
||||||
|
2, eventSpec, nil, &mEventHandler); |
||||||
|
|
||||||
|
mEventHandlerInstalled = YES; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
- (void)_hotKeyDown: (PTHotKey*)hotKey |
||||||
|
{ |
||||||
|
[hotKey invoke]; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)_hotKeyUp: (PTHotKey*)hotKey |
||||||
|
{ |
||||||
|
[hotKey uninvoke]; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)sendEvent: (NSEvent*)event |
||||||
|
{ |
||||||
|
// Not sure why this is needed? - Andy Kim (Aug 23, 2009) |
||||||
|
|
||||||
|
short subType; |
||||||
|
EventHotKeyRef carbonHotKey; |
||||||
|
|
||||||
|
if( [event type] == NSSystemDefined ) |
||||||
|
{ |
||||||
|
subType = [event subtype]; |
||||||
|
|
||||||
|
if( subType == 6 ) //6 is hot key down |
||||||
|
{ |
||||||
|
carbonHotKey= (EventHotKeyRef)[event data1]; //data1 is our hot key ref |
||||||
|
if( carbonHotKey != nil ) |
||||||
|
{ |
||||||
|
PTHotKey* hotKey = [self _hotKeyForCarbonHotKey: carbonHotKey]; |
||||||
|
[self _hotKeyDown: hotKey]; |
||||||
|
} |
||||||
|
} |
||||||
|
else if( subType == 9 ) //9 is hot key up |
||||||
|
{ |
||||||
|
carbonHotKey= (EventHotKeyRef)[event data1]; |
||||||
|
if( carbonHotKey != nil ) |
||||||
|
{ |
||||||
|
PTHotKey* hotKey = [self _hotKeyForCarbonHotKey: carbonHotKey]; |
||||||
|
[self _hotKeyUp: hotKey]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
- (OSStatus)sendCarbonEvent: (EventRef)event |
||||||
|
{ |
||||||
|
OSStatus err; |
||||||
|
EventHotKeyID hotKeyID; |
||||||
|
PTHotKey* hotKey; |
||||||
|
|
||||||
|
NSAssert( GetEventClass( event ) == kEventClassKeyboard, @"Unknown event class" ); |
||||||
|
|
||||||
|
err = GetEventParameter( event, |
||||||
|
kEventParamDirectObject, |
||||||
|
typeEventHotKeyID, |
||||||
|
nil, |
||||||
|
sizeof(EventHotKeyID), |
||||||
|
nil, |
||||||
|
&hotKeyID ); |
||||||
|
if( err ) |
||||||
|
return err; |
||||||
|
|
||||||
|
if( hotKeyID.signature != 'PTHk' ) |
||||||
|
return eventNotHandledErr; |
||||||
|
|
||||||
|
if (hotKeyID.id == 0) |
||||||
|
return eventNotHandledErr; |
||||||
|
|
||||||
|
hotKey = [self _hotKeyForCarbonHotKeyID:hotKeyID]; |
||||||
|
|
||||||
|
switch( GetEventKind( event ) ) |
||||||
|
{ |
||||||
|
case kEventHotKeyPressed: |
||||||
|
[self _hotKeyDown: hotKey]; |
||||||
|
break; |
||||||
|
|
||||||
|
case kEventHotKeyReleased: |
||||||
|
[self _hotKeyUp: hotKey]; |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
return eventNotHandledErr; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
return noErr; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)pause |
||||||
|
{ |
||||||
|
if ( mIsPaused ) |
||||||
|
return; |
||||||
|
|
||||||
|
mIsPaused = YES; |
||||||
|
for (NSNumber *hotKeyID in mHotKeys) |
||||||
|
{ |
||||||
|
PTHotKey *hotKey = [mHotKeys objectForKey:hotKeyID]; |
||||||
|
EventHotKeyRef carbonHotKey = [hotKey carbonEventHotKeyRef]; |
||||||
|
UnregisterEventHotKey( carbonHotKey ); |
||||||
|
[hotKey setCarbonEventHotKeyRef:NULL]; |
||||||
|
} |
||||||
|
if (mEventHandler != NULL) |
||||||
|
{ |
||||||
|
RemoveEventHandler(mEventHandler); |
||||||
|
mEventHandler = NULL; |
||||||
|
mEventHandlerInstalled = NO; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
- (void)resume |
||||||
|
{ |
||||||
|
if ( mIsPaused == NO) |
||||||
|
return; |
||||||
|
|
||||||
|
mIsPaused = NO; |
||||||
|
for (NSNumber *hotKeyIDNumber in mHotKeys) |
||||||
|
{ |
||||||
|
PTHotKey *hotKey = [mHotKeys objectForKey:hotKeyIDNumber]; |
||||||
|
EventHotKeyRef carbonHotKey = NULL; |
||||||
|
EventHotKeyID hotKeyID; |
||||||
|
hotKeyID.signature = 'PTHk'; |
||||||
|
hotKeyID.id = [hotKey carbonHotKeyID]; |
||||||
|
RegisterEventHotKey( (SInt32)[[hotKey keyCombo] keyCode], |
||||||
|
(UInt32)[[hotKey keyCombo] modifiers], |
||||||
|
hotKeyID, |
||||||
|
GetEventDispatcherTarget(), |
||||||
|
0, |
||||||
|
&carbonHotKey ); |
||||||
|
[hotKey setCarbonEventHotKeyRef:carbonHotKey]; |
||||||
|
} |
||||||
|
[self _updateEventHandler]; |
||||||
|
} |
||||||
|
|
||||||
|
- (BOOL)isPaused |
||||||
|
{ |
||||||
|
return mIsPaused; |
||||||
|
} |
||||||
|
|
||||||
|
static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* refCon ) |
||||||
|
{ |
||||||
|
return [[PTHotKeyCenter sharedCenter] sendCarbonEvent: inEvent]; |
||||||
|
} |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,26 @@ |
|||||||
|
//
|
||||||
|
// PTKeyCodeTranslator.h
|
||||||
|
// Chercher
|
||||||
|
//
|
||||||
|
// Created by Finlay Dobbie on Sat Oct 11 2003.
|
||||||
|
// Copyright (c) 2003 Cliché Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Carbon/Carbon.h> |
||||||
|
|
||||||
|
@interface PTKeyCodeTranslator : NSObject |
||||||
|
{ |
||||||
|
TISInputSourceRef keyboardLayout; |
||||||
|
const UCKeyboardLayout *uchrData; |
||||||
|
UInt32 keyTranslateState; |
||||||
|
UInt32 deadKeyState; |
||||||
|
} |
||||||
|
|
||||||
|
+ (id)currentTranslator; |
||||||
|
|
||||||
|
- (id)initWithKeyboardLayout:(TISInputSourceRef)aLayout; |
||||||
|
- (NSString *)translateKeyCode:(short)keyCode; |
||||||
|
|
||||||
|
- (TISInputSourceRef)keyboardLayout; |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,71 @@ |
|||||||
|
// |
||||||
|
// PTKeyCodeTranslator.m |
||||||
|
// Chercher |
||||||
|
// |
||||||
|
// Created by Finlay Dobbie on Sat Oct 11 2003. |
||||||
|
// Copyright (c) 2003 Cliché Software. All rights reserved. |
||||||
|
// |
||||||
|
|
||||||
|
#import "PTKeyCodeTranslator.h" |
||||||
|
|
||||||
|
|
||||||
|
@implementation PTKeyCodeTranslator |
||||||
|
|
||||||
|
+ (id)currentTranslator |
||||||
|
{ |
||||||
|
static PTKeyCodeTranslator *current = nil; |
||||||
|
TISInputSourceRef currentLayout = TISCopyCurrentKeyboardLayoutInputSource(); |
||||||
|
|
||||||
|
if (current == nil) { |
||||||
|
current = [[PTKeyCodeTranslator alloc] initWithKeyboardLayout:currentLayout]; |
||||||
|
} else if ([current keyboardLayout] != currentLayout) { |
||||||
|
current = [[PTKeyCodeTranslator alloc] initWithKeyboardLayout:currentLayout]; |
||||||
|
} |
||||||
|
|
||||||
|
CFRelease(currentLayout); |
||||||
|
|
||||||
|
return current; |
||||||
|
} |
||||||
|
|
||||||
|
- (id)initWithKeyboardLayout:(TISInputSourceRef)aLayout |
||||||
|
{ |
||||||
|
if ((self = [super init]) != nil) { |
||||||
|
keyboardLayout = aLayout; |
||||||
|
|
||||||
|
CFRetain(keyboardLayout); |
||||||
|
|
||||||
|
CFDataRef uchr = TISGetInputSourceProperty( keyboardLayout , kTISPropertyUnicodeKeyLayoutData ); |
||||||
|
uchrData = ( const UCKeyboardLayout* )CFDataGetBytePtr(uchr); |
||||||
|
} |
||||||
|
|
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)dealloc |
||||||
|
{ |
||||||
|
|
||||||
|
CFRelease(keyboardLayout); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
- (NSString *)translateKeyCode:(short)keyCode { |
||||||
|
UniCharCount maxStringLength = 4, actualStringLength; |
||||||
|
UniChar unicodeString[4]; |
||||||
|
UCKeyTranslate( uchrData, keyCode, kUCKeyActionDisplay, 0, LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, maxStringLength, &actualStringLength, unicodeString ); |
||||||
|
return [NSString stringWithCharacters:unicodeString length:1]; |
||||||
|
} |
||||||
|
|
||||||
|
- (TISInputSourceRef)keyboardLayout { |
||||||
|
return keyboardLayout; |
||||||
|
} |
||||||
|
|
||||||
|
- (NSString *)description { |
||||||
|
NSString *kind; |
||||||
|
kind = @"uchr"; |
||||||
|
|
||||||
|
NSString *layoutName; |
||||||
|
layoutName = (__bridge NSString *)(TISGetInputSourceProperty( keyboardLayout, kTISPropertyLocalizedName )); |
||||||
|
return [NSString stringWithFormat:@"PTKeyCodeTranslator layout=%@ (%@)", layoutName, kind]; |
||||||
|
} |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,38 @@ |
|||||||
|
//
|
||||||
|
// PTKeyCombo.h
|
||||||
|
// Protein
|
||||||
|
//
|
||||||
|
// Created by Quentin Carnicelli on Sat Aug 02 2003.
|
||||||
|
// Copyright (c) 2003 Quentin D. Carnicelli. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h> |
||||||
|
|
||||||
|
@interface PTKeyCombo : NSObject <NSCopying> |
||||||
|
{ |
||||||
|
NSInteger mKeyCode; |
||||||
|
NSUInteger mModifiers; |
||||||
|
} |
||||||
|
|
||||||
|
+ (id)clearKeyCombo; |
||||||
|
+ (id)keyComboWithKeyCode: (NSInteger)keyCode modifiers: (NSUInteger)modifiers; |
||||||
|
- (id)initWithKeyCode: (NSInteger)keyCode modifiers: (NSUInteger)modifiers; |
||||||
|
|
||||||
|
- (id)initWithPlistRepresentation: (id)plist; |
||||||
|
- (id)plistRepresentation; |
||||||
|
|
||||||
|
- (BOOL)isEqual: (PTKeyCombo*)combo; |
||||||
|
|
||||||
|
- (NSInteger)keyCode; |
||||||
|
- (NSUInteger)modifiers; |
||||||
|
|
||||||
|
- (BOOL)isClearCombo; |
||||||
|
- (BOOL)isValidHotKeyCombo; |
||||||
|
|
||||||
|
@end |
||||||
|
|
||||||
|
|
||||||
|
@interface PTKeyCombo (UserDisplayAdditions) |
||||||
|
- (NSString*)keyCodeString; |
||||||
|
- (NSUInteger)modifierMask; |
||||||
|
@end |
@ -0,0 +1,127 @@ |
|||||||
|
// |
||||||
|
// PTKeyCombo.m |
||||||
|
// Protein |
||||||
|
// |
||||||
|
// Created by Quentin Carnicelli on Sat Aug 02 2003. |
||||||
|
// Copyright (c) 2003 Quentin D. Carnicelli. All rights reserved. |
||||||
|
// |
||||||
|
|
||||||
|
#import "PTKeyCombo.h" |
||||||
|
#import "PTKeyCodeTranslator.h" |
||||||
|
|
||||||
|
@implementation PTKeyCombo |
||||||
|
|
||||||
|
+ (id)clearKeyCombo |
||||||
|
{ |
||||||
|
return [self keyComboWithKeyCode: -1 modifiers: -1]; |
||||||
|
} |
||||||
|
|
||||||
|
+ (id)keyComboWithKeyCode: (NSInteger)keyCode modifiers: (NSUInteger)modifiers |
||||||
|
{ |
||||||
|
return [[self alloc] initWithKeyCode: keyCode modifiers: modifiers]; |
||||||
|
} |
||||||
|
|
||||||
|
- (id)initWithKeyCode: (NSInteger)keyCode modifiers: (NSUInteger)modifiers |
||||||
|
{ |
||||||
|
self = [super init]; |
||||||
|
|
||||||
|
if( self ) |
||||||
|
{ |
||||||
|
switch ( keyCode ) |
||||||
|
{ |
||||||
|
case kVK_F1: |
||||||
|
case kVK_F2: |
||||||
|
case kVK_F3: |
||||||
|
case kVK_F4: |
||||||
|
case kVK_F5: |
||||||
|
case kVK_F6: |
||||||
|
case kVK_F7: |
||||||
|
case kVK_F8: |
||||||
|
case kVK_F9: |
||||||
|
case kVK_F10: |
||||||
|
case kVK_F11: |
||||||
|
case kVK_F12: |
||||||
|
case kVK_F13: |
||||||
|
case kVK_F14: |
||||||
|
case kVK_F15: |
||||||
|
case kVK_F16: |
||||||
|
case kVK_F17: |
||||||
|
case kVK_F18: |
||||||
|
case kVK_F19: |
||||||
|
case kVK_F20: |
||||||
|
mModifiers = modifiers | NSFunctionKeyMask; |
||||||
|
break; |
||||||
|
default: |
||||||
|
mModifiers = modifiers; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
mKeyCode = keyCode; |
||||||
|
} |
||||||
|
|
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
- (id)initWithPlistRepresentation: (id)plist |
||||||
|
{ |
||||||
|
int keyCode, modifiers; |
||||||
|
|
||||||
|
if( !plist || ![plist count] ) |
||||||
|
{ |
||||||
|
keyCode = -1; |
||||||
|
modifiers = -1; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
keyCode = [[plist objectForKey: @"keyCode"] intValue]; |
||||||
|
if( keyCode < 0 ) keyCode = -1; |
||||||
|
|
||||||
|
modifiers = [[plist objectForKey: @"modifiers"] intValue]; |
||||||
|
if( modifiers <= 0 ) modifiers = -1; |
||||||
|
} |
||||||
|
|
||||||
|
return [self initWithKeyCode: keyCode modifiers: modifiers]; |
||||||
|
} |
||||||
|
|
||||||
|
- (id)plistRepresentation |
||||||
|
{ |
||||||
|
return [NSDictionary dictionaryWithObjectsAndKeys: |
||||||
|
[NSNumber numberWithInteger: [self keyCode]], @"keyCode", |
||||||
|
[NSNumber numberWithInteger: [self modifiers]], @"modifiers", |
||||||
|
nil]; |
||||||
|
} |
||||||
|
|
||||||
|
- (id)copyWithZone:(NSZone*)zone; |
||||||
|
{ |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
- (BOOL)isEqual: (PTKeyCombo*)combo |
||||||
|
{ |
||||||
|
return [self keyCode] == [combo keyCode] && |
||||||
|
[self modifiers] == [combo modifiers]; |
||||||
|
} |
||||||
|
|
||||||
|
#pragma mark - |
||||||
|
|
||||||
|
- (NSInteger)keyCode |
||||||
|
{ |
||||||
|
return mKeyCode; |
||||||
|
} |
||||||
|
|
||||||
|
- (NSUInteger)modifiers |
||||||
|
{ |
||||||
|
return mModifiers; |
||||||
|
} |
||||||
|
|
||||||
|
- (BOOL)isValidHotKeyCombo |
||||||
|
{ |
||||||
|
return mKeyCode >= 0 && mModifiers > 0; |
||||||
|
} |
||||||
|
|
||||||
|
- (BOOL)isClearCombo |
||||||
|
{ |
||||||
|
return mKeyCode == -1 && mModifiers == 0; |
||||||
|
} |
||||||
|
|
||||||
|
@end |
@ -0,0 +1,112 @@ |
|||||||
|
ShortcutRecorder 2 |
||||||
|
==================== |
||||||
|
![pre-Yosemite ShortcutRecorder Preview](Demo/example.png) |
||||||
|
![Yosemite ShortcutRecorder Preview](Demo/example-yosemite.png) |
||||||
|
|
||||||
|
The only user interface control to record shortcuts. For Mac OS X 10.6+, 64bit. |
||||||
|
|
||||||
|
- :microscope: Support for Xcode 6 Quick Help |
||||||
|
- :microscope: Support for Xcode 6 Interface Builder integration |
||||||
|
- Fresh Look & Feel (brought to you by [Wireload](http://wireload.net) and [John Wells](https://github.com/jwells89)) |
||||||
|
- With Retina support |
||||||
|
- Auto Layout ready |
||||||
|
- Correct drawing on Layer-backed and Layer-hosted views |
||||||
|
- Accessibility for people with disabilities |
||||||
|
- Revised codebase with Automatic Reference Counting support |
||||||
|
- Translated into 24 languages |
||||||
|
|
||||||
|
Includes framework to set global shortcuts (PTHotKey). |
||||||
|
|
||||||
|
Get Sources |
||||||
|
----------- |
||||||
|
The preferred way to add the ShortcutRecorder to your project is to use git submodules: |
||||||
|
`git submodule add git://github.com/Kentzo/ShortcutRecorder.git` |
||||||
|
You can download sources from the site as well. |
||||||
|
|
||||||
|
Integrate into your project |
||||||
|
--------------------------- |
||||||
|
First, add ShortcutRecorder.xcodeproj to your workspace via Xcode ([Apple docs](https://developer.apple.com/library/mac/recipes/xcode_help-structure_navigator/articles/Adding_an_Existing_Project_to_a_Workspace.html)). Don't have a workspace? No problem, just add ShortcutRecorder.xcodeproj via the "Add Files to" dialog. |
||||||
|
|
||||||
|
Next step is to ensure your target is linked against the ShortcutRecorder or/and PTHotKey frameworks ([Apple docs](http://developer.apple.com/library/ios/#recipes/xcode_help-project_editor/Articles/AddingaLibrarytoaTarget.html#//apple_ref/doc/uid/TP40010155-CH17)). Desired frameworks will be listed under *Workspace*. |
||||||
|
|
||||||
|
Now it's time to make frameworks part of your app. To do this, you need to add custom Build Phase ([Apple docs](http://developer.apple.com/library/ios/#recipes/xcode_help-project_editor/Articles/CreatingaCopyFilesBuildPhase.html)). Remember to set *Destination* to *Frameworks* and clean up *Subpath*. |
||||||
|
|
||||||
|
Finally, ensure your app will find frameworks upon start. Open Build Settings of your target, look up *Runtime Search Paths*. Add `@executable_path/../Frameworks` to the list of paths. |
||||||
|
|
||||||
|
Add control in Interface Builder |
||||||
|
-------------------------------- |
||||||
|
Since Xcode 4 Apple removed Interface Builder Plugins. You can only use it to add and position/resize ShortcutRecorder control. To do this, add Custom View and set its class to SRRecorderControl. |
||||||
|
|
||||||
|
SRRecorderControl has fixed height of 25 points so ensure you do not use autoresizing masks/layout rules which allows vertical resizing. I recommend you to pin height in case you're using Auto Layout. |
||||||
|
|
||||||
|
Usage |
||||||
|
----- |
||||||
|
First, we want to keep value of the control across relaunches of the app. We can simply achieve this by using NSUserDefaultsController and bindings: |
||||||
|
|
||||||
|
[self.pingShortcutRecorder bind:NSValueBinding |
||||||
|
toObject:[NSUserDefaultsController sharedUserDefaultsController] |
||||||
|
withKeyPath:@"values.ping" |
||||||
|
options:nil]; |
||||||
|
|
||||||
|
The value can be used to set key equivalent of NSMenuItem or NSButton. It can also be used to register a global shortcut. |
||||||
|
|
||||||
|
Setting key equivalent of NSMenuItem using bindings: |
||||||
|
|
||||||
|
[self.pingItem bind:@"keyEquivalent" |
||||||
|
toObject:defaults |
||||||
|
withKeyPath:@"values.ping" |
||||||
|
options:@{NSValueTransformerBindingOption: [SRKeyEquivalentTransformer new]}]; |
||||||
|
[self.pingItem bind:@"keyEquivalentModifierMask" |
||||||
|
toObject:defaults |
||||||
|
withKeyPath:@"values.ping" |
||||||
|
options:@{NSValueTransformerBindingOption: [SRKeyEquivalentModifierMaskTransformer new]}]; |
||||||
|
|
||||||
|
Setting key equivalent of NSButton using bindings: |
||||||
|
|
||||||
|
[self.pingButton bind:@"keyEquivalent" |
||||||
|
toObject:defaults |
||||||
|
withKeyPath:@"values.ping" |
||||||
|
options:@{NSValueTransformerBindingOption: [SRKeyEquivalentTransformer new]}]; |
||||||
|
[self.pingButton bind:@"keyEquivalentModifierMask" |
||||||
|
toObject:defaults |
||||||
|
withKeyPath:@"values.ping" |
||||||
|
options:@{NSValueTransformerBindingOption: [SRKeyEquivalentModifierMaskTransformer new]}]; |
||||||
|
|
||||||
|
Setting global shortcut using PTHotKeyCenter: |
||||||
|
|
||||||
|
PTHotKeyCenter *hotKeyCenter = [PTHotKeyCenter sharedCenter]; |
||||||
|
PTHotKey *oldHotKey = [hotKeyCenter hotKeyWithIdentifier:aKeyPath]; |
||||||
|
[hotKeyCenter unregisterHotKey:oldHotKey]; |
||||||
|
|
||||||
|
PTHotKey *newHotKey = [PTHotKey hotKeyWithIdentifier:aKeyPath |
||||||
|
keyCombo:newShortcut |
||||||
|
target:self |
||||||
|
action:@selector(ping:)]; |
||||||
|
[hotKeyCenter registerHotKey:newHotKey]; |
||||||
|
|
||||||
|
Key Equivalents and Keyboard Layout |
||||||
|
---------------------------------------------------- |
||||||
|
While ShortcutRecorder keeps your shortcuts as combination of *key code* and modifier masks, key equivalents are expressed using *key character* and modifier mask. The difference is that position of key code on keyboard does not depend on current keyboard layout while position of key character does. |
||||||
|
|
||||||
|
ShortcutRecorder includes two special transformers to simplify binding to the key equivalents of NSMenuItem and NSButton: |
||||||
|
|
||||||
|
- SRKeyEquivalentTransformer |
||||||
|
- SRKeyEquivalentModifierMaskTransformer |
||||||
|
|
||||||
|
SRKeyEquivalentTransformer uses ASCII keyboard layout to convert key code into character, therefore resulting character does not depend on keyboard layout. |
||||||
|
The drawback is that position of the character on keyboard may change depending on layout and used modifier keys (primarly Option and Shift). |
||||||
|
|
||||||
|
NSButton |
||||||
|
-------- |
||||||
|
If you're going to bind ShortcutRecorder to key equivalent of NSButton, I encourage you to require `NSCommandKeyMask`. |
||||||
|
This is because NSButton handles key equivalents very strange. Rather than investigating full information of the keyboard event, it just asks for `charactersIgnoringModifiers` |
||||||
|
and compares returned value with its `keyEquivalent`. Unfortunately, Cocoa returns layout-independent (ASCII) representation of characters only when NSCommandKeyMask is set. |
||||||
|
If it's not set, assigned shortcut likely won't work with other layouts. |
||||||
|
|
||||||
|
Questions |
||||||
|
--------- |
||||||
|
Still have questions? [Create an issue](https://github.com/Kentzo/ShortcutRecorder/issues/new) immediately and feel free to ping me. |
||||||
|
|
||||||
|
Paid Support |
||||||
|
------------ |
||||||
|
If functional you need is missing but you're ready to pay for it, feel free to contact me. If not, create an issue anyway, I'll take a look as soon as I can. |
@ -0,0 +1,40 @@ |
|||||||
|
Copyright (c) 2006, contributors to ShortcutRecorder. (See the contributors listed in detail later in the file, or see <http://wafflesoftware.net/shortcut/contributors/>.) |
||||||
|
|
||||||
|
All rights reserved. |
||||||
|
Redistribution and use in source and binary forms, with or without |
||||||
|
modification, are permitted provided that the following conditions are met: |
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright |
||||||
|
notice, this list of conditions and the following disclaimer. |
||||||
|
* Redistributions in binary form must reproduce the above copyright |
||||||
|
notice, this list of conditions and the following disclaimer in the |
||||||
|
documentation and/or other materials provided with the distribution. |
||||||
|
* The name of the contributors may not be used to endorse or promote |
||||||
|
products derived from this software without specific prior written |
||||||
|
permission. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS "AS IS" AND ANY |
||||||
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY |
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
|
||||||
|
===================================================================== |
||||||
|
|
||||||
|
Contributors to Shortcut Recorder, in no order in particular: |
||||||
|
Jesper, waffle software, <wootest+shortcutrecorder@gmail.com>. Initial idea and concept, first shot at implementation using NSView. |
||||||
|
David Dauer, <david@daviddauer.de>. Refinement, cleaner reimplementation, documentation, IB Palette. |
||||||
|
Jamie Kirkpatrick, Kirk Consulting Ltd, <jkp@kirkconsulting.co.uk>. Further modularisation and re-factoring, and general bug fixes. |
||||||
|
Ilya Kulakov, <kulakov.ilya@gmail.com>. ShortcutRecorder 2.0 and further support. |
||||||
|
Alexander Ljungberg, <aljungberg@wireload.net>. Graphics for ShortcutRecorder 2.0 |
||||||
|
|
||||||
|
===================================================================== |
||||||
|
|
||||||
|
Some rights reserved: <http://creativecommons.org/licenses/by/3.0/> |
||||||
|
|
||||||
|
For more information, visit <http://blog.oofn.net/projects/misc/> |