/** \file
 * \brief Cocoa Base Functions
 *
 * See Copyright Notice in "iup.h"
 */

#include <string.h>
#include <unistd.h>

#import <CoreGraphics/CoreGraphics.h>

#include "iup.h"
#include "iupcbs.h"

#include "iup_object.h"
#include "iup_childtree.h"
#include "iup_key.h"
#include "iup_str.h"
#include "iup_class.h"
#include "iup_attrib.h"
#include "iup_image.h"
#include "iup_drv.h"
#include "iup_markup.h"

#include "iupcocoa_drv.h"

#ifdef GNUSTEP
/* No-op category stubs for AppKit methods GNUstep doesn't implement. Degradation is
   "feature does nothing" rather than a crash. */

@implementation NSControl (IupGnustepShimImpl)
- (void)setUsesSingleLineMode:(BOOL)flag       { (void)flag; }
- (void)setLineBreakMode:(NSLineBreakMode)mode { (void)mode; }
- (void)setTruncatesLastVisibleLine:(BOOL)flag { (void)flag; }
- (void)setControlSize:(NSInteger)size         { (void)size; }
@end

/* NSCell base doesn't declare setTextColor: (only subclasses do). Some call sites
   get a plain NSCell (e.g. NSBox titleCell); no-op keeps them safe. */
@implementation NSCell (IupGnustepShimImpl)
- (void)setTextColor:(NSColor*)color { (void)color; }
@end

@implementation NSSlider (IupGnustepShimImpl)
/* GNUstep derives orientation from cell bounds; nothing to store. */
- (void)setVertical:(BOOL)flag { (void)flag; }
@end

@implementation NSView (IupGnustepShimImpl)
- (void)setClipsToBounds:(BOOL)flag { (void)flag; }
- (void)setAccessibilityLabel:(NSString*)label { (void)label; }
/* fittingSize: callers size the temp widget first, so returning -frame is fine. */
- (NSSize)fittingSize { return [self frame].size; }
- (NSRect)convertRectToBacking:(NSRect)rect { return rect; }
/* Tracking areas: accepted but not wired on GNUstep; empty array + no-ops keep
   remove-then-add call patterns safe. */
- (void)updateTrackingAreas { }
- (NSArray*)trackingAreas { return @[]; }
- (void)addTrackingArea:(NSTrackingArea*)area { (void)area; }
- (void)removeTrackingArea:(NSTrackingArea*)area { (void)area; }
/* viewWillDraw is a 10.5+ hook GNUstep doesn't invoke; subclasses call super. */
- (void)viewWillDraw { }
/* NSLayoutAnchor/constraints absent; empty array so iterators find nothing to remove. */
- (NSArray*)constraints { return @[]; }
- (void)removeConstraints:(NSArray*)constraints { (void)constraints; }
@end

@implementation NSApplication (IupGnustepShimImpl)
- (void)setHelpMenu:(NSMenu*)menu    { (void)menu; }
- (NSInteger)activationPolicy        { return 0; /* NSApplicationActivationPolicyRegular */ }
- (BOOL)setActivationPolicy:(NSInteger)policy { (void)policy; return YES; }
- (id)effectiveAppearance            { return nil; }
- (id)dockTile                       { return nil; }
@end

@implementation NSColor (IupGnustepShimImpl)
+ (NSColor*)linkColor                         { return [NSColor blueColor]; }
+ (NSColor*)selectedContentBackgroundColor    { return [NSColor selectedTextBackgroundColor]; }
+ (NSColor*)underPageBackgroundColor          { return [NSColor windowBackgroundColor]; }
+ (NSColor*)separatorColor                    { return [NSColor gridColor]; }
@end

@implementation NSTimer (IupGnustepShimImpl)
- (void)setTolerance:(NSTimeInterval)tolerance { (void)tolerance; }
@end

@implementation NSOutlineView (IupGnustepShimImpl)
- (void)setDraggingDestinationFeedbackStyle:(NSInteger)s { (void)s; }
- (void)setAllowsExpansionToolTips:(BOOL)flag { (void)flag; }
/* Batch animated insert/remove/move (10.7+) not available; fall back to -reloadData. */
- (void)insertItemsAtIndexes:(NSIndexSet*)indexes inParent:(id)parent withAnimation:(NSUInteger)options
{
  (void)indexes; (void)parent; (void)options;
  [self reloadData];
}
- (void)removeItemsAtIndexes:(NSIndexSet*)indexes inParent:(id)parent withAnimation:(NSUInteger)options
{
  (void)indexes; (void)parent; (void)options;
  [self reloadData];
}
- (void)moveItemAtIndex:(NSInteger)fromIndex inParent:(id)oldParent toIndex:(NSInteger)toIndex inParent:(id)newParent
{
  (void)fromIndex; (void)toIndex; (void)oldParent; (void)newParent;
  [self reloadData];
}
@end

@implementation NSTableView (IupGnustepShimImpl)
- (void)setDraggingDestinationFeedbackStyle:(NSInteger)s { (void)s; }
- (void)setRowSizeStyle:(NSInteger)style { (void)style; }
@end

@implementation NSWindow (IupGnustepShimImpl)
- (void)setStyleMask:(NSUInteger)mask   { (void)mask; }
- (void)toggleFullScreen:(id)sender     { (void)sender; }
+ (void)setAllowsAutomaticWindowTabbing:(BOOL)flag { (void)flag; }
@end

@implementation NSMenu (IupGnustepShimImpl)
- (void)setMinimumWidth:(CGFloat)width  { (void)width; }
@end

@implementation NSFontManager (IupGnustepShimImpl)
- (void)setTarget:(id)target { (void)target; }
@end

/* NSPasteboard 10.6+ convenience API not on GNUstep; forward to legacy equivalents. */
@implementation NSPasteboard (IupGnustepShimImpl)
- (NSInteger)clearContents
{
  [self declareTypes:[NSArray array] owner:nil];
  return [self changeCount];
}
- (BOOL)writeObjects:(NSArray*)objects
{
  BOOL ok = YES;
  for (id obj in objects)
  {
    if ([obj isKindOfClass:[NSString class]])
    {
      NSString* s = (NSString*)obj;
      [self declareTypes:@[NSStringPboardType] owner:nil];
      if (![self setString:s forType:NSStringPboardType]) ok = NO;
    }
    else if ([obj isKindOfClass:[NSURL class]])
    {
      NSURL* u = (NSURL*)obj;
      [self declareTypes:@[NSURLPboardType] owner:nil];
      if (![self setString:[u absoluteString] forType:NSURLPboardType]) ok = NO;
    }
    else if ([obj respondsToSelector:@selector(writableTypesForPasteboard:)])
    {
      NSArray* types = [obj writableTypesForPasteboard:self];
      [self declareTypes:types owner:nil];
      for (NSString* t in types)
      {
        id data = [obj pasteboardPropertyListForType:t];
        if ([data isKindOfClass:[NSData class]])
          [self setData:data forType:t];
        else if ([data isKindOfClass:[NSString class]])
          [self setString:data forType:t];
      }
    }
  }
  return ok;
}
- (NSArray*)readObjectsForClasses:(NSArray*)classArray options:(NSDictionary*)options
{
  (void)options;
  NSMutableArray* result = [NSMutableArray array];
  for (Class cls in classArray)
  {
    if (cls == [NSString class])
    {
      NSString* s = [self stringForType:NSStringPboardType];
      if (s) [result addObject:s];
    }
    else if (cls == [NSURL class])
    {
      NSString* s = [self stringForType:NSURLPboardType];
      if (s) [result addObject:[NSURL URLWithString:s]];
    }
  }
  return result;
}
- (BOOL)canReadObjectForClasses:(NSArray*)classArray options:(NSDictionary*)options
{
  (void)options;
  NSArray* available = [self types];
  for (Class cls in classArray)
  {
    if (cls == [NSString class] && [available containsObject:NSStringPboardType]) return YES;
    if (cls == [NSURL class] && [available containsObject:NSURLPboardType]) return YES;
  }
  return NO;
}
@end
#endif

