Open an NSWindow by clicking NSRect in cocoa - cocoa

In my program I am able to determine whether a mouseclick was made within a certain NSRect. How can I open a new NSWindow by clicking this NSRect?

If you want to display an existing window (which you created with Interface Builder) you just call makeKeyAndOrderFront on your window object.
If you want to create a new window programmatically you find an answer here.

To handle events, you'd implement the relevant methods of NSResponder in your NSView or NSViewController subclass. For instance, you could implement mouseDown: and -mouseUp: to handle mouse clicks (in a fairly simplistic manner), like so:
- (void) mouseDown: (NSEvent *) event
{
if ( [event type] != NSLeftMouseDown )
{
// not the left button, let other things handle it
[super mouseDown: event];
return;
}
NSPoint location = [self convertPoint: [event locationInWindow] fromView: nil];
if ( !NSPointInRect(location, self.theRect) )
{
[super mouseDown: event];
return;
}
self.hasMouseDown = YES;
}
- (void) mouseUp: (NSEvent *) event
{
if ( (!self.hasMouseDown) || ([event type] != NSLeftMouseUp) )
{
[super mouseUp: event];
return;
}
NSPoint location = [self convertPoint: [event locationInWindow] fromView: nil];
if ( !NSPointInRect(location, self.theRect) )
{
[super mouseDown: event];
return;
}
self.hasMouseDown = NO;
// mouse went down and up within the target rect, so you can do stuff now
}

Related

How can I abort NSStepper auto-repeating?

I have an auto-repeating NSStepper that I want to stop tracking when I receive a certain NSNotification.
One idea was to just send [_stepper setAutorepeat: NO] from the method that receives the notification. That doesn't work. I suppose that the stepper only checks the autorepeat flag when tracking starts.
Then I thought I could subclass NSStepperCell, and use an override of-[NSCell continueTracking:at:inView:] to abort tracking. However, apparently that method does not get called when a stepper is auto-repeating without the mouse moving.
Do I need to completely rewrite trackMouse:inRect:ofView:untilMouseUp:? I suppose then I'd have to handle highlighting parts of the stepper as the mouse moves in or out, and I don't see any public API to even find out which part is highlighted.
I ended up posting a mouse up event. I subclassed NSStepperCell just as a convenient place to watch for the notification and post the event.
#interface AbortableStepperCell : NSStepperCell
{
BOOL _isTracking;
BOOL _isObserverInstalled;
}
#implementation AbortableStepperCell
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
[super dealloc];
}
- (BOOL)startTrackingAt: (NSPoint) startPoint
inView: (NSView *) controlView
{
_isTracking = YES;
if ( ! _isObserverInstalled )
{
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: #selector(abortTracking:)
name: #"AbortMouseTracking"
object: nil];
_isObserverInstalled = YES;
}
return [super startTrackingAt: startPoint inView: controlView];
}
- (void) abortTracking: (NSNotification*) note
{
if (_isTracking)
{
NSWindow* myWindow = self.controlView.window;
NSGraphicsContext* gc =
[NSGraphicsContext graphicsContextWithWindow: myWindow];
NSEvent* upEvent = [NSEvent
mouseEventWithType: NSLeftMouseUp
location: NSZeroPoint
modifierFlags: 0
timestamp: 0.0
windowNumber: myWindow.windowNumber
context: gc
eventNumber: 0
clickCount: 1
pressure: 0.0f ];
if (upEvent)
{
[NSApp postEvent: upEvent atStart: YES];
}
}
}
- (void)stopTracking:(NSPoint)lastPoint
at:(NSPoint)stopPoint
inView:(NSView *)controlView
mouseIsUp:(BOOL)flag
{
_isTracking = NO;
[super stopTracking: lastPoint
at: stopPoint
inView: controlView
mouseIsUp: flag];
}
#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

Dont deselect a selected row in view based NStableview when clicked outside

