/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2023  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#import <objc/objc-runtime.h>
#import <AppKit/Appkit.h>
#import <Foundation/Foundation.h>
#import <swindow/sosx/SExtern.h>
#import <swindow/sosx/SCocoa.h>
#import <swindow/sosx/SKeyboard.h>
#import <math.h>
/*
   Many thanks to:
   https://stackoverflow.com/questions/314256/how-do-i-create-a-cocoa-window-programmatically
   http://www.gnustep.it/nicola/Tutorials/FirstGUIApplication/FirstGUIApplication.html
   https://developer.apple.com/documentation/foundation/
   https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html
   http://cocoadevcentral.com/articles/000056.php
   http://juliuspaintings.co.uk/cgi-bin/paint_css/animatedPaint/072-NSView-drag-and-drop.pl
   https://pinkstone.co.uk/how-to-handle-mouse-events-in-os-x/
   http://blog.richpollock.com/2007/08/mouse-tracking-in-cocoa/
   https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Paths/Paths.html
   http://mirror.informatimago.com/next/developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/Classes/NSWindow.html
   https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40003290-CH201-SW1
   http://juliuspaintings.co.uk/cgi-bin/paint_css/animatedPaint/072-NSView-drag-and-drop.pl
*/

#define DEBUG 0
#define MAIN_LOOP_POLL_TIME 5.0
#define DIALOG_LOOP_POLL_TIME 1.0

static int windowCounter = 0;


static NSWindowStyleMask TOPLEVEL_WINDOW_STYLE = NSWindowStyleMaskTitled
                    | NSWindowStyleMaskMiniaturizable
                    | NSWindowStyleMaskClosable
                    | NSWindowStyleMaskResizable;

static NSWindowStyleMask DIALOG_WINDOW_STYLE = NSWindowStyleMaskTitled
                    | NSWindowStyleMaskClosable
                    | NSWindowStyleMaskResizable;

static CocoaView* cocoaClipboardOwner = 0;
static int cocoaClipboardChangeCount = 0;
static NSMutableArray<CocoaView*> * applicationViews = [NSMutableArray array];

static NSColor* cocoaConvertColor  (const SColor& color) {
    NSColor *ret = [NSColor colorWithCalibratedRed:color.red/255.0 green:color.green/255.0 blue:color.blue/255.0 alpha:color.alpha/255.0];
    return ret;
}

static bool cocoaRunning = false;

static void cocoaCheckPasteboard() {
    if (cocoaClipboardOwner) {
        NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
        int changeCount = pasteboard.changeCount;
        if (changeCount != cocoaClipboardChangeCount) {
            cocoaClipboardOwner.cppWindow->listener->lostClipSelection(cocoaClipboardOwner.cppWindow);
            cocoaClipboardOwner = 0;
        }
    }
}

static CocoaWindow * cocoaCreateWindow (CocoaView* view) {
    NSRect frame = [view frame];
    if (view.isFlipped) {
       NSScreen* screen = [NSScreen mainScreen];
       NSRect sframe = [screen frame];
       frame.origin.y = sframe.size.height - (frame.size.height + frame.origin.y );
    }
    NSWindowStyleMask mask = view.modalParent != nil ?
        DIALOG_WINDOW_STYLE : TOPLEVEL_WINDOW_STYLE;

    // FIXME: We dont do autorelease, because closing it will release it
    // unless we store it or specify otherwise.
    CocoaWindow* window  = [[CocoaWindow alloc] initWithContentRect:frame
                    styleMask:mask
                    backing:NSBackingStoreBuffered
                    defer:NO];

    window.topLevelCocoaView = view;
    [window setTitle: view.viewName];
    [window setBackgroundColor:[NSColor grayColor]];
    window.showsResizeIndicator = YES;
    //[window setHasShadow: false];
    CocoaWindowDelegate *windowDelegate = [[CocoaWindowDelegate alloc] init];
    [window setDelegate :windowDelegate];

    // after this view frame will be relative to window.
    [window setContentView: view];

    // this should be the last line otherwise window is shown.
    [window orderOut: nil];
    window.lastFocusView = view.pendingFocusView;
    if (view->cocoaHasMinimumSize) {
        NSRect frame = NSMakeRect(100, 100, 
            view->cocoaMinimumSize.width, view->cocoaMinimumSize.height);
        NSRect wframe = [window frameRectForContentRect: frame];
        // SGCX
        NSSize msize;
        msize.width = wframe.size.width;
        msize.height = wframe.size.height;
        [window setMinSize: msize];
    }
    return window;
}


@implementation CocoaAccelerator  {
    int keyVar;
    bool ctrlVar;
    bool shiftVar;
    bool metaVar;
    bool pressedVar;
    SAcceleratorListener* listenerVar;    
}
- (id) init:(int)key ctrl:(bool)ctrl shift:(bool)shift meta:(bool)meta listener:(SAcceleratorListener*)listener {
    [super init];
    self->keyVar = key;
    self->ctrlVar = ctrl;
    self->shiftVar = shift;
    self->metaVar = meta;
    self->listenerVar = listener;
    self->pressedVar = NO;
//NSLog(@"Added Accelerator %@", self);
    return self;
}
- (BOOL) isPressed {
    return pressedVar;
} 
- (BOOL) fireKeyPressed:(int)key ctrl:(bool)ctrl shift:(bool)shift meta:(bool)meta {
    if (key == self->keyVar
        & ctrl == self->ctrlVar
        & shift == self->shiftVar
        & meta == self->metaVar) {
        self->listenerVar->acceleratorPressed(SAccelerator(key, ctrl, shift, meta));
        self->pressedVar = YES;
        return YES;
    }
    return NO;
}

- (BOOL) fireKeyReleased {
    if (self->pressedVar) {
        self->pressedVar = NO;
        self->listenerVar->acceleratorReleased(SAccelerator(keyVar, ctrlVar, shiftVar, metaVar));
        return YES;
    }
    return NO;

}

- (BOOL) matches: (CocoaAccelerator*) accelerator {
    if (accelerator->keyVar  == self->keyVar
        && accelerator->ctrlVar  == self->ctrlVar
        && accelerator->shiftVar  == self->shiftVar
        && accelerator->metaVar  == self->metaVar) {
        return YES;
    }
    return NO;
}

@end

//- (void) applicationWillFinishLaunching: (NSNotification *)notification {