const void* IHANDLE_ASSOCIATED_OBJ_KEY = @"IHANDLE_ASSOCIATED_OBJ_KEY";
const void* MAINVIEW_ASSOCIATED_OBJ_KEY = @"MAINVIEW_ASSOCIATED_OBJ_KEY";
const void* ROOTVIEW_ASSOCIATED_OBJ_KEY = @"ROOTVIEW_ASSOCIATED_OBJ_KEY";

static NSView* cocoaGetAssociatedView(Ihandle* ih, const void* key)
{
  if (NULL == ih)
    return nil;

  id root_object = (id)ih->handle;
  if (nil == root_object)
    return nil;

  if (ih->iclass->nativetype == IUP_TYPEMENU || [root_object isKindOfClass:[NSStatusItem class]])
    return nil;

  NSView* view = (NSView*)objc_getAssociatedObject(root_object, key);
  NSCAssert([view isKindOfClass:[NSView class]], @"Expected NSView");
  return view;
}

IUP_DRV_API NSView* iupcocoaGetRootView(Ihandle* ih)
{
  return cocoaGetAssociatedView(ih, ROOTVIEW_ASSOCIATED_OBJ_KEY);
}

IUP_DRV_API NSView* iupcocoaGetMainView(Ihandle* ih)
{
  return cocoaGetAssociatedView(ih, MAINVIEW_ASSOCIATED_OBJ_KEY);
}

IUP_DRV_API void iupcocoaSetAssociatedViews(Ihandle* ih, NSView* main_view, NSView* root_view)
{
  NSCAssert(ih->handle, @"Expected ih->handle to be set");

  objc_setAssociatedObject((id)ih->handle, MAINVIEW_ASSOCIATED_OBJ_KEY, main_view, OBJC_ASSOCIATION_ASSIGN);
  objc_setAssociatedObject((id)ih->handle, ROOTVIEW_ASSOCIATED_OBJ_KEY, root_view, OBJC_ASSOCIATION_ASSIGN);
}

