How to test if the mouse is inside a specified window? - macos

With Cocoa, how do I check if the mouse is inside a specified window of mine? I have the following code that detects if it's within the bounds of the window, but it incorrectly prints that it's inside if the window is closed/hidden but the mouse is still in that rectangle. It will also incorrectly say it's inside if another window is on top of it, but the mouse is within the region of the window I'm testing below it.
NSPoint mouse = [NSEvent mouseLocation];
BOOL mouseInside = NSPointInRect(mouse, self.window.frame);
if (!mouseInside) {
NSLog(#"mouse isn't inside");
} else {
NSLog(#"mouse is inside");
}
I've tried something like this:
while ((screen = [screenEnum nextObject]) && !NSMouseInRect(mouse, [screen frame], NO));
if (screen != self.window.screen && mouseInside) {
NSLog(#"mouse is inside.");
}
but it would always print "mouse is inside".
Any ideas? Or is setting up a tracking area the only way?

mikeash on Freenode pointed me to NSWindow's windowNumberAtPoint:
The following code appears to work as needed:
if ([NSWindow windowNumberAtPoint:mouse belowWindowWithWindowNumber:0] != self.window.windowNumber) {
NSLog(#"mouse outside");
}

Related

Prevent NSTableRowView from changing highlight when not in focus?

I've got an NSSplitView with a table view in the left pane. I've noticed that when I have an item selected in the left pane and I change focus to the right pane, the left pane loses focus and the highlighted row's highlight color turns to gray and the text turns black.
I have overridden the highlight color by overriding drawSelectionInRect in NSTableRowView. By doing this, the highlight color remains the same custom color, but the text turns dark which looks wrong.
Can I either let the highlight color change when the table view is out of focus, or prevent the text from turning dark when it's out of focus?
It appears that OS X calls setBackgroundStyle on the row view and its subviews when you click on the other view in the split view. Interestingly it seems to only happen to NSOutlineView.
Since I have code in place already to handle changing my images to different images when rows are selected (for improved contrast), and I am explicitly setting the background style when I need to using outlineViewSelectionDidChange and outlineViewSelectionIsChanging I overrode setBackgroundStyle to be a no-op and I made my own custom setter which changed the _backgroundStyle ivar.
It looks like this:
#implementation TextFieldWithHighlighting
#synthesize backgroundStyle = _backgroundStyle;
- (NSBackgroundStyle) backgroundStyle {
return _backgroundStyle;
}
#synthesize secretBackgroundStyle = _secretBackgroundStyle;
- (NSBackgroundStyle) secretBackgroundStyle {
return _backgroundStyle;
}
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
}
- (void) setSecretBackgroundStyle:(NSBackgroundStyle)secretBackgroundStyle {
_backgroundStyle = secretBackgroundStyle;
self.needsDisplay = YES;
}
- (void)drawRect:(NSRect)dirtyRect {
if(_backgroundStyle == NSBackgroundStyleDark) {
if(self.originalTextColor == nil) {
self.originalTextColor = self.textColor;
}
self.textColor = [NSColor whiteColor];
} else {
if(self.originalTextColor) {
self.textColor = self.originalTextColor;
} else {
self.textColor = [NSColor colorWithCalibratedRed:0x40/255.0 green:0x40/255.0 blue:0x41/255.0 alpha:0xFF/255.0];
}
}
[super drawRect:dirtyRect];
}
#end
It only works for me because I am explicitly handling rows changing their selection and AFAIK I am not relying anywhere on the OS changing it for me. If I could do it again I'd just push for using the system highlight color in which case I get this functionality for free.
Just as a small contribution to your answer. This works as well with the weird contrast generated with the drawSelectionInRect implementation. Here's a Swift solution that also works implemented in an NSTableCellView:
override var backgroundStyle: NSBackgroundStyle {
get {
return self.backgroundStyle
}
set {
}
}

windowNumbersWithOptions: on Yosemite fails to return NSPanel windows of my application

I have a document-based application, with a main document window and several "satellite" NSPanel windows showing related info. They are not floating, they can (and do) become key, and seem to be at the same layer as the main window.
I try to implement a show/hide action like thus: If a panel is not visible - show it. If it is visible, but not front - make it front. If it is visible and front - hide it.
For that I need to know if an NSPanel is "frontmost". Sadly no NSWindow API exists for that. I tried to use windowNumbersWithOptions to compare z-order of my Panels for that.
-(void) togglePanelVisibility:(PMXPanelController *)panelController {
NSPanel *panel = [panelController window];
if ([panel isVisible]) {
NSArray *windowNumbers = [NSWindow windowNumbersWithOptions:0];
if ([panel windowNumber] == [[windowNumbers firstObject] integerValue]) {
[panel orderOut:self];
}
else {
[panel makeKeyAndOrderFront:self];
}
} else
[panelController showWindow:self];
}
Alas, the array I receive only includes one number - for my main document window. I can see my panels in the "Window" menu, I can click them to bring them to front, I can close them and use them - but I can't get their number via windowNumbersWithOptions:
Ideas anyone?
It doesn't state this anywhere, but judging by the small number of windows returned with NSWindowNumberListAllApplications it looks like windowNumbersWithOptions only returns windows from the normal level or applies some kind of filtering that doesn't include other windows. The best alternative is orderedWindows property of NSApplication instance, but since you've mentioned panels be warned:
Only windows that are typically scriptable are included in the array. For example, panels are not included.
It's good to remember that both methods depend on Quartz API and probably use CGWindowListCopyWindowInfo – in worst case scenario you can go to lower level and use that instead.
Following the given suggestions, I finally came out with the following, which seems to work for me reasonably.
/* Here is the logic: If panel of panelController is frontMost - hide it.
If it is visible, but not frontMost - bring it to front.
If it is not visible - Show it, and bring it to front. */
-(void) togglePanelVisibility:(PMXPanelController *)panelController
{
NSWindow *panel = [panelController window];
if ([panel isVisible]) {
BOOL panelIsFront = [self isPanelFront:panel];
if (panelIsFront)
[panel orderOut:self];
else
[panel makeKeyAndOrderFront:self];
} else
[panelController showWindow:self];
}
// Attempt to determine if a given panel (window) is front window
-(BOOL) isPanelFront:(NSWindow *)panel
{
BOOL panelIsFront = NO;
#if 0
// This simple implementation won't work because orderedWindows don't contain panels.
panelIsFront = [panel isEqualTo:[[NSApp orderedWindows] firstObject]];
// This used to work, but broke on OS X 10.10 Yosemite. I only receive my main window.
panelIsFront = ([panel windowNumber] == [[[NSWindow windowNumbersWithOptions:0] firstObject] integerValue]);
#endif
// Last resort, - using CoreGraphics window list. Seems to work on all systems up to 10.10.4
NSMutableArray *windowNumbers = [NSMutableArray arrayWithCapacity:32]; // I rely on window numbers to determine "frontness".
CFArrayRef windowInfoArray = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
for (NSDictionary* nswindowsdescription in (__bridge NSArray*)windowInfoArray) {
NSNumber *windowLayer = (NSNumber*)[nswindowsdescription objectForKey:(NSString *)kCGWindowLayer];
if (windowLayer.integerValue != 0) // filter out windows not in our normal window layer.
continue;
NSNumber* windowid = (NSNumber*)[nswindowsdescription objectForKey:(NSString *)kCGWindowNumber];
if(windowid)
[windowNumbers addObject:windowid];
}
CFRelease(windowInfoArray);
panelIsFront = ([panel windowNumber] == [[windowNumbers firstObject] integerValue]);
return panelIsFront;
}

-SendEvent does not behave as expected

I have a full-screen transparent window that displays above the main menu of my app. It has ignoresMouseEvents set to NO. To receive mouse clicks nonetheless, I added this code :
[NSEvent addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask handler:^(NSEvent *event) {
[self click:event];
return event;
}];
Every time the user clicks while my app is active, a method click is thus called :
- (BOOL)click:(NSEvent *)event {
NSPoint coordinate = [event locationInWindow];
float ycoord = coordinate.y;
float menuheight = [[NSApp mainMenu] menuBarHeight];
float windowheight = [[NSApp mainWindow] frame].size.height;
if (ycoord >= windowheight - menuheight && ![[NSApp mainWindow] ignoresMouseEvents]) {
[[NSApp mainWindow] setIgnoresMouseEvents:YES];
[NSApp sendEvent:event];
NSLog(#"click");
[[NSApp mainWindow] setIgnoresMouseEvents:NO];
return YES;
}
return NO;
}
As you can see, it changes the ignoresMouseEvents property of the main window to YES if the click was on the main menu bar - after which it calls sendEvent: in NSApplication. Finally it changes the ignoresMouseEvents property of the main window back to NO.
However, even though the log does say 'click' when the main menu bar is clicked, the click has no effect. If I click a menu item (eg. the 'File' menu item), for example, it will not open the corresponding menu (in this case the file menu).
What am I doing wrong?
The window that an event is targeted toward is decided by the window server before your app even receives it. It is not decided at the time of the call to -sendEvent:. The primary effect of -setIgnoresMouseEvents: is to inform the window server, not Cocoa's internals, how to dispatch mouse events.
Except for something like event taps, once you've received an event, it's too late to re-target it.
Note, for example, that the NSEvent already has an associated -window before your call to -sendEvent:. -sendEvent: is just going to use that to dispatch it.
If you want to allow clicks in the menu bar, you should either size your window so it doesn't overlap the menu bar or you should set its window level to be behind the menu bar.

Mac Cocoa: How to differentiate if a NSScrollWheel event is from a mouse or trackpad?

In my application, I want the scrolling to happen, only with scroll wheel action from a mouse and not from the two finger gesture on a trackpad. Basically, I am trying to determine if the scrollWheelEvent is generated from the mouse or trackpad, inside - (void)scrollWheel:(NSEvent *)theEvent method. From what I know so far, it seems like there is no straightforward way to accomplish this.
I tried a work around of setting a boolean variable to true and false inside -(void)beginGestureWithEvent:(NSEvent *)event; and -(void)endGestureWithEvent:(NSEvent *)event; But this is not a solution because the scrollWheel: method is getting called several times, after the endGestureWithEvent: method is called.
Here is my code:
$BOOL fromTrackPad = NO;
-(void)beginGestureWithEvent:(NSEvent *)event;
{
fromTrackPad = YES;
}
-(void) endGestureWithEvent:(NSEvent *)event;
{
fromTrackPad = NO;
}
- (void)scrollWheel:(NSEvent *)theEvent
{
if(!fromTrackPad)
{
//then do scrolling
}
else
{
//then don't scroll
}
}
I know this is something that is not standard, but this is my requirement. Does anyone know a way to do this?? Thanks!
-[NSEvent momentumPhase] is the solution. So, the events generated from the trackpad between the beginGesture and endGesture events returns a value other than NSEventPhaseNone for -[NSEvent phase] and the trackpad events that are generated after the endGesture event returns a value other than NSEventPhaseNone for -[NSEvent momentumPhase]. The code is below,
- (void)scrollWheel:(NSEvent *)theEvent
{
if(([theEvent momentumPhase] != NSEventPhaseNone) || [theEvent phase] != NSEventPhaseNone))
{
//theEvent is from trackpad
}
else
{
//theEvent is from mouse
}
}
You can use [event hasPreciseScrollingDeltas] to differentiate. It was added in OS X Lion. It differentiates between line scroll (mouse wheels) and pixel scroll (trackpads, magic mouse) events.
My answer in Swift, but for Objective C logic is the same:
override func scrollWheel(with event: NSEvent) {
if event.subtype == .mouseEvent {
// mouse
} else {
// trackpad
}
}
The answer given by #AProgrammer maybe not available. Because scrollwheel event generated by magic mouse has phase values of: began, changed and ended.
And scrollwheel event generated by mighty mouse has value of none for both phase and momentumphase.
So the method can only distinguish mighty mouse from magic mouse and trackpad.

cocoa - screen not refreshing after CGEventPost mouse click

I am programatically generating mouse clicks when a user clicks a certain keyboard key (CapsLock).
So I do a left mouse down when CapsLock is switched on, then a left mouse up when CapsLock is switched off.
This behaves correctly in that if I for example place the mouse over a window title bar, click CapsLock, then move the mouse, then click CapsLock, the window correctly moves. i.e. I correctly 'drag' the window as if I had held the left mouse button down whilst moving the mouse.
However there is one problem - the window does not move whilst I am moving the mouse, it only moves to the final position after I have clicked CapsLock a second time. i.e. after I have 'released' the mouse button.
What do I need to do to ensure the screen is refreshed during the mouse move?
Interestingly, I also hooked to
[NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDraggedMask
and found that my NSLog statement only output after I released the left mouse button (the real left mouse button)
Mouse click code is below, I can post all the code if necessary, there isn't much of it..
// simulate mouse down
// get current mouse pos
CGEventRef ourEvent = CGEventCreate(NULL);
CGPoint point = CGEventGetLocation(ourEvent);
NSLog(#"Location? x= %f, y = %f", (float)point.x, (float)point.y);
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
CGEventRef theEvent = CGEventCreateMouseEvent(source, kCGEventLeftMouseDown, point, kCGMouseButtonLeft);
CGEventSetType(theEvent, kCGEventLeftMouseDown);
CGEventPost(kCGHIDEventTap, theEvent);
CFRelease(theEvent);
// simulate mouse up
// get current mouse pos
CGEventRef ourEvent = CGEventCreate(NULL);
CGPoint point = CGEventGetLocation(ourEvent);
NSLog(#"Location? x= %f, y = %f", (float)point.x, (float)point.y);
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
CGEventRef theEvent = CGEventCreateMouseEvent(source, kCGEventLeftMouseUp, point, kCGMouseButtonLeft);
CGEventSetType(theEvent, kCGEventLeftMouseUp);
CGEventPost(kCGHIDEventTap, theEvent);
CFRelease(theEvent);
If you want to be able to drag windows, the problem is that you also need to post a LeftMouseDragged event.
Simply call beginEventMonitoring to start listening for caps lock key events and mouse move events. The event handlers will simulate a left mouse press and movement just as you wanted. Here is a link to my blog where you can download a full working example for Xcode 4: http://www.jakepetroules.com/2011/06/25/simulating-mouse-events-in-cocoa
The example is in the public domain, do whatever you like with it. :)
According to Apple (NSEvent documentation), "Enable access for assistive devices" needs to be checked in System Preferences > Universal Access for this to work, but I have it unchecked and it wasn't an issue. Just a heads up.
Please let me know if you have any further issues and I will try my best to help.
// Begin listening for caps lock key presses and mouse movements
- (void)beginEventMonitoring
{
// Determines whether the caps lock key was initially down before we started listening for events
wasCapsLockDown = CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, kVK_CapsLock);
capsLockEventMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:(NSFlagsChangedMask) handler: ^(NSEvent *event)
{
// Determines whether the caps lock key was pressed and posts a mouse down or mouse up event depending on its state
bool isCapsLockDown = [event modifierFlags] & NSAlphaShiftKeyMask;
if (isCapsLockDown && !wasCapsLockDown)
{
[self simulateMouseEvent: kCGEventLeftMouseDown];
wasCapsLockDown = true;
}
else if (wasCapsLockDown)
{
[self simulateMouseEvent: kCGEventLeftMouseUp];
wasCapsLockDown = false;
}
}];
mouseMovementEventMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:(NSMouseMovedMask) handler:^(NSEvent *event)
{
[self simulateMouseEvent: kCGEventLeftMouseDragged];
}];
}
// Cease listening for caps lock key presses and mouse movements
- (void)endEventMonitoring
{
if (capsLockEventMonitor)
{
[NSEvent removeMonitor: capsLockEventMonitor];
capsLockEventMonitor = nil;
}
if (mouseMovementEventMonitor)
{
[NSEvent removeMonitor: mouseMovementEventMonitor];
mouseMovementEventMonitor = nil;
}
}
-(void)simulateMouseEvent:(CGEventType)eventType
{
// Get the current mouse position
CGEventRef ourEvent = CGEventCreate(NULL);
CGPoint mouseLocation = CGEventGetLocation(ourEvent);
// Create and post the event
CGEventRef event = CGEventCreateMouseEvent(CGEventSourceCreate(kCGEventSourceStateHIDSystemState), eventType, mouseLocation, kCGMouseButtonLeft);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
}

Resources