@implementation CocoaApplicationDelegate
- (void) applicationWillFinishLaunching: (NSNotification *)notification {
#if DEBUG
NSLog (@"applicationWillFinishLaunching: %@", notification);
#endif
}

- (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *) application {
    return true;
}

- (void) applicationDidFinishLaunching: (NSNotification *)notification {
#if DEBUG
NSLog (@"applicationDidFinishLaunching: %@", notification);
#endif
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
#if DEBUG
NSLog (@"applicationShouldTerminate");
#endif
    for (CocoaView* cw in applicationViews) {
        if (cw.cppWindow->listener->windowClose(cw.cppWindow)) {
            cocoaHide (cw);
        }
    }
    // NSTerminateLater
    return (windowCounter == 0) ? NSTerminateNow : NSTerminateCancel;
}

@end

@implementation CocoaImage
@end

@implementation CocoaWindowDelegate 
- (void) windowDidMove: (NSNotification*) notification {
    CocoaWindow* w = (CocoaWindow*) notification.object;
    if (w.lastFocusView != 0) {
        [(CocoaView*)w.lastFocusView cocoaInvalidateCoordinates];
    }
}
- (void) windowDidResize: (NSNotification*) notification {
    CocoaWindow* w = (CocoaWindow*) notification.object;
    NSRect frame = [w frame];
    NSRect cframe = [w contentRectForFrameRect: frame];
    CocoaView* cw = [w contentView];
    unsigned int width = cframe.size.width;
    unsigned int height = cframe.size.height;
    if (cw.cppWindow != nil) {
        // top should be still top.
        cw.cppWindow->resized(width, height);
    }
    if (w.lastFocusView != 0) {
        [(CocoaView*)w.lastFocusView cocoaInvalidateCoordinates];
    }
}
- (void) windowDidBecomeKey: (NSNotification*) notification {
    CocoaWindow* w = (CocoaWindow*) notification.object;
    if (w.lastFocusView == nil) {
        CocoaView* cw = (CocoaView*) w.topLevelCocoaView;
        // gaining focus make lastFocusView bad.
        if (cw.cppWindow->listener->gainedKeyboardFocus(cw.cppWindow)) {
            if (w.lastFocusView == nil) {
                w.lastFocusView  = cw;
            }
            [(CocoaView*)w.lastFocusView cocoaMakeFirstResponder];
        }
    } else {
        CocoaView* cw = (CocoaView*) w.lastFocusView;
        cw.cppWindow->listener->gainedKeyboardFocus(cw.cppWindow);
        [(CocoaView*)w.lastFocusView cocoaMakeFirstResponder];
    }
    cocoaCheckPasteboard();
}

- (void) windowDidResignKey: (NSNotification*) notification {
    CocoaWindow* w = (CocoaWindow*) notification.object;
    if (w.lastFocusView != nil) {
        CocoaView* cw = (CocoaView*) w.lastFocusView;
        cw.cppWindow->listener->lostKeyboardFocus (cw.cppWindow);
        [cw cocoaCommitInput];
    }
}

- (BOOL) windowShouldClose: (NSWindow*) window {
    CocoaView * cw = (CocoaView *) [window contentView];
    if (![cw cocoaEventOK]) {
        return false;
    }
    if (cw.cppWindow->listener->windowClose(cw.cppWindow)) {
        cocoaHide (cw);
    }
    return false;
}
- (void)windowDidLoad {
    [super windowDidLoad];
    NSLog(@"Window loaded");
}

@end

@implementation CocoaWindow 

/*
SGCXXX
- (void) performClose: (id) sender {
    NSLog(@"Window performClose");
}
*/

// we dont use autorelease when creating.
// but lets have it anyway.
- (BOOL) releasedWhenClosed {
    return NO;
}

- (BOOL) canBecomeKeyWindow {
    BOOL ret = (self.currentModal == nil);
    return ret;
}
@end
@implementation CocoaView 
-(id) initWithFrame:(NSRect)frameRect {
    self = [super initWithFrame:frameRect];
    self.cppWindow = 0;
    self.currentPath = nil;
    self.currentImage = nil;
    self.graphicsContext = nil;
    self->cocoaHasMinimumSize = false;
    self->currentScroll = 0.0;
    return self;
}

- (CocoaView*) getLastFocusView {
    CocoaWindow * w = (CocoaWindow*) [self window];
    CocoaView * ret = (CocoaView*) w.lastFocusView;
    return ret;
}

- (void) cocoaMakeFirstResponder {
    if ([self window] != nil) {
        [[self window] makeFirstResponder: self];
    }
}

- (BOOL) acceptsFirstResponder {
    if (self.getLastFocusView == self) {
        return true;
    }
    CocoaWindow* w = (CocoaWindow*) [self window];
    // Top level SFileDialog has a funny recursive call
    // to gain focus, we just refuse here top level manipulation.
    if (self.cocoaIsTopLevel) {
        return false;
    }
    CocoaView * oldFocus = (CocoaView*) w.lastFocusView;
    if (self.cppWindow->listener->gainedKeyboardFocus (self.cppWindow)) {
        if (oldFocus != nil) {
            [oldFocus cocoaCommitInput];
            oldFocus.cppWindow->listener->lostKeyboardFocus (oldFocus.cppWindow);
        }
        if (oldFocus == w.lastFocusView) {
            w.lastFocusView = self;
        } else {
            NSLog(@"Get rid of recursive gainedKeyboardFocus calls.");
        }
        if (w.lastFocusView == self) {
            return true;
        }
    }
    return false;
}