IUP_DRV_API void iupcocoaAddToParent(Ihandle* ih)
{
  NSView* parent_view = iupcocoaCommonBaseLayoutGetParentView(ih);

  NSView* child_view = iupcocoaCommonBaseLayoutGetChildView(ih);
  if (!child_view || !parent_view)
    return;

  [child_view setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
  [parent_view addSubview:child_view];
}

IUP_DRV_API void iupcocoaRemoveFromParent(Ihandle* ih)
{
  id child_handle = ih->handle;
  if([child_handle isKindOfClass:[NSView class]])
  {
    NSView* the_view = (NSView*)child_handle;
    [the_view removeFromSuperview];
  }
  else if([child_handle isKindOfClass:[NSViewController class]])
  {
    NSViewController* view_controller = (NSViewController*)child_handle;
    NSView* the_view = [view_controller view];
    [the_view removeFromSuperview];
  }
  else
  {
    if (ih->iclass->nativetype != IUP_TYPEMENU)
    {
      NSCAssert(0, @"Unexpected type for widget");
      @throw @"Unexpected type for widget";
    }
  }
}

IUP_DRV_API int iupcocoaComputeCartesianScreenHeightFromIup(int iup_height)
{
  /* This function converts from IUP's top-left based system to Cocoa's global bottom-left based system. */
  NSRect main_screen_frame = [[NSScreen mainScreen] frame];
  CGFloat main_screen_top = main_screen_frame.origin.y + main_screen_frame.size.height;
  CGFloat cartesian_y = main_screen_top - iup_height;
  return iupROUND(cartesian_y);
}

IUP_DRV_API int iupcocoaComputeIupScreenHeightFromCartesian(int cartesian_height)
{
  /* This function converts from Cocoa's global bottom-left coordinate system
     to IUP's top-left based system, where (0,0) is the top-left of the main screen. */
  NSRect main_screen_frame = [[NSScreen mainScreen] frame];
  CGFloat main_screen_top = main_screen_frame.origin.y + main_screen_frame.size.height;
  CGFloat iup_y = main_screen_top - cartesian_height;
  return iupROUND(iup_y);
}

IUP_SDK_API void iupdrvActivate(Ihandle* ih)
{
  id control = ih->handle;
  if ([control respondsToSelector:@selector(performClick:)])
  {
    [control performClick:nil];
  }
}

IUP_SDK_API void iupdrvReparent(Ihandle* ih)
{
  NSView* child_view = iupcocoaCommonBaseLayoutGetChildView(ih);
  if (!child_view) return;

  NSView* old_parent_view = [child_view superview];
  NSView* new_parent_view = iupcocoaCommonBaseLayoutGetParentView(ih);

  if (old_parent_view != new_parent_view && new_parent_view)
  {
    /* Retain the view to prevent it from being deallocated when removed */
    [child_view retain];
    [child_view removeFromSuperview];
    [new_parent_view addSubview:child_view];
    [child_view release];
  }
}

IUP_DRV_API NSView* iupcocoaCommonBaseLayoutGetParentView(Ihandle* ih)
{
  NSView* specific_container = (NSView*)iupAttribGet(ih, "_IUPTAB_CONTAINER");
  if (specific_container != NULL)
  {
    NSCAssert([specific_container isKindOfClass:[NSView class]], @"Expected specific container (e.g., _IUPTAB_CONTAINER) to be an NSView");
    return specific_container;
  }

  id parent_native_handle = iupChildTreeGetNativeParentHandle(ih);
  NSView* parent_view = nil;

  if([parent_native_handle isKindOfClass:[NSWindow class]])
  {
    NSWindow* parent_window = (NSWindow*)parent_native_handle;
    parent_view = [parent_window contentView];
  }
  else if([parent_native_handle isKindOfClass:[NSBox class]])
  {
    parent_view = [(NSBox*)parent_native_handle contentView];
  }
  else if([parent_native_handle isKindOfClass:[NSView class]])
  {
    parent_view = (NSView*)parent_native_handle;
  }
  else if([parent_native_handle isKindOfClass:[NSViewController class]])
  {
    parent_view = [(NSViewController*)parent_native_handle view];
  }
  else if([parent_native_handle isKindOfClass:[NSStatusItem class]])
  {
    return nil;
  }
  else
  {
    if (ih->iclass->nativetype != IUP_TYPEMENU && parent_native_handle != nil)
    {
      NSCAssert(0, @"Unexpected type for parent widget: %@", [parent_native_handle class]);
      @throw @"Unexpected type for parent widget";
    }
  }

  return parent_view;
}

IUP_DRV_API NSView* iupcocoaCommonBaseLayoutGetChildView(Ihandle* ih)
{
  id child_handle = ih->handle;
  NSView* the_view = nil;

  if([child_handle isKindOfClass:[NSViewController class]])
  {
    child_handle = [(NSViewController*)child_handle view];
  }

  if([child_handle isKindOfClass:[NSView class]])
  {
    the_view = (NSView*)child_handle;
  }
  else
  {
    if (ih->iclass->nativetype != IUP_TYPEMENU)
    {
      NSCAssert(0, @"Unexpected type for child widget");
      @throw @"Unexpected type for child widget";
    }
  }
  return the_view;
}

IUP_SDK_API void iupdrvBaseLayoutUpdateMethod(Ihandle *ih)
{
  NSView* parent_view = iupcocoaCommonBaseLayoutGetParentView(ih);
  if (!parent_view) return;

  NSView* child_view = iupcocoaCommonBaseLayoutGetChildView(ih);
  if (!child_view) return;

  NSRect parent_bounds = [parent_view bounds];
  NSRect child_rect;

  if ([parent_view isFlipped])
  {
    child_rect = NSMakeRect(
        ih->x,
        ih->y,
        ih->currentwidth,
        ih->currentheight
    );
  }
  else
  {
    child_rect = NSMakeRect(
        ih->x,
        parent_bounds.size.height - ih->y - ih->currentheight,
        ih->currentwidth,
        ih->currentheight
    );
  }

  [child_view setFrame:child_rect];

  iupcocoaUpdateTip(ih);
}

IUP_SDK_API void iupdrvBaseUnMapMethod(Ihandle* ih)
{
  if (!ih->handle) return;

  Ihandle* context_menu_ih = (Ihandle*)iupcocoaCommonBaseGetContextMenuAttrib(ih);
  if(NULL != context_menu_ih)
  {
    IupDestroy(context_menu_ih);
  }
  iupcocoaCommonBaseSetContextMenuAttrib(ih, NULL);

  if (iupAttribGet(ih, "_IUPCOCOA_CURSOR_DELEGATE"))
  {
    iupdrvBaseSetCursorAttrib(ih, "NONE");
  }

  iupcocoaTipsDestroy(ih);

  objc_setAssociatedObject(ih->handle, IHANDLE_ASSOCIATED_OBJ_KEY, nil, OBJC_ASSOCIATION_ASSIGN);
  objc_setAssociatedObject(ih->handle, MAINVIEW_ASSOCIATED_OBJ_KEY, nil, OBJC_ASSOCIATION_ASSIGN);
  objc_setAssociatedObject(ih->handle, ROOTVIEW_ASSOCIATED_OBJ_KEY, nil, OBJC_ASSOCIATION_ASSIGN);

  id the_handle = ih->handle;
  iupcocoaRemoveFromParent(ih);

  [the_handle release];
  ih->handle = NULL;
}

static void iupCocoaDisplayUpdate(Ihandle *ih)
{
  id the_handle = ih->handle;

  if([the_handle isKindOfClass:[NSView class]])
  {
    NSView* the_view = (NSView*)the_handle;
    [the_view setNeedsDisplay:YES];
  }
  else if([the_handle isKindOfClass:[NSViewController class]])
  {
    NSView* the_view = [(NSViewController*)the_handle view];
    [the_view setNeedsDisplay:YES];
  }

  NSView* main_view = iupcocoaGetMainView(ih);
  if (main_view && main_view != (NSView*)the_handle)
  {
    [main_view setNeedsDisplay:YES];
  }
}

IUP_SDK_API void iupdrvRedrawNow(Ihandle *ih)
{
  iupCocoaDisplayUpdate(ih);
  NSView* view = iupcocoaGetMainView(ih);
  if(view)
  {
    [view displayIfNeeded];
  }
}

IUP_SDK_API void iupdrvPostRedraw(Ihandle *ih)
{
  iupCocoaDisplayUpdate(ih);
}

IUP_SDK_API void iupdrvScreenToClient(Ihandle* ih, int *x, int *y)
{
  NSRect main_screen = [[NSScreen mainScreen] frame];
  CGFloat main_screen_top = main_screen.origin.y + main_screen.size.height;
  NSPoint screen_point = { *x, main_screen_top - *y }; /* IUP screen to Cocoa screen (bottom-left) */

  NSView* main_view = iupcocoaGetMainView(ih);
  if (!main_view) return;

  NSWindow* the_window = [main_view window];
  if (!the_window) return;

  NSRect screen_rect = NSMakeRect(screen_point.x, screen_point.y, 0, 0);
  NSRect window_rect = [the_window convertRectFromScreen:screen_rect];

  NSPoint window_point = window_rect.origin;
  NSPoint view_point = [main_view convertPoint:window_point fromView:nil];

  /* If the view is not flipped, its coordinate system has the origin at the bottom-left.
     We must convert the y-coordinate to IUP's top-left based system. For flipped views, the coordinate is already correct. */
  if (![main_view isFlipped])
  {
    NSRect view_bounds = [main_view bounds];
    view_point.y = view_bounds.size.height - view_point.y;
  }

  *x = iupROUND(view_point.x);
  *y = iupROUND(view_point.y);
}

IUP_SDK_API void iupdrvClientToScreen(Ihandle* ih, int *x, int *y)
{
  NSView* main_view = iupcocoaGetMainView(ih);
  if (!main_view) return;

  NSWindow* the_window = [main_view window];
  if (!the_window) return;

  NSPoint start_point = { *x, *y };

  /* If the view is not flipped, we must convert from IUP's top-left
     coordinate system to Cocoa's bottom-left system before converting to screen coordinates. */
  if (![main_view isFlipped])
  {
    NSRect view_bounds = [main_view bounds];
    start_point.y = view_bounds.size.height - *y;
  }

  NSPoint window_point = [main_view convertPoint:start_point toView:nil];

  NSRect window_rect = NSMakeRect(window_point.x, window_point.y, 0, 0);
  NSRect screen_rect = [the_window convertRectToScreen:window_rect];

  NSRect main_screen = [[NSScreen mainScreen] frame];
  CGFloat main_screen_top = main_screen.origin.y + main_screen.size.height;

  *x = screen_rect.origin.x;
  *y = main_screen_top - screen_rect.origin.y; /* convert from cocoa screen coords to IUP screen coords */
}

IUP_SDK_API int iupdrvBaseSetZorderAttrib(Ihandle* ih, const char* value)
{
  NSView* child_view = iupcocoaGetMainView(ih);
  if (!child_view) return 0;

  NSView* parent_view = [child_view superview];
  if (!parent_view) return 0;

  if (iupStrEqualNoCase(value, "TOP"))
  {
    [parent_view addSubview:child_view positioned:NSWindowAbove relativeTo:nil];
  }
  else
  {
    [parent_view addSubview:child_view positioned:NSWindowBelow relativeTo:nil];
  }
  return 1;
}

IUP_SDK_API void iupdrvSetVisible(Ihandle* ih, int visible)
{
  id the_object = ih->handle;
  bool is_hidden = !(bool)visible;

  if([the_object isKindOfClass:[NSView class]])
  {
    [(NSView*)the_object setHidden:is_hidden];
  }
  else if([the_object isKindOfClass:[NSViewController class]])
  {
    [[(NSViewController*)the_object view] setHidden:is_hidden];
  }
}

IUP_SDK_API int iupdrvIsVisible(Ihandle* ih)
{
  id the_object = ih->handle;
  if([the_object isKindOfClass:[NSWindow class]])
  {
    return [(NSWindow*)ih->handle isVisible];
  }
  else if([the_object isKindOfClass:[NSView class]])
  {
    return [(NSView*)the_object isHidden] ? NO : YES;
  }
  else if([the_object isKindOfClass:[NSViewController class]])
  {
    return [[(NSViewController*)the_object view] isHidden] ? NO : YES;
  }
  else
  {
    return 1;
  }
}

IUP_SDK_API int iupdrvIsActive(Ihandle *ih)
{
  char* value = iupAttribGet(ih, "_IUPCOCOA_ACTIVE");
  int result;

  if (value == NULL)
    result = 1;
  else
    result = iupStrBoolean(value);

  return result;
}

IUP_SDK_API void iupdrvSetActive(Ihandle* ih, int enable)
{
  iupAttribSet(ih, "_IUPCOCOA_ACTIVE", iupStrReturnBoolean(enable));

  if (ih->iclass->nativetype == IUP_TYPEDIALOG)
  {
    NSWindow* window = iupcocoaDialogGetWindow(ih);
    if (!window)
    {
        return;
    }

    if (enable)
    {
      [window setIgnoresMouseEvents:NO];
      [NSApp activateIgnoringOtherApps:YES];
      [window makeKeyAndOrderFront:nil];
    }
    else
    {
      [window setIgnoresMouseEvents:YES];
    }
    return;
  }

  id main_view = iupcocoaGetMainView(ih);

  if ([main_view isKindOfClass:[NSImageView class]])
  {
    return;
  }

  if ([main_view respondsToSelector:@selector(setEnabled:)])
  {
    [(NSControl*)main_view setEnabled:(BOOL)enable];
  }
  else
  {
    if([main_view isKindOfClass:[NSView class]])
    {
      [(NSView*)main_view setNeedsDisplay:YES];
    }
  }
}

IUP_SDK_API int iupdrvBaseSetBgColorAttrib(Ihandle* ih, const char* value)
{
  id the_object = ih->handle;

  if([the_object respondsToSelector:@selector(setBackgroundColor:)])
  {
    unsigned char r, g, b;
    if(iupStrToRGB(value, &r, &g, &b))
    {
      CGFloat red = r/255.0;
      CGFloat green = g/255.0;
      CGFloat blue = b/255.0;

      NSColor* the_color = [NSColor colorWithSRGBRed:red green:green blue:blue alpha:1.0];
      [the_object setBackgroundColor:the_color];
    }
    else
    {
      [the_object setBackgroundColor:nil];
    }
    return 1;
  }

  return 0;
}

IUP_SDK_API int iupdrvBaseSetFgColorAttrib(Ihandle* ih, const char* value)
{
  id main_view = iupcocoaGetMainView(ih);
  if (!main_view)
    return 0;

  unsigned char r, g, b;
  if (!iupStrToRGB(value, &r, &g, &b))
    return 0;

  CGFloat red = r/255.0;
  CGFloat green = g/255.0;
  CGFloat blue = b/255.0;
  NSColor* the_color = [NSColor colorWithSRGBRed:red green:green blue:blue alpha:1.0];

  /* For NSTextField, NSTextView, etc. */
  if ([main_view respondsToSelector:@selector(setTextColor:)])
  {
    [main_view setTextColor:the_color];
    return 1;
  }

  /* For NSButton */
  if ([main_view isKindOfClass:[NSButton class]])
  {
    NSButton* button = (NSButton*)main_view;
    NSMutableAttributedString *coloredTitle = [[button attributedTitle] mutableCopy];
    NSRange titleRange = NSMakeRange(0, [coloredTitle length]);
    [coloredTitle addAttribute:NSForegroundColorAttributeName value:the_color range:titleRange];
    [button setAttributedTitle:coloredTitle];
    [coloredTitle release];
    return 1;
  }

  /* For NSBox (used by IupFrame) */
  if ([main_view respondsToSelector:@selector(setTitleColor:)])
  {
    [main_view setTitleColor:the_color];
    return 1;
  }

  return 0; /* Control does not support changing foreground color */
}

@interface IUPCursorTrackingDelegate : NSObject
{
  NSCursor* _cursor;
}
@property(nonatomic, retain) NSCursor* cursor;
- (void)mouseEntered:(NSEvent *)event;
- (void)mouseExited:(NSEvent *)event;
- (void)dealloc;
@end

@implementation IUPCursorTrackingDelegate
@synthesize cursor = _cursor;
- (void)mouseEntered:(NSEvent *)event { [self.cursor set]; }
- (void)mouseExited:(NSEvent *)event { [[NSCursor arrowCursor] set]; }
- (void)dealloc { self.cursor = nil; [super dealloc]; }
@end


static NSCursor* iupCocoaGetCursor(Ihandle* ih, const char* name)
{
  static struct {
    const char* iupname;
    const char* sel_name;
  } table[] = {
    {"ARROW",     "arrowCursor"},
    {"BUSY",      "busyButClickableCursor"}, /* Not part of the API for NSCursor but apps are using it (i.e. Blender) */
    {"CROSS",     "crosshairCursor"},
    {"HAND",      "pointingHandCursor"},
    {"MOVE",      "closedHandCursor"},
    {"RESIZE_N",  "_windowResizeNorthCursor"},
    {"RESIZE_S",  "_windowResizeSouthCursor"},
    {"RESIZE_NS", "resizeUpDownCursor"},
    {"RESIZE_W",  "_windowResizeWestCursor"},
    {"RESIZE_E",  "_windowResizeEastCursor"},
    {"RESIZE_WE", "resizeLeftRightCursor"},
    {"SPLITTER_VERT",  "resizeLeftRightCursor"},
    {"SPLITTER_HORIZ", "resizeUpDownCursor"},
    {"RESIZE_NE", "_windowResizeNorthEastCursor"},
    {"RESIZE_SE", "_windowResizeSouthEastCursor"},
    {"RESIZE_NW", "_windowResizeNorthWestCursor"},
    {"RESIZE_SW", "_windowResizeSouthWestCursor"},
    {"TEXT",      "IBeamCursor"},
    {"HELP",      "contextualMenuCursor"},
    {"IUP",       "contextualMenuCursor"},
    {"NO",        "operationNotAllowedCursor"},
    {"UPARROW",   "arrowCursor"} /* No direct up-arrow cursor */
  };

  if (iupStrEqualNoCase(name, "NONE") || iupStrEqualNoCase(name, "NULL"))
    return nil;

  for (int i = 0; i < sizeof(table)/sizeof(table[0]); i++)
  {
    if (iupStrEqualNoCase(name, table[i].iupname))
    {
      SEL syssel = NSSelectorFromString([NSString stringWithUTF8String:table[i].sel_name]);
      if ([NSCursor respondsToSelector:syssel])
      {
        return [NSCursor performSelector:syssel];
      }
    }
  }

  return iupImageGetCursor(name);
}

IUP_SDK_API int iupdrvBaseSetCursorAttrib(Ihandle* ih, const char* value)
{
  NSView* main_view = iupcocoaGetMainView(ih);
  if (!main_view) return 0;

  IUPCursorTrackingDelegate* old_delegate = (IUPCursorTrackingDelegate*)iupAttribGet(ih, "_IUPCOCOA_CURSOR_DELEGATE");
  if (old_delegate)
  {
    NSTrackingArea* old_area = (NSTrackingArea*)iupAttribGet(ih, "_IUPCOCOA_TRACKINGAREA");
    if (old_area)
      [main_view removeTrackingArea:old_area];

    [old_delegate release];
    iupAttribSet(ih, "_IUPCOCOA_CURSOR_DELEGATE", NULL);
    iupAttribSet(ih, "_IUPCOCOA_TRACKINGAREA", NULL);
  }

  NSCursor* cursor = iupCocoaGetCursor(ih, value);
  if (cursor)
  {
    IUPCursorTrackingDelegate* delegate = [[IUPCursorTrackingDelegate alloc] init];
    delegate.cursor = cursor;

    NSTrackingArea* area = [[NSTrackingArea alloc] initWithRect:[main_view bounds]
                                                        options:(NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect)
                                                          owner:delegate
                                                       userInfo:nil];
    [main_view addTrackingArea:area];

    /* Store delegate and area to be cleaned up later. The view retains the area. */
    iupAttribSet(ih, "_IUPCOCOA_CURSOR_DELEGATE", (char*)delegate);
    iupAttribSet(ih, "_IUPCOCOA_TRACKINGAREA", (char*)area);
    [area release];
  }

  return 1;
}

IUP_SDK_API int iupdrvGetScrollbarSize(void)
{
#ifdef GNUSTEP
  return (int)[NSScroller scrollerWidth];
#else
  return [NSScroller scrollerWidthForControlSize:NSControlSizeRegular scrollerStyle:NSScrollerStyleLegacy];
#endif
}

#ifdef GNUSTEP
IUP_DRV_API void iupcocoaGnustepConfigureTableView(NSTableView* tableView)
{
  [tableView setDrawsGrid:NO];
  [tableView setAutoresizesAllColumnsToFit:YES];
}

IUP_DRV_API int iupcocoaGnustepIntrinsicHeight(NSControl* control, int minimum)
{
  NSSize intrinsic = [control intrinsicContentSize];
  int h = (int)lroundf(intrinsic.height);
  if (h <= 0)
  {
    [control sizeToFit];
    h = (int)lroundf([control frame].size.height);
  }
  if (h <= 0)
  {
    NSFont* f = [control font];
    if (!f) f = [NSFont systemFontOfSize:0];
    h = (int)ceil([f ascender] - [f descender] + [f leading]) + 8;
  }
  if (h < minimum) h = minimum;
  return h;
}

IUP_DRV_API void iupcocoaGnustepFillCellRect(NSView* cellView, NSRect dirtyRect, NSColor* customBackgroundColor)
{
  NSTableRowView* rowView = nil;
  NSTableView* tableView = nil;
  if ([[cellView superview] isKindOfClass:[NSTableRowView class]])
  {
    rowView = (NSTableRowView*)[cellView superview];
    if ([[rowView superview] isKindOfClass:[NSTableView class]])
      tableView = (NSTableView*)[rowView superview];
  }
  NSColor* fill = nil;
  if (rowView && [rowView isSelected])
    fill = [NSColor selectedControlColor];
  else if (customBackgroundColor)
    fill = customBackgroundColor;
  else if (tableView && [tableView usesAlternatingRowBackgroundColors])
  {
    NSInteger row = [tableView rowForView:cellView];
    if (row >= 0)
    {
      NSArray* alt = [NSColor controlAlternatingRowBackgroundColors];
      if ([alt count] > 0) fill = [alt objectAtIndex:(row % [alt count])];
    }
  }
  if (!fill && tableView) fill = [tableView backgroundColor];
  if (!fill) fill = [NSColor controlBackgroundColor];
  [fill setFill];
  NSRectFill(dirtyRect);
}
#endif

IUP_SDK_API void iupdrvSetAccessibleTitle(Ihandle *ih, const char* title)
{
  id the_object = iupcocoaGetMainView(ih);
  if([the_object respondsToSelector:@selector(setAccessibilityLabel:)])
  {
    if(!title)
    {
      [the_object setAccessibilityLabel:nil];
    }
    else
    {
      NSString* ns_title = [NSString stringWithUTF8String:title];
      [the_object setAccessibilityLabel:ns_title];
    }
  }
}

IUP_SDK_API void iupdrvSetAccessibleDescription(Ihandle *ih, const char* description)
{
  id the_object = iupcocoaGetMainView(ih);
  if([the_object respondsToSelector:@selector(setAccessibilityHelp:)])
    [the_object setAccessibilityHelp:description ? [NSString stringWithUTF8String:description] : nil];
}

IUP_SDK_API void iupdrvBaseRegisterCommonAttrib(Iclass* ic)
{
}

IUP_SDK_API void iupdrvBaseRegisterVisualAttrib(Iclass* ic)
{
  iupClassRegisterAttribute(ic, "TIPBGCOLOR", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_DEFAULT);
  iupClassRegisterAttribute(ic, "TIPFGCOLOR", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_DEFAULT);
  iupClassRegisterAttribute(ic, "TIPFONT", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_DEFAULT);
  iupClassRegisterAttribute(ic, "TIPMARKUP", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_DEFAULT);
  iupClassRegisterAttribute(ic, "TIPICON", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_DEFAULT);
  iupClassRegisterAttribute(ic, "TIPVISIBLE", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_NO_INHERIT);
}

IUP_DRV_API NSMutableAttributedString* iupcocoaBuildMarkupAttributedString(Ihandle* ih, const char* value)
{
  ImarkupData* data = iupMarkupParse(value);
  if (!data)
    return nil;

  NSMutableAttributedString* result = [[NSMutableAttributedString alloc] init];
  IupCocoaFont* iup_font = iupcocoaGetFont(ih);
  NSFont* base_font = [iup_font nativeFont];
  CGFloat base_size = base_font ? [base_font pointSize] : [NSFont systemFontSize];

  for (int i = 0; i < data->count; i++)
  {
    ImarkupRun* run = &data->runs[i];
    NSString* text = [NSString stringWithUTF8String:run->text];
    NSMutableDictionary* attrs = [NSMutableDictionary dictionary];

    CGFloat font_size = base_size;
    if (run->font_size)
      font_size = (CGFloat)run->font_size;
    else if (run->big || run->small_size)
    {
      int net = run->big - run->small_size;
      int j;
      for (j = 0; j < net; j++) font_size *= 1.2;
      for (j = 0; j > net; j--) font_size *= 0.83;
    }

    NSString* family = run->font_family
      ? [NSString stringWithUTF8String:run->font_family]
      : (base_font ? [base_font familyName] : nil);

    NSFontTraitMask traits = 0;
    if (run->bold || run->font_weight >= 700)
      traits |= NSBoldFontMask;
    if (run->italic || run->font_style == 1 || run->font_style == 2)
      traits |= NSItalicFontMask;

    NSFont* font = nil;
    if (family)
      font = [[NSFontManager sharedFontManager] fontWithFamily:family traits:traits weight:5 size:font_size];
    if (!font)
      font = [NSFont systemFontOfSize:font_size];
    if (font)
      [attrs setObject:font forKey:NSFontAttributeName];

    if (run->fg_color)
    {
      unsigned char r, g, b;
      if (iupStrToRGB(run->fg_color, &r, &g, &b))
      {
        NSColor* color = [NSColor colorWithSRGBRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1.0];
        [attrs setObject:color forKey:NSForegroundColorAttributeName];
      }
    }

    if (run->bg_color)
    {
      unsigned char r, g, b;
      if (iupStrToRGB(run->bg_color, &r, &g, &b))
      {
        NSColor* color = [NSColor colorWithSRGBRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1.0];
        [attrs setObject:color forKey:NSBackgroundColorAttributeName];
      }
    }

    if (run->underline)
      [attrs setObject:@(NSUnderlineStyleSingle) forKey:NSUnderlineStyleAttributeName];

    if (run->strikethrough)
      [attrs setObject:@(NSUnderlineStyleSingle) forKey:NSStrikethroughStyleAttributeName];

    if (run->superscript)
      [attrs setObject:@(1) forKey:NSSuperscriptAttributeName];
    else if (run->subscript)
      [attrs setObject:@(-1) forKey:NSSuperscriptAttributeName];

    NSAttributedString* run_str = [[NSAttributedString alloc] initWithString:text attributes:attrs];
    [result appendAttributedString:run_str];
    [run_str release];
  }

  iupMarkupFree(data);
  return result;
}

static NSEvent* g_iupcocoa_pending_mousedown = nil;
static NSMutableArray<NSEvent*>* g_iupcocoa_pending_drag = nil;

static NSPoint iupcocoaQuartzToCocoaScreen(int x, int y)
{
  CGFloat primary_h = NSMaxY([[[NSScreen screens] firstObject] frame]);
  return NSMakePoint(x, primary_h - y);
}

static NSWindow* iupcocoaFindWindowAtScreenPoint(NSPoint screen_pt)
{
  for (NSWindow* win in [NSApp orderedWindows])
  {
    if (![win isVisible]) continue;
    if (NSPointInRect(screen_pt, [win frame]))
      return win;
  }
  return nil;
}

IUP_SDK_API void iupdrvSendKey(int key, int press)
{
  unsigned int maccode, state;
  iupdrvKeyEncode(key, &maccode, &state);
  if (!maccode) return;

  NSWindow* win = [NSApp keyWindow];
  if (!win) win = [NSApp mainWindow];
  if (!win) return;

  NSTimeInterval ts = [[NSProcessInfo processInfo] systemUptime];
  NSEventModifierFlags mods = (NSEventModifierFlags)state;
  NSInteger win_num = [win windowNumber];

  if (press & 0x01)
  {
    NSEvent* ev = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSZeroPoint
                              modifierFlags:mods timestamp:ts windowNumber:win_num context:nil
                                 characters:@"" charactersIgnoringModifiers:@""
                                  isARepeat:NO keyCode:(unsigned short)maccode];
    if (ev) [NSApp postEvent:ev atStart:NO];
  }
  if (press & 0x02)
  {
    NSEvent* ev = [NSEvent keyEventWithType:NSEventTypeKeyUp location:NSZeroPoint
                              modifierFlags:mods timestamp:ts windowNumber:win_num context:nil
                                 characters:@"" charactersIgnoringModifiers:@""
                                  isARepeat:NO keyCode:(unsigned short)maccode];
    if (ev) [NSApp postEvent:ev atStart:NO];
  }
}