In my view based NSTableView i don't want to deselect the selected cell, when clicking in an empty part of the NSTableView, how to obey this default behavior?
Improved version from Neha's answer (this obeys the select / deselect)
Subclass NSTableView and implement :
- (void)mouseDown:(NSEvent *)theEvent {
NSPoint globalLocation = [theEvent locationInWindow];
NSPoint localLocation = [self convertPoint:globalLocation fromView:nil];
NSInteger clickedRow = [self rowAtPoint:localLocation];
if(clickedRow != -1) {
[super mouseDown:theEvent];
}
}
We just ignore the event, when we don't hit a row...
Implement the mouse down event of your NSTableView by subclassing it. Inside it check if the clicked point is a row or empty area. If it is an empty area, then again select the previously selected rows of your table view.
- (void)mouseDown:(NSEvent *)theEvent
{
NSPoint globalLocation = [theEvent locationInWindow];
NSPoint localLocation = [self convertPoint:globalLocation fromView:nil];
NSInteger clickedRow = [self rowAtPoint:localLocation];
NSIndexSet* selectedRows = [self selectedRowIndexes];
NSLog(#"%ld",clickedRow);
[super mouseDown:theEvent];
if(clickedRow == -1)
{
[self selectRowIndexes:selectedRows byExtendingSelection:NO];
}
}
This the Swift4 version if anyone needs it:
override func mouseDown(with event: NSEvent) {
let globalLocation = event.locationInWindow
let localLocation = convert(globalLocation, from: nil)
let clickedRow = row(at: localLocation)
if clickedRow != -1 {
super.mouseDown(with: event)
}
}

Cocoa: Right Click NSStatusItem

I am a .Net developer who need to port a small project into Mac, so I know next to nothing about Objective C. In fact the code below was just a bunch of grasping at straws and shooting in the dark.
Trying to build a Status Menu program that opens one or another window depending on if it is a left-click or a right-click/ctrl+click. Here is what I have, and it works for left-click only (obviously):
-(void) awakeFromNib{
NSBundle *bundle = [NSbundle mainBundle];
statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
[statusImage = [[NSImage alloc] initWithContentsOfFile:[bundle pathForResource:#"icon" ofType:#"png"]];
[statusItem setImage:statusImage];
[statusItem setToolTip:#"Program Name"];
[statusItem setHighlightMode:YES];
[statusItem setAction:#selector(openWin:)];
[statusItem setTarget: self];
}
-(void)openWin:(id)sender{
[self openLeftWindow:sender];
}
-(IBAction)openLeftWindow:(id)sender{
//Code to populate Left Click Window
[leftWindow makeKeyAndorderFront:nil];
}
-(IBAction)openRightWindow:(id)sender{
//Code to populate Right Click Window
[rightWindow makeKeyAndorderFront:nil];
}
It seems to me that the solution would be either an if statement in the openWin() function to determine which button is clicked (or if ctrl was held down) then run the appropriate code or to make the menu aware of both the left and right clicks. But neither of these have worked when I attempted to do so.
Thanks In Advance.
If you will be satisfied with detecting control-click and not right click, then the first block of code will do what you want. If you really need the right click detection, you will have to use a custom view instead of an image in your NSStatusItem, and the second block of code will work.
Simple Method:
- (void)openWin:(id)sender {
NSEvent *event = [NSApp currentEvent];
if([event modifierFlags] & NSControlKeyMask) {
[self openRightWindow:nil];
} else {
[self openLeftWindow:nil];
}
}
Custom view method:
- (void)awakeFromNib {
...
statusImage = ...
MyView *view = [MyView new];
view.image = statusImage;
[statusItem setView:view];
[statusItem setToolTip:#"Program Name"];
view target = self;
view action = #selector(openLeftWindow:);
view rightAction = #selector(openRightWindow:);
[view release];
//[statusImage release]; //If you are not using it anymore, you should release it.
}
MyView.h
#import <Cocoa/Cocoa.h>
#interface MyView : NSControl {
NSImage *image;
id target;
SEL action, rightAction;
}
#property (retain) NSImage *image;
#property (assign) id target;
#property (assign) SEL action, rightAction;
#end
MyView.m
#import "MyView.h"
#implementation MyView
#synthesize image, target, action, rightAction;
- (void)mouseUp:(NSEvent *)event {
if([event modifierFlags] & NSControlKeyMask) {
[NSApp sendAction:self.rightAction to:self.target from:self];
} else {
[NSApp sendAction:self.action to:self.target from:self];
}
}
- (void)rightMouseUp:(NSEvent *)event {
[NSApp sendAction:self.rightAction to:self.target from:self];
}
- (void)dealloc {
self.image = nil;
[super dealloc];
}
- (void)drawRect:(NSRect)rect {
[self.image drawInRect:self.bounds fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1];
}
#end
I would create a view and use the status items method.
-setView:
Then in the subclassed view you can detect ctrl+LMB using the following
- (void)mouseDown:(NSEvent *)theEvent
{
[super mouseDown:theEvent];
//Respond to the mouse click
if ([theEvent modifierFlags] & NSCommandKeyMask) //Command + LMB
{
//Do something
}
}
I think you can figure out the rest.
A more simplified response (Note, only works with control + click)
Properties:
#property (strong, nonatomic) NSStatusItem *statusItem;
#property (weak) IBOutlet NSMenu *statusMenu;
In Your Application Did Load:
[self.statusItem setAction:#selector(itemClicked:)];
Clicked Function:
- (void)itemClicked:(id)sender
{
NSEvent *event = [NSApp currentEvent];
if([event modifierFlags] & NSControlKeyMask) {
NSLog(#"Right Click Pressed");
[self.statusItem popUpStatusItemMenu:self.statusMenu];
} else {
// Do Nothing
NSLog(#"Left Click Pressed");
}
}

Cocoa nextEventMatchingMask not receiving NSMouseMoved event

I created a local event loop and showed up a borderless window (derived from NSPanel),
I found in the event loop there's no NSMouseMoved event received, although I can receive Mouse button down/up events.
What should I do to get the NSMouseMoved events? I found making the float window as key window can receive the NSMouseMoved events, but I don't want to change key window. And it appears this is possible, because I found after clicking the test App Icon in System Dock Bar, I can receive the mousemoved events, and the key window/mainwindow are unchanged.
Here's the my test code: (Create a Cocoa App project names FloatWindowTest, and put a button to link with the onClick: IBAction).
Thanks in advance!
-Jonny
#import "FloatWindowTestAppDelegate.h"
#interface FloatWindow : NSPanel
#end
#interface FloatWindowContentView : NSView
#end
#implementation FloatWindowTestAppDelegate
#synthesize window;
- (void)delayedAction:(id)sender
{
// What does this function do?
// 1. create a float window
// 2. create a local event loop
// 3. print out the events got from nextEventMatchingMask.
// 4. send it to float window.
// What is the problem?
// In local event loop, althrough the event mask has set NSMouseMovedMask
// there's no mouse moved messages received.
//
FloatWindow* floatWindow = [[FloatWindow alloc] init];
NSEvent* event = [NSApp currentEvent];
NSPoint screenOrigin = [[self window] convertBaseToScreen:[event locationInWindow]];
[floatWindow setFrameTopLeftPoint:screenOrigin];
[floatWindow orderFront:nil];
//Making the float window as Key window will work, however
//change active window is not anticipated.
//[floatWindow makeKeyAndOrderFront:nil];
BOOL done = NO;
while (!done)
{
NSAutoreleasePool* pool = [NSAutoreleasePool new];
NSUInteger eventMask = NSLeftMouseDownMask|
NSLeftMouseUpMask|
NSMouseMovedMask|
NSMouseEnteredMask|
NSMouseExitedMask|
NSLeftMouseDraggedMask;
NSEvent* event = [NSApp nextEventMatchingMask:eventMask
untilDate:[NSDate distantFuture]
inMode:NSDefaultRunLoopMode
dequeue:YES];
//why I cannot get NSMouseMoved event??
NSLog(#"new event %#", [event description]);
[floatWindow sendEvent:event];
[pool drain];
}
[floatWindow release];
return;
}
-(IBAction)onClick:(id)sender
{
//Tried to postpone the local event loop
//after return from button's mouse tracking loop.
//but not fixes this problem.
[[NSRunLoop currentRunLoop]
performSelector:#selector(delayedAction:)
target:self
argument:nil
order:0
modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
}
#end
#implementation FloatWindow
- (id)init
{
NSRect contentRect = NSMakeRect(200,300,200,300);
self = [super initWithContentRect:contentRect
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:YES];
if (self) {
[self setLevel:NSFloatingWindowLevel];
NSRect frameRect = [self frameRectForContentRect:contentRect];
NSView* view = [[[FloatWindowContentView alloc]
initWithFrame:frameRect] autorelease];
[self setContentView:view];
[self setAcceptsMouseMovedEvents:YES];
[self setIgnoresMouseEvents:NO];
}
return self;
}
- (BOOL)becomesKeyOnlyIfNeeded
{
return YES;
}
- (void)becomeMainWindow
{
NSLog(#"becomeMainWindow");
[super becomeMainWindow];
}
- (void)becomeKeyWindow
{
NSLog(#"becomeKeyWindow");
[super becomeKeyWindow];
}
#end
#implementation FloatWindowContentView
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
return YES;
}
- (BOOL)acceptsFirstResponder
{
return YES;
}
- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if (self) {
NSTrackingArea* area;
area = [[NSTrackingArea alloc] initWithRect:frameRect
options:NSTrackingActiveAlways|
NSTrackingMouseMoved|
NSTrackingMouseEnteredAndExited
owner:self
userInfo:nil];
[self addTrackingArea:area];
[area release];
}
return self;
}
- (void)drawRect:(NSRect)rect
{
[[NSColor redColor] set];
NSRectFill([self bounds]);
}
- (BOOL)becomeFirstResponder
{
NSLog(#"becomeFirstResponder");
return [super becomeFirstResponder];
}
#end
I think I find the right answer. This is somehow related to tell NSWindow to accept MouseMoved event. but what to my surprise is, The window should accept mouseMoved events isn't the Floating Window, but the current Key window. So the solution is simple, just enable key window to accept mouse moved events before starting tracking, and revert the switch after ending tracking.

Resources