Delegate gets destroyed - c++

I'm building a cross platform app, and 1 of those platforms is Macos.
Now the core of my code is written in C++, and Obj-C++.
I create a window like this:
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(x, y, width, height) styleMask:macosWindowStyle backing:NSBackingStoreBuffered defer:false];
but I wanted to listen to the window. I could've subclassed it, but I chose not to.
The other way to get events from the NSWindow was to set a delegate.
Now since my code was in Obj-C++, I couldn't have a C++ class inherit from a Obj-C protocol.
So, I created a Obj-C header, which implemented NSWindowDelegate.
Here it is:
#interface SomeClass : NSObject<NSWindowDelegate>
#end
I overrode windowShouldClose as such:
- (BOOL)windowShouldClose:(NSWindow *)sender {
NSLog(#"Hello!");
return true;
}
and in my Obj-C++ file, I did this:
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(x, y, width, height) styleMask:macosWindowStyle backing:NSBackingStoreBuffered defer:false];
SomeClass* someClass = [[SomeClass alloc] init];
[window setDelegate:someClass];
However, when I pressed the X button, nothing happened.
I then proceeded to test the same thing in Swift, same result.
I then realized that the delegate was being destroyed because it was a weak reference.
My question is, how do I get around this?

OK, I figured it out.
I for some reason thought I could not have Obj-C class pointers in my Obj-C++ code. I thought that was one of the limitations.

Related

Register Global Hotkey under Qt and Mac OS X

Having read through various posts and threads that lead me nowhere I need your help.
I do have a Qt Application for Mac OS X that at some point of use will be in the background and not active. When this is the case I want to add a global hotkey so that the user can easily turn certain features on or off by clicking pre-defined hotkeys.
The following isn't working while the app is in the background and not focused.
QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_F12), parent);
shortcut->setContext(Qt::ApplicationShortcut);
So far I found Qxt which happens to be outdated for Qt 5.5.
Then there is DDHotkey which requires a certain compiler which we can not use for various reasons.
Lastly, I found the solution of adding a global AppleScript which registers an event, again, not what I am looking for.
tell application "System Events" to tell process "myApp"
click menu item "myButton" of menu 1 of menu bar item "Menu" of menu bar 1
end tell
Is there a way to use objective-c or cocoa to accomplish exactly what I am looking for?
Please lead me in the right direction if I may have missed something.
Thanks in advance!
To those who seek a more Qt way, check the following repository:
https://github.com/ddqd/qxtglobalshortcut5
It makes use of the outdated qxt library but gets it working again.
The person tested it until Qt 5.4, we use it successfully under Qt 5.5.
This might be what you're looking for
https://github.com/jaz303/JFHotkeyManager
You could also look at this example from Apple, using the RegisterEventHotKey API call which I think will point you in the right direction.
https://developer.apple.com/library/prerelease/mac/samplecode/FunkyOverlayWindow/Listings/FunkyOverlayWindow_OverlayWindow_m.html#//apple_ref/doc/uid/DTS10000391-FunkyOverlayWindow_OverlayWindow_m-DontLinkElementID_8
Or you could try this code
#import <Carbon/Carbon.h>
EventHandlerUPP hotKeyFunction;
pascal OSStatus hotKeyHandler(EventHandlerCallRef nextHandler,EventRef theEvent, void *userData)
{
Notify *obj = userData;
[obj foo];
return noErr;
}
#implementation Notify
- (id)init
{
self = [super init];
if (self) {
//handler
hotKeyFunction = NewEventHandlerUPP(hotKeyHandler);
EventTypeSpec eventType;
eventType.eventClass = kEventClassKeyboard;
eventType.eventKind = kEventHotKeyReleased;
InstallApplicationEventHandler(hotKeyFunction,1,&eventType,self,NULL);
//hotkey
UInt32 keyCode = 80; //F19
EventHotKeyRef theRef = NULL;
EventHotKeyID keyID;
keyID.signature = 'FOO '; //arbitrary string
keyID.id = 1;
RegisterEventHotKey(keyCode,0,keyID,GetApplicationEventTarget(),0,&theRef);
}
return self;
}
- (void)foo
{
}
#end
And the header
#include "notify.mm"
#interface Notify
- (id)init;
- (void)foo;
#end
Simply this is just a object with a method and a constructor, in objective-c this is called init, or initialize, and variants. Calling it should be straight forward with "new".
E.x
#include "notify.h"
int main(){
Notify* object = new Notify();
}
However, some basic understanding of Objective-C is needed. It's mostly syntax differences in my opinion. But I'm no Objective-C expert myself. Anyway, there is a lot of ways to solve it, this might not be the best idea. You can also call Objective-C code from inside of a C++ class of yours. Take a look at the links bellow for a great example of how that's done.
https://el-tramo.be/blog/mixing-cocoa-and-qt/
https://github.com/remko/mixing-cocoa-and-qt/
http://philjordan.eu/article/mixing-objective-c-c++-and-objective-c++