IUP_SDK_API void iupdrvSendMouse(int x, int y, int bt, int status)
{
  iupdrvWarpPointer(x, y);

  NSPoint screen_pt = iupcocoaQuartzToCocoaScreen(x, y);
  NSWindow* win = iupcocoaFindWindowAtScreenPoint(screen_pt);
  if (!win) return;

  NSPoint local_pt;
#ifndef GNUSTEP
  if ([win respondsToSelector:@selector(convertPointFromScreen:)])
    local_pt = [win convertPointFromScreen:screen_pt];
  else
#endif
    local_pt = [win convertRectFromScreen:NSMakeRect(screen_pt.x, screen_pt.y, 0, 0)].origin;

  NSTimeInterval ts = [[NSProcessInfo processInfo] systemUptime];
  NSInteger win_num = [win windowNumber];

  if (bt == 'W')
  {
#ifndef GNUSTEP
    CGEventRef wheel = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, status);
    if (wheel)
    {
      NSEvent* ev = [NSEvent eventWithCGEvent:wheel];
      if (ev) [NSApp postEvent:ev atStart:NO];
      CFRelease(wheel);
    }
#endif
    return;
  }

  if (status == -1)
  {
    NSEventType move_type = NSEventTypeMouseMoved;
    int drag_click_count = 0;
    switch (bt)
    {
      case IUP_BUTTON1: move_type = NSEventTypeLeftMouseDragged;  drag_click_count = 1; break;
      case IUP_BUTTON2: move_type = NSEventTypeOtherMouseDragged; drag_click_count = 1; break;
      case IUP_BUTTON3: move_type = NSEventTypeRightMouseDragged; drag_click_count = 1; break;
    }
    NSEvent* ev = [NSEvent mouseEventWithType:move_type location:local_pt
                                modifierFlags:0 timestamp:ts windowNumber:win_num context:nil
                                  eventNumber:0 clickCount:drag_click_count pressure:(bt=='0'?0:1.0f)];
    if (!ev) return;
    if (g_iupcocoa_pending_drag && bt != '0')
      [g_iupcocoa_pending_drag addObject:ev];
    else
      [NSApp postEvent:ev atStart:NO];
    return;
  }

  NSEventType type;
  int click_count = 1;
  switch (bt)
  {
    case IUP_BUTTON1:
      type = (status == 0) ? NSEventTypeLeftMouseUp  : NSEventTypeLeftMouseDown;  break;
    case IUP_BUTTON2:
      type = (status == 0) ? NSEventTypeOtherMouseUp : NSEventTypeOtherMouseDown; break;
    case IUP_BUTTON3:
      type = (status == 0) ? NSEventTypeRightMouseUp : NSEventTypeRightMouseDown; break;
    default: return;
  }
  if (status == 2) click_count = 2;

  NSEvent* ev = [NSEvent mouseEventWithType:type location:local_pt modifierFlags:0
                                  timestamp:ts windowNumber:win_num context:nil
                                eventNumber:0 clickCount:click_count
                                   pressure:(status == 0 ? 0 : 1.0f)];
  if (!ev) return;

  if (status == 1 || status == 2)
  {
    [g_iupcocoa_pending_mousedown release];
    g_iupcocoa_pending_mousedown = [ev retain];
    [g_iupcocoa_pending_drag release];
    g_iupcocoa_pending_drag = [[NSMutableArray alloc] init];
    if (status == 2)
    {
      NSEvent* up_ev = [NSEvent mouseEventWithType:(bt==IUP_BUTTON1?NSEventTypeLeftMouseUp:bt==IUP_BUTTON2?NSEventTypeOtherMouseUp:NSEventTypeRightMouseUp)
                                          location:local_pt modifierFlags:0 timestamp:ts windowNumber:win_num
                                           context:nil eventNumber:0 clickCount:click_count pressure:0];
      if (up_ev) [g_iupcocoa_pending_drag addObject:up_ev];
      for (NSEvent* e in g_iupcocoa_pending_drag) [NSApp postEvent:e atStart:NO];
      [NSApp sendEvent:g_iupcocoa_pending_mousedown];
      [g_iupcocoa_pending_mousedown release];
      g_iupcocoa_pending_mousedown = nil;
      [g_iupcocoa_pending_drag release];
      g_iupcocoa_pending_drag = nil;
    }
  }
  else if (g_iupcocoa_pending_mousedown)
  {
    [g_iupcocoa_pending_drag addObject:ev];
    for (NSEvent* e in g_iupcocoa_pending_drag) [NSApp postEvent:e atStart:NO];
    [NSApp sendEvent:g_iupcocoa_pending_mousedown];
    [g_iupcocoa_pending_mousedown release];
    g_iupcocoa_pending_mousedown = nil;
    [g_iupcocoa_pending_drag release];
    g_iupcocoa_pending_drag = nil;
  }
  else
  {
    [NSApp postEvent:ev atStart:NO];
  }
}

