Mouse events handlers - objective-c

MyNSImageView is a subclass of NSImageView, here I have:
#interface MyNSImageView : NSImageView
{
}
#end
#implementation MyNSImageView
//- (void) mouseDown: (NSEvent *) theEvent
//{
// do not wish to implement mouseDown event handler from here
//}
#end
In another class called MainView, I have:
#interface MainView : NSView
{
MyNSImageView *ImageView1;
MyNSImageView *ImageView2;
}
#end
- (void)awakeFromNib
{
ImageView1 = [[[MyNSImageView alloc] initWithFrame:NSMakeRect(5, 5, 240, 240)] autorelease];
NSImage* image1 = [[[NSImage alloc] initWithContentsOfFile: #"/Volumes/MAC DAT2/pictures/MP6107.jpg"] autorelease];
[ImageView1 setImage:image1];
[self addSubview:ImageView1];
ImageView2 = [[[MyNSImageView alloc] initWithFrame:NSMakeRect(300, 5, 240, 240)] autorelease];
image1 = [[[NSImage alloc] initWithContentsOfFile: #"/Volumes/MAC DAT2/pictures/MP5784.jpg"] autorelease];
[ImageView2 setImage:image1];
[self addSubview:ImageView2];
}
- (void) mouseDown2: (NSEvent *) theEvent
{
NSLog(#"mousedown2 from MainView");
}
- (void) mouseDown1: (NSEvent *) theEvent
{
NSLog(#"mousedown1 from MainView");
}
#end
- (void) mouseDown: (NSEvent *) theEvent
{
NSLog(#"mousedown from MainView");
}
In the MainView, when I click on the ImageView1 or ImageView2, I would like to have the mouseDown1 or mouseDown2 method to handle the event accordingly not the mouseDown method.
I have read about target/action/delegate and responder stuff, but still could not see the exact syntax to do this.

One way to handle this is with a delegate:
First you declare a delegate protocol for your NSImageView subclass:
#class MyNSImageView;
#protocol MyNSImageViewDelegate <NSObject>
- (void)myImageView:(MyNSImageView *)view mouseDown:(NSEvent *)event;
#end
#interface MyNSImageView : NSImageView {
}
// declare the delegate member
#property (assign) id<MyNSImageViewDelegate> delegate;
#end
#implementation MyNSImageView
#synthesize delegate = _delegate;
// In your mouseDown method, notify the delegate
- (void)mouseDown:(NSEvent *)event {
if([self.delegate respondsToSelector:#selector(myImageView:mouseDown:)]) {
[self.delegate myImageView:self mouseDown:event];
}
}
#end
Then, declare your MainView class to implement the MyNSImageViewDelegate protocol:
#interface MainView : NSView <MyNSImageViewDelegate> {
MyNSImageView *imageView1;
MyNSImageView *imageView2;
}
#end
And in your MainView implementation:
- (void)awakeFromNib {
// create your image views then add our instance as the delegate to each
ImageView1.delegate = self;
ImageView2.delegate = self;
}
// here we implement the `MyNSImageViewDelegate` method, which will get
// called when any `MyImageNSView` instance we've added ourselves as
// delegate to gets clicked.
- (void)myImageView:(MyNSImageView *)view mouseDown:(NSEvent *)event {
if (view == imageView1) {
NSLog(#"imageView1 clicked");
} else if (view == imageView2) {
NSLog(#"imageView2 clicked");
}
}

You should read about the responder chain. For MyCallingClass's -mouseDown: method to be called, an instance of that class has to be in the current responder chain, and no other responder further down the chain should handle that event.

Related

COCOA application crashed when trying to get the value from inactive dialog

I have created a main dialog (MainMenu.xib) and created two button on mainmenu.xib file and when click on each button, it will launch different xib file(say for button1 xib file is button1.xib and for button2 xib file is button2.xib file).
Now, My question I need the value of button1.xib file into button2.xib file.
I have tried the code
id appDelegate = (AppDelegate*)[[NSApplication sharedApplication] delegate] ;
this code always gives the delegate of active dialog.
Can you please tell me how to get the control or object of inactive dialog?
Thanks,
What you need is model object. Below is really simple approach how you should start. This model object should be stored in AppDelegate for non-document based app. Second window should not know about existence of any other window. The same applies for first window. It shouldn't know more that displaying/working with model.
Accessing model through delegate in fact accessing delegate like below is considered as really, really, really bad.
id model = [(AppDelegate*)[[NSApplication sharedApplication] delegate] model];
Simple example how you can implement model and monitor for changes
#import "AppDelegate.h"
#import "FirstWC.h"
#import "SecondWC.h"
#import "Model.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (nonatomic, strong) FirstWC *firstWC;
#property (nonatomic, strong) SecondWC *secondWC;
#property (nonatomic, strong) Model *model;
#end
#implementation AppDelegate
- (instancetype)init
{
self = [super init];
if (self) {
_model = [[Model alloc] init];
}
return self;
}
- (IBAction)changeColor:(id)sender
{
NSUInteger number = arc4random() % 4;
switch (number) {
case 0:
self.model.color = [NSColor redColor];
break;
case 1:
self.model.color = [NSColor blueColor];
break;
case 2:
self.model.color = [NSColor purpleColor];
break;
case 3:
self.model.color = [NSColor yellowColor];
break;
default:
break;
}
}
- (IBAction)showFirstWC:(id)sender
{
if (!_firstWC) {
_firstWC = [[FirstWC alloc] initWithModel:self.model];
}
[[_firstWC window] makeKeyAndOrderFront:self];
}
- (IBAction)showSecondWC:(id)sender
{
if (!_secondWC) {
_secondWC = [[SecondWC alloc] initWithModel:self.model];
}
[[_secondWC window] makeKeyAndOrderFront:self];
}
#end
Model:
#import <Cocoa/Cocoa.h>
#interface Model : NSObject
#property (nonatomic, strong) NSColor *color;
#end
#import "Model.h"
#implementation Model
- (instancetype)init
{
self = [super init];
if (self) {
_color = [NSColor blueColor];
}
return self;
}
#end
Window Controller:
#import "FirstWC.h"
#import "Model.h"
#interface FirstWC ()
#property (nonatomic, strong) Model *model;
#end
#implementation FirstWC
- (instancetype)initWithModel:(Model *)model
{
self = [super initWithWindowNibName:#"FirstWC"];
if (self) {
_model = model;
[self addObserver:self
forKeyPath:#"model.color"
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
- (void)windowDidLoad {
[super windowDidLoad];
self.window.backgroundColor = [self model].color;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:#"model.color"]) {
self.window.backgroundColor = self.model.color;
}
}
- (void)dealloc
{
[self removeObserver:self forKeyPath:#"model.color"];
}
#end
#import "SecondWC.h"
#interface SecondWC ()
#property (nonatomic, strong) Model *model;
#end
#implementation SecondWC
- (instancetype)initWithModel:(Model *)model
{
self = [super initWithWindowNibName:#"SecondWC"];
if (self) {
_model = model;
[self addObserver:self
forKeyPath:#"model.color"
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
- (void)windowDidLoad {
[super windowDidLoad];
self.window.backgroundColor = [self model].color;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:#"model.color"]) {
self.window.backgroundColor = self.model.color;
}
}
- (void)dealloc
{
[self removeObserver:self forKeyPath:#"model.color"];
}
#end
Add properties for both button1 and button2 in your controller, and a (weak) property for button2 in the button1 controller, and vice versa.
- (void)createButtons {
DlgController1 = [[Button1 alloc] initWithWindowNibName:#"Button1"];
DlgController2 = [[Button2 alloc] initWithWindowNibName:#"Button2"];
DlgController1.button2 = DlgController2;
DlgController2.button1 = DlgController1;
}
- (IBAction)Button1:(id)sender {
[DlgController1 showWindow:self];
[NSApp runModalForWindow:DlgController1.window];
[NSApp endSheet:DlgController1.window];
[DlgController1.window orderOut:self];
[DlgController1 close];
}
I have tried the code
id appDelegate = (AppDelegate*)[[NSApplication sharedApplication] delegate] ;
this code always gives the delegate of active dialog.
I'm not quite sure how this relates to the rest of your question, but the above issue suggests that you have an instance of your AppDelegate class in button1.xib and button2.xib. You should not.
There is only one application object in your app. It should have only one delegate. That instance is created in the MainMenu NIB and connected to the application's delegate outlet using the File's Owner placeholder's outlet. That should be the only instance of AppDelegate in your whole app.
If you also create an instance of AppDelegate in the other NIBs, then there will be multiple instances of that class once those NIBs are loaded, which will just confuse things. Also, if those instances are also connected to the application's delegate outlet, then they will replace the original delegate. That explains the symptom you described above where [[NSApplication sharedApplication] delegate] gives an object from the last NIB to load.
I'm not sure what button1.xib and button2.xib are supposed to contain. I'm guessing they are supposed to contain a window. In that case, you should write a custom subclass of NSWindowController to be the window controller for each window NIB. You would instantiate that class in the button's action method and provide it with whatever information it needs (possibly including a reference to the app delegate). That controller class would be responsible for loading the NIB. It would serve as the NIB's owner and would be represented in the NIB by the File's Owner placeholder. It's pretty common to make the window controller be the window's delegate, too. I recommend that you follow the advice of this blog post.

OSX: Why my app needs click on it first to receive keydown event?

I currently developing a small dictionary app under OSX for my own use, I would like to have a feature that when I hit the return key, the focus would go to the nssearchfeild.
So I try to make the app to receive keyDown event using a NSView and NSViewController told by this tutorial.
But every time I start the app, it wouldn't receive the keyDown event. I have to click on the window once, then hit the keyboard, so that it can receive keyDown event.
What did I do wrong? Can anyone help me out with this problem? I have been stuck in this problem for days, and searching throught Google and API wouldn't help much.
Thanks in advance!
Here is my code for AppDelegate.m
#import "AppDelegate.h"
#import "MyDictViewController.h"
#interface AppDelegate()
#property (nonatomic,strong) IBOutlet MyDictViewController *viewController;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.viewController = [[MyDictViewController alloc] initWithNibName:#"MyDictViewController" bundle:nil];
[self.window.contentView addSubview:self.viewController.view];
self.viewController.view.frame = ((NSView*)self.window.contentView).bounds;
[self.window makeKeyAndOrderFront:nil];
}
#end
And My ViewController.m
#import "MyDictViewController.h"
#import "FileHelper.h"
#import <Carbon/Carbon.h>
#interface MyDictViewController ()
#property (weak) IBOutlet NSTableView *wordsFilteredTable;
#end
#implementation MyDictViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.IMAGE_FILE = [NSImage imageNamed:#"Document.png"];
self.wordlist = [FileHelper readLines];
self.filterWordlist = [[NSMutableArray alloc] init];
}
return self;
}
- (void)loadView
{
[super loadView];
[self.view becomeFirstResponder];
}
-(void)keyDown:(NSEvent*)theEvent
{
NSLog(#"Caught key event");
}
-(void)keyUp:(NSEvent *)theEvent
{
unsigned short keycode = [theEvent keyCode];
switch (keycode)
{
case kVK_Return:
[self.searchField becomeFirstResponder];
default:
break;
}
}
-(void)mouseDown:(NSEvent*)theEvent
{
NSLog(#"Caught mouse event");
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
self.wordsFilteredTable.rowHeight = 37;
NSTableCellView *cellView = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
if( [tableColumn.identifier isEqualToString:#"WordColumn"] )
{
NSString *word = [self.filterWordlist objectAtIndex:row];
cellView.textField.stringValue = word;
cellView.imageView.image = self.IMAGE_FILE;
return cellView;
}
return cellView;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [self.filterWordlist count];
}
- (void)controlTextDidChange:(NSNotification *)obj
{
NSTextView* textView = [[obj userInfo] objectForKey:#"NSFieldEditor"];
self.currentWord = [textView string];
[self.filterWordlist removeAllObjects];
for(NSString* word in self.wordlist) {
if ([word hasPrefix:self.currentWord]) {
[self.filterWordlist addObject:word];
}
}
[self.wordsFilteredTable reloadData];
}
#end
And my AppView.m
#import "AppView.h"
#implementation AppView
- (void)setViewController:(NSViewController *)newController
{
if (viewController)
{
[super setNextResponder:[viewController nextResponder]];
[viewController setNextResponder:nil];
}
viewController = newController;
if (newController)
{
[super setNextResponder: viewController];
[viewController setNextResponder:[self nextResponder]];
}
}
- (void)setNextResponder:(NSResponder *)newNextResponder
{
if (viewController)
{
[viewController setNextResponder:newNextResponder];
return;
}
[super setNextResponder:newNextResponder];
}
#end

How to clear all object on NSView in Cocoa

I want to clear all objects which added to NSVIew before call any function.
How can I do that?
I use the following function
-(void)clearAllSubviewsOfView :(NSView *)parent
{
for (NSView *subview in [parent subviews]) {
[subview removeFromSuperview];
}
}
You can make something like this:
// TSClearSupporting.h
#protocol TSClearSupporting <NSObject>
- (void) clear;
#end
// TSTextField.h
#import <Cocoa/Cocoa.h>
#import "TSClearSupporting.h"
#interface TSTextField : NSTextField <TSClearSupporting>
#end
// TSTextField.m
#import "TSTextField.h"
#implementation TSTextField
- (void) clear
{
self.stringValue = #"";
}
#end
// TSMainView.m
#import "TSMainView.h"
#import "TSClearSupporting.h"
#implementation TSMainView
- (IBAction) clearAll: (id)sender
{
NSArray* subViews = self.subviews;
for (NSView* view in subViews)
{
if ([view conformsToProtocol: #protocol(TSClearSupporting)])
{
[view performSelector: #selector(clear)];
}
}
}

keyDown not being called

I have a custom NSView called SurfaceView. It is the contentView of a NSWindow and it handles basic events like mouse click and drawing. But don't matters what I do, it does not handle the keyDown function. I've already override the acceptsFirstResponder but nothing happens.
If it matters, I run the application using a custom NSEvent loop, shown below:
NSDictionary* info = [[NSBundle mainBundle] infoDictionary];
NSString* mainNibName = [info objectForKey:#"NSMainNibFile"];
NSApplication* app = [NSApplication sharedApplication];
NSNib* mainNib = [[NSNib alloc] initWithNibNamed:mainNibName bundle:[NSBundle mainBundle]];
[mainNib instantiateNibWithOwner:app topLevelObjects:nil];
[app finishLaunching];
while(true)
{
NSEvent* event = [app nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate date] inMode:NSDefaultRunLoopMode dequeue:YES];
[app sendEvent:event];
// Some code is execute here every frame to do some tasks...
usleep(5000);
}
Here's the SurfaceView code:
#interface SurfaceView : NSView
{
Panel* panel;
}
#property (nonatomic) Panel* panel;
- (void)drawRect:(NSRect)dirtyRect;
- (BOOL)isFlipped;
- (void)mouseDown:(NSEvent *)theEvent;
- (void)mouseDragged:(NSEvent *)theEvent;
- (void)mouseUp:(NSEvent *)theEvent;
- (void)keyDown:(NSEvent *)theEvent;
- (BOOL)acceptsFirstResponder;
- (BOOL)becomeFirstResponder;
#end
--
#implementation SurfaceView
#synthesize panel;
- (BOOL)acceptsFirstResponder
{
return YES;
};
- (void)keyDown:(NSEvent *)theEvent
{
// this function is never called
};
...
#end
Here's how I create the view:
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(left, top, wide, tall) styleMask:NSBorderlessWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask backing:NSBackingStoreBuffered defer:NO];
...
[window makeKeyAndOrderFront:nil];
SurfaceView* mainView = [SurfaceView alloc];
[mainView initWithFrame:NSMakeRect(0, 0, wide, tall)];
mainView.panel = panel;
[window setContentView:mainView];
[window setInitialFirstResponder:mainView];
[window setNextResponder:mainView];
[window makeFirstResponder:mainView];
I found out what was preventing the keyDown event from being called. It was the NSBorderlessWindowMask mask, it prevents the window from become the key and main window. So I have created a subclass of NSWindow called BorderlessWindow:
#interface BorderlessWindow : NSWindow
{
}
#end
#implementation BorderlessWindow
- (BOOL)canBecomeKeyWindow
{
return YES;
}
- (BOOL)canBecomeMainWindow
{
return YES;
}
#end
In addition to answer: Check in your IB checkbox for NSWindow.
Title Bar should be checked. It the similar to NSBorderlessWindowMask
In my case I had to override the keyDown method on both NSView and NSWindow (created a customization of both). In the NSWindow in the keyDown override I had to call
- (void) keyDown:(NSEvent *) event {
if (self.firstResponder != self) {
[self.firstResponder keyDown:event];
}
}
to let the event reach the view. Of course I had also to call makeFirstResponder first on the NSWindow and pass the contentView to it :
[window makeFirstResponder: [window contentView] ];

CCSprite not rendering when made in a method from an external class

I'm sure this is really obvious to someone, but this simple thing is really frustrating me.
I have a class I made called Class_Sprite, which is a sub-class of CCSprite.
I have a method in this class that is supposed to both create the texture for any given instance of Class_Sprite, and then move it to (200,200).
The program runs in the sim but all I get is a black screen.
I was able to render the sprite directly from the layer class.
Here are the files.
Class_Sprite:
#import "Class_Sprite.h"
#implementation Class_Sprite
-(id)init
{
if ((self = [super init]))
{
}
return self;
}
-(void)make:(id)sender
{
sender = [Class_Sprite spriteWithFile:#"Icon.png"];
[sender setPosition: ccp(200, 200)];
}
#end
Class Sprite header:
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface Class_Sprite : CCSprite {
}
-(void)make:(id)sender;
#end
HelloWorldLayer.m (where the method is being called)
#implementation HelloWorldLayer
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
// on "init" you need to initialize your instance
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super" return value
if( (self = [super init])) {
Class_Sprite *pc = [[Class_Sprite alloc] init];
[pc make:self]; //here is where I call the "make" method
[self addChild:pc];
[pc release];
}
return self;
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
// in case you have something to dealloc, do it in this method
// in this particular example nothing needs to be released.
// cocos2d will automatically release all the children (Label)
// don't forget to call "super dealloc"
[super dealloc];
}
#end
And finally the header file for HelloWorldLayer
#import "cocos2d.h"
#import "Class_Sprite.h"
// HelloWorldLayer
#interface HelloWorldLayer : CCLayer
{
}
// returns a CCScene that contains the HelloWorldLayer as the only child
+(CCScene *) scene;
#end
Thanks for your time
Try changing to this in Class_Sprite.m:
#implementation Class_Sprite
-(id)init
{
if ((self = [super initWithFile:#"Icon.png"]))
{
}
return self;
}
-(void)make:(CCNode *)sender
{
[self setPosition: ccp(200, 200)];
[sender addChild:self];
}
#end
And use it in HelloWorldLayer as follows:
Class_Sprite *pc = [[Class_Sprite alloc] init];
[pc make:self];
[pc release];