Creating windows and managing events with Cocoa

Rainer.Buerck's icon

I have written several Max externals, using Carbon to create windows and manage the events. Since Carbon is no longer supported by Apple, I want to switch to Cocoa and re-write my externals.

So far I have managed to create an NSWindow, open it and draw to it. What I haven't managed yet is to find out how to retrieve (mouse and keyboard) events for my windows.

In Carbon I invoked an "InitializeWindowEvents" function from the _new function in which I installed the events for the window, for example:

handlerUPP = NewEventHandlerUPP (HandleMouseEventsMyWindow);
InstallWindowEventHandler (myWindowRef, handlerUPP, 4, eventTypes, x, NULL);

HandleMouseEventsWindowmyWindow is the function to which the (mouse) events are dispatched (in order to be processed'), myWindowRef is a Carbon WindowRef of the window (myWindow).

Can anyone show me how to do this with Cocoa? How can I register events for the window in order to receive events?

Timothy Place's icon

I think this is a more general Cocoa issue than anything to do with Max... The code I've pasted below may not get you 100% of the way there but, hopefully it might get you going in the right direction. This is excerpted from the audiounit~ object, which supports both Carbon views and Cocoa views -- the Cocoa views requiring a (drum roll...) Cocoa window.

x->a_nsWindow = [[NSWindow alloc] initWithContentRect:bounds
                styleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask
                backing:NSBackingStoreBuffered
                defer:NO];
[x->a_nsWindow setReleasedWhenClosed:YES];
[x->a_nsWindow setContentView:auView];

NSString* nsTitle = [[NSString alloc] initWithCString:cWindowTitle];
AUCocoaWindowDelegate* windowDelegate = [[AUCocoaWindowDelegate alloc] init];

[x->a_nsWindow setTitle:nsTitle];
[x->a_nsWindow setDelegate:windowDelegate];
[x->a_nsWindow orderFrontRegardless];
[x->a_nsWindow makeKeyWindow];

[x->a_view setPostsBoundsChangedNotifications:YES];
[x->a_view setPostsFrameChangedNotifications:YES];
[[NSNotificationCenter defaultCenter] addObserver:windowDelegate
                selector:@selector(didResizeView:)
                name:@"NSViewFrameDidChangeNotification"
                object:x->a_view];

A couple of points to make:

1. It requires writing a delegate Cocoa class, creating an instance of that class, and then passing that instance to the window. Then the window calls methods on your delegate.

2. For some events, the window doesn't pass the events that we look for in audiounit~, so we register for global notifications in the last line of code. You wouldn't have to use the same class for these two different mechanisms, even though that's what we did here.

Hope this helps!
Tim

Rainer.Buerck's icon

Hi Tim,

thanks very much for your response. As I can gather from your code, your NSWindow seems to be part of your object struct. As a basis of my studies I used the "plussz2" example provided in the SDK.

In order to add a NSWindow to the plussz struct, I have to add the Cocoa framework (and some others) to the project.

When I tried to add #include to the plussz.c file, an error was displayed, so it struck me that I should use a Cocoa Class file unstead. So I included a new file which I called "plussz.m" and copied the code from "plussz.c" to it,and it worked fine. I managed to include Cocoa/Cocoa.h and add NSWindow* a_nsWindow to struct t_plussz2.

In the function void *plussz2_new(long n) I iallocate and initialize the window:

void *plussz2_new(long n)        // n = int argument typed into object box (A_DEFLONG) -- defaults to 0 if no args are typed
{
    t_plussz2 *x;                // local variable (pointer to a t_plussz2 data structure)

    x = (t_plussz2 *)object_alloc(plussz2_class); // create a new instance of this object
    if(x){
x->proxy = proxy_new(x, 1, &x->proxy_inletnum);    // fully-flexible inlet for any type
x->outlet = outlet_new(x, NULL);                // fully-flexible outlet for any type

     // initialize L and R inlet atoms to (int)0
atom_setlong(&x->l, 0);
atom_setlong(&x->r, 0);
        }

    x->a_nsWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(100,100,400,300)
                                         styleMask:NSTitledWindowMask
                                            backing:NSBackingStoreBuffered
                                             defer:NO];

    NSString *nsTitle = @"MyNSWindow"; // window title
    [x->a_nsWindow setTitle: nsTitle];

    return(x);                    // return a reference to the object instance
}

I use the function void plussz2_bang(t_plussz2 *x) to open and close the window:

void plussz2_bang(t_plussz2 *x)
{
    static Boolean toggle = 0;

    if (toggle == 0)
    {
        //[x->a_nsWindow orderFrontRegardless];
        [x->a_nsWindow orderFront: x->a_nsWindow];
        toggle = 1;
    }
    else
    {
        [x->a_nsWindow orderOut: x->a_nsWindow];
        toggle = 0;
    }
}

Whenever I send a bang to plussz, the variable toggle in turn opens and closes the window.

So far I managed to create a window and open / close it. Fine.

Since I am new to Cocoa, I am not familiar with all the details yet:

What have I got to do to write a delegate class? What does [x->a_nsWindow setDelegate:windowDelegate]; mean?

How can I receive events to the window? Which are the functions to retrieve them?

Sorry for the banal questions, but I am new to all this, and the Apple Documentaton is huge and very confusing in the beginning...

Rainer

Timothy Place's icon

Yes, navigating Apple's documentation can be a bit of a maze.

Maybe that's relevant to what you are trying to do. It also turned-up this, which looks really helpful:
http://stackoverflow.com/questions/626898/how-do-i-create-delegates-in-objective-c

If you do a Find on this page for "delegate" it shows a lot of places it comes up. Probably there is a coherent list somewhere, or you can just glean from this what you are looking for.

Good luck!
Tim

Rainer.Buerck's icon

Thanks, I will try to explore it.