IUP_SDK_API void iupdrvSleep(int time)
{
  usleep(time * 1000);
}

IUP_SDK_API void iupdrvWarpPointer(int x, int y)
{
#ifndef GNUSTEP
  CGPoint point = CGPointMake(x, y);
  CGWarpMouseCursorPosition(point);
  CGAssociateMouseAndMouseCursorPosition(true);
#else
  (void)x;
  (void)y;
#endif
}

IUP_DRV_API void iupcocoaCommonBaseAppendMenuItems(NSMenu* dst_menu, NSMenu* src_menu)
{
  if((src_menu != nil) && ([src_menu numberOfItems] > 0))
  {
    [dst_menu addItem:[NSMenuItem separatorItem]];

    NSArray<NSMenuItem*>* item_array = [src_menu itemArray];
    for(NSMenuItem* a_default_item in item_array)
    {
      NSMenuItem* item_copy = [a_default_item copy];
      [dst_menu addItem:item_copy];
      [item_copy release];
    }
  }
}

IUP_DRV_API void iupcocoaCommonBaseAppendDefaultMenuItemsForClassType(NSMenu* dst_menu, Class class_of_widget)
{
  if([class_of_widget respondsToSelector:@selector(defaultMenu)])
  {
    NSMenu* default_menu = [class_of_widget defaultMenu];
    iupcocoaCommonBaseAppendMenuItems(dst_menu, default_menu);
  }
}

