I have an LSUIElement application that displays a menubar status item. The application can display a dialog window that contains a text field.
If the user right-clicks/control-clicks the text field, a menu appears that allows cut, copy, paste, etc. However, the standard Command-X, Command-C, and Command-V keyboard shortcuts do not work in the field. I assume this is because my application does not provide an Edit menu with those shortcuts defined.
I've tried adding an Edit menu item to my application's menu, as suggested in the Ship Some Code blog, but that did not work. The menu items in the Edit menu can be used, but keyboard shortcuts still don't work.
I can imagine a few ways to hack the keyboard handling, but is there a "recommended" way to make this work?
(For details about the app, see Menubar Countdown.)
Related question: Copy/Paste Not Working in Modal Window
Improving on that CocoaRocket solution:
The following saves having to subclass NSTextField and remembering to use the subclass throughout your application; it will also enable copy, paste and friends for other responders that handle them, eg. NSTextView.
Put this in a subclass of NSApplication and alter the principal class in your Info.plist accordingly.
- (void) sendEvent:(NSEvent *)event {
if ([event type] == NSKeyDown) {
if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask) {
if ([[event charactersIgnoringModifiers] isEqualToString:#"x"]) {
if ([self sendAction:#selector(cut:) to:nil from:self])
return;
}
else if ([[event charactersIgnoringModifiers] isEqualToString:#"c"]) {
if ([self sendAction:#selector(copy:) to:nil from:self])
return;
}
else if ([[event charactersIgnoringModifiers] isEqualToString:#"v"]) {
if ([self sendAction:#selector(paste:) to:nil from:self])
return;
}
else if ([[event charactersIgnoringModifiers] isEqualToString:#"z"]) {
if ([self sendAction:#selector(undo:) to:nil from:self])
return;
}
else if ([[event charactersIgnoringModifiers] isEqualToString:#"a"]) {
if ([self sendAction:#selector(selectAll:) to:nil from:self])
return;
}
}
else if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSShiftKeyMask)) {
if ([[event charactersIgnoringModifiers] isEqualToString:#"Z"]) {
if ([self sendAction:#selector(redo:) to:nil from:self])
return;
}
}
}
[super sendEvent:event];
}
// Blank Selectors to silence Xcode warnings: 'Undeclared selector undo:/redo:'
- (IBAction)undo:(id)sender {}
- (IBAction)redo:(id)sender {}
What worked for me was using The View Solution presented in Copy and Paste Keyboard Shortcuts at CocoaRocket.
Basically, this means subclassing NSTextField and overriding performKeyEquivalent:.
Update: The CocoaRocket site is apparently gone. Here's the Internet Archive link: http://web.archive.org/web/20100126000339/http://www.cocoarocket.com/articles/copypaste.html
Edit: The Swift code looks like this
class Editing: NSTextField {
private let commandKey = NSEventModifierFlags.CommandKeyMask.rawValue
private let commandShiftKey = NSEventModifierFlags.CommandKeyMask.rawValue | NSEventModifierFlags.ShiftKeyMask.rawValue
override func performKeyEquivalent(event: NSEvent) -> Bool {
if event.type == NSEventType.KeyDown {
if (event.modifierFlags.rawValue & NSEventModifierFlags.DeviceIndependentModifierFlagsMask.rawValue) == commandKey {
switch event.charactersIgnoringModifiers! {
case "x":
if NSApp.sendAction(Selector("cut:"), to:nil, from:self) { return true }
case "c":
if NSApp.sendAction(Selector("copy:"), to:nil, from:self) { return true }
case "v":
if NSApp.sendAction(Selector("paste:"), to:nil, from:self) { return true }
case "z":
if NSApp.sendAction(Selector("undo:"), to:nil, from:self) { return true }
case "a":
if NSApp.sendAction(Selector("selectAll:"), to:nil, from:self) { return true }
default:
break
}
}
else if (event.modifierFlags.rawValue & NSEventModifierFlags.DeviceIndependentModifierFlagsMask.rawValue) == commandShiftKey {
if event.charactersIgnoringModifiers == "Z" {
if NSApp.sendAction(Selector("redo:"), to:nil, from:self) { return true }
}
}
}
return super.performKeyEquivalent(event)
}
}
Edit: The Swift 3 code looks like this
class Editing: NSTextView {
private let commandKey = NSEventModifierFlags.command.rawValue
private let commandShiftKey = NSEventModifierFlags.command.rawValue | NSEventModifierFlags.shift.rawValue
override func performKeyEquivalent(with event: NSEvent) -> Bool {
if event.type == NSEventType.keyDown {
if (event.modifierFlags.rawValue & NSEventModifierFlags.deviceIndependentFlagsMask.rawValue) == commandKey {
switch event.charactersIgnoringModifiers! {
case "x":
if NSApp.sendAction(#selector(NSText.cut(_:)), to:nil, from:self) { return true }
case "c":
if NSApp.sendAction(#selector(NSText.copy(_:)), to:nil, from:self) { return true }
case "v":
if NSApp.sendAction(#selector(NSText.paste(_:)), to:nil, from:self) { return true }
case "z":
if NSApp.sendAction(Selector(("undo:")), to:nil, from:self) { return true }
case "a":
if NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to:nil, from:self) { return true }
default:
break
}
}
else if (event.modifierFlags.rawValue & NSEventModifierFlags.deviceIndependentFlagsMask.rawValue) == commandShiftKey {
if event.charactersIgnoringModifiers == "Z" {
if NSApp.sendAction(Selector(("redo:")), to:nil, from:self) { return true }
}
}
}
return super.performKeyEquivalent(with: event)
}
}
I had the same problem as you, and I think I've managed to find a simpler solution. You just need to leave the original main menu in MainMenu.xib - it won't be displayed, but all the actions will be handled properly. The trick is that it needs to be the original one, if you just drag a new NSMenu from the library, the app won't recognize it as Main Menu and I have no idea how to mark it as such (if you uncheck LSUIElement, you'll see that it won't show up at the top if it's not the original one). If you've already deleted it, you can create a new sample app and drag a menu from its NIB, that works too.
I've improved Adrian's solution to work when Caps Lock is on as well:
- (void)sendEvent:(NSEvent *)event
{
if (event.type == NSKeyDown)
{
NSString *inputKey = [event.charactersIgnoringModifiers lowercaseString];
if ((event.modifierFlags & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask ||
(event.modifierFlags & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSAlphaShiftKeyMask))
{
if ([inputKey isEqualToString:#"x"])
{
if ([self sendAction:#selector(cut:) to:nil from:self])
return;
}
else if ([inputKey isEqualToString:#"c"])
{
if ([self sendAction:#selector(copy:) to:nil from:self])
return;
}
else if ([inputKey isEqualToString:#"v"])
{
if ([self sendAction:#selector(paste:) to:nil from:self])
return;
}
else if ([inputKey isEqualToString:#"z"])
{
if ([self sendAction:NSSelectorFromString(#"undo:") to:nil from:self])
return;
}
else if ([inputKey isEqualToString:#"a"])
{
if ([self sendAction:#selector(selectAll:) to:nil from:self])
return;
}
}
else if ((event.modifierFlags & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSShiftKeyMask) ||
(event.modifierFlags & NSDeviceIndependentModifierFlagsMask) == (NSCommandKeyMask | NSShiftKeyMask | NSAlphaShiftKeyMask))
{
if ([inputKey isEqualToString:#"z"])
{
if ([self sendAction:NSSelectorFromString(#"redo:") to:nil from:self])
return;
}
}
}
[super sendEvent:event];
}
Thomas Kilian solution in swift 3.
private let commandKey = NSEventModifierFlags.command.rawValue
private let commandShiftKey = NSEventModifierFlags.command.rawValue | NSEventModifierFlags.shift.rawValue
override func performKeyEquivalent(with event: NSEvent) -> Bool {
if event.type == NSEventType.keyDown {
if (event.modifierFlags.rawValue & NSEventModifierFlags.deviceIndependentFlagsMask.rawValue) == commandKey {
switch event.charactersIgnoringModifiers! {
case "x":
if NSApp.sendAction(#selector(NSText.cut(_:)), to:nil, from:self) { return true }
case "c":
if NSApp.sendAction(#selector(NSText.copy(_:)), to:nil, from:self) { return true }
case "v":
if NSApp.sendAction(#selector(NSText.paste(_:)), to:nil, from:self) { return true }
case "z":
if NSApp.sendAction(Selector(("undo:")), to:nil, from:self) { return true }
case "a":
if NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to:nil, from:self) { return true }
default:
break
}
}
else if (event.modifierFlags.rawValue & NSEventModifierFlags.deviceIndependentFlagsMask.rawValue) == commandShiftKey {
if event.charactersIgnoringModifiers == "Z" {
if NSApp.sendAction(Selector(("redo:")), to:nil, from:self) { return true }
}
}
}
return super.performKeyEquivalent(with: event)
}
Xcode10/Swift 4.2 solution:
import Cocoa
extension NSTextView {
override open func performKeyEquivalent(with event: NSEvent) -> Bool {
let commandKey = NSEvent.ModifierFlags.command.rawValue
let commandShiftKey = NSEvent.ModifierFlags.command.rawValue | NSEvent.ModifierFlags.shift.rawValue
if event.type == NSEvent.EventType.keyDown {
if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandKey {
switch event.charactersIgnoringModifiers! {
case "x":
if NSApp.sendAction(#selector(NSText.cut(_:)), to:nil, from:self) { return true }
case "c":
if NSApp.sendAction(#selector(NSText.copy(_:)), to:nil, from:self) { return true }
case "v":
if NSApp.sendAction(#selector(NSText.paste(_:)), to:nil, from:self) { return true }
case "z":
if NSApp.sendAction(Selector(("undo:")), to:nil, from:self) { return true }
case "a":
if NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to:nil, from:self) { return true }
default:
break
}
} else if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandShiftKey {
if event.charactersIgnoringModifiers == "Z" {
if NSApp.sendAction(Selector(("redo:")), to:nil, from:self) { return true }
}
}
}
return super.performKeyEquivalent(with: event)
}
}
Swift 4.2 for Thomas Kilian solution
class MTextField: NSSecureTextField {
private let commandKey = NSEvent.ModifierFlags.command.rawValue
private let commandShiftKey = NSEvent.ModifierFlags.command.rawValue | NSEvent.ModifierFlags.shift.rawValue
override func performKeyEquivalent(with event: NSEvent) -> Bool {
if event.type == NSEvent.EventType.keyDown {
if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandKey {
switch event.charactersIgnoringModifiers! {
case "x":
if NSApp.sendAction(#selector(NSText.cut(_:)), to:nil, from:self) { return true }
case "c":
if NSApp.sendAction(#selector(NSText.copy(_:)), to:nil, from:self) { return true }
case "v":
if NSApp.sendAction(#selector(NSText.paste(_:)), to:nil, from:self) { return true }
case "z":
if NSApp.sendAction(Selector(("undo:")), to:nil, from:self) { return true }
case "a":
if NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to:nil, from:self) { return true }
default:
break
}
}
else if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandShiftKey {
if event.charactersIgnoringModifiers == "Z" {
if NSApp.sendAction(Selector(("redo:")), to:nil, from:self) { return true }
}
}
}
return super.performKeyEquivalent(with: event)
}
}
Here's a quick step-by-step guide for swift, based on the excellent answers by #Adrian, Travis B and Thomas Kilian.
The goal will be to subclass the NSApplication, instead of the NSTextField. Once you have created this class, do link it in your "principal Class" setting of the Info.plist, as stated by Adrian. As opposed to the Objective-C folks, we swiftlers will have to add an additional prefix to the principalClass configuration. So, because my Project is called "Foo", I am going to set "Principal Class" to "Foo.MyApplication". You will get an "Class MyApplication not found" runtime error otherwise.
The contents of MyApplication read as follows (copied and adapted from all the answers given so far)
import Cocoa
class MyApplication: NSApplication {
override func sendEvent(event: NSEvent) {
if event.type == NSEventType.KeyDown {
if (event.modifierFlags & NSEventModifierFlags.DeviceIndependentModifierFlagsMask == NSEventModifierFlags.CommandKeyMask) {
switch event.charactersIgnoringModifiers!.lowercaseString {
case "x":
if NSApp.sendAction(Selector("cut:"), to:nil, from:self) { return }
case "c":
if NSApp.sendAction(Selector("copy:"), to:nil, from:self) { return }
case "v":
if NSApp.sendAction(Selector("paste:"), to:nil, from:self) { return }
case "z":
if NSApp.sendAction(Selector("undo:"), to:nil, from:self) { return }
case "a":
if NSApp.sendAction(Selector("selectAll:"), to:nil, from:self) { return }
default:
break
}
}
else if (event.modifierFlags & NSEventModifierFlags.DeviceIndependentModifierFlagsMask == (NSEventModifierFlags.CommandKeyMask | NSEventModifierFlags.ShiftKeyMask)) {
if event.charactersIgnoringModifiers == "Z" {
if NSApp.sendAction(Selector("redo:"), to:nil, from:self) { return }
}
}
}
return super.sendEvent(event)
}
}
I explain what worked for me in XCode 8 / Swift 3.
I created MyApplication.swift inside my project folder MyApp:
import Foundation
import Cocoa
class MyApplication: NSApplication {
override func sendEvent(_ event: NSEvent) {
if event.type == NSEventType.keyDown {
if (event.modifierFlags.contains(NSEventModifierFlags.command)) {
switch event.charactersIgnoringModifiers!.lowercased() {
case "x":
if NSApp.sendAction(#selector(NSText.cut(_:)), to:nil, from:self) { return }
case "c":
if NSApp.sendAction(#selector(NSText.copy(_:)), to:nil, from:self) { return }
case "v":
if NSApp.sendAction(#selector(NSText.paste(_:)), to:nil, from:self) { return }
case "a":
if NSApp.sendAction(#selector(NSText.selectAll(_:)), to:nil, from:self) { return }
default:
break
}
}
}
return super.sendEvent(event)
}
}
Then change the Info.plist Principal class to MyApp.MyApplication. Build, and run to validate that my text fields and text views have support for Cmd + X, Cmd + C, Cmd + V and Cmd + A.
No need to add a new class, extension or really any code at all.
Just add a new MenuItems in one of your menus and name them 'Copy', 'Cut' and 'Paste'.
Add the correct shortcut keys to each item.
Control+drag to connect them up to the corresponding methods listed under first responder.
The bonus here is that the items are not hidden from your users, and this takes less time that creating a new class and reassigning all of your existing TextFields to it.
Just about 1 hour ago I stumbled upon the same problem.
You don't need to code anything. I could do this in Interface Builder:
Create a menu (e.g. "Edit") which contains your Cut / Copy / Paste menu items
Add the KeyEquivalent for the CMD key to your "Edit" menu (don't know, if this is really needed, I just copied the structure from another project)
Add the KeyEquivalents to these menu items (CMD + X and so on)
Link the FirstResponder's cut:, copy: and paste: functions to your corresponding menu items
That worked for me.
Unfortunately this (default) behavior doesn't seem to work when you hide the "Edit" menu (just tried it).
Here's Travis' answer as C# for use with Xamarin.Mac:
public override bool PerformKeyEquivalent (AppKit.NSEvent e)
{
if (e.Type == NSEventType.KeyDown) {
var inputKey = e.CharactersIgnoringModifiers.ToLower ();
if ( (e.ModifierFlags & NSEventModifierMask.DeviceIndependentModifierFlagsMask) == NSEventModifierMask.CommandKeyMask
|| (e.ModifierFlags & NSEventModifierMask.DeviceIndependentModifierFlagsMask) == (NSEventModifierMask.CommandKeyMask | NSEventModifierMask.AlphaShiftKeyMask)) {
switch (inputKey) {
case "x":
NSApplication.SharedApplication.SendAction (new Selector ("cut:"), null, this);
return true;
case "c":
NSApplication.SharedApplication.SendAction (new Selector ("copy:"), null, this);
return true;
case "v":
NSApplication.SharedApplication.SendAction (new Selector ("paste:"), null, this);
return true;
case "z":
NSApplication.SharedApplication.SendAction (new Selector ("undo:"), null, this);
return true;
case "a":
NSApplication.SharedApplication.SendAction (new Selector ("selectAll:"), null, this);
return true;
}
} else if ( (e.ModifierFlags & NSEventModifierMask.DeviceIndependentModifierFlagsMask) == (NSEventModifierMask.CommandKeyMask | NSEventModifierMask.ShiftKeyMask)
|| (e.ModifierFlags & NSEventModifierMask.DeviceIndependentModifierFlagsMask) == (NSEventModifierMask.CommandKeyMask | NSEventModifierMask.ShiftKeyMask | NSEventModifierMask.AlphaShiftKeyMask)) {
switch (inputKey) {
case "z":
NSApplication.SharedApplication.SendAction (new Selector ("redo:"), null, this);
return true;
}
}
}
return base.PerformKeyEquivalent(e);
}
Based on Thomas Kilian's answer, you can actually create an extension for NSTextField
let commandKey = NSEvent.ModifierFlags.command.rawValue
let commandShiftKey = NSEvent.ModifierFlags.command.rawValue | NSEvent.ModifierFlags.shift.rawValue
extension NSTextField {
func performEditingKeyEquivalent(with event: NSEvent) -> Bool {
guard event.type == NSEvent.EventType.keyDown else { return false }
if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandKey {
if let character = event.charactersIgnoringModifiers {
switch character {
case "x":
if NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: self) { return true }
case "c":
if NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: self) { return true }
case "v":
if NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: self) { return true }
case "z":
if NSApp.sendAction(Selector(("undo:")), to: nil, from: self) { return true }
case "a":
if NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to: nil, from: self) { return true }
default:
break
}
}
} else if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandShiftKey {
if event.charactersIgnoringModifiers == "Z" {
if NSApp.sendAction(Selector(("redo:")), to: nil, from: self) { return true }
}
}
return false
}
}
In the followingg example, one can actually replace NSTextField with any of the NSTextField inheriting classes (e.g NSSearchField, NSSecureTextField) to have the new functionality
class SearchField: NSTextField {
override func performKeyEquivalent(with event: NSEvent) -> Bool {
if performEditingKeyEquivalent(with: event) {
return true
}
return super.performEditingKeyEquivalent(with: event)
}
}
Thank you for this solution! It helped me greatly, so I decided to contribute some code in hope it helps someone else. The suggested solution above worked perfectly after I converted it to Swift 4.2. I then refactored the code a little. I think this is a little cleaner. This is Swift 4.2 compatible:
// NSEventExtensions.swift
import AppKit
extension NSEvent {
func containsKeyModifierFlags(_ flags: NSEvent.ModifierFlags) -> Bool {
switch modifierFlags.intersection(.deviceIndependentFlagsMask) {
case [flags]: return true
default: return false
}
}
}
// SearchFiled.swift
import AppKit
import Carbon
final class SearchField: NSSearchField {
override func performKeyEquivalent(with event: NSEvent) -> Bool {
switch event.type {
case .keyDown: return performKeyDownEquivalent(with: event)
default: return super.performKeyEquivalent(with: event)
}
}
// MARK: - private
private func performKeyDownEquivalent(with event: NSEvent) -> Bool {
if event.containsKeyModifierFlags(.command) {
switch Int(event.keyCode) {
case kVK_ANSI_X: return NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: self)
case kVK_ANSI_C: return NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: self)
case kVK_ANSI_V: return NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: self)
case kVK_ANSI_Z: return NSApp.sendAction(Selector(("undo:")), to: nil, from: self)
case kVK_ANSI_A: return NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to: nil, from: self)
default: break
}
} else if event.containsKeyModifierFlags([.command, .shift]) {
switch Int(event.keyCode) {
case kVK_ANSI_Z: return NSApp.sendAction(Selector(("redo:")), to: nil, from: self)
default: break
}
}
return false
}
}
Swift 5 solution for NSApplication subclass
open override func sendEvent(_ event: NSEvent) {
if event.type == .keyDown {
if event.modifierFlags.contains(.command) && NSEvent.ModifierFlags.deviceIndependentFlagsMask.contains(.command) {
if event.modifierFlags.contains(.shift) && NSEvent.ModifierFlags.deviceIndependentFlagsMask.contains(.shift) {
if event.charactersIgnoringModifiers == "Z" {
if NSApp.sendAction(Selector("redo:"), to:nil, from:self) { return }
}
}
guard let key = event.charactersIgnoringModifiers else { return super.sendEvent(event) }
switch key {
case "x":
if NSApp.sendAction(Selector("cut:"), to:nil, from:self) { return }
case "c":
if NSApp.sendAction(Selector("copy:"), to:nil, from:self) { return }
case "v":
if NSApp.sendAction(Selector("paste:"), to:nil, from:self) { return }
case "z":
if NSApp.sendAction(Selector("undo:"), to:nil, from:self) { return }
case "a":
if NSApp.sendAction(Selector("selectAll:"), to:nil, from:self) { return }
default:
break
}
}
}
super.sendEvent(event)
}
Adrian's solution is good, but better I think to use a switch statement rather than all those string comparisons, e.g.:
uint const modifierCode = (theEvent.modifierFlags & NSDeviceIndependentModifierFlagsMask);
BOOL usingModifiers = ( modifierCode != 0 );
//BOOL const usingShiftKey = ((theEvent.modifierFlags & NSShiftKeyMask) != 0);
//BOOL const usingCommandKey = ((theEvent.modifierFlags & NSCommandKeyMask) != 0);
NSString * ch = [theEvent charactersIgnoringModifiers];
if ( ( usingModifiers ) && ( ch.length == 1 ) ) switch ( [ch characterAtIndex:0] )
{
case 'x':
if ( modifierCode == NSCommandKeyMask ) [m cut]; // <-- m = model
break;
case 'c':
if ( modifierCode == NSCommandKeyMask ) [m copy];
break;
case 'v':
if ( modifierCode == NSCommandKeyMask ) [m paste];
break;
case 'z':
if ( modifierCode == NSCommandKeyMask ) [m undo];
break;
case 'Z':
if ( modifierCode == ( NSCommandKeyMask | NSShiftKeyMask ) ) [m redo];
break;
default: // etc.
break;
}
else switch ( theEvent.keyCode ) // <-- for independent keycodes!
{
case kVK_Home:
[m moveToBeginningOfDocument:nil];
break;
case kVK_End: // etc!
Related
I want to make my code shorter and I'm trying to understand how reflect works. The code below works (the real code has many more structs and are much longer). So shortly the function processInput gets string data from a TCP connection and it is used to populate the Hyperdeck struct.
type configuration struct {
videoInput string `cmd:"video input:"`
audioMapping int64 `cmd:"audio mapping:"`
genlockInputResync bool `cmd:"genlock input resync:"`
}
type Hyperdeck struct {
configuration configuration
}
func (hd *Hyperdeck) processInput(s string) {
switch hd.multilineCmd {
case "configuration":
if s != "\r\n" {
if strings.HasPrefix(s, "video input:") {
hd.configuration.videoInput = strings.TrimSpace(s[strings.Index(s, ":")+1 : len(s)-1])
} else if strings.HasPrefix(s, "audio mapping:") {
hd.configuration.audioMapping = stringToInt(strings.TrimSpace(s[strings.Index(s, ":")+1:len(s)-1]), hd)
} else if strings.HasPrefix(s, "genlock input resync:") {
hd.configuration.genlockInputResync = stringToBool(strings.TrimSpace(s[strings.Index(s, ":")+1:len(s)-1]), hd)
}
} else {
hd.multilineCmd = ""
}
}
switch {
case strings.Contains(strings.ToLower(s), "configuration"):
hd.multilineCmd = "configuration"
}
}
I was thinking of making something like this that loops the stuct and identifies the type but I can't figure out how to update the struct. I've read and tried things from multiple websites but I just can't get it right.
func (hd *Hyperdeck) processInput(s string) {
switch hd.multilineCmd {
case "configuration":
if s != "\r\n" {
v := reflect.ValueOf(hd.configuration)
t := v.Type()
for i := 0; i < v.NumField(); i++ {
if strings.HasPrefix(s, t.Field(i).Tag.Get("cmd")) {
if t.Field(i).Type.String() == "string" {
// UPDATE hd.configuration.videoInput
} else if t.Field(i).Type.String() == "int" {
//UPDATE hd.configuration.audioMapping
} else if t.Field(i).Type.String() == "bool" {
//UPDATE hd.configuration.genlockInputResync
}
}
}
} else {
hd.multilineCmd = ""
}
}
switch {
case strings.Contains(strings.ToLower(s), "configuration"):
hd.multilineCmd = "configuration"
}
}
I think this code is usable for strings but can't figure out how to address it...
...FieldByName(types.Field(i).Name).SetString(strings.TrimSpace(s[strings.Index(s, ":")+1 : len(s)-1]))
And maybe the most important question, should I do it with reflect or leave it as it is now?
Where to call NotificationCenter.default.addObserver() ? in my Xcode Game Project
I successfully call the following from my func application (AppDelegate), but when I toggle the Gamepad on/off, my selectors are not being called.
class GameScene: SKScene {
func ObserveForGameControllers() {
// print("ObserveForGameControllers")
NotificationCenter.default.addObserver(
self,
selector: #selector(connectControllers),
name: NSNotification.Name.GCControllerDidConnect,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(disconnectControllers),
name: NSNotification.Name.GCControllerDidDisconnect,
object: nil)
} // ObserveForGameControllers
}
My selectors look like this:
#objc func connectControllers() {}
#objc func disconnectControllers() {}
One last thing:
Here are my Gamepad settings in my Project
It seems I really need some suggestions here.
Appreciate it.
EDIT
I have been in contact with a very talented jrturton on trying to discover why I am unable to detect the presence of my Gamepad as documented above.
He has asked for a more complete presentation of my Swift code. I initially thought of Dropbox, but he has asked for this EDIT .. so here goes:
I began with a iOS Game Project which presented me with AppDelegate, GameScene, GameViewController + Storyboard.
I’ve already covered AppDelegate above, which per jrturton’s recommendation is now reduced to the standard AppDelegate func’s which essentially are empty, such as:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// currently empty
}
Next, the GameScene ..
import SwiftUI
import WebKit
import SpriteKit
import GameplayKit
import GameController
class GameScene: SKScene {
override func sceneDidLoad() {
super.sceneDidLoad()
// print("sceneDidLoad")
ObserveForGameControllers()
} // sceneDidLoad
func ObserveForGameControllers() {
// print("ObserveForGameControllers")
NotificationCenter.default.addObserver(
self,
selector: #selector(connectControllers),
name: NSNotification.Name.GCControllerDidConnect,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(disconnectControllers),
name: NSNotification.Name.GCControllerDidDisconnect,
object: nil)
} // ObserveForGameControllers
#objc func connectControllers() {
// print("CONNECT")
self.isPaused = false
var indexNumber = 0
for controller in GCController.controllers() {
if controller.extendedGamepad != nil {
controller.playerIndex = GCControllerPlayerIndex.init(rawValue: indexNumber)!
indexNumber += 1
setupControllerControls(controller: controller)
}
}
} // connectControllers
#objc func disconnectControllers() {
// print("DIS-CONNECT")
self.isPaused = true
} // disconnectControllers
func setupControllerControls(controller: GCController) {
controller.extendedGamepad?.valueChangedHandler = {
(gamepad: GCExtendedGamepad, element: GCControllerElement) in
self.controllerInputDetected(gamepad: gamepad,
element: element,
index: controller.playerIndex.rawValue)
}
} // setupControllerControls
func controllerInputDetected(gamepad: GCExtendedGamepad,
element: GCControllerElement,
index: Int) {
// A-Button
if (gamepad.buttonA == element)
{
if (gamepad.buttonA.value != 0)
{
// These print(..) statements will be replaced later
// by code to access my Javascript methods.
print("movePaddleDown")
}
}
// B-Button
else if (gamepad.buttonB == element)
{
if (gamepad.buttonB.value != 0)
{
print("movePaddleRight")
}
}
// Y-Button
else if (gamepad.buttonY == element)
{
if (gamepad.buttonY.value != 0)
{
print("movePaddleUp")
}
}
// X-Button
else if (gamepad.buttonX == element)
{
if (gamepad.buttonX.value != 0)
{
print("movePaddleLeft")
}
}
// leftShoulder
else if (gamepad.leftShoulder == element)
{
if (gamepad.leftShoulder.value != 0)
{
print("cyclePages")
}
}
// rightShoulder
else if (gamepad.rightShoulder == element)
{
if (gamepad.rightShoulder.value != 0)
{
print("newGame")
}
}
// leftTrigger
else if (gamepad.leftTrigger == element)
{
if (gamepad.leftTrigger.value != 0)
{
print("pauseGame")
}
}
// rightTrigger
else if (gamepad.rightTrigger == element)
{
if (gamepad.rightTrigger.value != 0)
{
print("resumeGame")
}
}
// Left Thumbstick
else if (gamepad.leftThumbstick == element)
{
if (gamepad.leftThumbstick.xAxis.value > 0)
{
print("movePaddleRight")
}
else if (gamepad.leftThumbstick.xAxis.value < 0)
{
print("movePaddleLeft")
}
else if (gamepad.leftThumbstick.xAxis.value == 0)
{
print("decreaseSpeed")
}
else if (gamepad.leftThumbstick.yAxis.value > 0)
{
print("movePaddleDown")
}
else if (gamepad.leftThumbstick.yAxis.value < 0)
{
print("movePaddleUp")
}
else if (gamepad.leftThumbstick.yAxis.value == 0)
{
print("decreaseSpeed")
}
}
// Right Thumbstick
if (gamepad.rightThumbstick == element)
{
if (gamepad.rightThumbstick.xAxis.value > 0)
{
print("movePaddleRight")
}
else if (gamepad.rightThumbstick.xAxis.value < 0)
{
print("movePaddleLeft")
}
else if (gamepad.rightThumbstick.xAxis.value == 0)
{
print("decreaseSpeed")
}
else if (gamepad.rightThumbstick.yAxis.value > 0)
{
print("movePaddleDown")
}
else if (gamepad.rightThumbstick.yAxis.value < 0)
{
print("movePaddleUp")
}
else if (gamepad.rightThumbstick.yAxis.value == 0)
{
print("decreaseSpeed")
}
}
// D-Pad
else if (gamepad.dpad == element)
{
if (gamepad.dpad.xAxis.value > 0)
{
print("scrollWindowRight")
}
else if (gamepad.dpad.xAxis.value < 0)
{
print("scrollWindowLeft")
}
else if (gamepad.dpad.yAxis.value > 0)
{
print("scrollWindowDown")
}
else if (gamepad.dpad.yAxis.value < 0)
{
print("scrollWindowUp")
}
}
} // controllerInputDetected
} // class GameScene: SKScene
Now, the GameViewController ..
import UIKit
import SpriteKit
import GameplayKit
import WebKit
// This is now available across Classes
var theWebView: WKWebView!
class GameViewController: UIViewController, WKNavigationDelegate {
override func loadView() {
// print("loadView")
let webConfiguration = WKWebViewConfiguration()
theWebView = WKWebView(frame: .zero, configuration: webConfiguration)
theWebView.navigationDelegate = self
view = theWebView
} // loadView
override func viewDidLoad() {
super.viewDidLoad()
// print("viewDidLoad")
loadURL(webAddress: "https://www.lovesongforever.com/firstgame")
} // viewDidLoad
func loadURL(webAddress: String) {
let theURL = URL(string: webAddress)
let theRequest = URLRequest(url: theURL!)
theWebView.load(theRequest)
theWebView.allowsBackForwardNavigationGestures = false
} // loadURL
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
}
else {
return .all
}
} // supportedInterfaceOrientations
override var prefersStatusBarHidden: Bool {
return true
} // prefersStatusBarHidden
} // class GameViewController
Note that when I RUN my iOS App, thanks to the overridden loadView() above, it presents the following in the Simulator:
Simulator presentation
But, that’s is as far as it goes, because pressing all the buttons on my Gamepad does not result in detection of my Gamepad, as evidenced when I UN-comment all the above print(..) statements. In particular, those within:
#objc func connectControllers() and
#objc func disconnectControllers() and
func controllerInputDetected( .. )
So, hopefully that is all there currently is ..
You are doing this in your app delegate:
let itsGameScene = GameScene()
itsGameScene.ObserveForGameControllers()
You're creating an instance of GameScene, making it listen for notifications (adding self as the observer)... and then probably throwing that instance away.
Your game isn't actually using SpriteKit, and SpriteKit isn't needed to deal with game controllers, you just need GameController.
You have no need to create this scene at all. The best place to observe the game controller notifications would be in your GameViewController in viewDidLoad. Move the code (the observation method and the controller-related ones) you had in your scene into the view controller, and delete the scene file.
Is it possible to change the order of views in NSStackView by dragging the subviews, just like we do it in NSTableView ?
Here's an implementation of an NSStackView subclass whose contents can be reordered via dragging:
//
// DraggingStackView.swift
// Analysis
//
// Created by Mark Onyschuk on 2017-02-02.
// Copyright © 2017 Mark Onyschuk. All rights reserved.
//
import Cocoa
class DraggingStackView: NSStackView {
var isEnabled = true
// MARK: -
// MARK: Update Function
var update: (NSStackView, Array<NSView>)->Void = { stack, views in
stack.views.forEach {
stack.removeView($0)
}
views.forEach {
stack.addView($0, in: .leading)
switch stack.orientation {
case .horizontal:
$0.topAnchor.constraint(equalTo: stack.topAnchor).isActive = true
$0.bottomAnchor.constraint(equalTo: stack.bottomAnchor).isActive = true
case .vertical:
$0.leadingAnchor.constraint(equalTo: stack.leadingAnchor).isActive = true
$0.trailingAnchor.constraint(equalTo: stack.trailingAnchor).isActive = true
}
}
}
// MARK: -
// MARK: Event Handling
override func mouseDragged(with event: NSEvent) {
if isEnabled {
let location = convert(event.locationInWindow, from: nil)
if let dragged = views.first(where: { $0.hitTest(location) != nil }) {
reorder(view: dragged, event: event)
}
} else {
super.mouseDragged(with: event)
}
}
private func reorder(view: NSView, event: NSEvent) {
guard let layer = self.layer else { return }
guard let cached = try? self.cacheViews() else { return }
let container = CALayer()
container.frame = layer.bounds
container.zPosition = 1
container.backgroundColor = NSColor.underPageBackgroundColor.cgColor
cached
.filter { $0.view !== view }
.forEach { container.addSublayer($0) }
layer.addSublayer(container)
defer { container.removeFromSuperlayer() }
let dragged = cached.first(where: { $0.view === view })!
dragged.zPosition = 2
layer.addSublayer(dragged)
defer { dragged.removeFromSuperlayer() }
let d0 = view.frame.origin
let p0 = convert(event.locationInWindow, from: nil)
window!.trackEvents(matching: [.leftMouseDragged, .leftMouseUp], timeout: 1e6, mode: .eventTrackingRunLoopMode) { event, stop in
if event.type == .leftMouseDragged {
let p1 = self.convert(event.locationInWindow, from: nil)
let dx = (self.orientation == .horizontal) ? p1.x - p0.x : 0
let dy = (self.orientation == .vertical) ? p1.y - p0.y : 0
CATransaction.begin()
CATransaction.setDisableActions(true)
dragged.frame.origin.x = d0.x + dx
dragged.frame.origin.y = d0.y + dy
CATransaction.commit()
let reordered = self.views.map {
(view: $0,
position: $0 !== view
? NSPoint(x: $0.frame.midX, y: $0.frame.midY)
: NSPoint(x: dragged.frame.midX, y: dragged.frame.midY))
}
.sorted {
switch self.orientation {
case .vertical: return $0.position.y < $1.position.y
case .horizontal: return $0.position.x < $1.position.x
}
}
.map { $0.view }
let nextIndex = reordered.index(of: view)!
let prevIndex = self.views.index(of: view)!
if nextIndex != prevIndex {
self.update(self, reordered)
self.layoutSubtreeIfNeeded()
CATransaction.begin()
CATransaction.setAnimationDuration(0.15)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut))
for layer in cached {
layer.position = NSPoint(x: layer.view.frame.midX, y: layer.view.frame.midY)
}
CATransaction.commit()
}
} else {
view.mouseUp(with: event)
stop.pointee = true
}
}
}
// MARK: -
// MARK: View Caching
private class CachedViewLayer: CALayer {
let view: NSView!
enum CacheError: Error {
case bitmapCreationFailed
}
override init(layer: Any) {
self.view = (layer as! CachedViewLayer).view
super.init(layer: layer)
}
init(view: NSView) throws {
self.view = view
super.init()
guard let bitmap = view.bitmapImageRepForCachingDisplay(in: view.bounds) else { throw CacheError.bitmapCreationFailed }
view.cacheDisplay(in: view.bounds, to: bitmap)
frame = view.frame
contents = bitmap.cgImage
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private func cacheViews() throws -> [CachedViewLayer] {
return try views.map { try cacheView(view: $0) }
}
private func cacheView(view: NSView) throws -> CachedViewLayer {
return try CachedViewLayer(view: view)
}
}
The code requires your stack to be layer backed, and uses sublayers to simulate and animate its content views during drag handling. Dragging is detected by an override of mouseDragged(with:) so will not be initiated if the stack's contents consume this event.
There's no built-in support for re-ordering NSStackView subviews.
I implemented quick look in my project in the following way in Swift 2 (I'm including this here for reference and because it might help someone else set it up).
My NSViewController contains a NSTableView subclass where I implemented keyDown to listen to the spacebar key being pressed (maybe not the best way but it works):
override func keyDown(theEvent: NSEvent) {
let s = theEvent.charactersIgnoringModifiers!
let s1 = s.unicodeScalars
let s2 = s1[s1.startIndex].value
let s3 = Int(s2)
if s3 == Int(" ".utf16.first!) {
NSNotificationCenter.defaultCenter().postNotification(NSNotification(name: "MyTableViewSpacebar", object: nil))
return
}
super.keyDown(theEvent)
}
In my view controller, I have an observer for this notification and the functions required by the QLPreviewPanel:
//...
class ViewController: NSViewController {
#IBOutlet weak var myTableView: MyTableView!
var files = [FilesListData]() //array of custom class
//...
override func viewDidLoad() {
//...
NSNotificationCenter.defaultCenter().addObserver(self, selector: "spaceBarKeyDown:", name: "MyTableViewSpacebar", object: nil)
}
func spaceBarKeyDown(notification: NSNotification) {
if let panel = QLPreviewPanel.sharedPreviewPanel() {
panel.makeKeyAndOrderFront(self)
}
}
override func acceptsPreviewPanelControl(panel: QLPreviewPanel!) -> Bool {
return true
}
override func beginPreviewPanelControl(panel: QLPreviewPanel!) {
panel.delegate = self
panel.dataSource = self
}
override func endPreviewPanelControl(panel: QLPreviewPanel!) {
}
}
extension ViewController: QLPreviewPanelDataSource {
func numberOfPreviewItemsInPreviewPanel(panel: QLPreviewPanel!) -> Int {
return self.myTableView.selectedRowIndexes.count
}
func previewPanel(panel: QLPreviewPanel!, previewItemAtIndex index: Int) -> QLPreviewItem! {
if self.myTableView.selectedRow != -1 {
var items = [QLPreviewItem]()
let manager = NSFileManager.defaultManager()
for i in self.myTableView.selectedRowIndexes {
let path = self.files[i].path //path to a MP3 file
if manager.fileExistsAtPath(path) {
items.append(NSURL(fileURLWithPath: path))
} else {
items.append(qm_url) //image of a question mark used as placeholder
}
}
return items[index]
} else {
return qm_url //image of a question mark used as placeholder
}
}
}
What I would like to do now is listen to the keys "up arrow" and "down arrow" being pressed while the quick look panel is open, in order to change the selected row in the NSTableView, much like Finder behaves when you preview files with quick look. I have no clue as to how I could implement this. Any ideas?
Thanks.
Finally found what I was looking for and it's actually pretty simple.
Since my main view controller is also my delegate for the QLPreviewPanel, I added this:
extension ViewController: QLPreviewPanelDelegate {
func previewPanel(panel: QLPreviewPanel!, handleEvent event: NSEvent!) -> Bool {
let kc = event.keyCode
if (kc == 126 || kc == 125) { //up and down arrows
if event.type == NSEventType.KeyDown {
self.myTableView.keyDown(event) //send the event to the table
} else if event.type == NSEventType.KeyUp {
self.myTableView.keyUp(event)
}
return true
}
return false
}
}
Then in my table view delegate:
func tableViewSelectionDidChange(notification: NSNotification) {
guard myTableView.numberOfSelectedRows > 0 else {
if let panel = QLPreviewPanel.sharedPreviewPanel() {
if panel.visible {
panel.close()
}
}
return
}
if let panel = QLPreviewPanel.sharedPreviewPanel() {
if panel.visible {
panel.reloadData()
}
}
}
That's it! The QLPreviewPanelDataSource handles the rest.
In a view-based NSTableView, I have a subclass of NSTableCellView.
I want to change text color for the selected row's cellView.
class CellView: NSTableCellView {
override var backgroundStyle: NSBackgroundStyle {
set {
super.backgroundStyle = newValue
self.udpateSelectionHighlight()
}
get {
return super.backgroundStyle;
}
}
func udpateSelectionHighlight() {
if ( self.backgroundStyle == NSBackgroundStyle.Dark ) {
self.textField?.textColor = NSColor.whiteColor()
} else if( self.backgroundStyle == NSBackgroundStyle.Light ) {
self.textField?.textColor = NSColor.blackColor()
}
}
}
The problem is that all cellViews are set with NSBackgroundStyle.Light.
My selection is custom-drawn in a subclass of NSTableRowView.
class RowView: NSTableRowView {
override func drawSelectionInRect(dirtyRect: NSRect) {
if ( self.selectionHighlightStyle != NSTableViewSelectionHighlightStyle.None ) {
var selectionRect = NSInsetRect(self.bounds, 0, 2.5)
NSColor( fromHexString: "d1d1d1" ).setFill()
var selectionPath = NSBezierPath(
roundedRect: selectionRect,
xRadius: 10,
yRadius: 60
)
// ...
selectionPath.fill()
}
}
// ...
}
Why isn't the selected row cellView's backgroundStyle property set to Dark?
Thanks.
While I still don't know why doesn't the TableView/RowView set the Dark background on the selected row's cellView, I found this to be an acceptable workaround:
class CellView: NSTableCellView {
override var backgroundStyle: NSBackgroundStyle {
set {
if let rowView = self.superview as? NSTableRowView {
super.backgroundStyle = rowView.selected ? NSBackgroundStyle.Dark : NSBackgroundStyle.Light
} else {
super.backgroundStyle = newValue
}
self.udpateSelectionHighlight()
}
get {
return super.backgroundStyle;
}
}
func udpateSelectionHighlight() {
if ( self.backgroundStyle == NSBackgroundStyle.Dark ) {
self.textField?.textColor = NSColor.whiteColor()
} else {
self.textField?.textColor = NSColor.blackColor()
}
}
}