- (void) cocoaKeyDown: (NSEvent*) event {
    NSString* letters = [event charactersIgnoringModifiers];
    SWindowListener::SKey key = convertKeyCode ([event keyCode]);

    bool command = ([event modifierFlags] & NSEventModifierFlagCommand); 
    bool control = ([event modifierFlags] & NSEventModifierFlagControl); 
    bool shift = ([event modifierFlags] & NSEventModifierFlagShift); 

//  option key
//  bool alternate = ([event modifierFlags] & NSEventModifierFlagAlternate); 

    CocoaView* topView = [self getTopLevelView];
    // Function keys should work only when input method is done.
    if (![self hasMarkedText]) {
        for (CocoaAccelerator *item in topView.accelerators) {
            if ([item isPressed]) {
                return;
            }
        }
        for (CocoaAccelerator *item in topView.accelerators) {
            if ([item fireKeyPressed: key ctrl:control shift:shift meta:command]) {
                // On mac we wont get a key released.
                [item fireKeyReleased];
                return;
            }
        }
    }
    // snatch keys before input method sees them.
    if (![self hasMarkedText]) {
        switch (key) {
        // Handled
        case SWindowListener::Key_Enter:
        case SWindowListener::Key_Return:
            [self cocoaClearInput];
            // no break
        case SWindowListener::Key_Tab:
        case SWindowListener::Key_Home:
        case SWindowListener::Key_End:
        case SWindowListener::Key_BackSpace:
        case SWindowListener::Key_Delete:
        case SWindowListener::Key_Escape:
        case SWindowListener::Key_Left:
        case SWindowListener::Key_Right:
        case SWindowListener::Key_Up:
        case SWindowListener::Key_Down:
            self.cppWindow->listener->keyPressed (self.cppWindow, 
                key, [letters UTF8String], control, shift, command); 
            return;
        default:
            break;
        }
        if (control || command) {
            self.cppWindow->listener->keyPressed (self.cppWindow, 
                key, [letters UTF8String], control, shift, command); 
            return;
        }
    }

    // pass it to input method.
    if ([self cocoaInputEnabled]) {
        [self interpretKeyEvents: [NSArray arrayWithObject: event]];
        return;
    }
    self.cppWindow->listener->keyPressed (self.cppWindow, 
        key, [letters UTF8String], control, shift, command); 
}

- (void) keyDown: (NSEvent *) event {
    CocoaView* lastFocusView = [self getLastFocusView];
    if (lastFocusView == nil) return;
    if (lastFocusView != self) {
        NSLog(@"KeyPressed in different view!!!!!");
    }
    [lastFocusView cocoaKeyDown: event];
}

- (void) cocoaKeyUp: (NSEvent*) event {
    if ([self cocoaInputEnabled]) {
        return;
    }
    NSString* letters = [event charactersIgnoringModifiers];
    SWindowListener::SKey key = convertKeyCode ([event keyCode]);
    bool command = ([event modifierFlags] & NSEventModifierFlagCommand); 
    bool control = ([event modifierFlags] & NSEventModifierFlagControl); 
    bool shift = ([event modifierFlags] & NSEventModifierFlagShift); 

//  option key
//  bool alternate = ([event modifierFlags] & NSEventModifierFlagAlternate); 
    self.cppWindow->listener->keyReleased (self.cppWindow, 
        key, [letters UTF8String], control, shift, command); 
}

- (void) keyUp: (NSEvent *) event {
    CocoaView* lastFocusView = [self getLastFocusView];
    if (lastFocusView == nil) return;
    [lastFocusView cocoaKeyUp: event];
}

- (BOOL) cocoaEventOK {
    return (((CocoaWindow*)self.window).currentModal == nil);
}

// move child acccelerators to this array.
- (void) buildChildAccelerators: (NSMutableArray<CocoaAccelerator*>*) array {
    for (NSView* view in [self subviews]) {
        CocoaView* v = (CocoaView *) view;
        if (v.accelerators != nil) {
            [array addObjectsFromArray: v.accelerators];
        }
        [v buildChildAccelerators: array];
    }
}

- (void)cocoaShow {
    // parent will not be null after added to window.
    CocoaView* parent = (CocoaView*) self.superview;
    CocoaWindow* window = self.nsWindow;
    if (parent == nil && self.nsWindow == nil) {
        window = cocoaCreateWindow (self);
        self.nsWindow = window;
        // Move child accelerators to window level.
        if (self.accelerators == nil) {
            self.accelerators = [NSMutableArray array];
        }
        [self buildChildAccelerators: self.accelerators];
        // reverse order for closing.
        [applicationViews insertObject: self atIndex:0];
        
    }
    if (self.nsWindow == nil) {
        return;
    } 
   // Aftr a hide we lose child windows.
    [self.nsWindow makeKeyAndOrderFront: nil];
    // It seems like this is not needed.
    //[[self window] setAcceptsMouseMovedEvents:YES];

    if (self.modalParent != nil) {
        [self.modalParent.nsWindow addChildWindow: self.nsWindow ordered:NSWindowAbove];
    }
    [self setTrackingArea];
    // Not needed
    //[window makeKeyWindow];
    self.cppWindow->setVisible(true);
}

- (void)cocoaHide {
    [self clearTrackingArea];
    if (self.nsWindow == nil) {
        return;
    }
    [self.nsWindow orderOut: nil];
    self.cppWindow->setVisible(false);
}

- (BOOL)cocoaIsTopLevel {
    return self.superview == nil || self.nsWindow != nil;
}

-(CocoaView*) getTopLevelView {
    CocoaView* view = self;
    while (![view cocoaIsTopLevel]) {
        view = (CocoaView*) self.superview;
    }
    return view;
}

- (void) cocoaAddAccelerator:(CocoaAccelerator*)accelerator {
    CocoaView* view = [self getTopLevelView];
    if (view.accelerators == nil) {
        view.accelerators = [NSMutableArray array];
    }
    [view.accelerators addObject: accelerator];
}

- (void) cocoaRemoveAccelerator:(CocoaAccelerator*)accelerator {
    NSLog (@"cocoaRemoveAccelerator not implemented.");
/*
    CocoaView* view = [self getTopLevelView];
    NSMutableArray<CocoaAccelerator*> *discardedItems = [NSMutableArray array];
    for (CocoaAccelerator *item in view.accelerators) {
        if ([item matches: accelerator]) {
            [discardedItems addObject:item];
        }
    }
    [view.accelerators removeObjectsInArray:discardedItems];
*/
}

- (BOOL) gainKeyboardFocus {
    CocoaWindow* w = (CocoaWindow*) [self window];
    // If top level window is not there we put
    // this to pending.
    if (w == nil) {
        CocoaView* top = [self getTopLevelView];
        top.pendingFocusView = self;
        return false;
    }
    // Nothing to do if we have the focus already.
    if (w.lastFocusView == self) {
      return true;
    }
    CocoaView * oldFocus = (CocoaView*) w.lastFocusView;
    if (self.cppWindow->listener->gainedKeyboardFocus (self.cppWindow)) {
        if (oldFocus != nil) {
            [oldFocus cocoaCommitInput];
            oldFocus.cppWindow->listener->lostKeyboardFocus(oldFocus.cppWindow);
        }
        // we received a recursive call. hack it.
        if (oldFocus == w.lastFocusView) {
            w.lastFocusView = self;
            if (w.isVisible) {
                [self cocoaMakeFirstResponder];
            }
            return true;
        }
        return true;
    }
    return false;
}