IUP_DRV_API void iupcocoaCommonBaseSetContextMenuForWidget(Ihandle* ih, id widget_to_attach_menu_to, Ihandle* menu_ih)
{
  /* Mark that the user has configured this attribute. This allows delegate methods
     to distinguish between "never set" (use default behavior) and "set to nil" (disable menu). */
  iupAttribSet(ih, "_IUPCOCOA_CONTEXTMENU_SET", "1");
  iupAttribSet(ih, "_COCOA_CONTEXT_MENU_IH", (const char*)menu_ih);

  if (NULL == menu_ih)
  {
    if ([widget_to_attach_menu_to respondsToSelector:@selector(setMenu:)])
    {
      [widget_to_attach_menu_to setMenu:nil];
    }
    return;
  }

  if (NULL == menu_ih->handle)
  {
    IupMap(menu_ih);
  }

  if (menu_ih->iclass->nativetype != IUP_TYPEMENU || ![(id)menu_ih->handle isKindOfClass:[NSMenu class]])
  {
    return;
  }

  NSMenu* the_menu = (NSMenu*)menu_ih->handle;
  if ([widget_to_attach_menu_to respondsToSelector:@selector(setMenu:)])
  {
    iupcocoaCommonBaseAppendDefaultMenuItemsForClassType(the_menu, [widget_to_attach_menu_to class]);
    [widget_to_attach_menu_to setMenu:the_menu];
  }
}