How to prioritize touches for my CCMenu?

I have two CCMenu instances. At some point in the game, menu A is overlapped by menu B. However, when I press a button within menu B, the one that "gets it" is menu A.
How can I give touch priority to CCMenu B?
I tried this:
[[CCTouchDispatcher sharedDispatcher] setPriority:-130 forDelegate:menuB];
However, Xcode says that this delegate (menuB) was not found.
Okay, I fixed this, but I still think there should be a better way.
First, we have to edit CCMenu's interface. We have to create a new integer property.
#interface CCMenu : CCLayer <CCRGBAProtocol>
{
tCCMenuState state_;
CCMenuItem *selectedItem_;
GLubyte opacity_;
ccColor3B color_;
int extraTouchPriority; // Our new integer
}
#property (readwrite) int extraTouchPriority;
Now change the registerWithTouchDispatcher method to this:
-(void) registerWithTouchDispatcher
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:kCCMenuTouchPriority - extraTouchPriority swallowsTouches:YES];
}
Done. Now, when you have to give your CCMenu instance more priority than others, just give a higher extraTouchPriority value to it after initializing it.
I had the same problem. What i did is copied the entire CCMenu from cocos2d library, renamed it and then modified kCCMenuTouchPriority to what i wanted. Note that you have to rename kCCMenuTouchPriority for the custom menu. I used kkCCMenuTouchPriority.
I called it in code like this:
CCMenuPopUp *menu =[CCMenuPopUp menuWithItems:item1,nil];
I tried to subclass it but i ran into some problems and gave up and gone with the solution above.
the CCTouchDispatcher thing doesn't work because the menu isn't inited yet when you call it
Here's anoter variation on one of the anwers above, which doesn't alter the cocos2D code base, because that is bad practice: https://gist.github.com/tudormunteanu/6174624

Passing variable from one scene to other - Cocos2D