- (void) registerDroppable {
    if (self.droppable) {
        NSLog (@"CocoaView registerDroppable already set.");
    }
    [self registerForDraggedTypes: [NSArray arrayWithObjects: 
       //NSPasteboardTypeString,
       NSPasteboardTypeFileURL,
       nil]];
}

- (NSDragOperation)draggingEntered:(id )sender
{
   if ((NSDragOperationGeneric & [sender draggingSourceOperationMask])
      == NSDragOperationGeneric) {
      return NSDragOperationGeneric;
   } 
   return NSDragOperationNone;

} 

- (BOOL)prepareForDragOperation:(id) sender {
   return YES;
} 

- (BOOL)performDragOperation:(id) sender {
    if (!self.cocoaEventOK) {
        return NO;
    }
    NSPasteboard *pasteboard = [sender draggingPasteboard];
    NSURL* fileURL = [NSURL URLFromPasteboard: pasteboard];
    if (fileURL == nil) {
        return NO;
    }
    if (!fileURL.isFileURL) {
        return NO;
    }
    NSString* urlString = [fileURL path];
    NSMutableString *url = [NSMutableString stringWithString:urlString];
    // FIXME: this is the real url.
    //[url insertString: @"file://" atIndex: 0];
    [url insertString: @"file:" atIndex: 0];
    self.cppWindow->listener->drop (self.cppWindow, "text/uri-list", [url UTF8String]);
    
    return YES;
}

/*
-(void)beginGestureWithEvent:(NSEvent *)event;
{
     NSLog (@"beginGesture from trackpad");
}
-(void) endGestureWithEvent:(NSEvent *)event;
{
     NSLog (@"endGesture");
}
*/

- (void)scrollWheel:(NSEvent *)theEvent
    {
        if (![self cocoaEventOK]) return;
        currentScroll += [theEvent deltaY ];
        if (self->currentScroll > 12.0) {
            self->currentScroll = 12.0;
        }
        if (self->currentScroll < -12.0) {
            self->currentScroll = -12.0;
        }
        if (self->currentScroll < 0) {
            while (self->currentScroll < -4.0) {
                // caretDown
                self.cppWindow->listener->buttonPressed (self.cppWindow, 4,
                    (int)0, (int)0);
                self->currentScroll += 4.0;
            }
        } else {
            while (self->currentScroll > 4.0) {
                // caretUp
                self.cppWindow->listener->buttonPressed (self.cppWindow, 3,
                    (int)0, (int)0);
                self->currentScroll -= 4.0;
            }
        }
    }

- (void)otherMouseDown:(NSEvent *)evt {
    NSPoint loc = [evt locationInWindow];
    NSPoint point = [self convertPoint:loc fromView:nil];
    if ([self cocoaEventOK]) {
        self.cppWindow->listener->buttonPressed (self.cppWindow, 1,
            (int)point.x, (int)point.y);
    } 
}

- (void)mouseDown:(NSEvent *)evt {
    NSPoint loc = [evt locationInWindow];
    NSPoint point = [self convertPoint:loc fromView:nil];
    if ([self cocoaEventOK]) {
        self.cppWindow->listener->buttonPressed (self.cppWindow, 0,
            (int)point.x, (int)point.y);
    } 
}
 
- (void)mouseDragged:(NSEvent *)evt {
    NSPoint loc = [evt locationInWindow];
    NSPoint point = [self convertPoint:loc fromView:nil];
    
    if ([self cocoaEventOK]) {
        self.cppWindow->listener->buttonDragged (self.cppWindow, 0,
            (int)point.x, (int)point.y);
    }
}
 
- (void)mouseUp:(NSEvent *)evt {
    NSPoint loc = [evt locationInWindow];
    NSPoint point = [self convertPoint:loc fromView:nil];
    
    if ([self cocoaEventOK]) {
        self.cppWindow->listener->buttonReleased (self.cppWindow, 0,
              (int)point.x, (int)point.y);
        if ([evt clickCount] > 1) {
// MAC sends both.
        }
    }
}
// first clicks are directed to top level.
- (BOOL)acceptsFirstMouse:(NSEvent *)evt {
    if (!self.cocoaEventOK) {
        return true;
    }
    [self gainKeyboardFocus];
    return true;
}

- (void)viewWillMoveToWindow:(NSWindow *)newWindow {
   if ( [self window]) {
        [self clearTrackingArea];
   }
}

-(void)resetCursorRects
{
   [super resetCursorRects];
   [self clearTrackingArea];
   [self setTrackingArea];
}

- (void) clearTrackingArea {
    if ( [self window] && self.trackingRect ) {
        [self removeTrackingRect:self.trackingRect];
        self.trackingRect = 0;
        for (NSView* child in self.subviews) {
            [(CocoaView*)child clearTrackingArea];
        }
    }
}
- (void) setTrackingArea {
    if ( [self window]) {
        NSRect rect = [self visibleRect];
        self.trackingRect = [self addTrackingRect:rect
            owner:self userData:nil assumeInside:NO];
        for (NSView* child in self.subviews) {
            [(CocoaView*)child setTrackingArea];
        }
    }
}

- (void)mouseEntered:(NSEvent *)evt {
    if ([self cocoaEventOK]) {
        self.cppWindow->listener->enterWindow (self.cppWindow);
        self.trackingIN = TRUE;
    }
}

- (void)mouseExited:(NSEvent *)evt {
    if ([self cocoaEventOK] || self.trackingIN) {
        self.cppWindow->listener->leaveWindow (self.cppWindow);
        self.trackingIN = FALSE;
    }
}


-(void)drawRect:(NSRect)dirtyRect {
    if (self.cppWindow == nil) return;
    self.graphicsContext = nil;
    
    NSColor *bgc = cocoaConvertColor (self.cppWindow->background);
    [bgc set];
    NSRectFill (dirtyRect);

    if (self.cppWindow->listener != nil) {
        self.cppWindow->listener->redraw (self.cppWindow, 
            (int)dirtyRect.origin.x, 
            (int)dirtyRect.origin.y, 
            (unsigned int)dirtyRect.size.width, 
            (unsigned int)dirtyRect.size.height);
    }
    // redraw set it.
    if (self.graphicsContext != nil) {
        [self.graphicsContext restoreGraphicsState];
        self.graphicsContext = nil;
    }
}

