@ -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/> |