IUP_DRV_API int iupcocoaCommonBaseIupButtonForCocoaButton(NSInteger which_cocoa_button)
{
#ifdef GNUSTEP
  /* GNUstep-back follows X11: 1=left, 2=middle, 3=right, 4/5=wheel. */
  if(1 == which_cocoa_button) return IUP_BUTTON1;
  if(2 == which_cocoa_button) return IUP_BUTTON2;
  if(3 == which_cocoa_button) return IUP_BUTTON3;
  if(4 == which_cocoa_button) return IUP_BUTTON4;
  if(5 == which_cocoa_button) return IUP_BUTTON5;
#else
  if(0 == which_cocoa_button) return IUP_BUTTON1;
  if(1 == which_cocoa_button) return IUP_BUTTON3;
  if(2 == which_cocoa_button) return IUP_BUTTON2;
  if(3 == which_cocoa_button) return IUP_BUTTON4;
  if(4 == which_cocoa_button) return IUP_BUTTON5;
#endif
  return (int)(which_cocoa_button + '0'); /* Other buttons */
}

IUP_DRV_API bool iupcocoaCommonBaseHandleMouseButtonCallback(Ihandle* ih, NSEvent* the_event, NSView* represented_view, bool is_pressed)
{
  IFniiiis callback_function;
  bool caller_should_propagate = true;

  if (!iupdrvIsActive(ih))
    return false;

  callback_function = (IFniiiis)IupGetCallback(ih, "BUTTON_CB");
  if(callback_function)
  {
    NSPoint the_point = [the_event locationInWindow];
    NSPoint converted_point = [represented_view convertPoint:the_point fromView:nil];
    CGFloat final_y = converted_point.y;

    /* Convert from Cocoa's coordinate system (origin bottom-left) to IUP's (origin top-left),
       but only if the view is not already flipped (which would mean it is already top-left). */
    if(![represented_view isFlipped])
    {
      NSRect view_bounds = [represented_view bounds];
      final_y = view_bounds.size.height - converted_point.y;
    }

    NSInteger which_cocoa_button = [the_event buttonNumber];
    char mod_status[IUPKEY_STATUS_SIZE] = IUPKEY_STATUS_INIT;
    iupcocoaButtonKeySetStatus(the_event, mod_status);

    if([the_event modifierFlags] & NSEventModifierFlagControl && 0 == which_cocoa_button)
    {
      which_cocoa_button = 1; /* Ctrl-Left-click becomes a right-click */
    }

    int which_iup_button = iupcocoaCommonBaseIupButtonForCocoaButton(which_cocoa_button);

    int callback_result = callback_function(ih, which_iup_button, is_pressed, iupROUND(converted_point.x), iupROUND(final_y), mod_status);
    if(IUP_CLOSE == callback_result)
    {
      IupExitLoop();
      caller_should_propagate = false;
    }
    else if(IUP_IGNORE == callback_result)
    {
      caller_should_propagate = false;
    }
    else if(IUP_CONTINUE == callback_result)
    {
      caller_should_propagate = true;
    }
    else
    {
      caller_should_propagate = false;
    }

    /* Discard pending mouse-up events that may have been queued during
       a modal dialog run inside the callback. */
    if(is_pressed)
    {
      NSUInteger mask;
      switch(which_cocoa_button)
      {
        case 0:  mask = NSEventMaskLeftMouseUp; break;
        case 1:  mask = NSEventMaskRightMouseUp; break;
        default: mask = NSEventMaskOtherMouseUp; break;
      }
      [NSApp discardEventsMatchingMask:mask beforeEvent:nil];
    }
  }
  else
  {
    caller_should_propagate = true;
  }
  return !caller_should_propagate;
}