I have a level scene in my game in which I am selecting the level. In this scene I am displaying the level selected in a CCLabelTTF. Now I want to pass the value displayed on this label to my main scene. I am doing this as follows:
HelloWorld *hello=[HelloWorld getInstance]; //HelloWorld is main scene
hello.strLevel=[lblLevel string]; //strLevel is NSString to which I am passing the label text
[[CCDirector sharedDirector]replaceScene:[HelloWorld node]];
In my HelloWorld scene I am using singleton to share the value of label used in Level scene.
//HelloWorld.h
#interface HelloWorld : CCColorLayer
{
NSString *strLevel;
}
#property(nonAtomic,retain)NSString *strLevel;
+(HelloWorld*)getInstance;
HelloWorld.mm
#implementation HelloWorld
#synthesize strLevel;
static HelloWorld *instance=nil;
__________________________
//Some code
__________________________
+(HelloWorld*)getInstance
{
if(instance==nil)
{
instance=[HelloWorld new];
}
return instance;
}
However this isn't working.As soon as control reaches
instance=[HelloWorld new];
init() is called. And why not. However when the control reaches back to Level scene at the line where I am passing the value, nothing happens and HelloWorld shows the value null for strLevel.
I know singleton is a better way to pass values than AppDelegate. But I am unable to do so.Can someone correct me?
Thanks
Use singleton. What should my Objective-C singleton look like? this is a good discussion on the singleton in obj-c. good luck
[EDIT]
HelloWorld *hello=[HelloWorld getInstance]; //HelloWorld is main scene
hello.strLevel=[lblLevel string]; //strLevel is NSString to which I am passing the label text
[[CCDirector sharedDirector]replaceScene:[HelloWorld node]];
The HelloWorld instance that you're passing to the replaceScene is not the same as
the HelloWorld *hello you passed the singleton instance to. That's why there is no strLevel value in it. The strLevel value is placed in your HelloWorld singleton though. Try
NSLog(#"%#",[[HelloWorld getInstance] strLevel]); //somewhere in the code

cocos2d scene question - where does init() get called within an app?

I am totally new to cocos2d and Objective C. I just started studying the HelloWorld example that came with cocos2d package, and just couldn't figure out where in the application the -init() function within HelloWorldScene.m is getting called.
Here is the tutorial that I was following:
http://www.bit-101.com/blog/?p=2123
Thanks in advance!
jtalarico is correct. I'd like to expand on his answer a bit.
In general, some form of [init] is called by convention whenever an object gets instantiated. For many objects, [init] is all that is needed, but some objects have more complex forms, such as [initWithSomething].
In Cocos2d, the init function is generally called by the [node] method, which is often used to construct an object in Cocos2d. For example, look in CCNode.m, and you will see this code:
+(id) node
{
return [[[self alloc] init] autorelease];
}
Other objects have other constructors, but this is the main example.
So, if you subclass CCNode, you can override the [init] method and do your own stuff when an object gets created. Just be sure to call [super init] so that CCNode can do its own initialization, too.
The init() method is being overridden in the scene. It is getting called within the base class when an instance of the scene is created. By overriding it, you get the opportunity to fire your own code.

WebView not showing in NSWindow

I am developing a C++ app and I need to display a NSWindow with a WebKit WebView inside it. I've coded up the Objective-C class which will manage creating and displaying the window but the WebView contained inside it does not display. Here is my code. Any idea on what is wrong and how I can fix it?
I'm compiling the below code with
$g++ -x objective-c++ -framework Cocoa -framework WebKit Foo.m main.m -o test
Foo.h
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
#interface Foo :NSObject {
NSWindow *window;
WebView *view;
}
- (void)displayWindow;
#end
Foo.m
#import "Foo.h"
#implementation Foo
- (id)init {
self = [super init];
// Window Container
window = [[NSWindow alloc] initWithContentRect:NSMakeRect(500.0f,500.0f,250.0f,250.0f)
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreNonretained
defer:NO];
// WebView
view = [[WebView alloc] initWithFrame:NSMakeRect(0, 0, 250.0f, 250.0f)
frameName:#"Frame"
groupName:nil];
[[view mainFrame] loadHTMLString:#"<html><head></head><body><h1>Hello</h1></body></html>"
baseURL:nil];
return self;
}
- (void)displayWindow {
NSLog(#"In Display window");
[window setContentView:view];
[window setLevel:NSStatusWindowLevel];
[window orderFrontRegardless];
sleep(5); // leave it up for 5 seconds
}
- (void)dealloc {
[window release];
[super dealloc];
}
#end
main.m
#import "Foo.h"
int main() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSApplication sharedApplication];
Foo *foo = [[Foo alloc] init];
[foo displayWindow];
[foo release];
[pool release];
return 0;
}
You need to run the run loop. If you just order the window in and then exit, that's exactly what will happen: The window will appear, and then (five seconds later) your program will exit. You can run the run loop by telling the application (which you create but don't otherwise use) to run.
On the main thread of a Cocoa app, sleep is always the wrong answer. The same goes for its Cocoa cousins, +[NSThread sleepUntilDate:] and +[NSThread sleepForTimeInterval:]. The run loop will let you tell it to run for a fixed amount of time, but that won't get the application running; you do need to send the application the run message, which provides no opportunity to exit after a fixed interval.
The solution there is to first create an NSTimer object whose target is the application and whose selector is #selector(terminate:). Create it scheduled and non-repeating, with the interval set to five seconds. (Creating it scheduled means you don't need to schedule it separately—it is already ready to go from the moment you create it.) Then, send the application the run message. Five seconds later, the run loop will fire the timer, which will tell the application to terminate itself. This is assuming that you actually have a good reason to make your application quit after five seconds.
As noted by Yuji, every window in modern Cocoa should use NSBackingStoreBuffered.
And don't forget to release what you have created; you currently are forgetting that in the case of the view. See the Memory Management Programming Guide for Cocoa.
Once you have this working, I suggest moving toward a more typical architecture for this application:
Create a subclass of NSObject, and make an instance of that class your application's delegate.
Put the window and its WebView into a nib, and have the app delegate create a window controller to load and own the contents of that nib.
The app delegate should also be responsible for loading the page into the WebView and for setting up the self-termination timer.
Finally, create a nib to hold your application's main menu (the contents of the menu bar) and the application delegate. Interface Builder has a template for the first part; you create the app delegate object by dragging a blank Object in from the Library, setting its class on the ⌘6 Inspector, and dragging the connection from the application to the object. Then, you can reduce main to the single line that Xcode's project templates put in it: return NSApplicationMain(argc, argv);.
Doing all this will help your understanding of Cocoa, as well as your maintenance of the application—cramming everything into main will not scale.
You should also read the Cocoa Fundamentals Guide, if you haven't already.
Don't make it sleep. It stops the execution of the main thread, in which the GUI is dealt with. Instead, you need to run the run loop. Also, Cocoa needs to set itself up. So, call [[NSApplication sharedApplication] run] to set it up correctly and run the event loop.
Also, don't use backing mode other than buffered mode. Other modes are remnants from the time immemorial, and only NSBackingStoreBuffered should be used. As discussed in this Apple document, the non-retained mode is a remnant to support Classic Blue Box (OS 9 virtualizer), and newer classes like WebKit just can't operate within it.
So, what you need to do is practically:
change NSBackingStoreNonretained to NSBackingStoreBuffered.
Remove the line
sleep(5);
add a line
[[NSApplication sharedApplication] run];
after
[foo displayWindow];
Also, in order for an app to receive events from the window server correctly, you need to pack it into an app bundle. Compile it into a binary called foo, and create the following structure:
foo.app/
foo.app/Contents/
foo.app/Contents/MacOS/
foo.app/Contents/MacOS/foo <--- this is the executable
Then you can double-click foo.app from the Finder, or just call ./foo from the command line.