Cocoa webview textfield not accepting keystrokes - macos

I am a newbie in OSx development.
I have a Cocoa application which uses a Webview. Everything is working fine, except for the textfield in the webview. I know how to enable keystrokes in NSTextField, but not the ones in the Webview. I've been searching the web all day, but with no luck.
I badly need some help on how to enable the keystrokes to implement keyboard shortcut keys.
Example:
copy -> command + c
paste -> command + v
cut -> command + x
Any help would be very much appreciated.

I got the answer now. I've realized that I forgot to implement
- (BOOL)performKeyEquivalent:(NSEvent *)theEvent
to the class which handles the Webview.

#Kimpoy, thanks for the reference to performKeyEquivalent!
For completeness, I implemented it this way...
Subclass your webview from WebView and implement the method:
- (BOOL)performKeyEquivalent:(NSEvent *)theEvent {
NSString * chars = [theEvent characters];
BOOL status = NO;
if ([theEvent modifierFlags] & NSCommandKeyMask){
if ([chars isEqualTo:#"a"]){
[self selectAll:nil];
status = YES;
}
if ([chars isEqualTo:#"c"]){
[self copy:nil];
status = YES;
}
if ([chars isEqualTo:#"v"]){
[self paste:nil];
status = YES;
}
if ([chars isEqualTo:#"x"]){
[self cut:nil];
status = YES;
}
}
if (status)
return YES;
return [super performKeyEquivalent:theEvent];
}
Credit to #aventurella over here: https://github.com/Beats-Music/mac-miniplayer/issues/3. Just modified slightly to return the super response as default because it should propagate down to its subviews.
As a note, I'd recommend implementing a log or similar in your custom webview to make sure you really are working with your class:
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
NSLog(#"Custom webview running...");
}

Related

Prevent QLPreviewView from grabbing focus

I have a list of files. Next to it I have a QLPreviewView which shows the currently selected file.
Unfortunately QLPreviewView loads a web view to preview bookmark files. Some web pages can grab keyboard focus. E.g. the Gmail login form places the insertion point into the user name field.
This breaks the flow of my application. I want to navigate my list using arrow keys. This is disrupted when keyboard focus is taken away from the table view.
So far the best I could come up with is to override - [NSWindow makeFirstResponder:] and not call super for instances of classes named with a QL prefix. Yuck.
Is there a more reasonable way to
Prevent unwanted changes of first responder?
or prevent user interaction on QLPreviewView and its subviews?
I ended up using a NSWindow subclass that allows QLPreviewViews and its private subviews to become first responder on user interaction, but prevents these views from simply stealing focus.
- (BOOL)makeFirstResponder:(NSResponder *)aResponder
{
NSString *classname = NSStringFromClass([aResponder class]);
// This is a hack to prevent Quick Look from stealing first responder
if ([classname hasPrefix:#"QL"]) {
BOOL shouldMakeFirstRespnder = NO;
NSEvent *currentEvent = [[NSApplication sharedApplication] currentEvent] ;
NSEventType eventType = currentEvent.type;
if ((eventType == NSLeftMouseDown) || (eventType == NSRightMouseDown) || (eventType == NSMouseEntered)) {
if ([aResponder isKindOfClass:[NSView class]]) {
NSView *view = (NSView *)aResponder;
NSPoint locationInWindow = currentEvent.locationInWindow;
NSPoint locationInView = [view convertPoint:locationInWindow fromView:nil];
BOOL pointInRect = NSPointInRect(locationInView, [view bounds]);
shouldMakeFirstRespnder = pointInRect;
}
}
if (!shouldMakeFirstRespnder) {
return NO;
}
}
return [super makeFirstResponder:aResponder];
}
Maybe you can subclass QLPreviewView and override its becomeFirstResponder so that you can either enable or disable it when your application should allow it to accept focus.
Header
#interface MyQLPreviewView : QLPreviewView
#end
Implementation
#implementation
- (BOOL)becomeFirstResponder
{
return NO;
}
#end

How to intercept toggling fullscreen mode

I'm creating Mac OS plugin(bundle) for Unity3D. How can I intercept entering cmd-f combination (toggling full screen mode)? I can't create my own window, I can only use default (mainWindow). I've tried to use NSNotificationCenter, but I need to stop event, I don't need just a notification. I've tried to create NSResponder and add it to capture input events, but something don't work. Any ideas how to do it?
NSWindow* window = [[NSApplication sharedApplication] mainWindow];
NSView* view = [window contentView];
NSResponder* oldresp = [view nextResponder];
MyResponder* myres = [MyResponder alloc];
[myres retain];
[view setNextResponder:myres];
and
#interface MyResponder : NSResponder
{
}
- (void)keyDown:(NSEvent *)theEvent;
#end
#implementation MyResponder
- (void)keyDown:(NSEvent *)theEvent
{
NSLog(#"%#",#"!KeyDown Event");
NSString *theArrow = [theEvent charactersIgnoringModifiers];
unichar keyChar = 0;
if ( [theArrow length] == 1 )
{
keyChar = [theArrow characterAtIndex:0];
if ( keyChar == NSModeSwitchFunctionKey )
{
NSLog(#"%#",#"!!!___!!! GOT NSModeSwitchFunctionKey !!!");
return;
}
NSLog(#"%# %d",#"! Key:",keyChar);
}
[super keyDown:theEvent];
}
#end
One solution is to check constantly if the user has switched to fullscreen mode, and if he has, toggle fullscreen off from your program. This may cause some brief lag or graphics sketchiness momentarily, but it should work.
function Update ()
{
if (Screen.fullScreen) {
Screen.fullScreen = false;
}
}
Im not sure of a way to intercept the key press and ignore the command before the program switches though.
See this related post on unity answers about dealing with this on windows:
http://answers.unity3d.com/questions/544183/block-or-override-alt-enter-fullscreen.html

Remove elasticity from webview

I have a webview which I would like to remove the elasticity from. As it is now, when scrolling a page that is smaller than the webview, it will make an elasticity effect revealing the background underneath. I would like to remove this.
I have tried doing the following but without success. It finds the WebDynamicScrollBarsView but setting the elasticity of this, does not change anything.
- (void)awakeFromNib
{
NSScrollView *scrollView = [self findScrollViewInSubviews:self.subviews];
scrollView.horizontalScrollElasticity = NSScrollElasticityNone;
scrollView.verticalScrollElasticity = NSScrollElasticityNone;
}
- (NSScrollView *)findScrollViewInSubviews:(NSArray *)subviews
{
for (NSView *view in subviews)
{
if ([view isKindOfClass:[NSScrollView class]])
return (NSScrollView *) view;
else
return [self findScrollViewInSubviews:view.subviews];
}
return nil;
}
Does anyone know how to remove the elasticity effect from a webview?
In my subclass of WebView (or you can just do it directly, with webView.mainFrame.frameView.documentView.enclosingScrollView, but this makes it easier for me, personally)
// return the scroll view that we are currently using, if applicatble
- (NSScrollView *)mainScrollView {
return [[[[self mainFrame] frameView] documentView] enclosingScrollView]; // can be nil
}
Then on finish load, as Anne noted literally as I was typing my answer :) ,
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
NSScrollView *mainScrollView = [sender mainScrollView];
[mainScrollView setVerticalScrollElasticity:NSScrollElasticityNone];
[mainScrollView setHorizontalScrollElasticity:NSScrollElasticityNone];
}
I'm not sure if doing it after the content has loaded is necessary, but I don't have my mac with me to try right now.

Drag and Drop with NSStatusItem

I'm trying to write an application that allows the user to drag files from the Finder and drop them onto an NSStatusItem. So far, I've created a custom view that implements the drag and drop interface. When I add this view as a subview of an NSWindow it all works correctly -- the mouse cursor gives appropriate feedback, and when dropped my code gets executed.
However, when I use the same view as an NSStatusItem's view it doesn't behave correctly. The mouse cursor gives appropriate feedback indicating that the file can be dropped, but when I drop the file my drop code never gets executed.
Is there something special I need to do to enable drag and drop with an NSStatusItem?
I finally got around to testing this and it works perfectly, so there's definitely something wrong with your code.
Here's a custom view that allows dragging:
#implementation DragStatusView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
//register for drags
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
//the status item will just be a yellow rectangle
[[NSColor yellowColor] set];
NSRectFill([self bounds]);
}
//we want to copy the files
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
return NSDragOperationCopy;
}
//perform the drag and log the files that are dropped
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
NSLog(#"Files: %#",files);
}
return YES;
}
#end
Here's how you'd create the status item:
NSStatusItem* item = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
DragStatusView* dragView = [[DragStatusView alloc] initWithFrame:NSMakeRect(0, 0, 24, 24)];
[item setView:dragView];
[dragView release];
Since Yosemite, the method for setting a view on NSStatusItem is deprecated but fortunately there is a much nicer way using the new NSStatusItemButton property on NSStatusItem:
- (void)applicationDidFinishLaunching: (NSNotification *)notification {
NSImage *icon = [NSImage imageNamed:#"iconName"];
//This is the only way to be compatible to all ~30 menu styles (e.g. dark mode) available in Yosemite
[normalImage setTemplate:YES];
statusItem.button.image = normalImage;
// register with an array of types you'd like to accept
[statusItem.button.window registerForDraggedTypes:#[NSFilenamesPboardType]];
statusItem.button.window.delegate = self;
}
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
return NSDragOperationCopy;
}
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
//drag handling logic
}
Please be aware that the button property is only available starting in 10.10 and you might have to keep your old solution if you support 10.9 Mavericks or below.

Changing NSApplicationIcon across a running application?

I'd like to adjust the NSApplicationIcon image that gets shown automatically in all alerts to be something different than what is in the app bundle.
I know that it's possible to set the dock icon with [NSApplication setApplicationIconImage:] -- but this only affects the dock, and nothing else.
I'm able to work around this issue some of the time: I have an NSAlert *, I can call setIcon: to display my alternate image.
Unfortunately, I have a lot of nibs that have NSImageView's with NSApplicationIcon, that I would like to affect, and it would be a hassle to create outlets and put in code to change the icon. And for any alerts that I'm bringing up with the BeginAlert... type calls (which don't give an NSAlert object to muck with), I'm completely out of luck.
Can anybody think of a reasonable way to globally (for the life of a running application) override the NSApplicationIcon that is used by AppKit, with my own image, so that I can get 100% of the alerts replaced (and make my code simpler)?
Swizzle the [NSImage imageNamed:] method? This method works at least on Snow Leopard, YMMV.
In an NSImage category:
#implementation NSImage (Magic)
+ (void)load {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// have to call imageNamed: once prior to swizzling to avoid infinite loop
[[NSApplication sharedApplication] applicationIconImage];
// swizzle!
NSError *error = nil;
if (![NSImage jr_swizzleClassMethod:#selector(imageNamed:) withClassMethod:#selector(_sensible_imageNamed:) error:&error])
NSLog(#"couldn't swizzle imageNamed: application icons will not update: %#", error);
[pool release];
}
+ (id)_sensible_imageNamed:(NSString *)name {
if ([name isEqualToString:#"NSApplicationIcon"])
return [[NSApplication sharedApplication] applicationIconImage];
return [self _sensible_imageNamed:name];
}
#end
With this hacked up (untested, just wrote it) jr_swizzleClassMethod:... implementation:
+ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_ {
#if OBJC_API_VERSION >= 2
Method origMethod = class_getClassMethod(self, origSel_);
if (!origMethod) {
SetNSError(error_, #"original method %# not found for class %#", NSStringFromSelector(origSel_), [self className]);
return NO;
}
Method altMethod = class_getClassMethod(self, altSel_);
if (!altMethod) {
SetNSError(error_, #"alternate method %# not found for class %#", NSStringFromSelector(altSel_), [self className]);
return NO;
}
id metaClass = objc_getMetaClass(class_getName(self));
class_addMethod(metaClass,
origSel_,
class_getMethodImplementation(metaClass, origSel_),
method_getTypeEncoding(origMethod));
class_addMethod(metaClass,
altSel_,
class_getMethodImplementation(metaClass, altSel_),
method_getTypeEncoding(altMethod));
method_exchangeImplementations(class_getClassMethod(self, origSel_), class_getClassMethod(self, altSel_));
return YES;
#else
assert(0);
return NO;
#endif
}
Then, this method to illustrate the point:
- (void)doMagic:(id)sender {
static int i = 0;
i = (i+1) % 2;
if (i)
[[NSApplication sharedApplication] setApplicationIconImage:[NSImage imageNamed:NSImageNameBonjour]];
else
[[NSApplication sharedApplication] setApplicationIconImage:[NSImage imageNamed:NSImageNameDotMac]];
// any pre-populated image views have to be set to nil first, otherwise their icon won't change
// [imageView setImage:nil];
// [imageView setImage:[NSImage imageNamed:NSImageNameApplicationIcon]];
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
[alert setMessageText:#"Shazam!"];
[alert runModal];
}
A couple of caveats:
Any image view already created must have setImage: called twice, as seen above to register the image changing. Don't know why.
There may be a better way to force the initial imageNamed: call with #"NSApplicationIcon" than how I've done it.
Try [myImage setName:#"NSApplicationIcon"] (after setting it as the application icon image in NSApp).
Note: On 10.6 and later, you can and should use NSImageNameApplicationIcon instead of the string literal #"NSApplicationIcon".

Resources