view ios/dw.m @ 2909:3fe7641f027c

WARNING: Fix an API inconsistency in dw_notebook_page_destroy/set() These two APIs incorrectly referenced the page ID as an int not long. This change may cause ABI problems with programs that use these on some platforms. Recompile your apps that use these functions for safety.
author bsmith@81767d24-ef19-dc11-ae90-00e081727c95
date Tue, 27 Dec 2022 00:58:58 +0000
parents 761b7a12b079
children edb4307ac7ce
line wrap: on
line source

/*
 * Dynamic Windows:
 *          A GTK like implementation of the iOS GUI
 *
 * (C) 2011-2022 Brian Smith <brian@dbsoft.org>
 * (C) 2011-2021 Mark Hessling <mark@rexx.org>
 * (C) 2017 Ralph Shane (Base tree view implementation)
 *
 * Requires 13.0 or later.
 * clang -g -o dwtest -D__IOS__ -I. dwtest.c ios/dw.m -framework UIKit -framework WebKit -framework Foundation -framework UserNotifications
 */
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
#import <AudioToolbox/AudioToolbox.h>
#import <UserNotifications/UserNotifications.h>
#import <UniformTypeIdentifiers/UTDefines.h>
#import <UniformTypeIdentifiers/UTType.h>
#import <UniformTypeIdentifiers/UTCoreTypes.h>
#include "dw.h"
#include <sys/utsname.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <math.h>
#include <spawn.h>

/* Macros to handle local auto-release pools */
#define DW_LOCAL_POOL_IN NSAutoreleasePool *localpool = nil; \
        if(DWThread != (DWTID)-1 && pthread_self() != DWThread) \
            localpool = [[NSAutoreleasePool alloc] init];
#define DW_LOCAL_POOL_OUT if(localpool) [localpool drain];

/* Macros to encapsulate running functions on the main thread */
#define DW_FUNCTION_INIT
#define DW_FUNCTION_DEFINITION(func, rettype, ...)  void _##func(NSPointerArray *_args); \
rettype API func(__VA_ARGS__) { \
    DW_LOCAL_POOL_IN; \
    NSPointerArray *_args = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsOpaqueMemory]; \
    [_args addPointer:(void *)_##func];
#define DW_FUNCTION_ADD_PARAM1(param1) [_args addPointer:(void *)&param1]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_fg_color_key)]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_bg_color_key)];
#define DW_FUNCTION_ADD_PARAM2(param1, param2) [_args addPointer:(void *)&param1]; \
    [_args addPointer:(void *)&param2]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_fg_color_key)]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_bg_color_key)];
#define DW_FUNCTION_ADD_PARAM3(param1, param2, param3) [_args addPointer:(void *)&param1]; \
    [_args addPointer:(void *)&param2]; \
    [_args addPointer:(void *)&param3]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_fg_color_key)]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_bg_color_key)];
#define DW_FUNCTION_ADD_PARAM4(param1, param2, param3, param4) [_args addPointer:(void *)&param1]; \
    [_args addPointer:(void *)&param2]; \
    [_args addPointer:(void *)&param3]; \
    [_args addPointer:(void *)&param4]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_fg_color_key)]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_bg_color_key)];
#define DW_FUNCTION_ADD_PARAM5(param1, param2, param3, param4, param5) [_args addPointer:(void *)&param1]; \
    [_args addPointer:(void *)&param2]; \
    [_args addPointer:(void *)&param3]; \
    [_args addPointer:(void *)&param4]; \
    [_args addPointer:(void *)&param5]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_fg_color_key)]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_bg_color_key)];
#define DW_FUNCTION_ADD_PARAM6(param1, param2, param3, param4, param5, param6) \
    [_args addPointer:(void *)&param1]; \
    [_args addPointer:(void *)&param2]; \
    [_args addPointer:(void *)&param3]; \
    [_args addPointer:(void *)&param4]; \
    [_args addPointer:(void *)&param5]; \
    [_args addPointer:(void *)&param6]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_fg_color_key)]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_bg_color_key)];
#define DW_FUNCTION_ADD_PARAM7(param1, param2, param3, param4, param5, param6, param7) \
    [_args addPointer:(void *)&param1]; \
    [_args addPointer:(void *)&param2]; \
    [_args addPointer:(void *)&param3]; \
    [_args addPointer:(void *)&param4]; \
    [_args addPointer:(void *)&param5]; \
    [_args addPointer:(void *)&param6]; \
    [_args addPointer:(void *)&param7]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_fg_color_key)]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_bg_color_key)];
#define DW_FUNCTION_ADD_PARAM8(param1, param2, param3, param4, param5, param6, param7, param8) \
    [_args addPointer:(void *)&param1]; \
    [_args addPointer:(void *)&param2]; \
    [_args addPointer:(void *)&param3]; \
    [_args addPointer:(void *)&param4]; \
    [_args addPointer:(void *)&param5]; \
    [_args addPointer:(void *)&param6]; \
    [_args addPointer:(void *)&param7]; \
    [_args addPointer:(void *)&param8]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_fg_color_key)]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_bg_color_key)];
#define DW_FUNCTION_ADD_PARAM9(param1, param2, param3, param4, param5, param6, param7, param8, param9) \
    [_args addPointer:(void *)&param1]; \
    [_args addPointer:(void *)&param2]; \
    [_args addPointer:(void *)&param3]; \
    [_args addPointer:(void *)&param4]; \
    [_args addPointer:(void *)&param5]; \
    [_args addPointer:(void *)&param6]; \
    [_args addPointer:(void *)&param7]; \
    [_args addPointer:(void *)&param8]; \
    [_args addPointer:(void *)&param9]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_fg_color_key)]; \
    [_args addPointer:(void *)pthread_getspecific(_dw_bg_color_key)];
#define DW_FUNCTION_RESTORE_PARAM1(param1, vartype1) \
    vartype1 param1 = *((vartype1 *)[_args pointerAtIndex:1]); \
    UIColor *__attribute__((__unused__))_dw_fg_color = (UIColor *)[_args pointerAtIndex:2]; \
    UIColor *__attribute__((__unused__))_dw_bg_color = (UIColor *)[_args pointerAtIndex:3];
#define DW_FUNCTION_RESTORE_PARAM2(param1, vartype1, param2, vartype2) \
    vartype1 param1 = *((vartype1 *)[_args pointerAtIndex:1]); \
    vartype2 param2 = *((vartype2 *)[_args pointerAtIndex:2]); \
    UIColor *__attribute__((__unused__))_dw_fg_color = (UIColor *)[_args pointerAtIndex:3]; \
    UIColor *__attribute__((__unused__))_dw_bg_color = (UIColor *)[_args pointerAtIndex:4];
#define DW_FUNCTION_RESTORE_PARAM3(param1, vartype1, param2, vartype2, param3, vartype3) \
    vartype1 param1 = *((vartype1 *)[_args pointerAtIndex:1]); \
    vartype2 param2 = *((vartype2 *)[_args pointerAtIndex:2]); \
    vartype3 param3 = *((vartype3 *)[_args pointerAtIndex:3]); \
    UIColor *__attribute__((__unused__))_dw_fg_color = (UIColor *)[_args pointerAtIndex:4]; \
    UIColor *__attribute__((__unused__))_dw_bg_color = (UIColor *)[_args pointerAtIndex:5];
#define DW_FUNCTION_RESTORE_PARAM4(param1, vartype1, param2, vartype2, param3, vartype3, param4, vartype4) \
    vartype1 param1 = *((vartype1 *)[_args pointerAtIndex:1]); \
    vartype2 param2 = *((vartype2 *)[_args pointerAtIndex:2]); \
    vartype3 param3 = *((vartype3 *)[_args pointerAtIndex:3]); \
    vartype4 param4 = *((vartype4 *)[_args pointerAtIndex:4]); \
    UIColor *__attribute__((__unused__))_dw_fg_color = (UIColor *)[_args pointerAtIndex:5]; \
    UIColor *__attribute__((__unused__))_dw_bg_color = (UIColor *)[_args pointerAtIndex:6];
#define DW_FUNCTION_RESTORE_PARAM5(param1, vartype1, param2, vartype2, param3, vartype3, param4, vartype4, param5, vartype5) \
    vartype1 param1 = *((vartype1 *)[_args pointerAtIndex:1]); \
    vartype2 param2 = *((vartype2 *)[_args pointerAtIndex:2]); \
    vartype3 param3 = *((vartype3 *)[_args pointerAtIndex:3]); \
    vartype4 param4 = *((vartype4 *)[_args pointerAtIndex:4]); \
    vartype5 param5 = *((vartype5 *)[_args pointerAtIndex:5]); \
    UIColor *__attribute__((__unused__))_dw_fg_color = (UIColor *)[_args pointerAtIndex:6]; \
    UIColor *__attribute__((__unused__))_dw_bg_color = (UIColor *)[_args pointerAtIndex:7];
#define DW_FUNCTION_RESTORE_PARAM6(param1, vartype1, param2, vartype2, param3, vartype3, param4, vartype4, param5, vartype5, param6, vartype6) \
    vartype1 param1 = *((vartype1 *)[_args pointerAtIndex:1]); \
    vartype2 param2 = *((vartype2 *)[_args pointerAtIndex:2]); \
    vartype3 param3 = *((vartype3 *)[_args pointerAtIndex:3]); \
    vartype4 param4 = *((vartype4 *)[_args pointerAtIndex:4]); \
    vartype5 param5 = *((vartype5 *)[_args pointerAtIndex:5]); \
    vartype6 param6 = *((vartype6 *)[_args pointerAtIndex:6]); \
    UIColor *__attribute__((__unused__))_dw_fg_color = (UIColor *)[_args pointerAtIndex:7]; \
    UIColor *__attribute__((__unused__))_dw_bg_color = (UIColor *)[_args pointerAtIndex:8];
#define DW_FUNCTION_RESTORE_PARAM7(param1, vartype1, param2, vartype2, param3, vartype3, param4, vartype4, param5, vartype5, param6, vartype6, param7, vartype7) \
    vartype1 param1 = *((vartype1 *)[_args pointerAtIndex:1]); \
    vartype2 param2 = *((vartype2 *)[_args pointerAtIndex:2]); \
    vartype3 param3 = *((vartype3 *)[_args pointerAtIndex:3]); \
    vartype4 param4 = *((vartype4 *)[_args pointerAtIndex:4]); \
    vartype5 param5 = *((vartype5 *)[_args pointerAtIndex:5]); \
    vartype6 param6 = *((vartype6 *)[_args pointerAtIndex:6]); \
    vartype7 param7 = *((vartype7 *)[_args pointerAtIndex:7]); \
    UIColor *__attribute__((__unused__))_dw_fg_color = (UIColor *)[_args pointerAtIndex:8]; \
    UIColor *__attribute__((__unused__))_dw_bg_color = (UIColor *)[_args pointerAtIndex:9];
#define DW_FUNCTION_RESTORE_PARAM8(param1, vartype1, param2, vartype2, param3, vartype3, param4, vartype4, param5, vartype5, param6, vartype6, param7, vartype7, param8, vartype8) \
    vartype1 param1 = *((vartype1 *)[_args pointerAtIndex:1]); \
    vartype2 param2 = *((vartype2 *)[_args pointerAtIndex:2]); \
    vartype3 param3 = *((vartype3 *)[_args pointerAtIndex:3]); \
    vartype4 param4 = *((vartype4 *)[_args pointerAtIndex:4]); \
    vartype5 param5 = *((vartype5 *)[_args pointerAtIndex:5]); \
    vartype6 param6 = *((vartype6 *)[_args pointerAtIndex:6]); \
    vartype7 param7 = *((vartype7 *)[_args pointerAtIndex:7]); \
    vartype8 param8 = *((vartype8 *)[_args pointerAtIndex:8]); \
    UIColor *__attribute__((__unused__))_dw_fg_color = (UIColor *)[_args pointerAtIndex:9]; \
    UIColor *__attribute__((__unused__))_dw_bg_color = (UIColor *)[_args pointerAtIndex:10];
#define DW_FUNCTION_RESTORE_PARAM9(param1, vartype1, param2, vartype2, param3, vartype3, param4, vartype4, param5, vartype5, param6, vartype6, param7, vartype7, param8, vartype8, param9, vartype9) \
    vartype1 param1 = *((vartype1 *)[_args pointerAtIndex:1]); \
    vartype2 param2 = *((vartype2 *)[_args pointerAtIndex:2]); \
    vartype3 param3 = *((vartype3 *)[_args pointerAtIndex:3]); \
    vartype4 param4 = *((vartype4 *)[_args pointerAtIndex:4]); \
    vartype5 param5 = *((vartype5 *)[_args pointerAtIndex:5]); \
    vartype6 param6 = *((vartype6 *)[_args pointerAtIndex:6]); \
    vartype7 param7 = *((vartype7 *)[_args pointerAtIndex:7]); \
    vartype8 param8 = *((vartype8 *)[_args pointerAtIndex:8]); \
    vartype9 param9 = *((vartype9 *)[_args pointerAtIndex:9]); \
    UIColor *__attribute__((__unused__))_dw_fg_color = (UIColor *)[_args pointerAtIndex:10]; \
    UIColor *__attribute__((__unused__))_dw_bg_color = (UIColor *)[_args pointerAtIndex:11];
#define DW_FUNCTION_END }
#define DW_FUNCTION_NO_RETURN(func) [DWObj safeCall:@selector(callBack:) withObject:_args]; \
    [_args release]; \
    DW_LOCAL_POOL_OUT; } \
void _##func(NSPointerArray *_args) {
#define DW_FUNCTION_RETURN(func, rettype) [DWObj safeCall:@selector(callBack:) withObject:_args]; {\
        void *tmp = [_args pointerAtIndex:[_args count]-1]; \
        rettype myreturn = *((rettype *)tmp); \
        free(tmp); \
        [_args release]; \
        DW_LOCAL_POOL_OUT; \
        return myreturn; }} \
void _##func(NSPointerArray *_args) {
#define DW_FUNCTION_RETURN_THIS(_retvar) { void *_myreturn = malloc(sizeof(_retvar)); \
    memcpy(_myreturn, (void *)&_retvar, sizeof(_retvar)); \
    [_args addPointer:_myreturn]; }}
#define DW_FUNCTION_RETURN_NOTHING }

unsigned long _dw_colors[] =
{
    0x00000000,   /* 0  black */
    0x000000bb,   /* 1  red */
    0x0000bb00,   /* 2  green */
    0x0000aaaa,   /* 3  yellow */
    0x00cc0000,   /* 4  blue */
    0x00bb00bb,   /* 5  magenta */
    0x00bbbb00,   /* 6  cyan */
    0x00bbbbbb,   /* 7  white */
    0x00777777,   /* 8  grey */
    0x000000ff,   /* 9  bright red */
    0x0000ff00,   /* 10 bright green */
    0x0000eeee,   /* 11 bright yellow */
    0x00ff0000,   /* 12 bright blue */
    0x00ff00ff,   /* 13 bright magenta */
    0x00eeee00,   /* 14 bright cyan */
    0x00ffffff,   /* 15 bright white */
    0xff000000    /* 16 default color */
};

/*
 * List those icons that have transparency first
 */
#define NUM_EXTS 8
char *_dw_image_exts[NUM_EXTS+1] =
{
   ".png",
   ".ico",
   ".icns",
   ".gif",
   ".jpg",
   ".jpeg",
   ".tiff",
   ".bmp",
    NULL
};

char *_dw_get_image_extension(const char *filename)
{
   char *file = alloca(strlen(filename) + 6);
   int found_ext = 0,i;

   /* Try various extentions */
   for(i = 0; i < NUM_EXTS; i++)
   {
      strcpy(file, filename);
      strcat(file, _dw_image_exts[i]);
      if(access(file, R_OK ) == 0)
      {
         found_ext = 1;
         break;
      }
   }
   if(found_ext == 1)
   {
      return _dw_image_exts[i];
   }
   return NULL;
}

/* Return the RGB color regardless if a predefined color was passed */
unsigned long _dw_get_color(unsigned long thiscolor)
{
    if(thiscolor & DW_RGB_COLOR)
    {
        return thiscolor & ~DW_RGB_COLOR;
    }
    else if(thiscolor < 17)
    {
        return _dw_colors[thiscolor];
    }
    return 0;
}

/* Thread specific storage */
pthread_key_t _dw_pool_key;
pthread_key_t _dw_fg_color_key;
pthread_key_t _dw_bg_color_key;
static int DWOSMajor, DWOSMinor, DWOSBuild;
static char _dw_bundle_path[PATH_MAX+1] = { 0 };
static char _dw_app_id[_DW_APP_ID_SIZE+1]= {0};
static int _dw_dark_mode_state = DW_FEATURE_UNSUPPORTED;
static int _dw_container_mode = DW_CONTAINER_MODE_DEFAULT;
static CGPoint _dw_event_point = {0};
static NSMutableArray *_dw_toplevel_windows;

/* Create a default colors for a thread */
void _dw_init_colors(void)
{
    UIColor *fgcolor = [[UIColor grayColor] retain];
    pthread_setspecific(_dw_fg_color_key, fgcolor);
    pthread_setspecific(_dw_bg_color_key, NULL);
}

typedef struct _dwsighandler
{
    struct _dwsighandler   *next;
    ULONG message;
    HWND window;
    int id;
    void *signalfunction;
    void *discfunction;
    void *data;

} DWSignalHandler;

static DWSignalHandler *DWRoot = NULL;

/* Some internal prototypes */
static void _dw_do_resize(Box *thisbox, int x, int y);
void _dw_handle_resize_events(Box *thisbox);
int _dw_remove_userdata(UserData **root, const char *varname, int all);
int _dw_main_iteration(NSDate *date);
CGContextRef _dw_draw_context(id image, bool antialias);
typedef id (*DWIMP)(id, SEL, ...);

/* Internal function to queue a window redraw */
void _dw_redraw(id window, int skip)
{
    static id lastwindow = nil;
    id redraw = lastwindow;

    if(skip && window == nil)
        return;

    lastwindow = window;
    if(redraw != lastwindow && redraw != nil)
    {
        dw_window_redraw(redraw);
    }
}

DWSignalHandler *_dw_get_handler(HWND window, int messageid)
{
    DWSignalHandler *tmp = DWRoot;

    /* Find any callbacks for this function */
    while(tmp)
    {
        if(tmp->message == messageid && window == tmp->window)
        {
            return tmp;
        }
        tmp = tmp->next;
    }
    return NULL;
}

typedef struct
{
    ULONG message;
    char name[30];

} DWSignalList;

/* List of signals */
#define SIGNALMAX 19

static DWSignalList DWSignalTranslate[SIGNALMAX] = {
    { _DW_EVENT_CONFIGURE,       DW_SIGNAL_CONFIGURE },
    { _DW_EVENT_KEY_PRESS,       DW_SIGNAL_KEY_PRESS },
    { _DW_EVENT_BUTTON_PRESS,    DW_SIGNAL_BUTTON_PRESS },
    { _DW_EVENT_BUTTON_RELEASE,  DW_SIGNAL_BUTTON_RELEASE },
    { _DW_EVENT_MOTION_NOTIFY,   DW_SIGNAL_MOTION_NOTIFY },
    { _DW_EVENT_DELETE,          DW_SIGNAL_DELETE },
    { _DW_EVENT_EXPOSE,          DW_SIGNAL_EXPOSE },
    { _DW_EVENT_CLICKED,         DW_SIGNAL_CLICKED },
    { _DW_EVENT_ITEM_ENTER,      DW_SIGNAL_ITEM_ENTER },
    { _DW_EVENT_ITEM_CONTEXT,    DW_SIGNAL_ITEM_CONTEXT },
    { _DW_EVENT_LIST_SELECT,     DW_SIGNAL_LIST_SELECT },
    { _DW_EVENT_ITEM_SELECT,     DW_SIGNAL_ITEM_SELECT },
    { _DW_EVENT_SET_FOCUS,       DW_SIGNAL_SET_FOCUS },
    { _DW_EVENT_VALUE_CHANGED,   DW_SIGNAL_VALUE_CHANGED },
    { _DW_EVENT_SWITCH_PAGE,     DW_SIGNAL_SWITCH_PAGE },
    { _DW_EVENT_TREE_EXPAND,     DW_SIGNAL_TREE_EXPAND },
    { _DW_EVENT_COLUMN_CLICK,    DW_SIGNAL_COLUMN_CLICK },
    { _DW_EVENT_HTML_RESULT,     DW_SIGNAL_HTML_RESULT },
    { _DW_EVENT_HTML_CHANGED,    DW_SIGNAL_HTML_CHANGED }
};

int _dw_event_handler1(id object, id event, int message)
{
    DWSignalHandler *handler = _dw_get_handler(object, message);
    /* NSLog(@"Event handler - type %d\n", message); */

    if(handler)
    {
        switch(message)
        {
            /* Timer event */
            case _DW_EVENT_TIMER:
            {
                int (* API timerfunc)(void *) = (int (* API)(void *))handler->signalfunction;

                if(!timerfunc(handler->data))
                    dw_timer_disconnect(handler->window);
                return 0;
            }
            /* Configure/Resize event */
            case _DW_EVENT_CONFIGURE:
            {
                int (*sizefunc)(HWND, int, int, void *) = handler->signalfunction;
                CGSize size;

                if([object isKindOfClass:[UIWindow class]])
                {
                    UIWindow *window = object;
                    size = [window frame].size;
                }
                else
                {
                    UIView *view = object;
                    size = [view frame].size;
                }

                if(size.width > 0 && size.height > 0)
                {
                    return sizefunc(object, size.width, size.height, handler->data);
                }
                return 0;
            }
            case _DW_EVENT_KEY_PRESS:
            {
                int (*keypressfunc)(HWND, char, int, int, void *, char *) = handler->signalfunction;
                int special = 0;
                NSString *nchar = @"";
                if (@available(iOS 13.4, *)) {
                    UIKey *key = event;
                    
                    nchar = [key charactersIgnoringModifiers];
                    special = (int)[key modifierFlags];
                } else {
                    // Probably won't even get here if not 13.4
                }
                unichar vk = [nchar characterAtIndex:0];
                char *utf8 = NULL, ch = '\0';

                /* Handle a valid key */
                if([nchar length] == 1)
                {
                    char *tmp = (char *)[nchar UTF8String];
                    if(tmp && strlen(tmp) == 1)
                    {
                        ch = tmp[0];
                    }
                    utf8 = tmp;
                }

                return keypressfunc(handler->window, ch, (int)vk, special, handler->data, utf8);
            }
            /* Button press and release event */
            case _DW_EVENT_BUTTON_PRESS:
            case _DW_EVENT_BUTTON_RELEASE:
            {
                int (* API buttonfunc)(HWND, int, int, int, void *) = (int (* API)(HWND, int, int, int, void *))handler->signalfunction;
                /* Event will be nil when handling the context menu events...
                 * So button should default to 2 if event is nil
                 */
                int button = event ? 1 : 2;
                CGPoint p = _dw_event_point;

                if(event && [event isKindOfClass:[UIEvent class]])
                {
                    NSSet *touches = [event allTouches];

                    for(id touch in touches)
                    {
                        if((message == 3 && [touch phase] == UITouchPhaseBegan) ||
                           (message == 4 && [touch phase] == UITouchPhaseEnded))
                        {
                            p = [touch locationInView:[touch view]];
                            
                            if(@available(ios 13.4, *))
                            {
                                if([event buttonMask] & UIEventButtonMaskSecondary)
                                    button = 2;
                            }
                        }
                    }
                }

                return buttonfunc(object, (int)p.x, (int)p.y, button, handler->data);
            }
            /* Motion notify event */
            case _DW_EVENT_MOTION_NOTIFY:
            {
                int (* API motionfunc)(HWND, int, int, int, void *) = (int (* API)(HWND, int, int, int, void *))handler->signalfunction;
                int button = 1;

                if(event && [event isKindOfClass:[UIEvent class]])
                {
                    NSSet *touches = [event allTouches];

                    for(id touch in touches)
                    {
                        if([touch phase] == UITouchPhaseMoved)
                        {
                            CGPoint p = [touch locationInView:[touch view]];

                            return motionfunc(object, (int)p.x, (int)p.y, button, handler->data);
                        }
                    }
                }
                return -1;
            }
            /* Window close event */
            case _DW_EVENT_DELETE:
            {
                int (* API closefunc)(HWND, void *) = (int (* API)(HWND, void *))handler->signalfunction;
                return closefunc(object, handler->data);
            }
            /* Window expose/draw event */
            case _DW_EVENT_EXPOSE:
            {
                DWExpose exp;
                int (* API exposefunc)(HWND, DWExpose *, void *) = (int (* API)(HWND, DWExpose *, void *))handler->signalfunction;
                CGRect rect = [object frame];

                exp.x = rect.origin.x;
                exp.y = rect.origin.y;
                exp.width = rect.size.width;
                exp.height = rect.size.height;
                int result = exposefunc(object, &exp, handler->data);
                return result;
            }
            /* Clicked event for buttons and menu items */
            case _DW_EVENT_CLICKED:
            {
                int (* API clickfunc)(HWND, void *) = (int (* API)(HWND, void *))handler->signalfunction;

                return clickfunc(object, handler->data);
            }
            /* Container class selection event */
            case _DW_EVENT_ITEM_ENTER:
            {
                int (*containerselectfunc)(HWND, char *, void *, void *) = handler->signalfunction;

                return containerselectfunc(handler->window, [event pointerAtIndex:0], handler->data,
                                           [event pointerAtIndex:1]);
            }
            /* Container context menu event */
            case _DW_EVENT_ITEM_CONTEXT:
            {
                int (* API containercontextfunc)(HWND, char *, int, int, void *, void *) = (int (* API)(HWND, char *, int, int, void *, void *))handler->signalfunction;
                char *text = (char *)[event pointerAtIndex:0];
                void *user = [event pointerAtIndex:1];
                int x = DW_POINTER_TO_INT([event pointerAtIndex:2]);
                int y = DW_POINTER_TO_INT([event pointerAtIndex:3]);

                return containercontextfunc(handler->window, text, x, y, handler->data, user);
            }
            /* Generic selection changed event for several classes */
            case _DW_EVENT_LIST_SELECT:
            case _DW_EVENT_VALUE_CHANGED:
            {
                int (* API valuechangedfunc)(HWND, int, void *) = (int (* API)(HWND, int, void *))handler->signalfunction;
                int selected = DW_POINTER_TO_INT(event);

                return valuechangedfunc(handler->window, selected, handler->data);;
            }
            /* Tree class selection event */
            case _DW_EVENT_ITEM_SELECT:
            {
                int (* API treeselectfunc)(HWND, HTREEITEM, char *, void *, void *) = (int (* API)(HWND, HTREEITEM, char *, void *, void *))handler->signalfunction;
                char *text = NULL;
                void *user = NULL;
                id item = nil;

                if([object isKindOfClass:[UITableView class]] && event)
                {
                    if([event isKindOfClass:[NSPointerArray class]])
                    {
                       /* The NSPointerArray count will be 2 for Containers */
                        text = [event pointerAtIndex:0];
                        user = [event pointerAtIndex:1];

                        /* The NSPointerArray count will be 3 for Trees */
                        if([event count] > 2)
                            item = [event pointerAtIndex:2];
                    }
                }

                return treeselectfunc(handler->window, item, text, handler->data, user);
            }
            /* Set Focus event */
            case _DW_EVENT_SET_FOCUS:
            {
                int (* API setfocusfunc)(HWND, void *) = (int (* API)(HWND, void *))handler->signalfunction;

                return setfocusfunc(handler->window, handler->data);
            }
            /* Notebook page change event */
            case _DW_EVENT_SWITCH_PAGE:
            {
                int (* API switchpagefunc)(HWND, unsigned long, void *) = (int (* API)(HWND, unsigned long, void *))handler->signalfunction;
                int pageid = DW_POINTER_TO_INT(event);

                return switchpagefunc(handler->window, pageid, handler->data);
            }
            /* Tree expand event */
            case _DW_EVENT_TREE_EXPAND:
            {
                int (* API treeexpandfunc)(HWND, HTREEITEM, void *) = (int (* API)(HWND, HTREEITEM, void *))handler->signalfunction;

                return treeexpandfunc(handler->window, (HTREEITEM)event, handler->data);
            }
            /* Column click event */
            case _DW_EVENT_COLUMN_CLICK:
            {
                int (* API clickcolumnfunc)(HWND, int, void *) = handler->signalfunction;
                int column_num = DW_POINTER_TO_INT(event);

                return clickcolumnfunc(handler->window, column_num, handler->data);
            }
            /* HTML result event */
            case _DW_EVENT_HTML_RESULT:
            {
                int (* API htmlresultfunc)(HWND, int, char *, void *, void *) = handler->signalfunction;
                void **params = (void **)event;
                NSString *result = params[0];

                return htmlresultfunc(handler->window, [result length] ? DW_ERROR_NONE : DW_ERROR_UNKNOWN, [result length] ? (char *)[result UTF8String] : NULL, params[1], handler->data);
            }
            /* HTML changed event */
            case _DW_EVENT_HTML_CHANGED:
            {
                int (* API htmlchangedfunc)(HWND, int, char *, void *) = handler->signalfunction;
                void **params = (void **)event;
                NSString *uri = params[1];

                return htmlchangedfunc(handler->window, DW_POINTER_TO_INT(params[0]), (char *)[uri UTF8String], handler->data);
            }
        }
    }
    return -1;
}

/* Sub function to handle redraws */
int _dw_event_handler(id object, id event, int message)
{
    int ret = _dw_event_handler1(object, event, message);
    if(ret != -1)
        _dw_redraw(nil, FALSE);
    return ret;
}

/* Subclass for the Timer type */
@interface DWTimerHandler : NSObject { }
-(void)runTimer:(id)sender;
@end

@implementation DWTimerHandler
-(void)runTimer:(id)sender { _dw_event_handler(sender, nil, _DW_EVENT_TIMER); }
@end

@interface DWAppDel : UIResponder <UIApplicationDelegate> { }
-(void)applicationWillTerminate:(UIApplication *)application;
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions;
@end

@implementation DWAppDel
-(void)applicationWillTerminate:(UIApplication *)application
{
    /* On iOS we can't prevent temrination, but send the notificatoin anyway */
    _dw_event_handler(application, nil, _DW_EVENT_DELETE);
}
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions
{
    return true;
}
@end

static UIApplication *DWApp = nil;
static UIFont *DWDefaultFont;
static DWTimerHandler *DWHandler;
static NSMutableArray *_DWDirtyDrawables;
static DWTID DWThread = (DWTID)-1;
static HEV DWMainEvent;

/* Used for doing bitblts from the main thread */
typedef struct _dw_bitbltinfo
{
    id src;
    id dest;
    int xdest;
    int ydest;
    int width;
    int height;
    int xsrc;
    int ysrc;
    int srcwidth;
    int srcheight;
} DWBitBlt;

/* Subclass for a test object type */
@interface DWObject : NSObject
{
    /* A normally hidden window, at the top of the view hierarchy.
     * Since iOS messageboxes and such require a view controller,
     * we show this hidden window when necessary and use it during
     * the creation of alerts and dialog boxes that don't have one.
     */
    UIWindow *hiddenWindow;
}
-(void)uselessThread:(id)sender;
-(void)menuHandler:(id)param;
-(void)doBitBlt:(id)param;
-(void)doFlush:(id)param;
-(UIWindow *)hiddenWindow;
@end

API_AVAILABLE(ios(13.0))
@interface DWMenu : NSObject
{
    UIMenu *menu;
    NSMutableArray *children;
    NSString *title;
    unsigned long tag;
    UIWindow *window;
}
-(id)initWithTag:(unsigned long)newtag;
-(void)setTitle:(NSString *)input;
-(UIMenu *)menu;
-(unsigned long)tag;
-(id)childWithTag:(unsigned long)tag;
-(void)addItem:(id)item;
-(void)removeItem:(id)item;
-(void)dealloc;
@end

API_AVAILABLE(ios(13.0))
@interface DWMenuItem : UIAction
{
    BOOL check, enabled;
    unsigned long tag;
    DWMenu *submenu, *menu;
    void *userdata;
}
-(void)setCheck:(BOOL)input;
-(void)setEnabled:(BOOL)input;
-(void)setTag:(unsigned long)input;
-(void)setSubmenu:(DWMenu *)input;
-(void)setMenu:(DWMenu *)input;
-(DWMenu *)submenu;
-(DWMenu *)menu;
-(BOOL)check;
-(BOOL)enabled;
-(unsigned long)tag;
-(void *)userdata;
-(void)setUserdata:(void *)input;
-(void)dealloc;
@end

/* So basically to implement our event handlers...
 * it looks like we are going to have to subclass
 * basically everything.  Was hoping to add methods
 * to the superclasses but it looks like you can
 * only add methods and no variables, which isn't
 * going to work. -Brian
 */

/* Subclass for a box type */
@interface DWBox : UIView
{
    Box *box;
    void *userdata;
}
-(id)init;
-(void)dealloc;
-(Box *)box;
-(void *)userdata;
-(void)setUserdata:(void *)input;
-(void)keyDown:(UIKey *)key API_AVAILABLE(ios(13.4));
@end

@implementation DWBox
-(id)init
{
    self = [super init];

    if (self)
    {
        box = calloc(1, sizeof(Box));
        box->type = DW_VERT;
        box->vsize = box->hsize = _DW_SIZE_EXPAND;
        box->width = box->height = 1;
    }
    return self;
}
-(void)dealloc
{
    UserData *root = userdata;
    if(box->items)
        free(box->items);
    free(box);
    _dw_remove_userdata(&root, NULL, TRUE);
    dw_signal_disconnect_by_window(self);
    for(id object in [self subviews])
    {
        NSUInteger rc = [object retainCount];
        [object removeFromSuperview];
        /* TODO: Fix the cause of this...
         * DWButtons have a lower retain count than the other widgets...
         * so this release causes a crash. Figure out why it is lower...
         * and make the others that way too.
         */
        if(rc > 2)
            [object release];
    }
    [super dealloc];
}
-(Box *)box { return box; }
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)keyDown:(UIKey *)key  API_AVAILABLE(ios(13.4)){ _dw_event_handler(self, key, _DW_EVENT_KEY_PRESS); }
@end

@interface DWWindow : UIWindow
{
    DWMenu *windowmenu, *popupmenu;
    int shown;
    void *userdata;
}
-(void)sendEvent:(UIEvent *)theEvent;
-(void)keyDown:(UIKey *)key API_AVAILABLE(ios(13.4));
-(int)shown;
-(void)setShown:(int)val;
-(void)layoutSubviews;
-(void)setMenu:(DWMenu *)input;
-(void)setPopupMenu:(DWMenu *)input;
-(DWMenu *)menu;
-(DWMenu *)popupMenu;
-(void)updateMenu;
-(void *)userdata;
-(void)setUserdata:(void *)input;
@end

@implementation DWWindow
-(void)sendEvent:(UIEvent *)theEvent
{
   int rcode = -1;
   if([theEvent type] == UIEventTypePresses)
   {
      rcode = _dw_event_handler(self, theEvent, _DW_EVENT_KEY_PRESS);
   }
   if ( rcode != TRUE )
      [super sendEvent:theEvent];
}
-(void)keyDown:(UIKey *)key { }
-(int)shown { return shown; }
-(void)setShown:(int)val { shown = val; }
-(void)layoutSubviews { }
-(void)setMenu:(DWMenu *)input { [windowmenu release]; windowmenu = input; [windowmenu retain]; }
-(void)setPopupMenu:(DWMenu *)input { [popupmenu release]; popupmenu = input; [popupmenu retain]; }
-(void)updateMenu
{
    if(windowmenu)
    {
        NSArray *array = [[[self rootViewController] view] subviews];
        UINavigationBar *nav = nil;

        for(id obj in array)
        {
            if([obj isMemberOfClass:[UINavigationBar class]])
                nav = obj;
        }
        if(nav)
        {
            UINavigationItem *item = [[nav items] firstObject];

            if (@available(iOS 14.0, *)) {
                if(item && item.rightBarButtonItem && item.rightBarButtonItem.menu)
                    [item.rightBarButtonItem setMenu:[windowmenu menu]];
            }
        }
    }
}
-(DWMenu *)menu { return windowmenu; }
-(DWMenu *)popupMenu { return popupmenu; }
-(void)closeWindow:(id)sender
{
    if(_dw_event_handler(self, nil, _DW_EVENT_DELETE) < 1)
        dw_window_destroy(self);
}
-(void)dealloc
{
    UserData *root = userdata;
    _dw_remove_userdata(&root, NULL, TRUE);
    if(windowmenu)
        [windowmenu release];
    if(popupmenu)
        [popupmenu release];
    dw_signal_disconnect_by_window(self);
    [super dealloc];
}
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
@end

@interface DWImage : NSObject
{
    UIImage *image;
    CGContextRef cgcontext;
}
-(id)initWithSize:(CGSize)size;
-(id)initWithCGImage:(CGImageRef)cgimage;
-(id)initWithUIImage:(UIImage *)newimage;
-(void)setImage:(UIImage *)input;
-(UIImage *)image;
-(CGContextRef)cgcontext;
-(void)dealloc;
@end

/* Subclass for a render area type */
@interface DWRender : UIView <UIContextMenuInteractionDelegate>
{
    void *userdata;
    UIFont *font;
    CGSize size;
    DWImage *cachedImage;
}
-(void *)userdata;
-(void)setUserdata:(void *)input;
-(void)setFont:(UIFont *)input;
-(UIFont *)font;
-(void)setSize:(CGSize)input;
-(CGSize)size;
-(DWImage *)cachedImage;
-(void)keyDown:(UIKey *)key API_AVAILABLE(ios(13.4));
-(UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction configurationForMenuAtLocation:(CGPoint)location;
@end

@implementation DWRender
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)setFont:(UIFont *)input { [font release]; font = input; [font retain]; }
-(UIFont *)font { return font; }
-(void)setSize:(CGSize)input {
    size = input;
    if(cachedImage)
    {
        DWImage *oldimage = cachedImage;
        UIImage *newimage;
        UIGraphicsBeginImageContext(self.frame.size);
        [[self layer] renderInContext:UIGraphicsGetCurrentContext()];
        newimage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        cachedImage = [[DWImage alloc] initWithUIImage:newimage];
        [cachedImage retain];
        [oldimage release];
    }
}
-(CGSize)size { return size; }
-(DWImage *)cachedImage {
    if(!cachedImage)
    {
        UIImage *newimage;
        UIGraphicsBeginImageContext(self.frame.size);
        [[self layer] renderInContext:UIGraphicsGetCurrentContext()];
        newimage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        cachedImage = [[DWImage alloc] initWithUIImage:newimage];
        [cachedImage retain];
    }
    /* Mark this render dirty if something is requesting it to draw */
    if(![_DWDirtyDrawables containsObject:self])
        [_DWDirtyDrawables addObject:self];
    return cachedImage;
}
-(void)drawRect:(CGRect)rect {
    _dw_event_handler(self, nil, _DW_EVENT_EXPOSE);
    if(cachedImage)
    {
        [[cachedImage image] drawInRect:self.bounds];
        [_DWDirtyDrawables removeObject:self];
        [self setNeedsDisplay];
    }
}
-(void)keyDown:(UIKey *)key  API_AVAILABLE(ios(13.4)){ _dw_event_handler(self, key, _DW_EVENT_KEY_PRESS); }
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    _dw_event_handler(self, event, _DW_EVENT_BUTTON_PRESS);
    [super touchesBegan:touches withEvent:event];
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    _dw_event_handler(self, event, _DW_EVENT_BUTTON_RELEASE);
    [super touchesEnded:touches withEvent:event];
}
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    _dw_event_handler(self, event, _DW_EVENT_MOTION_NOTIFY);
    [super touchesMoved:touches withEvent:event];
}
-(UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction configurationForMenuAtLocation:(CGPoint)location
{
    DWWindow *window = (DWWindow *)[self window];
    UIContextMenuConfiguration *config = nil;

    _dw_event_point = location;
    _dw_event_handler(self, nil, _DW_EVENT_BUTTON_PRESS);

    if(window && [window popupMenu])
    {
        __block UIMenu *popupmenu = [[[window popupMenu] menu] retain];
        config = [UIContextMenuConfiguration configurationWithIdentifier:nil
                                                         previewProvider:nil
                                                          actionProvider:^(NSArray* suggestedAction){return popupmenu;}];
        [window setPopupMenu:nil];
    }
    return config;
}
-(void)dealloc {
    UserData *root = userdata;
    _dw_remove_userdata(&root, NULL, TRUE);
    [font release];
    dw_signal_disconnect_by_window(self);
    [cachedImage release];
    [_DWDirtyDrawables removeObject:self];
    [super dealloc];
}
-(BOOL)acceptsFirstResponder { return YES; }
@end

/* Subclass page renderer to implement page by page rendering */
@interface DWPrintPageRenderer : UIPrintPageRenderer
{
    int (*drawfunc)(HPRINT, HPIXMAP, int, void *);
    void *drawdata;
    unsigned long flags;
    unsigned int pages;
    HPRINT *print;
}
-(void)setDrawFunc:(void *)input;
-(void)setDrawData:(void *)input;
-(void *)drawData;
-(void)setFlags:(unsigned long)input;
-(unsigned long)flags;
-(void)setPages:(unsigned int)input;
-(void)setPrint:(HPRINT)input;
@end

@implementation DWPrintPageRenderer
-(void)setDrawFunc:(void *)input { drawfunc = input; }
-(void)setDrawData:(void *)input { drawdata = input; }
-(void *)drawData { return drawdata; }
-(void)setFlags:(unsigned long)input { flags = input; }
-(unsigned long)flags { return flags; }
-(void)setPages:(unsigned int)input { pages = input; }
-(void)setPrint:(HPRINT)input { print = input; }
-(void)drawContentForPageAtIndex:(NSInteger)pageIndex inRect:(CGRect)contentRect
{
    HPIXMAP pm = dw_pixmap_new(nil, contentRect.size.width, contentRect.size.height, 32);
    DWImage *image = pm->image;
    CGBlendMode op = kCGBlendModeNormal;

    drawfunc(print, pm, (int)pageIndex, drawdata);
    [[image image] drawInRect:contentRect blendMode:op alpha:1.0];
    dw_pixmap_destroy(pm);
}
-(NSInteger)numberOfPages { return (NSInteger)pages; }
@end

@interface DWFontPickerDelegate : UIResponder <UIFontPickerViewControllerDelegate>
{
    DWDialog *dialog;
}
-(void)fontPickerViewControllerDidPickFont:(UIFontPickerViewController *)viewController;
-(void)fontPickerViewControllerDidCancel:(UIFontPickerViewController *)viewController;
-(void)setDialog:(DWDialog *)newdialog;
@end

@implementation DWFontPickerDelegate
-(void)fontPickerViewControllerDidPickFont:(UIFontPickerViewController *)viewController
{
    if(viewController.selectedFontDescriptor)
    {
        UIFont *font = [UIFont fontWithDescriptor:viewController.selectedFontDescriptor size:9];
        dw_dialog_dismiss(dialog, font);
    }
    else
        dw_dialog_dismiss(dialog, NULL);
}
-(void)fontPickerViewControllerDidCancel:(UIFontPickerViewController *)viewController
{
    dw_dialog_dismiss(dialog, NULL);
}
-(void)setDialog:(DWDialog *)newdialog { dialog = newdialog; }
@end

@interface DWColorPickerDelegate : UIResponder <UIColorPickerViewControllerDelegate>
{
    DWDialog *dialog;
}
-(void)colorPickerViewControllerDidSelectColor:(UIColorPickerViewController *)viewController API_AVAILABLE(ios(14.0));
-(void)colorPickerViewControllerDidFinish:(UIColorPickerViewController *)viewController API_AVAILABLE(ios(14.0));
-(void)setDialog:(DWDialog *)newdialog;
@end

@implementation DWColorPickerDelegate
-(void)colorPickerViewControllerDidSelectColor:(UIColorPickerViewController *)viewController API_AVAILABLE(ios(14.0))
{
    if([viewController selectedColor])
    {
        CGFloat red, green, blue;
        [[viewController selectedColor] getRed:&red green:&green blue:&blue alpha:NULL];
        dw_dialog_dismiss(dialog, DW_UINT_TO_POINTER(DW_RGB((int)(red * 255), (int)(green *255), (int)(blue *255))));
    }
    else
        dw_dialog_dismiss(dialog, NULL);
    [viewController dismissViewControllerAnimated:YES completion:nil];
}
-(void)colorPickerViewControllerDidFinish:(UIColorPickerViewController *)viewController API_AVAILABLE(ios(14.0))
{
    dw_dialog_dismiss(dialog, NULL);
    [viewController dismissViewControllerAnimated:YES completion:nil];
}
-(void)setDialog:(DWDialog *)newdialog { dialog = newdialog; }
@end

@interface DWDocumentPickerDelegate : UIResponder <UIDocumentPickerDelegate>
{
    DWDialog *dialog;
}
-(void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls;
-(void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller;
-(void)setDialog:(DWDialog *)newdialog;
@end

#define _DW_URL_LIMIT 10
static NSMutableArray *_dw_URLs = nil;

@implementation DWDocumentPickerDelegate
-(void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
{
    NSURL *url = [urls firstObject];
    char *file = NULL;

    if(url)
    {
        const char *tmp = [url fileSystemRepresentation];

        if(tmp && [url startAccessingSecurityScopedResource])
        {
            /* We need to allow access to the returned URLs on iOS 13
             * but we have no way of knowing when it is no longer needed...
             * since the C API we are connected to does not have that information.
             * So maintain a list of URLs, and when the list exceeds the limit...
             * revoke access to the oldest URL.
             */
            if(!_dw_URLs)
                _dw_URLs = [[[NSMutableArray alloc] init] retain];
            if(_dw_URLs)
            {
                if([_dw_URLs count] >= _DW_URL_LIMIT)
                {
                    NSURL *oldurl = [_dw_URLs firstObject];
                    [oldurl stopAccessingSecurityScopedResource];
                    [_dw_URLs removeObject:oldurl];
                }
                [_dw_URLs addObject:url];
            }
            file = strdup(tmp);
        }
    }
    dw_dialog_dismiss(dialog, file);
}
-(void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller
{
    dw_dialog_dismiss(dialog, NULL);
}
-(void)setDialog:(DWDialog *)newdialog { dialog = newdialog; }
@end

@implementation DWObject
-(id)init
{
    self = [super init];
    /* This previously had the code in delayedIinit: */
    return self;
}
-(void)delayedInit:(id)param
{
    /* When DWObject is initialized, UIApplicationMain() has not yet been called...
     * So the created objects can't interact with the user interface... therefore
     * we wait until UIApplicationMain() has been called and run this then.
     */
    hiddenWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    [hiddenWindow setWindowLevel:UIWindowLevelAlert+1];
    [hiddenWindow setHidden:YES];
    [hiddenWindow setRootViewController:[UIViewController new]];
    /* Handle setting the dark mode state now that DWObj is valid */
    if(_dw_dark_mode_state != DW_FEATURE_UNSUPPORTED)
        dw_feature_set(DW_FEATURE_DARK_MODE, _dw_dark_mode_state);
}
-(UIWindow *)hiddenWindow { return hiddenWindow; };
-(void)uselessThread:(id)sender { /* Thread only to initialize threading */ }
-(void)menuHandler:(id)param
{
    _dw_event_handler(param, nil, _DW_EVENT_CLICKED);
}
-(void)callBack:(NSPointerArray *)params
{
    void (*mycallback)(NSPointerArray *) = [params pointerAtIndex:0];
    if(mycallback)
        mycallback(params);
}
-(void)colorPicker:(NSMutableArray *)params
{
    if (@available(iOS 14.0, *))
    {
        DWDialog *dialog = dw_dialog_new(NULL);
        UIColorPickerViewController *picker = [[UIColorPickerViewController alloc] init];
        DWColorPickerDelegate *delegate = [[DWColorPickerDelegate alloc] init];
        unsigned long color = [[params firstObject] unsignedLongValue];

        /* Setup our picker */
        [picker setSupportsAlpha:NO];
        /* Unhide our hidden window and make it key */
        [hiddenWindow setHidden:NO];
        [hiddenWindow makeKeyAndVisible];
        [delegate setDialog:dialog];
        [picker setDelegate:delegate];
        [picker setSelectedColor:[UIColor colorWithRed: DW_RED_VALUE(color)/255.0 green: DW_GREEN_VALUE(color)/255.0 blue: DW_BLUE_VALUE(color)/255.0 alpha: 1]];
        /* Wait for them to pick a color */
        [[hiddenWindow rootViewController] presentViewController:picker animated:YES completion:nil];
        [params addObject:[NSNumber numberWithUnsignedLong:DW_POINTER_TO_UINT(dw_dialog_wait(dialog))]];
        /* Once the dialog is gone we can rehide our window */
        [hiddenWindow resignKeyWindow];
        [hiddenWindow setHidden:YES];
        [picker release];
        [delegate release];
    } else {
        // Fallback on earlier versions
    };
}
-(void)fontPicker:(NSPointerArray *)params
{
    DWDialog *dialog = dw_dialog_new(NULL);
    UIFontPickerViewController *picker = [[UIFontPickerViewController alloc] init];
    DWFontPickerDelegate *delegate = [[DWFontPickerDelegate alloc] init];
    UIFont *font;

    /* Unhide our hidden window and make it key */
    [hiddenWindow setHidden:NO];
    [hiddenWindow makeKeyAndVisible];
    [delegate setDialog:dialog];
    [picker setDelegate:delegate];
    /* Wait for them to pick a font */
    [[hiddenWindow rootViewController] presentViewController:picker animated:YES completion:nil];
    font = dw_dialog_wait(dialog);
    /* Once the dialog is gone we can rehide our window */
    [hiddenWindow resignKeyWindow];
    [hiddenWindow setHidden:YES];

    if(font)
    {
        NSString *fontname = [font fontName];
        NSString *output = [NSString stringWithFormat:@"%d.%s", (int)[font pointSize], [fontname UTF8String]];
        [params addPointer:strdup([output UTF8String])];
    }
    else
        [params addPointer:NULL];
    [picker release];
    [delegate release];
}
-(void)filePicker:(NSPointerArray *)params
{
    DWDialog *dialog = dw_dialog_new(NULL);
    UIDocumentPickerViewController *picker;
    DWDocumentPickerDelegate *delegate = [[DWDocumentPickerDelegate alloc] init];
    char *defpath = [params pointerAtIndex:0];
    char *ext = [params pointerAtIndex:1];
    int flags = DW_POINTER_TO_INT([params pointerAtIndex:2]);
    char *file = NULL;
    NSArray *UTIs;

    if (@available(iOS 14, *)) {
        UTType *extuti = ext ? [UTType typeWithFilenameExtension:[NSString stringWithUTF8String:ext]] : nil;
        
        /* Try to generate a UTI for our passed extension */
        if(flags & DW_DIRECTORY_OPEN)
            UTIs = @[UTTypeFolder];
        else
        {
            if(extuti)
                UTIs = [NSArray arrayWithObjects:extuti, UTTypeText, nil];
            else
                UTIs = @[UTTypeText];
        }
        picker = [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:UTIs
                                                                             asCopy:(flags & DW_FILE_SAVE ? YES: NO)];
    } else {
        UIDocumentPickerMode mode = UIDocumentPickerModeOpen;
        
        /* Try to generate a UTI for our passed extension */
        if(flags & DW_DIRECTORY_OPEN)
            UTIs = @[@"public.directory"];
        else
        {
            if(ext)
                UTIs = [NSArray arrayWithObjects:[NSString stringWithFormat:@"public.%s", ext], @"public.text", nil];
            else
                UTIs = @[@"public.text"];
        }
        
        /* Setup the picker */
        if(flags & DW_FILE_SAVE)
            mode = UIDocumentPickerModeExportToService;

        picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:UTIs inMode:mode];
    }
    [picker setAllowsMultipleSelection:NO];
    [picker setShouldShowFileExtensions:YES];
    if(defpath)
        [picker setDirectoryURL:[NSURL URLWithString:[NSString stringWithUTF8String:defpath]]];
    /* Unhide our hidden window and make it key */
    [hiddenWindow setHidden:NO];
    [hiddenWindow makeKeyAndVisible];
    [delegate setDialog:dialog];
    [picker setDelegate:delegate];
    /* Wait for them to pick a file */
    [[hiddenWindow rootViewController] presentViewController:picker animated:YES completion:nil];
    file = dw_dialog_wait(dialog);
    /* Once the dialog is gone we can rehide our window */
    [hiddenWindow resignKeyWindow];
    [hiddenWindow setHidden:YES];
    [params addPointer:file];
    [picker release];
    [delegate release];
}
-(void)messageBox:(NSMutableArray *)params
{
    __block DWDialog *dialog = dw_dialog_new(NULL);
    NSInteger iResponse;
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:[params objectAtIndex:0]
                                   message:[params objectAtIndex:1]
                                   preferredStyle:[[params objectAtIndex:2] integerValue]];
    UIAlertAction* action = [UIAlertAction actionWithTitle:[params objectAtIndex:3] style:UIAlertActionStyleDefault
                                                   handler:^(UIAlertAction * action) { dw_dialog_dismiss(dialog, DW_INT_TO_POINTER(1)); }];
    [alert addAction:action];
    if([params count] > 4)
    {
        action = [UIAlertAction actionWithTitle:[params objectAtIndex:4] style:UIAlertActionStyleDefault
                                        handler:^(UIAlertAction * action) { dw_dialog_dismiss(dialog, DW_INT_TO_POINTER(2)); }];
        [alert addAction:action];
    }
    if([params count] > 5)
    {
        action = [UIAlertAction actionWithTitle:[params objectAtIndex:5] style:UIAlertActionStyleDefault
                                        handler:^(UIAlertAction * action) { dw_dialog_dismiss(dialog, DW_INT_TO_POINTER(3)); }];
        [alert addAction:action];
    }

    /* Unhide our hidden window and make it key */
    [hiddenWindow setHidden:NO];
    [hiddenWindow makeKeyAndVisible];
    [[hiddenWindow rootViewController] presentViewController:alert animated:YES completion:nil];
    iResponse = DW_POINTER_TO_INT(dw_dialog_wait(dialog));
    /* Once the dialog is gone we can rehide our window */
    [hiddenWindow resignKeyWindow];
    [hiddenWindow setHidden:YES];
    [params addObject:[NSNumber numberWithInteger:iResponse]];
}
-(void)printDialog:(NSMutableArray *)params
{
    __block DWDialog *dialog = dw_dialog_new(NULL);
    UIPrintInteractionController *pc = [UIPrintInteractionController sharedPrintController];
    UIPrintInfo *pi = [params firstObject];
    DWPrintPageRenderer *renderer = [params objectAtIndex:1];
    /* Not currently using the passed in flags, but when we need them...
     NSNumber *flags = [params objectAtIndex:2];*/
    int result = DW_ERROR_UNKNOWN;

    /* Setup our print dialog */
    [pc setPrintInfo:pi];
    [pc setPrintPageRenderer:renderer];

    /* Present the shared printer dialog */
    [pc presentAnimated:YES completionHandler:^(UIPrintInteractionController * _Nonnull printInteractionController, BOOL completed, NSError * _Nullable error) {
        dw_dialog_dismiss(dialog, DW_INT_TO_POINTER((completed ? DW_ERROR_NONE : DW_ERROR_UNKNOWN)));
    }];
    result = DW_POINTER_TO_INT(dw_dialog_wait(dialog));

    [params addObject:[NSNumber numberWithInteger:result]];
}
-(void)safeCall:(SEL)sel withObject:(id)param
{
    if([self respondsToSelector:sel])
    {
        DWTID curr = pthread_self();

        if(DWThread == (DWTID)-1 || DWThread == curr)
            [self performSelector:sel withObject:param];
        else
            [self performSelectorOnMainThread:sel withObject:param waitUntilDone:YES];
    }
}
-(void)updateMenu:(id)param
{
    for(id obj in _dw_toplevel_windows)
        [obj updateMenu];
}
-(void)doBitBlt:(id)param
{
    NSValue *bi = (NSValue *)param;
    DWBitBlt *bltinfo = (DWBitBlt *)[bi pointerValue];
    id bltdest = bltinfo->dest;
    id bltsrc = bltinfo->src;
    CGContextRef context = _dw_draw_context(bltdest, NO);

    if(context)
        UIGraphicsPushContext(context);

    if(bltdest && [bltsrc isMemberOfClass:[DWImage class]])
    {
        DWImage *rep = bltsrc;
        UIImage *image = [rep image];
        CGBlendMode op = kCGBlendModeNormal;

        if(bltinfo->srcwidth != -1)
        {
            [image drawInRect:CGRectMake(bltinfo->xdest, bltinfo->ydest, bltinfo->width, bltinfo->height)
                     /*fromRect:CGRectMake(bltinfo->xsrc, bltinfo->ysrc, bltinfo->srcwidth, bltinfo->srcheight)*/
                    blendMode:op alpha:1.0];
        }
        else
        {
            [image drawAtPoint:CGPointMake(bltinfo->xdest, bltinfo->ydest)
                      /*fromRect:CGRectMake(bltinfo->xsrc, bltinfo->ysrc, bltinfo->width, bltinfo->height)*/
                     blendMode:op alpha:1.0];
        }
    }
    if(context)
        UIGraphicsPopContext();
    free(bltinfo);
}
-(void)doFlush:(id)param
{
    NSEnumerator *enumerator = [_DWDirtyDrawables objectEnumerator];
    DWRender *rend;

    while(rend = [enumerator nextObject])
        [rend setNeedsDisplay];
    [_DWDirtyDrawables removeAllObjects];
}
-(void)doWindowFunc:(id)param
{
    NSValue *v = (NSValue *)param;
    void **params = (void **)[v pointerValue];
    void (* windowfunc)(void *);

    if(params)
    {
        windowfunc = params[0];
        if(windowfunc)
        {
            windowfunc(params[1]);
        }
    }
}
-(void)getUserInterfaceStyle:(id)param
{
    NSMutableArray *array = param;
    UIUserInterfaceStyle overridestyle = [hiddenWindow overrideUserInterfaceStyle];
    int retval;
    
    switch(overridestyle)
    {
        case UIUserInterfaceStyleLight:
            retval = DW_DARK_MODE_DISABLED;
            break;
        case UIUserInterfaceStyleDark:
            retval = DW_DARK_MODE_FORCED;
            break;
        default:  /* UIUserInterfaceStyleUnspecified */
        {
            UIUserInterfaceStyle style = [[[hiddenWindow rootViewController] traitCollection] userInterfaceStyle];

            switch(style)
            {
                case UIUserInterfaceStyleLight:
                    retval = DW_DARK_MODE_BASIC;
                    break;
                case UIUserInterfaceStyleDark:
                    retval = DW_DARK_MODE_FULL;
                    break;
                default: /* UIUserInterfaceStyleUnspecified */
                    retval = DW_FEATURE_UNSUPPORTED;
                    break;
            }
        }
    }
    [array addObject:[NSNumber numberWithInt:retval]];
}
-(void)setUserInterfaceStyle:(id)param
{
    NSNumber *number = param;
    UIUserInterfaceStyle style = [number intValue];
    
    [hiddenWindow setOverrideUserInterfaceStyle:style];
}
@end

DWObject *DWObj;

/* Returns TRUE if iOS 12+ is in Dark Mode */
BOOL _dw_is_dark(void)
{
    if([[[[DWObj hiddenWindow] rootViewController] traitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark)
        return YES;
    return NO;
}

@interface DWWebView : WKWebView <WKNavigationDelegate>
{
    void *userdata;
}
-(void *)userdata;
-(void)setUserdata:(void *)input;
-(void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation;
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;
-(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation;
-(void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation;
@end

@implementation DWWebView : WKWebView
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
{
    void *params[2] = { DW_INT_TO_POINTER(DW_HTML_CHANGE_STARTED), [[self URL] absoluteString] };
    _dw_event_handler(self, (id)params, _DW_EVENT_HTML_CHANGED);
}
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    void *params[2] = { DW_INT_TO_POINTER(DW_HTML_CHANGE_COMPLETE), [[self URL] absoluteString] };
    _dw_event_handler(self, (id)params, _DW_EVENT_HTML_CHANGED);
}
-(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
{
    void *params[2] = { DW_INT_TO_POINTER(DW_HTML_CHANGE_LOADING), [[self URL] absoluteString] };
    _dw_event_handler(self, (id)params, _DW_EVENT_HTML_CHANGED);
}
-(void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation
{
    void *params[2] = { DW_INT_TO_POINTER(DW_HTML_CHANGE_REDIRECT), [[self URL] absoluteString] };
    _dw_event_handler(self, (id)params, _DW_EVENT_HTML_CHANGED);
}
-(void)dealloc { UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE); dw_signal_disconnect_by_window(self); [super dealloc]; }
@end

/* Subclass for a top-level window */
@interface DWView : DWBox /* <UIWindowDelegate> */
{
    CGSize oldsize;
}
-(void)windowDidBecomeMain:(id)sender;
-(void)menuHandler:(id)sender;
-(UIResponder *)nextResponder;
@end

@implementation DWView
-(void)willMoveToSuperview:(UIView *)newSuperview
{
    if(newSuperview)
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeMain:) name:UIWindowDidBecomeKeyNotification object:[newSuperview window]];
}
-(void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    dw_signal_disconnect_by_window(self);
    [super dealloc];
}
-(void)windowResized:(CGSize)size;
{
    if(oldsize.width != size.width || oldsize.height != size.height)
    {
        _dw_do_resize(box, size.width, size.height);
        _dw_event_handler([self window], nil, _DW_EVENT_CONFIGURE);
        oldsize.width = size.width;
        oldsize.height = size.height;
        _dw_handle_resize_events(box);
    }
}
-(void)showWindow
{
    CGSize size = [self frame].size;

    if(oldsize.width == size.width && oldsize.height == size.height)
    {
        _dw_do_resize(box, size.width, size.height);
        _dw_handle_resize_events(box);
    }

}
-(void)windowDidBecomeMain:(id)sender
{
    _dw_event_handler([self window], nil, _DW_EVENT_SET_FOCUS);
}
-(void)menuHandler:(id)sender
{
    [DWObj menuHandler:sender];
}
-(UIResponder *)nextResponder { return [[self window] rootViewController]; }
@end

@interface DWViewController : UIViewController
-(void)viewWillLayoutSubviews;
-(UIResponder *)nextResponder;
@end


@implementation DWViewController : UIViewController {}
-(void)viewWillLayoutSubviews
{
    DWWindow *window = (DWWindow *)[[self view] window];
    NSArray *array = [[self view] subviews];
    CGRect frame = [window frame];
    DWView *view = nil;
    UINavigationBar *nav = nil;
    NSInteger sbheight = [[[window windowScene] statusBarManager] statusBarFrame].size.height;

    for(id obj in array)
    {
        if([obj isMemberOfClass:[DWView class]])
            view = obj;
        else if([obj isMemberOfClass:[UINavigationBar class]])
            nav = obj;
    }
    /* Adjust the frame to account for the status bar and navigation bar if it exists */
    if(nav)
    {
        CGRect navrect = [nav frame];
        
        navrect.size.width = frame.size.width;
        navrect.origin.x = 0;
        navrect.origin.y = sbheight;
        sbheight += navrect.size.height;
        [nav setFrame:navrect];
        
        if (@available(iOS 14.0, *)) {
            DWMenu *windowmenu = [window menu];
            UINavigationItem *item = [[nav items] firstObject];

            if(windowmenu && !item.rightBarButtonItem)
            {
                UIMenu *menu = [windowmenu menu];
                UIBarButtonItem *options = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"list.bullet"]
                                                                             menu:menu];
                item.rightBarButtonItem = options;
            }
        } else {
            // Fallback on earlier versions
        }
    }
    frame.size.height -= sbheight;
    /* Account for the special area on iPhone X and iPad Pro
     * https://blog.maxrudberg.com/post/165590234593/ui-design-for-iphone-x-bottom-elements
     */
    frame.size.height -= 24;
    frame.origin.y += sbheight;
    [view setFrame:frame];
    [view windowResized:frame.size];
}
-(UIResponder *)nextResponder { return [[self viewIfLoaded] window]; }
@end

#define _DW_BUTTON_TYPE_NORMAL 0
#define _DW_BUTTON_TYPE_CHECK  1
#define _DW_BUTTON_TYPE_RADIO  2
#define _DW_BUTTON_TYPE_TREE   3

/* Subclass for a button type */
@interface DWButton : UIButton
{
    void *userdata;
    DWBox *parent;
    int type, checkstate;
}
@property(nonatomic, strong) void(^didCheckedChanged)(BOOL checked);
-(void *)userdata;
-(void)setUserdata:(void *)input;
-(void)buttonClicked:(id)sender;
-(void)setParent:(DWBox *)input;
-(DWBox *)parent;
-(int)type;
-(void)setType:(int)input;
-(int)checkState;
-(void)setCheckState:(int)input;
@end

@implementation DWButton
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)buttonClicked:(id)sender
{
    /* Toggle the button */
    if(type == _DW_BUTTON_TYPE_CHECK || type == _DW_BUTTON_TYPE_TREE)
        [self setCheckState:(checkstate ? FALSE : TRUE)];
    else if(type == _DW_BUTTON_TYPE_RADIO)
        [self setCheckState:TRUE];

    _dw_event_handler(self, nil, _DW_EVENT_CLICKED);

    /* If it is a radio button, uncheck all the other radio buttons in the box */
    if(type == _DW_BUTTON_TYPE_RADIO)
    {
        DWBox *viewbox = [self parent];
        Box *thisbox = [viewbox box];
        int z;

        for(z=0;z<thisbox->count;z++)
        {
            if(thisbox->items[z].type != _DW_TYPE_BOX)
            {
                id object = thisbox->items[z].hwnd;

                if([object isMemberOfClass:[DWButton class]])
                {
                    DWButton *button = object;

                    if(button != self && [button type] == _DW_BUTTON_TYPE_RADIO)
                    {
                        [button setCheckState:FALSE];
                    }
                }
            }
        }
    }
    if(_didCheckedChanged)
        _didCheckedChanged(checkstate);
}
-(void)setParent:(DWBox *)input { parent = input; }
-(DWBox *)parent { return parent; }
-(int)type { return type; }
-(void)setType:(int)input
{
    type = input;
    if(type == _DW_BUTTON_TYPE_NORMAL)
        [self setContentHorizontalAlignment:UIControlContentHorizontalAlignmentCenter];
    else
        [self setContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft];
    [self updateImage];
}
-(void)updateImage
{
    NSString *imagename = nil;

    switch(type)
    {
        case _DW_BUTTON_TYPE_CHECK:
        {

            if(checkstate)
                imagename = @"checkmark.square";
            else
                imagename = @"square";
        }
        break;
        case _DW_BUTTON_TYPE_RADIO:
        {
            if(checkstate)
                imagename = @"largecircle.fill.circle";
            else
                imagename = @"circle";
        }
        break;
        case _DW_BUTTON_TYPE_TREE:
        {
            if(checkstate)
                imagename = @"chevron.down";
            else
                imagename = @"chevron.forward";
        }
        break;
    }
    if(imagename)
    {
        UIImage *image = [UIImage systemImageNamed:imagename];
        CGSize size = [image size];
        [self setImage:image forState:UIControlStateNormal];
        [self setTitleEdgeInsets:UIEdgeInsetsMake(0,size.width,0,0)];
    }
}
-(int)checkState { return checkstate; }
-(void)setCheckState:(int)input { checkstate = input; [self updateImage]; }
-(void)dealloc { UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE); dw_signal_disconnect_by_window(self); [super dealloc]; }
@end

/* Subclass for a progress type */
@interface DWPercent : UIProgressView
{
    void *userdata;
}
-(void *)userdata;
-(void)setUserdata:(void *)input;
@end

@implementation DWPercent
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)dealloc { UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE); dw_signal_disconnect_by_window(self); [super dealloc]; }
@end

/* Subclass for a menu item type */
@implementation DWMenuItem
-(void)setCheck:(BOOL)input { check = input; }
-(void)setEnabled:(BOOL)input { enabled = input; [self setAttributes:(enabled ? 0 : UIMenuElementAttributesDisabled)]; }
-(void)setSubmenu:(DWMenu *)input { submenu = input; }
-(void)setMenu:(DWMenu *)input { menu = input; }
-(void)setTag:(unsigned long)input { tag = input; }
-(BOOL)check { return check; }
-(BOOL)enabled { return enabled; }
-(DWMenu *)submenu { return submenu; }
-(DWMenu *)menu { return menu; }
-(unsigned long)tag { return tag; }
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)dealloc { UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE); dw_signal_disconnect_by_window(self); [super dealloc]; }
@end
/*
 * Encapsulate immutable objects in our own containers,
 * so we can recreate the immutable subobjects as needed.
 * Currently in this category: DWMenu and DWImage
 */
@implementation DWMenu
-(id)initWithTag:(unsigned long)newtag
{
    self = [super init];
    if(self)
    {
        children = [[NSMutableArray alloc] init];
        tag = newtag;
    }
    return self;
}
-(void)setTitle:(NSString *)input { title = input; [title retain]; }
-(UIMenu *)menu
{
    /* Create or recreate the UIMenu recursively */
    UIMenu *oldmenu = menu;
    NSMutableArray *menuchildren = [[NSMutableArray alloc] init];
    NSMutableArray *section = menuchildren;

    for(id child in children)
    {
        if([child isMemberOfClass:[DWMenuItem class]])
        {
            DWMenuItem *menuitem = child;
            DWMenu *submenu = [menuitem submenu];

            if(submenu)
                [section addObject:[submenu menu]];
            else
                [section addObject:child];
        }
        /* NSNull entry tells us to make a new section...
         * we do this by making a new UIMenu inline.
         */
        else if([child isMemberOfClass:[NSNull class]])
        {
            if(section != menuchildren)
            {
                UIMenu *sectionmenu = [UIMenu menuWithTitle:@""
                                                      image:nil
                                                 identifier:nil
                                                    options:UIMenuOptionsDisplayInline
                                                   children:section];
                [menuchildren addObject:sectionmenu];
            }
            section = [[NSMutableArray alloc] init];
        }
    }
    if(section != menuchildren)
    {
        UIMenu *sectionmenu = [UIMenu menuWithTitle:@""
                                              image:nil
                                         identifier:nil
                                            options:UIMenuOptionsDisplayInline
                                           children:section];
        [menuchildren addObject:sectionmenu];
    }
    if(title)
    {
        menu = [UIMenu menuWithTitle:title image:[UIImage systemImageNamed:@"ellipsis"] identifier:nil
                             options:0  children:menuchildren];
    }
    else
    {
        if(@available(iOS 14.0, *))
            menu = [UIMenu menuWithChildren:menuchildren];
        else
            menu = [UIMenu menuWithTitle:@"" children:menuchildren];
    }
    [menu retain];
    [menuchildren release];
    if(oldmenu)
        [oldmenu release];
    return menu;
}
-(void)addItem:(id)item
{
    [children addObject:item];
}
-(void)removeItem:(id)item
{
    [children removeObject:item];
}
-(unsigned long)tag { return tag; }
-(id)childWithTag:(unsigned long)tag
{
    if(tag > 0)
    {
        for(id child in children)
        {
            if([child isMemberOfClass:[DWMenuItem class]] && [child tag] == tag)
                return child;
        }
    }
    return nil;
}
-(void)dealloc
{
    if(menu)
        [menu release];
    [super dealloc];
}
@end

@implementation DWImage
-(id)initWithSize:(CGSize)size
{
    self = [super init];
    if(self)
    {
        CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
        CGContextRef cgcontext = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0, rgb, kCGImageAlphaPremultipliedFirst);
        CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, size.height);
        CGContextConcatCTM(cgcontext, flipVertical);
        CGImageRef cgimage = CGBitmapContextCreateImage(cgcontext);
        image = [UIImage imageWithCGImage:cgimage];
        CGContextRelease(cgcontext);
        [image retain];
    }
    return self;
}
-(id)initWithCGImage:(CGImageRef)cgimage
{
    self = [super init];
    if(self)
    {
        image = [UIImage imageWithCGImage:cgimage];
        [image retain];
    }
    return self;
}
-(id)initWithUIImage:(UIImage *)newimage
{
    self = [super init];
    if(self)
    {
        image = newimage;
        [image retain];
    }
    return self;
}
-(void)setImage:(UIImage *)input
{
    UIImage *oldimage = image;
    image = input;
    [image retain];
    [oldimage release];
}
-(UIImage *)image
{
    /* If our CGContext has been modified... */
    if(cgcontext)
    {
        UIImage *oldimage = image;
        CGImageRef cgimage;

        /* Create a new UIImage from the CGContext via CGImage */
        cgimage = CGBitmapContextCreateImage(cgcontext);
        image = [UIImage imageWithCGImage:cgimage];
        CGContextRelease(cgcontext);
        cgcontext = nil;
        [image retain];
        [oldimage release];
        CGImageRelease(cgimage);
    }
    return image;
}
-(CGContextRef)cgcontext
{
    /* If we don't have an active context, create a bitmap
     * context and copy the image from our UIImage.
     */
    if(!cgcontext)
    {
        CGSize size = [image size];
        CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();

        cgcontext = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0, rgb, kCGImageAlphaPremultipliedFirst);
        CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, size.height);
        CGContextConcatCTM(cgcontext, flipVertical);
        CGContextDrawImage(cgcontext, CGRectMake(0,0,size.width,size.height), [image CGImage]);
    }
    return cgcontext;
}
-(void)dealloc { if(cgcontext) CGContextRelease(cgcontext); if(image) [image release]; [super dealloc]; }
@end

/* Subclass for a scrollbox type */
@interface DWScrollBox : UIScrollView
{
    void *userdata;
    id box;
}
-(void *)userdata;
-(void)setUserdata:(void *)input;
-(void)setBox:(void *)input;
-(id)box;
@end

@implementation DWScrollBox
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)setBox:(void *)input { box = input; }
-(id)box { return box; }
-(void)dealloc { UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE); dw_signal_disconnect_by_window(self); [box removeFromSuperview]; [box release]; [super dealloc]; }
@end

/* Subclass for a entryfield type */
@interface DWEntryField : UITextField <UITextFieldDelegate>
{
    void *userdata;
    id clickDefault;
    unsigned long limit;
}
-(void *)userdata;
-(void)setUserdata:(void *)input;
-(void)setClickDefault:(id)input;
@end

@implementation DWEntryField
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)setClickDefault:(id)input { clickDefault = input; }
-(void)dealloc { UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE); dw_signal_disconnect_by_window(self); [super dealloc]; }
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    // Prevent crashing undo bug – see note below.
    if(range.length + range.location > textField.text.length)
        return NO;

    if(limit > 0)
    {
        NSUInteger newLength = [textField.text length] + [string length] - range.length;
        return newLength <= limit;
    }
    return YES;
}
-(void)setMaximumLength:(unsigned long)length { limit = length; }
-(unsigned long)maximumLength { return limit; }
@end

/* Subclass for a text and status text type */
@interface DWText : UILabel
{
    void *userdata;
    id clickDefault;
}
-(void *)userdata;
-(void)setUserdata:(void *)input;
@end

@implementation DWText
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)dealloc { UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE); [super dealloc]; }
@end

/* Subclass for a Notebook page type */
@interface DWNotebookPage : DWBox
{
    int pageid;
}
-(int)pageid;
-(void)setPageid:(int)input;
@end

/* Subclass for a Notebook control type */
@interface DWNotebook : UIView
{
    UISegmentedControl *tabs;
    void *userdata;
    int pageid;
    NSMutableArray<DWNotebookPage *> *views;
    DWNotebookPage *visible;
}
-(void *)userdata;
-(void)setUserdata:(void *)input;
-(int)pageid;
-(void)setPageid:(int)input;
-(void)setVisible:(DWNotebookPage *)input;
-(UISegmentedControl *)tabs;
-(NSMutableArray<DWNotebookPage *> *)views;
-(void)pageChanged:(id)sender;
@end

@implementation DWNotebook
-(id)init
{
    self = [super init];
    tabs = [[[UISegmentedControl alloc] init] retain];
    views = [[[NSMutableArray alloc] init] retain];
    [self addSubview:tabs];
    return self;
}
-(void)setFrame:(CGRect)frame
{
    [super setFrame:frame];
    frame.size.height = 40;
    [tabs setFrame:frame];
}
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(int)pageid { return pageid; }
-(void)setPageid:(int)input { pageid = input; }
-(void)setVisible:(DWNotebookPage *)input { visible = input; }
-(UISegmentedControl *)tabs { return tabs; }
-(NSMutableArray<DWNotebookPage *> *)views { return views; };
-(void)pageChanged:(id)sender
{
    NSInteger intpageid = [tabs selectedSegmentIndex];
    
    if(intpageid != -1 && intpageid < [views count])
    {
        DWNotebookPage *page = [views objectAtIndex:intpageid];

        /* Hide the previously visible page contents */
        if(page != visible)
            [visible setHidden:YES];

        /* If the new page is a valid box, lay it out */
        if([page isKindOfClass:[DWBox class]])
        {
            Box *box = [page box];
            /* Start with the entire notebook size and then adjust
             * it to account for the segement control's height.
             */
            NSInteger height = [tabs frame].size.height;
            CGRect frame = [self frame];
            frame.origin.y += height;
            frame.size.height -= height;
            [page setFrame:frame];
            [page setHidden:NO];
            visible = page;
            _dw_do_resize(box, frame.size.width, frame.size.height);
            _dw_handle_resize_events(box);
        }
        _dw_event_handler(self, DW_INT_TO_POINTER([page pageid]), _DW_EVENT_SWITCH_PAGE);
    }
    else
        visible = nil;
}
-(void)dealloc
{
    UserData *root = userdata;
    _dw_remove_userdata(&root, NULL, TRUE);
    dw_signal_disconnect_by_window(self);
    [tabs removeFromSuperview];
    [tabs release];
    [views release];
    [super dealloc];
}
@end

@implementation DWNotebookPage
-(int)pageid { return pageid; }
-(void)setPageid:(int)input { pageid = input; }
-(void)dealloc { UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE); dw_signal_disconnect_by_window(self); [super dealloc]; }
@end

/* Subclass for a splitbar type */
@interface DWSplitBar : UIView
{
    void *userdata;
    float percent;
    NSInteger Tag;
    id topleft;
    id bottomright;
    int splittype;
}
-(void)setTag:(NSInteger)tag;
-(void *)userdata;
-(void)setUserdata:(void *)input;
-(float)percent;
-(void)setPercent:(float)input;
-(id)topLeft;
-(void)setTopLeft:(id)input;
-(id)bottomRight;
-(void)setBottomRight:(id)input;
-(int)type;
-(void)setType:(int)input;
-(void)resize;
@end

@implementation DWSplitBar
-(void)setTag:(NSInteger)tag { Tag = tag; }
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(float)percent { return percent; }
-(void)setPercent:(float)input
{
    percent = input;
    if(percent > 100.0)
        percent = 100.0;
    else if(percent < 0.0)
        percent = 0.0;
    [self resize];
}
-(id)topLeft { return topleft; }
-(void)setTopLeft:(id)input
{
    topleft = input;
    [self addSubview:topleft];
    [topleft release];
}
-(id)bottomRight { return bottomright; }
-(void)setBottomRight:(id)input
{
    bottomright = input;
    [self addSubview:bottomright];
    [bottomright release];
}
-(int)type { return splittype; }
-(void)setType:(int)input { splittype = input; }
-(void)resize
{
    CGRect rect = self.frame;
    CGRect half = rect;

    half.origin.x = half.origin.y = 0;
    
    if(splittype == DW_HORZ)
        half.size.width = (percent/100.0) * rect.size.width;
    else
        half.size.height = (percent/100.0) * rect.size.height;
    if(topleft)
    {
        DWBox *view = topleft;
        Box *box = [view box];
        [topleft setFrame:half];
        _dw_do_resize(box, half.size.width, half.size.height);
        _dw_handle_resize_events(box);
    }
    if(splittype == DW_HORZ)
    {
        half.origin.x = half.size.width;
        half.size.width = rect.size.width - half.size.width;
    }
    else
    {
        half.origin.y = half.size.height;
        half.size.height = rect.size.height - half.size.height;
    }
    if(bottomright)
    {
        DWBox *view = bottomright;
        Box *box = [view box];
        [bottomright setFrame:half];
        _dw_do_resize(box, half.size.width, half.size.height);
        _dw_handle_resize_events(box);
    }
}
-(void)dealloc
{
    UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE);
    dw_signal_disconnect_by_window(self);
    if(topleft)
    {
        [topleft removeFromSuperview];
        [topleft release];
    }
    if(bottomright)
    {
        [bottomright removeFromSuperview];
        [bottomright release];
    }
    [super dealloc];
}
@end

/* Subclass for a slider type */
@interface DWSlider : UISlider
{
    void *userdata;
    BOOL vertical;
}
-(void)setVertical:(BOOL)vert;
-(BOOL)vertical;
-(void *)userdata;
-(void)setUserdata:(void *)input;
-(void)sliderChanged:(id)sender;
@end

@implementation DWSlider
-(void)setVertical:(BOOL)vert
{
    if(vert)
    {
        CGAffineTransform trans = CGAffineTransformMakeRotation(M_PI * 0.5);
        self.transform = trans;
    }
    vertical = vert;
}
-(BOOL)vertical { return vertical; }
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)sliderChanged:(id)sender { int intVal = (int)[self value]; _dw_event_handler(self, DW_INT_TO_POINTER(intVal), _DW_EVENT_VALUE_CHANGED); }
-(void)dealloc { UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE); dw_signal_disconnect_by_window(self); [super dealloc]; }
@end

/* Subclass for a MLE type */
@interface DWMLE : UITextView
{
    void *userdata;
}
-(void *)userdata;
-(void)setUserdata:(void *)input;
@end

@implementation DWMLE
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)dealloc { UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE); dw_signal_disconnect_by_window(self); [super dealloc]; }
@end

/* Subclass UITableViewCell so we can support multiple columns...
 * Enabled via the DW_FEATURE_CONTAINER_MODE feature setting.
 */
@interface DWTableViewCell : UITableViewCell
{
    NSMutableArray *columndata;
    UIStackView *stack;
    UILabel *label;
    UIImageView *image;
}
-(void)setColumnData:(NSMutableArray *)input;
-(NSMutableArray *)columnData;
-(UIImageView *)image;
-(UILabel *)label;
-(void)columnClicked:(id)sender;
@end

@implementation DWTableViewCell
-(id)init {
    self = [super init];

    image = [[[UIImageView alloc] init] retain];
    [image setTranslatesAutoresizingMaskIntoConstraints:NO];
    [[self contentView] addSubview:image];

    label = [[[UILabel alloc] init] retain];
    [label setTranslatesAutoresizingMaskIntoConstraints:NO];
    [[self contentView] addSubview:label];

    stack = [[[UIStackView alloc] init] retain];
    [stack setTranslatesAutoresizingMaskIntoConstraints:NO];
    [stack setSpacing:5.0];
    [[self contentView] addSubview:stack];

    /* Let's use the default margins */
    UILayoutGuide *g = [[self contentView] layoutMarginsGuide];

    [NSLayoutConstraint activateConstraints:@[
        /* Constrain label Top/Leading/Trailing to content margins guide */
        [image.topAnchor constraintEqualToAnchor:g.topAnchor constant:0.0],
        [image.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:0.0],
        [image.trailingAnchor constraintEqualToAnchor:label.leadingAnchor constant:-4.0],
        [label.topAnchor constraintEqualToAnchor:g.topAnchor constant:0.0],
        [label.leadingAnchor constraintEqualToAnchor:image.trailingAnchor constant:4.0],
        /* No Height, because we'll use the label's intrinsic size */

        /* Constrain stack view Top to label bottom plus 8-points */
        [stack.topAnchor constraintEqualToAnchor:label.bottomAnchor constant:0.0],
        /* Leading/Trailing/Bottom to content margins guide */
        [stack.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:0.0],
        [stack.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:0.0],
        /* No Height, because we'll use the arranged subviews heights */
    ]];
    return self;
}
-(UILabel *)label { return label; }
-(UIImageView *)image { return image; }
-(void)columnClicked:(id)sender
{
    if([sender isMemberOfClass:[UIButton class]])
    {
        id view = [self superview];

        while(view && [view isKindOfClass:[UITableView class]] == NO)
            view = [view superview];

        _dw_event_handler(view, DW_INT_TO_POINTER([sender tag]+1), _DW_EVENT_COLUMN_CLICK);
    }
}
-(void)setColumnData:(NSMutableArray *)input
{
    if(columndata != input)
    {
        NSMutableArray *olddata = columndata;
        columndata = input;
        [columndata retain];
        [olddata release];
    }
    /* If we have columndata and are not in default mode */
    if(columndata && _dw_container_mode > DW_CONTAINER_MODE_DEFAULT)
    {
        bool extra = _dw_container_mode == DW_CONTAINER_MODE_EXTRA;
        id subviews = [stack arrangedSubviews];
        int index = 0;

        /* Extra mode stack is horizontal, multi stack is vertical */
        [stack setAxis:(extra ? UILayoutConstraintAxisHorizontal : UILayoutConstraintAxisVertical)];
        /* For Multi-line, we don't want fill, we want leading */
        if(!extra)
            [stack setAlignment:UIStackViewAlignmentLeading];

        /* Create the stack using columndata, reusing objects when possible */
        for(id object in columndata)
        {
            if([object isKindOfClass:[NSString class]])
            {
                id label = nil;

                /* Check if we already have a view, reuse it if possible.. */
                if(index < [subviews count])
                {
                    id oldview = [subviews objectAtIndex:index];

                    if([oldview isMemberOfClass:(extra ? [UILabel class] : [UIButton class])])
                        label = oldview;
                    else
                        [stack removeArrangedSubview:oldview];
                }
                if(!label)
                {
                    label = extra ? [[UILabel alloc] init] : [UIButton buttonWithType:UIButtonTypeCustom];

                    [label setTag:index];
                    [label setTranslatesAutoresizingMaskIntoConstraints:NO];
                    if(extra)
                        [label setTextAlignment:NSTextAlignmentCenter];
                    else
                    {
                        [label addTarget:self action:@selector(columnClicked:)
                                    forControlEvents:UIControlEventTouchUpInside];
                        [label setTitleColor:[UIColor darkTextColor] forState:UIControlStateNormal];
                    }

                    if(index < [subviews count])
                        [stack insertArrangedSubview:label atIndex:index];
                    else /* Remove the view if it won't work */
                        [stack addArrangedSubview:label];
                }
                /* Set the label or button text/title */
                if(extra)
                    [label setText:object];
                else
                    [label setTitle:object forState:UIControlStateNormal];
                index++;
            }
            else if([object isMemberOfClass:[UIImage class]])
            {
                id image = nil;

                /* Check if we already have a view, reuse it if possible.. */
                if(index < [subviews count])
                {
                    id oldview = [subviews objectAtIndex:index];

                    if([oldview isMemberOfClass:[UIImageView class]])
                        image = oldview;
                    else /* Remove the view if it won't work */
                        [stack removeArrangedSubview:oldview];
                }
                if(!image)
                {
                    image = [[UIImageView alloc] init];

                    [image setTag:index];
                    [image setTranslatesAutoresizingMaskIntoConstraints:NO];

                    if(index < [subviews count])
                        [stack insertArrangedSubview:image atIndex:index];
                    else
                        [stack addArrangedSubview:image];
                }
                /* Set the image view or button image */
                [image setImage:object];

                index++;
            }
        }
    }
}
-(NSMutableArray *)columnData { return columndata; }
-(void)dealloc
{
    [columndata release];
    [image removeFromSuperview];
    [image release];
    [label removeFromSuperview];
    [label release];
    [stack removeFromSuperview];
    [stack release];
    [super dealloc];
}
@end

 /* Create a tableview cell for containers using DW_CONTAINER_MODE_* or listboxes */
DWTableViewCell *_dw_table_cell_view_new(UIImage *icon, NSString *text, NSMutableArray *columndata)
{
    DWTableViewCell *browsercell = [[DWTableViewCell alloc] init];
    [browsercell setAutoresizesSubviews:YES];

    if(icon)
        [[browsercell image] setImage:icon];
    if(text)
        [[browsercell label] setText:text];
    if(columndata)
        [browsercell setColumnData:columndata];
    return browsercell;
}

/* Subclass for a Container/List type */
@interface DWContainer : UITableView <UITableViewDataSource,UITableViewDelegate>
{
    void *userdata;
    NSMutableArray *tvcols;
    NSMutableArray *data;
    NSMutableArray *types;
    NSPointerArray *titles;
    NSPointerArray *rowdatas;
    UIColor *fgcolor, *oddcolor, *evencolor;
    unsigned long dw_oddcolor, dw_evencolor;
    unsigned long _DW_COLOR_ROW_ODD, _DW_COLOR_ROW_EVEN;
    int iLastAddPoint, iLastQueryPoint;
    int filesystem;
    NSTimeInterval lastClick;
    NSInteger lastClickRow;
}
-(NSInteger)tableView:(UITableView *)aTable numberOfRowsInSection:(NSInteger)section;
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
-(void)tableView:(UITableView*)tableView willDisplayCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexPath;
-(UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point;
-(void)addColumn:(NSString *)input andType:(int)type;
-(NSString *)getColumn:(int)col;
-(void *)userdata;
-(void)setUserdata:(void *)input;
-(void)setFilesystem:(int)input;
-(int)filesystem;
-(int)addRow:(DWTableViewCell *)input;
-(int)addRows:(int)number;
-(void)editCell:(id)input at:(int)row;
-(void)removeRow:(int)row;
-(void)setRow:(int)row title:(const char *)input;
-(void *)getRowTitle:(int)row;
-(id)getRow:(int)row;
-(NSMutableArray *)getColumns;
-(int)cellType:(int)col;
-(int)lastAddPoint;
-(int)lastQueryPoint;
-(void)setLastQueryPoint:(int)input;
-(void)clear;
-(void)setup;
-(CGSize)getsize;
-(void)setForegroundColor:(UIColor *)input;
@end

@implementation DWContainer
-(NSInteger)tableView:(UITableView *)aTable numberOfRowsInSection:(NSInteger)section
{
    /* Ignoring section for now, everything in one section */
    if(tvcols && data)
        return [data count];
    return 0;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    /* Not reusing cell views, so get the cell from our array */
    int index = (int)indexPath.row;
    id celldata = [data objectAtIndex:index];

    /* The data is already a DWTableViewCell so just return that */
    if([celldata isMemberOfClass:[DWTableViewCell class]])
    {
        DWTableViewCell *result = celldata;
        
        /* Copy the alignment setting from the column,
         * and set the text color from the container.
         */
        if(fgcolor)
            [[result label] setTextColor:fgcolor];

        /* Return the result */
        return result;
    }
    return nil;
}
-(void)tableView:(UITableView*)tableView willDisplayCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexPath
{
    if(indexPath.row % 2 == 0)
    {
        if(evencolor)
            [cell setBackgroundColor:evencolor];
        else
            [cell setBackgroundColor:[UIColor clearColor]];
    }
    else
    {
        if(oddcolor)
            [cell setBackgroundColor:oddcolor];
        else
            [cell setBackgroundColor:[UIColor clearColor]];
    }
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    int index = (int)indexPath.row;
    id cell = [data objectAtIndex:index];
    CGFloat height = 0.0;

    /* The data is already a DWTableViewCell so just return that */
    if([cell isMemberOfClass:[DWTableViewCell class]])
        height = [[cell contentView] systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
    return height > 0.0 ? height : UITableViewAutomaticDimension;
}
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return _dw_container_mode > DW_CONTAINER_MODE_DEFAULT ? 85.0 : 44.0;
}
-(UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point
{
    DWWindow *window = (DWWindow *)[self window];
    UIContextMenuConfiguration *config = nil;
    NSPointerArray *params = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsOpaqueMemory];

    [params addPointer:[self getRowTitle:(int)indexPath.row]];
    [params addPointer:[self getRowData:(int)indexPath.row]];
    [params addPointer:DW_INT_TO_POINTER((int)point.x)];
    [params addPointer:DW_INT_TO_POINTER((int)point.y)];

    _dw_event_point = point;
    _dw_event_handler(self, params, _DW_EVENT_ITEM_CONTEXT);

    if(window && [window popupMenu])
    {
        __block UIMenu *popupmenu = [[[window popupMenu] menu] retain];
        config = [UIContextMenuConfiguration configurationWithIdentifier:nil
                                                         previewProvider:nil
                                                          actionProvider:^(NSArray* suggestedAction){return popupmenu;}];
        [window setPopupMenu:nil];
    }
    return config;
}
-(void)addColumn:(NSString *)input andType:(int)type { if(tvcols) { [tvcols addObject:input]; [types addObject:[NSNumber numberWithInt:type]]; } }
-(NSString *)getColumn:(int)col { if(tvcols) { return [tvcols objectAtIndex:col]; } return nil; }
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)setFilesystem:(int)input { filesystem = input; }
-(int)filesystem { return filesystem; }
-(void)refreshColors
{
    UIColor *oldodd = oddcolor;
    UIColor *oldeven = evencolor;
    unsigned long thisodd = dw_oddcolor == DW_CLR_DEFAULT ? _DW_COLOR_ROW_ODD : dw_oddcolor;
    unsigned long _odd = _dw_get_color(thisodd);
    unsigned long thiseven = dw_evencolor == DW_CLR_DEFAULT ? _DW_COLOR_ROW_EVEN : dw_evencolor;
    unsigned long _even = _dw_get_color(thiseven);

    /* Get the UIColor for non-default colors */
    if(thisodd != DW_RGB_TRANSPARENT)
        oddcolor = [[UIColor colorWithRed: DW_RED_VALUE(_odd)/255.0 green: DW_GREEN_VALUE(_odd)/255.0 blue: DW_BLUE_VALUE(_odd)/255.0 alpha: 1] retain];
    else
        oddcolor = NULL;
    if(thiseven != DW_RGB_TRANSPARENT)
        evencolor = [[UIColor colorWithRed: DW_RED_VALUE(_even)/255.0 green: DW_GREEN_VALUE(_even)/255.0 blue: DW_BLUE_VALUE(_even)/255.0 alpha: 1] retain];
    else
        evencolor = NULL;
    [oldodd release];
    [oldeven release];
}
-(void)setRowBgOdd:(unsigned long)oddcol andEven:(unsigned long)evencol
{
    /* Save the set colors in case we get a theme change */
    dw_oddcolor = oddcol;
    dw_evencolor = evencol;
    [self refreshColors];
}
-(void)checkDark
{
    /* Update any system colors based on the Dark Mode */
    _DW_COLOR_ROW_EVEN = DW_RGB_TRANSPARENT;
    if(_dw_is_dark())
        _DW_COLOR_ROW_ODD = DW_RGB(100, 100, 100);
    else
        _DW_COLOR_ROW_ODD = DW_RGB(230, 230, 230);
    /* Only refresh if we've been setup already */
    if(titles)
        [self refreshColors];
}
-(void)viewDidChangeEffectiveAppearance { [self checkDark]; }
-(int)insertRow:(DWTableViewCell *)input at:(int)index
{
    if(data)
    {
        if(index < iLastAddPoint)
        {
            iLastAddPoint++;
        }
        [data insertObject:input atIndex:index];
        [titles insertPointer:NULL atIndex:index];
        [rowdatas insertPointer:NULL atIndex:index];
        return (int)[titles count];
    }
    return 0;
}
-(int)addRow:(DWTableViewCell *)input
{
    if(data)
    {
        iLastAddPoint = (int)[titles count];
        [data addObject:input];
        [titles addPointer:NULL];
        [rowdatas addPointer:NULL];
        return (int)[titles count];
    }
    return 0;
}
-(int)addRows:(int)number
{
    if(tvcols)
    {
        int z;

        iLastAddPoint = (int)[titles count];

        for(z=0;z<number;z++)
        {
            [data addObject:[NSNull null]];
            [titles addPointer:NULL];
            [rowdatas addPointer:NULL];
        }
        return (int)[titles count];
    }
    return 0;
}
-(void)editCell:(id)input at:(int)row
{
    if(tvcols)
    {
        if(row < [data count])
        {
            if(!input)
                input = [NSNull null];
            [data replaceObjectAtIndex:row withObject:input];
        }
    }
}
-(void)removeRow:(int)row
{
    if(tvcols)
    {
        void *oldtitle;

        [data removeObjectAtIndex:row];
        oldtitle = [titles pointerAtIndex:row];
        [titles removePointerAtIndex:row];
        [rowdatas removePointerAtIndex:row];
        if(iLastAddPoint > 0 && iLastAddPoint > row)
        {
            iLastAddPoint--;
        }
        if(oldtitle)
            free(oldtitle);
    }
}
-(void)setRow:(int)row title:(const char *)input
{
    if(titles && input)
    {
        void *oldtitle = [titles pointerAtIndex:row];
        void *newtitle = input ? (void *)strdup(input) : NULL;
        [titles replacePointerAtIndex:row withPointer:newtitle];
        if(oldtitle)
            free(oldtitle);
    }
}
-(void)setRowData:(int)row title:(void *)input { if(rowdatas && input) { [rowdatas replacePointerAtIndex:row withPointer:input]; } }
-(void *)getRowTitle:(int)row { if(titles && row > -1) { return [titles pointerAtIndex:row]; } return NULL; }
-(void *)getRowData:(int)row { if(rowdatas && row > -1) { return [rowdatas pointerAtIndex:row]; } return NULL; }
-(id)getRow:(int)row { if(data && [data count]) {  return [data objectAtIndex:row]; } return nil; }
-(NSMutableArray *)getColumns { return tvcols; }
-(int)cellType:(int)col { return [[types objectAtIndex:col] intValue]; }
-(int)lastAddPoint { return iLastAddPoint; }
-(int)lastQueryPoint { return iLastQueryPoint; }
-(void)setLastQueryPoint:(int)input { iLastQueryPoint = input; }
-(void)clear
{
    if(data)
    {
        [data removeAllObjects];
        while([titles count])
        {
            void *oldtitle = [titles pointerAtIndex:0];
            [titles removePointerAtIndex:0];
            [rowdatas removePointerAtIndex:0];
            if(oldtitle)
                free(oldtitle);
        }
    }
    iLastAddPoint = 0;
}
-(void)setup
{
    titles = [[NSPointerArray pointerArrayWithOptions:NSPointerFunctionsOpaqueMemory] retain];
    rowdatas = [[NSPointerArray pointerArrayWithOptions:NSPointerFunctionsOpaqueMemory] retain];
    tvcols = [[[NSMutableArray alloc] init] retain];
    data = [[[NSMutableArray alloc] init] retain];
    types = [[[NSMutableArray alloc] init] retain];
    if(!dw_oddcolor && !dw_evencolor)
        dw_oddcolor = dw_evencolor = DW_CLR_DEFAULT;
    [self checkDark];
}
-(CGSize)getsize
{
    int cwidth = 0, cheight = 0;

    if(tvcols)
    {
        int colcount = (int)[tvcols count];
        int rowcount = (int)[self numberOfRowsInSection:0];
        int width = 0;

        if(rowcount > 0)
        {
            int x;

            for(x=0;x<rowcount;x++)
            {
                DWTableViewCell *cell = [data objectAtIndex:(x*colcount)];
                int thiswidth = 4, thisheight = 0;
                
                if([cell imageView])
                {
                    thiswidth += [[cell image] image].size.width;
                    thisheight = [[cell image] image].size.height;
                }
                if([cell textLabel])
                {
                    int textheight = [[cell label] intrinsicContentSize].width;
                    thiswidth += [[cell label] intrinsicContentSize].width;
                    if(textheight > thisheight)
                        thisheight = textheight;
                }

                cheight += thisheight;

                if(thiswidth > width)
                {
                    width = thiswidth;
                }
            }
            /* If the image is missing default the optimized width to 16. */
            if(!width && [[types objectAtIndex:0] intValue] & DW_CFA_BITMAPORICON)
            {
                width = 16;
            }
        }
        if(width)
            cwidth += width;
    }
    cwidth += 16;
    cheight += 16;
    return CGSizeMake(cwidth, cheight);
}
-(void)setForegroundColor:(UIColor *)input
{
    UIColor *oldfgcolor = fgcolor;

    fgcolor = input;
    [fgcolor retain];
    [oldfgcolor release];
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSPointerArray *params = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsOpaqueMemory];

    [params addPointer:[self getRowTitle:(int)indexPath.row]];
    [params addPointer:[self getRowData:(int)indexPath.row]];

    /* If multiple selection is enabled, treat it as selectionChanged: */
    if([self allowsMultipleSelection])
    {
        NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
        
        /* Handler for container class... check for double tap */
        if(lastClickRow == indexPath.row && now - lastClick < 0.3)
            _dw_event_handler(self, params, _DW_EVENT_ITEM_ENTER);
        else
            _dw_event_handler(self, params, _DW_EVENT_ITEM_SELECT);
        /* Handler for listbox class */
        _dw_event_handler(self, DW_INT_TO_POINTER((int)indexPath.row), _DW_EVENT_LIST_SELECT);
        /* Update lastClick for double tap check */
        lastClick = now;
        lastClickRow = indexPath.row;
    }
    else /* Otherwise treat it as doubleClicked: */
    {
        /* Handler for container class */
        _dw_event_handler(self, params, _DW_EVENT_ITEM_ENTER);
   }
}
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if([self allowsMultipleSelection])
        [self tableView:tableView didSelectRowAtIndexPath:indexPath];
}
-(void)dealloc { UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE); dw_signal_disconnect_by_window(self); [super dealloc]; }
@end

/* Custom tree view subclasses: DWTree, DWTreeItem, DWTreeViewCell, DWTreeViewCellDelegate */
@interface DWTreeItem : NSObject
@property(nonatomic, strong) UIImage *icon;
@property(nonatomic, strong) NSString *title;
@property(nonatomic, assign) void *data;
@property(nonatomic, weak) DWTreeItem *parent;
@property(nonatomic, retain, readonly) NSMutableArray<DWTreeItem *> *children;
@property(nonatomic, assign, readonly) NSUInteger levelDepth;
@property(nonatomic, assign, readonly) BOOL isRoot;
@property(nonatomic, assign, readonly) BOOL hasChildren;
@property(nonatomic, assign) BOOL expanded;
-(instancetype)initWithIcon:(UIImage *)icon Title:(NSString *)title andData:(void *)itemdata;
-(void)insertChildAfter:(DWTreeItem *)treeItem;
-(void)appendChild:(DWTreeItem *)newChild;
-(void)removeFromParent;
-(void)moveToDestination:(DWTreeItem *)destination;
-(BOOL)containsTreeItem:(DWTreeItem *)treeItem;
-(NSArray<DWTreeItem *> *)visibleNodes;
@end

@implementation DWTreeItem
{
    NSArray *_flattenedTreeCache;
}
-(void)dealloc { NSLog(@"DWTreeItem \"%@\" dealloc", self.title); [_title release]; [_icon release]; [super dealloc]; }
-(instancetype)initWithIcon:(UIImage *)icon Title:(NSString *)title andData:(void *)itemdata
{
    if(self = [super init])
    {
        _title = [title retain];
        _icon = [icon retain];
        _data = itemdata;
        _children = [[NSMutableArray alloc] initWithCapacity:1];
    }
    return self;
}
-(NSString *)title
{
    if(_title)
        return _title;
    return self.description;
}
-(UIImage *)icon
{
    if(_icon)
        return _icon;
    return nil;
}
-(NSArray<DWTreeItem *> *)visibleNodes
{
    NSMutableArray *allElements = [[NSMutableArray alloc] init];
    if(![self isRoot])
        [allElements addObject:self];
    if(_expanded)
    {
        for (DWTreeItem *child in _children)
            [allElements addObjectsFromArray:[child visibleNodes]];
    }
    if(_flattenedTreeCache)
        [_flattenedTreeCache release];
    _flattenedTreeCache = allElements;
    return allElements;
}
-(void)insertChildAfter:(DWTreeItem *)treeItem
{
    DWTreeItem *parent = self.parent;
    NSUInteger index = [parent.children indexOfObject:self];
    if(index == NSNotFound)
        index = [parent.children count];
    treeItem.parent = parent;
    [parent.children insertObject:treeItem atIndex:(index+1)];
}
-(void)appendChild:(DWTreeItem *)newChild
{
    newChild.parent = self;
    [_children addObject:newChild];
}
-(void)removeFromParent
{
    DWTreeItem *parent = self.parent;
    if(parent)
    {
        [parent.children removeObject:self];
        self.parent = nil;
    }
}
-(void)moveToDestination:(DWTreeItem *)destination
{
    NSAssert([self containsTreeItem:destination]==NO, @"[self containsTreeItem:destination] something went wrong!");
    if(self == destination || destination == nil)
        return;
    [self removeFromParent];

    [destination insertChildAfter:self];
}
-(BOOL)containsTreeItem:(DWTreeItem *)treeItem
{
    DWTreeItem *parent = treeItem.parent;
    if(parent == nil)
        return NO;
    if(self == parent)
        return YES;
    return [self containsTreeItem:parent];
}
-(NSUInteger)levelDepth
{
    NSUInteger cnt = 0;
    if(_parent != nil)
    {
        cnt += 1;
        cnt += [_parent levelDepth];
    }
    return cnt;
}
-(BOOL)isRoot { return (!_parent); }
-(BOOL)hasChildren { return (_children.count > 0); }
@end

@class DWTreeViewCell;
@protocol DWTreeViewCellDelegate <NSObject>
//@optional
- (BOOL) queryExpandableInTreeViewCell:(DWTreeViewCell *)treeViewCell;
- (void) treeViewCell:(DWTreeViewCell *)treeViewCell expanded:(BOOL)expanded;
@end

@interface DWTreeViewCell : UITableViewCell
@property(nonatomic, strong) UILabel *titleLabel;
@property(nonatomic) NSUInteger level;
@property(nonatomic) BOOL expanded;
@property(nonatomic) BOOL children;
@property(nonatomic, assign) id <DWTreeViewCellDelegate> delegate;
-(instancetype)initWithStyle:(UITableViewCellStyle)style
             reuseIdentifier:(NSString *)reuseIdentifier
                       level:(NSUInteger)level
                    expanded:(BOOL)expanded
                    children:(BOOL)children;
@end

CGRect DWRectInflate(CGRect rect, CGFloat dx, CGFloat dy)
{
    return CGRectMake(rect.origin.x-dx, rect.origin.y-dy, rect.size.width+2*dx, rect.size.height+2*dy);
}

static CGFloat _DW_TREE_IMG_HEIGHT_WIDTH = 20;
static CGFloat _DW_TREE_XOFFSET = 3;

@implementation DWTreeViewCell
{
    DWButton *_arrowImageButton;
}
-(id)initWithStyle:(UITableViewCellStyle)style
   reuseIdentifier:(NSString *)reuseIdentifier
             level:(NSUInteger)level
          expanded:(BOOL)expanded
          children:(BOOL)children
{
    // We should never display the root node
    if(level < 1)
        return nil;

    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];

    if(self)
    {
        _level = level - 1;
        _expanded = expanded;
        _children = children;

        UIView *content = self.contentView;

        UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
        titleLabel.numberOfLines = 0;
        titleLabel.textAlignment = NSTextAlignmentLeft;
        [content addSubview:titleLabel];
        _titleLabel = titleLabel;

        DWButton *arrowImageButton = [[DWButton alloc] initWithFrame:CGRectMake(0, 0,
                                                                                _DW_TREE_IMG_HEIGHT_WIDTH,
                                                                                _DW_TREE_IMG_HEIGHT_WIDTH)];
        [arrowImageButton addTarget:arrowImageButton
                             action:@selector(buttonClicked:)
                   forControlEvents:UIControlEventTouchUpInside];

        [arrowImageButton setType:_DW_BUTTON_TYPE_TREE];
        [arrowImageButton setDidCheckedChanged:^(BOOL checked) {
            _expanded = checked;
            if([_delegate respondsToSelector:@selector(treeViewCell:expanded:)])
                [_delegate treeViewCell:self expanded:checked];
        }];
        [arrowImageButton setCheckState:_expanded];
        [arrowImageButton setContentHorizontalAlignment:UIControlContentHorizontalAlignmentCenter];
        if(!children)
            [arrowImageButton setHidden:YES];
        [content addSubview:arrowImageButton];
        _arrowImageButton = arrowImageButton;
    }
    return self;
}
#pragma mark -
#pragma mark Other overrides
-(void)layoutSubviews
{
    [super layoutSubviews];

    CGSize size = self.contentView.bounds.size;
    CGFloat stepSize = size.height;
    CGRect rc = CGRectMake(_level * stepSize, 0, stepSize, stepSize);
    _arrowImageButton.frame = DWRectInflate(rc, -_DW_TREE_XOFFSET, -_DW_TREE_XOFFSET);

    rc = CGRectMake((_level + 1) * stepSize, 0, stepSize, stepSize);
    self.imageView.frame = DWRectInflate(rc, -_DW_TREE_XOFFSET, -_DW_TREE_XOFFSET);
    _titleLabel.frame = CGRectMake((_level + 2) * stepSize, 0, size.width - (_level + 3) * stepSize, stepSize);
}
@end

@class DWTree;

@protocol DWTreeViewDelegate <NSObject>
@required
-(NSInteger)numberOfRowsInTreeView:(DWTree *)treeView;
-(DWTreeItem *)treeView:(DWTree *)treeView treeItemForRow:(NSInteger)row;
-(NSInteger)treeView:(DWTree *)treeView rowForTreeItem:(DWTreeItem *)treeItem;
-(void)treeView:(DWTree *)treeView removeTreeItem:(DWTreeItem *)treeItem;
-(void)treeView:(DWTree *)treeView moveTreeItem:(DWTreeItem *)treeItem to:(DWTreeItem *)to;
-(void)treeView:(DWTree *)treeView addTreeItem:(DWTreeItem *)treeItem;
-(void)treeView:(DWTree *)treeView didSelectForTreeItem:(DWTreeItem *)treeItem;
-(UIContextMenuConfiguration *)treeView:(DWTree *)treeView contextMenuConfigurationForTreeItem:(DWTreeItem *)treeItem point:(CGPoint)point;
-(BOOL)treeView:(DWTree *)treeView queryExpandableInTreeItem:(DWTreeItem *)treeItem;
-(void)treeView:(DWTree *)treeView treeItem:(DWTreeItem *)treeItem expanded:(BOOL)expanded;
@optional
-(BOOL)treeView:(DWTree *)treeView canEditTreeItem:(DWTreeItem *)treeItem;
-(BOOL)treeView:(DWTree *)treeView canMoveTreeItem:(DWTreeItem *)treeItem;
@end

@interface DWTree : UITableView
@property(nonatomic, strong) UIFont *font;
@property(nonatomic, strong) DWTreeItem *treeItem;
@property(nonatomic, weak) id<DWTreeViewDelegate> treeViewDelegate;
-(instancetype)initWithFrame:(CGRect)frame;
-(void)insertTreeItem:(DWTreeItem *)treeItem;
@end

@interface DWTree () <UITableViewDataSource, UITableViewDelegate, DWTreeViewCellDelegate, DWTreeViewDelegate>
@end

@implementation DWTree
{
    DWTreeItem *_rootNode;
    DWTreeItem *_selectedNode;
}
-(instancetype)initWithFrame:(CGRect)frame
{
    if(self = [super initWithFrame:frame])
    {
        self.delegate=self;
        self.dataSource=self;
        _treeViewDelegate=self;
        self.separatorStyle= UITableViewCellSeparatorStyleNone;
        _font = [UIFont systemFontOfSize:16];
        _rootNode = [[[DWTreeItem alloc] initWithIcon:nil Title:@"@Root" andData:NULL] retain];
        _rootNode.expanded = YES;
    }
    return self;
}
-(void)dealloc { [_rootNode release]; [super dealloc]; }
-(void)insertTreeItem:(DWTreeItem *)treeItem
{
    DWTreeItem *targetNode = nil;

    if([self window])
    {
        NSArray<DWTreeViewCell *> *cells = [self visibleCells];

        // Target the selected tree node first if any
        for(DWTreeViewCell *cell in cells)
        {
            DWTreeItem *iter = [self treeItemForTreeViewCell:cell];
            if(iter == _selectedNode)
            {
                targetNode = iter;
                break;
            }
        }
        // Otherwise target first visible node if any
        if(targetNode == nil && [cells count])
            targetNode = [self treeItemForTreeViewCell:cells[0]];
    }
    // Finally put it on the root level
    if(targetNode == nil)
        targetNode = _rootNode;
    // If target is still nil something went horrible wrong
    NSAssert(targetNode, @"targetNode == nil, something went wrong!");
    if(targetNode.isRoot)
        [targetNode appendChild:treeItem];
    else
        [targetNode insertChildAfter:treeItem];

    if([_treeViewDelegate respondsToSelector:@selector(treeView:addTreeItem:)])
        [_treeViewDelegate treeView:self addTreeItem:treeItem];

    [self reloadData];
    [self resetSelection:NO];
}
-(void)setFont:(UIFont *)font
{
    _font = font;
    [self reloadData];
    [self resetSelection:NO];
}
-(void)clear
{
    [[_rootNode children] removeAllObjects];
    [self reloadData];
    [self resetSelection:NO];
}
-(void)resetSelection:(BOOL)delay
{
    NSInteger row = NSNotFound;
    if([_treeViewDelegate respondsToSelector:@selector(treeView:rowForTreeItem:)])
        row = [_treeViewDelegate treeView:self rowForTreeItem:_selectedNode];
    if(row != NSNotFound)
    {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
        dispatch_block_t run = ^ {
            [self selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
        };
        if(delay)
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), run);
        else
            run();
    }
}
#pragma mark - DWTreeViewDelegate
-(NSInteger)numberOfRowsInTreeView:(DWTree *)treeView { return [_rootNode visibleNodes].count; }
-(void)treeView:(DWTree *)treeView addTreeItem:(DWTreeItem *)treeItem {}
-(void)treeView:(DWTree *)treeView didSelectForTreeItem:(DWTreeItem *)treeItem
{
    if(treeItem)
    {
        const char *title = [[treeItem title] UTF8String];
        NSPointerArray *params = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsOpaqueMemory];

        [params addPointer:strdup(title ? title : "")];
        [params addPointer:[treeItem data]];
        [params addPointer:treeItem];
        _dw_event_handler(treeView, params, _DW_EVENT_ITEM_SELECT);
        free([params pointerAtIndex:0]);
    }
}
-(UIContextMenuConfiguration *)treeView:(DWTree *)treeView contextMenuConfigurationForTreeItem:(DWTreeItem *)treeItem point:(CGPoint)point
{
    UIContextMenuConfiguration *config = nil;
    DWWindow *window = (DWWindow *)[self window];
    const char *title = [[treeItem title] UTF8String];
    NSPointerArray *params = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsOpaqueMemory];

    [params addPointer:strdup(title ? title : "")];
    [params addPointer:[treeItem data]];
    [params addPointer:DW_INT_TO_POINTER((int)point.x)];
    [params addPointer:DW_INT_TO_POINTER((int)point.y)];

    _dw_event_point = point;
    _dw_event_handler(self, params, _DW_EVENT_ITEM_CONTEXT);
    free([params pointerAtIndex:0]);

    if(window && [window popupMenu])
    {
        __block UIMenu *popupmenu = [[[window popupMenu] menu] retain];
        config = [UIContextMenuConfiguration configurationWithIdentifier:nil
                                                         previewProvider:nil
                                                          actionProvider:^(NSArray* suggestedAction){return popupmenu;}];
        [window setPopupMenu:nil];
    }
    return config;
}
-(void)treeView:(DWTree *)treeView moveTreeItem:(DWTreeItem *)treeItem to:(DWTreeItem *)to {}
-(BOOL)treeView:(DWTree *)treeView queryExpandableInTreeItem:(DWTreeItem *)treeItem { return YES; }
-(void)treeView:(DWTree *)treeView removeTreeItem:(DWTreeItem *)treeItem {}
-(NSInteger)treeView:(DWTree *)treeView rowForTreeItem:(DWTreeItem *)treeItem {  return [[_rootNode visibleNodes] indexOfObject:treeItem]; }
-(void)treeView:(DWTree *)treeView treeItem:(DWTreeItem *)treeItem expanded:(BOOL)expanded
{
    if(treeItem)
        _dw_event_handler(treeView, (void *)treeItem, _DW_EVENT_TREE_EXPAND);
}
- (DWTreeItem *)treeView:(DWTree *)treeView treeItemForRow:(NSInteger)row { return [[_rootNode visibleNodes] objectAtIndex:row]; }
#pragma mark - UITableViewDataSource
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; }
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger count = 0;
    if([_treeViewDelegate respondsToSelector:@selector(numberOfRowsInTreeView:)])
        count = [_treeViewDelegate numberOfRowsInTreeView:self];
    return count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    
    DWTreeItem *treeItem = [self treeItemForIndexPath:indexPath];
    DWTreeViewCell *cell = [[DWTreeViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                                 reuseIdentifier:CellIdentifier
                                                           level:[treeItem levelDepth]
                                                        expanded:treeItem.expanded
                                                        children:[treeItem hasChildren]];
    cell.titleLabel.text = treeItem.title;
    cell.imageView.image = treeItem.icon;
    cell.titleLabel.font = _font;
    //cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.delegate = self;
    return cell;
}
-(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    DWTreeItem *treeItem = [self treeItemForIndexPath:indexPath];
    if([_treeViewDelegate respondsToSelector:@selector(treeView:canEditTreeItem:)])
        return [_treeViewDelegate treeView:self canEditTreeItem:treeItem];
    return NO;
}
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    DWTreeItem *treeItem = [self treeItemForIndexPath:indexPath];
    if(editingStyle == UITableViewCellEditingStyleDelete)
    {
        [treeItem removeFromParent];
        if([_treeViewDelegate respondsToSelector:@selector(treeView:removeTreeItem:)])
            [_treeViewDelegate treeView:self removeTreeItem:treeItem];
        if(treeItem.expanded && treeItem.hasChildren)
            [self reloadData];
        else
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
        [self resetSelection:YES];
    } else if (editingStyle == UITableViewCellEditingStyleInsert) {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }
}
-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
    if([fromIndexPath isEqual:toIndexPath])
        return;
    DWTreeItem *srcNode = [self treeItemForIndexPath:fromIndexPath];
    DWTreeItem *targetNode = [self treeItemForIndexPath:toIndexPath];

    if(srcNode && targetNode)
    {
        [srcNode moveToDestination:targetNode];

        if([_treeViewDelegate respondsToSelector:@selector(treeView:moveTreeItem:to:)])
            [_treeViewDelegate treeView:self moveTreeItem:srcNode to:targetNode];

        [self reloadData];
        [self resetSelection:NO];
    }
}
-(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
    DWTreeItem *treeItem = [self treeItemForIndexPath:indexPath];
    if(treeItem && [_treeViewDelegate respondsToSelector:@selector(treeView:canMoveTreeItem:)])
        return [_treeViewDelegate treeView:self canMoveTreeItem:treeItem];
    return NO;
}
#pragma mark - DWTableViewDelegate
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    DWTreeItem *treeItem = [self treeItemForIndexPath:indexPath];
    if(treeItem && [_treeViewDelegate respondsToSelector:@selector(treeView:didSelectForTreeItem:)])
        [_treeViewDelegate treeView:self didSelectForTreeItem:treeItem];
    _selectedNode = treeItem;
}
-(UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point
{
    UIContextMenuConfiguration *config = nil;
    DWTreeItem *treeItem = [self treeItemForIndexPath:indexPath];
    if(treeItem && [_treeViewDelegate respondsToSelector:@selector(treeView:contextMenuConfigurationForTreeItem:point:)])
        config = [_treeViewDelegate treeView:self contextMenuConfigurationForTreeItem:treeItem point:point];

    return config;
}
-(NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
    DWTreeItem *srcNode = [self treeItemForIndexPath:sourceIndexPath];
    DWTreeItem *targetNode = [self treeItemForIndexPath:proposedDestinationIndexPath];
    if([srcNode containsTreeItem:targetNode] || srcNode==targetNode)
        return sourceIndexPath;
    // NSLog(@"Moving to target node \"%@\"", targetNode.title);
    return proposedDestinationIndexPath;
}
#pragma mark - DWTreeViewCellDelegate
-(BOOL)queryExpandableInTreeViewCell:(DWTreeViewCell *)treeViewCell
{
    BOOL allow = YES;
    if([_treeViewDelegate respondsToSelector:@selector(treeView:queryExpandableInTreeItem:)])
    {
        DWTreeItem *treeItem = [self treeItemForTreeViewCell:treeViewCell];
        allow = [_treeViewDelegate treeView:self queryExpandableInTreeItem:treeItem];
    }
    return allow;
}
-(void)treeViewCell:(DWTreeViewCell *)treeViewCell expanded:(BOOL)expanded
{
    DWTreeItem *treeItem = [self treeItemForTreeViewCell:treeViewCell];
    treeItem.expanded = expanded;
    if(treeItem.hasChildren)
    {
        [self reloadData];
        [self resetSelection:NO];
    }
    if([_treeViewDelegate respondsToSelector:@selector(treeView:treeItem:expanded:)])
        [_treeViewDelegate treeView:self treeItem:treeItem expanded:expanded];
}
#pragma mark - retrieve TreeItem object from special cell
-(DWTreeItem *)treeItemForTreeViewCell:(DWTreeViewCell *)treeViewCell
{
    NSIndexPath *indexPath = [self indexPathForCell:treeViewCell];
    return [self treeItemForIndexPath:indexPath];
}
-(DWTreeItem *)treeItemForIndexPath:(NSIndexPath *)indexPath
{
    DWTreeItem *treeItem = nil;
    if([_treeViewDelegate respondsToSelector:@selector(treeView:treeItemForRow:)])
        treeItem = [_treeViewDelegate treeView:self treeItemForRow:indexPath.row];
    NSAssert(treeItem, @"Can't get the Tree Node data");
    return treeItem;
}
@end

/* Subclass for a Calendar type */
@interface DWCalendar : UIDatePicker
{
    void *userdata;
}
-(void *)userdata;
-(void)setUserdata:(void *)input;
@end

@implementation DWCalendar
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)dealloc { UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE); dw_signal_disconnect_by_window(self); [super dealloc]; }
@end

/* Subclass for a stepper component of the spinbutton type */
/* This is a bad way of doing this... but I can't get the other methods to work */
@interface DWStepper : UIStepper
{
    id textfield;
    id parent;
}
-(void)setTextfield:(id)input;
-(id)textfield;
-(void)setParent:(id)input;
-(id)parent;
@end

@implementation DWStepper
-(void)setTextfield:(id)input { textfield = input; }
-(id)textfield { return textfield; }
-(void)setParent:(id)input { parent = input; }
-(id)parent { return parent; }
@end

/* Subclass for a Spinbutton type */
@interface DWSpinButton : UIView <UITextFieldDelegate>
{
    void *userdata;
    UITextField *textfield;
    DWStepper *stepper;
    id clickDefault;
}
-(id)init;
-(void *)userdata;
-(void)setUserdata:(void *)input;
-(UITextField *)textfield;
-(UIStepper *)stepper;
-(void)textFieldDidEndEditing:(UITextField *)textField;
-(void)setClickDefault:(id)input;
@end

@implementation DWSpinButton
-(id)init
{
    self = [super init];

    if(self)
    {
        textfield = [[UITextField alloc] init];
        [self addSubview:textfield];
        stepper = [[DWStepper alloc] init];
        [self addSubview:stepper];
        [stepper setParent:self];
        [stepper setTextfield:textfield];
        [textfield setText:[NSString stringWithFormat:@"%ld",(long)[stepper value]]];
        [textfield setDelegate:self];
        [stepper addTarget:self action:@selector(stepperChanged:) forControlEvents:UIControlEventValueChanged];
    }
    return self;
}
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(UITextField *)textfield { return textfield; }
-(UIStepper *)stepper { return stepper; }
-(void)textFieldDidEndEditing:(UITextField *)textField
{
    long val = [[textfield text] intValue];
    [stepper setValue:(float)val];
    [textfield setText:[NSString stringWithFormat:@"%d", (int)[stepper value]]];
    _dw_event_handler(self, DW_INT_TO_POINTER((int)[stepper value]), _DW_EVENT_VALUE_CHANGED);
}
-(void)stepperChanged:(UIStepper*)theStepper
{
    [textfield setText:[NSString stringWithFormat:@"%d", (int)[theStepper value]]];
    _dw_event_handler(self, DW_INT_TO_POINTER((int)[stepper value]), _DW_EVENT_VALUE_CHANGED);
}
-(void)setClickDefault:(id)input { clickDefault = input; }
-(void)dealloc
{
    UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE);
    dw_signal_disconnect_by_window(self);
    [textfield removeFromSuperview];
    [textfield release];
    [stepper removeFromSuperview];
    [stepper release];
    [super dealloc];
}
@end

API_AVAILABLE(ios(10.0))
@interface DWUserNotificationCenterDelegate : NSObject <UNUserNotificationCenterDelegate>
@end

@implementation DWUserNotificationCenterDelegate
/* Called when a notification is delivered to a foreground app. */
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(macos(10.14))
{
    completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge);
}
/* Called to let your app know which action was selected by the user for a given notification. */
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler API_AVAILABLE(macos(10.14))
{
    NSScanner *objScanner = [NSScanner scannerWithString:response.notification.request.identifier];
    unsigned long long handle;
    HWND notification;

    /* Skip the dw-notification- prefix */
    [objScanner scanString:@"dw-notification-" intoString:nil];
    [objScanner scanUnsignedLongLong:&handle];
    notification = DW_UINT_TO_POINTER(handle);
    
    if ([response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier])
    {
        /* The user dismissed the notification without taking action. */
        dw_signal_disconnect_by_window(notification);
    }
    else if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier])
    {
        /* The user launched the app. */
        _dw_event_handler(notification, nil, _DW_EVENT_CLICKED);
        dw_signal_disconnect_by_window(notification);
    }
    completionHandler();
}
@end

@interface DWComboBox : UITextField <UIPickerViewDelegate,UIPickerViewDataSource,UITextFieldDelegate>
{
    UIPickerView* pickerView;
    NSMutableArray* dataArray;
    UIBarStyle toolbarStyle;
    int selectedIndex;
    void *userdata;
}
-(void)setToolbarStyle:(UIBarStyle)style;
-(void)append:(NSString *)item;
-(void)insert:(NSString *)item atIndex:(int)index;
-(void)clear;
-(int)count;
-(NSString *)getTextAtIndex:(int)index;
-(void)setText:(NSString *)item atIndex:(int)index;
-(void)deleteAtIndex:(int)index;
-(int)selectedIndex;
-(void *)userdata;
-(void)setUserdata:(void *)input;
@end

@implementation DWComboBox
-(id)init
{
    self = [super init];
    if(self)
    {
        [self setDelegate:self];

        /* Set UI defaults */
        toolbarStyle = UIBarStyleDefault;
        dataArray = [[NSMutableArray alloc] init];

        /* Setup the arrow image */
        UIButton *imageButton = [UIButton buttonWithType:UIButtonTypeCustom];
        UIImage *image = [UIImage systemImageNamed:@"chevron.down"];
        [imageButton setImage:image forState:UIControlStateNormal];
        [self setRightView:imageButton];
        [self setRightViewMode:UITextFieldViewModeAlways];
        [imageButton addTarget:self action:@selector(showPicker:) forControlEvents:UIControlEventTouchUpInside];
        selectedIndex = -1;
    }
    return self;
}
-(void)setToolbarStyle:(UIBarStyle)style { toolbarStyle = style; }
-(void)append:(NSString *)item { if(item) [dataArray addObject:item]; }
-(void)insert:(NSString *)item atIndex:(int)index { if(item) [dataArray insertObject:item atIndex:index]; }
-(void)clear { [dataArray removeAllObjects]; }
-(int)count { return (int)[dataArray count]; }
-(void)selectIndex:(int)index
{
    if(index > -1 && index < (int)[dataArray count])
    {
        selectedIndex = index;
        [self setText:[dataArray objectAtIndex:index]];
    }
}
-(NSString *)getTextAtIndex:(int)index
{
    if(index > -1 && index < [dataArray count])
        return [dataArray objectAtIndex:index];
    return nil;
}
-(void)setText:(NSString *)item atIndex:(int)index
{
    if(item && index > -1 && index < [dataArray count])
        [dataArray replaceObjectAtIndex:index withObject:item];
}
-(void)deleteAtIndex:(int)index { if(index > -1 && index < [dataArray count]) [dataArray removeObjectAtIndex:index]; }
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 1; }
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
    if(row > -1 && row < [dataArray count])
    {
        selectedIndex = (int)row;
        [self setText:[dataArray objectAtIndex:row]];
        [self sendActionsForControlEvents:UIControlEventValueChanged];
        _dw_event_handler(self, DW_INT_TO_POINTER(selectedIndex), _DW_EVENT_LIST_SELECT);
    }
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { return [dataArray count]; }
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
    if(row > -1 && row < [dataArray count])
        return [dataArray objectAtIndex:row];
    return nil;
}
-(void)doneClicked:(id)sender
{
    /* Hides the pickerView */
    [self resignFirstResponder];
    
    if([[self text] length] == 0 || ![dataArray containsObject:[self text]])
    {
        selectedIndex = -1;
    }
    [self sendActionsForControlEvents:UIControlEventValueChanged];
}
-(void)cancelClicked:(id)sender
{
    /* Hides the pickerView */
    [self resignFirstResponder];
}
-(void)showPicker:(id)sender
{
    [self resignFirstResponder];

    pickerView = [[UIPickerView alloc] init];
    [pickerView setDataSource:self];
    [pickerView setDelegate:self];

    /* If the text field is empty show the place holder otherwise show the last selected option */
    if([[self text] length] == 0 || ![dataArray containsObject:[self text]])
    {
        [pickerView selectRow:0 inComponent:0 animated:YES];
    }
    else
    {
        if([dataArray containsObject:[self text]])
        {
            [pickerView selectRow:[dataArray indexOfObject:[self text]] inComponent:0 animated:YES];
        }
    }

    UIToolbar* toolbar = [[UIToolbar alloc] init];
    [toolbar setBarStyle:toolbarStyle];
    
    /* Space between buttons */
    UIBarButtonItem *flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
                                                                                   target:nil
                                                                                   action:nil];
    
    UIBarButtonItem *doneButton = [[UIBarButtonItem alloc]
                                   initWithTitle:@"Done"
                                   style:UIBarButtonItemStyleDone
                                   target:self
                                   action:@selector(doneClicked:)];

    UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]
                                    initWithTitle:@"Cancel"
                                    style:UIBarButtonItemStylePlain
                                    target:self
                                    action:@selector(cancelClicked:)];
        
    [toolbar setItems:[NSArray arrayWithObjects:cancelButton, flexibleSpace, doneButton, nil]];
    [toolbar sizeToFit];

    /* Custom input view */
    [self setInputView:pickerView];
    [self setInputAccessoryView:toolbar];
    [super becomeFirstResponder];
}
-(BOOL)becomeFirstResponder
{
    [self setInputView:nil];
    [self setInputAccessoryView:nil];
    return [super becomeFirstResponder];
}
-(int)selectedIndex { return selectedIndex; }
-(void *)userdata { return userdata; }
-(void)setUserdata:(void *)input { userdata = input; }
-(void)dealloc
{
    UserData *root = userdata; _dw_remove_userdata(&root, NULL, TRUE);
    dw_signal_disconnect_by_window(self);
    [dataArray release];
    [super dealloc];
}
@end

/* Subclass for a MDI type
 * This is just a box for display purposes... but it is a
 * unique class so it can be identified when creating windows.
 */
@interface DWMDI : DWBox {}
@end

@implementation DWMDI
@end

/* This function adds a signal handler callback into the linked list.
 */
void _dw_new_signal(ULONG message, HWND window, int msgid, void *signalfunction, void *discfunc, void *data)
{
    DWSignalHandler *new = malloc(sizeof(DWSignalHandler));

    new->message = message;
    new->window = window;
    new->id = msgid;
    new->signalfunction = signalfunction;
    new->discfunction = discfunc;
    new->data = data;
    new->next = NULL;

    if (!DWRoot)
        DWRoot = new;
    else
    {
        DWSignalHandler *prev = NULL, *tmp = DWRoot;
        while(tmp)
        {
            if(tmp->message == message &&
               tmp->window == window &&
               tmp->id == msgid &&
               tmp->signalfunction == signalfunction)
            {
                tmp->data = data;
                free(new);
                return;
            }
            prev = tmp;
            tmp = tmp->next;
        }
        if(prev)
            prev->next = new;
        else
            DWRoot = new;
    }
}

/* Finds the message number for a given signal name */
ULONG _dw_findsigmessage(const char *signame)
{
    int z;

    for(z=0;z<SIGNALMAX;z++)
    {
        if(strcasecmp(signame, DWSignalTranslate[z].name) == 0)
            return DWSignalTranslate[z].message;
    }
    return 0L;
}

unsigned long _dw_foreground = 0xAAAAAA, _dw_background = 0;

void _dw_handle_resize_events(Box *thisbox)
{
    int z;

    for(z=0;z<thisbox->count;z++)
    {
        id handle = thisbox->items[z].hwnd;

        if(thisbox->items[z].type == _DW_TYPE_BOX)
        {
            Box *tmp = (Box *)[handle box];

            if(tmp)
            {
                _dw_handle_resize_events(tmp);
            }
        }
        else
        {
            if([handle isMemberOfClass:[DWRender class]])
            {
                DWRender *render = (DWRender *)handle;
                CGSize oldsize = [render size];
                CGSize newsize = [render frame].size;

                /* Eliminate duplicate configure requests */
                if(oldsize.width != newsize.width || oldsize.height != newsize.height)
                {
                    if(newsize.width > 0 && newsize.height > 0)
                    {
                        [render setSize:newsize];
                        _dw_event_handler(handle, nil, _DW_EVENT_CONFIGURE);
                    }
                }
            }
            /* Special handling for notebook controls */
            else if([handle isMemberOfClass:[DWNotebook class]])
            {
                DWNotebook *notebook = handle;
                NSInteger intpageid = [[notebook tabs] selectedSegmentIndex];
                
                if(intpageid != -1 && intpageid < [[notebook views] count])
                {
                    DWNotebookPage *page = [[notebook views] objectAtIndex:intpageid];

                    if([page isKindOfClass:[DWBox class]])
                    {
                        Box *box = [page box];
                        _dw_handle_resize_events(box);
                    }
                }
            }
            /* Handle laying out scrollviews... if required space is less
             * than available space, then expand.  Otherwise use required space.
             */
            else if([handle isMemberOfClass:[DWScrollBox class]])
            {
                DWScrollBox *scrollbox = (DWScrollBox *)handle;
                NSArray *subviews = [scrollbox subviews];
                DWBox *contentbox = [subviews firstObject];
                Box *thisbox = [contentbox box];

                /* Get the required space for the box */
                _dw_handle_resize_events(thisbox);
            }
        }
    }
}

/* This function calculates how much space the widgets and boxes require
 * and does expansion as necessary.
 */
static void _dw_resize_box(Box *thisbox, int *depth, int x, int y, int pass)
{
    /* Current item position */
    int z, currentx = thisbox->pad, currenty = thisbox->pad;
    /* Used x, y and padding maximum values...
     * These will be used to find the widest or
     * tallest items in a box.
     */
    int uymax = 0, uxmax = 0;
    int upymax = 0, upxmax = 0;

    /* Reset the box sizes */
    thisbox->minwidth = thisbox->minheight = thisbox->usedpadx = thisbox->usedpady = thisbox->pad * 2;

    /* Count up all the space for all items in the box */
    for(z=0;z<thisbox->count;z++)
    {
        int itempad, itemwidth, itemheight;

        if(thisbox->items[z].type == _DW_TYPE_BOX)
        {
            id box = thisbox->items[z].hwnd;
            Box *tmp = (Box *)[box box];

            if(tmp)
            {
                /* On the first pass calculate the box contents */
                if(pass == 1)
                {
                    (*depth)++;

                    /* Save the newly calculated values on the box */
                    _dw_resize_box(tmp, depth, x, y, pass);

                    /* Duplicate the values in the item list for use below */
                    thisbox->items[z].width = tmp->minwidth;
                    thisbox->items[z].height = tmp->minheight;

                    /* If the box has no contents but is expandable... default the size to 1 */
                    if(!thisbox->items[z].width && thisbox->items[z].hsize)
                       thisbox->items[z].width = 1;
                    if(!thisbox->items[z].height && thisbox->items[z].vsize)
                       thisbox->items[z].height = 1;

                    (*depth)--;
                }
            }
        }

        /* Precalculate these values, since they will
         * be used used repeatedly in the next section.
         */
        itempad = thisbox->items[z].pad * 2;
        itemwidth = thisbox->items[z].width + itempad;
        itemheight = thisbox->items[z].height + itempad;

        /* Calculate the totals and maximums */
        if(thisbox->type == DW_VERT)
        {
            if(itemwidth > uxmax)
                uxmax = itemwidth;

            if(thisbox->items[z].hsize != _DW_SIZE_EXPAND)
            {
                if(itemwidth > upxmax)
                    upxmax = itemwidth;
            }
            else
            {
                if(itempad > upxmax)
                    upxmax = itempad;
            }
            thisbox->minheight += itemheight;
            if(thisbox->items[z].vsize != _DW_SIZE_EXPAND)
                thisbox->usedpady += itemheight;
            else
                thisbox->usedpady += itempad;
        }
        else
        {
            if(itemheight > uymax)
                uymax = itemheight;
            if(thisbox->items[z].vsize != _DW_SIZE_EXPAND)
            {
                if(itemheight > upymax)
                    upymax = itemheight;
            }
            else
            {
                if(itempad > upymax)
                    upymax = itempad;
            }
            thisbox->minwidth += itemwidth;
            if(thisbox->items[z].hsize != _DW_SIZE_EXPAND)
                thisbox->usedpadx += itemwidth;
            else
                thisbox->usedpadx += itempad;
        }
    }

    /* Add the maximums which were calculated in the previous loop */
    thisbox->minwidth += uxmax;
    thisbox->minheight += uymax;
    thisbox->usedpadx += upxmax;
    thisbox->usedpady += upymax;

    /* The second pass is for actual placement. */
    if(pass > 1)
    {
        for(z=0;z<(thisbox->count);z++)
        {
            int height = thisbox->items[z].height;
            int width = thisbox->items[z].width;
            int itempad = thisbox->items[z].pad * 2;
            int thispad = thisbox->pad * 2;

            /* Calculate the new sizes */
            if(thisbox->items[z].hsize == _DW_SIZE_EXPAND)
            {
                if(thisbox->type == DW_HORZ)
                {
                    int expandablex = thisbox->minwidth - thisbox->usedpadx;

                    if(expandablex)
                        width = (int)(((float)width / (float)expandablex) * (float)(x - thisbox->usedpadx));
                }
                else
                    width = x - (itempad + thispad + thisbox->grouppadx);
            }
            if(thisbox->items[z].vsize == _DW_SIZE_EXPAND)
            {
                if(thisbox->type == DW_VERT)
                {
                    int expandabley = thisbox->minheight - thisbox->usedpady;

                    if(expandabley)
                        height = (int)(((float)height / (float)expandabley) * (float)(y - thisbox->usedpady));
                }
                else
                    height = y - (itempad + thispad + thisbox->grouppady);
            }

            /* If the calculated size is valid... */
            if(height > 0 && width > 0)
            {
                int pad = thisbox->items[z].pad;
                id handle = thisbox->items[z].hwnd;
                CGRect rect;

                rect.origin.x = currentx + pad;
                rect.origin.y = currenty + pad;
                rect.size.width = width;
                rect.size.height = height;
                [handle setFrame:rect];

                /* After placing a box... place its components */
                if(thisbox->items[z].type == _DW_TYPE_BOX)
                {
                    id box = thisbox->items[z].hwnd;
                    Box *tmp = (Box *)[box box];

                    if(tmp)
                    {
                        (*depth)++;
                        _dw_resize_box(tmp, depth, width, height, pass);
                        (*depth)--;
                    }
                }

                /* Special handling for notebook controls */
                if([handle isMemberOfClass:[DWNotebook class]])
                {
                    DWNotebook *notebook = handle;
                    NSInteger intpageid = [[notebook tabs] selectedSegmentIndex];

                    if(intpageid == -1)
                    {
                        if([[notebook tabs] numberOfSegments] > 0)
                        {
                            DWNotebookPage *notepage = [[notebook views] firstObject];

                            /* If there is no selected segment, select the first one... */
                            [[notebook tabs] setSelectedSegmentIndex:0];
                            intpageid = 0;
                            [notepage setHidden:NO];
                            [notebook setVisible:notepage];
                        }
                    }

                    if(intpageid != -1 && intpageid < [[notebook views] count])
                    {
                        DWNotebookPage *page = [[notebook views] objectAtIndex:intpageid];

                        /* If the new page is a valid box, lay it out */
                        if([page isKindOfClass:[DWBox class]])
                        {
                            Box *box = [page box];
                            /* Start with the entire notebook size and then adjust
                             * it to account for the segement control's height.
                             */
                            NSInteger height = [[notebook tabs] frame].size.height;
                            CGRect frame = [notebook frame];
                            frame.origin.y += height;
                            frame.size.height -= height;
                            [page setFrame:frame];
                            _dw_do_resize(box, frame.size.width, frame.size.height);
                            _dw_handle_resize_events(box);
                        }
                    }
                }
                /* Handle laying out scrollviews... if required space is less
                 * than available space, then expand.  Otherwise use required space.
                 */
                else if([handle isMemberOfClass:[DWScrollBox class]])
                {
                    int depth = 0;
                    DWScrollBox *scrollbox = (DWScrollBox *)handle;
                    NSArray *subviews = [scrollbox subviews];
                    DWBox *contentbox = [subviews firstObject];
                    Box *thisbox = [contentbox box];
                    /* We start with the content being the available size */
                    CGRect frame = CGRectMake(0,0,rect.size.width,rect.size.height);

                    /* Get the required space for the box */
                    _dw_resize_box(thisbox, &depth, x, y, 1);

                    /* Expand the content box to the size of the contents */
                    if(frame.size.width < thisbox->minwidth)
                    {
                        frame.size.width = thisbox->minwidth;
                    }
                    if(frame.size.height < thisbox->minheight)
                    {
                        frame.size.height = thisbox->minheight;
                    }
                    [scrollbox setContentSize:frame.size];
                    [contentbox setFrame:frame];

                    /* Layout the content of the scrollbox */
                    _dw_do_resize(thisbox, frame.size.width, frame.size.height);
                    _dw_handle_resize_events(thisbox);
                }
                /* Special handling for spinbutton controls */
                else if([handle isMemberOfClass:[DWSpinButton class]])
                {
                    DWSpinButton *spinbutton = (DWSpinButton *)handle;
                    UITextField *textfield = [spinbutton textfield];
                    UIStepper *stepper = [spinbutton stepper];
                    NSInteger stepperwidth = [stepper intrinsicContentSize].width;
                    [textfield setFrame:CGRectMake(0,0,rect.size.width-stepperwidth,rect.size.height)];
                    [stepper setFrame:CGRectMake(rect.size.width-stepperwidth,0,stepperwidth,rect.size.height)];
                }
                else if([handle isMemberOfClass:[DWSplitBar class]])
                {
                    DWSplitBar *split = (DWSplitBar *)handle;
                    [split resize];
                }
                else if([handle isMemberOfClass:[DWMLE class]])
                {
                    DWMLE *mle = (DWMLE *)handle;

                    if([[mle textContainer] lineBreakMode] == NSLineBreakByClipping)
                        [[mle textContainer] setSize:CGRectInfinite.size];
                }

                /* Advance the current position in the box */
                if(thisbox->type == DW_HORZ)
                    currentx += width + (pad * 2);
                if(thisbox->type == DW_VERT)
                    currenty += height + (pad * 2);
            }
        }
    }
}

static void _dw_do_resize(Box *thisbox, int x, int y)
{
    if(x > 0 && y > 0)
    {
        if(thisbox)
        {
            int depth = 0;

            /* Calculate space requirements */
            _dw_resize_box(thisbox, &depth, x, y, 1);

            /* Finally place all the boxes and controls */
            _dw_resize_box(thisbox, &depth, x, y, 2);
        }
    }
}

/*
 * Runs a message loop for Dynamic Windows.
 */
void API dw_main(void)
{
    /* We don't actually run a loop here,
     * we launched a new thread to run the loop there.
     * Just wait for dw_main_quit() on the DWMainEvent.
     */
    dw_event_wait(DWMainEvent, DW_TIMEOUT_INFINITE);
}

/*
 * Causes running dw_main() to return.
 */
void API dw_main_quit(void)
{
    dw_event_post(DWMainEvent);
}

/*
 * Runs a message loop for Dynamic Windows, for a period of milliseconds.
 * Parameters:
 *           milliseconds: Number of milliseconds to run the loop for.
 */
void API dw_main_sleep(int milliseconds)
{
    DWTID curr = pthread_self();

    if(DWThread == (DWTID)-1 || DWThread == curr)
    {
        DWTID orig = DWThread;
        NSDate *until = [NSDate dateWithTimeIntervalSinceNow:(milliseconds/1000.0)];

        if(orig == (DWTID)-1)
        {
            DWThread = curr;
        }
        /* Process any pending events */
        _dw_main_iteration(until);
        if(orig == (DWTID)-1)
        {
            DWThread = orig;
        }
    }
    else
    {
        usleep(milliseconds * 1000);
    }
}

/* Internal version that doesn't lock the run mutex */
int _dw_main_iteration(NSDate *date)
{
    return [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
}

/*
 * Processes a single message iteration and returns.
 */
void API dw_main_iteration(void)
{
    DWTID curr = pthread_self();

    if(DWThread == (DWTID)-1)
    {
        DWThread = curr;
        _dw_main_iteration([NSDate distantPast]);
        DWThread = (DWTID)-1;
    }
    else if(DWThread == curr)
    {
        _dw_main_iteration([NSDate distantPast]);
    }
}

/*
 * Cleanly terminates a DW session, should be signal handler safe.
 */
void API dw_shutdown(void)
{
}

/*
 * Cleanly terminates a DW session, should be signal handler safe.
 * Parameters:
 *       exitcode: Exit code reported to the operating system.
 */
void API dw_exit(int exitcode)
{
    dw_shutdown();
    exit(exitcode);
}

/*
 * Free's memory allocated by dynamic windows.
 * Parameters:
 *           ptr: Pointer to dynamic windows allocated
 *                memory to be free()'d.
 */
void API dw_free(void *ptr)
{
   free(ptr);
}

/*
 * Returns a pointer to a static buffer which containes the
 * current user directory.  Or the root directory (C:\ on
 * OS/2 and Windows).
 */
char *dw_user_dir(void)
{
    static char _user_dir[PATH_MAX+1] = { 0 };

    if(!_user_dir[0])
    {
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                             NSUserDomainMask, YES);
        NSString *nshome = [paths firstObject];
        const char *home;

        if(nshome && (home = [nshome UTF8String]))
            strncpy(_user_dir, home, PATH_MAX);
        else if((nshome = NSHomeDirectory()) && (home = [nshome UTF8String]))
            strncpy(_user_dir, home, PATH_MAX);
        else
            strcpy(_user_dir, "/");
    }
    return _user_dir;
}

/*
 * Returns a pointer to a static buffer which containes the
 * private application data directory.
 */
char * API dw_app_dir(void)
{
    return _dw_bundle_path;
}

/*
 * Sets the application ID used by this Dynamic Windows application instance.
 * Parameters:
 *         appid: A string typically in the form: com.company.division.application
 *         appname: The application name used on Windows or NULL.
 * Returns:
 *         DW_ERROR_NONE after successfully setting the application ID.
 *         DW_ERROR_UNKNOWN if unsupported on this system.
 *         DW_ERROR_GENERAL if the application ID is not allowed.
 * Remarks:
 *          This must be called before dw_init().  If dw_init() is called first
 *          it will create a unique ID in the form: org.dbsoft.dwindows.application
 *          or if the application name cannot be detected: org.dbsoft.dwindows.pid.#
 *          The appname is only required on Windows.  If NULL is passed the detected
 *          application name will be used, but a prettier name may be desired.
 */
int dw_app_id_set(const char *appid, const char *appname)
{
    strncpy(_dw_app_id, appid, _DW_APP_ID_SIZE);
    return DW_ERROR_NONE;
}

/*
 * Displays a debug message on the console...
 * Parameters:
 *           format: printf style format string.
 *           ...: Additional variables for use in the format.
 */
void API dw_debug(const char *format, ...)
{
   va_list args;

   va_start(args, format);
   dw_vdebug(format, args);
   va_end(args);
}

void API dw_vdebug(const char *format, va_list args)
{
   NSString *nformat = [[NSString stringWithUTF8String:format] autorelease];

   NSLogv(nformat, args);
}

/*
 * Displays a Message Box with given text and title..
 * Parameters:
 *           title: The title of the message box.
 *           flags: flags to indicate buttons and icon
 *           format: printf style format string.
 *           ...: Additional variables for use in the format.
 */
int API dw_messagebox(const char *title, int flags, const char *format, ...)
{
   va_list args;
   int rc;

   va_start(args, format);
   rc = dw_vmessagebox(title, flags, format, args);
   va_end(args);
   return rc;
}

int API dw_vmessagebox(const char *title, int flags, const char *format, va_list args)
{
    NSInteger iResponse;
    NSString *button1 = @"OK";
    NSString *button2 = nil;
    NSString *button3 = nil;
    NSString *mtitle = [NSString stringWithUTF8String:title];
    NSString *mtext;
    UIAlertControllerStyle mstyle = UIAlertControllerStyleAlert;
    NSArray *params;
    static int in_mb = FALSE;

    /* Prevent recursion */
    if(in_mb)
        return 0;
    in_mb = TRUE;

    if(flags & DW_MB_OKCANCEL)
    {
        button2 = @"Cancel";
    }
    else if(flags & DW_MB_YESNO)
    {
        button1 = @"Yes";
        button2 = @"No";
    }
    else if(flags & DW_MB_YESNOCANCEL)
    {
        button1 = @"Yes";
        button2 = @"No";
        button3 = @"Cancel";
    }

    mtext = [[NSString alloc] initWithFormat:[NSString stringWithUTF8String:format] arguments:args];

    params = [NSMutableArray arrayWithObjects:mtitle, mtext, [NSNumber numberWithInteger:mstyle], button1, button2, button3, nil];
    [DWObj safeCall:@selector(messageBox:) withObject:params];
    in_mb = FALSE;
    iResponse = [[params lastObject] integerValue];

    switch(iResponse)
    {
        case 1:    /* user pressed OK */
            if(flags & DW_MB_YESNO || flags & DW_MB_YESNOCANCEL)
            {
                return DW_MB_RETURN_YES;
            }
            return DW_MB_RETURN_OK;
        case 2:  /* user pressed Cancel */
            if(flags & DW_MB_OKCANCEL)
            {
                return DW_MB_RETURN_CANCEL;
            }
            return DW_MB_RETURN_NO;
        case 3:      /* user pressed the third button */
            return DW_MB_RETURN_CANCEL;
    }
    return 0;
}

/*
 * Opens a file dialog and queries user selection.
 * Parameters:
 *       title: Title bar text for dialog.
 *       defpath: The default path of the open dialog.
 *       ext: Default file extention.
 *       flags: DW_FILE_OPEN or DW_FILE_SAVE.
 * Returns:
 *       NULL on error. A malloced buffer containing
 *       the file path on success.
 *
 */
char * API dw_file_browse(const char *title, const char *defpath, const char *ext, int flags)
{
    NSPointerArray *params = [[NSPointerArray pointerArrayWithOptions:NSPointerFunctionsOpaqueMemory] retain];
    char *file = NULL;

    [params addPointer:(void *)defpath];
    [params addPointer:(void *)ext];
    [params addPointer:DW_INT_TO_POINTER(flags)];
    [DWObj safeCall:@selector(filePicker:) withObject:params];
    if([params count] > 3)
        file = [params pointerAtIndex:3];

    return file;
}

/*
 * Gets the contents of the default clipboard as text.
 * Parameters:
 *       None.
 * Returns:
 *       Pointer to an allocated string of text or NULL if clipboard empty or contents could not
 *       be converted to text.
 */
char *dw_clipboard_get_text()
{
    UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
    NSString *str = [pasteboard string];
    if(str != nil)
        return strdup([str UTF8String]);
    return NULL;
}

/*
 * Sets the contents of the default clipboard to the supplied text.
 * Parameters:
 *       Text.
 */
void dw_clipboard_set_text(const char *str, int len)
{
    UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];

    [pasteboard setString:[NSString stringWithUTF8String:str]];
}


/*
 * Allocates and initializes a dialog struct.
 * Parameters:
 *           data: User defined data to be passed to functions.
 */
DWDialog * API dw_dialog_new(void *data)
{
    DWDialog *tmp = malloc(sizeof(DWDialog));

    if(tmp)
    {
        tmp->eve = dw_event_new();
        dw_event_reset(tmp->eve);
        tmp->data = data;
        tmp->done = FALSE;
        tmp->result = NULL;
    }
    return tmp;
}

/*
 * Accepts a dialog struct and returns the given data to the
 * initial called of dw_dialog_wait().
 * Parameters:
 *           dialog: Pointer to a dialog struct aquired by dw_dialog_new).
 *           result: Data to be returned by dw_dialog_wait().
 */
int API dw_dialog_dismiss(DWDialog *dialog, void *result)
{
    dialog->result = result;
    dw_event_post(dialog->eve);
    dialog->done = TRUE;
    return 0;
}

/*
 * Accepts a dialog struct waits for dw_dialog_dismiss() to be
 * called by a signal handler with the given dialog struct.
 * Parameters:
 *           dialog: Pointer to a dialog struct aquired by dw_dialog_new).
 */
void * API dw_dialog_wait(DWDialog *dialog)
{
    void *tmp = NULL;

    if(dialog)
    {
        while(!dialog->done)
        {
            _dw_main_iteration([NSDate dateWithTimeIntervalSinceNow:0.01]);
        }
        dw_event_close(&dialog->eve);
        tmp = dialog->result;
        free(dialog);
    }
    return tmp;
}

/*
 * Create a new Box to be packed.
 * Parameters:
 *       type: Either DW_VERT (vertical) or DW_HORZ (horizontal).
 *       pad: Number of pixels to pad around the box.
 */
DW_FUNCTION_DEFINITION(dw_box_new, HWND, int type, int pad)
DW_FUNCTION_ADD_PARAM2(type, pad)
DW_FUNCTION_RETURN(dw_box_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(type, int, pad, int)
{
    DW_FUNCTION_INIT;
    DWBox *view = [[[DWBox alloc] init] retain];
    Box *newbox = [view box];
    memset(newbox, 0, sizeof(Box));
    newbox->pad = pad;
    newbox->type = type;
    DW_FUNCTION_RETURN_THIS(view);
}

/*
 * Create a new Group Box to be packed.
 * Parameters:
 *       type: Either DW_VERT (vertical) or DW_HORZ (horizontal).
 *       pad: Number of pixels to pad around the box.
 *       title: Text to be displayined in the group outline.
 */
HWND API dw_groupbox_new(int type, int pad, const char *title)
{
    return dw_box_new(type, pad);
}

/*
 * Create a new scrollable Box to be packed.
 * Parameters:
 *       type: Either DW_VERT (vertical) or DW_HORZ (horizontal).
 *       pad: Number of pixels to pad around the box.
 */
DW_FUNCTION_DEFINITION(dw_scrollbox_new, HWND, int type, int pad)
DW_FUNCTION_ADD_PARAM2(type, pad)
DW_FUNCTION_RETURN(dw_scrollbox_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(type, int, pad, int)
{
    DWScrollBox *scrollbox = [[[DWScrollBox alloc] init] retain];
    DWBox *box = dw_box_new(type, pad);
    DWBox *tmpbox = dw_box_new(DW_VERT, 0);
    dw_box_pack_start(tmpbox, box, 1, 1, TRUE, TRUE, 0);
    [scrollbox setBox:box];
    [scrollbox addSubview:tmpbox];
    [scrollbox setScrollEnabled:YES];
    [tmpbox release];
    DW_FUNCTION_RETURN_THIS(scrollbox);
}

/*
 * Returns the position of the scrollbar in the scrollbox
 * Parameters:
 *          handle: Handle to the scrollbox to be queried.
 *          orient: The vertical or horizontal scrollbar.
 */
DW_FUNCTION_DEFINITION(dw_scrollbox_get_pos, int, HWND handle, int orient)
DW_FUNCTION_ADD_PARAM2(handle, orient)
DW_FUNCTION_RETURN(dw_scrollbox_get_pos, int)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, orient, int)
{
    DWScrollBox *scrollbox = handle;
    NSArray *subviews = [scrollbox subviews];
    UIView *view = [subviews firstObject];
    CGSize contentsize = [scrollbox contentSize];
    CGPoint contentoffset = [scrollbox contentOffset];
    int range = 0;
    int val = 0;
    if(orient == DW_VERT)
    {
        range = [view bounds].size.height - contentsize.height;
        val = contentoffset.y;
    }
    else
    {
        range = [view bounds].size.width - contentsize.width;
        val = contentoffset.x;
    }
    if(val > range)
    {
        val = range;
    }
    DW_FUNCTION_RETURN_THIS(val);
}

/*
 * Gets the range for the scrollbar in the scrollbox.
 * Parameters:
 *          handle: Handle to the scrollbox to be queried.
 *          orient: The vertical or horizontal scrollbar.
 */
DW_FUNCTION_DEFINITION(dw_scrollbox_get_range, int, HWND handle, int orient)
DW_FUNCTION_ADD_PARAM2(handle, orient)
DW_FUNCTION_RETURN(dw_scrollbox_get_range, int)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, orient, int)
{
    DWScrollBox *scrollbox = handle;
    NSArray *subviews = [scrollbox subviews];
    UIView *view = [subviews firstObject];
    int range = 0;
    if(orient == DW_VERT)
    {
        range = [view bounds].size.height;
    }
    else
    {
        range = [view bounds].size.width;
    }
    DW_FUNCTION_RETURN_THIS(range);
}

/* Return the handle to the text object */
id _dw_text_handle(id object)
{
    if([object isMemberOfClass:[DWButton class]])
    {
        DWButton *button = object;
        object = [button titleLabel];
    }
    else if([object isMemberOfClass:[DWSpinButton class]])
    {
        DWSpinButton *spinbutton = object;
        object = [spinbutton textfield];
    }
#if 0 /* TODO: Fix this when we have a groupbox implemented */
    if([object isMemberOfClass:[NSBox class]])
    {
        NSBox *box = object;
        id content = [box contentView];

        if([content isMemberOfClass:[DWText class]])
        {
            object = content;
        }
    }
#endif
    return object;
}

/* Internal function to calculate the widget's required size..
 * These are the general rules for widget sizes:
 *
 * Render/Unspecified: 1x1
 * Scrolled(Container,Tree,MLE): Guessed size clamped to min and max in dw.h
 * Entryfield/Combobox/Spinbutton: 150x(maxfontheight)
 * Spinbutton: 50x(maxfontheight)
 * Text/Status: (textwidth)x(textheight)
 * Ranged: 100x14 or 14x100 for vertical.
 * Buttons/Bitmaps: Size of text or image and border.
*/
void _dw_control_size(id handle, int *width, int *height)
{
    int thiswidth = 1, thisheight = 1, extrawidth = 0, extraheight = 0;
    NSString *nsstr = nil;
    id object = _dw_text_handle(handle);

    /* Handle all the different button types */
    if([handle isMemberOfClass:[DWButton class]])
    {
        UIImage *image = [handle imageForState:UIControlStateNormal];

        if(image)
        {
            /* Image button */
            CGSize size = [image size];
            extrawidth = (int)size.width + 1;
            /* Height isn't additive */
            thisheight = (int)size.height + 1;
        }
        /* Text button */
        nsstr = [[handle titleLabel] text];

        if(nsstr && [nsstr length] > 0)
        {
            /* With combined text/image we seem to
             * need a lot of additional width space.
             */
            if(image)
                extrawidth += 30;
            else
                extrawidth += 8;
            extraheight += 4;
        }
    }
    /* If the control is an entryfield set width to 150 */
    else if([object isKindOfClass:[UITextField class]])
    {
        UIFont *font = [object font];

        if([object isEditable])
        {
            /* Spinbutton text doesn't need to be as wide */
            if([handle isMemberOfClass:[DWSpinButton class]])
                thiswidth = 50;
            else
                thiswidth = 150;
        }
        nsstr = [object text];

        if(font)
            thisheight = (int)[font lineHeight];

        /* Spinbuttons need some extra */
        if([handle isMemberOfClass:[DWSpinButton class]])
        {
            DWSpinButton *spinbutton = handle;
            CGSize size = [[spinbutton stepper] intrinsicContentSize];
            
            /* Add the stepper width as extra... */
            extrawidth = size.width;
            /* The height should be the bigger of the two */
            if(size.height > thisheight)
                thisheight = size.height;
        }
    }
    /* Handle the ranged widgets */
    else if([object isMemberOfClass:[DWPercent class]] ||
            [object isMemberOfClass:[DWSlider class]])
    {
        if([object isMemberOfClass:[DWSlider class]] && [object vertical])
        {
            thiswidth = 25;
            thisheight = 100;
        }
        else
        {
            thiswidth = 100;
            thisheight = 25;
        }
    }
    /* Handle bitmap size */
    else if([object isMemberOfClass:[UIImageView class]])
    {
        UIImage *image = (UIImage *)[object image];

        if(image)
        {
            CGSize size = [image size];
            thiswidth = (int)size.width;
            thisheight = (int)size.height;
        }
    }
    /* Handle calendar */
    else if([object isMemberOfClass:[DWCalendar class]])
    {
        DWCalendar *calendar = object;
        CGSize size = [calendar intrinsicContentSize];

        /* If we can't detect the size... */
        if(size.width < 1 || size.height < 1)
        {
            if(DWOSMajor >= 14)
            {
                /* Bigger new style in ios 14 */
                thiswidth = 200;
                thisheight = 200;
            }
            else
            {
                /* Smaller spinner style in iOS 13 and earlier */
                thiswidth = 200;
                thisheight = 100;
            }
        }
        else
        {
            thiswidth = size.width;
            thisheight = size.height;
        }
    }
    /* MLE and Container */
    else if([object isMemberOfClass:[DWMLE class]] ||
            [object isMemberOfClass:[DWContainer class]])
    {
        CGSize size;

        if([object isMemberOfClass:[DWMLE class]])
            size = [object contentSize];
        else
            size = [object getsize];

        thiswidth = size.width;
        thisheight = size.height;

        if(thiswidth < _DW_SCROLLED_MIN_WIDTH)
            thiswidth = _DW_SCROLLED_MIN_WIDTH;
        if(thiswidth > _DW_SCROLLED_MAX_WIDTH)
            thiswidth = _DW_SCROLLED_MAX_WIDTH;
        if(thisheight < _DW_SCROLLED_MIN_HEIGHT)
            thisheight = _DW_SCROLLED_MIN_HEIGHT;
        if(thisheight > _DW_SCROLLED_MAX_HEIGHT)
            thisheight = _DW_SCROLLED_MAX_HEIGHT;
    }
    else if([object isKindOfClass:[UILabel class]])
    {
        nsstr = [object text];
        extrawidth = extraheight = 2;
    }
#ifdef DW_INCLUDE_DEPRECATED
    /* Any other control type */
    else if([object isKindOfClass:[UIControl class]])
        nsstr = [object text];
#endif

    /* If we have a string...
     * calculate the size with the current font.
     */
    if(nsstr)
    {
        int textwidth, textheight;

        /* If we have an empty string, use "gT" to get the most height for the font */
        dw_font_text_extents_get(object, NULL, [nsstr length] ? (char *)[nsstr UTF8String] : "gT", &textwidth, &textheight);

        if(textheight > thisheight)
            thisheight = textheight;
        if(textwidth > thiswidth)
            thiswidth = textwidth;
    }

    /* Set the requested sizes */
    if(width)
        *width = thiswidth + extrawidth;
    if(height)
        *height = thisheight + extraheight;
}

/* Internal box packing function called by the other 3 functions */
void _dw_box_pack(HWND box, HWND item, int index, int width, int height, int hsize, int vsize, int pad, char *funcname)
{
    id object = box;
    DWBox *view = box;
    DWBox *this = item;
    Box *thisbox;
    int z, x = 0;
    Item *tmpitem, *thisitem;

    /*
     * If you try and pack an item into itself VERY bad things can happen; like at least an
     * infinite loop on GTK! Lets be safe!
     */
    if(box == item)
    {
        dw_messagebox(funcname, DW_MB_OK|DW_MB_ERROR, "Danger! Danger! Will Robinson; box and item are the same!");
        return;
    }

    /* Query the objects */
    if([object isKindOfClass:[UIWindow class]])
    {
        UIWindow *window = box;
        NSArray *subviews = [[[window rootViewController] view] subviews];
        view = [subviews firstObject];
    }
    else if([object isMemberOfClass:[DWScrollBox class]])
    {
        DWScrollBox *scrollbox = box;
        view = [scrollbox box];
    }

    thisbox = [view box];
    thisitem = thisbox->items;
    object = item;

    /* Do some sanity bounds checking */
    if(!thisitem)
        thisbox->count = 0;
    if(index < 0)
        index = 0;
    if(index > thisbox->count)
        index = thisbox->count;

    /* Duplicate the existing data */
    tmpitem = calloc(sizeof(Item), (thisbox->count+1));

    for(z=0;z<thisbox->count;z++)
    {
        if(z == index)
            x++;
        tmpitem[x] = thisitem[z];
        x++;
    }

    /* Sanity checks */
    if(vsize && !height)
       height = 1;
    if(hsize && !width)
       width = 1;

    /* Fill in the item data appropriately */
    if([object isKindOfClass:[DWBox class]])
       tmpitem[index].type = _DW_TYPE_BOX;
    else
    {
        if(width == 0 && hsize == FALSE)
            dw_messagebox(funcname, DW_MB_OK|DW_MB_ERROR, "Width and expand Horizonal both unset for box: %x item: %x",box,item);
        if(height == 0 && vsize == FALSE)
            dw_messagebox(funcname, DW_MB_OK|DW_MB_ERROR, "Height and expand Vertical both unset for box: %x item: %x",box,item);

        tmpitem[index].type = _DW_TYPE_ITEM;
    }

    tmpitem[index].hwnd = item;
    tmpitem[index].origwidth = tmpitem[index].width = width;
    tmpitem[index].origheight = tmpitem[index].height = height;
    tmpitem[index].pad = pad;
    tmpitem[index].hsize = hsize ? _DW_SIZE_EXPAND : _DW_SIZE_STATIC;
    tmpitem[index].vsize = vsize ? _DW_SIZE_EXPAND : _DW_SIZE_STATIC;

    /* If either of the parameters are -1 (DW_SIZE_AUTO) ... calculate the size */
    if(width == DW_SIZE_AUTO || height == DW_SIZE_AUTO)
        _dw_control_size(object, width == DW_SIZE_AUTO ? &tmpitem[index].width : NULL, height == DW_SIZE_AUTO ? &tmpitem[index].height : NULL);

    thisbox->items = tmpitem;

    /* Update the item count */
    thisbox->count++;

    /* Add the item to the box */
    [view addSubview:this];
    [this release];

    /* If we are packing a button... */
    if([this isMemberOfClass:[DWButton class]])
    {
        DWButton *button = (DWButton *)this;

        /* Save the parent box so radio
         * buttons can use it later.
         */
        [button setParent:view];
    }
    /* Queue a redraw on the top-level window */
    _dw_redraw([view window], TRUE);

    /* Free the old data */
    if(thisitem)
       free(thisitem);
}

/*
 * Remove windows (widgets) from the box they are packed into.
 * Parameters:
 *       handle: Window handle of the packed item to be removed.
 * Returns:
 *       DW_ERROR_NONE on success and DW_ERROR_GENERAL on failure.
 */
DW_FUNCTION_DEFINITION(dw_box_unpack, int, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_RETURN(dw_box_unpack, int)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DW_FUNCTION_INIT;
    DW_LOCAL_POOL_IN;
    id object = handle;
    int retval = DW_ERROR_NONE;

    if([object isKindOfClass:[UIView class]] || [object isKindOfClass:[UIControl class]])
    {
        DWBox *parent = (DWBox *)[object superview];

        if([parent isKindOfClass:[DWBox class]])
        {
            id window = [object window];
            Box *thisbox = [parent box];
            int z, index = -1;
            Item *tmpitem = NULL, *thisitem = thisbox->items;

            if(!thisitem)
                thisbox->count = 0;

            for(z=0;z<thisbox->count;z++)
            {
                if(thisitem[z].hwnd == object)
                    index = z;
            }

            if(index == -1)
                retval = DW_ERROR_GENERAL;
            else
            {
                [object retain];
                [object removeFromSuperview];

                if(thisbox->count > 1)
                {
                    tmpitem = calloc(sizeof(Item), (thisbox->count-1));

                    /* Copy all but the current entry to the new list */
                    for(z=0;z<index;z++)
                    {
                        tmpitem[z] = thisitem[z];
                    }
                    for(z=index+1;z<thisbox->count;z++)
                    {
                        tmpitem[z-1] = thisitem[z];
                    }
                }

                thisbox->items = tmpitem;
                if(thisitem)
                    free(thisitem);
                if(tmpitem)
                    thisbox->count--;
                else
                    thisbox->count = 0;
                /* Queue a redraw on the top-level window */
                _dw_redraw(window, TRUE);
            }
        }
    }
    DW_LOCAL_POOL_OUT;
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Remove windows (widgets) from a box at an arbitrary location.
 * Parameters:
 *       box: Window handle of the box to be removed from.
 *       index: 0 based index of packed items.
 * Returns:
 *       Handle to the removed item on success, 0 on failure or padding.
 */
DW_FUNCTION_DEFINITION(dw_box_unpack_at_index, HWND, HWND box, int index)
DW_FUNCTION_ADD_PARAM2(box, index)
DW_FUNCTION_RETURN(dw_box_unpack_at_index, HWND)
DW_FUNCTION_RESTORE_PARAM2(box, HWND, index, int)
{
    DW_FUNCTION_INIT;
    DW_LOCAL_POOL_IN;
    DWBox *parent = (DWBox *)box;
    id object = nil;

    if([parent isKindOfClass:[DWBox class]])
    {
        id window = [parent window];
        Box *thisbox = [parent box];

        if(thisbox && index > -1 && index < thisbox->count)
        {
            int z;
            Item *tmpitem = NULL, *thisitem = thisbox->items;

            object = thisitem[index].hwnd;

            if(object)
            {
                [object retain];
                [object removeFromSuperview];
            }

            if(thisbox->count > 1)
            {
                tmpitem = calloc(sizeof(Item), (thisbox->count-1));

                /* Copy all but the current entry to the new list */
                for(z=0;thisitem && z<index;z++)
                {
                    tmpitem[z] = thisitem[z];
                }
                for(z=index+1;z<thisbox->count;z++)
                {
                    tmpitem[z-1] = thisitem[z];
                }
            }

            thisbox->items = tmpitem;
            if(thisitem)
                free(thisitem);
            if(tmpitem)
                thisbox->count--;
            else
                thisbox->count = 0;

            /* Queue a redraw on the top-level window */
            _dw_redraw(window, TRUE);
        }
    }
    DW_LOCAL_POOL_OUT;
    DW_FUNCTION_RETURN_THIS(object);
}

/*
 * Pack windows (widgets) into a box at an arbitrary location.
 * Parameters:
 *       box: Window handle of the box to be packed into.
 *       item: Window handle of the item to pack.
 *       index: 0 based index of packed items.
 *       width: Width in pixels of the item or -1 to be self determined.
 *       height: Height in pixels of the item or -1 to be self determined.
 *       hsize: TRUE if the window (widget) should expand horizontally to fill space given.
 *       vsize: TRUE if the window (widget) should expand vertically to fill space given.
 *       pad: Number of pixels of padding around the item.
 */
DW_FUNCTION_DEFINITION(dw_box_pack_at_index, void, HWND box, HWND item, int index, int width, int height, int hsize, int vsize, int pad)
DW_FUNCTION_ADD_PARAM8(box, item, index, width, height, hsize, vsize, pad)
DW_FUNCTION_NO_RETURN(dw_box_pack_at_index)
DW_FUNCTION_RESTORE_PARAM8(box, HWND, item, HWND, index, int, width, int, height, int, hsize, int, vsize, int, pad, int)
{
    _dw_box_pack(box, item, index, width, height, hsize, vsize, pad, "dw_box_pack_at_index()");
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Pack windows (widgets) into a box from the start (or top).
 * Parameters:
 *       box: Window handle of the box to be packed into.
 *       item: Window handle of the item to pack.
 *       width: Width in pixels of the item or -1 to be self determined.
 *       height: Height in pixels of the item or -1 to be self determined.
 *       hsize: TRUE if the window (widget) should expand horizontally to fill space given.
 *       vsize: TRUE if the window (widget) should expand vertically to fill space given.
 *       pad: Number of pixels of padding around the item.
 */
DW_FUNCTION_DEFINITION(dw_box_pack_start, void, HWND box, HWND item, int width, int height, int hsize, int vsize, int pad)
DW_FUNCTION_ADD_PARAM7(box, item, width, height, hsize, vsize, pad)
DW_FUNCTION_NO_RETURN(dw_box_pack_start)
DW_FUNCTION_RESTORE_PARAM7(box, HWND, item, HWND, width, int, height, int, hsize, int, vsize, int, pad, int)
{
    /* 65536 is the table limit on GTK...
     * seems like a high enough value we will never hit it here either.
     */
    _dw_box_pack(box, item, 65536, width, height, hsize, vsize, pad, "dw_box_pack_start()");
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Pack windows (widgets) into a box from the end (or bottom).
 * Parameters:
 *       box: Window handle of the box to be packed into.
 *       item: Window handle of the item to pack.
 *       width: Width in pixels of the item or -1 to be self determined.
 *       height: Height in pixels of the item or -1 to be self determined.
 *       hsize: TRUE if the window (widget) should expand horizontally to fill space given.
 *       vsize: TRUE if the window (widget) should expand vertically to fill space given.
 *       pad: Number of pixels of padding around the item.
 */
DW_FUNCTION_DEFINITION(dw_box_pack_end, void, HWND box, HWND item, int width, int height, int hsize, int vsize, int pad)
DW_FUNCTION_ADD_PARAM7(box, item, width, height, hsize, vsize, pad)
DW_FUNCTION_NO_RETURN(dw_box_pack_end)
DW_FUNCTION_RESTORE_PARAM7(box, HWND, item, HWND, width, int, height, int, hsize, int, vsize, int, pad, int)
{
    _dw_box_pack(box, item, 0, width, height, hsize, vsize, pad, "dw_box_pack_end()");
    DW_FUNCTION_RETURN_NOTHING;
}

/* Internal function to create a basic button, used by all button types */
HWND _dw_internal_button_new(const char *text, ULONG cid, UIButtonType type)
{
    DWButton *button = [[DWButton buttonWithType:type] retain];
    if(text)
    {
        [button setTitle:[NSString stringWithUTF8String:text] forState:UIControlStateNormal];
    }
    [button addTarget:button
               action:@selector(buttonClicked:)
     forControlEvents:UIControlEventTouchUpInside];
    [button setTag:cid];
    if(DWDefaultFont)
    {
        [[button titleLabel] setFont:DWDefaultFont];
    }
    return button;
}

/*
 * Create a new button window (widget) to be packed.
 * Parameters:
 *       text: The text to be display by the static text widget.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_button_new, HWND, const char *text, ULONG cid)
DW_FUNCTION_ADD_PARAM2(text, cid)
DW_FUNCTION_RETURN(dw_button_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(text, const char *, cid, ULONG)
{
    DWButton *button = _dw_internal_button_new(text, cid, UIButtonTypeSystem);
    [button setContentHorizontalAlignment:UIControlContentHorizontalAlignmentCenter];
    DW_FUNCTION_RETURN_THIS(button);
}

/*
 * Create a new Entryfield window (widget) to be packed.
 * Parameters:
 *       text: The default text to be in the entryfield widget.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_entryfield_new, HWND, const char *text, ULONG cid)
DW_FUNCTION_ADD_PARAM2(text, cid)
DW_FUNCTION_RETURN(dw_entryfield_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(text, const char *, cid, ULONG)
{
    DWEntryField *entry = [[[DWEntryField alloc] init] retain];
    [entry setText:[ NSString stringWithUTF8String:text ]];
    [entry setTag:cid];
    [entry setDelegate:entry];
    DW_FUNCTION_RETURN_THIS(entry);
}

/*
 * Create a new Entryfield (password) window (widget) to be packed.
 * Parameters:
 *       text: The default text to be in the entryfield widget.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_entryfield_password_new, HWND, const char *text, ULONG cid)
DW_FUNCTION_ADD_PARAM2(text, cid)
DW_FUNCTION_RETURN(dw_entryfield_password_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(text, const char *, cid, ULONG)
{
    DWEntryField *entry = dw_entryfield_new(text, cid);
    [entry setSecureTextEntry:YES];
    [entry setDelegate:entry];
    DW_FUNCTION_RETURN_THIS(entry);
}

/*
 * Sets the entryfield character limit.
 * Parameters:
 *          handle: Handle to the spinbutton to be set.
 *          limit: Number of characters the entryfield will take.
 */
DW_FUNCTION_DEFINITION(dw_entryfield_set_limit, void, HWND handle, unsigned long limit)
DW_FUNCTION_ADD_PARAM2(handle, limit)
DW_FUNCTION_NO_RETURN(dw_entryfield_set_limit)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, limit, unsigned long)
{
    DWEntryField *entry = handle;

    [entry setMaximumLength:limit];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Create a new bitmap button window (widget) to be packed.
 * Parameters:
 *       text: Bubble help text to be displayed.
 *       id: An ID of a bitmap in the resource file.
 */
DW_FUNCTION_DEFINITION(dw_bitmapbutton_new, HWND, DW_UNUSED(const char *text), ULONG cid)
DW_FUNCTION_ADD_PARAM2(text, cid)
DW_FUNCTION_RETURN(dw_bitmapbutton_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(DW_UNUSED(text), const char *, cid, ULONG)
{
    NSBundle *bundle = [NSBundle mainBundle];
    NSString *respath = [bundle resourcePath];
    NSString *filepath = [respath stringByAppendingFormat:@"/%lu.png", cid];
    UIImage *image = [[UIImage alloc] initWithContentsOfFile:filepath];
    DWButton *button = _dw_internal_button_new("", cid, UIButtonTypeCustom);
    if(image)
    {
        [button setImage:image forState:UIControlStateNormal];
        [image release];
    }
    DW_FUNCTION_RETURN_THIS(button);
}

/*
 * Create a new bitmap button window (widget) to be packed from a file.
 * Parameters:
 *       text: Bubble help text to be displayed.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 *       filename: Name of the file, omit extention to have
 *                 DW pick the appropriate file extension.
 *                 (BMP on OS/2 or Windows, XPM on Unix)
 */
DW_FUNCTION_DEFINITION(dw_bitmapbutton_new_from_file, HWND, DW_UNUSED(const char *text), ULONG cid, const char *filename)
DW_FUNCTION_ADD_PARAM3(text, cid, filename)
DW_FUNCTION_RETURN(dw_bitmapbutton_new_from_file, HWND)
DW_FUNCTION_RESTORE_PARAM3(DW_UNUSED(text), const char *, cid, ULONG, filename, const char *)
{
    char *ext = _dw_get_image_extension(filename);

    NSString *nstr = [ NSString stringWithUTF8String:filename ];
    UIImage *image = [[UIImage alloc] initWithContentsOfFile:nstr];

    if(!image && ext)
    {
        nstr = [nstr stringByAppendingString: [NSString stringWithUTF8String:ext]];
        image = [[UIImage alloc] initWithContentsOfFile:nstr];
    }
    DWButton *button = _dw_internal_button_new("", cid, UIButtonTypeCustom);
    if(image)
    {
        [button setImage:image forState:UIControlStateNormal];
        [image release];
    }
    DW_FUNCTION_RETURN_THIS(button);
}

/*
 * Create a new bitmap button window (widget) to be packed from data.
 * Parameters:
 *       text: Bubble help text to be displayed.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 *       data: The contents of the image
 *            (BMP or ICO on OS/2 or Windows, XPM on Unix)
 *       len: length of str
 */
DW_FUNCTION_DEFINITION(dw_bitmapbutton_new_from_data, HWND, DW_UNUSED(const char *text), ULONG cid, const char *data, int len)
DW_FUNCTION_ADD_PARAM4(text, cid, data, len)
DW_FUNCTION_RETURN(dw_bitmapbutton_new_from_data, HWND)
DW_FUNCTION_RESTORE_PARAM4(DW_UNUSED(text), const char *, cid, ULONG, data, const char *, len, int)
{
    NSData *thisdata = [NSData dataWithBytes:data length:len];
    UIImage *image = [[UIImage alloc] initWithData:thisdata];
    DWButton *button = _dw_internal_button_new("", cid, UIButtonTypeCustom);
    if(image)
    {
        [button setImage:image forState:UIControlStateNormal];
        [image release];
    }
    DW_FUNCTION_RETURN_THIS(button);
}

/*
 * Create a new spinbutton window (widget) to be packed.
 * Parameters:
 *       text: The text to be display by the static text widget.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_spinbutton_new, HWND, const char *text, ULONG cid)
DW_FUNCTION_ADD_PARAM2(text, cid)
DW_FUNCTION_RETURN(dw_spinbutton_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(text, const char *, cid, ULONG)
{
    DWSpinButton *spinbutton = [[[DWSpinButton alloc] init] retain];
    UIStepper *stepper = [spinbutton stepper];
    UITextField *textfield = [spinbutton textfield];
    long val = atol(text);

    [stepper setStepValue:1];
    [stepper setTag:cid];
    [stepper setMinimumValue:-65536];
    [stepper setMaximumValue:65536];
    [stepper setValue:(float)val];
    [textfield setText:[NSString stringWithFormat:@"%ld",val]];
    DW_FUNCTION_RETURN_THIS(spinbutton);
}

/*
 * Sets the spinbutton value.
 * Parameters:
 *          handle: Handle to the spinbutton to be set.
 *          position: Current value of the spinbutton.
 */
DW_FUNCTION_DEFINITION(dw_spinbutton_set_pos, void, HWND handle, long position)
DW_FUNCTION_ADD_PARAM2(handle, position)
DW_FUNCTION_NO_RETURN(dw_spinbutton_set_pos)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, position, long)
{
    DWSpinButton *spinbutton = handle;
    UIStepper *stepper = [spinbutton stepper];
    UITextField *textfield = [spinbutton textfield];
    [stepper setValue:(float)position];
    [textfield setText:[NSString stringWithFormat:@"%ld",position]];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the spinbutton limits.
 * Parameters:
 *          handle: Handle to the spinbutton to be set.
 *          upper: Upper limit.
 *          lower: Lower limit.
 */
DW_FUNCTION_DEFINITION(dw_spinbutton_set_limits, void, HWND handle, long upper, long lower)
DW_FUNCTION_ADD_PARAM3(handle, upper, lower)
DW_FUNCTION_NO_RETURN(dw_spinbutton_set_limits)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, upper, long, lower, long)
{
    DWSpinButton *spinbutton = handle;
    UIStepper *stepper = [spinbutton stepper];
    [stepper setMinimumValue:(double)lower];
    [stepper setMaximumValue:(double)upper];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Returns the current value of the spinbutton.
 * Parameters:
 *          handle: Handle to the spinbutton to be queried.
 */
DW_FUNCTION_DEFINITION(dw_spinbutton_get_pos, long, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_RETURN(dw_spinbutton_get_pos, long)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DWSpinButton *spinbutton = handle;
    long retval;
    UIStepper *stepper = [spinbutton stepper];
    retval = (long)[stepper value];
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Create a new radiobutton window (widget) to be packed.
 * Parameters:
 *       text: The text to be display by the static text widget.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_radiobutton_new, HWND, const char *text, ULONG cid)
DW_FUNCTION_ADD_PARAM2(text, cid)
DW_FUNCTION_RETURN(dw_radiobutton_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(text, const char *, cid, ULONG)
{
    DWButton *button = _dw_internal_button_new(text, cid, UIButtonTypeSystem);
    [button setType:_DW_BUTTON_TYPE_RADIO];
    DW_FUNCTION_RETURN_THIS(button);
}

/*
 * Create a new slider window (widget) to be packed.
 * Parameters:
 *       vertical: TRUE or FALSE if slider is vertical.
 *       increments: Number of increments available.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_slider_new, HWND, int vertical, int increments, ULONG cid)
DW_FUNCTION_ADD_PARAM3(vertical, increments, cid)
DW_FUNCTION_RETURN(dw_slider_new, HWND)
DW_FUNCTION_RESTORE_PARAM3(vertical, int, increments, int, cid, ULONG)
{
    DWSlider *slider = [[[DWSlider alloc] init] retain];
    [slider setMaximumValue:(double)increments];
    [slider setMinimumValue:0];
    [slider setContinuous:YES];
    [slider addTarget:slider
               action:@selector(sliderChanged:)
     forControlEvents:UIControlEventValueChanged];
    [slider setTag:cid];
    [slider setVertical:(vertical ? YES : NO)];
    DW_FUNCTION_RETURN_THIS(slider);
}

/*
 * Returns the position of the slider.
 * Parameters:
 *          handle: Handle to the slider to be queried.
 */
unsigned int API dw_slider_get_pos(HWND handle)
{
    DWSlider *slider = handle;
    double val = [slider value];
    return (int)val;
}

/*
 * Sets the slider position.
 * Parameters:
 *          handle: Handle to the slider to be set.
 *          position: Position of the slider withing the range.
 */
void API dw_slider_set_pos(HWND handle, unsigned int position)
{
    DWSlider *slider = handle;
    [slider setValue:(double)position];
}

/*
 * Create a new scrollbar window (widget) to be packed.
 * Parameters:
 *       vertical: TRUE or FALSE if scrollbar is vertical.
 *       increments: Number of increments available.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_scrollbar_new, HWND, int vertical, ULONG cid)
DW_FUNCTION_ADD_PARAM2(vertical, cid)
DW_FUNCTION_RETURN(dw_scrollbar_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(vertical, int, cid, ULONG)
{
    DWSlider *slider = dw_slider_new(vertical, 1, cid);
    [slider setMinimumTrackTintColor:[UIColor clearColor]];
    [slider setMaximumTrackTintColor:[UIColor clearColor]];
    DW_FUNCTION_RETURN_THIS(slider);
}

/*
 * Returns the position of the scrollbar.
 * Parameters:
 *          handle: Handle to the scrollbar to be queried.
 */
unsigned int API dw_scrollbar_get_pos(HWND handle)
{
    return dw_slider_get_pos(handle);
}

/*
 * Sets the scrollbar position.
 * Parameters:
 *          handle: Handle to the scrollbar to be set.
 *          position: Position of the scrollbar withing the range.
 */
void API dw_scrollbar_set_pos(HWND handle, unsigned int position)
{
    dw_slider_set_pos(handle, position);
}

/*
 * Sets the scrollbar range.
 * Parameters:
 *          handle: Handle to the scrollbar to be set.
 *          range: Maximum range value.
 *          visible: Visible area relative to the range.
 */
void API dw_scrollbar_set_range(HWND handle, unsigned int range, unsigned int visible)
{
    DWSlider *slider = handle;
    [slider setMaximumValue:(double)range];
}

/*
 * Create a new percent bar window (widget) to be packed.
 * Parameters:
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_percent_new, HWND, ULONG cid)
DW_FUNCTION_ADD_PARAM1(cid)
DW_FUNCTION_RETURN(dw_percent_new, HWND)
DW_FUNCTION_RESTORE_PARAM1(cid, ULONG)
{
    DWPercent *percent = [[[DWPercent alloc] init] retain];
    [percent setTag:cid];
    DW_FUNCTION_RETURN_THIS(percent);
}

/*
 * Sets the percent bar position.
 * Parameters:
 *          handle: Handle to the percent bar to be set.
 *          position: Position of the percent bar withing the range.
 */
DW_FUNCTION_DEFINITION(dw_percent_set_pos, void, HWND handle, unsigned int position)
DW_FUNCTION_ADD_PARAM2(handle, position)
DW_FUNCTION_NO_RETURN(dw_percent_set_pos)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, position, unsigned int)
{
    DW_FUNCTION_INIT;
    DWPercent *percent = handle;

    /* Handle indeterminate */
    if(position == DW_PERCENT_INDETERMINATE)
    {
        [percent setProgress:0 animated:NO];
    }
    else
    {
        /* Handle normal */
        [percent setProgress:(float)position/100.0 animated:YES];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Create a new checkbox window (widget) to be packed.
 * Parameters:
 *       text: The text to be display by the static text widget.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_checkbox_new, HWND, const char *text, ULONG cid)
DW_FUNCTION_ADD_PARAM2(text, cid)
DW_FUNCTION_RETURN(dw_checkbox_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(text, const char *, cid, ULONG)
{
    DWButton *button = _dw_internal_button_new(text, cid, UIButtonTypeSystem);
    [button setType:_DW_BUTTON_TYPE_CHECK];
    DW_FUNCTION_RETURN_THIS(button);
}

/*
 * Returns the state of the checkbox.
 * Parameters:
 *          handle: Handle to the checkbox to be queried.
 */
DW_FUNCTION_DEFINITION(dw_checkbox_get, int, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_RETURN(dw_checkbox_get, int)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DWButton *button = handle;
    int retval = FALSE;

    if([button checkState])
        retval = TRUE;
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Sets the state of the checkbox.
 * Parameters:
 *          handle: Handle to the checkbox to be queried.
 *          value: TRUE for checked, FALSE for unchecked.
 */
DW_FUNCTION_DEFINITION(dw_checkbox_set, void, HWND handle, int value)
DW_FUNCTION_ADD_PARAM2(handle, value)
DW_FUNCTION_NO_RETURN(dw_checkbox_set)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, value, int)
{
    DWButton *button = handle;
    [button setCheckState:value];
    DW_FUNCTION_RETURN_NOTHING;
}

/* Internal common function to create containers and listboxes */
HWND _dw_cont_new(ULONG cid, int multi)
{
    DWContainer *cont = [[[DWContainer alloc] init] retain];

    [cont setAllowsMultipleSelection:(multi ? YES : NO)];
    [cont setDataSource:cont];
    [cont setDelegate:cont];
    [cont setTag:cid];
    [cont setRowHeight:UITableViewAutomaticDimension];
    [cont setEstimatedRowHeight:(_dw_container_mode > DW_CONTAINER_MODE_DEFAULT ? 85.0 : 44.0)];
    [cont setRowBgOdd:DW_RGB_TRANSPARENT andEven:DW_RGB_TRANSPARENT];
    return cont;
}

/*
 * Create a new listbox window (widget) to be packed.
 * Parameters:
 *       id: An ID to be used with dw_window_from_id() or 0L.
 *       multi: Multiple select TRUE or FALSE.
 */
DW_FUNCTION_DEFINITION(dw_listbox_new, HWND, ULONG cid, int multi)
DW_FUNCTION_ADD_PARAM2(cid, multi)
DW_FUNCTION_RETURN(dw_listbox_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(cid, ULONG, multi, int)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = _dw_cont_new(cid, multi);
    [cont setup];
    [cont addColumn:@"" andType:DW_CFA_STRING];
    DW_FUNCTION_RETURN_THIS(cont);
}

/*
 * Appends the specified text to the listbox's (or combobox) entry list.
 * Parameters:
 *          handle: Handle to the listbox to be appended to.
 *          text: Text to append into listbox.
 */
DW_FUNCTION_DEFINITION(dw_listbox_append, void, HWND handle, const char *text)
DW_FUNCTION_ADD_PARAM2(handle, text)
DW_FUNCTION_NO_RETURN(dw_listbox_append)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, text, char *)
{
    DW_FUNCTION_INIT;
    id object = handle;

    if([object isMemberOfClass:[DWComboBox class]])
    {
        DWComboBox *combo = handle;

        [combo append:[NSString stringWithUTF8String:text]];
    }
    else if([object isMemberOfClass:[DWContainer class]])
    {
        DWContainer *cont = handle;
        NSString *nstr = [NSString stringWithUTF8String:text];

        [cont addRow:_dw_table_cell_view_new(nil, nstr, nil)];
        [cont reloadData];
        [cont setNeedsDisplay];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Inserts the specified text into the listbox's (or combobox) entry list.
 * Parameters:
 *          handle: Handle to the listbox to be inserted into.
 *          text: Text to insert into listbox.
 *          pos: 0-based position to insert text
 */
DW_FUNCTION_DEFINITION(dw_listbox_insert, void, HWND handle, const char *text, int pos)
DW_FUNCTION_ADD_PARAM3(handle, text, pos)
DW_FUNCTION_NO_RETURN(dw_listbox_insert)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, text, const char *, pos, int)
{
    DW_FUNCTION_INIT;
    id object = handle;

    if([object isMemberOfClass:[DWComboBox class]])
    {
        DWComboBox *combo = handle;

        [combo insert:[NSString stringWithUTF8String:text] atIndex:pos];
    }
    else if([object isMemberOfClass:[DWContainer class]])
    {
        DWContainer *cont = handle;
        NSString *nstr = [NSString stringWithUTF8String:text];

        [cont insertRow:_dw_table_cell_view_new(nil, nstr, nil) at:pos];
        [cont reloadData];
        [cont setNeedsDisplay];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Appends the specified text items to the listbox's (or combobox) entry list.
 * Parameters:
 *          handle: Handle to the listbox to be appended to.
 *          text: Text strings to append into listbox.
 *          count: Number of text strings to append
 */
DW_FUNCTION_DEFINITION(dw_listbox_list_append, void, HWND handle, char **text, int count)
DW_FUNCTION_ADD_PARAM3(handle, text, count)
DW_FUNCTION_NO_RETURN(dw_listbox_list_append)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, text, char **, count, int)
{
    DW_FUNCTION_INIT;
    id object = handle;

    if([object isMemberOfClass:[DWComboBox class]])
    {
        DWComboBox *combo = handle;
        int z;

        for(z=0;z<count;z++)
        {
            NSString *nstr = [NSString stringWithUTF8String:text[z]];

            [combo append:nstr];
        }
    }
    else if([object isMemberOfClass:[DWContainer class]])
    {
        DWContainer *cont = handle;
        int z;

        for(z=0;z<count;z++)
        {
            NSString *nstr = [NSString stringWithUTF8String:text[z]];

            [cont addRow:_dw_table_cell_view_new(nil, nstr, nil)];
        }
        [cont reloadData];
        [cont setNeedsDisplay];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Clears the listbox's (or combobox) list of all entries.
 * Parameters:
 *          handle: Handle to the listbox to be cleared.
 */
DW_FUNCTION_DEFINITION(dw_listbox_clear, void, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_NO_RETURN(dw_listbox_clear)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DW_FUNCTION_INIT;
    id object = handle;

    if([object isMemberOfClass:[DWComboBox class]])
    {
        DWComboBox *combo = handle;

        [combo clear];
    }
    if([object isMemberOfClass:[DWContainer class]])
    {
        DWContainer *cont = handle;

        [cont clear];
        [cont reloadData];
        [cont setNeedsDisplay];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Returns the listbox's item count.
 * Parameters:
 *          handle: Handle to the listbox to be cleared.
 */
DW_FUNCTION_DEFINITION(dw_listbox_count, int, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_RETURN(dw_listbox_count, int)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DW_FUNCTION_INIT;
    id object = handle;
    int result = 0;

    if([object isMemberOfClass:[DWComboBox class]])
    {
        DWComboBox *combo = handle;

        result = [combo count];
    }
    else if([object isMemberOfClass:[DWContainer class]])
    {
        DWContainer *cont = handle;
        result = (int)[cont numberOfRowsInSection:0];
    }
    DW_FUNCTION_RETURN_THIS(result);
}

/*
 * Sets the topmost item in the viewport.
 * Parameters:
 *          handle: Handle to the listbox to be cleared.
 *          top: Index to the top item.
 */
DW_FUNCTION_DEFINITION(dw_listbox_set_top, void, HWND handle, int top)
DW_FUNCTION_ADD_PARAM2(handle, top)
DW_FUNCTION_NO_RETURN(dw_listbox_set_top)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, top, int)
{
    DW_FUNCTION_INIT;
    id object = handle;

    if([object isMemberOfClass:[DWContainer class]])
    {
        DWContainer *cont = handle;
        NSIndexPath *myIP = [NSIndexPath indexPathForRow:top inSection:0];

        [cont scrollToRowAtIndexPath:myIP atScrollPosition:UITableViewScrollPositionNone animated:NO];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Copies the given index item's text into buffer.
 * Parameters:
 *          handle: Handle to the listbox to be queried.
 *          index: Index into the list to be queried.
 *          buffer: Buffer where text will be copied.
 *          length: Length of the buffer (including NULL).
 */
DW_FUNCTION_DEFINITION(dw_listbox_get_text, void, HWND handle, unsigned int index, char *buffer, unsigned int length)
DW_FUNCTION_ADD_PARAM4(handle, index, buffer, length)
DW_FUNCTION_NO_RETURN(dw_listbox_get_text)
DW_FUNCTION_RESTORE_PARAM4(handle, HWND, index, unsigned int, buffer, char *, length, unsigned int)
{
    DW_FUNCTION_INIT;
    id object = handle;

    if([object isMemberOfClass:[DWComboBox class]])
    {
        DWComboBox *combo = handle;
        NSString *nstr = [combo getTextAtIndex:index];

        if(nstr)
            strncpy(buffer, [nstr UTF8String], length - 1);
        else
            *buffer = '\0';
    }
    else if([object isMemberOfClass:[DWContainer class]])
    {
        DWContainer *cont = handle;
        int count = (int)[cont numberOfRowsInSection:0];

        if(index > count)
        {
            *buffer = '\0';
        }
        else
        {
            DWTableViewCell *cell = [cont getRow:index];
            NSString *nstr = [[cell label] text];

            strncpy(buffer, [nstr UTF8String], length - 1);
        }
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the text of a given listbox entry.
 * Parameters:
 *          handle: Handle to the listbox to be queried.
 *          index: Index into the list to be queried.
 *          buffer: Buffer where text will be copied.
 */
DW_FUNCTION_DEFINITION(dw_listbox_set_text, void, HWND handle, unsigned int index, const char *buffer)
DW_FUNCTION_ADD_PARAM3(handle, index, buffer)
DW_FUNCTION_NO_RETURN(dw_listbox_set_text)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, index, unsigned int, buffer, char *)
{
    DW_FUNCTION_INIT;
    id object = handle;

    if([object isMemberOfClass:[DWComboBox class]])
    {
        DWComboBox *combo = handle;

        [combo setText:[NSString stringWithUTF8String:buffer] atIndex:index];
    }
    else if([object isMemberOfClass:[DWContainer class]])
    {
        DWContainer *cont = handle;
        int count = (int)[cont numberOfRowsInSection:0];

        if(index <= count)
        {
            NSString *nstr = [NSString stringWithUTF8String:buffer];
            DWTableViewCell *cell = [cont getRow:index];

            [[cell label] setText:nstr];
            [cont reloadData];
            [cont setNeedsDisplay];
        }
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Returns the index to the item in the list currently selected.
 * Parameters:
 *          handle: Handle to the listbox to be queried.
 */
DW_FUNCTION_DEFINITION(dw_listbox_selected, int, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_RETURN(dw_listbox_selected, int)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DW_FUNCTION_INIT;
    id object = handle;
    int result = -1;

    if([object isMemberOfClass:[DWComboBox class]])
    {
        DWComboBox *combo = handle;

        result = [combo selectedIndex];
    }
    else if([object isMemberOfClass:[DWContainer class]])
    {
        DWContainer *cont = handle;
        NSIndexPath *ip = [cont indexPathForSelectedRow];

        if(ip)
            result = (int)ip.row;
    }
    DW_FUNCTION_RETURN_THIS(result);
}

/*
 * Returns the index to the current selected item or -1 when done.
 * Parameters:
 *          handle: Handle to the listbox to be queried.
 *          where: Either the previous return or -1 to restart.
 */
DW_FUNCTION_DEFINITION(dw_listbox_selected_multi, int, HWND handle, int where)
DW_FUNCTION_ADD_PARAM2(handle, where)
DW_FUNCTION_RETURN(dw_listbox_selected_multi, int)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, where, int)
{
    DW_FUNCTION_INIT;
    id object = handle;
    int retval = -1;

    if([object isMemberOfClass:[DWContainer class]])
    {
        DWContainer *cont = handle;
        NSArray *selected = [cont indexPathsForSelectedRows];
        NSIndexPath *ip = [selected objectAtIndex:(where == -1 ? 0 :where)];

        if(ip)
            retval = (int)ip.row;
    }
    DW_FUNCTION_RETURN_THIS(retval)
}

/*
 * Sets the selection state of a given index.
 * Parameters:
 *          handle: Handle to the listbox to be set.
 *          index: Item index.
 *          state: TRUE if selected FALSE if unselected.
 */
DW_FUNCTION_DEFINITION(dw_listbox_select, void, HWND handle, int index, DW_UNUSED(int state))
DW_FUNCTION_ADD_PARAM3(handle, index, state)
DW_FUNCTION_NO_RETURN(dw_listbox_select)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, index, int, DW_UNUSED(state), int)
{
    DW_FUNCTION_INIT;
    id object = handle;

    if([object isMemberOfClass:[DWContainer class]])
    {
        DWContainer *cont = handle;
        NSIndexPath *ip = [NSIndexPath indexPathForRow:index inSection:0];
        
        [cont selectRowAtIndexPath:ip
                          animated:NO
                    scrollPosition:UITableViewScrollPositionNone];
    }
    else if([object isMemberOfClass:[DWComboBox class]])
    {
        DWComboBox *combo = handle;
        [combo selectIndex:index];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Deletes the item with given index from the list.
 * Parameters:
 *          handle: Handle to the listbox to be set.
 *          index: Item index.
 */
DW_FUNCTION_DEFINITION(dw_listbox_delete, void, HWND handle, int index)
DW_FUNCTION_ADD_PARAM2(handle, index)
DW_FUNCTION_NO_RETURN(dw_listbox_delete)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, index, int)
{
    DW_FUNCTION_INIT;
    id object = handle;

    if([object isMemberOfClass:[DWComboBox class]])
    {
        DWComboBox *combo = handle;

        [combo deleteAtIndex:index];
    }
    else if([object isMemberOfClass:[DWContainer class]])
    {
        DWContainer *cont = handle;

        [cont removeRow:index];
        [cont reloadData];
        [cont setNeedsDisplay];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Create a new Combobox window (widget) to be packed.
 * Parameters:
 *       text: The default text to be in the combpbox widget.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_combobox_new, HWND, const char *text, ULONG cid)
DW_FUNCTION_ADD_PARAM2(text, cid)
DW_FUNCTION_RETURN(dw_combobox_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(text, const char *, cid, ULONG)
{
    DWComboBox *combo = [[[DWComboBox alloc] init] retain];
    if(text)
        [combo setText:[NSString stringWithUTF8String:text]];
    [combo setTag:cid];
    DW_FUNCTION_RETURN_THIS(combo);
}

/*
 * Create a new Multiline Editbox window (widget) to be packed.
 * Parameters:
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_mle_new, HWND, ULONG cid)
DW_FUNCTION_ADD_PARAM1(cid)
DW_FUNCTION_RETURN(dw_mle_new, HWND)
DW_FUNCTION_RESTORE_PARAM1(cid, ULONG)
{
    CGRect frame = CGRectMake(0, 0, 100, 50);
    NSTextContainer *tc = [[NSTextContainer alloc] initWithSize:frame.size];
    NSLayoutManager *lm = [[NSLayoutManager alloc] init];
    NSTextStorage *ts = [[NSTextStorage alloc] init];
    [lm addTextContainer:tc];
    [ts addLayoutManager:lm];
    DWMLE *mle = [[[DWMLE alloc] initWithFrame:frame textContainer:tc] retain];
    CGSize size = [mle intrinsicContentSize];

    size.width = size.height;
    [mle setAutoresizingMask:UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth];
    [mle setScrollEnabled:YES];
    [mle setTag:cid];
    DW_FUNCTION_RETURN_THIS(mle);
}

/*
 * Adds text to an MLE box and returns the current point.
 * Parameters:
 *          handle: Handle to the MLE to be queried.
 *          buffer: Text buffer to be imported.
 *          startpoint: Point to start entering text.
 */
DW_FUNCTION_DEFINITION(dw_mle_import, unsigned int, HWND handle, const char *buffer, int startpoint)
DW_FUNCTION_ADD_PARAM3(handle, buffer, startpoint)
DW_FUNCTION_RETURN(dw_mle_import, unsigned int)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, buffer, const char *, startpoint, int)
{
    DW_FUNCTION_INIT;
    DWMLE *mle = handle;
    unsigned int retval;
    NSTextStorage *ts = [mle textStorage];
    NSString *nstr = [NSString stringWithUTF8String:buffer];
    UIColor *fgcolor = [mle textColor];
    UIFont *font = [mle font];
    NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
    [attributes setObject:(fgcolor ? fgcolor : [UIColor labelColor]) forKey:NSForegroundColorAttributeName];
    if(font)
        [attributes setObject:font forKey:NSFontAttributeName];
    NSAttributedString *nastr = [[NSAttributedString alloc] initWithString:nstr attributes:attributes];
    NSUInteger length = [ts length];
    if(startpoint < 0)
        startpoint = 0;
    if(startpoint > length)
        startpoint = (int)length;
    [ts insertAttributedString:nastr atIndex:startpoint];
    retval = (unsigned int)strlen(buffer) + startpoint;
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Grabs text from an MLE box.
 * Parameters:
 *          handle: Handle to the MLE to be queried.
 *          buffer: Text buffer to be exported.
 *          startpoint: Point to start grabbing text.
 *          length: Amount of text to be grabbed.
 */
DW_FUNCTION_DEFINITION(dw_mle_export, void, HWND handle, char *buffer, int startpoint, int length)
DW_FUNCTION_ADD_PARAM4(handle, buffer, startpoint, length)
DW_FUNCTION_NO_RETURN(dw_mle_export)
DW_FUNCTION_RESTORE_PARAM4(handle, HWND, buffer, char *, startpoint, int, length, int)
{
    DW_FUNCTION_INIT;
    DWMLE *mle = handle;
    NSTextStorage *ts = [mle textStorage];
    NSMutableString *ms = [ts mutableString];
    const char *tmp = [ms UTF8String];
    strncpy(buffer, tmp+startpoint, length);
    buffer[length] = '\0';
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Obtains information about an MLE box.
 * Parameters:
 *          handle: Handle to the MLE to be queried.
 *          bytes: A pointer to a variable to return the total bytes.
 *          lines: A pointer to a variable to return the number of lines.
 */
DW_FUNCTION_DEFINITION(dw_mle_get_size, void, HWND handle, unsigned long *bytes, unsigned long *lines)
DW_FUNCTION_ADD_PARAM3(handle, bytes, lines)
DW_FUNCTION_NO_RETURN(dw_mle_get_size)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, bytes, unsigned long *, lines, unsigned long *)
{
    DW_FUNCTION_INIT;
    DWMLE *mle = handle;
    NSTextStorage *ts = [mle textStorage];
    NSMutableString *ms = [ts mutableString];
    NSUInteger numberOfLines, index, stringLength = [ms length];

    if(bytes)
        *bytes = stringLength;
    if(lines)
    {
        for(index=0, numberOfLines=0; index < stringLength; numberOfLines++)
            index = NSMaxRange([ms lineRangeForRange:NSMakeRange(index, 0)]);

        *lines = numberOfLines;
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Deletes text from an MLE box.
 * Parameters:
 *          handle: Handle to the MLE to be deleted from.
 *          startpoint: Point to start deleting text.
 *          length: Amount of text to be deleted.
 */
DW_FUNCTION_DEFINITION(dw_mle_delete, void, HWND handle, int startpoint, int length)
DW_FUNCTION_ADD_PARAM3(handle, startpoint, length)
DW_FUNCTION_NO_RETURN(dw_mle_delete)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, startpoint, int, length, int)
{
    DW_FUNCTION_INIT;
    DWMLE *mle = handle;
    NSTextStorage *ts = [mle textStorage];
    NSMutableString *ms = [ts mutableString];
    NSUInteger mslength = [ms length];
    if(startpoint < 0)
        startpoint = 0;
    if(startpoint > mslength)
        startpoint = (int)mslength;
    if(startpoint + length > mslength)
        length = (int)mslength - startpoint;
    [ms deleteCharactersInRange:NSMakeRange(startpoint, length)];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Clears all text from an MLE box.
 * Parameters:
 *          handle: Handle to the MLE to be cleared.
 */
DW_FUNCTION_DEFINITION(dw_mle_clear, void, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_NO_RETURN(dw_mle_clear)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DW_FUNCTION_INIT;
    DWMLE *mle = handle;
    NSTextStorage *ts = [mle textStorage];
    NSMutableString *ms = [ts mutableString];
    NSUInteger length = [ms length];
    [ms deleteCharactersInRange:NSMakeRange(0, length)];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the visible line of an MLE box.
 * Parameters:
 *          handle: Handle to the MLE to be positioned.
 *          line: Line to be visible.
 */
DW_FUNCTION_DEFINITION(dw_mle_set_visible, void, HWND handle, int line)
DW_FUNCTION_ADD_PARAM2(handle, line)
DW_FUNCTION_NO_RETURN(dw_mle_set_visible)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, line, int)
{
    DW_FUNCTION_INIT;
    DWMLE *mle = handle;
    NSTextStorage *ts = [mle textStorage];
    NSMutableString *ms = [ts mutableString];
    NSUInteger numberOfLines, index, stringLength = [ms length];

    for(index=0, numberOfLines=0; index < stringLength && numberOfLines < line; numberOfLines++)
        index = NSMaxRange([ms lineRangeForRange:NSMakeRange(index, 0)]);

    if(line == numberOfLines)
    {
        [mle scrollRangeToVisible:[ms lineRangeForRange:NSMakeRange(index, 0)]];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the editablity of an MLE box.
 * Parameters:
 *          handle: Handle to the MLE.
 *          state: TRUE if it can be edited, FALSE for readonly.
 */
DW_FUNCTION_DEFINITION(dw_mle_set_editable, void, HWND handle, int state)
DW_FUNCTION_ADD_PARAM2(handle, state)
DW_FUNCTION_NO_RETURN(dw_mle_set_editable)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, state, int)
{
    DWMLE *mle = handle;
    [mle setEditable:(state ? YES : NO)];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the word wrap state of an MLE box.
 * Parameters:
 *          handle: Handle to the MLE.
 *          state: TRUE if it wraps, FALSE if it doesn't.
 */
DW_FUNCTION_DEFINITION(dw_mle_set_word_wrap, void, HWND handle, int state)
DW_FUNCTION_ADD_PARAM2(handle, state)
DW_FUNCTION_NO_RETURN(dw_mle_set_word_wrap)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, state, int)
{
    DWMLE *mle = handle;

    [[mle textContainer] setLineBreakMode:(state ? NSLineBreakByWordWrapping : NSLineBreakByClipping)];
    [[mle textContainer] setWidthTracksTextView:(state ? YES : NO)];
    if(!state)
        [[mle textContainer] setSize:CGRectInfinite.size];
    else
    {
        CGSize size = [mle frame].size;
        [[mle textContainer] setSize:CGSizeMake(size.width, CGRectInfinite.size.height)];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the word auto complete state of an MLE box.
 * Parameters:
 *          handle: Handle to the MLE.
 *          state: Bitwise combination of DW_MLE_COMPLETE_TEXT/DASH/QUOTE
 */
DW_FUNCTION_DEFINITION(dw_mle_set_auto_complete, void, HWND handle, int state)
DW_FUNCTION_ADD_PARAM2(handle, state)
DW_FUNCTION_NO_RETURN(dw_mle_set_auto_complete)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, state, int)
{
    DWMLE *mle = handle;
    NSInteger autocorrect = (state & DW_MLE_COMPLETE_TEXT) ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo;
    [mle setAutocorrectionType:autocorrect];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the current cursor position of an MLE box.
 * Parameters:
 *          handle: Handle to the MLE to be positioned.
 *          point: Point to position cursor.
 */
DW_FUNCTION_DEFINITION(dw_mle_set_cursor, void, HWND handle, int point)
DW_FUNCTION_ADD_PARAM2(handle, point)
DW_FUNCTION_NO_RETURN(dw_mle_set_cursor)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, point, int)
{
    DW_FUNCTION_INIT;
    DWMLE *mle = handle;
    NSTextStorage *ts = [mle textStorage];
    NSMutableString *ms = [ts mutableString];
    NSUInteger length = [ms length];
    if(point < 0)
        point = 0;
    if(point > length)
        point = (int)length;
    [mle setSelectedRange: NSMakeRange(point,point)];
    [mle scrollRangeToVisible:NSMakeRange(point,point)];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Finds text in an MLE box.
 * Parameters:
 *          handle: Handle to the MLE to be cleared.
 *          text: Text to search for.
 *          point: Start point of search.
 *          flags: Search specific flags.
 */
DW_FUNCTION_DEFINITION(dw_mle_search, int, HWND handle, const char *text, int point, unsigned long flags)
DW_FUNCTION_ADD_PARAM4(handle, text, point, flags)
DW_FUNCTION_RETURN(dw_mle_search, int)
DW_FUNCTION_RESTORE_PARAM4(handle, HWND, text, const char *, point, int, flags, unsigned long)
{
    DW_FUNCTION_INIT;
    DWMLE *mle = handle;
    NSTextStorage *ts = [mle textStorage];
    NSMutableString *ms = [ts mutableString];
    NSString *searchForMe = [NSString stringWithUTF8String:text];
    NSRange searchRange = NSMakeRange(point, [ms length] - point);
    NSRange range = NSMakeRange(NSNotFound, 0);
    NSUInteger options = flags ? flags : NSCaseInsensitiveSearch;
    int retval = -1;

    if(ms)
        range = [ms rangeOfString:searchForMe options:options range:searchRange];
    if(range.location == NSNotFound)
        retval = (int)range.location;
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Stops redrawing of an MLE box.
 * Parameters:
 *          handle: Handle to the MLE to freeze.
 */
void API dw_mle_freeze(HWND handle)
{
    /* Don't think this is necessary */
}

/*
 * Resumes redrawing of an MLE box.
 * Parameters:
 *          handle: Handle to the MLE to thaw.
 */
void API dw_mle_thaw(HWND handle)
{
    /* Don't think this is necessary */
}

/*
 * Create a new status text window (widget) to be packed.
 * Parameters:
 *       text: The text to be display by the static text widget.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_status_text_new, HWND, const char *text, ULONG cid)
DW_FUNCTION_ADD_PARAM2(text, cid)
DW_FUNCTION_RETURN(dw_status_text_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(text, const char *, cid, ULONG)
{
    DWText *textfield = [[[DWText alloc] init] retain];
    [textfield setText:[NSString stringWithUTF8String:text]];
    [textfield setTag:cid];
    if(DWDefaultFont)
    {
        [textfield setFont:DWDefaultFont];
    }
    [textfield layer].borderWidth = 2.0;
    [textfield layer].borderColor = [[UIColor darkGrayColor] CGColor];
    DW_FUNCTION_RETURN_THIS(textfield);
}

/*
 * Create a new static text window (widget) to be packed.
 * Parameters:
 *       text: The text to be display by the static text widget.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_text_new, HWND, const char *text, ULONG cid)
DW_FUNCTION_ADD_PARAM2(text, cid)
DW_FUNCTION_RETURN(dw_text_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(text, const char *, cid, ULONG)
{
    DWText *textfield = [[[DWText alloc] init] retain];
    [textfield setText:[NSString stringWithUTF8String:text]];
    [textfield setTag:cid];
    if(DWDefaultFont)
    {
        [textfield setFont:DWDefaultFont];
    }
    DW_FUNCTION_RETURN_THIS(textfield);
}

/*
 * Creates a rendering context widget (window) to be packed.
 * Parameters:
 *       id: An id to be used with dw_window_from_id.
 * Returns:
 *       A handle to the widget or NULL on failure.
 */
DW_FUNCTION_DEFINITION(dw_render_new, HWND, ULONG cid)
DW_FUNCTION_ADD_PARAM1(cid)
DW_FUNCTION_RETURN(dw_render_new, HWND)
DW_FUNCTION_RESTORE_PARAM1(cid, ULONG)
{
    DWRender *render = [[[DWRender alloc] init] retain];
    UIContextMenuInteraction *interaction = [[UIContextMenuInteraction alloc] initWithDelegate:render];
    [render setTag:cid];
    [render addInteraction:interaction];
    DW_FUNCTION_RETURN_THIS(render);
}

/*
 * Invalidate the render widget triggering an expose event.
 * Parameters:
 *       handle: A handle to a render widget to be redrawn.
 */
DW_FUNCTION_DEFINITION(dw_render_redraw, void, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_NO_RETURN(dw_render_redraw)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DW_FUNCTION_INIT;
    DWRender *render = (DWRender *)handle;

    [render setNeedsDisplay];
    DW_FUNCTION_RETURN_NOTHING;
}

/* Sets the current foreground drawing color.
 * Parameters:
 *       red: red value.
 *       green: green value.
 *       blue: blue value.
 */
void API dw_color_foreground_set(unsigned long value)
{
    UIColor *oldcolor = pthread_getspecific(_dw_fg_color_key);
    UIColor *newcolor;
    DW_LOCAL_POOL_IN;

    _dw_foreground = _dw_get_color(value);

    newcolor = [[UIColor colorWithRed:  DW_RED_VALUE(_dw_foreground)/255.0 green:
                                        DW_GREEN_VALUE(_dw_foreground)/255.0 blue:
                                        DW_BLUE_VALUE(_dw_foreground)/255.0 alpha: 1] retain];
    pthread_setspecific(_dw_fg_color_key, newcolor);
    [oldcolor release];
    DW_LOCAL_POOL_OUT;
}

/* Sets the current background drawing color.
 * Parameters:
 *       red: red value.
 *       green: green value.
 *       blue: blue value.
 */
void API dw_color_background_set(unsigned long value)
{
    UIColor *oldcolor = pthread_getspecific(_dw_bg_color_key);
    UIColor *newcolor;
    DW_LOCAL_POOL_IN;

    if(value == DW_CLR_DEFAULT)
    {
        pthread_setspecific(_dw_bg_color_key, NULL);
    }
    else
    {
        _dw_background = _dw_get_color(value);

        newcolor = [[UIColor colorWithRed:  DW_RED_VALUE(_dw_background)/255.0 green:
                                            DW_GREEN_VALUE(_dw_background)/255.0 blue:
                                            DW_BLUE_VALUE(_dw_background)/255.0 alpha: 1] retain];
        pthread_setspecific(_dw_bg_color_key, newcolor);
    }
    [oldcolor release];
    DW_LOCAL_POOL_OUT;
}

/* Allows the user to choose a color using the system's color chooser dialog.
 * Parameters:
 *       value: current color
 * Returns:
 *       The selected color or the current color if cancelled.
 */
unsigned long API dw_color_choose(unsigned long value)
{
    NSMutableArray *params = [NSMutableArray arrayWithObject:[NSNumber numberWithUnsignedLong:_dw_get_color(value)]];
    unsigned long newcolor = value;

    [DWObj safeCall:@selector(colorPicker:) withObject:params];
    if([params count] > 1)
        newcolor = [[params lastObject] unsignedLongValue];

    return newcolor;
}

CGContextRef _dw_draw_context(id source, bool antialiased)
{
    CGContextRef context = nil;

    if([source isMemberOfClass:[DWImage class]])
    {
        DWImage *image = source;

        context = [image cgcontext];
    }
    if(context)
        CGContextSetAllowsAntialiasing(context, antialiased);
    return context;
}

DWImage *_dw_dest_image(HPIXMAP pixmap, id object)
{
    if(pixmap && pixmap->image)
        return pixmap->image;
    if([object isMemberOfClass:[DWRender class]])
        return [object cachedImage];
    return nil;
}

/* Draw a point on a window (preferably a render window).
 * Parameters:
 *       handle: Handle to the window.
 *       pixmap: Handle to the pixmap. (choose only one of these)
 *       x: X coordinate.
 *       y: Y coordinate.
 */
DW_FUNCTION_DEFINITION(dw_draw_point, void, HWND handle, HPIXMAP pixmap, int x, int y)
DW_FUNCTION_ADD_PARAM4(handle, pixmap, x, y)
DW_FUNCTION_NO_RETURN(dw_draw_point)
DW_FUNCTION_RESTORE_PARAM4(handle, HWND, pixmap, HPIXMAP, x, int, y, int)
{
    DW_FUNCTION_INIT;
    DWImage *bi = _dw_dest_image(pixmap, handle);
    CGContextRef context = _dw_draw_context(bi, NO);
    
    if(context)
        UIGraphicsPushContext(context);

    if(bi)
    {
        UIBezierPath* aPath = [UIBezierPath bezierPath];
        [aPath setLineWidth: 0.5];
        [_dw_fg_color set];

        [aPath moveToPoint:CGPointMake(x, y)];
        [aPath stroke];
    }
    if(context)
        UIGraphicsPopContext();
    DW_FUNCTION_RETURN_NOTHING;
}

/* Draw a line on a window (preferably a render window).
 * Parameters:
 *       handle: Handle to the window.
 *       pixmap: Handle to the pixmap. (choose only one of these)
 *       x1: First X coordinate.
 *       y1: First Y coordinate.
 *       x2: Second X coordinate.
 *       y2: Second Y coordinate.
 */
DW_FUNCTION_DEFINITION(dw_draw_line, void, HWND handle, HPIXMAP pixmap, int x1, int y1, int x2, int y2)
DW_FUNCTION_ADD_PARAM6(handle, pixmap, x1, y1, x2, y2)
DW_FUNCTION_NO_RETURN(dw_draw_line)
DW_FUNCTION_RESTORE_PARAM6(handle, HWND, pixmap, HPIXMAP, x1, int, y1, int, x2, int, y2, int)
{
    DW_FUNCTION_INIT;
    DWImage *bi = _dw_dest_image(pixmap, handle);
    CGContextRef context = _dw_draw_context(bi, NO);

    if(context)
        UIGraphicsPushContext(context);

    if(bi)
    {
        UIBezierPath* aPath = [UIBezierPath bezierPath];
        [_dw_fg_color set];

        [aPath moveToPoint:CGPointMake(x1 + 0.5, y1 + 0.5)];
        [aPath addLineToPoint:CGPointMake(x2 + 0.5, y2 + 0.5)];
        [aPath stroke];
    }
    if(context)
        UIGraphicsPopContext();
    DW_FUNCTION_RETURN_NOTHING;
}

/* Draw text on a window (preferably a render window).
 * Parameters:
 *       handle: Handle to the window.
 *       pixmap: Handle to the pixmap. (choose only one of these)
 *       x: X coordinate.
 *       y: Y coordinate.
 *       text: Text to be displayed.
 */
DW_FUNCTION_DEFINITION(dw_draw_text, void, HWND handle, HPIXMAP pixmap, int x, int y, const char *text)
DW_FUNCTION_ADD_PARAM5(handle, pixmap, x, y, text)
DW_FUNCTION_NO_RETURN(dw_draw_text)
DW_FUNCTION_RESTORE_PARAM5(handle, HWND, pixmap, HPIXMAP, x, int, y, int, text, const char *)
{
    DW_FUNCTION_INIT;
    NSString *nstr = [ NSString stringWithUTF8String:text ];
    DWImage *bi = nil;
    UIFont *font = nil;
    DWRender *render;
    id image = handle;

    if(pixmap)
    {
        bi = (id)pixmap->image;
        font = pixmap->font;
        render = pixmap->handle;
        if(!font && [render isMemberOfClass:[DWRender class]])
        {
            font = [render font];
        }
    }
    else if(image && [image isMemberOfClass:[DWRender class]])
    {
        render = image;
        font = [render font];
        bi = [render cachedImage];
    }
    if(bi)
    {
        CGContextRef context = _dw_draw_context(bi, NO);

        if(context)
            UIGraphicsPushContext(context);

        NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithObjectsAndKeys:_dw_fg_color, NSForegroundColorAttributeName, nil];
        if(_dw_bg_color)
            [dict setValue:_dw_bg_color forKey:NSBackgroundColorAttributeName];
        if(font)
            [dict setValue:font forKey:NSFontAttributeName];
        [nstr drawAtPoint:CGPointMake(x, y) withAttributes:dict];
        [dict release];
        if(context)
            UIGraphicsPopContext();
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/* Query the width and height of a text string.
 * Parameters:
 *       handle: Handle to the window.
 *       pixmap: Handle to the pixmap. (choose only one of these)
 *       text: Text to be queried.
 *       width: Pointer to a variable to be filled in with the width.
 *       height Pointer to a variable to be filled in with the height.
 */
void API dw_font_text_extents_get(HWND handle, HPIXMAP pixmap, const char *text, int *width, int *height)
{
    id object = handle;
    NSString *nstr;
    UIFont *font = nil;
    DW_LOCAL_POOL_IN;

    nstr = [NSString stringWithUTF8String:text];

    /* Check the pixmap for associated object or font */
    if(pixmap)
    {
        object = pixmap->handle;
        font = pixmap->font;
    }
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
    /* If we didn't get a font from the pixmap... try the associated object */
    if(!font && ([object isMemberOfClass:[DWRender class]] || [object isKindOfClass:[UIControl class]]
                 || [object isKindOfClass:[UILabel class]]))
    {
        font = [object font];
    }
    /* If we got a font... add it to the dictionary */
    if(font)
    {
        [dict setValue:font forKey:NSFontAttributeName];
    }
    /* Calculate the size of the string */
    CGSize size = [nstr sizeWithAttributes:dict];
    [dict release];
    /* Return whatever information we can */
    if(width)
    {
        *width = size.width;
    }
    if(height)
    {
        *height = size.height;
    }
    DW_LOCAL_POOL_OUT;
}

/* Draw a polygon on a window (preferably a render window).
 * Parameters:
 *       handle: Handle to the window.
 *       pixmap: Handle to the pixmap. (choose only one of these)
 *       flags: DW_DRAW_FILL (1) to fill the polygon or DW_DRAW_DEFAULT (0).
 *       x: X coordinate.
 *       y: Y coordinate.
 *       width: Width of rectangle.
 *       height: Height of rectangle.
 */
DW_FUNCTION_DEFINITION(dw_draw_polygon, void, HWND handle, HPIXMAP pixmap, int flags, int npoints, int *x, int *y)
DW_FUNCTION_ADD_PARAM6(handle, pixmap, flags, npoints, x, y)
DW_FUNCTION_NO_RETURN(dw_draw_polygon)
DW_FUNCTION_RESTORE_PARAM6(handle, HWND, pixmap, HPIXMAP, flags, int, npoints, int, x, int *, y, int *)
{
    DW_FUNCTION_INIT;
    DWImage *bi = _dw_dest_image(pixmap, handle);
    CGContextRef context = _dw_draw_context(bi, flags & DW_DRAW_NOAA ? NO : YES);

    if(context)
        UIGraphicsPushContext(context);

    if(bi)
    {
        UIBezierPath* aPath = [UIBezierPath bezierPath];
        int z;

        [_dw_fg_color set];

        [aPath moveToPoint:CGPointMake(*x + 0.5, *y + 0.5)];
        for(z=1;z<npoints;z++)
        {
            [aPath addLineToPoint:CGPointMake(x[z] + 0.5, y[z] + 0.5)];
        }
        [aPath closePath];
        if(flags & DW_DRAW_FILL)
            [aPath fill];
        else
            [aPath stroke];
    }
    if(context)
        UIGraphicsPopContext();
    DW_FUNCTION_RETURN_NOTHING;
}

/* Draw a rectangle on a window (preferably a render window).
 * Parameters:
 *       handle: Handle to the window.
 *       pixmap: Handle to the pixmap. (choose only one of these)
 *       flags: DW_DRAW_FILL (1) to fill the box or DW_DRAW_DEFAULT (0).
 *       x: X coordinate.
 *       y: Y coordinate.
 *       width: Width of rectangle.
 *       height: Height of rectangle.
 */
DW_FUNCTION_DEFINITION(dw_draw_rect, void, HWND handle, HPIXMAP pixmap, int flags, int x, int y, int width, int height)
DW_FUNCTION_ADD_PARAM7(handle, pixmap, flags, x, y, width, height)
DW_FUNCTION_NO_RETURN(dw_draw_rect)
DW_FUNCTION_RESTORE_PARAM7(handle, HWND, pixmap, HPIXMAP, flags, int, x, int, y, int, width, int, height, int)
{
    DW_FUNCTION_INIT;
    DWImage *bi = _dw_dest_image(pixmap, handle);
    CGContextRef context = _dw_draw_context(bi, flags & DW_DRAW_NOAA ? NO : YES);

    if(context)
        UIGraphicsPushContext(context);

    if(bi)
    {
        UIBezierPath *bp = [UIBezierPath bezierPathWithRect:CGRectMake(x, y, width, height)];;
        
        [_dw_fg_color set];

        if(flags & DW_DRAW_FILL)
            [bp fill];
        else
            [bp stroke];
    }
    if(context)
        UIGraphicsPopContext();
    DW_FUNCTION_RETURN_NOTHING;
}

/* Draw an arc on a window (preferably a render window).
 * Parameters:
 *       handle: Handle to the window.
 *       pixmap: Handle to the pixmap. (choose only one of these)
 *       flags: DW_DRAW_FILL (1) to fill the arc or DW_DRAW_DEFAULT (0).
 *              DW_DRAW_FULL will draw a complete circle/elipse.
 *       xorigin: X coordinate of center of arc.
 *       yorigin: Y coordinate of center of arc.
 *       x1: X coordinate of first segment of arc.
 *       y1: Y coordinate of first segment of arc.
 *       x2: X coordinate of second segment of arc.
 *       y2: Y coordinate of second segment of arc.
 */
DW_FUNCTION_DEFINITION(dw_draw_arc, void, HWND handle, HPIXMAP pixmap, int flags, int xorigin, int yorigin, int x1, int y1, int x2, int y2)
DW_FUNCTION_ADD_PARAM9(handle, pixmap, flags, xorigin, yorigin, x1, y1, x2, y2)
DW_FUNCTION_NO_RETURN(dw_draw_arc)
DW_FUNCTION_RESTORE_PARAM9(handle, HWND, pixmap, HPIXMAP, flags, int, xorigin, int, yorigin, int, x1, int, y1, int, x2, int, y2, int)
{
    DW_FUNCTION_INIT;
    DWImage *bi = _dw_dest_image(pixmap, handle);
    CGContextRef context = _dw_draw_context(bi, flags & DW_DRAW_NOAA ? NO : YES);

    if(context)
        UIGraphicsPushContext(context);

    if(bi)
    {
        UIBezierPath* aPath;

        [_dw_fg_color set];

        /* Special case of a full circle/oval */
        if(flags & DW_DRAW_FULL)
            aPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(x1, y1, x2 - x1, y2 - y1)];
        else
        {
            double a1 = atan2((y1-yorigin), (x1-xorigin));
            double a2 = atan2((y2-yorigin), (x2-xorigin));
            double dx = xorigin - x1;
            double dy = yorigin - y1;
            double r = sqrt(dx*dx + dy*dy);

            /* Prepare to draw */
            aPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(xorigin, yorigin)
                                                   radius:r startAngle:a1 endAngle:a2 clockwise:YES];
        }
        /* If the fill flag is passed */
        if(flags & DW_DRAW_FILL)
            [aPath fill];
        else
            [aPath stroke];
    }
    if(context)
        UIGraphicsPopContext();
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Create a tree object to be packed.
 * Parameters:
 *       id: An ID to be used for getting the resource from the
 *           resource file.
 * Returns:
 *       A handle to a tree window or NULL on failure.
 */
DW_FUNCTION_DEFINITION(dw_tree_new, HWND, ULONG cid)
DW_FUNCTION_ADD_PARAM1(cid)
DW_FUNCTION_RETURN(dw_tree_new, HWND)
DW_FUNCTION_RESTORE_PARAM1(cid, ULONG)
{
    DW_FUNCTION_INIT;
    DWTree *tree = [[[DWTree alloc] init] retain];
    [tree setTag:cid];
    DW_FUNCTION_RETURN_THIS(tree);
}

/*
 * Inserts an item into a tree window (widget) after another item.
 * Parameters:
 *          handle: Handle to the tree to be inserted.
 *          item: Handle to the item to be positioned after.
 *          title: The text title of the entry.
 *          icon: Handle to coresponding icon.
 *          parent: Parent handle or 0 if root.
 *          itemdata: Item specific data.
 * Returns:
 *       A handle to a tree item or NULL on failure.
 */
DW_FUNCTION_DEFINITION(dw_tree_insert_after, HTREEITEM, HWND handle, HTREEITEM item, const char *title, HICN icon, HTREEITEM parent, void *itemdata)
DW_FUNCTION_ADD_PARAM6(handle, item, title, icon, parent, itemdata)
DW_FUNCTION_RETURN(dw_tree_insert_after, HTREEITEM)
DW_FUNCTION_RESTORE_PARAM6(handle, HWND, item, HTREEITEM, title, char *, icon, HICN, parent, HTREEITEM, itemdata, void *)
{
    DW_FUNCTION_INIT;
    DWTree *tree = handle;
    DWTreeItem *treeparent = item ? item : parent;
    DWTreeItem *treeitem = [[[DWTreeItem alloc] initWithIcon:icon
                                                       Title:[NSString stringWithUTF8String:(title ? title : "")]
                                                     andData:itemdata] retain];
    if(treeparent)
    {
        if(item)
            [treeparent insertChildAfter:treeitem];
        else
            [treeparent appendChild:treeitem];
    }
    else
        [tree insertTreeItem:treeitem];
    DW_FUNCTION_RETURN_THIS(treeitem);
}

/*
 * Inserts an item into a tree window (widget).
 * Parameters:
 *          handle: Handle to the tree to be inserted.
 *          title: The text title of the entry.
 *          icon: Handle to coresponding icon.
 *          parent: Parent handle or 0 if root.
 *          itemdata: Item specific data.
 * Returns:
 *       A handle to a tree item or NULL on failure.
 */
HTREEITEM API dw_tree_insert(HWND handle, const char *title, HICN icon, HTREEITEM parent, void *itemdata)
{
    return dw_tree_insert_after(handle, NULL, title, icon, parent, itemdata);
}

/*
 * Gets the text an item in a tree window (widget).
 * Parameters:
 *          handle: Handle to the tree containing the item.
 *          item: Handle of the item to be modified.
 * Returns:
 *       A malloc()ed buffer of item text to be dw_free()ed or NULL on error.
 */
DW_FUNCTION_DEFINITION(dw_tree_get_title, char *, HWND handle, HTREEITEM item)
DW_FUNCTION_ADD_PARAM2(handle, item)
DW_FUNCTION_RETURN(dw_tree_get_title, char *)
DW_FUNCTION_RESTORE_PARAM2(DW_UNUSED(handle), HWND, item, HTREEITEM)
{
    DW_FUNCTION_INIT;
    char *title = NULL;
    DWTreeItem *treeitem = item;
    if(treeitem)
        title = strdup([treeitem.title UTF8String]);
    DW_FUNCTION_RETURN_THIS(title);
}

/*
 * Gets the text an item in a tree window (widget).
 * Parameters:
 *          handle: Handle to the tree containing the item.
 *          item: Handle of the item to be modified.
 * Returns:
 *       A handle to a tree item or NULL on failure.
 */
DW_FUNCTION_DEFINITION(dw_tree_get_parent, HTREEITEM, HWND handle, HTREEITEM item)
DW_FUNCTION_ADD_PARAM2(handle, item)
DW_FUNCTION_RETURN(dw_tree_get_parent, HTREEITEM)
DW_FUNCTION_RESTORE_PARAM2(DW_UNUSED(handle), HWND, item, HTREEITEM)
{
    DW_FUNCTION_INIT;
    DWTreeItem *treeparent = nil;
    DWTreeItem *treeitem = item;
    if(treeitem)
        treeparent = treeitem.parent;
    if(treeparent && [treeparent isRoot])
        treeparent = NULL;
    DW_FUNCTION_RETURN_THIS(treeparent);
}

/*
 * Sets the text and icon of an item in a tree window (widget).
 * Parameters:
 *          handle: Handle to the tree containing the item.
 *          item: Handle of the item to be modified.
 *          title: The text title of the entry.
 *          icon: Handle to coresponding icon.
 */
DW_FUNCTION_DEFINITION(dw_tree_item_change, void, HWND handle, HTREEITEM item, const char *title, HICN icon)
DW_FUNCTION_ADD_PARAM4(handle, item, title, icon)
DW_FUNCTION_NO_RETURN(dw_tree_item_change)
DW_FUNCTION_RESTORE_PARAM4(DW_UNUSED(handle), HWND, item, HTREEITEM, title, const char *, icon, HICN)
{
    DW_FUNCTION_INIT;
    DWTreeItem *treeitem = item;
    if(handle && treeitem)
    {
        [treeitem setIcon:icon];
        [treeitem setTitle:[NSString stringWithUTF8String:(title ? title : "")]];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the item data of a tree item.
 * Parameters:
 *          handle: Handle to the tree containing the item.
 *          item: Handle of the item to be modified.
 *          itemdata: User defined data to be associated with item.
 */
DW_FUNCTION_DEFINITION(dw_tree_item_set_data, void, HWND handle, HTREEITEM item, void *itemdata)
DW_FUNCTION_ADD_PARAM3(handle, item, itemdata)
DW_FUNCTION_NO_RETURN(dw_tree_item_set_data)
DW_FUNCTION_RESTORE_PARAM3(DW_UNUSED(handle), HWND, item, HTREEITEM, itemdata, void *)
{
    DW_FUNCTION_INIT;
    DWTreeItem *treeitem = item;
    if(handle && treeitem)
        [treeitem setData:itemdata];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Gets the item data of a tree item.
 * Parameters:
 *          handle: Handle to the tree containing the item.
 *          item: Handle of the item to be modified.
 * Returns:
 *       A pointer to tree item data or NULL on failure.
 */
DW_FUNCTION_DEFINITION(dw_tree_item_get_data, void *, HWND handle, HTREEITEM item)
DW_FUNCTION_ADD_PARAM2(handle, item)
DW_FUNCTION_RETURN(dw_tree_item_get_data, void *)
DW_FUNCTION_RESTORE_PARAM2(DW_UNUSED(handle), HWND, item, HTREEITEM)
{
    DW_FUNCTION_INIT;
    void *result = NULL;
    DWTreeItem *treeitem = item;
    if(handle && treeitem)
        result = [treeitem data];
    DW_FUNCTION_RETURN_THIS(result);
}

/*
 * Sets this item as the active selection.
 * Parameters:
 *       handle: Handle to the tree window (widget) to be selected.
 *       item: Handle to the item to be selected.
 */
DW_FUNCTION_DEFINITION(dw_tree_item_select, void, HWND handle, HTREEITEM item)
DW_FUNCTION_ADD_PARAM2(handle, item)
DW_FUNCTION_NO_RETURN(dw_tree_item_select)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, item, HTREEITEM)
{
    DW_FUNCTION_INIT;
    DWTree *tree = handle;
    DWTreeItem *treeitem = item;

    if(tree && treeitem)
    {
        NSInteger itemIndex = [tree treeView:tree rowForTreeItem:treeitem];
        if(itemIndex > -1)
        {
            NSIndexPath *ip = [NSIndexPath indexPathForRow:(NSUInteger)itemIndex inSection:0];

            [tree selectRowAtIndexPath:ip
                              animated:NO
                        scrollPosition:UITableViewScrollPositionNone];
        }
    }
    DW_FUNCTION_RETURN_NOTHING;
}


/*
 * Removes all nodes from a tree.
 * Parameters:
 *       handle: Handle to the window (widget) to be cleared.
 */
DW_FUNCTION_DEFINITION(dw_tree_clear, void, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_NO_RETURN(dw_tree_clear)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DW_FUNCTION_INIT;
    DWTree *tree = handle;
    [tree clear];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Expands a node on a tree.
 * Parameters:
 *       handle: Handle to the tree window (widget).
 *       item: Handle to node to be expanded.
 */
DW_FUNCTION_DEFINITION(dw_tree_item_expand, void, HWND handle, HTREEITEM item)
DW_FUNCTION_ADD_PARAM2(handle, item)
DW_FUNCTION_NO_RETURN(dw_tree_item_expand)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, item, HTREEITEM)
{
    DW_FUNCTION_INIT;
    DWTree *tree = handle;
    DWTreeItem *treeitem = item;
    if(![treeitem expanded])
    {
        [treeitem setExpanded:YES];
        [tree reloadData];
        [tree resetSelection:NO];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Collapses a node on a tree.
 * Parameters:
 *       handle: Handle to the tree window (widget).
 *       item: Handle to node to be collapsed.
 */
DW_FUNCTION_DEFINITION(dw_tree_item_collapse, void, HWND handle, HTREEITEM item)
DW_FUNCTION_ADD_PARAM2(handle, item)
DW_FUNCTION_NO_RETURN(dw_tree_item_collapse)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, item, HTREEITEM)
{
    DW_FUNCTION_INIT;
    DWTree *tree = handle;
    DWTreeItem *treeitem = item;
    if([treeitem expanded])
    {
        [treeitem setExpanded:NO];
        [tree reloadData];
        [tree resetSelection:NO];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Removes a node from a tree.
 * Parameters:
 *       handle: Handle to the window (widget) to be cleared.
 *       item: Handle to node to be deleted.
 */
DW_FUNCTION_DEFINITION(dw_tree_item_delete, void, HWND handle, HTREEITEM item)
DW_FUNCTION_ADD_PARAM2(handle, item)
DW_FUNCTION_NO_RETURN(dw_tree_item_delete)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, item, HTREEITEM)
{
    DW_FUNCTION_INIT;
    DWTree *tree = handle;
    DWTreeItem *treeitem = item;
    [treeitem removeFromParent];
    [tree reloadData];
    [tree resetSelection:NO];
    DW_FUNCTION_RETURN_NOTHING;
}


/*
 * Create a container object to be packed.
 * Parameters:
 *       id: An ID to be used for getting the resource from the
 *           resource file.
 */
DW_FUNCTION_DEFINITION(dw_container_new, HWND, ULONG cid, int multi)
DW_FUNCTION_ADD_PARAM2(cid, multi)
DW_FUNCTION_RETURN(dw_container_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(cid, ULONG, multi, int)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = _dw_cont_new(cid, multi);
    DW_FUNCTION_RETURN_THIS(cont);
}

/*
 * Sets up the container columns.
 * Parameters:
 *          handle: Handle to the container to be configured.
 *          flags: An array of unsigned longs with column flags.
 *          titles: An array of strings with column text titles.
 *          count: The number of columns (this should match the arrays).
 *          separator: The column number that contains the main separator.
 *                     (this item may only be used in OS/2)
 */
DW_FUNCTION_DEFINITION(dw_container_setup, int, HWND handle, unsigned long *flags, char **titles, int count, int separator)
DW_FUNCTION_ADD_PARAM5(handle, flags, titles, count, separator)
DW_FUNCTION_RETURN(dw_container_setup, int)
DW_FUNCTION_RESTORE_PARAM5(handle, HWND, flags, unsigned long *, titles, char **, count, int, DW_UNUSED(separator), int)
{
    DW_FUNCTION_INIT;
    int z, retval = DW_ERROR_NONE;
    DWContainer *cont = handle;

    [cont setup];

    for(z=0;z<count;z++)
    {
        /* Even though we don't have columns on iOS, we save the data...
         * So we can simulate columns when displaying the data in the list.
         */
        NSString *title = [NSString stringWithUTF8String:titles[z]];

        [cont addColumn:title andType:(int)flags[z]];
    }
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Configures the main filesystem columnn title for localization.
 * Parameters:
 *          handle: Handle to the container to be configured.
 *          title: The title to be displayed in the main column.
 */
void API dw_filesystem_set_column_title(HWND handle, const char *title)
{
	char *newtitle = strdup(title ? title : "");
	
	dw_window_set_data(handle, "_dw_coltitle", newtitle);
}

/*
 * Sets up the filesystem columns, note: filesystem always has an icon/filename field.
 * Parameters:
 *          handle: Handle to the container to be configured.
 *          flags: An array of unsigned longs with column flags.
 *          titles: An array of strings with column text titles.
 *          count: The number of columns (this should match the arrays).
 */
int API dw_filesystem_setup(HWND handle, unsigned long *flags, char **titles, int count)
{
    char **newtitles = malloc(sizeof(char *) * (count + 1));
    unsigned long *newflags = malloc(sizeof(unsigned long) * (count + 1));
    char *coltitle = (char *)dw_window_get_data(handle, "_dw_coltitle");
    DWContainer *cont = handle;

    newtitles[0] = coltitle ? coltitle : "Filename";

    newflags[0] = DW_CFA_STRINGANDICON | DW_CFA_LEFT | DW_CFA_HORZSEPARATOR;

    memcpy(&newtitles[1], titles, sizeof(char *) * count);
    memcpy(&newflags[1], flags, sizeof(unsigned long) * count);

    dw_container_setup(handle, newflags, newtitles, count + 1, 0);
    [cont setFilesystem:YES];

    if(coltitle)
    {
        dw_window_set_data(handle, "_dw_coltitle", NULL);
        free(coltitle);
    }
    free(newtitles);
    free(newflags);
    return DW_ERROR_NONE;
}

/*
 * Allocates memory used to populate a container.
 * Parameters:
 *          handle: Handle to the container window (widget).
 *          rowcount: The number of items to be populated.
 */
DW_FUNCTION_DEFINITION(dw_container_alloc, void *, HWND handle, int rowcount)
DW_FUNCTION_ADD_PARAM2(handle, rowcount)
DW_FUNCTION_RETURN(dw_container_alloc, void *)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, rowcount, int)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    [cont addRows:rowcount];
    DW_FUNCTION_RETURN_THIS(cont);
}

/*
 * Sets an item in specified row and column to the given data.
 * Parameters:
 *          handle: Handle to the container window (widget).
 *          pointer: Pointer to the allocated memory in dw_container_alloc().
 *          column: Zero based column of data being set.
 *          row: Zero based row of data being set.
 *          data: Pointer to the data to be added.
 */
DW_FUNCTION_DEFINITION(dw_container_set_item, void, HWND handle, void *pointer, int column, int row, void *data)
DW_FUNCTION_ADD_PARAM5(handle, pointer, column, row, data)
DW_FUNCTION_NO_RETURN(dw_container_set_item)
DW_FUNCTION_RESTORE_PARAM5(handle, HWND, pointer, void *, column, int, row, int, data, void *)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    id icon = nil, text = nil;
    int type = [cont cellType:column];
    int lastadd = 0;

    /* If pointer is NULL we are getting a change request instead of set */
    if(pointer)
        lastadd = [cont lastAddPoint];

    if(data)
    {
        if(type & DW_CFA_BITMAPORICON)
        {
            icon = *((UIImage **)data);
        }
        else if(type & DW_CFA_STRING)
        {
            char *str = *((char **)data);
            text = [NSString stringWithUTF8String:str];
        }
        else
        {
            char textbuffer[101] = {0};

            if(type & DW_CFA_ULONG)
            {
                ULONG tmp = *((ULONG *)data);

                snprintf(textbuffer, 100, "%lu", tmp);
            }
            else if(type & DW_CFA_DATE)
            {
                struct tm curtm;
                CDATE cdate = *((CDATE *)data);

                memset(&curtm, 0, sizeof(curtm));
                curtm.tm_mday = cdate.day;
                curtm.tm_mon = cdate.month - 1;
                curtm.tm_year = cdate.year - 1900;

                strftime(textbuffer, 100, "%x", &curtm);
            }
            else if(type & DW_CFA_TIME)
            {
                struct tm curtm;
                CTIME ctime = *((CTIME *)data);

                memset( &curtm, 0, sizeof(curtm) );
                curtm.tm_hour = ctime.hours;
                curtm.tm_min = ctime.minutes;
                curtm.tm_sec = ctime.seconds;

                strftime(textbuffer, 100, "%X", &curtm);
            }
            if(textbuffer[0])
                text = [NSString stringWithUTF8String:textbuffer];
        }
    }

    id object = [cont getRow:(row+lastadd)];
    NSUInteger capacity = [[cont getColumns] count];

    /* If it is a cell, change the content of the cell */
    if([object isMemberOfClass:[DWTableViewCell class]])
    {
        DWTableViewCell *cell = object;

        if(column == 0)
        {
            if(icon)
                [[cell image] setImage:icon];
            else
                [[cell label] setText:text];
        }
        else if(icon || text)
        {
            NSMutableArray *cd = [cell columnData];

            if(column >= capacity)
                capacity = column + 1;

            if(!cd)
                cd = [NSMutableArray arrayWithCapacity:capacity];
            if(cd)
            {
                while([cd count] <= column)
                    [cd addObject:[NSNull null]];
                [cd replaceObjectAtIndex:column withObject:(text ? text : icon)];
            }
            [cell setColumnData:cd];
        }
    }
    else
    {
        NSMutableArray *cd  = [NSMutableArray arrayWithCapacity:(capacity > column ? capacity : column + 1)];
        if(cd)
            [cd replaceObjectAtIndex:column withObject:(text ? text : icon)];
        /* Otherwise replace it with a new cell */
        [cont editCell:_dw_table_cell_view_new(icon, text, cd) at:(row+lastadd)];
    }
    [cont setNeedsDisplay];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Changes an existing item in specified row and column to the given data.
 * Parameters:
 *          handle: Handle to the container window (widget).
 *          column: Zero based column of data being set.
 *          row: Zero based row of data being set.
 *          data: Pointer to the data to be added.
 */
void API dw_container_change_item(HWND handle, int column, int row, void *data)
{
    dw_container_set_item(handle, NULL, column, row, data);
}

/*
 * Changes an existing item in specified row and column to the given data.
 * Parameters:
 *          handle: Handle to the container window (widget).
 *          column: Zero based column of data being set.
 *          row: Zero based row of data being set.
 *          data: Pointer to the data to be added.
 */
void API dw_filesystem_change_item(HWND handle, int column, int row, void *data)
{
    dw_container_change_item(handle, column+1, row, data);
}

/*
 * Changes an item in specified row and column to the given data.
 * Parameters:
 *          handle: Handle to the container window (widget).
 *          pointer: Pointer to the allocated memory in dw_container_alloc().
 *          column: Zero based column of data being set.
 *          row: Zero based row of data being set.
 *          data: Pointer to the data to be added.
 */
void API dw_filesystem_change_file(HWND handle, int row, const char *filename, HICN icon)
{
    dw_filesystem_set_file(handle, NULL, row, filename, icon);
}

/*
 * Sets an item in specified row and column to the given data.
 * Parameters:
 *          handle: Handle to the container window (widget).
 *          pointer: Pointer to the allocated memory in dw_container_alloc().
 *          column: Zero based column of data being set.
 *          row: Zero based row of data being set.
 *          data: Pointer to the data to be added.
 */
DW_FUNCTION_DEFINITION(dw_filesystem_set_file, void, HWND handle, void *pointer, int row, const char *filename, HICN icon)
DW_FUNCTION_ADD_PARAM5(handle, pointer, row, filename, icon)
DW_FUNCTION_NO_RETURN(dw_filesystem_set_file)
DW_FUNCTION_RESTORE_PARAM5(handle, HWND, pointer, void *, row, int, filename, char *, icon, HICN)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    NSString *text = filename ? [NSString stringWithUTF8String:filename] : nil;
    int lastadd = 0;

    /* If pointer is NULL we are getting a change request instead of set */
    if(pointer)
        lastadd = [cont lastAddPoint];

    id object = [cont getRow:(row+lastadd)];
    
    /* If it is a cell, change the content of the cell */
    if([object isMemberOfClass:[DWTableViewCell class]])
    {
        DWTableViewCell *cell = object;

        if(icon)
            [[cell image] setImage:icon];
        if(text)
            [[cell label] setText:text];
    }
    else /* Otherwise replace it with a new cell */
        [cont editCell:_dw_table_cell_view_new(icon, text, nil) at:(row+lastadd)];
    [cont setNeedsDisplay];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets an item in specified row and column to the given data.
 * Parameters:
 *          handle: Handle to the container window (widget).
 *          pointer: Pointer to the allocated memory in dw_container_alloc().
 *          column: Zero based column of data being set.
 *          row: Zero based row of data being set.
 *          data: Pointer to the data to be added.
 */
void API dw_filesystem_set_item(HWND handle, void *pointer, int column, int row, void *data)
{
    dw_container_set_item(handle, pointer, column+1, row, data);
}

/*
 * Gets column type for a container column
 * Parameters:
 *          handle: Handle to the container window (widget).
 *          column: Zero based column.
 */
DW_FUNCTION_DEFINITION(dw_container_get_column_type, int, HWND handle, int column)
DW_FUNCTION_ADD_PARAM2(handle, column)
DW_FUNCTION_RETURN(dw_container_get_column_type, int)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, column, int)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    int rc;
    int flag = [cont cellType:column];
    if(flag & DW_CFA_BITMAPORICON)
        rc = DW_CFA_BITMAPORICON;
    else if(flag & DW_CFA_STRING)
        rc = DW_CFA_STRING;
    else if(flag & DW_CFA_ULONG)
        rc = DW_CFA_ULONG;
    else if(flag & DW_CFA_DATE)
        rc = DW_CFA_DATE;
    else if(flag & DW_CFA_TIME)
        rc = DW_CFA_TIME;
    else
        rc = 0;
    DW_FUNCTION_RETURN_THIS(rc);
}

/*
 * Gets column type for a filesystem container column
 * Parameters:
 *          handle: Handle to the container window (widget).
 *          column: Zero based column.
 */
int API dw_filesystem_get_column_type(HWND handle, int column)
{
    return dw_container_get_column_type(handle, column+1);
}

/*
 * Sets the alternating row colors for container window (widget) handle.
 * Parameters:
 *          handle: The window (widget) handle.
 *          oddcolor: Odd row background color in DW_RGB format or a default color index.
 *          evencolor: Even row background color in DW_RGB format or a default color index.
 *                    DW_RGB_TRANSPARENT will disable coloring rows.
 *                    DW_CLR_DEFAULT will use the system default alternating row colors.
 */
DW_FUNCTION_DEFINITION(dw_container_set_stripe, void, HWND handle, unsigned long oddcolor, unsigned long evencolor)
DW_FUNCTION_ADD_PARAM3(handle, oddcolor, evencolor)
DW_FUNCTION_NO_RETURN(dw_container_set_stripe)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, oddcolor, unsigned long, evencolor, unsigned long)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    [cont setRowBgOdd:oddcolor
              andEven:evencolor];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the width of a column in the container.
 * Parameters:
 *          handle: Handle to window (widget) of container.
 *          column: Zero based column of width being set.
 *          width: Width of column in pixels.
 */
void API dw_container_set_column_width(HWND handle, int column, int width)
{
}

/*
 * Sets the title of a row in the container.
 * Parameters:
 *          pointer: Pointer to the allocated memory in dw_container_alloc().
 *          row: Zero based row of data being set.
 *          title: String title of the item.
 */
DW_FUNCTION_DEFINITION(dw_container_set_row_title, void, void *pointer, int row, const char *title)
DW_FUNCTION_ADD_PARAM3(pointer, row, title)
DW_FUNCTION_NO_RETURN(dw_container_set_row_title)
DW_FUNCTION_RESTORE_PARAM3(pointer, void *, row, int, title, char *)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = pointer;
    int lastadd = [cont lastAddPoint];
    [cont setRow:(row+lastadd) title:title];
    DW_FUNCTION_RETURN_NOTHING;
}


/*
 * Sets the data pointer of a row in the container.
 * Parameters:
 *          pointer: Pointer to the allocated memory in dw_container_alloc().
 *          row: Zero based row of data being set.
 *          data: Data pointer.
 */
DW_FUNCTION_DEFINITION(dw_container_set_row_data, void, void *pointer, int row, void *data)
DW_FUNCTION_ADD_PARAM3(pointer, row, data)
DW_FUNCTION_NO_RETURN(dw_container_set_row_data)
DW_FUNCTION_RESTORE_PARAM3(pointer, void *, row, int, data, void *)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = pointer;
    int lastadd = [cont lastAddPoint];
    [cont setRowData:(row+lastadd) title:data];
    DW_FUNCTION_RETURN_NOTHING;
}


/*
 * Sets the title of a row in the container.
 * Parameters:
 *          handle: Handle to window (widget) of container.
 *          row: Zero based row of data being set.
 *          title: String title of the item.
 */
DW_FUNCTION_DEFINITION(dw_container_change_row_title, void, HWND handle, int row, const char *title)
DW_FUNCTION_ADD_PARAM3(handle, row, title)
DW_FUNCTION_NO_RETURN(dw_container_change_row_title)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, row, int, title, char *)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    [cont setRow:row title:title];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the data pointer of a row in the container.
 * Parameters:
 *          handle: Handle to window (widget) of container.
 *          row: Zero based row of data being set.
 *          data: Data pointer.
 */
DW_FUNCTION_DEFINITION(dw_container_change_row_data, void, HWND handle, int row, void *data)
DW_FUNCTION_ADD_PARAM3(handle, row, data)
DW_FUNCTION_NO_RETURN(dw_container_change_row_data)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, row, int, data, void *)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    [cont setRowData:row title:data];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the title of a row in the container.
 * Parameters:
 *          handle: Handle to the container window (widget).
 *          pointer: Pointer to the allocated memory in dw_container_alloc().
 *          rowcount: The number of rows to be inserted.
 */
DW_FUNCTION_DEFINITION(dw_container_insert, void, HWND handle, void *pointer, int rowcount)
DW_FUNCTION_ADD_PARAM3(handle, pointer, rowcount)
DW_FUNCTION_NO_RETURN(dw_container_insert)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, DW_UNUSED(pointer), void *, DW_UNUSED(rowcount), int)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    [cont reloadData];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Removes all rows from a container.
 * Parameters:
 *       handle: Handle to the window (widget) to be cleared.
 *       redraw: TRUE to cause the container to redraw immediately.
 */
DW_FUNCTION_DEFINITION(dw_container_clear, void, HWND handle, int redraw)
DW_FUNCTION_ADD_PARAM2(handle, redraw)
DW_FUNCTION_NO_RETURN(dw_container_clear)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, redraw, int)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    [cont clear];
    if(redraw)
        [cont reloadData];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Removes the first x rows from a container.
 * Parameters:
 *       handle: Handle to the window (widget) to be deleted from.
 *       rowcount: The number of rows to be deleted.
 */
DW_FUNCTION_DEFINITION(dw_container_delete, void, HWND handle, int rowcount)
DW_FUNCTION_ADD_PARAM2(handle, rowcount)
DW_FUNCTION_NO_RETURN(dw_container_delete)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, rowcount, int)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    int x;

    for(x=0;x<rowcount;x++)
    {
        [cont removeRow:0];
    }
    [cont reloadData];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Scrolls container up or down.
 * Parameters:
 *       handle: Handle to the window (widget) to be scrolled.
 *       direction: DW_SCROLL_UP, DW_SCROLL_DOWN, DW_SCROLL_TOP or
 *                  DW_SCROLL_BOTTOM. (rows is ignored for last two)
 *       rows: The number of rows to be scrolled.
 */
DW_FUNCTION_DEFINITION(dw_container_scroll, void, HWND handle, int direction, DW_UNUSED(long rows))
DW_FUNCTION_ADD_PARAM3(handle, direction, rows)
DW_FUNCTION_NO_RETURN(dw_container_scroll)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, direction, int, DW_UNUSED(rows), long)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    int rowcount = (int)[cont numberOfRowsInSection:0];
    CGPoint offset = [cont contentOffset];

    /* Safety check */
    if(rowcount < 1)
    {
        return;
    }

    switch(direction)
    {
        case DW_SCROLL_TOP:
        {
            offset.y = 0;
            break;
        }
        case DW_SCROLL_BOTTOM:
        {
            offset.y = [cont contentSize].height - [cont visibleSize].height;
            break;
        }
        /* TODO: Currently scrolling a full page, need to use row parameter instead */
        case DW_SCROLL_UP:
        {
            offset.y -= [cont visibleSize].height;
            break;
        }
        case DW_SCROLL_DOWN:
        {
            offset.y += [cont visibleSize].height;
            break;
        }
    }
    if(offset.y < 0)
        offset.y = 0;
    [cont setContentOffset:offset];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Starts a new query of a container.
 * Parameters:
 *       handle: Handle to the window (widget) to be queried.
 *       flags: If this parameter is DW_CRA_SELECTED it will only
 *              return items that are currently selected.  Otherwise
 *              it will return all records in the container.
 */
DW_FUNCTION_DEFINITION(dw_container_query_start, char *, HWND handle, unsigned long flags)
DW_FUNCTION_ADD_PARAM2(handle, flags)
DW_FUNCTION_RETURN(dw_container_query_start, char *)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, flags, unsigned long)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    NSArray *selected = [cont indexPathsForSelectedRows];
    NSIndexPath *result = [selected firstObject];
    void *retval = NULL;

    if(result)
    {
        if(flags & DW_CR_RETDATA)
            retval = [cont getRowData:(int)result.row];
        else
        {
            char *temp = [cont getRowTitle:(int)result.row];
            if(temp)
               retval = strdup(temp);
        }
        [cont setLastQueryPoint:1];
    }
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Continues an existing query of a container.
 * Parameters:
 *       handle: Handle to the window (widget) to be queried.
 *       flags: If this parameter is DW_CRA_SELECTED it will only
 *              return items that are currently selected.  Otherwise
 *              it will return all records in the container.
 */
DW_FUNCTION_DEFINITION(dw_container_query_next, char *, HWND handle, unsigned long flags)
DW_FUNCTION_ADD_PARAM2(handle, flags)
DW_FUNCTION_RETURN(dw_container_query_next, char *)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, flags, unsigned long)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    int lastQueryPoint = [cont lastQueryPoint];
    NSArray *selected = [cont indexPathsForSelectedRows];
    NSIndexPath *result = lastQueryPoint < [selected count] ?  [selected objectAtIndex:lastQueryPoint] : nil;
    void *retval = NULL;

    if(result)
    {
        if(flags & DW_CR_RETDATA)
            retval = [cont getRowData:(int)result.row];
        else
        {
            char *temp = [cont getRowTitle:(int)result.row];
            if(temp)
               retval = strdup(temp);
        }
        [cont setLastQueryPoint:(int)lastQueryPoint+1];
    }
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Cursors the item with the text speficied, and scrolls to that item.
 * Parameters:
 *       handle: Handle to the window (widget) to be queried.
 *       text:  Text usually returned by dw_container_query().
 */
DW_FUNCTION_DEFINITION(dw_container_cursor, void, HWND handle, const char *text)
DW_FUNCTION_ADD_PARAM2(handle, text)
DW_FUNCTION_NO_RETURN(dw_container_cursor)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, text, char *)
{
    DW_FUNCTION_INIT;
    DW_LOCAL_POOL_IN;
    DWContainer *cont = handle;
    char *thistext;
    int x, count = (int)[cont numberOfRowsInSection:0];

    for(x=0;x<count;x++)
    {
        thistext = [cont getRowTitle:x];

        if(thistext && strcmp(thistext, text) == 0)
        {
            NSIndexPath *ip = [NSIndexPath indexPathForRow:(NSUInteger)x inSection:0];

            [cont selectRowAtIndexPath:ip
                              animated:NO
                        scrollPosition:UITableViewScrollPositionNone];
            x=count;
            break;
        }
    }
    DW_LOCAL_POOL_OUT;
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Cursors the item with the data speficied, and scrolls to that item.
 * Parameters:
 *       handle: Handle to the window (widget) to be queried.
 *       data: Data associated with the row.
 */
DW_FUNCTION_DEFINITION(dw_container_cursor_by_data, void, HWND handle, void *data)
DW_FUNCTION_ADD_PARAM2(handle, data)
DW_FUNCTION_NO_RETURN(dw_container_cursor_by_data)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, data, void *)
{
    DW_FUNCTION_INIT;
    DW_LOCAL_POOL_IN;
    DWContainer *cont = handle;
    void *thisdata;
    int x, count = (int)[cont numberOfRowsInSection:0];

    for(x=0;x<count;x++)
    {
        thisdata = [cont getRowData:x];

        if(thisdata == data)
        {
            NSIndexPath *ip = [NSIndexPath indexPathForRow:(NSUInteger)x inSection:0];

            [cont selectRowAtIndexPath:ip
                              animated:NO
                        scrollPosition:UITableViewScrollPositionNone];
            x=count;
            break;
        }
    }
    DW_LOCAL_POOL_OUT;
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Deletes the item with the text speficied.
 * Parameters:
 *       handle: Handle to the window (widget).
 *       text:  Text usually returned by dw_container_query().
 */
DW_FUNCTION_DEFINITION(dw_container_delete_row, void, HWND handle, const char *text)
DW_FUNCTION_ADD_PARAM2(handle, text)
DW_FUNCTION_NO_RETURN(dw_container_delete_row)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, text, char *)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    char *thistext;
    int x, count = (int)[cont numberOfRowsInSection:0];

    for(x=0;x<count;x++)
    {
        thistext = [cont getRowTitle:x];

        if(thistext && strcmp(thistext, text) == 0)
        {
            [cont removeRow:x];
            [cont reloadData];
            x=count;
            break;
        }
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Deletes the item with the data speficied.
 * Parameters:
 *       handle: Handle to the window (widget).
 *       data: Data specified.
 */
DW_FUNCTION_DEFINITION(dw_container_delete_row_by_data, void, HWND handle, void *data)
DW_FUNCTION_ADD_PARAM2(handle, data)
DW_FUNCTION_NO_RETURN(dw_container_delete_row_by_data)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, data, void *)
{
    DW_FUNCTION_INIT;
    DWContainer *cont = handle;
    void *thisdata;
    int x, count = (int)[cont numberOfRowsInSection:0];

    for(x=0;x<count;x++)
    {
        thisdata = [cont getRowData:x];

        if(thisdata == data)
        {
            [cont removeRow:x];
            [cont reloadData];
            x=count;
            break;
        }
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Optimizes the column widths so that all data is visible.
 * Parameters:
 *       handle: Handle to the window (widget) to be optimized.
 */
void dw_container_optimize(HWND handle)
{
    /* TODO: Not sure if we need to implement this on iOS */
}

/*
 * Inserts an icon into the taskbar.
 * Parameters:
 *       handle: Window handle that will handle taskbar icon messages.
 *       icon: Icon handle to display in the taskbar.
 *       bubbletext: Text to show when the mouse is above the icon.
 */
void API dw_taskbar_insert(HWND handle, HICN icon, const char *bubbletext)
{
    /* Taskbar unsupported on iOS */
}

/*
 * Deletes an icon from the taskbar.
 * Parameters:
 *       handle: Window handle that was used with dw_taskbar_insert().
 *       icon: Icon handle that was used with dw_taskbar_insert().
 */
void API dw_taskbar_delete(HWND handle, HICN icon)
{
    /* Taskbar unsupported on iOS */
}

/* Internal function to keep HICNs from getting too big */
UIImage *_dw_icon_resize(UIImage *image)
{
    if(image)
    {
        CGSize size = [image size];
        if(size.width > 24 || size.height > 24)
        {
            if(size.width > 24)
                size.width = 24;
            if(size.height > 24)
                size.height = 24;
            // Pass 0.0 to use the current device's pixel scaling factor (and thus account for Retina resolution).
            // Pass 1.0 to force exact pixel size.
            UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
            [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
            UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            return newImage;
        }
    }
    return image;
}

/* Internal version that does not resize the image */
HICN _dw_icon_load(unsigned long resid)
{
    NSBundle *bundle = [NSBundle mainBundle];
    NSString *respath = [bundle resourcePath];
    NSString *filepath = [respath stringByAppendingFormat:@"/%lu.png", resid];
    UIImage *image = [[UIImage alloc] initWithContentsOfFile:filepath];
    return image;
}

/*
 * Obtains an icon from a module (or header in GTK).
 * Parameters:
 *          module: Handle to module (DLL) in OS/2 and Windows.
 *          id: A unsigned long id int the resources on OS/2 and
 *              Windows, on GTK this is converted to a pointer
 *              to an embedded XPM.
 */
HICN API dw_icon_load(unsigned long module, unsigned long resid)
{
    UIImage *image = _dw_icon_load(resid);
    return _dw_icon_resize(image);
}

/*
 * Obtains an icon from a file.
 * Parameters:
 *       filename: Name of the file, omit extention to have
 *                 DW pick the appropriate file extension.
 *                 (ICO on OS/2 or Windows, XPM on Unix)
 */
HICN API dw_icon_load_from_file(const char *filename)
{
    char *ext = _dw_get_image_extension( filename );

    NSString *nstr = [ NSString stringWithUTF8String:filename ];
    UIImage *image = [[UIImage alloc] initWithContentsOfFile:nstr];
    if(!image && ext)
    {
        nstr = [nstr stringByAppendingString: [NSString stringWithUTF8String:ext]];
        image = [[UIImage alloc] initWithContentsOfFile:nstr];
    }
    return _dw_icon_resize(image);
}

/*
 * Obtains an icon from data
 * Parameters:
 *       filename: Name of the file, omit extention to have
 *                 DW pick the appropriate file extension.
 *                 (ICO on OS/2 or Windows, XPM on Unix)
 */
HICN API dw_icon_load_from_data(const char *data, int len)
{
    NSData *thisdata = [NSData dataWithBytes:data length:len];
    UIImage *image = [[UIImage alloc] initWithData:thisdata];
    return _dw_icon_resize(image);
}

/*
 * Frees a loaded resource in OS/2 and Windows.
 * Parameters:
 *          handle: Handle to icon returned by dw_icon_load().
 */
void API dw_icon_free(HICN handle)
{
    UIImage *image = handle;
    DW_LOCAL_POOL_IN;
    [image release];
    DW_LOCAL_POOL_OUT;
}

/*
 * Create a new MDI Frame to be packed.
 * Parameters:
 *       id: An ID to be used with dw_window_from_id or 0L.
 */
HWND API dw_mdi_new(unsigned long cid)
{
    /* There isn't anything like quite like MDI on MacOS...
     * However we will make floating windows that hide
     * when the application is deactivated to simulate
     * similar behavior.
     */
    DWMDI *mdi = [[[DWMDI alloc] init] retain];
    [mdi setTag:cid];
    return mdi;
}

/*
 * Creates a splitbar window (widget) with given parameters.
 * Parameters:
 *       type: Value can be DW_VERT or DW_HORZ.
 *       topleft: Handle to the window to be top or left.
 *       bottomright:  Handle to the window to be bottom or right.
 * Returns:
 *       A handle to a splitbar window or NULL on failure.
 */
DW_FUNCTION_DEFINITION(dw_splitbar_new, HWND, int type, HWND topleft, HWND bottomright, unsigned long cid)
DW_FUNCTION_ADD_PARAM4(type, topleft, bottomright, cid)
DW_FUNCTION_RETURN(dw_splitbar_new, HWND)
DW_FUNCTION_RESTORE_PARAM4(type, int, topleft, HWND, bottomright, HWND, cid, unsigned long)
{
    DW_FUNCTION_INIT;
    id tmpbox = dw_box_new(DW_VERT, 0);
    DWSplitBar *split = [[DWSplitBar alloc] init];
    dw_box_pack_start(tmpbox, topleft, 0, 0, TRUE, TRUE, 0);
    [split setTopLeft:tmpbox];
    tmpbox = dw_box_new(DW_VERT, 0);
    dw_box_pack_start(tmpbox, bottomright, 0, 0, TRUE, TRUE, 0);
    [split setBottomRight:tmpbox];
    [split setPercent:50.0];
    [split setType:type];
    [split setTag:cid];
    DW_FUNCTION_RETURN_THIS(split);
}

/*
 * Sets the position of a splitbar (pecentage).
 * Parameters:
 *       handle: The handle to the splitbar returned by dw_splitbar_new().
 *       percent: The position of the splitbar.
 */
DW_FUNCTION_DEFINITION(dw_splitbar_set, void, HWND handle, float percent)
DW_FUNCTION_ADD_PARAM2(handle, percent)
DW_FUNCTION_NO_RETURN(dw_splitbar_set)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, percent, float)
{
    DW_FUNCTION_INIT;
    DWSplitBar *split = handle;
    [split setPercent:percent];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Gets the position of a splitbar (pecentage).
 * Parameters:
 *       handle: The handle to the splitbar returned by dw_splitbar_new().
 */
float API dw_splitbar_get(HWND handle)
{
    DWSplitBar *split = handle;
    float retval = 50.0;

    if(split)
        retval = [split percent];
    return retval;
}

/* Internal function to convert fontname to UIFont */
UIFont *_dw_font_by_name(const char *fontname)
{
    UIFont *font = DWDefaultFont;
    
    if(fontname)
    {
        char *name = strchr(fontname, '.');

        if(name && (name++))
        {
            UIFontDescriptorSymbolicTraits traits = 0;
            UIFontDescriptor* fd;
            int size = atoi(fontname);
            char *Italic = strstr(name, " Italic");
            char *Bold = strstr(name, " Bold");
            size_t len = (Italic ? (Bold ? (Italic > Bold ? (Bold - name) : (Italic - name)) : (Italic - name)) : (Bold ? (Bold - name) : strlen(name)));
            char *newname = alloca(len+1);

            memset(newname, 0, len+1);
            strncpy(newname, name, len);

            if(Bold)
                traits |= UIFontDescriptorTraitBold;
            if(Italic)
                traits |= UIFontDescriptorTraitItalic;

            fd = [UIFontDescriptor
                    fontDescriptorWithFontAttributes:@{UIFontDescriptorFamilyAttribute:[NSString stringWithUTF8String:newname],
                    UIFontDescriptorTraitsAttribute: @{UIFontSymbolicTrait:[NSNumber numberWithInteger:traits]}}];
            NSArray* matches = [fd matchingFontDescriptorsWithMandatoryKeys:
                                  [NSSet setWithObjects:UIFontDescriptorFamilyAttribute, UIFontDescriptorTraitsAttribute, nil]];
            if(matches.count != 0)
                font = [UIFont fontWithDescriptor:matches[0] size:size];
        }
    }
    return font;
}

/*
 * Create a bitmap object to be packed.
 * Parameters:
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_bitmap_new, HWND, ULONG cid)
DW_FUNCTION_ADD_PARAM1(cid)
DW_FUNCTION_RETURN(dw_bitmap_new, HWND)
DW_FUNCTION_RESTORE_PARAM1(cid, ULONG)
{
    DW_FUNCTION_INIT;
    UIImageView *bitmap = [[[UIImageView alloc] init] retain];
    [bitmap setTag:cid];
    DW_FUNCTION_RETURN_THIS(bitmap);
}

/*
 * Creates a pixmap with given parameters.
 * Parameters:
 *       handle: Window handle the pixmap is associated with.
 *       width: Width of the pixmap in pixels.
 *       height: Height of the pixmap in pixels.
 *       depth: Color depth of the pixmap.
 * Returns:
 *       A handle to a pixmap or NULL on failure.
 */
HPIXMAP API dw_pixmap_new(HWND handle, unsigned long width, unsigned long height, int depth)
{
    HPIXMAP pixmap = NULL;

    if((pixmap = calloc(1,sizeof(struct _hpixmap))))
    {
        pixmap->width = width;
        pixmap->height = height;
        pixmap->handle = handle;
        pixmap->image = [[[DWImage alloc] initWithSize:CGSizeMake(width,height)] retain];
    }
    return pixmap;
}

/*
 * Creates a pixmap from a file.
 * Parameters:
 *       handle: Window handle the pixmap is associated with.
 *       filename: Name of the file, omit extention to have
 *                 DW pick the appropriate file extension.
 *                 (BMP on OS/2 or Windows, XPM on Unix)
 * Returns:
 *       A handle to a pixmap or NULL on failure.
 */
HPIXMAP API dw_pixmap_new_from_file(HWND handle, const char *filename)
{
    HPIXMAP pixmap;
    DW_LOCAL_POOL_IN;
    char *ext = _dw_get_image_extension(filename);

    if(!(pixmap = calloc(1,sizeof(struct _hpixmap))))
    {
        DW_LOCAL_POOL_OUT;
        return NULL;
    }
    NSString *nstr = [ NSString stringWithUTF8String:filename ];
    UIImage *tmpimage = [[UIImage alloc] initWithContentsOfFile:nstr];
    if(!tmpimage && ext)
    {
        nstr = [nstr stringByAppendingString: [NSString stringWithUTF8String:ext]];
        tmpimage = [[UIImage alloc] initWithContentsOfFile:nstr];
    }
    if(!tmpimage)
    {
        DW_LOCAL_POOL_OUT;
        return NULL;
    }
    pixmap->width = [tmpimage size].width;
    pixmap->height = [tmpimage size].height;
    pixmap->image = [[[DWImage alloc] initWithUIImage:tmpimage] retain];
    pixmap->handle = handle;
    DW_LOCAL_POOL_OUT;
    return pixmap;
}

/*
 * Creates a pixmap from memory.
 * Parameters:
 *       handle: Window handle the pixmap is associated with.
 *       data: Source of the image data
 *                 (BMP on OS/2 or Windows, XPM on Unix)
 *       le: length of data
 * Returns:
 *       A handle to a pixmap or NULL on failure.
 */
HPIXMAP API dw_pixmap_new_from_data(HWND handle, const char *data, int len)
{
    HPIXMAP pixmap;
    DW_LOCAL_POOL_IN;

    if(!(pixmap = calloc(1,sizeof(struct _hpixmap))))
    {
        DW_LOCAL_POOL_OUT;
        return NULL;
    }
    NSData *thisdata = [NSData dataWithBytes:data length:len];
    UIImage *tmpimage = [[UIImage alloc] initWithData:thisdata];
    if(!tmpimage)
    {
        DW_LOCAL_POOL_OUT;
        return NULL;
    }
    pixmap->width = [tmpimage size].width;
    pixmap->height = [tmpimage size].height;
    pixmap->image = [[[DWImage alloc] initWithUIImage:tmpimage] retain];
    pixmap->handle = handle;
    DW_LOCAL_POOL_OUT;
    return pixmap;
}

/*
 * Sets the transparent color for a pixmap
 * Parameters:
 *       pixmap: Handle to a pixmap returned by
 *               dw_pixmap_new..
 *       color:  transparent color
 * Note: This does nothing on Mac as transparency
 *       is handled automatically
 */
void API dw_pixmap_set_transparent_color(HPIXMAP pixmap, ULONG color)
{
    /* Don't do anything, all images support transparency on iOS */
}

/*
 * Creates a pixmap from internal resource graphic specified by id.
 * Parameters:
 *       handle: Window handle the pixmap is associated with.
 *       id: Resource ID associated with requested pixmap.
 * Returns:
 *       A handle to a pixmap or NULL on failure.
 */
HPIXMAP API dw_pixmap_grab(HWND handle, ULONG resid)
{
    HPIXMAP pixmap;
    DW_LOCAL_POOL_IN;

    if(!(pixmap = calloc(1,sizeof(struct _hpixmap))))
    {
        DW_LOCAL_POOL_OUT;
        return NULL;
    }

    NSBundle *bundle = [NSBundle mainBundle];
    NSString *respath = [bundle resourcePath];
    NSString *filepath = [respath stringByAppendingFormat:@"/%lu.png", resid];
    UIImage *tmpimage = [[UIImage alloc] initWithContentsOfFile:filepath];

    if(tmpimage)
    {
        pixmap->width = [tmpimage size].width;
        pixmap->height = [tmpimage size].height;
        pixmap->image = [[DWImage alloc] initWithUIImage:tmpimage];
        pixmap->handle = handle;
        DW_LOCAL_POOL_OUT;
        return pixmap;
    }
    free(pixmap);
    DW_LOCAL_POOL_OUT;
    return NULL;
}

/*
 * Sets the font used by a specified pixmap.
 * Normally the pixmap font is obtained from the associated window handle.
 * However this can be used to override that, or for pixmaps with no window.
 * Parameters:
 *          pixmap: Handle to a pixmap returned by dw_pixmap_new() or
 *                  passed to the application via a callback.
 *          fontname: Name and size of the font in the form "size.fontname"
 * Returns:
 *       DW_ERROR_NONE on success and DW_ERROR_GENERAL on failure.
 */
int API dw_pixmap_set_font(HPIXMAP pixmap, const char *fontname)
{
    if(pixmap)
    {
        UIFont *font = _dw_font_by_name(fontname);

        if(font)
        {
            DW_LOCAL_POOL_IN;
            UIFont *oldfont = pixmap->font;
            [font retain];
            pixmap->font = font;
            if(oldfont)
                [oldfont release];
            DW_LOCAL_POOL_OUT;
            return DW_ERROR_NONE;
        }
    }
    return DW_ERROR_GENERAL;
}

/*
 * Destroys an allocated pixmap.
 * Parameters:
 *       pixmap: Handle to a pixmap returned by
 *               dw_pixmap_new..
 */
void API dw_pixmap_destroy(HPIXMAP pixmap)
{
    if(pixmap)
    {
        DWImage *image = (DWImage *)pixmap->image;
        UIFont *font = pixmap->font;
        DW_LOCAL_POOL_IN;
        [image release];
        [font release];
        free(pixmap);
        DW_LOCAL_POOL_OUT;
    }
}

/*
 * Copies from one item to another.
 * Parameters:
 *       dest: Destination window handle.
 *       destp: Destination pixmap. (choose only one).
 *       xdest: X coordinate of destination.
 *       ydest: Y coordinate of destination.
 *       width: Width of area to copy.
 *       height: Height of area to copy.
 *       src: Source window handle.
 *       srcp: Source pixmap. (choose only one).
 *       xsrc: X coordinate of source.
 *       ysrc: Y coordinate of source.
 */
void API dw_pixmap_bitblt(HWND dest, HPIXMAP destp, int xdest, int ydest, int width, int height, HWND src, HPIXMAP srcp, int xsrc, int ysrc)
{
    dw_pixmap_stretch_bitblt(dest, destp, xdest, ydest, width, height, src, srcp, xsrc, ysrc, -1, -1);
}

/*
 * Copies from one surface to another allowing for stretching.
 * Parameters:
 *       dest: Destination window handle.
 *       destp: Destination pixmap. (choose only one).
 *       xdest: X coordinate of destination.
 *       ydest: Y coordinate of destination.
 *       width: Width of the target area.
 *       height: Height of the target area.
 *       src: Source window handle.
 *       srcp: Source pixmap. (choose only one).
 *       xsrc: X coordinate of source.
 *       ysrc: Y coordinate of source.
 *       srcwidth: Width of area to copy.
 *       srcheight: Height of area to copy.
 * Returns:
 *       DW_ERROR_NONE on success and DW_ERROR_GENERAL on failure.
 */
int API dw_pixmap_stretch_bitblt(HWND dest, HPIXMAP destp, int xdest, int ydest, int width, int height, HWND src, HPIXMAP srcp, int xsrc, int ysrc, int srcwidth, int srcheight)
{
    DWBitBlt *bltinfo;
    NSValue* bi;
    DW_LOCAL_POOL_IN;

    /* Sanity checks */
    if((!dest && !destp) || (!src && !srcp) ||
       ((srcwidth == -1 || srcheight == -1) && srcwidth != srcheight))
    {
        DW_LOCAL_POOL_OUT;
        return DW_ERROR_GENERAL;
    }

    bltinfo = calloc(1, sizeof(DWBitBlt));
    bi = [NSValue valueWithPointer:bltinfo];

    /* Fill in the information */
    bltinfo->dest = _dw_dest_image(destp, dest);
    bltinfo->src = src;
    bltinfo->xdest = xdest;
    bltinfo->ydest = ydest;
    bltinfo->width = width;
    bltinfo->height = height;
    bltinfo->xsrc = xsrc;
    bltinfo->ysrc = ysrc;
    bltinfo->srcwidth = srcwidth;
    bltinfo->srcheight = srcheight;

    if(srcp)
    {
        id object = bltinfo->src = (id)srcp->image;
        [object retain];
    }
    [DWObj safeCall:@selector(doBitBlt:) withObject:bi];
    DW_LOCAL_POOL_OUT;
    return DW_ERROR_NONE;
}

/*
 * Create a new static text window (widget) to be packed.
 * Not available under OS/2, eCS
 * Parameters:
 *       text: The text to be display by the static text widget.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_calendar_new, HWND, ULONG cid)
DW_FUNCTION_ADD_PARAM1(cid)
DW_FUNCTION_RETURN(dw_calendar_new, HWND)
DW_FUNCTION_RESTORE_PARAM1(cid, ULONG)
{
    DWCalendar *calendar = [[[DWCalendar alloc] init] retain];
    if (@available(iOS 14.0, *)) {
        [calendar setPreferredDatePickerStyle:UIDatePickerStyleInline];
    } else {
        // We really want the iOS 14 style to match the other platforms...
        // but just leave it the default spinner style on 13 and earlier.
    }
    [calendar setDatePickerMode:UIDatePickerModeDate];
    [calendar setDate:[NSDate date]];
    [calendar setTag:cid];
    DW_FUNCTION_RETURN_THIS(calendar);
}

/*
 * Sets the current date of a calendar.
 * Parameters:
 *       handle: The handle to the calendar returned by dw_calendar_new().
 *       year...
 */
DW_FUNCTION_DEFINITION(dw_calendar_set_date, void, HWND handle, unsigned int year, unsigned int month, unsigned int day)
DW_FUNCTION_ADD_PARAM4(handle, year, month, day)
DW_FUNCTION_NO_RETURN(dw_calendar_set_date)
DW_FUNCTION_RESTORE_PARAM4(handle, HWND, year, unsigned int, month, unsigned int, day, unsigned int)
{
    DWCalendar *calendar = handle;
    NSDate *date;
    char buffer[101] = {0};

    snprintf(buffer, 100, "%04d-%02d-%02d", year, month, day);

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.dateFormat = @"yyyy-MM-dd";

    date = [dateFormatter dateFromString:[NSString stringWithUTF8String:buffer]];
    [calendar setDate:date];
    [date release];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Gets the current date of a calendar.
 * Parameters:
 *       handle: The handle to the calendar returned by dw_calendar_new().
 */
DW_FUNCTION_DEFINITION(dw_calendar_get_date, void, HWND handle, unsigned int *year, unsigned int *month, unsigned int *day)
DW_FUNCTION_ADD_PARAM4(handle, year, month, day)
DW_FUNCTION_NO_RETURN(dw_calendar_get_date)
DW_FUNCTION_RESTORE_PARAM4(handle, HWND, year, unsigned int *, month, unsigned int *, day, unsigned int *)
{
    DWCalendar *calendar = handle;
    NSCalendar *mycalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    NSDate *date = [calendar date];
    NSDateComponents* components = [mycalendar components:NSCalendarUnitDay|NSCalendarUnitMonth|NSCalendarUnitYear fromDate:date];
    *day = (unsigned int)[components day];
    *month = (unsigned int)[components month];
    *year = (unsigned int)[components year];
    [mycalendar release];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Causes the embedded HTML widget to take action.
 * Parameters:
 *       handle: Handle to the window.
 *       action: One of the DW_HTML_* constants.
 */
DW_FUNCTION_DEFINITION(dw_html_action, void, HWND handle, int action)
DW_FUNCTION_ADD_PARAM2(handle, action)
DW_FUNCTION_NO_RETURN(dw_html_action)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, action, int)
{
    DWWebView *html = handle;
    switch(action)
    {
        case DW_HTML_GOBACK:
            [html goBack];
            break;
        case DW_HTML_GOFORWARD:
            [html goForward];
            break;
        case DW_HTML_GOHOME:
            dw_html_url(handle, DW_HOME_URL);
            break;
        case DW_HTML_SEARCH:
            break;
        case DW_HTML_RELOAD:
            [html reload];
            break;
        case DW_HTML_STOP:
            [html stopLoading];
            break;
        case DW_HTML_PRINT:
            break;
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Render raw HTML code in the embedded HTML widget..
 * Parameters:
 *       handle: Handle to the window.
 *       string: String buffer containt HTML code to
 *               be rendered.
 * Returns:
 *       0 on success.
 */
DW_FUNCTION_DEFINITION(dw_html_raw, int, HWND handle, const char *string)
DW_FUNCTION_ADD_PARAM2(handle, string)
DW_FUNCTION_RETURN(dw_html_raw, int)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, string, const char *)
{
    DWWebView *html = handle;
    int retval = DW_ERROR_NONE;
    [html loadHTMLString:[ NSString stringWithUTF8String:string ] baseURL:nil];
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Render file or web page in the embedded HTML widget..
 * Parameters:
 *       handle: Handle to the window.
 *       url: Universal Resource Locator of the web or
 *               file object to be rendered.
 * Returns:
 *       0 on success.
 */
DW_FUNCTION_DEFINITION(dw_html_url, int, HWND handle, const char *url)
DW_FUNCTION_ADD_PARAM2(handle, url)
DW_FUNCTION_RETURN(dw_html_url, int)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, url, const char *)
{
    DWWebView *html = handle;
    int retval = DW_ERROR_NONE;
    [html loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[ NSString stringWithUTF8String:url ]]]];
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Executes the javascript contained in "script" in the HTML window.
 * Parameters:
 *       handle: Handle to the HTML window.
 *       script: Javascript code to execute.
 *       scriptdata: Data passed to the signal handler.
 * Notes: A DW_SIGNAL_HTML_RESULT event will be raised with scriptdata.
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
DW_FUNCTION_DEFINITION(dw_html_javascript_run, int, HWND handle, const char *script, void *scriptdata)
DW_FUNCTION_ADD_PARAM3(handle, script, scriptdata)
DW_FUNCTION_RETURN(dw_html_javascript_run, int)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, script, const char *, scriptdata, void *)
{
    DWWebView *html = handle;
    int retval = DW_ERROR_NONE;
    DW_LOCAL_POOL_IN;

    [html evaluateJavaScript:[NSString stringWithUTF8String:script] completionHandler:^(NSString *result, NSError *error)
    {
        void *params[2] = { result, scriptdata };
        _dw_event_handler(html, (id)params, _DW_EVENT_HTML_RESULT);
    }];
    DW_LOCAL_POOL_OUT;
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Create a new HTML window (widget) to be packed.
 * Not available under OS/2, eCS
 * Parameters:
 *       text: The default text to be in the entryfield widget.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 */
DW_FUNCTION_DEFINITION(dw_html_new, HWND, ULONG cid)
DW_FUNCTION_ADD_PARAM1(cid)
DW_FUNCTION_RETURN(dw_html_new, HWND)
DW_FUNCTION_RESTORE_PARAM1(DW_UNUSED(cid), ULONG)
{
    DW_FUNCTION_INIT;
    DWWebView *web = [[[DWWebView alloc] init] retain];
    web.navigationDelegate = web;
    [web setTag:cid];
    DW_FUNCTION_RETURN_THIS(web);
}

/*
 * Returns the current X and Y coordinates of the mouse pointer.
 * Parameters:
 *       x: Pointer to variable to store X coordinate.
 *       y: Pointer to variable to store Y coordinate.
 */
void API dw_pointer_query_pos(long *x, long *y)
{
    if(x)
    {
        *x = (long)_dw_event_point.x;
    }
    if(y)
    {
        *y = (long)_dw_event_point.y;
    }
}

/*
 * Sets the X and Y coordinates of the mouse pointer.
 * Parameters:
 *       x: X coordinate.
 *       y: Y coordinate.
 */
void API dw_pointer_set_pos(long x, long y)
{
    /* From what I have read this isn't possible, agaist human interface rules */
}

/*
 * Create a menu object to be popped up.
 * Parameters:
 *       id: An ID to be used for getting the resource from the
 *           resource file.
 */
HMENUI API dw_menu_new(ULONG cid)
{
    DWMenu *menu = [[[DWMenu alloc] initWithTag:cid] retain];
    return menu;
}

/*
 * Create a menubar on a window.
 * Parameters:
 *       location: Handle of a window frame to be attached to.
 */
HMENUI API dw_menubar_new(HWND location)
{
    DWWindow *window = location;
    DWMenu *menu = [[[DWMenu alloc] initWithTag:0] retain];

    [window setMenu:menu];
    return menu;
}

/*
 * Destroys a menu created with dw_menubar_new or dw_menu_new.
 * Parameters:
 *       menu: Handle of a menu.
 */
void API dw_menu_destroy(HMENUI *menu)
{
    if(menu)
    {
        DWMenu *thismenu = *menu;
        DW_LOCAL_POOL_IN;
        [thismenu release];
        DW_LOCAL_POOL_OUT;
        *menu = NULL;
    }
}

/*
 * Pops up a context menu at given x and y coordinates.
 * Parameters:
 *       menu: The handle the the existing menu.
 *       parent: Handle to the window initiating the popup.
 *       x: X coordinate.
 *       y: Y coordinate.
 */
void API dw_menu_popup(HMENUI *menu, HWND parent, int x, int y)
{
    if(menu)
    {
        DWMenu *thismenu = (DWMenu *)*menu;
        id object = parent;
        DWWindow *window = [object isMemberOfClass:[DWWindow class]] ? object : (DWWindow *)[object window];
        [window setPopupMenu:thismenu];
        *menu = nil;
    }
}

char _dw_removetilde(char *dest, const char *src)
{
    int z, cur=0;
    char accel = '\0';

    for(z=0;z<strlen(src);z++)
    {
        if(src[z] != '~')
        {
            dest[cur] = src[z];
            cur++;
        }
        else
        {
            accel = src[z+1];
        }
    }
    dest[cur] = 0;
    return accel;
}

/*
 * Adds a menuitem or submenu to an existing menu.
 * Parameters:
 *       menu: The handle the the existing menu.
 *       title: The title text on the menu item to be added.
 *       id: An ID to be used for message passing.
 *       flags: Extended attributes to set on the menu.
 *       end: If TRUE memu is positioned at the end of the menu.
 *       check: If TRUE menu is "check"able.
 *       flags: Extended attributes to set on the menu.
 *       submenu: Handle to an existing menu to be a submenu or NULL.
 */
HWND API dw_menu_append_item(HMENUI menux, const char *title, ULONG itemid, ULONG flags, int end, int check, HMENUI submenux)
{
    DWMenu *menu = menux;
    __block DWMenuItem *item = nil;
    if(!title || strlen(title) == 0)
        [menu addItem:[[NSNull alloc] init]];
    else
    {
        char accel[2];
        char *newtitle = malloc(strlen(title)+1);
        NSString *nstr;
        
        accel[0] = _dw_removetilde(newtitle, title);
        accel[1] = 0;

        nstr = [NSString stringWithUTF8String:newtitle];
        free(newtitle);

        item = [DWMenuItem actionWithTitle:nstr image:nil identifier:nil
                                    handler:^(__kindof UIAction * _Nonnull action) {
            if(check)
            {
                UIMenuElementState state = [item state] == UIMenuElementStateOn ? UIMenuElementStateOff : UIMenuElementStateOn;
                [item setState:state];
            }
            [DWObj menuHandler:item];
            if(check)
                [DWObj safeCall:@selector(updateMenu:) withObject:nil];
        }];
        /* Don't set the tag if the ID is 0 or -1 */
        if(itemid != DW_MENU_AUTO && itemid != DW_MENU_POPUP)
            [item setTag:itemid];
        [menu addItem:item];
        
        if(check)
        {
            [item setCheck:YES];
            if(flags & DW_MIS_CHECKED)
                [item setState:UIMenuElementStateOn];
        }
        [item setEnabled:(flags & DW_MIS_DISABLED ? NO : YES)];

        if(submenux)
        {
            DWMenu *submenu = submenux;

            [submenu setTitle:nstr];
            [item setSubmenu:submenu];
        }
        return item;
    }
    return item;
}

/*
 * Sets the state of a menu item check.
 * Deprecated; use dw_menu_item_set_state()
 * Parameters:
 *       menu: The handle the the existing menu.
 *       id: Menuitem id.
 *       check: TRUE for checked FALSE for not checked.
 */
void API dw_menu_item_set_check(HMENUI menux, unsigned long itemid, int check)
{
    id menu = menux;
    DWMenuItem *menuitem = [menu childWithTag:itemid];

    if(menuitem != nil)
    {
        UIMenuElementState newstate = check ? UIMenuElementStateOn : UIMenuElementStateOff;

        /* Only force the menus to repopulate if something changed */
        if(newstate != [menuitem state])
        {
            [menuitem setState:newstate];
            [DWObj safeCall:@selector(updateMenu:) withObject:nil];
        }
    }
}

/*
 * Deletes the menu item specified.
 * Parameters:
 *       menu: The handle to the  menu in which the item was appended.
 *       id: Menuitem id.
 * Returns:
 *       DW_ERROR_NONE (0) on success or DW_ERROR_UNKNOWN on failure.
 */
int API dw_menu_delete_item(HMENUI menux, unsigned long itemid)
{
    id menu = menux;
    DWMenuItem *menuitem = [menu childWithTag:itemid];

    if(menuitem != nil)
    {
        [menu removeItem:menuitem];
        return DW_ERROR_NONE;
    }
    return DW_ERROR_UNKNOWN;
}

/*
 * Sets the state of a menu item.
 * Parameters:
 *       menu: The handle to the existing menu.
 *       id: Menuitem id.
 *       flags: DW_MIS_ENABLED/DW_MIS_DISABLED
 *              DW_MIS_CHECKED/DW_MIS_UNCHECKED
 */
void API dw_menu_item_set_state(HMENUI menux, unsigned long itemid, unsigned long state)
{
    id menu = menux;
    DWMenuItem *menuitem = [menu childWithTag:itemid];

    if(menuitem != nil)
    {
        UIMenuElementState oldstate = [menuitem state];
        BOOL oldenabled = [menuitem enabled];

        if(state & DW_MIS_CHECKED)
            [menuitem setState:UIMenuElementStateOn];
        else if(state & DW_MIS_UNCHECKED)
            [menuitem setState:UIMenuElementStateOff];
        if(state & DW_MIS_ENABLED)
            [menuitem setEnabled:YES];
        else if(state & DW_MIS_DISABLED)
            [menuitem setEnabled:NO];

        /* Only force the menus to repopulate if something changed */
        if(oldstate != [menuitem state] || oldenabled != [menuitem enabled])
            [DWObj safeCall:@selector(updateMenu:) withObject:nil];
    }
}

/* Gets the notebook page from associated ID */
DWNotebookPage *_dw_notepage_from_id(DWNotebook *notebook, unsigned long pageid)
{
    NSArray *pages = [notebook views];
    for(DWNotebookPage *notepage in pages)
    {
        if([notepage pageid] == pageid)
        {
            return notepage;
        }
    }
    return nil;
}

/*
 * Create a notebook object to be packed.
 * Parameters:
 *       id: An ID to be used for getting the resource from the
 *           resource file.
 */
DW_FUNCTION_DEFINITION(dw_notebook_new, HWND, ULONG cid, DW_UNUSED(int top))
DW_FUNCTION_ADD_PARAM2(cid, top)
DW_FUNCTION_RETURN(dw_notebook_new, HWND)
DW_FUNCTION_RESTORE_PARAM2(cid, ULONG, DW_UNUSED(top), int)
{
    DWNotebook *notebook = [[[DWNotebook alloc] init] retain];
    [[notebook tabs] addTarget:notebook
                        action:@selector(pageChanged:)
              forControlEvents:UIControlEventValueChanged];
    [notebook setTag:cid];
    DW_FUNCTION_RETURN_THIS(notebook);
}

/*
 * Adds a new page to specified notebook.
 * Parameters:
 *          handle: Window (widget) handle.
 *          flags: Any additional page creation flags.
 *          front: If TRUE page is added at the beginning.
 */
DW_FUNCTION_DEFINITION(dw_notebook_page_new, ULONG, HWND handle, DW_UNUSED(ULONG flags), int front)
DW_FUNCTION_ADD_PARAM3(handle, flags, front)
DW_FUNCTION_RETURN(dw_notebook_page_new, ULONG)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, DW_UNUSED(flags), ULONG, front, int)
{
    DWNotebook *notebook = handle;
    NSInteger page = [notebook pageid];
    DWNotebookPage *notepage = [[DWNotebookPage alloc] init];
    NSMutableArray<DWNotebookPage *> *views = [notebook views];
    unsigned long retval;

    [notepage setPageid:(int)page];
    if(front)
    {
        [[notebook tabs] insertSegmentWithTitle:@"" atIndex:(NSInteger)0 animated:NO];
        [views addObject:notepage];
    }
    else
    {
        [[notebook tabs] insertSegmentWithTitle:@"" atIndex:[[notebook tabs] numberOfSegments] animated:NO];
        [views addObject:notepage];
    }
    [notebook addSubview:notepage];
    [notebook setPageid:(int)(page+1)];

    if([views count] != 1)
    {
        /* Otherwise hide the page */
        [notepage setHidden:YES];
    }

    retval = (unsigned long)page;
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Remove a page from a notebook.
 * Parameters:
 *          handle: Handle to the notebook widget.
 *          pageid: ID of the page to be destroyed.
 */
DW_FUNCTION_DEFINITION(dw_notebook_page_destroy, void, HWND handle, unsigned long pageid)
DW_FUNCTION_ADD_PARAM2(handle, pageid)
DW_FUNCTION_NO_RETURN(dw_notebook_page_destroy)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, pageid, unsigned long)
{
    DWNotebook *notebook = handle;
    DWNotebookPage *notepage = _dw_notepage_from_id(notebook, pageid);
    DW_LOCAL_POOL_IN;

    if(notepage != nil)
    {
        NSMutableArray<DWNotebookPage *> *views = [notebook views];
        NSUInteger index = [views indexOfObject:notepage];

        if(index != NSNotFound)
        {
            [[notebook tabs] removeSegmentAtIndex:index animated:NO];
            [notepage removeFromSuperview];
            [views removeObject:notepage];
            index--;
            if(index >= 0 && index < [views count])
               [[notebook tabs] setSelectedSegmentIndex:index];
            else if([views count] > 0)
               [[notebook tabs] setSelectedSegmentIndex:0];
            [notebook pageChanged:nil];
            [notepage release];
        }
    }
    DW_LOCAL_POOL_OUT;
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Queries the currently visible page ID.
 * Parameters:
 *          handle: Handle to the notebook widget.
 */
DW_FUNCTION_DEFINITION(dw_notebook_page_get, unsigned long, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_RETURN(dw_notebook_page_get, unsigned long)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DWNotebook *notebook = handle;
    NSInteger index = [[notebook tabs] selectedSegmentIndex];
    unsigned long retval = 0;
    
    if(index != -1)
    {
        NSMutableArray<DWNotebookPage *> *views = [notebook views];
        DWNotebookPage *notepage = [views objectAtIndex:index];

        retval = [notepage pageid];
    }
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Sets the currently visibale page ID.
 * Parameters:
 *          handle: Handle to the notebook widget.
 *          pageid: ID of the page to be made visible.
 */
DW_FUNCTION_DEFINITION(dw_notebook_page_set, void, HWND handle, unsigned long pageid)
DW_FUNCTION_ADD_PARAM2(handle, pageid)
DW_FUNCTION_NO_RETURN(dw_notebook_page_set)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, pageid, unsigned long)
{
    DWNotebook *notebook = handle;
    DWNotebookPage *notepage = _dw_notepage_from_id(notebook, pageid);

    if(notepage != nil)
    {
        NSMutableArray<DWNotebookPage *> *views = [notebook views];
        NSUInteger index = [views indexOfObject:notepage];

        if(index != -1)
        {
            [[notebook tabs] setSelectedSegmentIndex:index];
            [notebook pageChanged:nil];
        }
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the text on the specified notebook tab.
 * Parameters:
 *          handle: Notebook handle.
 *          pageid: Page ID of the tab to set.
 *          text: Pointer to the text to set.
 */
DW_FUNCTION_DEFINITION(dw_notebook_page_set_text, void, HWND handle, ULONG pageid, const char *text)
DW_FUNCTION_ADD_PARAM3(handle, pageid, text)
DW_FUNCTION_NO_RETURN(dw_notebook_page_set_text)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, pageid, ULONG, text, const char *)
{
    DWNotebook *notebook = handle;
    DWNotebookPage *notepage = _dw_notepage_from_id(notebook, pageid);

    if(notepage != nil)
    {
        NSMutableArray<DWNotebookPage *> *views = [notebook views];
        NSUInteger index = [views indexOfObject:notepage];

        if(index != -1)
            [[notebook tabs] setTitle:[NSString stringWithUTF8String:text] forSegmentAtIndex:index];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the text on the specified notebook tab status area.
 * Parameters:
 *          handle: Notebook handle.
 *          pageid: Page ID of the tab to set.
 *          text: Pointer to the text to set.
 */
void API dw_notebook_page_set_status_text(HWND handle, ULONG pageid, const char *text)
{
    /* Not supported here... do nothing */
}

/*
 * Packs the specified box into the notebook page.
 * Parameters:
 *          handle: Handle to the notebook to be packed.
 *          pageid: Page ID in the notebook which is being packed.
 *          page: Box handle to be packed.
 */
DW_FUNCTION_DEFINITION(dw_notebook_pack, void, HWND handle, ULONG pageid, HWND page)
DW_FUNCTION_ADD_PARAM3(handle, pageid, page)
DW_FUNCTION_NO_RETURN(dw_notebook_pack)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, pageid, ULONG, page, HWND)
{
    DWNotebook *notebook = handle;
    DWNotebookPage *notepage = _dw_notepage_from_id(notebook, pageid);

    if(notepage != nil)
    {
        dw_box_pack_start(notepage, page, 0, 0, TRUE, TRUE, 0);
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Create a new Window Frame.
 * Parameters:
 *       owner: The Owner's window handle or HWND_DESKTOP.
 *       title: The Window title.
 *       flStyle: Style flags, see the PM reference.
 */
DW_FUNCTION_DEFINITION(dw_window_new, HWND, DW_UNUSED(HWND hwndOwner), const char *title, ULONG flStyle)
DW_FUNCTION_ADD_PARAM3(hwndOwner, title, flStyle)
DW_FUNCTION_RETURN(dw_window_new, HWND)
DW_FUNCTION_RESTORE_PARAM3(DW_UNUSED(hwndOwner), HWND, title, char *, flStyle, ULONG)
{
    DW_FUNCTION_INIT;
    CGRect screenrect = [[UIScreen mainScreen] bounds];
    DWWindow *window = [[DWWindow alloc] initWithFrame:screenrect];
    DWView *view = [[DWView alloc] init];
    UIUserInterfaceStyle style = [[DWObj hiddenWindow] overrideUserInterfaceStyle];

    /* Copy the overrideUserInterfaceStyle property from the hiddenWindow */
    if(style != UIUserInterfaceStyleUnspecified)
        [window setOverrideUserInterfaceStyle:style];
    [window setWindowLevel:UIWindowLevelNormal];
    [window setRootViewController:[[DWViewController alloc] init]];
    [window setBackgroundColor:[UIColor systemBackgroundColor]];
    [[[window rootViewController] view] addSubview:view];
    [_dw_toplevel_windows addObject:window];

    /* Handle style flags... There is no visible frame...
     * On iOS 13 and higher if a titlebar is requested create a navigation bar.
     */
    if(@available(iOS 13.0, *)) {
        NSString *nstitle = [NSString stringWithUTF8String:title];
        [window setLargeContentTitle:nstitle];
        if(flStyle & DW_FCF_TITLEBAR)
        {
            NSInteger sbheight = [[[window windowScene] statusBarManager] statusBarFrame].size.height;
            CGRect navrect = CGRectMake(0, sbheight, screenrect.size.width, 44);
            UINavigationBar* navbar = [[UINavigationBar alloc] initWithFrame:navrect];
            UINavigationItem* navItem = [[UINavigationItem alloc] initWithTitle:nstitle];

            /* Double check the intrinsic height... */
            CGSize navdefault = [navbar intrinsicContentSize];
            if(navdefault.height != navrect.size.height)
            {
                navrect.size.height = navdefault.height;
                navbar.frame = navrect;
            }

            /* We maintain a list of top-level windows...If there is more than one window,
             * and our window has the close button style, add a Back button that will close it.
             */
            if([_dw_toplevel_windows count] > 1 && flStyle & DW_FCF_CLOSEBUTTON)
            {
                UIBarButtonItem *back = [[UIBarButtonItem alloc] initWithTitle:@"Back"
                                                                         style:UIBarButtonItemStylePlain
                                                                        target:window
                                                                        action:@selector(closeWindow:)];

                navItem.leftBarButtonItem = back;
            }
            [navbar setItems:@[navItem]];
            [[[window rootViewController] view] addSubview:navbar];
        }
    }
    DW_FUNCTION_RETURN_THIS(window);
}

/*
 * Call a function from the window (widget)'s context.
 * Parameters:
 *       handle: Window handle of the widget.
 *       function: Function pointer to be called.
 *       data: Pointer to the data to be passed to the function.
 */
void API dw_window_function(HWND handle, void *function, void *data)
{
    void **params = calloc(2, sizeof(void *));
    NSValue *v;
    DW_LOCAL_POOL_IN;
    v = [NSValue valueWithPointer:params];
    params[0] = function;
    params[1] = data;
    [DWObj performSelectorOnMainThread:@selector(doWindowFunc:) withObject:v waitUntilDone:YES];
    free(params);
    DW_LOCAL_POOL_OUT;
}


/*
 * Changes the appearance of the mouse pointer.
 * Parameters:
 *       handle: Handle to widget for which to change.
 *       cursortype: ID of the pointer you want.
 */
void API dw_window_set_pointer(HWND handle, int pointertype)
{
    /* TODO: Only might be possible on Catalyst */
}

/*
 * Makes the window visible.
 * Parameters:
 *           handle: The window handle to make visible.
 */
DW_FUNCTION_DEFINITION(dw_window_show, int, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_RETURN(dw_window_show, int)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    NSObject *object = handle;
    int retval = DW_ERROR_NONE;

    if([ object isMemberOfClass:[DWWindow class]])
    {
        DWWindow *window = handle;
        CGRect rect = [window frame];

        /* If we haven't been sized by a call.. */
        if(rect.size.width <= 1 || rect.size.height <= 1)
        {
            /* Determine the contents size */
            dw_window_set_size(handle, 0, 0);
        }
        if(![window shown])
        {
            [window makeKeyAndVisible];
            [window setShown:YES];
        }
        else
            [window setHidden:NO];
    }
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Makes the window invisible.
 * Parameters:
 *           handle: The window handle to make visible.
 */
DW_FUNCTION_DEFINITION(dw_window_hide, int, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_RETURN(dw_window_hide, int)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    NSObject *object = handle;
    int retval = DW_ERROR_NONE;

    if([ object isKindOfClass:[ UIWindow class ] ])
    {
        UIWindow *window = handle;

        [window setHidden:YES];
    }
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Sets the colors used by a specified window (widget) handle.
 * Parameters:
 *          handle: The window (widget) handle.
 *          fore: Foreground color in DW_RGB format or a default color index.
 *          back: Background color in DW_RGB format or a default color index.
 */
DW_FUNCTION_DEFINITION(dw_window_set_color, int, HWND handle, unsigned long fore, unsigned long back)
DW_FUNCTION_ADD_PARAM3(handle, fore, back)
DW_FUNCTION_RETURN(dw_window_set_color, int)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, fore, unsigned long, back, unsigned long)
{
    id object = handle;
    unsigned long _fore = _dw_get_color(fore);
    unsigned long _back = _dw_get_color(back);
    UIColor *fg = NULL;
    UIColor *bg = NULL;
    int retval = DW_ERROR_NONE;

    /* Get the UIColor for non-default colors */
    if(fore != DW_CLR_DEFAULT)
    {
        fg = [UIColor colorWithRed: DW_RED_VALUE(_fore)/255.0 green: DW_GREEN_VALUE(_fore)/255.0 blue: DW_BLUE_VALUE(_fore)/255.0 alpha: 1];
    }
    if(back != DW_CLR_DEFAULT)
    {
        bg = [UIColor colorWithRed: DW_RED_VALUE(_back)/255.0 green: DW_GREEN_VALUE(_back)/255.0 blue: DW_BLUE_VALUE(_back)/255.0 alpha: 1];
    }

    /* Get the textfield from the spinbutton */
    if([object isMemberOfClass:[DWSpinButton class]])
    {
        object = [object textfield];
    }
    /* Get the cell on classes using NSCell */
    if([object isKindOfClass:[UITextField class]])
    {
        [object setTextColor:(fg ? fg : [UIColor labelColor])];
    }
    if([object isMemberOfClass:[DWButton class]])
    {
       [[object titleLabel] setTextColor:(fg ? fg : [UIColor labelColor])];
    }
    if([object isKindOfClass:[UITextField class]] || [object isKindOfClass:[UIButton class]])
    {
        [object setBackgroundColor:(bg ? bg : [UIColor systemBackgroundColor])];
    }
    else if([object isMemberOfClass:[DWBox class]])
    {
        DWBox *box = object;

        [box setBackgroundColor:bg];
    }
    else if([object isKindOfClass:[UITableView class]])
    {
        DWContainer *cont = handle;

        [cont setBackgroundColor:(bg ? bg : [UIColor systemBackgroundColor])];
        [cont setForegroundColor:(fg ? fg : [UIColor labelColor])];
    }
    else if([object isMemberOfClass:[DWMLE class]])
    {
        DWMLE *mle = handle;
        [mle setBackgroundColor:(bg ? bg : [UIColor systemBackgroundColor])];
        [mle setTextColor:(fg ? fg : [UIColor labelColor])];
    }
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Sets the font used by a specified window (widget) handle.
 * Parameters:
 *          handle: The window (widget) handle.
 *          border: Size of the window border in pixels.
 */
int API dw_window_set_border(HWND handle, int border)
{
    /* No overlapping windows on iOS, unsupported */
    return DW_ERROR_UNKNOWN;
}

/*
 * Sets the style of a given window (widget).
 * Parameters:
 *          handle: Window (widget) handle.
 *          width: New width in pixels.
 *          height: New height in pixels.
 */
DW_FUNCTION_DEFINITION(dw_window_set_style, void, HWND handle, ULONG style, ULONG mask)
DW_FUNCTION_ADD_PARAM3(handle, style, mask)
DW_FUNCTION_NO_RETURN(dw_window_set_style)
DW_FUNCTION_RESTORE_PARAM3(handle, HWND, style, ULONG, mask, ULONG)
{
    id object = _dw_text_handle(handle);

    if([object isKindOfClass:[UILabel class]])
    {
        UILabel *label = object;

        [label setTextAlignment:(style & 0xF)];
        if(mask & DW_DT_VCENTER)
            [label setBaselineAdjustment:(style & DW_DT_VCENTER ? UIBaselineAdjustmentAlignCenters : UIBaselineAdjustmentNone)];
        if(mask & DW_DT_WORDBREAK)
        {
            if(style & DW_DT_WORDBREAK)
                [label setLineBreakMode:NSLineBreakByWordWrapping];
            else
                [label setLineBreakMode:NSLineBreakByTruncatingTail];
        }
    }
    else if([object isMemberOfClass:[DWMLE class]])
    {
        DWMLE *mle = handle;
        [mle setTextAlignment:(style & mask)];
    }
    else if([object isMemberOfClass:[DWMenuItem class]])
    {
        DWMenuItem *menuitem = object;
        UIMenuElementState oldstate = [menuitem state];
        BOOL oldenabled = [menuitem enabled];

        if(mask & (DW_MIS_CHECKED | DW_MIS_UNCHECKED))
        {
            if(style & DW_MIS_CHECKED)
                [menuitem setState:UIMenuElementStateOn];
            else if(style & DW_MIS_UNCHECKED)
                [menuitem setState:UIMenuElementStateOff];
        }
        if(mask & (DW_MIS_ENABLED | DW_MIS_DISABLED))
        {
            if(style & DW_MIS_ENABLED)
                [menuitem setEnabled:YES];
            else if(style & DW_MIS_DISABLED)
                [menuitem setEnabled:NO];
        }
        /* Only force the menus to repopulate if something changed */
        if(oldstate != [menuitem state] || oldenabled != [menuitem enabled])
            [DWObj safeCall:@selector(updateMenu:) withObject:nil];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the current focus item for a window/dialog.
 * Parameters:
 *         handle: Handle to the dialog item to be focused.
 * Remarks:
 *          This is for use after showing the window/dialog.
 */
DW_FUNCTION_DEFINITION(dw_window_set_focus, void, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_NO_RETURN(dw_window_set_focus)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    id object = handle;

    [object becomeFirstResponder];
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the default focus item for a window/dialog.
 * Parameters:
 *         window: Toplevel window or dialog.
 *         defaultitem: Handle to the dialog item to be default.
 * Remarks:
 *          This is for use before showing the window/dialog.
 */
DW_FUNCTION_DEFINITION(dw_window_default, void, HWND handle, HWND defaultitem)
DW_FUNCTION_ADD_PARAM2(handle, defaultitem)
DW_FUNCTION_NO_RETURN(dw_window_default)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, defaultitem, HWND)
{
    DWWindow *window = handle;
    id object = defaultitem;

    if([window isKindOfClass:[UIWindow class]] && [object isKindOfClass:[UIControl class]])
    {
        [object becomeFirstResponder];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets window to click the default dialog item when an ENTER is pressed.
 * Parameters:
 *         window: Window (widget) to look for the ENTER press.
 *         next: Window (widget) to move to next (or click)
 */
void API dw_window_click_default(HWND handle, HWND next)
{
    /* TODO: Figure out how to do this if we should */
}

/*
 * Captures the mouse input to this window.
 * Parameters:
 *       handle: Handle to receive mouse input.
 */
void API dw_window_capture(HWND handle)
{
    /* Don't do anything for now */
}

/*
 * Releases previous mouse capture.
 */
void API dw_window_release(void)
{
    /* Don't do anything for now */
}

/*
 * Changes a window's parent to newparent.
 * Parameters:
 *           handle: The window handle to destroy.
 *           newparent: The window's new parent window.
 */
void API dw_window_reparent(HWND handle, HWND newparent)
{
    /* TODO: Not sure if we should even bother with this */
}

/* Allows the user to choose a font using the system's font chooser dialog.
 * Parameters:
 *       currfont: current font
 * Returns:
 *       A malloced buffer with the selected font or NULL on error.
 */
char * API dw_font_choose(const char *currfont)
{
    NSPointerArray *params = [[NSPointerArray pointerArrayWithOptions:NSPointerFunctionsOpaqueMemory] retain];
    char *font = NULL;

    [params addPointer:(void *)currfont];
    [DWObj safeCall:@selector(fontPicker:) withObject:params];
    if([params count] > 1)
        font = [params pointerAtIndex:1];

    return font;
}

/* Internal function to return a pointer to an item struct
 * with information about the packing information regarding object.
 */
Item *_dw_box_item(id object)
{
    /* Find the item within the box it is packed into */
    if([object isKindOfClass:[DWBox class]] || [object isKindOfClass:[UIControl class]])
    {
        DWBox *parent = (DWBox *)[object superview];

        if([parent isKindOfClass:[DWBox class]])
        {
            Box *thisbox = [parent box];
            Item *thisitem = thisbox->items;
            int z;

            for(z=0;z<thisbox->count;z++)
            {
                if(thisitem[z].hwnd == object)
                    return &thisitem[z];
            }
        }
    }
    return NULL;
}

/*
 * Sets the font used by a specified window (widget) handle.
 * Parameters:
 *          handle: The window (widget) handle.
 *          fontname: Name and size of the font in the form "size.fontname"
 */
int API dw_window_set_font(HWND handle, const char *fontname)
{
    UIFont *font = fontname ? _dw_font_by_name(fontname) :
    (DWDefaultFont ? DWDefaultFont : [UIFont systemFontOfSize:[UIFont smallSystemFontSize]]);

    if(font)
    {
        id object = _dw_text_handle(handle);
        if([object isMemberOfClass:[DWMLE class]])
        {
            DWMLE *mle = object;
            
            [mle setFont:font];
        }
        else if([object isKindOfClass:[UIControl class]])
        {
            [object setFont:font];
        }
        else if([object isMemberOfClass:[DWRender class]])
        {
            DWRender *render = object;

            [render setFont:font];
        }
        else
            return DW_ERROR_UNKNOWN;
        /* If we changed the text... */
        Item *item = _dw_box_item(handle);

        /* Check to see if any of the sizes need to be recalculated */
        if(item && (item->origwidth == DW_SIZE_AUTO || item->origheight == DW_SIZE_AUTO))
        {
            _dw_control_size(handle, item->origwidth == DW_SIZE_AUTO ? &item->width : NULL, item->origheight == DW_SIZE_AUTO ? &item->height : NULL);
            /* Queue a redraw on the top-level window */
            _dw_redraw([object window], TRUE);
        }
        return DW_ERROR_NONE;
    }
    return DW_ERROR_UNKNOWN;
}

/*
 * Returns the current font for the specified window
 * Parameters:
 *           handle: The window handle from which to obtain the font.
 */
char * API dw_window_get_font(HWND handle)
{
    id object = _dw_text_handle(handle);
    UIFont *font = nil;

    if([object isKindOfClass:[UIControl class]] || [object isMemberOfClass:[DWRender class]])
    {
         font = [object font];
    }
    if(font)
    {
        NSString *fontname = [font fontName];
        NSString *output = [NSString stringWithFormat:@"%d.%s", (int)[font pointSize], [fontname UTF8String]];
        return strdup([output UTF8String]);
    }
    return NULL;
}

/*
 * Destroys a window and all of it's children.
 * Parameters:
 *           handle: The window handle to destroy.
 */
DW_FUNCTION_DEFINITION(dw_window_destroy, int, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_RETURN(dw_window_destroy, int)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DW_FUNCTION_INIT;
    DW_LOCAL_POOL_IN;
    id object = handle;
    int retval = 0;

    /* Handle destroying a top-level window */
    if([object isKindOfClass:[UIWindow class]])
    {
        DWWindow *window = handle;
        [window setHidden:YES];
        [_dw_toplevel_windows removeObject:window];
        for(id object in [[[window rootViewController] view] subviews])
        {
            [object removeFromSuperview];
            [object release];
        }
        [[[window rootViewController] view] removeFromSuperview];
        [window setRootViewController:nil];
    }
    /* Handle removing menu items from menus */
    else if([object isMemberOfClass:[DWMenuItem class]])
    {
        DWMenuItem *item = object;
        DWMenu *menu = [item menu];

        [menu removeItem:object];
    }
    /* Handle destroying a control or box */
    else if([object isKindOfClass:[UIView class]] || [object isKindOfClass:[UIControl class]])
    {
        DWBox *parent = (DWBox *)[object superview];

        if([parent isKindOfClass:[DWBox class]])
        {
            id window = [object window];
            Box *thisbox = [parent box];
            int z, index = -1;
            Item *tmpitem = NULL, *thisitem = thisbox->items;

            if(!thisitem)
                thisbox->count = 0;

            for(z=0;z<thisbox->count;z++)
            {
                if(thisitem[z].hwnd == object)
                    index = z;
            }

            if(index != -1)
            {
                [object removeFromSuperview];
                [object release];

                if(thisbox->count > 1)
                {
                    tmpitem = calloc(sizeof(Item), (thisbox->count-1));

                    /* Copy all but the current entry to the new list */
                    for(z=0;z<index;z++)
                    {
                        tmpitem[z] = thisitem[z];
                    }
                    for(z=index+1;z<thisbox->count;z++)
                    {
                        tmpitem[z-1] = thisitem[z];
                    }
                }

                thisbox->items = tmpitem;
                if(thisitem)
                    free(thisitem);
                if(tmpitem)
                    thisbox->count--;
                else
                    thisbox->count = 0;

                /* Queue a redraw on the top-level window */
                _dw_redraw(window, TRUE);
            }
        }
    }
    DW_LOCAL_POOL_OUT;
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Gets the text used for a given window.
 * Parameters:
 *       handle: Handle to the window.
 * Returns:
 *       text: The text associsated with a given window.
 */
DW_FUNCTION_DEFINITION(dw_window_get_text, char *, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_RETURN(dw_window_get_text, char *)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DW_FUNCTION_INIT;
    id object = _dw_text_handle(handle);
    id control = handle;
    NSString *nsstr = nil;
    char *retval = NULL;

    if([control isKindOfClass:[UIButton class]])
    {
        nsstr = [control titleForState:UIControlStateNormal];
    }
    else if([object isKindOfClass:[UILabel class]] || [object isKindOfClass:[UITextField class]])
    {
        nsstr = [object text];
    }
    else if([object isMemberOfClass:[DWWindow class]])
    {
        DWWindow *window = object;
        UIView *view = [[window rootViewController] view];
        NSArray *array = [view subviews];

        for(id obj in array)
        {
            if([obj isMemberOfClass:[UINavigationBar class]])
            {
                UINavigationBar *nav = obj;
                UINavigationItem *item = [[nav items] firstObject];

                nsstr = [item title];
            }
        }
    }
    if(nsstr)
        retval = strdup([nsstr UTF8String]);
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Sets the text used for a given window.
 * Parameters:
 *       handle: Handle to the window.
 *       text: The text associsated with a given window.
 */
DW_FUNCTION_DEFINITION(dw_window_set_text, void, HWND handle, const char *text)
DW_FUNCTION_ADD_PARAM2(handle, text)
DW_FUNCTION_NO_RETURN(dw_window_set_text)
DW_FUNCTION_RESTORE_PARAM2(handle, HWND, text, char *)
{
    DW_FUNCTION_INIT;
    id object = _dw_text_handle(handle);
    id control = handle;
    Item *item = NULL;

    if([control isKindOfClass:[UIButton class]])
    {
        [control setTitle:[NSString stringWithUTF8String:text] forState:UIControlStateNormal];
        item = _dw_box_item(handle);
    }
    else if([object isKindOfClass:[UILabel class]] || [object isKindOfClass:[UITextField class]])
    {
        [object setText:[NSString stringWithUTF8String:text]];
        item = _dw_box_item(handle);
    }
    else if([object isMemberOfClass:[DWWindow class]])
    {
        DWWindow *window = object;
        UIView *view = [[window rootViewController] view];
        NSArray *array = [view subviews];
        NSString *nstr = [NSString stringWithUTF8String:text];

        [window setLargeContentTitle:nstr];

        for(id obj in array)
        {
            if([obj isMemberOfClass:[UINavigationBar class]])
            {
                UINavigationBar *nav = obj;
                UINavigationItem *item = [[nav items] firstObject];

                [item setTitle:nstr];
            }
        }
    }
    /* If we changed the text...
     * Check to see if any of the sizes need to be recalculated
     */
    if(item && (item->origwidth == DW_SIZE_AUTO || item->origheight == DW_SIZE_AUTO))
    {
      int newwidth, newheight;

      _dw_control_size(handle, &newwidth, &newheight);

      /* Only update the item and redraw the window if it changed */
      if((item->origwidth == DW_SIZE_AUTO && item->width != newwidth) ||
         (item->origheight == DW_SIZE_AUTO && item->height != newheight))
      {
         if(item->origwidth == DW_SIZE_AUTO)
            item->width = newwidth;
         if(item->origheight == DW_SIZE_AUTO)
            item->height = newheight;
         /* Queue a redraw on the top-level window */
         _dw_redraw([object window], TRUE);
      }
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the text used for a given window's floating bubble help.
 * Parameters:
 *       handle: Handle to the window (widget).
 *       bubbletext: The text in the floating bubble tooltip.
 */
void API dw_window_set_tooltip(HWND handle, const char *bubbletext)
{
    /* Tooltips don't exist on iOS */
}

/*
 * Disables given window (widget).
 * Parameters:
 *       handle: Handle to the window.
 */
DW_FUNCTION_DEFINITION(dw_window_disable, void, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_NO_RETURN(dw_window_disable)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DW_FUNCTION_INIT;
    id object = handle;

    if([object isMemberOfClass:[UIScrollView class]])
    {
        UIScrollView *sv = handle;
        NSArray *subviews = [sv subviews];
        object = [subviews firstObject];
    }
    if([object isKindOfClass:[UIControl class]] || [object isMemberOfClass:[DWMenuItem class]])
    {
        [object setEnabled:NO];
    }
    if([object isKindOfClass:[UITextView class]])
    {
        UITextView *mle = object;

        [mle setEditable:NO];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Enables given window (widget).
 * Parameters:
 *       handle: Handle to the window.
 */
DW_FUNCTION_DEFINITION(dw_window_enable, void, HWND handle)
DW_FUNCTION_ADD_PARAM1(handle)
DW_FUNCTION_NO_RETURN(dw_window_enable)
DW_FUNCTION_RESTORE_PARAM1(handle, HWND)
{
    DW_FUNCTION_INIT;
    id object = handle;

    if([object isMemberOfClass:[UIScrollView class]])
    {
        UIScrollView *sv = handle;
        NSArray *subviews = [sv subviews];
        object = [subviews firstObject];
    }
    if([object isKindOfClass:[UIControl class]] || [object isMemberOfClass:[DWMenuItem class]])
    {
        [object setEnabled:YES];
    }
    if([object isKindOfClass:[UITextView class]])
    {
        UITextView *mle = object;

        [mle setEditable:YES];
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Sets the bitmap used for a given static window.
 * Parameters:
 *       handle: Handle to the window.
 *       id: An ID to be used to specify the icon,
 *           (pass 0 if you use the filename param)
 *       filename: a path to a file (Bitmap on OS/2 or
 *                 Windows and a pixmap on Unix, pass
 *                 NULL if you use the id param)
 * Returns:
 *        DW_ERROR_NONE on success.
 *        DW_ERROR_UNKNOWN if the parameters were invalid.
 *        DW_ERROR_GENERAL if the bitmap was unable to be loaded.
 */
int API dw_window_set_bitmap_from_data(HWND handle, unsigned long cid, const char *data, int len)
{
    id object = handle;
    int retval = DW_ERROR_UNKNOWN;

    if([object isKindOfClass:[UIImageView class]] || [object isMemberOfClass:[DWButton class]])
    {
        if(data)
        {
            DW_LOCAL_POOL_IN;
            NSData *thisdata = [NSData dataWithBytes:data length:len];
            UIImage *pixmap = [[UIImage alloc] initWithData:thisdata];

            if(pixmap)
            {
                if([object isMemberOfClass:[DWButton class]])
                {
                    DWButton *button = object;
                    [button setImage:pixmap forState:UIControlStateNormal];
                }
                else
                {
                    UIImageView *iv = object;
                    [iv setImage:pixmap];
                }
                /* If we changed the bitmap... */
                Item *item = _dw_box_item(handle);

                /* Check to see if any of the sizes need to be recalculated */
                if(item && (item->origwidth == DW_SIZE_AUTO || item->origheight == DW_SIZE_AUTO))
                {
                    _dw_control_size(handle, item->origwidth == DW_SIZE_AUTO ? &item->width : NULL, item->origheight == DW_SIZE_AUTO ? &item->height : NULL);
                    /* Queue a redraw on the top-level window */
                    _dw_redraw([object window], TRUE);
                }
                retval = DW_ERROR_NONE;
            }
            else
                retval = DW_ERROR_GENERAL;
            DW_LOCAL_POOL_OUT;
        }
        else
            return dw_window_set_bitmap(handle, cid, NULL);
    }
    return retval;
}

/*
 * Sets the bitmap used for a given static window.
 * Parameters:
 *       handle: Handle to the window.
 *       id: An ID to be used to specify the icon,
 *           (pass 0 if you use the filename param)
 *       filename: a path to a file (Bitmap on OS/2 or
 *                 Windows and a pixmap on Unix, pass
 *                 NULL if you use the id param)
 * Returns:
 *        DW_ERROR_NONE on success.
 *        DW_ERROR_UNKNOWN if the parameters were invalid.
 *        DW_ERROR_GENERAL if the bitmap was unable to be loaded.
 */
int API dw_window_set_bitmap(HWND handle, unsigned long resid, const char *filename)
{
    id object = handle;
    int retval = DW_ERROR_UNKNOWN;
    DW_LOCAL_POOL_IN;

    if([object isKindOfClass:[UIImageView class]] || [object isMemberOfClass:[DWButton class]])
    {
        UIImage *bitmap = nil;

        if(filename)
        {
            char *ext = _dw_get_image_extension( filename );
            NSString *nstr = [ NSString stringWithUTF8String:filename ];

            bitmap = [[UIImage alloc] initWithContentsOfFile:nstr];

            if(!bitmap && ext)
            {
                nstr = [nstr stringByAppendingString: [NSString stringWithUTF8String:ext]];
                bitmap = [[UIImage alloc] initWithContentsOfFile:nstr];
            }
            if(!bitmap)
                retval = DW_ERROR_GENERAL;
        }
        if(!bitmap && resid > 0 && resid < 65536)
        {
            bitmap = _dw_icon_load(resid);
            if(!bitmap)
                retval = DW_ERROR_GENERAL;
        }

        if(bitmap)
        {
            if([object isMemberOfClass:[DWButton class]])
            {
                DWButton *button = object;
                [button setImage:bitmap forState:UIControlStateNormal];
            }
            else
            {
                UIImageView *iv = object;
                [iv setImage:bitmap];
            }

            /* If we changed the bitmap... */
            Item *item = _dw_box_item(handle);

            /* Check to see if any of the sizes need to be recalculated */
            if(item && (item->origwidth == DW_SIZE_AUTO || item->origheight == DW_SIZE_AUTO))
            {
                _dw_control_size(handle, item->origwidth == DW_SIZE_AUTO ? &item->width : NULL, item->origheight == DW_SIZE_AUTO ? &item->height : NULL);
                /* Queue a redraw on the top-level window */
                _dw_redraw([object window], TRUE);
            }
            retval = DW_ERROR_NONE;
        }
    }
    DW_LOCAL_POOL_OUT;
    return retval;
}

/*
 * Sets the icon used for a given window.
 * Parameters:
 *       handle: Handle to the window.
 *       id: An ID to be used to specify the icon.
 */
void API dw_window_set_icon(HWND handle, HICN icon)
{
    /* This isn't needed, it is loaded from the bundle */
}

/*
 * Gets the child window handle with specified ID.
 * Parameters:
 *       handle: Handle to the parent window.
 *       id: Integer ID of the child.
 */
HWND API dw_window_from_id(HWND handle, int cid)
{
    NSObject *object = handle;
    UIView *view = handle;
    if([ object isKindOfClass:[ UIWindow class ] ])
    {
        UIWindow *window = handle;
        NSArray *subviews = [window subviews];
        view = [subviews firstObject];
    }
    return [view viewWithTag:cid];
}

/*
 * Minimizes or Iconifies a top-level window.
 * Parameters:
 *           handle: The window handle to minimize.
 */
int API dw_window_minimize(HWND handle)
{
    /* TODO: Not sure if we should do anything here on iOS */
    return DW_ERROR_GENERAL;
}

/* Causes entire window to be invalidated and redrawn.
 * Parameters:
 *           handle: Toplevel window handle to be redrawn.
 */
void API dw_window_redraw(HWND handle)
{
    DWWindow *window = handle;
    NSArray *array = [[[window rootViewController] view] subviews];

    for(id obj in array)
    {
        if([obj isMemberOfClass:[DWView class]])
        {
            DWView *view = obj;
            [view showWindow];
        }
    }

    [window setShown:YES];
}

/*
 * Makes the window topmost.
 * Parameters:
 *           handle: The window handle to make topmost.
 */
int API dw_window_raise(HWND handle)
{
    /* TODO: Not sure if we should do anything here on iOS */
    return DW_ERROR_GENERAL;
}

/*
 * Makes the window bottommost.
 * Parameters:
 *           handle: The window handle to make bottommost.
 */
int API dw_window_lower(HWND handle)
{
    /* TODO: Not sure if we should do anything here on iOS */
    return DW_ERROR_GENERAL;
}

/*
 * Sets the size of a given window (widget).
 * Parameters:
 *          handle: Window (widget) handle.
 *          width: New width in pixels.
 *          height: New height in pixels.
 */
void API dw_window_set_size(HWND handle, ULONG width, ULONG height)
{
    /* On iOS the window usually takes up the full screen */
}

/*
 * Gets the size the system thinks the widget should be.
 * Parameters:
 *       handle: Window handle of the item to be back.
 *       width: Width in pixels of the item or NULL if not needed.
 *       height: Height in pixels of the item or NULL if not needed.
 */
void API dw_window_get_preferred_size(HWND handle, int *width, int *height)
{
    id object = handle;

    if([object isMemberOfClass:[DWBox class]])
    {
        Box *thisbox;

        if((thisbox = (Box *)[object box]))
        {
            int depth = 0;

            /* Calculate space requirements */
            _dw_resize_box(thisbox, &depth, 0, 0, 1);

            /* Return what was requested */
            if(width) *width = thisbox->minwidth;
            if(height) *height = thisbox->minheight;
        }
    }
    else
        _dw_control_size(handle, width, height);
}

/*
 * Sets the gravity of a given window (widget).
 * Gravity controls which corner of the screen and window the position is relative to.
 * Parameters:
 *          handle: Window (widget) handle.
 *          horz: DW_GRAV_LEFT (default), DW_GRAV_RIGHT or DW_GRAV_CENTER.
 *          vert: DW_GRAV_TOP (default), DW_GRAV_BOTTOM or DW_GRAV_CENTER.
 */
void API dw_window_set_gravity(HWND handle, int horz, int vert)
{
    dw_window_set_data(handle, "_dw_grav_horz", DW_INT_TO_POINTER(horz));
    dw_window_set_data(handle, "_dw_grav_vert", DW_INT_TO_POINTER(vert));
}

/*
 * Sets the position of a given window (widget).
 * Parameters:
 *          handle: Window (widget) handle.
 *          x: X location from the bottom left.
 *          y: Y location from the bottom left.
 */
void API dw_window_set_pos(HWND handle, LONG x, LONG y)
{
    /* iOS windows take up the whole screen */
}

/*
 * Sets the position and size of a given window (widget).
 * Parameters:
 *          handle: Window (widget) handle.
 *          x: X location from the bottom left.
 *          y: Y location from the bottom left.
 *          width: Width of the widget.
 *          height: Height of the widget.
 */
void API dw_window_set_pos_size(HWND handle, LONG x, LONG y, ULONG width, ULONG height)
{
    /* iOS windows take up the whole screen */
}

/*
 * Gets the position and size of a given window (widget).
 * Parameters:
 *          handle: Window (widget) handle.
 *          x: X location from the bottom left.
 *          y: Y location from the bottom left.
 *          width: Width of the widget.
 *          height: Height of the widget.
 */
void API dw_window_get_pos_size(HWND handle, LONG *x, LONG *y, ULONG *width, ULONG *height)
{
    NSObject *object = handle;

    if([object isKindOfClass:[UIWindow class]])
    {
        UIWindow *window = handle;
        CGRect rect = [window frame];
        if(x)
            *x = rect.origin.x;
        if(y)
            *y = rect.origin.y;
        if(width)
            *width = rect.size.width;
        if(height)
            *height = rect.size.height;
        return;
    }
    else if([object isKindOfClass:[UIControl class]])
    {
        UIControl *control = handle;
        CGRect rect = [control frame];
        if(x)
            *x = rect.origin.x;
        if(y)
            *y = rect.origin.y;
        if(width)
            *width = rect.size.width;
        if(height)
            *height = rect.size.height;
        return;
    }
}

/*
 * Returns the width of the screen.
 */
int API dw_screen_width(void)
{
    CGRect screenRect = [[UIScreen mainScreen] bounds];
    return screenRect.size.width;
}

/*
 * Returns the height of the screen.
 */
int API dw_screen_height(void)
{
    CGRect screenRect = [[UIScreen mainScreen] bounds];
    return screenRect.size.height;
}

/* This should return the current color depth */
unsigned long API dw_color_depth_get(void)
{
    /* iOS always runs in 32bit depth */
    return 32;
}

/*
 * Creates a new system notification if possible.
 * Parameters:
 *         title: The short title of the notification.
 *         imagepath: Path to an image to display or NULL if none.
 *         description: A longer description of the notification,
 *                      or NULL if none is necessary.
 * Returns:
 *         A handle to the notification which can be used to attach a "clicked" event if desired,
 *         or NULL if it fails or notifications are not supported by the system.
 * Remarks:
 *          This will create a system notification that will show in the notifaction panel
 *          on supported systems, which may be clicked to perform another task.
 */
HWND dw_notification_new(const char *title, const char *imagepath, const char *description, ...)
{
    char outbuf[1025] = {0};
    HWND retval = NULL;

    if(description)
    {
        va_list args;

        va_start(args, description);
        vsnprintf(outbuf, 1024, description, args);
        va_end(args);
    }

    /* Configure the notification's payload. */
    if (@available(iOS 10.0, *))
    {
        if([[NSBundle mainBundle] bundleIdentifier] != nil)
        {
            UNMutableNotificationContent* notification = [[UNMutableNotificationContent alloc] init];

            if(notification)
            {
                notification.title = [NSString stringWithUTF8String:title];
                if(description)
                    notification.body = [NSString stringWithUTF8String:outbuf];
                if(imagepath)
                {
                    NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:imagepath]];
                    NSError *error;
                    UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"imageID"
                                                                URL:url
                                                            options:nil
                                                              error:&error];
                    if(attachment)
                        notification.attachments = @[attachment];
                }
                retval = notification;
            }
        }
    }
    return retval;
}

/*
 * Sends a notification created by dw_notification_new() after attaching signal handler.
 * Parameters:
 *         notification: The handle to the notification returned by dw_notification_new().
 * Returns:
 *         DW_ERROR_NONE on success, DW_ERROR_UNKNOWN on error or not supported.
 */
int dw_notification_send(HWND notification)
{
    if(notification)
    {
        NSString *notid = [NSString stringWithFormat:@"dw-notification-%llu", DW_POINTER_TO_ULONGLONG(notification)];
        
        /* Schedule the notification. */
        if (@available(iOS 10.0, *))
        {
            if([[NSBundle mainBundle] bundleIdentifier] != nil)
            {
                UNMutableNotificationContent* content = (UNMutableNotificationContent *)notification;
                UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:notid content:content trigger:nil];

                UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
                [center addNotificationRequest:request withCompletionHandler:nil];
            }
        }
        return DW_ERROR_NONE;
    }
    return DW_ERROR_UNKNOWN;
}


/*
 * Returns some information about the current operating environment.
 * Parameters:
 *       env: Pointer to a DWEnv struct.
 */
void dw_environment_query(DWEnv *env)
{
    memset(env, '\0', sizeof(DWEnv));
    strcpy(env->osName, "iOS");

    strncpy(env->buildDate, __DATE__, sizeof(env->buildDate)-1);
    strncpy(env->buildTime, __TIME__, sizeof(env->buildTime)-1);
    strncpy(env->htmlEngine, "WEBKIT", sizeof(env->htmlEngine)-1);
    env->DWMajorVersion = DW_MAJOR_VERSION;
    env->DWMinorVersion = DW_MINOR_VERSION;
#ifdef VER_REV
    env->DWSubVersion = VER_REV;
#else
    env->DWSubVersion = DW_SUB_VERSION;
#endif

    env->MajorVersion = DWOSMajor;
    env->MinorVersion = DWOSMinor;
    env->MajorBuild = DWOSBuild;
}

/*
 * Emits a beep.
 * Parameters:
 *       freq: Frequency.
 *       dur: Duration.
 */
void API dw_beep(int freq, int dur)
{
    AudioServicesPlayAlertSound((SystemSoundID)1104);
}

/* Call this after drawing to the screen to make sure
 * anything you have drawn is visible.
 */
void API dw_flush(void)
{
    /* This may need to be thread specific */
    [DWObj performSelectorOnMainThread:@selector(doFlush:) withObject:nil waitUntilDone:NO];
}

/* Functions for managing the user data lists that are associated with
 * a given window handle.  Used in dw_window_set_data() and
 * dw_window_get_data().
 */
UserData *_dw_find_userdata(UserData **root, const char *varname)
{
    UserData *tmp = *root;

    while(tmp)
    {
        if(strcasecmp(tmp->varname, varname) == 0)
            return tmp;
        tmp = tmp->next;
    }
    return NULL;
}

int _dw_new_userdata(UserData **root, const char *varname, void *data)
{
    UserData *new = _dw_find_userdata(root, varname);

    if(new)
    {
        new->data = data;
        return TRUE;
    }
    else
    {
        new = malloc(sizeof(UserData));
        if(new)
        {
            new->varname = strdup(varname);
            new->data = data;

            new->next = NULL;

            if (!*root)
                *root = new;
            else
            {
                UserData *prev = *root, *tmp = prev->next;

                while(tmp)
                {
                    prev = tmp;
                    tmp = tmp->next;
                }
                prev->next = new;
            }
            return TRUE;
        }
    }
    return FALSE;
}

int _dw_remove_userdata(UserData **root, const char *varname, int all)
{
    UserData *prev = NULL, *tmp = *root;

    while(tmp)
    {
        if(all || strcasecmp(tmp->varname, varname) == 0)
        {
            if(!prev)
            {
                *root = tmp->next;
                free(tmp->varname);
                free(tmp);
                if(!all)
                    return 0;
                tmp = *root;
            }
            else
            {
                /* If all is true we should
                 * never get here.
                 */
                prev->next = tmp->next;
                free(tmp->varname);
                free(tmp);
                return 0;
            }
        }
        else
        {
            prev = tmp;
            tmp = tmp->next;
        }
    }
    return 0;
}

/*
 * Add a named user data item to a window handle.
 * Parameters:
 *       window: Window handle of signal to be called back.
 *       dataname: A string pointer identifying which signal to be hooked.
 *       data: User data to be passed to the handler function.
 */
DW_FUNCTION_DEFINITION(dw_window_set_data, void, HWND window, const char *dataname, void *data)
DW_FUNCTION_ADD_PARAM3(window, dataname, data)
DW_FUNCTION_NO_RETURN(dw_window_set_data)
DW_FUNCTION_RESTORE_PARAM3(window, HWND, dataname, const char *, data, void *)
{
    id object = window;
    if([object isMemberOfClass:[DWWindow class]])
    {
        UIWindow *win = window;
        NSArray *subviews = [win subviews];
        for(id obj in subviews)
        {
            if([obj isMemberOfClass:[DWView class]])
            {
                object = obj;
                break;
            }
        }
    }
    else if([object isMemberOfClass:[UIScrollView class]])
    {
        UIScrollView *sv = window;
        NSArray *subviews = [sv subviews];
        object = [subviews firstObject];
    }
    WindowData *blah = (WindowData *)[object userdata];

    if(!blah)
    {
        if(!dataname)
            return;

        blah = calloc(1, sizeof(WindowData));
        [object setUserdata:blah];
    }

    if(data)
        _dw_new_userdata(&(blah->root), dataname, data);
    else
    {
        if(dataname)
            _dw_remove_userdata(&(blah->root), dataname, FALSE);
        else
            _dw_remove_userdata(&(blah->root), NULL, TRUE);
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Gets a named user data item to a window handle.
 * Parameters:
 *       window: Window handle of signal to be called back.
 *       dataname: A string pointer identifying which signal to be hooked.
 *       data: User data to be passed to the handler function.
 */
DW_FUNCTION_DEFINITION(dw_window_get_data, void *, HWND window, const char *dataname)
DW_FUNCTION_ADD_PARAM2(window, dataname)
DW_FUNCTION_RETURN(dw_window_get_data, void *)
DW_FUNCTION_RESTORE_PARAM2(window, HWND, dataname, const char *)
{
    id object = window;
    void *retval = NULL;

    if([object isMemberOfClass:[DWWindow class]])
    {
        UIWindow *win = window;
        NSArray *subviews = [win subviews];
        for(id obj in subviews)
        {
            if([obj isMemberOfClass:[DWView class]])
            {
                object = obj;
                break;
            }
        }
    }
    else if([object isMemberOfClass:[UIScrollView class]])
    {
        UIScrollView *sv = window;
        NSArray *subviews = [sv subviews];
        object = [subviews firstObject];
    }
    WindowData *blah = (WindowData *)[object userdata];

    if(blah && blah->root && dataname)
    {
        UserData *ud = _dw_find_userdata(&(blah->root), dataname);
        if(ud)
            retval = ud->data;
    }
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Compare two window handles.
 * Parameters:
 *       window1: First window handle to compare.
 *       window2: Second window handle to compare.
 * Returns:
 *       TRUE if the windows are the same object, FALSE if not.
 */
int API dw_window_compare(HWND window1, HWND window2)
{
    /* If anything special is require to compare... do it
     * here otherwise just compare the handles.
     */
    if(window1 && window2 && window1 == window2)
        return TRUE;
    return FALSE;
}

/*
 * Add a callback to a timer event.
 * Parameters:
 *       interval: Milliseconds to delay between calls.
 *       sigfunc: The pointer to the function to be used as the callback.
 *       data: User data to be passed to the handler function.
 * Returns:
 *       Timer ID for use with dw_timer_disconnect(), 0 on error.
 */
DW_FUNCTION_DEFINITION(dw_timer_connect, HTIMER, int interval, void *sigfunc, void *data)
DW_FUNCTION_ADD_PARAM3(interval, sigfunc, data)
DW_FUNCTION_RETURN(dw_timer_connect, HTIMER)
DW_FUNCTION_RESTORE_PARAM3(interval, int, sigfunc, void *, data, void *)
{
    HTIMER retval = 0;

    if(sigfunc)
    {
        NSTimeInterval seconds = (double)interval / 1000.0;

        retval = [NSTimer scheduledTimerWithTimeInterval:seconds target:DWHandler selector:@selector(runTimer:) userInfo:nil repeats:YES];
        _dw_new_signal(0, retval, 0, sigfunc, NULL, data);
    }
    DW_FUNCTION_RETURN_THIS(retval);
}

/*
 * Removes timer callback.
 * Parameters:
 *       id: Timer ID returned by dw_timer_connect().
 */
DW_FUNCTION_DEFINITION(dw_timer_disconnect, void, HTIMER timerid)
DW_FUNCTION_ADD_PARAM1(timerid)
DW_FUNCTION_NO_RETURN(dw_timer_disconnect)
DW_FUNCTION_RESTORE_PARAM1(timerid, HTIMER)
{
    DWSignalHandler *prev = NULL, *tmp = DWRoot;

    /* 0 is an invalid timer ID */
    if(timerid)
    {
        NSTimer *thistimer = timerid;

        [thistimer invalidate];

        while(tmp)
        {
            if(tmp->window == thistimer)
            {
                if(prev)
                {
                    prev->next = tmp->next;
                    free(tmp);
                    tmp = prev->next;
                }
                else
                {
                    DWRoot = tmp->next;
                    free(tmp);
                    tmp = DWRoot;
                }
            }
            else
            {
                prev = tmp;
                tmp = tmp->next;
            }
        }
    }
    DW_FUNCTION_RETURN_NOTHING;
}

/*
 * Add a callback to a window event.
 * Parameters:
 *       window: Window handle of signal to be called back.
 *       signame: A string pointer identifying which signal to be hooked.
 *       sigfunc: The pointer to the function to be used as the callback.
 *       data: User data to be passed to the handler function.
 */
void API dw_signal_connect(HWND window, const char *signame, void *sigfunc, void *data)
{
    dw_signal_connect_data(window, signame, sigfunc, NULL, data);
}

/*
 * Add a callback to a window event with a closure callback.
 * Parameters:
 *       window: Window handle of signal to be called back.
 *       signame: A string pointer identifying which signal to be hooked.
 *       sigfunc: The pointer to the function to be used as the callback.
 *       discfunc: The pointer to the function called when this handler is removed.
 *       data: User data to be passed to the handler function.
 */
void API dw_signal_connect_data(HWND window, const char *signame, void *sigfunc, void *discfunc, void *data)
{
    ULONG message = 0, msgid = 0;

    /* Handle special case of application delete signal */
    if(!window && signame && strcmp(signame, DW_SIGNAL_DELETE) == 0)
    {
        window = DWApp;
    }

    if(window && signame && sigfunc)
    {
        if((message = _dw_findsigmessage(signame)) != 0)
        {
            _dw_new_signal(message, window, (int)msgid, sigfunc, discfunc, data);
        }
    }
}

/*
 * Removes callbacks for a given window with given name.
 * Parameters:
 *       window: Window handle of callback to be removed.
 */
void API dw_signal_disconnect_by_name(HWND window, const char *signame)
{
    DWSignalHandler *prev = NULL, *tmp = DWRoot;
    ULONG message;

    if(!window || !signame || (message = _dw_findsigmessage(signame)) == 0)
        return;

    while(tmp)
    {
        if(tmp->window == window && tmp->message == message)
        {
            void (*discfunc)(HWND, void *) = tmp->discfunction;

            if(discfunc)
            {
                discfunc(tmp->window, tmp->data);
            }

            if(prev)
            {
                prev->next = tmp->next;
                free(tmp);
                tmp = prev->next;
            }
            else
            {
                DWRoot = tmp->next;
                free(tmp);
                tmp = DWRoot;
            }
        }
        else
        {
            prev = tmp;
            tmp = tmp->next;
        }
    }
}

/*
 * Removes all callbacks for a given window.
 * Parameters:
 *       window: Window handle of callback to be removed.
 */
void API dw_signal_disconnect_by_window(HWND window)
{
    DWSignalHandler *prev = NULL, *tmp = DWRoot;

    while(tmp)
    {
        if(tmp->window == window)
        {
            void (*discfunc)(HWND, void *) = tmp->discfunction;

            if(discfunc)
            {
                discfunc(tmp->window, tmp->data);
            }

            if(prev)
            {
                prev->next = tmp->next;
                free(tmp);
                tmp = prev->next;
            }
            else
            {
                DWRoot = tmp->next;
                free(tmp);
                tmp = DWRoot;
            }
        }
        else
        {
            prev = tmp;
            tmp = tmp->next;
        }
    }
}

/*
 * Removes all callbacks for a given window with specified data.
 * Parameters:
 *       window: Window handle of callback to be removed.
 *       data: Pointer to the data to be compared against.
 */
void API dw_signal_disconnect_by_data(HWND window, void *data)
{
    DWSignalHandler *prev = NULL, *tmp = DWRoot;

    while(tmp)
    {
        if(tmp->window == window && tmp->data == data)
        {
            void (*discfunc)(HWND, void *) = tmp->discfunction;

            if(discfunc)
            {
                discfunc(tmp->window, tmp->data);
            }

            if(prev)
            {
                prev->next = tmp->next;
                free(tmp);
                tmp = prev->next;
            }
            else
            {
                DWRoot = tmp->next;
                free(tmp);
                tmp = DWRoot;
            }
        }
        else
        {
            prev = tmp;
            tmp = tmp->next;
        }
    }
}

void _dw_my_strlwr(char *buf)
{
   int z, len = (int)strlen(buf);

   for(z=0;z<len;z++)
   {
      if(buf[z] >= 'A' && buf[z] <= 'Z')
         buf[z] -= 'A' - 'a';
   }
}

/* Open a shared library and return a handle.
 * Parameters:
 *         name: Base name of the shared library.
 *         handle: Pointer to a module handle,
 *                 will be filled in with the handle.
 */
int dw_module_load(const char *name, HMOD *handle)
{
   int len;
   char *newname;
   char errorbuf[1025];


   if(!handle)
      return -1;

   if((len = (int)strlen(name)) == 0)
      return   -1;

   /* Lenth + "lib" + ".dylib" + NULL */
   newname = malloc(len + 10);

   if(!newname)
      return -1;

   sprintf(newname, "lib%s.dylib", name);
   _dw_my_strlwr(newname);

   *handle = dlopen(newname, RTLD_NOW);
   if(*handle == NULL)
   {
      strncpy(errorbuf, dlerror(), 1024);
      printf("%s\n", errorbuf);
      sprintf(newname, "lib%s.dylib", name);
      *handle = dlopen(newname, RTLD_NOW);
   }

   free(newname);

   return (NULL == *handle) ? -1 : 0;
}

/* Queries the address of a symbol within open handle.
 * Parameters:
 *         handle: Module handle returned by dw_module_load()
 *         name: Name of the symbol you want the address of.
 *         func: A pointer to a function pointer, to obtain
 *               the address.
 */
int dw_module_symbol(HMOD handle, const char *name, void**func)
{
   if(!func || !name)
      return   -1;

   if(strlen(name) == 0)
      return   -1;

   *func = (void*)dlsym(handle, name);
   return   (NULL == *func);
}

/* Frees the shared library previously opened.
 * Parameters:
 *         handle: Module handle returned by dw_module_load()
 */
int dw_module_close(HMOD handle)
{
   if(handle)
      return dlclose(handle);
   return 0;
}

/*
 * Returns the handle to an unnamed mutex semaphore.
 */
HMTX dw_mutex_new(void)
{
    HMTX mutex = malloc(sizeof(pthread_mutex_t));

    pthread_mutex_init(mutex, NULL);
    return mutex;
}

/*
 * Closes a semaphore created by dw_mutex_new().
 * Parameters:
 *       mutex: The handle to the mutex returned by dw_mutex_new().
 */
void dw_mutex_close(HMTX mutex)
{
   if(mutex)
   {
      pthread_mutex_destroy(mutex);
      free(mutex);
   }
}

/*
 * Tries to gain access to the semaphore, if it can't it blocks.
 * Parameters:
 *       mutex: The handle to the mutex returned by dw_mutex_new().
 */
void dw_mutex_lock(HMTX mutex)
{
    /* We need to handle locks from the main thread differently...
     * since we can't stop message processing... otherwise we
     * will deadlock... so try to acquire the lock and continue
     * processing messages in between tries.
     */
    if(DWThread == pthread_self())
    {
        while(pthread_mutex_trylock(mutex) != 0)
        {
            /* Process any pending events */
            _dw_main_iteration([NSDate dateWithTimeIntervalSinceNow:0.01]);
        }
    }
    else
    {
        pthread_mutex_lock(mutex);
    }
}

/*
 * Tries to gain access to the semaphore.
 * Parameters:
 *       mutex: The handle to the mutex returned by dw_mutex_new().
 * Returns:
 *       DW_ERROR_NONE on success, DW_ERROR_TIMEOUT if it is already locked.
 */
int API dw_mutex_trylock(HMTX mutex)
{
    if(pthread_mutex_trylock(mutex) == 0)
        return DW_ERROR_NONE;
    return DW_ERROR_TIMEOUT;
}

/*
 * Reliquishes the access to the semaphore.
 * Parameters:
 *       mutex: The handle to the mutex returned by dw_mutex_new().
 */
void dw_mutex_unlock(HMTX mutex)
{
   pthread_mutex_unlock(mutex);
}

/*
 * Returns the handle to an unnamed event semaphore.
 */
HEV dw_event_new(void)
{
   HEV eve = (HEV)malloc(sizeof(struct _dw_unix_event));

   if(!eve)
      return NULL;

   /* We need to be careful here, mutexes on Linux are
    * FAST by default but are error checking on other
    * systems such as FreeBSD and OS/2, perhaps others.
    */
   pthread_mutex_init (&(eve->mutex), NULL);
   pthread_mutex_lock (&(eve->mutex));
   pthread_cond_init (&(eve->event), NULL);

   pthread_mutex_unlock (&(eve->mutex));
   eve->alive = 1;
   eve->posted = 0;

   return eve;
}

/*
 * Resets a semaphore created by dw_event_new().
 * Parameters:
 *       eve: The handle to the event returned by dw_event_new().
 */
int dw_event_reset (HEV eve)
{
   if(!eve)
      return DW_ERROR_NON_INIT;

   pthread_mutex_lock (&(eve->mutex));
   pthread_cond_broadcast (&(eve->event));
   pthread_cond_init (&(eve->event), NULL);
   eve->posted = 0;
   pthread_mutex_unlock (&(eve->mutex));
   return DW_ERROR_NONE;
}

/*
 * Posts a semaphore created by dw_event_new(). Causing all threads
 * waiting on this event in dw_event_wait to continue.
 * Parameters:
 *       eve: The handle to the event returned by dw_event_new().
 */
int dw_event_post (HEV eve)
{
   if(!eve)
      return FALSE;

   pthread_mutex_lock (&(eve->mutex));
   pthread_cond_broadcast (&(eve->event));
   eve->posted = 1;
   pthread_mutex_unlock (&(eve->mutex));
   return 0;
}

/*
 * Waits on a semaphore created by dw_event_new(), until the
 * event gets posted or until the timeout expires.
 * Parameters:
 *       eve: The handle to the event returned by dw_event_new().
 */
int dw_event_wait(HEV eve, unsigned long timeout)
{
    int rc;

    if(!eve)
        return DW_ERROR_NON_INIT;

    pthread_mutex_lock (&(eve->mutex));

    if(eve->posted)
    {
        pthread_mutex_unlock (&(eve->mutex));
        return DW_ERROR_NONE;
    }

    if(timeout != -1)
    {
        struct timeval now;
        struct timespec timeo;

        gettimeofday(&now, 0);
        timeo.tv_sec = now.tv_sec + (timeout / 1000);
        timeo.tv_nsec = now.tv_usec * 1000;
        rc = pthread_cond_timedwait(&(eve->event), &(eve->mutex), &timeo);
    }
    else
        rc = pthread_cond_wait(&(eve->event), &(eve->mutex));
    pthread_mutex_unlock (&(eve->mutex));
    if(!rc)
        return DW_ERROR_NONE;
    if(rc == ETIMEDOUT)
        return DW_ERROR_TIMEOUT;
    return DW_ERROR_GENERAL;
}

/*
 * Closes a semaphore created by dw_event_new().
 * Parameters:
 *       eve: The handle to the event returned by dw_event_new().
 */
int dw_event_close(HEV *eve)
{
   if(!eve || !(*eve))
      return DW_ERROR_NON_INIT;

   pthread_mutex_lock (&((*eve)->mutex));
   pthread_cond_destroy (&((*eve)->event));
   pthread_mutex_unlock (&((*eve)->mutex));
   pthread_mutex_destroy (&((*eve)->mutex));
   free(*eve);
   *eve = NULL;

   return DW_ERROR_NONE;
}

struct _dw_seminfo {
   int fd;
   int waiting;
};

static void _dw_handle_sem(int *tmpsock)
{
   fd_set rd;
   struct _dw_seminfo *array = (struct _dw_seminfo *)malloc(sizeof(struct _dw_seminfo));
   int listenfd = tmpsock[0];
   int bytesread, connectcount = 1, maxfd, z, posted = 0;
   char command;
   sigset_t mask;

   sigfillset(&mask); /* Mask all allowed signals */
   pthread_sigmask(SIG_BLOCK, &mask, NULL);

   /* problems */
   if(tmpsock[1] == -1)
   {
      free(array);
      return;
   }

   array[0].fd = tmpsock[1];
   array[0].waiting = 0;

   /* Free the memory allocated in dw_named_event_new. */
   free(tmpsock);

   while(1)
   {
      FD_ZERO(&rd);
      FD_SET(listenfd, &rd);

      maxfd = listenfd;

      /* Added any connections to the named event semaphore */
      for(z=0;z<connectcount;z++)
      {
         if(array[z].fd > maxfd)
            maxfd = array[z].fd;

         FD_SET(array[z].fd, &rd);
      }

      if(select(maxfd+1, &rd, NULL, NULL, NULL) == -1)
      {
          free(array);
          return;
      }

      if(FD_ISSET(listenfd, &rd))
      {
         struct _dw_seminfo *newarray;
            int newfd = accept(listenfd, 0, 0);

         if(newfd > -1)
         {
            /* Add new connections to the set */
            newarray = (struct _dw_seminfo *)malloc(sizeof(struct _dw_seminfo)*(connectcount+1));
            memcpy(newarray, array, sizeof(struct _dw_seminfo)*(connectcount));

            newarray[connectcount].fd = newfd;
            newarray[connectcount].waiting = 0;

            connectcount++;

            /* Replace old array with new one */
            free(array);
            array = newarray;
         }
      }

      /* Handle any events posted to the semaphore */
      for(z=0;z<connectcount;z++)
      {
         if(FD_ISSET(array[z].fd, &rd))
         {
            if((bytesread = (int)read(array[z].fd, &command, 1)) < 1)
            {
               struct _dw_seminfo *newarray = NULL;

               /* Remove this connection from the set */
               if(connectcount > 1)
               {
                   newarray = (struct _dw_seminfo *)malloc(sizeof(struct _dw_seminfo)*(connectcount-1));
                   if(!z)
                       memcpy(newarray, &array[1], sizeof(struct _dw_seminfo)*(connectcount-1));
                   else
                   {
                       memcpy(newarray, array, sizeof(struct _dw_seminfo)*z);
                       if(z!=(connectcount-1))
                           memcpy(&newarray[z], &array[z+1], sizeof(struct _dw_seminfo)*(connectcount-(z+1)));
                   }
               }
               connectcount--;

               /* Replace old array with new one */
               free(array);
               array = newarray;
            }
            else if(bytesread == 1)
            {
               switch(command)
               {
               case 0:
                  {
                  /* Reset */
                  posted = 0;
                  }
                  break;
               case 1:
                  /* Post */
                  {
                     int s;
                     char tmp = (char)0;

                     posted = 1;

                     for(s=0;s<connectcount;s++)
                     {
                        /* The semaphore has been posted so
                         * we tell all the waiting threads to
                         * continue.
                         */
                        if(array[s].waiting)
                           write(array[s].fd, &tmp, 1);
                     }
                  }
                  break;
               case 2:
                  /* Wait */
                  {
                     char tmp = (char)0;

                     array[z].waiting = 1;

                     /* If we are posted exit immeditately */
                     if(posted)
                        write(array[z].fd, &tmp, 1);
                  }
                  break;
               case 3:
                  {
                     /* Done Waiting */
                     array[z].waiting = 0;
                  }
                  break;
               }
            }
         }
      }
   }
}

/* Using domain sockets on unix for IPC */
/* Create a named event semaphore which can be
 * opened from other processes.
 * Parameters:
 *         eve: Pointer to an event handle to receive handle.
 *         name: Name given to semaphore which can be opened
 *               by other processes.
 */
HEV dw_named_event_new(const char *name)
{
    struct sockaddr_un un;
    int ev, *tmpsock = (int *)malloc(sizeof(int)*2);
    HEV eve;
    DWTID dwthread;

    if(!tmpsock)
        return NULL;

    eve = (HEV)malloc(sizeof(struct _dw_unix_event));

    if(!eve)
    {
        free(tmpsock);
        return NULL;
    }

    tmpsock[0] = socket(AF_UNIX, SOCK_STREAM, 0);
    ev = socket(AF_UNIX, SOCK_STREAM, 0);
    memset(&un, 0, sizeof(un));
    un.sun_family=AF_UNIX;
    mkdir("/tmp/.dw", S_IWGRP|S_IWOTH);
    strcpy(un.sun_path, "/tmp/.dw/");
    strcat(un.sun_path, name);

    /* just to be safe, this should be changed
     * to support multiple instances.
     */
    remove(un.sun_path);

    bind(tmpsock[0], (struct sockaddr *)&un, sizeof(un));
    listen(tmpsock[0], 0);
    connect(ev, (struct sockaddr *)&un, sizeof(un));
    tmpsock[1] = accept(tmpsock[0], 0, 0);

    if(tmpsock[0] < 0 || tmpsock[1] < 0 || ev < 0)
    {
        if(tmpsock[0] > -1)
            close(tmpsock[0]);
        if(tmpsock[1] > -1)
            close(tmpsock[1]);
        if(ev > -1)
            close(ev);
        free(tmpsock);
        free(eve);
        return NULL;
    }

    /* Create a thread to handle this event semaphore */
    pthread_create(&dwthread, NULL, (void *)_dw_handle_sem, (void *)tmpsock);
    eve->alive = ev;
    return eve;
}

/* Open an already existing named event semaphore.
 * Parameters:
 *         eve: Pointer to an event handle to receive handle.
 *         name: Name given to semaphore which can be opened
 *               by other processes.
 */
HEV dw_named_event_get(const char *name)
{
    struct sockaddr_un un;
    HEV eve;
    int ev = socket(AF_UNIX, SOCK_STREAM, 0);
    if(ev < 0)
        return NULL;

    eve = (HEV)malloc(sizeof(struct _dw_unix_event));

    if(!eve)
    {
        close(ev);
        return NULL;
    }

    un.sun_family=AF_UNIX;
    mkdir("/tmp/.dw", S_IWGRP|S_IWOTH);
    strcpy(un.sun_path, "/tmp/.dw/");
    strcat(un.sun_path, name);
    connect(ev, (struct sockaddr *)&un, sizeof(un));
    eve->alive = ev;
    return eve;
}

/* Resets the event semaphore so threads who call wait
 * on this semaphore will block.
 * Parameters:
 *         eve: Handle to the semaphore obtained by
 *              an open or create call.
 */
int dw_named_event_reset(HEV eve)
{
   /* signal reset */
   char tmp = (char)0;

   if(!eve || eve->alive < 0)
      return 0;

   if(write(eve->alive, &tmp, 1) == 1)
      return 0;
   return 1;
}

/* Sets the posted state of an event semaphore, any threads
 * waiting on the semaphore will no longer block.
 * Parameters:
 *         eve: Handle to the semaphore obtained by
 *              an open or create call.
 */
int dw_named_event_post(HEV eve)
{

   /* signal post */
   char tmp = (char)1;

   if(!eve || eve->alive < 0)
      return 0;

   if(write(eve->alive, &tmp, 1) == 1)
      return 0;
   return 1;
}

/* Waits on the specified semaphore until it becomes
 * posted, or returns immediately if it already is posted.
 * Parameters:
 *         eve: Handle to the semaphore obtained by
 *              an open or create call.
 *         timeout: Number of milliseconds before timing out
 *                  or -1 if indefinite.
 */
int dw_named_event_wait(HEV eve, unsigned long timeout)
{
   fd_set rd;
   struct timeval tv, *useme = NULL;
   int retval = 0;
   char tmp;

   if(!eve || eve->alive < 0)
      return DW_ERROR_NON_INIT;

   /* Set the timout or infinite */
   if(timeout != -1)
   {
      tv.tv_sec = timeout / 1000;
      tv.tv_usec = (int)timeout % 1000;

      useme = &tv;
   }

   FD_ZERO(&rd);
   FD_SET(eve->alive, &rd);

   /* Signal wait */
   tmp = (char)2;
   write(eve->alive, &tmp, 1);

   retval = select(eve->alive+1, &rd, NULL, NULL, useme);

   /* Signal done waiting. */
   tmp = (char)3;
   write(eve->alive, &tmp, 1);

   if(retval == 0)
      return DW_ERROR_TIMEOUT;
   else if(retval == -1)
      return DW_ERROR_INTERRUPT;

   /* Clear the entry from the pipe so
    * we don't loop endlessly. :)
    */
   read(eve->alive, &tmp, 1);
   return 0;
}

/* Release this semaphore, if there are no more open
 * handles on this semaphore the semaphore will be destroyed.
 * Parameters:
 *         eve: Handle to the semaphore obtained by
 *              an open or create call.
 */
int dw_named_event_close(HEV eve)
{
   /* Finally close the domain socket,
    * cleanup will continue in _dw_handle_sem.
    */
    if(eve)
    {
        close(eve->alive);
        free(eve);
    }
    return 0;
}

/* Mac specific function to cause garbage collection */
void _dw_pool_drain(void)
{
    NSAutoreleasePool *pool = pthread_getspecific(_dw_pool_key);
    [pool drain];
    pool = [[NSAutoreleasePool alloc] init];
    pthread_setspecific(_dw_pool_key, pool);
}

/*
 * Generally an internal function called from a newly created
 * thread to setup the Dynamic Windows environment for the thread.
 * However it is exported so language bindings can call it when
 * they create threads that require access to Dynamic Windows.
 */
void API _dw_init_thread(void)
{
    /* If we aren't using garbage collection we need autorelease pools */
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    pthread_setspecific(_dw_pool_key, pool);
    _dw_init_colors();
}

/*
 * Generally an internal function called from a terminating
 * thread to cleanup the Dynamic Windows environment for the thread.
 * However it is exported so language bindings can call it when
 * they exit threads that require access to Dynamic Windows.
 */
void API _dw_deinit_thread(void)
{
    UIColor *color;

    /* Release the pool when we are done so we don't leak */
    color = pthread_getspecific(_dw_fg_color_key);
    [color release];
    color = pthread_getspecific(_dw_bg_color_key);
    [color release];
    NSAutoreleasePool *pool = pthread_getspecific(_dw_pool_key);
    [pool drain];
}

/*
 * Setup thread independent pools.
 */
void _dwthreadstart(void *data)
{
    void (*threadfunc)(void *) = NULL;
    void **tmp = (void **)data;

    _dw_init_thread();

    threadfunc = (void (*)(void *))tmp[0];

    /* Start our thread function */
    threadfunc(tmp[1]);

    free(tmp);

    _dw_deinit_thread();
}

/*
 * Sets the default font used on text based widgets.
 * Parameters:
 *           fontname: Font name in Dynamic Windows format.
 */
void API dw_font_set_default(const char *fontname)
{
    UIFont *oldfont = DWDefaultFont;
    DWDefaultFont = nil;
    if(fontname)
    {
        DWDefaultFont = _dw_font_by_name(fontname);
        [DWDefaultFont retain];
    }
    [oldfont release];
}

/* Thread start stub to launch our main */
void _dw_main_launch(void **data)
{
    int (*_dwmain)(int argc, char **argv) = (int (*)(int, char **))data[0];
    int argc = DW_POINTER_TO_INT(data[1]);
    char **argv = (char **)data[2];

    _dwmain(argc, argv);
    free(data);
}

/* The iOS main thread... all this does is handle messages */
void _dw_main_thread(int argc, char *argv[])
{
    DWMainEvent = dw_event_new();
    dw_event_reset(DWMainEvent);
    /* We wait for the dw_init() in the user's main function */
    dw_event_wait(DWMainEvent, DW_TIMEOUT_INFINITE);
    DWThread = dw_thread_id();
    DWObj = [[DWObject alloc] init];
    /* Create object for handling timers */
    DWHandler = [[DWTimerHandler alloc] init];
    UIApplicationMain(argc, argv, nil, NSStringFromClass([DWAppDel class]));
    /* Shouldn't get here, but ... just in case */
    DWThread = (DWTID)-1;
}

/* If DWApp is uninitialized, initialize it */
void _dw_app_init(void)
{
    /* The UIApplication singleton won't be created until we call
     * UIApplicationMain() which blocks indefinitely.. so we have
     * a thread to just run UIApplicationMain() .. and wait until
     * we get a non-nil sharedApplication result.
     */
    while(!DWApp)
    {
        static int posted = FALSE;

        if(DWMainEvent && !posted)
        {
            /* Post the DWMainEvent so the main thread
             * will call UIApplicationMain() creating
             * the UIApplication sharedApplication.
             */
            posted = TRUE;
            dw_event_post(DWMainEvent);
        }
        pthread_yield_np();
        DWApp = [UIApplication sharedApplication];
    }
    dw_event_reset(DWMainEvent);
    [DWObj performSelectorOnMainThread:@selector(delayedInit:) withObject:nil waitUntilDone:YES];
}

/*
 * Initializes the Dynamic Windows engine.
 * Parameters:
 *           newthread: True if this is the only thread.
 *                      False if there is already a message loop running.
 */
int API dw_init(int newthread, int argc, char *argv[])
{
    char *lang = getenv("LANG");

    /* Correct the startup path if run from a bundle */
    if(argc > 0 && argv[0])
    {
        char *pathcopy = strdup(argv[0]);
        char *app = strstr(pathcopy, ".app/");
        char *binname = strrchr(pathcopy, '/');

        if(binname && (binname++) && !_dw_app_id[0])
        {
            /* If we have a binary name, use that for the Application ID instead. */
            snprintf(_dw_app_id, _DW_APP_ID_SIZE, "%s.%s", DW_APP_DOMAIN_DEFAULT, binname);
        }
        if(app)
        {
            char pathbuf[PATH_MAX+1] = { 0 };
            size_t len = (size_t)(app - pathcopy);

            if(len > 0)
            {
                strncpy(_dw_bundle_path, pathcopy, len + 4);
            }
            *app = 0;

            getcwd(pathbuf, PATH_MAX);

            /* If run from a bundle the path seems to be / */
            if(strcmp(pathbuf, "/") == 0)
            {
                char *pos = strrchr(pathcopy, '/');

                if(pos)
                {
                    strncpy(pathbuf, pathcopy, (size_t)(pos - pathcopy));
                    chdir(pathbuf);
                }
            }
        }
        if(pathcopy)
            free(pathcopy);
    }

    /* Just in case we can't obtain a path */
    if(!_dw_bundle_path[0])
        getcwd(_dw_bundle_path, PATH_MAX);

    /* Get the operating system version */
    NSString *version = [[NSProcessInfo processInfo] operatingSystemVersionString];
    const char *versionstr = [version UTF8String];
    sscanf(versionstr, "Version %d.%d.%d", &DWOSMajor, &DWOSMinor, &DWOSBuild);
    /* Set the locale... if it is UTF-8 pass it
     * directly, otherwise specify UTF-8 explicitly.
     */
    setlocale(LC_ALL, lang && strstr(lang, ".UTF-8") ? lang : "UTF-8");
    /* Create the application object */
    _dw_app_init();
    pthread_key_create(&_dw_pool_key, NULL);
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    _dw_toplevel_windows = [[NSMutableArray new] retain];
    pthread_setspecific(_dw_pool_key, pool);
    pthread_key_create(&_dw_fg_color_key, NULL);
    pthread_key_create(&_dw_bg_color_key, NULL);
    _dw_init_colors();
    DWDefaultFont = nil;
    if (@available(iOS 10.0, *))
    {
        if([[NSBundle mainBundle] bundleIdentifier] != nil)
        {
            UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
            if(center)
            {
                [center requestAuthorizationWithOptions:UNAuthorizationOptionAlert|UNAuthorizationOptionSound
                        completionHandler:^(BOOL granted, NSError * _Nullable error) {
                            if (granted)
                            {
                                center.delegate = [[DWUserNotificationCenterDelegate alloc] init];
                            }
                            else
                            {
                                NSLog(@"WARNING: Unable to get notification permission. %@", error.localizedDescription);
                            }
                }];
            }
        }
    }
    _DWDirtyDrawables = [[NSMutableArray alloc] init];
    /* Use NSThread to start a dummy thread to initialize the threading subsystem */
    NSThread *thread = [[ NSThread alloc] initWithTarget:DWObj selector:@selector(uselessThread:) object:nil];
    [thread start];
    [thread release];
    if(!_dw_app_id[0])
    {
        /* Generate an Application ID based on the PID if all else fails. */
        snprintf(_dw_app_id, _DW_APP_ID_SIZE, "%s.pid.%d", DW_APP_DOMAIN_DEFAULT, getpid());
    }
    return DW_ERROR_NONE;
}

/*
 * Allocates a shared memory region with a name.
 * Parameters:
 *         handle: A pointer to receive a SHM identifier.
 *         dest: A pointer to a pointer to receive the memory address.
 *         size: Size in bytes of the shared memory region to allocate.
 *         name: A string pointer to a unique memory name.
 */
HSHM dw_named_memory_new(void **dest, int size, const char *name)
{
   char namebuf[1025] = {0};
   struct _dw_unix_shm *handle = malloc(sizeof(struct _dw_unix_shm));

   mkdir("/tmp/.dw", S_IWGRP|S_IWOTH);
   snprintf(namebuf, 1024, "/tmp/.dw/%s", name);

   if((handle->fd = open(namebuf, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)) < 0)
   {
      free(handle);
      return NULL;
   }

   ftruncate(handle->fd, size);

   /* attach the shared memory segment to our process's address space. */
   *dest = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle->fd, 0);

   if(*dest == MAP_FAILED)
   {
      close(handle->fd);
      *dest = NULL;
      free(handle);
      return NULL;
   }

   handle->size = size;
   handle->sid = getsid(0);
   handle->path = strdup(namebuf);

   return handle;
}

/*
 * Aquires shared memory region with a name.
 * Parameters:
 *         dest: A pointer to a pointer to receive the memory address.
 *         size: Size in bytes of the shared memory region to requested.
 *         name: A string pointer to a unique memory name.
 */
HSHM dw_named_memory_get(void **dest, int size, const char *name)
{
   char namebuf[1025];
   struct _dw_unix_shm *handle = malloc(sizeof(struct _dw_unix_shm));

   mkdir("/tmp/.dw", S_IWGRP|S_IWOTH);
   snprintf(namebuf, 1024, "/tmp/.dw/%s", name);

   if((handle->fd = open(namebuf, O_RDWR)) < 0)
   {
      free(handle);
      return NULL;
   }

   /* attach the shared memory segment to our process's address space. */
   *dest = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle->fd, 0);

   if(*dest == MAP_FAILED)
   {
      close(handle->fd);
      *dest = NULL;
      free(handle);
      return NULL;
   }

   handle->size = size;
   handle->sid = -1;
   handle->path = NULL;

   return handle;
}

/*
 * Frees a shared memory region previously allocated.
 * Parameters:
 *         handle: Handle obtained from DB_named_memory_allocate.
 *         ptr: The memory address aquired with DB_named_memory_allocate.
 */
int dw_named_memory_free(HSHM handle, void *ptr)
{
   struct _dw_unix_shm *h = handle;
   int rc = munmap(ptr, h->size);

   close(h->fd);
   if(h->path)
   {
      /* Only remove the actual file if we are the
       * creator of the file.
       */
      if(h->sid != -1 && h->sid == getsid(0))
         remove(h->path);
      free(h->path);
   }
   return rc;
}

/*
 * Creates a new thread with a starting point of func.
 * Parameters:
 *       func: Function which will be run in the new thread.
 *       data: Parameter(s) passed to the function.
 *       stack: Stack size of new thread (OS/2 and Windows only).
 */
DWTID dw_thread_new(void *func, void *data, int stack)
{
    DWTID thread;
    void **tmp = malloc(sizeof(void *) * 2);
    int rc;

   tmp[0] = func;
   tmp[1] = data;

   rc = pthread_create(&thread, NULL, (void *)_dwthreadstart, (void *)tmp);
   if(rc == 0)
      return thread;
   return (DWTID)-1;
}

/*
 * Ends execution of current thread immediately.
 */
void dw_thread_end(void)
{
   pthread_exit(NULL);
}

/*
 * Returns the current thread's ID.
 */
DWTID dw_thread_id(void)
{
   return (DWTID)pthread_self();
}

/*
 * Execute and external program in a seperate session.
 * Parameters:
 *       program: Program name with optional path.
 *       type: Either DW_EXEC_CON or DW_EXEC_GUI.
 *       params: An array of pointers to string arguements.
 * Returns:
 *       DW_ERROR_UNKNOWN (-1) on error.
 */
int API dw_exec(const char *program, int type, char **params)
{
    int retval = DW_ERROR_UNKNOWN;
    pid_t pid;

    /* Launch the application directly using posix_spawnp()
     * Might need to use openURLs to open DW_EXEC_GUI.
     */
    if(posix_spawnp(&pid, program, NULL, NULL, params, NULL) == 0)
    {
        if(pid > 0)
            retval = pid;
        else
            retval = DW_ERROR_NONE;
    }
    return retval;
}

/*
 * Loads a web browser pointed at the given URL.
 * Parameters:
 *       url: Uniform resource locator.
 */
int dw_browse(const char *url)
{
    NSURL *myurl = [NSURL URLWithString:[NSString stringWithUTF8String:url]];
    [DWApp openURL:myurl options:@{}
           completionHandler:^(BOOL success) {}];
    return DW_ERROR_NONE;
}

/*
 * Creates a new print object.
 * Parameters:
 *       jobname: Name of the print job to show in the queue.
 *       flags: Flags to initially configure the print object.
 *       pages: Number of pages to print.
 *       drawfunc: The pointer to the function to be used as the callback.
 *       drawdata: User data to be passed to the handler function.
 * Returns:
 *       A handle to the print object or NULL on failure.
 */
HPRINT API dw_print_new(const char *jobname, unsigned long flags, unsigned int pages, void *drawfunc, void *drawdata)
{
    if(drawfunc)
    {
        UIPrintInfo *pi = [UIPrintInfo alloc];
        DWPrintPageRenderer *renderer = [DWPrintPageRenderer new];
        NSMutableArray *pa = [[[NSMutableArray alloc] initWithObjects:pi, renderer, nil] retain];

        if(!jobname)
            jobname = "Dynamic Windows Print Job";

        [pi setOutputType:UIPrintInfoOutputGeneral];
        [pi setJobName:[NSString stringWithUTF8String:jobname]];
        [pi setOrientation:UIPrintInfoOrientationPortrait];
        [renderer setDrawFunc:drawfunc];
        [renderer setDrawData:drawdata];
        [renderer setPages:pages];
        [renderer setFlags:flags];
        [renderer setPrint:pa];
        return pa;
    }
    return NULL;
}

/*
 * Runs the print job, causing the draw page callbacks to fire.
 * Parameters:
 *       print: Handle to the print object returned by dw_print_new().
 *       flags: Flags to run the print job.
 * Returns:
 *       DW_ERROR_UNKNOWN on error or DW_ERROR_NONE on success.
 */
int API dw_print_run(HPRINT print, unsigned long flags)
{
    int retval = DW_ERROR_UNKNOWN;

    if(print)
    {
        NSMutableArray *pa = print;

        [pa addObject:[NSNumber numberWithUnsignedLong:flags]];
        [DWObj safeCall:@selector(printDialog:) withObject:pa];
        retval = (int)[[pa lastObject] integerValue];
        [pa release];
    }
    return retval;
}

/*
 * Cancels the print job, typically called from a draw page callback.
 * Parameters:
 *       print: Handle to the print object returned by dw_print_new().
 */
void API dw_print_cancel(HPRINT print)
{
    if(print)
    {
        NSMutableArray *pa = print;
        UIPrintInteractionController *pc = [UIPrintInteractionController sharedPrintController];

        [pc dismissAnimated:YES];
        [pa release];
    }
}

/*
 * Converts a UTF-8 encoded string into a wide string.
 * Parameters:
 *       utf8string: UTF-8 encoded source string.
 * Returns:
 *       Wide string that needs to be freed with dw_free()
 *       or NULL on failure.
 */
wchar_t * API dw_utf8_to_wchar(const char *utf8string)
{
    size_t buflen = strlen(utf8string) + 1;
    wchar_t *temp = malloc(buflen * sizeof(wchar_t));
    if(temp)
        mbstowcs(temp, utf8string, buflen);
    return temp;
}

/*
 * Converts a wide string into a UTF-8 encoded string.
 * Parameters:
 *       wstring: Wide source string.
 * Returns:
 *       UTF-8 encoded string that needs to be freed with dw_free()
 *       or NULL on failure.
 */
char * API dw_wchar_to_utf8(const wchar_t *wstring)
{
    size_t bufflen = 8 * wcslen(wstring) + 1;
    char *temp = malloc(bufflen);
    if(temp)
        wcstombs(temp, wstring, bufflen);
    return temp;
}

/*
 * Gets the state of the requested library feature.
 * Parameters:
 *       feature: The requested feature for example DW_FEATURE_DARK_MODE
 * Returns:
 *       DW_FEATURE_UNSUPPORTED if the library or OS does not support the feature.
 *       DW_FEATURE_DISABLED if the feature is supported but disabled.
 *       DW_FEATURE_ENABLED if the feature is supported and enabled.
 *       Other value greater than 1, same as enabled but with extra info.
 */
int API dw_feature_get(DWFEATURE feature)
{
    switch(feature)
    {
        case DW_FEATURE_NOTIFICATION:
        case DW_FEATURE_MLE_AUTO_COMPLETE:
        case DW_FEATURE_HTML:
        case DW_FEATURE_HTML_RESULT:
        case DW_FEATURE_CONTAINER_STRIPE:
        case DW_FEATURE_MLE_WORD_WRAP:
        case DW_FEATURE_UTF8_UNICODE:
        case DW_FEATURE_TREE:
            return DW_FEATURE_ENABLED;
        case DW_FEATURE_CONTAINER_MODE:
            return _dw_container_mode;
        case DW_FEATURE_DARK_MODE:
        {
            if(DWObj)
            {
                NSMutableArray *array = [[NSMutableArray alloc] init];
                int retval = DW_FEATURE_UNSUPPORTED;
                NSNumber *number;

                [DWObj safeCall:@selector(getUserInterfaceStyle:) withObject:array];
                number = [array lastObject];
                if(number)
                    retval = [number intValue];
                return retval;
            }
            return _dw_dark_mode_state;
        }
        default:
            return DW_FEATURE_UNSUPPORTED;
    }
}

/*
 * Sets the state of the requested library feature.
 * Parameters:
 *       feature: The requested feature for example DW_FEATURE_DARK_MODE
 *       state: DW_FEATURE_DISABLED, DW_FEATURE_ENABLED or any value greater than 1.
 * Returns:
 *       DW_FEATURE_UNSUPPORTED if the library or OS does not support the feature.
 *       DW_ERROR_NONE if the feature is supported and successfully configured.
 *       DW_ERROR_GENERAL if the feature is supported but could not be configured.
 * Remarks: 
 *       These settings are typically used during dw_init() so issue before 
 *       setting up the library with dw_init().
 */
int API dw_feature_set(DWFEATURE feature, int state)
{
    switch(feature)
    {
        /* These features are supported but not configurable */
        case DW_FEATURE_NOTIFICATION:
        case DW_FEATURE_MLE_AUTO_COMPLETE:
        case DW_FEATURE_HTML:
        case DW_FEATURE_HTML_RESULT:
        case DW_FEATURE_CONTAINER_STRIPE:
        case DW_FEATURE_MLE_WORD_WRAP:
        case DW_FEATURE_UTF8_UNICODE:
        case DW_FEATURE_TREE:
            return DW_ERROR_GENERAL;
        case DW_FEATURE_CONTAINER_MODE:
        {
            if(state >= DW_CONTAINER_MODE_DEFAULT && state < DW_CONTAINER_MODE_MAX)
            {
                _dw_container_mode = state;
                return DW_ERROR_NONE;
            }
            return DW_ERROR_GENERAL;
        }
        case DW_FEATURE_DARK_MODE:
        {
            /* Make sure DWObj is initialized */
            if(DWObj)
            {
                /* Disabled forces the non-dark aqua theme */
                if(state == DW_FEATURE_DISABLED)
                    [DWObj safeCall:@selector(setUserInterfaceStyle:) withObject:[NSNumber numberWithInt:UIUserInterfaceStyleLight]];
                /* Enabled lets the OS decide the mode */
                else if(state == DW_FEATURE_ENABLED || state == DW_DARK_MODE_FULL)
                    [DWObj safeCall:@selector(setUserInterfaceStyle:) withObject:[NSNumber numberWithInt:UIUserInterfaceStyleUnspecified]];
                /* 2 forces dark mode aqua appearance */
                else if(state == DW_DARK_MODE_FORCED)
                    [DWObj safeCall:@selector(setUserInterfaceStyle:) withObject:[NSNumber numberWithInt:UIUserInterfaceStyleDark]];
            }
            else /* Save the requested state for dw_init() */
                _dw_dark_mode_state = state;
            return DW_ERROR_NONE;
        }
        default:
            return DW_FEATURE_UNSUPPORTED;
    }
}