IUP_DRV_API bool iupcocoaCommonBaseHandleMouseMotionCallback(Ihandle* ih, NSEvent* the_event, NSView* represented_view)
{
  bool caller_should_propagate = true;
  IFniis callback_function;

  if (!iupdrvIsActive(ih))
    return false;

  callback_function = (IFniis)IupGetCallback(ih, "MOTION_CB");
  if(callback_function)
  {
    NSPoint the_point = [the_event locationInWindow];
    NSPoint converted_point = [represented_view convertPoint:the_point fromView:nil];
    CGFloat final_y = converted_point.y;

    /* Convert from Cocoa's coordinate system (origin bottom-left) to IUP's (origin top-left),
       but only if the view is not already flipped (which would mean it is already top-left). */
    if(![represented_view isFlipped])
    {
      NSRect view_bounds = [represented_view bounds];
      final_y = view_bounds.size.height - converted_point.y;
    }

    char mod_status[IUPKEY_STATUS_SIZE] = IUPKEY_STATUS_INIT;
    iupcocoaButtonKeySetStatus(the_event, mod_status);

    callback_function(ih,  iupROUND(converted_point.x), iupROUND(final_y), mod_status);
  }
  return !caller_should_propagate;
}

IUP_DRV_API bool iupcocoaCommonBaseScrollWheelCallback(Ihandle* ih, NSEvent* the_event, NSView* represented_view)
{
  IFnfiis callback_function;
  bool caller_should_propagate = true;

  callback_function = (IFnfiis)IupGetCallback(ih, "WHEEL_CB");
  if(callback_function)
  {
    NSPoint the_point = [the_event locationInWindow];
    NSPoint converted_point = [represented_view convertPoint:the_point fromView:nil];
    CGFloat final_y = converted_point.y;

    /* IUP's WHEEL_CB 'delta' corresponds to vertical scrolling.
       On macOS, deltaY > 0 means scroll up (content should move up, showing content above).
       This matches the IUP convention where positive delta means scroll up. */
    CGFloat delta = [the_event deltaY];

    /* IUP does not have a separate parameter for horizontal scrolling (deltaX). */

    /* Convert from Cocoa's coordinate system (origin bottom-left) to IUP's (origin top-left),
       but only if the view is not already flipped (which would mean it is already top-left). */
    if(![represented_view isFlipped])
    {
      NSRect view_bounds = [represented_view bounds];
      final_y = view_bounds.size.height - converted_point.y;
    }

    char mod_status[IUPKEY_STATUS_SIZE] = IUPKEY_STATUS_INIT;
    iupcocoaButtonKeySetStatus(the_event, mod_status);

    int callback_result = callback_function(ih, delta, iupROUND(converted_point.x), iupROUND(final_y), mod_status);
    if(IUP_CLOSE == callback_result)
    {
      IupExitLoop();
      caller_should_propagate = false;
    }
    else if(IUP_IGNORE == callback_result)
    {
      caller_should_propagate = false;
    }
  }
  return !caller_should_propagate;
}

IUP_DRV_API int iupcocoaCommonBaseSetContextMenuAttrib(Ihandle* ih, const char* value)
{
  Ihandle* menu_ih = (Ihandle*)value;
  id widget_to_attach_menu_to = iupcocoaGetMainView(ih);

  iupcocoaCommonBaseSetContextMenuForWidget(ih, widget_to_attach_menu_to, menu_ih);

  return 1;
}

IUP_DRV_API char* iupcocoaCommonBaseGetContextMenuAttrib(Ihandle* ih)
{
  return (char*)iupAttribGet(ih, "_COCOA_CONTEXT_MENU_IH");
}


IUP_DRV_API NSWindow* iupcocoaDialogGetWindow(Ihandle* ih)
{
  if (!ih || !ih->handle) return nil;
  id root_object = (id)ih->handle;
  if ([root_object isKindOfClass:[NSWindow class]])
  {
    return (NSWindow*)root_object;
  }
  return nil;
}

IUP_DRV_API int iupcocoaIsSystemDarkMode(void)
{
  NSAppearance *appearance = [NSApp effectiveAppearance];
  NSString *appearanceName = [appearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]];
  return [appearanceName isEqualToString:NSAppearanceNameDarkAqua] ? 1 : 0;
}