// override
-(BOOL) isFlipped {
    return TRUE;
}
@end

NSApplication *myApplication;


bool cocoaInit () {
  if (myApplication != nil) return false;
  
  myApplication = [NSApplication sharedApplication];
  // these 2 lines bring it to front.
  [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
  CocoaApplicationDelegate *appDelegate = [[CocoaApplicationDelegate alloc] init];
  [NSApp setDelegate:appDelegate];


// Menu from:
// https://www.cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
    id menubar = [[NSMenu new] autorelease];
    id appMenuItem = [[NSMenuItem new] autorelease];
    [menubar addItem:appMenuItem];
    [NSApp setMainMenu:menubar];
    id appMenu = [[NSMenu new] autorelease];
    id quitTitle = @"Quit";
    id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:quitTitle
        action:@selector(terminate:) keyEquivalent:@"q"] autorelease];
    [appMenu addItem:quitMenuItem];
    [appMenuItem setSubmenu:appMenu];
//----

  [NSApp activateIgnoringOtherApps:YES];
  return true;
}

void cocoaRun () {
    if (windowCounter == 0) return;
    // application delegate will allow closing after last window closed.
    [myApplication run];
}

static NSMutableArray<CocoaView*>* allViews = [NSMutableArray array];

void * cocoaCreateView (const char* name) {
    NSRect frame = NSMakeRect(0, 0, 400, 400);

    CocoaView* view = [[[CocoaView alloc] initWithFrame:frame] autorelease];

    view.viewName = [[NSString alloc] initWithUTF8String:name];
    [allViews addObject: view];
    return view;
}
    
void cocoaShow (void* view) {
    CocoaView* cview = (CocoaView*) view;
    [cview cocoaShow];
}

void cocoaHide (void* view) {
    CocoaView* cview = (CocoaView*) view;
    [cview cocoaHide];
}


void cocoaSetCPPWindow (void* view, void* cppWindow) {
  CocoaView* cv = (CocoaView*) view;
  SOSXWindow* osxWindow = (SOSXWindow*) cppWindow;
  cv.cppWindow = osxWindow;
}


void cocoaSetParent (void* view, void*parent) {
    CocoaView* v = (CocoaView*) view;
    if (v.superview != nil) {
        return;
    }
    if (v.nsWindow != nil) {
        NSLog (@"cocoaSetParent reparenting not allowed once window was shown: %@", 
            [NSThread callStackSymbols]);
        return;
    }

    CocoaView* p = (CocoaView*) parent;
    [p addSubview: v positioned:NSWindowAbove relativeTo:nil];
}

void cocoaSetSize (void* view, unsigned int width, unsigned int height) {
    CocoaView* v = (CocoaView*) view;
    NSRect frame = [v frame];
    if (!v.isFlipped) {
        int delta = frame.size.height - height;
        frame.origin.y += delta; 
    }
    frame.size.width = width;
    frame.size.height = height;
    [v setFrame: frame];
    if (v.nsWindow != nil) {
        [v.nsWindow setContentSize: frame.size];
    }
    if (v.cppWindow != nil) v.cppWindow->resized(width, height);
}

static void cocoaSetPositionWindow (CocoaView* v, int x, int y) {
    NSScreen* screen = [v.nsWindow screen];
    NSRect frame = [v.nsWindow frame];
    NSRect pframe = [screen visibleFrame];

    // not flipped.
    int dx = x - frame.origin.x + pframe.origin.x;
    int dy = pframe.size.height - y -frame.size.height + pframe.origin.y - frame.origin.y;

    frame.origin.x += dx;
    frame.origin.y += dy;
    if (dx != 0 && dy != 0) {
        // no animation
        [v.nsWindow setFrame: frame display: NO];
        if (v.cppWindow != nil) v.cppWindow->moved((int)x, (int)y);
    }
}

void cocoaSetPosition (void* view, int x, int y) {
    CocoaView* v = (CocoaView*) view;
    if (v.nsWindow != nil) {
        cocoaSetPositionWindow (v, x, y);
        return;
    }
    NSRect frame = [v frame];
    bool isFlipped = [v isFlipped];
    CocoaView* pv = (CocoaView*) v.superview;
    int dx = 0;
    int dy = 0;
    // origin is not parent coordinates, but screen coordinates.
    // If parent window is there our position is relative to parent.
    NSRect pframe;
    if (pv == nil) {
        NSScreen* screen = nil;
        if (v.window != nil) {
            screen = [v.window screen];
        } 
        if (screen == nil) screen = [NSScreen mainScreen];
        pframe = [screen visibleFrame];
        // top level has window with decorations
        pframe = [NSWindow contentRectForFrameRect:pframe
                styleMask: TOPLEVEL_WINDOW_STYLE];
        if (isFlipped) {
            NSRect sframe = [screen frame];
            // flip parent which is a window.
            pframe.origin.y = sframe.size.height - pframe.size.height - pframe.origin.y;
        }
    } else {
        CocoaView* p = (CocoaView*) pv;
        pframe = [p frame];
        // origin 0
        pframe = NSMakeRect(0, 0, pframe.size.width, pframe.size.height);
    }
    if (isFlipped) {
        dx = x - frame.origin.x + pframe.origin.x;
        dy = y - frame.origin.y + pframe.origin.y;
    } else {
        dx = x - frame.origin.x + pframe.origin.x;
        dy = pframe.size.height - y -frame.size.height + pframe.origin.y - frame.origin.y;
    }
    frame.origin.x += dx;
    frame.origin.y += dy;
    [v setFrame: frame];
    if (v.cppWindow != nil) v.cppWindow->moved((int)x, (int)y);
}

void cocoaCenter (void* view, void* parent) {
    CocoaView* v = (CocoaView*) view;
    NSRect vframe = [v frame];
    NSRect sframe; 
    NSRect pframe; 
    NSScreen* screen = nil;
    if (v.window != nil) {
        screen = [v.window screen];
    } 
    if (screen == nil) screen = [NSScreen mainScreen];
    sframe = [screen visibleFrame]; 
    if (parent == nil) {
        pframe = sframe; 
    } else {
        CocoaView* p = (CocoaView*) parent;
        pframe = [p.nsWindow frame]; 
    }
    float hw = pframe.size.width / 2 - vframe.size.width / 2;
    float hh = pframe.size.height / 2 - vframe.size.height / 2;
    float screenY = pframe.origin.y + hh;
    float screenX = pframe.origin.x + hw - sframe.origin.x;
    float flippedY = sframe.size.height - (screenY - sframe.origin.y) - vframe.size.height; 
    // This is working on current screen coordinates.
    cocoaSetPosition(v, screenX, flippedY);
}

