NSTextField not noticing lost focus when pressing Tab? - cocoa

I can't seem to find a way to get notified when an NSTextField loses focus by pressing the Tab key. I get a nice textDidEndEditing when clicking another control or when pressing Enter, but not if I change the focus by pressing the Tab key.
Also tried to yank KeyDown and doCommandBySelector for this purpose but I got nowhere.
Any ideas?
Thanks in advance
Edit:
Forgot to mention, but I tried resignFirstResponder too. This is the code I tried:
- (BOOL)resignFirstResponder
{
NSRunAlertPanel(#"", #"Lost Focus",#"OK", nil, nil);
return [super resignFirstResponder];
}
- (BOOL)becomeFirstResponder
{
NSRunAlertPanel(#"", #"Got focus",#"OK", nil, nil);
return [super becomeFirstResponder];
}
Strangely, what happens here is that when getting focus, both becomeFirstResponder and resignFirstResponder are called one after the other. But when changing focus away from the control, neither are.

"I get a nice textDidEndEditing when
clicking another control or when
pressing Enter, but not if I change
the focus by pressing the Tab key."
As of April 2011, with OS X 10.6 libs, I'm using:
- (void)controlTextDidEndEditing:(NSNotification *)aNotification
...to listen for NSTextField losing focus, and it's working correctly. Is this possible in your situation? Is it something that used to be broken, but is now fixed by Apple?
If so, it's much less code :).

Ok, I've found a way to do it: use a window delegate to make the window return a custom field editor. This field editor keeps track of the last TextField that's been activated and calls its textDidEndEditting method when losing firstResponder itself. Here's an example of how to do it:
#import <Cocoa/Cocoa.h>
#import <AppKit/AppKit.h>
#interface MyTextField : NSTextField
- (BOOL)resignFirstResponder;
- (void)textDidEndEditing:(NSNotification *)notification;
#end
#interface MyFieldEditor : NSTextView
{
MyTextField * lastBox;
}
-(void) setLastEditBox:(MyTextField*) box;
#end
#interface MyWindowDelegate : NSWindowController
{
MyFieldEditor *fieldEditor;
}
#end
#implementation MyFieldEditor
-(void) setLastEditBox:(MyTextField*) box{ lastBox = box; }
-(id)init
{
if (self = [super init])
[self setFieldEditor:YES];
return self;
}
- (BOOL)resignFirstResponder
{
// Activate the last active editbox editting-end event
if(lastBox != nil)
{
[lastBox textShouldEndEditing:self];
lastBox = nil;
}
return [super resignFirstResponder];
}
#end
#implementation MyWindowDelegate
-(id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client
{
if(fieldEditor == nil) // Return our special field editor
fieldEditor = [[[MyFieldEditor alloc] autorelease] init];
return fieldEditor;
}
#end
#implementation MyTextField
- (BOOL)resignFirstResponder
{
// We're losing first responder, inform the field editor that this was the last edit box activated
MyFieldEditor* myTf = (MyFieldEditor*) [[self window] fieldEditor:YES forObject:self];
[myTf setLastEditBox:self];
return [super resignFirstResponder];
}
- (void)textDidEndEditing:(NSNotification *)notification;
{
[super textDidEndEditing:notification];
[self setStringValue:#"RECEIVED ENDEDITING"];
}
#end
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSApplication *app = [NSApplication sharedApplication];
NSRect frame = NSMakeRect(100, 100, 200, 150);
// Create the window
NSWindow* window = [[[NSWindow alloc] autorelease ] initWithContentRect:frame styleMask:NSClosableWindowMask|NSResizableWindowMask
backing:NSBackingStoreBuffered defer:NO];
[window setDelegate:[[MyWindowDelegate alloc] autorelease]];
MyTextField * tf = [ [[ MyTextField alloc ] autorelease] initWithFrame: NSMakeRect( 30.0, 100.0, 150.0, 22.0 ) ];
[ [ window contentView ] addSubview: tf ];
MyTextField * tf2 = [ [[ MyTextField alloc ] autorelease] initWithFrame: NSMakeRect( 30.0, 40.0, 150.0, 22.0 ) ];
[ [ window contentView ] addSubview: tf2 ];
[window makeKeyAndOrderFront: window];
[app run];
[pool release];
return 0;
}

You have to do only this
For key Tab
self.textfield.delegate = self;
and then implement this method
- (void)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
{
NSLog(#"Selector method is (%#)", NSStringFromSelector( commandSelector ) );
if (commandSelector == #selector(insertTab:)) {
//Do something against TAB key
//Or Call a Method
}
}
or see my answer at
Execute an Action when the Enter-Key is pressed in a NSTextField?

With the understanding that I mentioned in my other post, I figured out an answer. It's a little convoluted but it works. You have to subclass both the NSTextField and the NSWindow because you need information from both to set this up. Here's the subclasses:
HMTextField.h
#import <Foundation/Foundation.h>
#interface HMTextField : NSTextField {
}
#end
HMTextField.m
#import "HMTextField.h"
#import "HMWindow.h"
#implementation HMTextField
- (BOOL)becomeFirstResponder {
[(HMWindow*)[self window] setTfBecameFirstResponder:YES];
return [super becomeFirstResponder];
}
#end
HMWindow.h
#import <Foundation/Foundation.h>
#interface HMWindow : NSWindow {
BOOL tfIsFirstResponder, tfBecameFirstResponder;
}
#property (nonatomic, readwrite, assign) BOOL tfBecameFirstResponder;
#end
HMWindow.m
#import "HMWindow.h"
#implementation HMWindow
#synthesize tfBecameFirstResponder;
-(id)init {
if (self = [super init]) {
tfIsFirstResponder = NO;
}
return self;
}
- (NSResponder *)firstResponder {
id fr = [super firstResponder];
if ([fr isEqualTo:[self fieldEditor:NO forObject:nil]]) {
tfIsFirstResponder = YES;
} else {
if (tfIsFirstResponder && tfBecameFirstResponder) {
NSLog(#"the text field stopped being first responder");
tfBecameFirstResponder = NO;
}
tfIsFirstResponder = NO;
}
return fr;
}
#end
Make the classes and make your objects their class. You'll be notified of the first responder change from your text field where the NSLog message is in the HMWindow.m file. If you need help understanding how it works let me know.

Here's an example of how to indicate the appropriate time a custom NSTextFieldCell (NSCell) should draw its own bezel & focus ring (in the method [NSTextFieldCell drawWithFrame: inView]), by 'borrowing' the cell's highlight field, setting it when the text field gains focus, and clearing it when the text field loses focus (editing completes).
This technique overcomes some problems:
The cell can't easily determine if it has focus.
The cell can't easily determine which higher level component (e.g. text field or button) it belongs to to track via its parent
NSTextField can instantaneously resign first responder after gaining it, which could make it seem like it lost user focus when it didn't.
Since we're re-purposing the cell's "highlighted" state field, in order to communicate the focus state to the cell, be sure to return nil from the custom NSTextFieldCell's [highlightColorWithFrame: inView:] method.
#import "CustomTextField.h"
#implementation CustomTextField
-(BOOL)becomeFirstResponder {
((NSTextFieldCell *)self.cell).highlighted = true;
return [super becomeFirstResponder];
}
-(void)textDidEndEditing:(NSNotification *)notification {
((NSTextFieldCell *)self.cell).highlighted = false;
[super textDidEndEditing:notification];
}
#end

Complex answers. There is a simpler way to do it.
Don't forget to subclass your NSTextField to NotificableTextField and set its delegate to your view controller.
NotificableTextField.h:
#import <Cocoa/Cocoa.h>
#protocol NotificableTextFieldDelegate <NSObject>
#optional
- (void)textFieldStartedEditing:(NSTextField *)textField;
- (void)textFieldEndedEditing:(NSTextField *)textField;
#end
#interface NotificableTextField : NSTextField
#end
NotificableTextField.m:
#import "NotificableTextField.h"
#implementation NotificableTextField
- (void)awakeFromNib
{
[super awakeFromNib];
self.target = self;
self.action = #selector(inputEnd);
}
- (BOOL)becomeFirstResponder
{
BOOL status = [super becomeFirstResponder];
if (status && [self.delegate respondsToSelector:#selector(textFieldStartedEditing:)])
[(id<NotificableTextFieldDelegate>)self.delegate textFieldStartedEditing:self];
return status;
}
- (void)inputEnd
{
if ([self.delegate respondsToSelector:#selector(textFieldEndedEditing:)])
[(id<NotificableTextFieldDelegate>)self.delegate textFieldEndedEditing:self];
}
#end

NSTextField is a subclass of NSResponder. NSResponder has a method - (BOOL)resignFirstResponder. That will notify you when the NSTextField is no longer first responder... ie. loses focus. So subclass your NSTextField and do your stuff in there.

Related

NSOutlineView show indentation marker

How do I generate indentation marker for NSOutlineView?
I am not sure if this is an inbuilt functionality because it appears in other apps like Instruments
Update
I tried solving the problem by iterating all the children of the item that the row represents and show the marker on all children rows based on indentation level, but I faced a few problems
How to handle the case where the item has thousands of children. One simply cannot draw marker to every row as NSOutlineView would draw rows as they are displayed
When I scroll the NSOutlineView, the mouse moves out of the specified row but mouseExited is not being called. Thus the user has to manually move the mouse to reload the highlighting.
I had solved this problem but my solution looks hacky hence wanted to know if there is a better solution. And hence the question
First to receive mouseEntered: and mouseExited: events you need to setup a tracking rect using NSTrackingArea.
I would start with a subclass of NSTableRowView thats overwrites setFrame: making sure the tracking rect gets updated when the view is resize:
#interface TableRowView : NSTableRowView {
NSBox *_box;
NSTrackingArea *_trackingArea;
}
#property (weak) id owner;
#property (copy) NSDictionary<id, id> *userInfo;
#property (nonatomic) CGFloat indentation;
#property (nonatomic) BOOL indentationMarkerHidden;
#end
#implementation TableRowView
- (void)setFrame:(NSRect)frame
{
[super setFrame:frame];
if (_trackingArea) {
[self removeTrackingArea:_trackingArea];
}
_trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInKeyWindow owner:[self owner] userInfo:[self userInfo]];
[self addTrackingArea:_trackingArea];
}
#end
To use the NSTableRowView subclass, implement the NSOutlineViewDelegate messages like this:
- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
{
TableRowView *view = [[TableRowView alloc] init];
view.owner = self;
view.userInfo = item;
return view;
}
With this in place you're ready to receive mouseEntered: and mouseExited: events. Use NSOutlineView levelForItem: together with indentationPerLevel to calculate the position of the marker NSBox.:
- (void)mouseEntered:(NSEvent *)event
{
id item = [event userData];
CGFloat indentation = [_outlineView levelForItem:item] * [_outlineView indentationPerLevel];
[self setIndentationMarker:indentation hidden:NO item:item];
}
- (void)mouseExited:(NSEvent *)event
{
id item = [event userData];
[self setIndentationMarker:0.0 hidden:YES item:item];
}
Now you get the NSTableRowView subclass by rowViewAtRow:makeIfNecessary: and recursively do the same for all children in your data:
- (void)setIndentationMarker:(CGFloat)indentation hidden:(BOOL)hidden item:(NSDictionary *)item
{
TableRowView *view = [_outlineView rowViewAtRow:[_outlineView rowForItem:item] makeIfNecessary:NO];
view.indentationMarkerHidden = hidden;
view.indentation = indentation;
for (NSMutableDictionary *child in [item objectForKey:#"children"]) {
[self setIndentationMarker:indentation hidden:hidden item:child];
}
}
Now layout the NSBox the NSTableRowView subclass:
#implementation TableRowView
- (instancetype)init
{
self = [super init];
if (self) {
_indentationMarkerHidden = YES;
_box = [[NSBox alloc] init];
_box.boxType = NSBoxCustom;
_box.borderWidth = 0.0;
_box.fillColor = [NSColor tertiaryLabelColor];
_box.hidden = _indentationMarkerHidden;
[self addSubview:_box];
}
return self;
}
- (void)layout
{
[super layout];
NSRect rect = [self bounds];
rect.origin.x = _indentation + 7;
rect.size.width = 10;
_box.frame = rect;
}
- (void)setIndentation:(CGFloat)indentation
{
_indentation = indentation;
[self setNeedsLayout:YES];
}
- (void)setIndentationMarkerHidden:(BOOL)indentationMarkerHidden
{
if (_indentationMarkerHidden != indentationMarkerHidden) {
_indentationMarkerHidden = indentationMarkerHidden;
_box.hidden = indentationMarkerHidden;
}
}
#end
This enough to make a basic version like here:

Cocoa: NSToolBar with Custom Views. Animation issues

So I got a Window:
Window.xib
I got a WindowController too:
WindowController.h reads:
#import <Cocoa/Cocoa.h>
#interface MainWindowController : NSWindowController
{
IBOutlet NSView *firstView;
IBOutlet NSView *secondView;
IBOutlet NSView *thirdView;
int currentViewTag;
}
-(IBAction)switchView:(id)sender;
#end
And the WindowController.m reads:
#import "MainWindowController.h"
#interface MainWindowController ()
#end
#implementation MainWindowController
-(id)init
{
self = [super initWithWindowNibName:#"MainWindow"];
if (self){
// Initialization code here
}
return self;
}
//- (void)windowDidLoad {
// [super windowDidLoad];
//
// // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
//}
#pragma mark - Custom view drawing
-(NSRect)newFrameForNewContentView:(NSView *)view
{
NSWindow *window = [self window];
NSRect newFrameRect = [window frameRectForContentRect:[view frame]];
NSRect oldFrameRect = [window frame];
NSSize newSize = newFrameRect.size;
NSSize oldSize = oldFrameRect.size;
NSRect frame = [window frame];
frame.size = newSize;
frame.origin.y -= (newSize.height - oldSize.height);
return frame;
}
-(NSView *)viewForTag:(int)tag{
NSView *view = nil;
if (tag == 0) {
view = firstView;
} else if (tag == 1) {
view = secondView;
} else {
view = thirdView;
}
return view;
}
-(BOOL) validateToolbarItem:(NSToolbarItem *)item
{
if ([item tag] == currentViewTag) return NO;
else return YES;
}
-(void)awakeFromNib
{
[[self window] setContentSize:[firstView frame].size];
[[[self window] contentView]addSubview:firstView];
[[[self window] contentView]setWantsLayer:YES];
}
-(IBAction)switchView:(id)sender
{
int tag = [sender tag];
NSView *view = [self viewForTag:tag];
NSView *previousView = [self viewForTag:currentViewTag];
currentViewTag = tag;
NSRect newFrame = [self newFrameForNewContentView:view];
[NSAnimationContext beginGrouping];
if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask)
[[NSAnimationContext currentContext] setDuration:1.0];
[[[[self window]contentView]animator]replaceSubview:previousView with:view];
[[[self window]animator]setFrame:newFrame display:YES];
[NSAnimationContext endGrouping];
}
#end
The problem I have is that when i switch tabs in my app, the custom view (and there are three of different sizes) draw differently each time. Look at the screenshots, all of the numbers should be centre aligned but they are sometimes and others not. Can anyone see what my error is please?
I will also add that all of the actions have been correctly configured + the code works perfectly if the custom view size is the same all the time.
The view that works
The view that almost works
Again pointing out that in my .xib all of the numbers are aligned to 0x and 0y axis.
Appdelegate.h
#import <Cocoa/Cocoa.h>
#class MainWindowController;
#interface AppDelegate : NSObject <NSApplicationDelegate> {
MainWindowController *mainWindowController;
}
#end
Appdelegate.m
#interface AppDelegate ()
#property (nonatomic) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
-(void)awakeFromNib
{
if(!mainWindowController){
mainWindowController = [[MainWindowController alloc]init];
}
[mainWindowController showWindow:nil];
}
#end
In Interface Builder, make sure to disable the "autoresizes subviews" checkbox of the default view of your window

Cocoa Drag and Drop delegates not working the way I would expect

I made a very simple class that subclasses NSTextField so I could have more control over the drag and drop behavior, but I'm running into confusion. According to what I think I understand in the apple.developer docs on Dragging destinations, I should be able to get the delegates to fire when I enter or exit the bounds of the text field, but what I get instead is that the delegates don't fire until I have dropped something into the textField and then I try to drag it out.
I have a MyController class which is an NSObject and I have a CustomFields class of type NSTextField; There is nothing in the controller class.
Here is the code:
// CustomFields.h
// Drags
//
#import <AppKit/AppKit.h>
#import <Foundation/foundation.h>
#interface CustomFields : NSTextField{
NSString *tempStorage_;
unsigned long last_;
}
#property(readwrite, retain) NSString *tempStorage;
#end
//
// CustomFields.m
// Drags
//
#import "CustomFields.h"
#implementation CustomFields
#synthesize tempStorage = tempStorage_;
- (id)init{
if (self) {
self = [super init];
}
return self;
}
-(void)dealloc{
[self.tempStorage release ];
[super release];
}
-(void)awakeFromNib{
NSLog(#"Awake from nib called");
self.tempStorage = #"";
self->last_ = 0;
[self setBackgroundColor:[NSColor lightGrayColor]];
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSPasteboardTypeString , NSPasteboardTypeString, nil]];
[super awakeFromNib];
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
NSLog(#"Inside dragging entered");
return NSDragOperationCopy;
}
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
NSLog(#" draggingUpdated called");
return NSDragOperationCopy;
}
- (void)draggingExited:(id <NSDraggingInfo>)sender
{
NSLog(#"Inside dragging exited");
}
#end
//
// MyController.h
// Drags
//
#import <Foundation/Foundation.h>
#class CustomFields;
#interface MyController : NSObject{
IBOutlet NSButton *myButton_;
IBOutlet CustomFields *field1_;
IBOutlet CustomFields *field2_;
}
#end
//
// MyController.m
// Drags
//
#import "MyController.h"
#import "CustomFields.h"
#implementation MyController
#end
I made sure I assigned the correct class to the fields(CustomFields) in IB and connected them. As you can see, for the moment I would be happy to get log statements at the proper time. I also tried changing the MyController class to NSViewController and NSView so I could try registerForDraggedTypes there. That did'nt help anything and I don't think I should expect it to. I think the behaviors I want should be confined to the CustomFields Class which is one of the reasons the MyController class can be empty for the moment.
So any help is much apprecieted.

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