void cocoaSetTitle (void* view, const char* name) {
    CocoaView* v = (CocoaView*) view;
    v.viewName = [[[NSString alloc] initWithUTF8String:name] autorelease];
    if (v.window != nil) {
        [v.window setTitle: v.viewName];
    }
}

extern void cocoaSetBackground (void* view, unsigned char red, unsigned char green, unsigned char blue, unsigned char alpha) { 

    CocoaView* v = (CocoaView*) view;
    NSColor *color = [NSColor colorWithCalibratedRed:red/255.0 green:green/255.0 blue:blue/255.0 alpha:alpha/255.0];
    // This code is if:
    // [self setWantsLayer:YES];
    // warning: 'CocoaView' may not respond to 'setBackgroundColor:'
    // [v setBackgroundColor: color];
    if([v respondsToSelector:@selector(setBackgroundColor:)]){
        [v performSelector:@selector(setBackgroundColor:) withObject:color] ;
    }
}

void cocoaBitpoints (void* view, const SColor& fg, const int* x, const int* y, unsigned int size) {
    [cocoaConvertColor (fg) set];
    for (unsigned int i=0; i<size; i++) {
        NSRect rect = NSMakeRect(x[i], y[i], 1, 1);
        NSRectFill (rect);
    }
}

void cocoaBitfill (void* view, const SColor& bg, int x, int y, unsigned int width, unsigned int height) {
    [cocoaConvertColor (bg) set];
    NSRect rect = NSMakeRect(x, y, width, height);
    NSRectFill (rect);
}

void cocoaBitpoint (void* view, const SColor& fg, int x, int y) {
    [cocoaConvertColor (fg) set];
    NSRect rect = NSMakeRect(x, y, 1, 1);
    NSRectFill (rect);
}

void cocoaBitline (void* view, const SColor& fg, int x, int y, 
    int tox, int toy) {
    double over=0;
    if (x == tox) {
        [cocoaConvertColor (fg) set];
        int size = toy > y ? toy - y : y - toy;
        int offset = toy > y ? 0 : y - toy;
        NSRect rect = NSMakeRect(x, y-offset-over, 1, size+1 + 2 * over);
        NSRectFill (rect);
        return;
    }
    if (y == toy) {
        [cocoaConvertColor (fg) set];
        int size = tox > x ? tox - x : x - tox;
        int offset = tox > x ? 0 : x - tox;
        NSRect rect = NSMakeRect(x-offset-over, y, size+1, 1 + 2*over);
        NSRectFill (rect);
        return;
    }
    [cocoaConvertColor (fg) setStroke];
    NSBezierPath* path = [NSBezierPath bezierPath];
    [path setLineWidth:1];
    // Depprivated, Bevel -> Mitter
    // https://developer.apple.com/documentation/appkit/nsbevellinejoinstyle?language=objc
    // [path setLineJoinStyle:NSBevelLineJoinStyle];
    [path setLineJoinStyle:NSLineJoinStyleMiter];
    //[path setLineJoinStyle:NSMiterLineJoinStyle];
    //[path setLineJoinStYLE:nsrOUndLineJoinStyle];
    NSPoint from = NSMakePoint((double)x,(double)y);
    NSPoint to = NSMakePoint((double)tox,(double)toy);
    [path moveToPoint:from];
    [path lineToPoint:to];
    [path stroke];
}

@implementation CocoaPath  
- (id) initWithPath: (NSBezierPath*) path {
    [super init];
    self.path = path;
    self.fillColor = nil;
    self.strokeColor = nil;
    strokeWidth = 0.0;
    return self;
}
@end
// FIXME: This cache is never freed.
NSMutableDictionary<NSString*,NSArray<CocoaPath*>*> *pathCache = [[NSMutableDictionary alloc]initWithCapacity:10];
static double newPathX = 0.0;
static double newPathY = 0.0;
static int newPathCount = 0;
static NSArray<CocoaPath*>* cachedPath = nil;

bool cocoaBeginImage (void* view, double _x, double _y, const char* _id) {
    CocoaView* cw = (CocoaView*) view;
    NSString* key  = [[[NSString alloc] initWithUTF8String:_id] autorelease];
    cachedPath = nil;
    if ([key length] > 0) {
        cachedPath = [pathCache objectForKey:key];
    }
    newPathX = _x;
    newPathY = _y;
    newPathCount = 0;
    if (cachedPath != nil) {
        cw.currentPath = nil;
        cw.currentImage = nil;
        return true;
    }
    //NSLog (@"Creating new image %@", key);
    cw.currentImage = [NSMutableArray array];
    if ([key length] > 0) {
        [pathCache setObject: cw.currentImage forKey: key];
    }
    cw.currentPath = nil;
    return false;
}

void cocoaNewPath (void* view) {
    CocoaView* cw = (CocoaView*) view;
    cw.currentPath = [[[CocoaPath alloc] 
        initWithPath: [NSBezierPath bezierPath]] autorelease];
    if (cw.currentImage != nil) {
        [cw.currentImage addObject: cw.currentPath];
    }
}

void cocoaEndImage (void* view) {
    CocoaView* cw = (CocoaView*) view;
    if (cachedPath != nil) {
        for (CocoaPath* cp in cachedPath) {
            NSAffineTransform *transform = [NSAffineTransform transform]; 
            NSAffineTransformStruct identity = [transform transformStruct];
            [transform setTransformStruct:identity];
            [transform translateXBy:newPathX yBy:newPathY];
            NSBezierPath * path = [transform transformBezierPath: cp.path];
            if (cp.fillColor != nil) {
                [cp.fillColor setFill ];
                [path fill];
            }
            if (cp.strokeColor != nil) {
                [cp.strokeColor setStroke ];
                [path setLineWidth: cp->strokeWidth];
                // https://developer.apple.com/documentation/appkit/nsroundlinejoinstyle?language=objc
                //[path setLineJoinStyle:NSRoundLineJoinStyle];
                [path setLineJoinStyle:NSLineJoinStyleRound];
                [path stroke];
            }
        }
    }
    cachedPath = nil;
    cw.currentPath = nil;
    cw.currentImage = nil;
}

void cocoaMoveTo (void* view, double _x, double _y) {
    if (cachedPath) {
        NSLog (@"Not supposed to call cocoaMoveTo");
        return;
    }
    CocoaView* cw = (CocoaView*) view;
    if (cw.currentPath == nil) return;
    NSPoint xy = NSMakePoint(_x-newPathX, _y-newPathY);
    [cw.currentPath.path moveToPoint:xy];
}

void cocoaLineTo (void* view, double _x, double _y) {
    if (cachedPath) {
        NSLog (@"Not supposed to call cocoaLineTo");
        return;
    }
    CocoaView* cw = (CocoaView*) view;
    if (cw.currentPath == nil) return;
    newPathCount++;
    NSPoint xy = NSMakePoint(_x-newPathX, _y-newPathY);
    [cw.currentPath.path lineToPoint:xy];
}

void cocoaCurveTo (void* view, double _x0, double _y0, double _x1, 
  double _y1, double _x2, double _y2) {
    if (cachedPath) {
        NSLog (@"Not supposed to call cocoaCurveTo");
        return;
    }
    CocoaView* cw = (CocoaView*) view;
    if (cw.currentPath == nil) return;
    newPathCount++;
    NSPoint to = NSMakePoint(_x2-newPathX, _y2-newPathY);
    NSPoint c1 = NSMakePoint(_x0-newPathX, _y0-newPathY);
    NSPoint c2 = NSMakePoint(_x1-newPathX, _y1-newPathY);
    [cw.currentPath.path curveToPoint: to controlPoint1: c1 controlPoint2: c2];
}

void cocoaClosePath (void* view) {
    if (cachedPath) {
        NSLog (@"Not supposed to call cocoaClosePath");
        return;
    }
    CocoaView* cw = (CocoaView*) view;
    if (cw.currentPath == nil) return;
    newPathCount++;
    [cw.currentPath.path closePath];
}

void cocoaFill (void* view, const SPen& pen) {
    CocoaView* cw = (CocoaView*) view;
    if (cw.currentPath == nil) return;

    cw.currentPath.fillColor = cocoaConvertColor (pen.getForeground());
    [cw.currentPath.fillColor setFill];

    NSAffineTransform *transform = [NSAffineTransform transform]; 
    NSAffineTransformStruct identity = [transform transformStruct];
    [transform setTransformStruct:identity];
    [transform translateXBy:newPathX yBy:newPathY];

    NSBezierPath * path = [transform transformBezierPath:
        cw.currentPath.path];
    [path fill];
}

void cocoaStroke (void* view, const SPen& pen) {
    CocoaView* cw = (CocoaView*) view;
    if (cw.currentPath == nil) return;

    cw.currentPath.strokeColor = cocoaConvertColor (pen.getForeground());
    cw.currentPath->strokeWidth = pen.getLineWidth();
    [cw.currentPath.strokeColor setStroke ];

    NSAffineTransform *transform = [NSAffineTransform transform]; 
    NSAffineTransformStruct identity = [transform transformStruct];
    [transform setTransformStruct:identity];
    [transform translateXBy:newPathX yBy:newPathY];
 
    NSBezierPath * path = [transform transformBezierPath:
        cw.currentPath.path];

    [path setLineWidth: cw.currentPath->strokeWidth];
    // https://developer.apple.com/documentation/appkit/nsroundlinejoinstyle?language=objc
    //[path setLineJoinStyle:NSRoundLineJoinStyle];
    [path setLineJoinStyle:NSLineJoinStyleRound];
    [path stroke];
}

void* cocoaCreateImage (void* view, const SImage& im) {
    // FIXME: add support to svg with the code below.
/*
    NSImage* nsImage = [[NSImage alloc] initWithSize:NSMakeSize(100.0,  100.0)];
    [NSGraphicsContext saveGraphicsState];
NSGraphicsContext *g = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep];
[NSGraphicsContext setCurrentContext:g];
    [nsImage lockFocus];
    // draw here
    [nsImage unlockFocus];
    [NSGraphicsContext restoreGraphicsState];
*/
    NSBitmapImageRep* bitmapImageRep = [[[NSBitmapImageRep alloc] 
        initWithBitmapDataPlanes:nil
        pixelsWide:im.getWidth()
        pixelsHigh:im.getHeight()
        bitsPerSample:8
        samplesPerPixel:4
        hasAlpha:YES
        isPlanar:NO
        colorSpaceName:NSDeviceRGBColorSpace
        bytesPerRow:4 * im.getWidth()
        bitsPerPixel:32] autorelease];
    CocoaImage *nsim = [[[CocoaImage alloc] init] autorelease];
    for (int x=0; x<im.getWidth(); x++) {
        for (int y=0; y<im.getHeight(); y++) {
            SColor color = SColor(im.getShade (x, y));
            // flipped view.
            [bitmapImageRep setColor:cocoaConvertColor(color) 
                atX:x y:im.getHeight()-y-1];
                
        }
    }
    nsim.translate = NSMakePoint((double)im.getOrigoX(),(double)im.getOrigoY());
    [nsim addRepresentation:bitmapImageRep];
    return nsim;
}

void cocoaDrawImage (void* view, int _x, int _y, void* im) {
    CocoaImage* nsi = (CocoaImage*) im;
    [nsi drawAtPoint:NSMakePoint((double)_x+nsi.translate.x, 
        ((double)_y+nsi.translate.y))
        fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1];  
}


void cocoaSetClippingRectangle (void* view, int x, int y, unsigned int width, unsigned int height) {
    CocoaView* cw = (CocoaView*) view;
    cw.clipRect = NSMakeRect(x, y, width, height);
    if (cw.graphicsContext!=nil) {
        [cw.graphicsContext restoreGraphicsState];
    }
    cw.graphicsContext = [NSGraphicsContext currentContext];
    if (cw.graphicsContext!=nil) {
        [cw.graphicsContext saveGraphicsState];
        NSRectClip (cw.clipRect);
    }
}

void cocoaRemoveClippingRectangle (void* view) {
    CocoaView* cw = (CocoaView*) view;
    if (cw.graphicsContext == nil) {
///        NSLog (@"cocoaRemoveClippingRectangle already enabled: %@", [NSThread callStackSymbols]);
    }
    [cw.graphicsContext restoreGraphicsState];
    cw.graphicsContext = nil;
}

void cocoaNeedsDisplay (void* view) {
    CocoaView* cw = (CocoaView*) view;
    [cw setNeedsDisplay:YES];
}

void cocoaSetModal (void* view, void* parent, bool decorated) {
    CocoaView* cw = (CocoaView*) view;
    cw.modalParent = (CocoaView*) parent;
    cw.decorated = decorated;
}

void cocoaWait (void* view) {
    CocoaView* cw = (CocoaView*) view;
    if (cw.modalParent == nil) {
        NSLog(@"cocoaWait modalParent is not set");
       //return;
    } else {
        if (cw.modalParent.nsWindow.currentModal != nil) {
            NSLog(@"cocoaWait Parent already has a modal window.");
        }
        cw.modalParent.nsWindow.styleMask &= ~(NSWindowStyleMaskClosable);
        cw.modalParent.nsWindow.currentModal = cw;
    }
    NSEvent* ev;
    do {
        if (!cw.cppWindow->isVisible()) break;
        ev = [NSApp nextEventMatchingMask: NSEventMaskAny
             untilDate: [NSDate dateWithTimeIntervalSinceNow:DIALOG_LOOP_POLL_TIME]
             inMode: NSDefaultRunLoopMode
// Timer events are not delivered in NSModalPanelRunLoopMode.
             //inMode: NSModalPanelRunLoopMode
             dequeue: YES];
        if (ev) {
            // handle events here
            [myApplication sendEvent: ev];
            if (!cw.cppWindow->isVisible()) break;
        } else {
            cocoaCheckPasteboard();
        }
    } while (true);

    //if (cw.cppWindow->isVisible()) cocoaHide(cw);
    if (cw.modalParent != nil) {
        cw.modalParent.nsWindow.styleMask |= NSWindowStyleMaskClosable;
        cw.modalParent.nsWindow.currentModal = nil;
        [cw.modalParent.nsWindow makeKeyWindow];
    }

}

void cocoaCountWindows (void* view, int count) {
    windowCounter += count;
    if (windowCounter == 0) {
        cocoaExitApplication();
    }
}

void cocoaExitApplication () {
    cocoaRunning = false;
    [myApplication stop:nil];
}

@implementation CocoaTimer
-(id) initWithRepeating:(SEventOSX*)eventHandler client:(SClientOSX*)client timeout:(float)timeout {
    self = [super init];
    self.eventHandler = eventHandler;
    self.client = client;
    self.delegate = [NSTimer scheduledTimerWithTimeInterval:timeout 
        target:self
        selector:@selector(handleTimeout)
        userInfo:nil
        repeats:YES];
    return self;
}
-(void) handleTimeout {
   if (self.delegate == nil) {
        NSLog(@"handleTimeout called for a removed timer.");
        return;
   }
   (void) self.eventHandler->fireTimer(self.client); 
}

-(void) invalidate {
    if (self.delegate != nil) {
        [self.delegate invalidate];
        self.delegate = nil; 
    }
    // cant do it till we are called for a removed timer.
    [self release];
}

@end

void cocoaGetKeyboardFocus (void* view) {
    CocoaView* v = (CocoaView*) view;
    [v gainKeyboardFocus];
}
void* cocoaAddTimer (SEventOSX* eventHandler, SClientOSX* client, float repeatTimeout) {
    CocoaTimer* ret = [[CocoaTimer alloc] initWithRepeating: eventHandler client:client timeout:repeatTimeout];
    return ret;
}

void cocoaRemoveTimer (void* cocoaData) {
    CocoaTimer* timer = (CocoaTimer*) cocoaData;  
    [timer invalidate];
}

const char* cocoaGetClipboard () {
    NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
    NSString* nsString = [pasteboard stringForType:NSPasteboardTypeString];
    if (nsString == nil) {
        return "";
    } 
    return [nsString UTF8String];
}

void cocoaPutClipboard(void* view, const char* utf8) {
    CocoaView* v = (CocoaView*) view;
    if (cocoaClipboardOwner != nil && v != cocoaClipboardOwner) {
        cocoaClipboardOwner.cppWindow->listener->lostClipSelection(cocoaClipboardOwner.cppWindow);
    }
    cocoaClipboardOwner = v;
    NSString* nsString = [[[NSString alloc] initWithUTF8String: utf8] autorelease];
    NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
    [pasteboard clearContents];
    [pasteboard setString:nsString forType:NSPasteboardTypeString];
    cocoaClipboardChangeCount = pasteboard.changeCount;
}

// drag and drop
void cocoaRegisterDroppable (void * view, char** droppable) {
    CocoaView* v = (CocoaView*) view;
    [v registerDroppable];
}

void cocoaAddAccelerator (void* view, const SAccelerator& a, SAcceleratorListener* l) {
    CocoaView* v = (CocoaView*) view;
    // FIXME memory leak.
    CocoaAccelerator* accel = [[CocoaAccelerator alloc] 
        init:a.key ctrl:a.ctrl shift:a.shift meta:a.meta listener:l];
    [v cocoaAddAccelerator: accel];
}
void cocoaRemoveAccelerator (void* view, const SAccelerator& a, SAcceleratorListener* l) {
    CocoaView* v = (CocoaView*) view;
    CocoaAccelerator* accel = [[CocoaAccelerator alloc] 
        init:a.key ctrl:a.ctrl shift:a.shift meta:a.meta listener:l];
    [v cocoaRemoveAccelerator: accel];
}

void cocoaStartMacKeyboard (void* view, SPreEditor* preEditor) {
    CocoaView* v = (CocoaView*) view;
    [v cocoaStartMacKeyboard: preEditor];
}

void cocoaStopMacKeyboard (void* view) {
    CocoaView* v = (CocoaView*) view;
    [v cocoaStopMacKeyboard];
}
bool cocoaCommitMacKeyboard (void* view) {
    CocoaView* v = (CocoaView*) view;
    return [v cocoaCommitInput];
}

void cocoaSetMinimumSize (void* view, 
    unsigned int width, 
    unsigned int height) {
    
    CocoaView* v = (CocoaView*) view;
    v->cocoaMinimumSize.width = width;
    v->cocoaMinimumSize.height = height;
    v->cocoaHasMinimumSize = YES;
}
