view android/dw.cpp @ 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 GUI implementation of the Android GUI.
 *
 * (C) 2011-2022 Brian Smith <brian@dbsoft.org>
 * (C) 2011-2022 Mark Hessling <mark@rexx.org>
 *
 * Requires Android API 23 (Marshmallow) or higher.
 *
 */

#include "dw.h"
#include <stdlib.h>
#include <string.h>
#include <sys/utsname.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#if defined(__ANDROID__) && (__ANDROID_API__+0) < 21
#include <sys/syscall.h>

/* Until Android API version 21 NDK does not define getsid wrapper in libc, although there is the corresponding syscall */
inline pid_t getsid(pid_t pid)
{
    return static_cast< pid_t >(::syscall(__NR_getsid, pid));
}
#endif

#ifdef __cplusplus
extern "C" {
#endif

#define DW_CLASS_NAME "org/dbsoft/dwindows/DWindows"

/* Dynamic Windows internal variables */
static char _dw_app_id[_DW_APP_ID_SIZE+1]= {0};
static char _dw_app_name[_DW_APP_ID_SIZE+1]= {0};
static char _dw_exec_dir[MAX_PATH+1] = {0};
static char _dw_user_dir[MAX_PATH+1] = {0};
static int _dw_android_api = 0;
static int _dw_container_mode = DW_CONTAINER_MODE_DEFAULT;

static pthread_key_t _dw_env_key;
static pthread_key_t _dw_fgcolor_key;
static pthread_key_t _dw_bgcolor_key;
static HEV _dw_main_event;
static JavaVM *_dw_jvm;
static jobject _dw_obj;
static jobject _dw_class_loader;
static jmethodID _dw_class_method;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved)
{
    JNIEnv *env;
    jclass randomClass, classClass, classLoaderClass;
    jmethodID getClassLoaderMethod;

    /* Initialize the handle to the Java Virtual Machine */
    _dw_jvm = pjvm;
    _dw_jvm->GetEnv((void**)&env, JNI_VERSION_1_6);

    randomClass = env->FindClass(DW_CLASS_NAME);
    classClass = env->GetObjectClass(randomClass);
    classLoaderClass = env->FindClass("java/lang/ClassLoader");
    getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
                                                 "()Ljava/lang/ClassLoader;");
    _dw_class_loader = env->NewGlobalRef(env->CallObjectMethod(randomClass, getClassLoaderMethod));
    _dw_class_method = env->GetMethodID(classLoaderClass, "findClass",
                                        "(Ljava/lang/String;)Ljava/lang/Class;");

    return JNI_VERSION_1_6;
}

jclass _dw_find_class(JNIEnv *env, const char* name)
{
    jstring jname = env->NewStringUTF(name);
    jclass clazz = static_cast<jclass>(env->CallObjectMethod(_dw_class_loader, _dw_class_method, jname));
    env->DeleteLocalRef(jname);
    return clazz;
}

// Do a quick check if an exception occurred...
// Display then clear it so we can continue...
// Return TRUE if an exception had occurred
int _dw_jni_check_exception(JNIEnv *env)
{
    if(env->ExceptionCheck())
    {
        env->ExceptionDescribe();
        env->ExceptionClear();
        return TRUE;
    }
    return FALSE;
}

#define _DW_REFERENCE_NONE   0
#define _DW_REFERENCE_WEAK   1
#define _DW_REFERENCE_STRONG 2

// Handle returns from JNI functions and make sure the
// return value is sane...
// Reference: 0 no reference 1 weak 2 strong
jobject _dw_jni_check_result(JNIEnv *env, jobject obj, int reference)
{
    jobject result = nullptr;

    if(!_dw_jni_check_exception(env) && obj)
    {
        if(reference == _DW_REFERENCE_WEAK)
            result = env->NewWeakGlobalRef(obj);
        else if(reference == _DW_REFERENCE_STRONG)
            result = env->NewGlobalRef(obj);
        else
            result = obj;
    }
    return result;
}

/* Call the dwmain entry point, Android has no args, so just pass the app path */
void _dw_main_launch(char *arg)
{
    static HEV startup = nullptr;

    /* Safety check to prevent multiple initializations... */
    if(startup)
    {
        /* If we are called a second time.. post the event...
         * so we stop waiting.
         */
        dw_event_post(startup);
        return;
    }
    else
    {
        char *argv[2] = {arg, nullptr};

        /* Wait for a short while to see if we get called again...
         * if we get called again we will be posted, if not...
         * just wait for the timer to expire.
         */
        startup = dw_event_new();
        /* Wait for 10 seconds to see if we get called again */
        dw_event_wait(startup, 10000);

        /* Continue after being posted or after our timeout */
        dwmain(1, argv);
    }
    free(arg);
}

/* Called when DWindows activity starts up... so we create a thread
 *  to call the dwmain() entrypoint... then we wait for dw_main()
 *  to be called and return.
 * Parameters:
 *      path: The path to the Android app.
 */
JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWindows_dwindowsInit(JNIEnv* env, jobject obj, jstring apppath, jstring appcache, jstring appID)
{
    const char *arg = env->GetStringUTFChars((jstring)apppath, nullptr);
    const char *cache = env->GetStringUTFChars((jstring)appcache, nullptr);
    const char *appid = env->GetStringUTFChars((jstring)appID, nullptr);
    char *home = getenv("HOME");

    if(!_dw_main_event)
    {
        /* Save our class object pointer for later */
        _dw_obj = env->NewGlobalRef(obj);

        /* Save the JNIEnv for the main thread */
        pthread_key_create(&_dw_env_key, nullptr);
        pthread_setspecific(_dw_env_key, env);
        pthread_key_create(&_dw_fgcolor_key, nullptr);
        pthread_setspecific(_dw_fgcolor_key, nullptr);
        pthread_key_create(&_dw_bgcolor_key, nullptr);
        pthread_setspecific(_dw_bgcolor_key, (void *)DW_RGB_TRANSPARENT);

        /* Create the dwmain event */
        _dw_main_event = dw_event_new();
    }

    if(cache)
    {
        /* Store the passed in path for dw_app_dir() */
        strncpy(_dw_exec_dir, cache, MAX_PATH);
        env->ReleaseStringUTFChars(appcache, cache);
    }
    /* Store the best path for dw_user_dir() */
    if(home)
        strncpy(_dw_user_dir, home, MAX_PATH);
    else if(arg)
        strncpy(_dw_user_dir, arg, MAX_PATH);
    else
        strcpy(_dw_user_dir, "/");
    if(appid)
    {
        /* Store our reported Android AppID */
        strncpy(_dw_app_id, appid, _DW_APP_ID_SIZE);
        env->ReleaseStringUTFChars(appID, appid);
    }

    /* Launch the new thread to execute dwmain() */
    dw_thread_new((void *) _dw_main_launch, strdup(arg ? arg : ""), 0);

    if(arg)
        env->ReleaseStringUTFChars(apppath, arg);
}

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

} DWSignalHandler;

static DWSignalHandler *DWRoot = nullptr;

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

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key))) {
        /* Find any callbacks for this function */
        while (tmp) {
            if (tmp->message == messageid && env->IsSameObject(window, tmp->window)) {
                return tmp;
            }
            tmp = tmp->next;
        }
    }
    return nullptr;
}

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 }
};

#define _DW_EVENT_PARAM_SIZE 10

int _dw_event_handler2(void **params)
{
    DWSignalHandler *handler = (DWSignalHandler *)params[9];
    int message = DW_POINTER_TO_INT(params[8]);
    int retval = -1;

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

                if(!timerfunc(handler->data))
                    dw_timer_disconnect(handler->window);
                retval = 0;
                break;
            }
                /* Configure/Resize event */
            case _DW_EVENT_CONFIGURE:
            {
                int (*sizefunc)(HWND, int, int, void *) = (int (* API)(HWND, int, int, void *))handler->signalfunction;
                int width = DW_POINTER_TO_INT(params[3]);
                int height = DW_POINTER_TO_INT(params[4]);

                if(width > 0 && height > 0)
                    retval = sizefunc(handler->window, width, height, handler->data);
                else
                    retval = 0;
                break;
            }
            case _DW_EVENT_KEY_PRESS:
            {
                int (*keypressfunc)(HWND, char, int, int, void *, char *) = (int (* API)(HWND, char, int, int, void *, char *))handler->signalfunction;
                char *utf8 = (char *)params[1], ch = (char)DW_POINTER_TO_INT(params[3]);
                int vk = DW_POINTER_TO_INT(params[4]), special = DW_POINTER_TO_INT(params[5]);

                retval = keypressfunc(handler->window, ch, (int)vk, special, handler->data, utf8);
                if(utf8)
                    free(utf8);
                break;
            }
                /* 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;
                int button = params[5] ? DW_POINTER_TO_INT(params[5]) : 1;

                retval = buttonfunc(handler->window, DW_POINTER_TO_INT(params[3]), DW_POINTER_TO_INT(params[4]), button, handler->data);
                break;
            }
            /* 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;

                retval = motionfunc(handler->window, DW_POINTER_TO_INT(params[3]), DW_POINTER_TO_INT(params[4]), DW_POINTER_TO_INT(params[5]), handler->data);
                break;
            }
            /* Window close event */
            case _DW_EVENT_DELETE:
            {
                int (* API closefunc)(HWND, void *) = (int (* API)(HWND, void *))handler->signalfunction;
                retval = closefunc(handler->window, handler->data);
                if(retval < 1)
                    dw_window_destroy(handler->window);
                break;
            }
            /* Window expose/draw event */
            case _DW_EVENT_EXPOSE:
            {
                DWExpose exp;
                int (* API exposefunc)(HWND, DWExpose *, void *) = (int (* API)(HWND, DWExpose *, void *))handler->signalfunction;

                exp.x = DW_POINTER_TO_INT(params[3]);
                exp.y = DW_POINTER_TO_INT(params[4]);
                exp.width = DW_POINTER_TO_INT(params[5]);
                exp.height = DW_POINTER_TO_INT(params[6]);
                retval = exposefunc(handler->window, &exp, handler->data);
                /* Return here so we don't free params since we
                 * are always handling expose/draw on the UI thread.
                 */
                return retval;
            }
                /* Clicked event for buttons and menu items */
            case _DW_EVENT_CLICKED:
            {
                int (* API clickfunc)(HWND, void *) = (int (* API)(HWND, void *))handler->signalfunction;

                retval = clickfunc(handler->window, handler->data);
                break;
            }
                /* Container class selection event */
            case _DW_EVENT_ITEM_ENTER:
            {
                int (*containerselectfunc)(HWND, char *, void *, void *) =(int (* API)(HWND, char *, void *, void *)) handler->signalfunction;
                char *text = (char *)params[1];

                retval = containerselectfunc(handler->window, text, handler->data, params[7]);
                if(text)
                    free(text);
                break;
            }
                /* 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 *)params[1];
                void *user = params[7];
                int x = DW_POINTER_TO_INT(params[3]);
                int y = DW_POINTER_TO_INT(params[4]);

                retval = containercontextfunc(handler->window, text, x, y, handler->data, user);
                if(text)
                    free(text);
                break;
            }
                /* 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(params[3]);

                retval = valuechangedfunc(handler->window, selected, handler->data);
                break;
            }
                /* 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 = (char *)params[1];
                void *user = params[7];

                retval = treeselectfunc(handler->window, (jobject)params[0], text, handler->data, user);
                if(text)
                    free(text);
                break;
            }
                /* Set Focus event */
            case _DW_EVENT_SET_FOCUS:
            {
                int (* API setfocusfunc)(HWND, void *) = (int (* API)(HWND, void *))handler->signalfunction;

                retval = setfocusfunc(handler->window, handler->data);
                break;
            }
                /* Notebook page change event */
            case _DW_EVENT_SWITCH_PAGE:
            {
                int (* API switchpagefunc)(HWND, unsigned long, void *) = (int (* API)(HWND, unsigned long, void *))handler->signalfunction;
                unsigned long pageID = DW_POINTER_TO_INT(params[3]);

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

                retval = treeexpandfunc(handler->window, (HTREEITEM)params[0], handler->data);
                break;
            }
                /* Column click event */
            case _DW_EVENT_COLUMN_CLICK:
            {
                int (* API clickcolumnfunc)(HWND, int, void *) = (int (* API)(HWND, int, void *))handler->signalfunction;
                int column_num = DW_POINTER_TO_INT(params[3]);

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

                retval = htmlresultfunc(handler->window, result ? DW_ERROR_NONE : DW_ERROR_UNKNOWN, result, params[7], handler->data);
                if(result)
                    free(result);
                break;
            }
                /* HTML changed event */
            case _DW_EVENT_HTML_CHANGED:
            {
                int (* API htmlchangedfunc)(HWND, int, char *, void *) = (int (* API)(HWND, int, char *, void *))handler->signalfunction;
                char *uri = (char *)params[1];

                retval = htmlchangedfunc(handler->window, DW_POINTER_TO_INT(params[3]), uri, handler->data);
                if(uri)
                    free(uri);
                break;
            }
        }
    }
    return retval;
}

/* We create a queue of events... the oldest event indexed by
 * _dw_event_head, the newest by _dw_event_tail.
 */
#define _DW_EVENT_QUEUE_LENGTH 10
void *_dw_event_queue[_DW_EVENT_QUEUE_LENGTH][_DW_EVENT_PARAM_SIZE];
int _dw_event_head = -1, _dw_event_tail = -1, _dw_main_active = TRUE;
HMTX _dw_event_mutex = nullptr;
DWTID _dw_main_thread = -1;

/* Add a new event to the queue if there is space.
 * This will be handled in the thread running dw_main()
 */
int _dw_queue_event(void **params)
{
    int newtail = _dw_event_tail + 1;
    int retval = FALSE;

    /* Initialize the mutex if necessary... return on failure. */
    if(!_dw_event_mutex && !(_dw_event_mutex = dw_mutex_new()))
        return retval;

    /* Protect the queue in a mutex... hold for as short as possible */
    while(dw_mutex_trylock(_dw_event_mutex) != DW_ERROR_NONE)
        sched_yield();

    /* If we are at the end of the queue, loop back to the start. */
    if(newtail >= _DW_EVENT_QUEUE_LENGTH)
        newtail = 0;
    /* If the new tail will be at the head, the event queue
     * if full... drop the event.
     */
    if(newtail != _dw_event_head)
    {
        /* If the queue was empty, head and tail will be the same. */
        if (_dw_event_head == -1)
            _dw_event_head = newtail;
        /* Copy the new event from the stack into the event queue. */
        memcpy(&_dw_event_queue[newtail], params, _DW_EVENT_PARAM_SIZE * sizeof(void *));
        /* Update the tail index */
        _dw_event_tail = newtail;
        /* Successfully queued event */
        retval = TRUE;
    }
    dw_mutex_unlock(_dw_event_mutex);
    return retval;
}

/* If there is an event waiting, pop it off the queue,
 * advance the head and return TRUE.
 * If there are no events waiting return FALSE
 */
int _dw_dequeue_event(void **params)
{
    int retval = FALSE;

    /* Initialize the mutex if necessary... return FALSE on failure. */
    if(!_dw_event_mutex && !(_dw_event_mutex = dw_mutex_new()))
        return retval;
    dw_mutex_lock(_dw_event_mutex);
    if(_dw_event_head != -1)
    {
        /* Copy the params out of the queue so it can be filled in by new events */
        memcpy(params, &_dw_event_queue[_dw_event_head], _DW_EVENT_PARAM_SIZE * sizeof(void *));

        /* If the head is the same as the tail...
         * there was only one event... so set the
         * head and tail to -1 to indicate empty.
         */
        if(_dw_event_head == _dw_event_tail)
            _dw_event_head = _dw_event_tail = -1;
        else
        {
            /* Advance the head */
            _dw_event_head++;

            /* If we are at the end of the queue, loop back to the start. */
            if(_dw_event_head >= _DW_EVENT_QUEUE_LENGTH)
                _dw_event_head = 0;
        }
        /* Successfully dequeued event */
        retval = TRUE;
        /* Notify dw_main() that there is an event to handle */
        dw_event_post(_dw_main_event);
    }
    dw_mutex_unlock(_dw_event_mutex);
    return retval;
}

int _dw_event_handler(jobject object, void **params)
{
    int messageid = DW_POINTER_TO_INT(params[8]);
    DWSignalHandler *handler = _dw_get_handler(object, messageid);

    if (handler)
    {
        params[9] = (void *)handler;

        /* We have to handle draw events in the main thread...
         * If it isn't a draw event, either queue the event
         * or launch a new thread to handle it.
         */
        if(DW_POINTER_TO_INT(params[8]) != _DW_EVENT_EXPOSE)
        {
            /* Push the new event onto the queue if it fits */
            _dw_queue_event(params);
        }
        else
            return _dw_event_handler2(params);

    } /* If we don't have a handler, destroy the window */
    else if(messageid == _DW_EVENT_DELETE)
        dw_window_destroy(object);
    return 0;
}

/*
 * Entry location for all event handlers from the Android UI
 */
JNIEXPORT jint JNICALL
Java_org_dbsoft_dwindows_DWindows_eventHandler(JNIEnv* env, jobject obj, jobject obj1, jobject obj2,
                                                      jint message, jstring str1, jstring str2,
                                                      jint inta, jint intb, jint intc, jint intd) {
    const char *utf81 = str1 ? env->GetStringUTFChars(str1, nullptr) : nullptr;
    const char *utf82 = str2 ? env->GetStringUTFChars(str2, nullptr) : nullptr;
    void *params[_DW_EVENT_PARAM_SIZE] = { DW_POINTER(obj2), DW_POINTER(utf81 ? strdup(utf81) : nullptr),
                                           DW_POINTER(utf82 ? strdup(utf82) : nullptr),
                                           DW_INT_TO_POINTER(inta), DW_INT_TO_POINTER(intb),
                                           DW_INT_TO_POINTER(intc), DW_INT_TO_POINTER(intd), nullptr,
                                           DW_INT_TO_POINTER(message), nullptr };

    int retval = _dw_event_handler(obj1, params);
    if(utf81)
        env->ReleaseStringUTFChars(str1, utf81);
    if(utf82)
        env->ReleaseStringUTFChars(str2, utf82);
    return retval;
}

/* A more simple method for quicker calls */
JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWindows_eventHandlerSimple(JNIEnv* env, jobject obj, jobject obj1, jint message)
{
    void *params[_DW_EVENT_PARAM_SIZE] = { nullptr };

    params[8] = DW_INT_TO_POINTER(message);
    _dw_event_handler(obj1, params);
}

/* Handler for notebook page changes */
JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWindows_eventHandlerNotebook(JNIEnv* env, jobject obj, jobject obj1, jint message, jlong pageID)
{
    void *params[_DW_EVENT_PARAM_SIZE] = { nullptr };

    params[3] = DW_INT_TO_POINTER(pageID);
    params[8] = DW_INT_TO_POINTER(message);
    _dw_event_handler(obj1, params);
}

/* Handlers for HTML events */
JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWindows_eventHandlerHTMLResult(JNIEnv* env, jobject obj, jobject obj1,
                                               jint message, jstring htmlResult, jlong data) {
    const char *result = env->GetStringUTFChars(htmlResult, nullptr);
    void *params[_DW_EVENT_PARAM_SIZE] = { nullptr, DW_POINTER(result ? strdup(result) : nullptr), nullptr,
                                           nullptr, nullptr, nullptr, nullptr, DW_INT_TO_POINTER(data),
                                           DW_INT_TO_POINTER(message), nullptr };

    _dw_event_handler(obj1, params);
    if(result)
        env->ReleaseStringUTFChars(htmlResult, result);
}

JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWWebViewClient_eventHandlerHTMLChanged(JNIEnv* env, jobject obj, jobject obj1,
                                                         jint message, jstring URI, jint status) {
    const char *uri = env->GetStringUTFChars(URI, nullptr);
    void *params[_DW_EVENT_PARAM_SIZE] = { nullptr, DW_POINTER(uri ? strdup(uri) : nullptr), nullptr,
                                           DW_INT_TO_POINTER(status), nullptr, nullptr, nullptr, nullptr,
                                           DW_INT_TO_POINTER(message), nullptr };

    _dw_event_handler(obj1, params);
    if(uri)
        env->ReleaseStringUTFChars(URI, uri);
}

typedef struct _dwprint
{
    int (*drawfunc)(HPRINT, HPIXMAP, int, void *);
    void *drawdata;
    unsigned long flags;
    char *jobname;
    jint pages;
    jobject printjob;
} DWPrint;

/* Handlers for Print events */
JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWPrintDocumentAdapter_eventHandlerPrintDraw(JNIEnv* env, jobject obj, jlong pr,
                                                                      jobject bitmap, jint page, jint width, jint height)
{
    DWPrint *print = (DWPrint *)pr;

    if(print && print->drawfunc)
    {
        HPIXMAP pixmap = (HPIXMAP)alloca(sizeof(struct _hpixmap));

        memset(pixmap, 0, sizeof(struct _hpixmap));
        pixmap->width = width;
        pixmap->height = height;
        pixmap->bitmap = bitmap;

        print->drawfunc(print, pixmap, (int)page, print->drawdata);
    }
}

JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWPrintDocumentAdapter_eventHandlerPrintFinish(JNIEnv* env, jobject obj, jlong pr)
{
    DWPrint *print = (DWPrint *) pr;

    if(print)
    {
        if(print->jobname)
            free(print->jobname);
        free(print);
    }
}

JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWindows_eventHandlerInt(JNIEnv* env, jobject obj, jobject obj1, jint message,
                                               jint inta, jint intb, jint intc, jint intd) {
    void *params[_DW_EVENT_PARAM_SIZE] = { nullptr, nullptr, nullptr,
                                           DW_INT_TO_POINTER(inta), DW_INT_TO_POINTER(intb),
                                           DW_INT_TO_POINTER(intc), DW_INT_TO_POINTER(intd), nullptr,
                                           DW_INT_TO_POINTER(message), nullptr };

    _dw_event_handler(obj1, params);
}

JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWComboBox_eventHandlerInt(JNIEnv* env, jobject obj, jint message,
                                                  jint inta, jint intb, jint intc, jint intd) {
    void *params[_DW_EVENT_PARAM_SIZE] = { nullptr, nullptr, nullptr,
                                           DW_INT_TO_POINTER(inta), DW_INT_TO_POINTER(intb),
                                           DW_INT_TO_POINTER(intc), DW_INT_TO_POINTER(intd), nullptr,
                                           DW_INT_TO_POINTER(message), nullptr };

    _dw_event_handler(obj, params);
}

JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWSpinButton_eventHandlerInt(JNIEnv* env, jobject obj, jint message,
                                                    jint inta, jint intb, jint intc, jint intd) {
    void *params[_DW_EVENT_PARAM_SIZE] = { nullptr, nullptr, nullptr,
                                           DW_INT_TO_POINTER(inta), DW_INT_TO_POINTER(intb),
                                           DW_INT_TO_POINTER(intc), DW_INT_TO_POINTER(intd), nullptr,
                                           DW_INT_TO_POINTER(message), nullptr };

    _dw_event_handler(obj, params);
}

JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWListBox_eventHandlerInt(JNIEnv* env, jobject obj, jint message,
                                                    jint inta, jint intb, jint intc, jint intd) {
    void *params[_DW_EVENT_PARAM_SIZE] = { nullptr, nullptr, nullptr,
                                           DW_INT_TO_POINTER(inta), DW_INT_TO_POINTER(intb),
                                           DW_INT_TO_POINTER(intc), DW_INT_TO_POINTER(intd), nullptr,
                                           DW_INT_TO_POINTER(message), nullptr };

    _dw_event_handler(obj, params);
}

JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWRender_eventHandlerInt(JNIEnv* env, jobject obj, jint message,
                                                   jint inta, jint intb, jint intc, jint intd) {
    void *params[_DW_EVENT_PARAM_SIZE] = { nullptr, nullptr, nullptr,
                                           DW_INT_TO_POINTER(inta), DW_INT_TO_POINTER(intb),
                                           DW_INT_TO_POINTER(intc), DW_INT_TO_POINTER(intd), nullptr,
                                           DW_INT_TO_POINTER(message), nullptr };

    _dw_event_handler(obj, params);
}

JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWContainerAdapter_eventHandlerInt(JNIEnv* env, jobject obj, jobject obj1,
                                                            jint message, jint inta, jint intb, jint intc, jint intd) {
    void *params[_DW_EVENT_PARAM_SIZE] = { nullptr, nullptr, nullptr,
                                           DW_INT_TO_POINTER(inta), DW_INT_TO_POINTER(intb),
                                           DW_INT_TO_POINTER(intc), DW_INT_TO_POINTER(intd), nullptr,
                                           DW_INT_TO_POINTER(message), nullptr };

    _dw_event_handler(obj1, params);
}

JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWindows_eventHandlerContainer(JNIEnv* env, jobject obj, jobject obj1,
                                                  jint message, jstring jtitle, jint x, jint y, jlong data) {
    const char *title = jtitle ? env->GetStringUTFChars(jtitle, nullptr) : nullptr;
    void *params[_DW_EVENT_PARAM_SIZE] = { nullptr, DW_POINTER(title ? strdup(title) : nullptr), nullptr,
                                           DW_INT_TO_POINTER(x), DW_INT_TO_POINTER(y), nullptr, nullptr,
                                           DW_POINTER(data), DW_INT_TO_POINTER(message), nullptr };

    _dw_event_handler(obj1, params);
    if(title)
        env->ReleaseStringUTFChars(jtitle, title);
}

JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWindows_eventHandlerTree(JNIEnv* env, jobject obj, jobject obj1,
                                                   jint message, jobject item, jstring jtitle, jlong data) {
    const char *title = jtitle ? env->GetStringUTFChars(jtitle, nullptr) : nullptr;
    void *params[_DW_EVENT_PARAM_SIZE] = { DW_POINTER(item), DW_POINTER(title ? strdup(title) : nullptr),
                                           nullptr, nullptr, nullptr, nullptr, nullptr, DW_POINTER(data),
                                           DW_INT_TO_POINTER(message), nullptr };

    _dw_event_handler(obj1, params);
    if(title)
        env->ReleaseStringUTFChars(jtitle, title);
}

JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWindows_eventHandlerTreeItem(JNIEnv* env, jobject obj, jobject obj1,
                                                   jint message, jobject item) {
    void *params[_DW_EVENT_PARAM_SIZE] = { DW_POINTER(item), nullptr, nullptr, nullptr, nullptr,
                                           nullptr, nullptr, nullptr, DW_INT_TO_POINTER(message), nullptr };

    _dw_event_handler(obj1, params);
}

JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWindows_eventHandlerKey(JNIEnv *env, jobject obj, jobject obj1, jint message, jint ch,
                                                  jint vk, jint modifiers, jstring str) {
    const char *cstr = str ? env->GetStringUTFChars(str, nullptr) : nullptr;
    void *params[_DW_EVENT_PARAM_SIZE] = { nullptr, DW_POINTER(cstr ? strdup(cstr) : nullptr), nullptr,
                                           DW_INT_TO_POINTER(ch), DW_INT_TO_POINTER(vk), DW_INT_TO_POINTER(modifiers),
                                           nullptr, nullptr, DW_INT_TO_POINTER(message), nullptr };

    _dw_event_handler(obj1, params);
    if(cstr)
        env->ReleaseStringUTFChars(str, cstr);
}

/* Handler for Timer events */
JNIEXPORT jint JNICALL
Java_org_dbsoft_dwindows_DWindows_eventHandlerTimer(JNIEnv* env, jobject obj, jlong sigfunc, jlong data) {
    int (*timerfunc)(void *) = (int (* API)(void *))sigfunc;

    pthread_setspecific(_dw_env_key, env);
    return timerfunc((void *)data);
}

/* A more simple method for quicker calls */
JNIEXPORT void JNICALL
Java_org_dbsoft_dwindows_DWMenu_eventHandlerSimple(JNIEnv* env, jobject obj, jobject obj1, jint message) {
    void *params[_DW_EVENT_PARAM_SIZE] = { nullptr };

    params[8] = DW_INT_TO_POINTER(message);
    _dw_event_handler(obj1, params);
}


/* 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 *newsig = (DWSignalHandler *)malloc(sizeof(DWSignalHandler));

    newsig->message = message;
    newsig->window = window;
    newsig->id = msgid;
    newsig->signalfunction = signalfunction;
    newsig->discfunction = discfunc;
    newsig->data = data;
    newsig->next = nullptr;

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

/* 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_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 */
};

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

int _dw_dark_mode_detected()
{
    JNIEnv *env;
    int retval = DW_ERROR_UNKNOWN;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID darkModeDetected = env->GetMethodID(clazz, "darkModeDetected",
                                                      "()I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, darkModeDetected);
        _dw_jni_check_exception(env);
    }
    return retval;
}

/*
 * Runs a message loop for Dynamic Windows.
 */
void API dw_main(void)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID dwMain = env->GetMethodID(clazz, "dwMain",
                                            "()V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, dwMain);
        _dw_jni_check_exception(env);
    }

    /* Save our thread ID, so we know if we should handle
     * events in callback dw_main_sleep/iteration() calls.
     */
    _dw_main_thread = dw_thread_id();

    do
    {
        void *params[_DW_EVENT_PARAM_SIZE];

        dw_event_reset(_dw_main_event);

        /* Dequeue and handle any pending events */
        while(_dw_dequeue_event(params))
            _dw_event_handler2(params);

        /* Wait for something to wake us up,
         * either a posted event, or dw_main_quit()
         */
        dw_event_wait(_dw_main_event, 100);

    } while(_dw_main_active);
    _dw_main_thread = -1;
}

/*
 * Causes running dw_main() to return.
 */
void API dw_main_quit(void)
{
    _dw_main_active = FALSE;
    dw_event_post(_dw_main_event);
}

/*
 * 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)
{
    JNIEnv *env;

    /* If we are in an event callback from dw_main() ...
     * we need to continue handling events from the UI.
     */
    if(_dw_main_thread == dw_thread_id())
    {
        struct timeval tv, start;
        /* The time left to wait from the start */
        int difference = milliseconds;

        gettimeofday(&start, NULL);

        do
        {
            void *params[_DW_EVENT_PARAM_SIZE];

            dw_event_reset(_dw_main_event);

            /* Dequeue and handle any pending events */
            while(_dw_dequeue_event(params))
                _dw_event_handler2(params);

            /* Wait for something to wake us up,
             * either a posted event, or dw_main_quit()
             */
            dw_event_wait(_dw_main_event, difference);

            gettimeofday(&tv, NULL);

        } while(_dw_main_active && (difference = ((tv.tv_sec - start.tv_sec)*1000) + ((tv.tv_usec - start.tv_usec)/1000)) <= milliseconds);
    }
    else if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID mainSleep = env->GetMethodID(clazz, "mainSleep",
                                                  "(I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, mainSleep, milliseconds);
        _dw_jni_check_exception(env);
    }
}

/*
 * Processes a single message iteration and returns.
 */
void API dw_main_iteration(void)
{
    if(_dw_main_thread == dw_thread_id())
    {
        void *params[_DW_EVENT_PARAM_SIZE];

        /* Dequeue a single pending event */
        if (_dw_dequeue_event(params))
            _dw_event_handler2(params);
    }
    else
    /* If we sleep for 0 milliseconds... we will drop out
     * of the loop at the first idle moment
     */
        dw_main_sleep(0);
}

/*
 * 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)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID dwindowsExit = env->GetMethodID(clazz, "dwindowsExit",
                                                  "(I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, dwindowsExit, exitcode);
        _dw_jni_check_exception(env);
    }
    // We shouldn't get here, but in case JNI can't call dwindowsExit...
    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 contains the
 * current user directory.  Or the root directory if it could
 * not be determined.
 */
char * API dw_user_dir(void)
{
    return _dw_user_dir;
}

/*
 * Returns a pointer to a static buffer which contains the
 * private application data directory.
 */
char * API dw_app_dir(void)
{
    /* The path is passed in via JNI dwindowsInit() */
    return _dw_exec_dir;
}

/*
 * 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 nullptr.
 * 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 used on Windows and Android.  If nullptr is passed the
 *          detected name will be used, but a prettier name may be desired.
 */
int API dw_app_id_set(const char *appid, const char *appname)
{
    if(appid)
        strncpy(_dw_app_id, appid, _DW_APP_ID_SIZE);
    if(appname)
        strncpy(_dw_app_name, appname, _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);
    vfprintf(stderr, format, args);
    va_end(args);
}

void API dw_vdebug(const char *format, va_list args)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        char outbuf[1025] = {0};

        vsnprintf(outbuf, 1024, format, args);

        // Construct a String
        jstring jstr = env->NewStringUTF(outbuf);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID debugMessage = env->GetMethodID(clazz, "debugMessage",
                                               "(Ljava/lang/String;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, debugMessage, jstr);
        _dw_jni_check_exception(env);
        env->DeleteLocalRef(jstr);
    }
    else {
        /* Output to stderr, if there is another way to send it
         * on the implementation platform, change this.
         */
        vfprintf(stderr, format, 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.
 * Returns:
 *       DW_MB_RETURN_YES, DW_MB_RETURN_NO, DW_MB_RETURN_OK,
 *       or DW_MB_RETURN_CANCEL based on flags and user response.
 */
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)
{
    JNIEnv *env;
    int retval = 0;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        char outbuf[1025] = {0};

        vsnprintf(outbuf, 1024, format, args);

        // Construct a String
        jstring jstr = env->NewStringUTF(outbuf);
        jstring jstrtitle = env->NewStringUTF(title);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID messageBox = env->GetMethodID(clazz, "messageBox",
                                                  "(Ljava/lang/String;Ljava/lang/String;I)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, messageBox, jstrtitle, jstr, flags);
        _dw_jni_check_exception(env);
        env->DeleteLocalRef(jstr);
        env->DeleteLocalRef(jstrtitle);
    }
    return retval;
}

/*
 * 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:
 *       nullptr 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)
{
    JNIEnv *env;
    char *retval = nullptr;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Use a long parameter
        jstring jstr = env->NewStringUTF(title);
        jstring path = nullptr;
        jstring jext = nullptr;
        if(defpath)
            path = env->NewStringUTF(defpath);
        if(ext)
            jext = env->NewStringUTF(defpath);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID fileBrowse = env->GetMethodID(clazz, "fileBrowseNew",
                                                "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;");
        // Call the method on the object
        jstring jresult = (jstring)_dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, fileBrowse, jstr, path, jext, flags), _DW_REFERENCE_NONE);
        if(jresult)
        {
            const char *str = env->GetStringUTFChars(jresult, nullptr);
            if(str)
                retval = strdup(str);
        }
        env->DeleteLocalRef(jstr);
        if(path)
            env->DeleteLocalRef(path);
        if(jext)
            env->DeleteLocalRef(jext);
    }
    return retval;
}

int API dw_file_open(const char *path, int mode)
{
    int retval = -1;

    if(path)
    {
        JNIEnv *env;

        // If we have no URI we can use the normal open call
        if(!strstr(path, "://"))
            return open(path, mode);
        // If we have a URI, use Android calls to get the URI file descriptor
        if ((env = (JNIEnv *) pthread_getspecific(_dw_env_key)))
        {
            // dw_file_browse saves a second string with the URI after the path
            // So find the end of the string and check for a trailing \n
            // The URI will be after that if found.
            jstring jstr = env->NewStringUTF(path);
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID fileOpen = env->GetMethodID(clazz, "fileOpen",
                                                  "(Ljava/lang/String;I)I");
            // Call the method on the object
            retval = (int) env->CallIntMethod(_dw_obj, fileOpen, jstr, (jint) mode);
            _dw_jni_check_exception(env);
            env->DeleteLocalRef(jstr);
        }
    }
    return retval;
}

/*
 * Gets the contents of the default clipboard as text.
 * Parameters:
 *       None.
 * Returns:
 *       Pointer to an allocated string of text or nullptr if clipboard empty or contents could not
 *       be converted to text.
 */
char * API dw_clipboard_get_text()
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        const char *utf8 = nullptr;
        char *retval = nullptr;

        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID clipboardGetText = env->GetMethodID(clazz, "clipboardGetText",
                                                      "()Ljava/lang/String;");
        // Call the method on the object
        jstring result = (jstring)_dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, clipboardGetText), _DW_REFERENCE_NONE);
        // Get the UTF8 string result
        if(result)
            utf8 = env->GetStringUTFChars(result, nullptr);
        if(utf8)
        {
            retval = strdup(utf8);
            env->ReleaseStringUTFChars(result, utf8);
        }
        return retval;
    }
    return nullptr;
}

/*
 * Sets the contents of the default clipboard to the supplied text.
 * Parameters:
 *       str: Text to put on the clipboard.
 *       len: Length of the text.
 */
void API dw_clipboard_set_text(const char *str, int len)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(str);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID clipboardSetText = env->GetMethodID(clazz, "clipboardSetText",
                                                      "(Ljava/lang/String;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, clipboardSetText, jstr);
        _dw_jni_check_exception(env);
        env->DeleteLocalRef(jstr);
    }
}

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

    if(tmp)
    {
        tmp->eve = dw_event_new();
        dw_event_reset(tmp->eve);
        tmp->data = data;
        tmp->done = FALSE;
        tmp->result = nullptr;
    }
    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().
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_dialog_dismiss(DWDialog *dialog, void *result)
{
    dialog->result = result;
    dw_event_post(dialog->eve);
    dialog->done = TRUE;
    return DW_ERROR_NONE;
}

/*
 * 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).
 * Returns:
 *       The data passed to dw_dialog_dismiss().
 */
void * API dw_dialog_wait(DWDialog *dialog)
{
    void *tmp = nullptr;

    while(!dialog->done)
    {
        dw_main_iteration();
    }
    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.
 * Returns:
 *       A handle to a box or nullptr on failure.
 */
HWND API dw_box_new(int type, int pad)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID boxNew = env->GetMethodID(clazz, "boxNew", "(II)Landroid/widget/LinearLayout;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, boxNew, type, pad), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * 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.
 * Returns:
 *       A handle to a groupbox window or nullptr on failure.
 */
HWND API dw_groupbox_new(int type, int pad, const char *title)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = title ? env->NewStringUTF(title) : nullptr;
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID groupBoxNew = env->GetMethodID(clazz, "groupBoxNew",
                                                 "(IILjava/lang/String;)Landroid/widget/LinearLayout;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, groupBoxNew, type, pad, jstr), _DW_REFERENCE_WEAK);
        if(jstr)
            env->DeleteLocalRef(jstr);
        return result;
    }
    return nullptr;
}

/*
 * 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.
 * Returns:
 *       A handle to a scrollbox or nullptr on failure.
 */
HWND API dw_scrollbox_new(int type, int pad)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID scrollBoxNew = env->GetMethodID(clazz, "scrollBoxNew",
                                            "(II)Landroid/widget/ScrollView;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, scrollBoxNew, type, pad), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * Returns the position of the scrollbar in the scrollbox.
 * Parameters:
 *          handle: Handle to the scrollbox to be queried.
 *          orient: The vertical or horizontal scrollbar.
 * Returns:
 *       The vertical or horizontal position in the scrollbox.
 */
int API dw_scrollbox_get_pos(HWND handle, int orient)
{
    JNIEnv *env;
    int retval = DW_ERROR_UNKNOWN;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID scrollBoxGetPos = env->GetMethodID(clazz, "scrollBoxGetPos",
                                                     "(Landroid/widget/ScrollView;I)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, scrollBoxGetPos, handle, orient);
        if(_dw_jni_check_exception(env))
            retval = DW_ERROR_UNKNOWN;
    }
    return retval;
}

/*
 * Gets the range for the scrollbar in the scrollbox.
 * Parameters:
 *          handle: Handle to the scrollbox to be queried.
 *          orient: The vertical or horizontal scrollbar.
 * Returns:
 *       The vertical or horizontal range of the scrollbox.
 */
int API dw_scrollbox_get_range(HWND handle, int orient)
{
    JNIEnv *env;
    int retval = DW_ERROR_UNKNOWN;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID scrollBoxGetRange = env->GetMethodID(clazz, "scrollBoxGetRange",
                                                       "(Landroid/widget/ScrollView;I)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, scrollBoxGetRange, handle, orient);
        if(_dw_jni_check_exception(env))
            retval = DW_ERROR_UNKNOWN;
    }
    return retval;
}

/* 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, const char *funcname)
{
    JNIEnv *env;

    if(box && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID boxPack = env->GetMethodID(clazz, "boxPack", "(Landroid/view/View;Landroid/view/View;IIIIII)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, boxPack, box, item, index, width, height, hsize, vsize, pad);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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.
 */
int API dw_box_unpack(HWND handle)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID boxUnpack = env->GetMethodID(clazz, "boxUnpack", "(Landroid/view/View;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, boxUnpack, handle);
        _dw_jni_check_exception(env);
        return DW_ERROR_NONE;
    }
    return DW_ERROR_GENERAL;
}

/*
 * 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.
 */
HWND API dw_box_unpack_at_index(HWND box, int index)
{
    JNIEnv *env;
    HWND retval = nullptr;

    if(box && (env = (JNIEnv *)pthread_getspecific(_dw_env_key))) {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID boxUnpackAtIndex = env->GetMethodID(clazz, "boxUnpackAtIndex",
                                                      "(Landroid/widget/LinearLayout;I)Landroid/view/View;");
        // Call the method on the object
        retval = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, boxUnpackAtIndex, box, index), _DW_REFERENCE_WEAK);
    }
    return retval;
}

/*
 * 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.
 */
void API dw_box_pack_at_index(HWND box, HWND item, int index, int width, int height, int hsize, int vsize, int pad)
{
    _dw_box_pack(box, item, index, width, height, hsize, vsize, pad, "dw_box_pack_at_index()");
}

/*
 * 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.
 */
void API dw_box_pack_start(HWND box, HWND item, int width, int height, int hsize, int vsize, int pad)
{
    /* 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, -1, width, height, hsize, vsize, pad, "dw_box_pack_start()");
}

/*
 * 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.
 */
void API dw_box_pack_end(HWND box, HWND item, int width, int height, int hsize, int vsize, int pad)
{
    _dw_box_pack(box, item, 0, width, height, hsize, vsize, pad, "dw_box_pack_end()");
}

/*
 * 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.
 * Returns:
 *       A handle to a button window or nullptr on failure.
 */
HWND API dw_button_new(const char *text, ULONG cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID buttonNew = env->GetMethodID(clazz, "buttonNew",
                                               "(Ljava/lang/String;I)Landroid/widget/Button;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, buttonNew, jstr, (int)cid), _DW_REFERENCE_WEAK);
        env->DeleteLocalRef(jstr);
        return result;
    }
    return nullptr;
}

HWND _dw_entryfield_new(const char *text, ULONG cid, int password)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID entryfieldNew = env->GetMethodID(clazz, "entryfieldNew",
                                                   "(Ljava/lang/String;II)Landroid/widget/EditText;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, entryfieldNew, jstr, (int)cid, password), _DW_REFERENCE_WEAK);
        env->DeleteLocalRef(jstr);
        return result;
    }
    return nullptr;
}
/*
 * 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.
 * Returns:
 *       A handle to an entryfield window or nullptr on failure.
 */
HWND API dw_entryfield_new(const char *text, ULONG cid)
{
    return _dw_entryfield_new(text, cid, FALSE);
}

/*
 * 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.
 * Returns:
 *       A handle to an entryfield password window or nullptr on failure.
 */
HWND API dw_entryfield_password_new(const char *text, ULONG cid)
{
    return _dw_entryfield_new(text, cid, TRUE);
}

/*
 * Sets the entryfield character limit.
 * Parameters:
 *          handle: Handle to the spinbutton to be set.
 *          limit: Number of characters the entryfield will take.
 */
void API dw_entryfield_set_limit(HWND handle, ULONG limit)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID entryfieldSetLimit = env->GetMethodID(clazz, "entryfieldSetLimit",
                                                        "(Landroid/widget/EditText;J)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, entryfieldSetLimit, handle, (jlong)limit);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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.
 * Returns:
 *       A handle to a bitmap button window or nullptr on failure.
 */
HWND API dw_bitmapbutton_new(const char *text, ULONG resid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID bitmapButtonNew = env->GetMethodID(clazz, "bitmapButtonNew",
                                                     "(Ljava/lang/String;I)Landroid/widget/ImageButton;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, bitmapButtonNew, jstr, (int)resid), _DW_REFERENCE_WEAK);
        env->DeleteLocalRef(jstr);
        return result;
    }
    return nullptr;
}

/*
 * 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)
 * Returns:
 *       A handle to a bitmap button window or nullptr on failure.
 */
HWND API dw_bitmapbutton_new_from_file(const char *text, unsigned long cid, const char *filename)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        jstring path = env->NewStringUTF(filename);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID bitmapButtonNewFromFile = env->GetMethodID(clazz, "bitmapButtonNewFromFile",
                                                             "(Ljava/lang/String;ILjava/lang/String;)Landroid/widget/ImageButton;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, bitmapButtonNewFromFile, jstr, (int)cid, path), _DW_REFERENCE_WEAK);
        env->DeleteLocalRef(jstr);
        env->DeleteLocalRef(path);
        return result;
    }
    return nullptr;
}

/*
 * 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
 * Returns:
 *       A handle to a bitmap button window or nullptr on failure.
 */
HWND API dw_bitmapbutton_new_from_data(const char *text, unsigned long cid, const char *data, int len)
{
    JNIEnv *env;

    if(data && len > 0 && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // Construct a byte array
        jbyteArray bytearray = env->NewByteArray(len);
        env->SetByteArrayRegion(bytearray, 0, len, reinterpret_cast<const jbyte *>(data));
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID bitmapButtonNewFromData = env->GetMethodID(clazz, "bitmapButtonNewFromData",
                                                             "(Ljava/lang/String;I[BI)Landroid/widget/ImageButton;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, bitmapButtonNewFromData, jstr, (int)cid, bytearray, len), _DW_REFERENCE_WEAK);
        // TODO: Clean up after the array now that we are finished
        //env->ReleaseByteArrayElements(bytearray, (jbyte *) data, 0);
        env->DeleteLocalRef(jstr);
        return result;
    }
    return nullptr;
}

/*
 * 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.
 * Returns:
 *       A handle to a spinbutton window or nullptr on failure.
 */
HWND API dw_spinbutton_new(const char *text, ULONG cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID spinButtonNew = env->GetMethodID(clazz, "spinButtonNew",
                                                   "(Ljava/lang/String;I)Lorg/dbsoft/dwindows/DWSpinButton;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, spinButtonNew, jstr, (int)cid), _DW_REFERENCE_WEAK);
        env->DeleteLocalRef(jstr);
        return result;
    }
    return nullptr;
}

/*
 * Sets the spinbutton value.
 * Parameters:
 *          handle: Handle to the spinbutton to be set.
 *          position: Current value of the spinbutton.
 */
void API dw_spinbutton_set_pos(HWND handle, long position)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID spinButtonSetPos = env->GetMethodID(clazz, "spinButtonSetPos",
                                                      "(Lorg/dbsoft/dwindows/DWSpinButton;J)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, spinButtonSetPos, handle, (jlong)position);
        _dw_jni_check_exception(env);
    }
}

/*
 * Sets the spinbutton limits.
 * Parameters:
 *          handle: Handle to the spinbutton to be set.
 *          upper: Upper limit.
 *          lower: Lower limit.
 */
void API dw_spinbutton_set_limits(HWND handle, long upper, long lower)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID spinButtonSetLimits = env->GetMethodID(clazz, "spinButtonSetLimits",
                                                         "(Lorg/dbsoft/dwindows/DWSpinButton;JJ)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, spinButtonSetLimits, handle, (jlong)upper, (jlong)lower);
        _dw_jni_check_exception(env);
    }
}

/*
 * Returns the current value of the spinbutton.
 * Parameters:
 *          handle: Handle to the spinbutton to be queried.
 * Returns:
 *       Number value displayed in the spinbutton.
 */
long API dw_spinbutton_get_pos(HWND handle)
{
    JNIEnv *env;
    long retval = 0;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID spinButtonGetPos = env->GetMethodID(clazz, "spinButtonGetPos",
                                                      "(Lorg/dbsoft/dwindows/DWSpinButton;)J");
        // Call the method on the object
        retval = env->CallLongMethod(_dw_obj, spinButtonGetPos, handle);
        if(_dw_jni_check_exception(env))
            retval = 0;
    }
    return 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.
 * Returns:
 *       A handle to a radio button window or nullptr on failure.
 */
HWND API dw_radiobutton_new(const char *text, ULONG cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID radioButtonNew = env->GetMethodID(clazz, "radioButtonNew",
                                                    "(Ljava/lang/String;I)Landroid/widget/RadioButton;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, radioButtonNew, jstr, (int)cid), _DW_REFERENCE_WEAK);
        env->DeleteLocalRef(jstr);
        return result;
    }
    return nullptr;
}

/*
 * 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.
 * Returns:
 *       A handle to a slider window or nullptr on failure.
 */
HWND API dw_slider_new(int vertical, int increments, ULONG cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID sliderNew = env->GetMethodID(clazz, "sliderNew",
                                               "(III)Lorg/dbsoft/dwindows/DWSlider;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, sliderNew, vertical, increments, (jint)cid), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * Returns the position of the slider.
 * Parameters:
 *          handle: Handle to the slider to be queried.
 * Returns:
 *       Position of the slider in the set range.
 */
unsigned int API dw_slider_get_pos(HWND handle)
{
    JNIEnv *env;
    unsigned int retval = 0;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID percentGetPos = env->GetMethodID(clazz, "percentGetPos",
                                                   "(Landroid/view/View;)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, percentGetPos, handle);
        if(_dw_jni_check_exception(env))
            retval = 0;
    }
    return retval;
}

/*
 * 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)
{
    dw_percent_set_pos(handle, position);
}

/*
 * Create a new scrollbar window (widget) to be packed.
 * Parameters:
 *       vertical: TRUE or FALSE if scrollbar is vertical.
 *       id: An ID to be used with dw_window_from_id() or 0L.
 * Returns:
 *       A handle to a scrollbar window or nullptr on failure.
 */
HWND API dw_scrollbar_new(int vertical, ULONG cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID scrollBarNew = env->GetMethodID(clazz, "scrollBarNew",
                                                  "(II)Lorg/dbsoft/dwindows/DWSlider;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, scrollBarNew, vertical, (jint)cid), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * Returns the position of the scrollbar.
 * Parameters:
 *          handle: Handle to the scrollbar to be queried.
 * Returns:
 *       Position of the scrollbar in the set range.
 */
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_percent_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)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID percentSetRange = env->GetMethodID(clazz, "percentSetRange",
                                                     "(Landroid/view/View;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, percentSetRange, handle, (jint)range);
        _dw_jni_check_exception(env);
    }
}

/*
 * Create a new percent bar window (widget) to be packed.
 * Parameters:
 *       id: An ID to be used with dw_window_from_id() or 0L.
 * Returns:
 *       A handle to a percent bar window or nullptr on failure.
 */
HWND API dw_percent_new(ULONG cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID percentNew = env->GetMethodID(clazz, "percentNew",
                                               "(I)Landroid/widget/ProgressBar;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, percentNew, (jint)cid), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * Sets the percent bar position.
 * Parameters:
 *          handle: Handle to the percent bar to be set.
 *          position: Position of the percent bar withing the range.
 */
void API dw_percent_set_pos(HWND handle, unsigned int position)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID percentSetPos = env->GetMethodID(clazz, "percentSetPos",
                                                   "(Landroid/view/View;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, percentSetPos, handle, (jint)position);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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.
 * Returns:
 *       A handle to a checkbox window or nullptr on failure.
 */
HWND API dw_checkbox_new(const char *text, ULONG cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID checkboxNew = env->GetMethodID(clazz, "checkboxNew",
                                                 "(Ljava/lang/String;I)Landroid/widget/CheckBox;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, checkboxNew, jstr, (int)cid), _DW_REFERENCE_WEAK);
        env->DeleteLocalRef(jstr);
        return result;
    }
    return nullptr;
}

/*
 * Returns the state of the checkbox.
 * Parameters:
 *          handle: Handle to the checkbox to be queried.
 * Returns:
 *       State of checkbox (TRUE or FALSE).
 */
int API dw_checkbox_get(HWND handle)
{
    JNIEnv *env;
    int retval = FALSE;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID checkOrRadioGetChecked = env->GetMethodID(clazz, "checkOrRadioGetChecked",
                                                            "(Landroid/view/View;)Z");
        // Call the method on the object
        retval = env->CallBooleanMethod(_dw_obj, checkOrRadioGetChecked, handle);
        if(_dw_jni_check_exception(env))
            retval = FALSE;
    }
    return retval;
}

/*
 * Sets the state of the checkbox.
 * Parameters:
 *          handle: Handle to the checkbox to be queried.
 *          value: TRUE for checked, FALSE for unchecked.
 */
void API dw_checkbox_set(HWND handle, int value)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID checkOrRadioSetChecked = env->GetMethodID(clazz, "checkOrRadioSetChecked",
                                                            "(Landroid/view/View;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, checkOrRadioSetChecked, handle, value);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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.
 * Returns:
 *       A handle to a listbox window or nullptr on failure.
 */
HWND API dw_listbox_new(ULONG cid, int multi)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID listBoxNew = env->GetMethodID(clazz, "listBoxNew",
                                                 "(II)Lorg/dbsoft/dwindows/DWListBox;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, listBoxNew, (int)cid, multi), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * 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.
 */
void API dw_listbox_append(HWND handle, const char *text)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID listOrComboBoxAppend = env->GetMethodID(clazz, "listOrComboBoxAppend",
                                                          "(Landroid/view/View;Ljava/lang/String;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, listOrComboBoxAppend, handle, jstr);
        _dw_jni_check_exception(env);
        env->DeleteLocalRef(jstr);
    }
}

/*
 * 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
 */
void API dw_listbox_insert(HWND handle, const char *text, int pos)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID listOrComboBoxInsert = env->GetMethodID(clazz, "listOrComboBoxInsert",
                                                          "(Landroid/view/View;Ljava/lang/String;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, listOrComboBoxInsert, handle, jstr, pos);
        _dw_jni_check_exception(env);
        env->DeleteLocalRef(jstr);
    }
}

/*
 * 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
 */
void API dw_listbox_list_append(HWND handle, char **text, int count)
{
    int x;

    /* TODO: this would be more efficient passing in an array */
    for(x=0;x<count;x++)
        dw_listbox_append(handle, text[x]);
}

/*
 * Clears the listbox's (or combobox) list of all entries.
 * Parameters:
 *          handle: Handle to the listbox to be cleared.
 */
void API dw_listbox_clear(HWND handle)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID listOrComboBoxClear = env->GetMethodID(clazz, "listOrComboBoxClear",
                                                          "(Landroid/view/View;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, listOrComboBoxClear, handle);
        _dw_jni_check_exception(env);
    }
}

/*
 * Returns the listbox's item count.
 * Parameters:
 *          handle: Handle to the listbox to be counted.
 * Returns:
 *       The number of items in the listbox.
 */
int API dw_listbox_count(HWND handle)
{
    JNIEnv *env;
    int retval = 0;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID listOrComboBoxCount = env->GetMethodID(clazz, "listOrComboBoxCount",
                                                         "(Landroid/view/View;)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, listOrComboBoxCount, handle);
        if(_dw_jni_check_exception(env))
            retval = 0;
    }
    return retval;
}

/*
 * Sets the topmost item in the viewport.
 * Parameters:
 *          handle: Handle to the listbox to be cleared.
 *          top: Index to the top item.
 */
void API dw_listbox_set_top(HWND handle, int top)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID listBoxSetTop = env->GetMethodID(clazz, "listBoxSetTop",
                                                         "(Landroid/view/View;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, listBoxSetTop, handle, top);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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 nullptr).
 */
void API dw_listbox_get_text(HWND handle, unsigned int index, char *buffer, unsigned int length)
{
    JNIEnv *env;

    if(buffer && length > 0 && handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID listOrComboBoxGetText = env->GetMethodID(clazz, "listOrComboBoxGetText",
                                                           "(Landroid/view/View;I)Ljava/lang/String;");
        // Call the method on the object
        jstring result = (jstring)_dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, listOrComboBoxGetText, handle, index), _DW_REFERENCE_NONE);
        // Get the UTF8 string result
        if(result)
        {
            const char *utf8 = env->GetStringUTFChars(result, nullptr);

            if(utf8)
            {
                strncpy(buffer, utf8, length);
                env->ReleaseStringUTFChars(result, utf8);
            }
        }
    }
}

/*
 * 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.
 */
void API dw_listbox_set_text(HWND handle, unsigned int index, const char *buffer)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(buffer);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID listOrComboBoxSetText = env->GetMethodID(clazz, "listOrComboBoxSetText",
                                                          "(Landroid/view/View;ILjava/lang/String;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, listOrComboBoxSetText, handle, index, jstr);
        _dw_jni_check_exception(env);
        env->DeleteLocalRef(jstr);
    }
}

/*
 * Returns the index to the item in the list currently selected.
 * Parameters:
 *          handle: Handle to the listbox to be queried.
 * Returns:
 *       The selected item index or DW_ERROR_UNKNOWN (-1) on error.
 */
int API dw_listbox_selected(HWND handle)
{
    JNIEnv *env;
    int retval = DW_ERROR_UNKNOWN;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID listOrComboBoxGetSelected = env->GetMethodID(clazz, "listOrComboBoxGetSelected",
                                                           "(Landroid/view/View;)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, listOrComboBoxGetSelected, handle);
        if(_dw_jni_check_exception(env))
            retval = DW_ERROR_UNKNOWN;
    }
    return retval;
}

/*
 * 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.
 * Returns:
 *       The next selected item or DW_ERROR_UNKNOWN (-1) on error.
 */
int API dw_listbox_selected_multi(HWND handle, int where)
{
    JNIEnv *env;
    int retval = DW_ERROR_UNKNOWN;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID listBoxSelectedMulti = env->GetMethodID(clazz, "listBoxSelectedMulti",
                                                          "(Landroid/view/View;I)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, listBoxSelectedMulti, handle, where);
        if(_dw_jni_check_exception(env))
            retval = DW_ERROR_UNKNOWN;
    }
    return 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.
 */
void API dw_listbox_select(HWND handle, int index, int state)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID listOrComboBoxSelect = env->GetMethodID(clazz, "listOrComboBoxSelect",
                                                           "(Landroid/view/View;II)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, listOrComboBoxSelect, handle, index, state);
        _dw_jni_check_exception(env);
    }
}

/*
 * Deletes the item with given index from the list.
 * Parameters:
 *          handle: Handle to the listbox to be set.
 *          index: Item index.
 */
void API dw_listbox_delete(HWND handle, int index)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID listOrComboBoxDelete = env->GetMethodID(clazz, "listOrComboBoxDelete",
                                                          "(Landroid/view/View;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, listOrComboBoxDelete, handle, index);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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.
 * Returns:
 *       A handle to a combobox window or nullptr on failure.
 */
HWND API dw_combobox_new(const char *text, ULONG cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID comboBoxNew = env->GetMethodID(clazz, "comboBoxNew",
                                                 "(Ljava/lang/String;I)Lorg/dbsoft/dwindows/DWComboBox;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, comboBoxNew, jstr, (int)cid), _DW_REFERENCE_WEAK);
        env->DeleteLocalRef(jstr);
        return result;
    }
    return nullptr;
}

/*
 * Create a new Multiline Editbox window (widget) to be packed.
 * Parameters:
 *       id: An ID to be used with dw_window_from_id() or 0L.
 * Returns:
 *       A handle to a MLE window or nullptr on failure.
 */
HWND API dw_mle_new(ULONG cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID mleNew = env->GetMethodID(clazz, "mleNew",
                                            "(I)Landroid/widget/EditText;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, mleNew, (int)cid), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * 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.
 * Returns:
 *       Current position in the buffer.
 */
unsigned int API dw_mle_import(HWND handle, const char *buffer, int startpoint)
{
    JNIEnv *env;
    int retval = 0;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(buffer);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID mleImport = env->GetMethodID(clazz, "mleImport",
                                               "(Landroid/widget/EditText;Ljava/lang/String;I)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, mleImport, handle, jstr, startpoint);
        if(_dw_jni_check_exception(env))
            retval = 0;
        env->DeleteLocalRef(jstr);
    }
    return 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.
 */
void API dw_mle_export(HWND handle, char *buffer, int startpoint, int length)
{
    if(buffer && length > 0) {
        char *text = dw_window_get_text(handle);

        if (text) {
            int len = strlen(text);

            if (startpoint < len)
                strncpy(buffer, &text[startpoint], length);
            else
                buffer[0] = '\0';
            free(text);
        }
    }
}

/*
 * 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.
 */
void API dw_mle_get_size(HWND handle, unsigned long *bytes, unsigned long *lines)
{
    char *text = dw_window_get_text(handle);

    if(bytes) {
        if(text) {
            *bytes = strlen(text);
        } else {
            *bytes = 0;
        }
    }
    if(lines) {
        if(text)
        {
            int count = 0;
            char *tmp = text;

            while((tmp = strchr(tmp, '\n'))) {
                count++;
            }
            *lines = count;
        } else {
            *lines = 0;
        }
    }
    if(text)
        free(text);
}

/*
 * 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.
 */
void API dw_mle_delete(HWND handle, int startpoint, int length)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID mleDelete = env->GetMethodID(clazz, "mleDelete",
                                               "(Landroid/widget/EditText;II)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, mleDelete, handle, startpoint, length);
        _dw_jni_check_exception(env);
    }
}

/*
 * Clears all text from an MLE box.
 * Parameters:
 *          handle: Handle to the MLE to be cleared.
 */
void API dw_mle_clear(HWND handle)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID mleClear = env->GetMethodID(clazz, "mleClear",
                                              "(Landroid/widget/EditText;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, mleClear, handle);
        _dw_jni_check_exception(env);
    }
}

/*
 * Sets the visible line of an MLE box.
 * Parameters:
 *          handle: Handle to the MLE to be positioned.
 *          line: Line to be visible.
 */
void API dw_mle_set_visible(HWND handle, int line)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID mleSetVisible = env->GetMethodID(clazz, "mleSetVisible",
                                                    "(Landroid/widget/EditText;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, mleSetVisible, handle, line);
        _dw_jni_check_exception(env);
    }
}

/*
 * Sets the editablity of an MLE box.
 * Parameters:
 *          handle: Handle to the MLE.
 *          state: TRUE if it can be edited, FALSE for readonly.
 */
void API dw_mle_set_editable(HWND handle, int state)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID mleSetEditable = env->GetMethodID(clazz, "mleSetEditable",
                                                    "(Landroid/widget/EditText;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, mleSetEditable, handle, state);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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.
 */
void API dw_mle_set_word_wrap(HWND handle, int state)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID mleSetWordWrap = env->GetMethodID(clazz, "mleSetWordWrap",
                                                    "(Landroid/widget/EditText;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, mleSetWordWrap, handle, state);
        _dw_jni_check_exception(env);
    }
}

/*
 * Sets the current cursor position of an MLE box.
 * Parameters:
 *          handle: Handle to the MLE to be positioned.
 *          point: Point to position cursor.
 */
void API dw_mle_set_cursor(HWND handle, int point)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID mleSetCursor = env->GetMethodID(clazz, "mleSetCursor",
                                                    "(Landroid/widget/EditText;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, mleSetCursor, handle, point);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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
 */
void API dw_mle_set_auto_complete(HWND handle, int state)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID mleSetAutoComplete = env->GetMethodID(clazz, "mleSetAutoComplete",
                                                  "(Landroid/widget/EditText;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, mleSetAutoComplete, handle, state);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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.
 * Returns:
 *       Position in buffer or DW_ERROR_UNKNOWN (-1) on error.
 */
int API dw_mle_search(HWND handle, const char *text, int point, unsigned long flags)
{
    JNIEnv *env;
    int retval = DW_ERROR_UNKNOWN;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID mleSearch = env->GetMethodID(clazz, "mleSearch",
                                               "(Landroid/widget/EditText;Ljava/lang/String;II)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, mleSearch, handle, jstr, point, (jint)flags);
        if(_dw_jni_check_exception(env))
            retval = DW_ERROR_UNKNOWN;
        env->DeleteLocalRef(jstr);
    }
    return 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 on Android */
}

/*
 * 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 on Android */
}

HWND _dw_text_new(const char *text, ULONG cid, int status)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID textNew = env->GetMethodID(clazz, "textNew",
                                             "(Ljava/lang/String;II)Landroid/widget/TextView;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, textNew, jstr, (int)cid, status), _DW_REFERENCE_WEAK);
        env->DeleteLocalRef(jstr);
        return result;
    }
    return nullptr;
}

/*
 * 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.
 * Returns:
 *       A handle to a status text window or nullptr on failure.
 */
HWND API dw_status_text_new(const char *text, ULONG cid)
{
    return _dw_text_new(text, cid, TRUE);
}

/*
 * 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.
 * Returns:
 *       A handle to a text window or nullptr on failure.
 */
HWND API dw_text_new(const char *text, ULONG cid)
{
    return _dw_text_new(text, cid, FALSE);
}

/*
 * 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 nullptr on failure.
 */
HWND API dw_render_new(unsigned long cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID renderNew = env->GetMethodID(clazz, "renderNew",
                                               "(I)Lorg/dbsoft/dwindows/DWRender;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, renderNew, (int)cid), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * Invalidate the render widget triggering an expose event.
 * Parameters:
 *       handle: A handle to a render widget to be redrawn.
 */
void API dw_render_redraw(HWND handle)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID renderRedraw = env->GetMethodID(clazz, "renderRedraw",
                                                  "(Lorg/dbsoft/dwindows/DWRender;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, renderRedraw, handle);
        _dw_jni_check_exception(env);
    }
}

/* 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)
{
    pthread_setspecific(_dw_fgcolor_key, (void *)value);
}

/* 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)
{
    pthread_setspecific(_dw_bgcolor_key, (void *)value);
}

/* 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)
{
    JNIEnv *env;
    unsigned long newcolor = value;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        unsigned long _value = _dw_get_color(value);
        int r, g, b;
        jint ac;

        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID colorChoose = env->GetMethodID(clazz, "colorChoose",
                                                 "(IIIII)I");
        // Call the method on the object
        ac = env->CallIntMethod(_dw_obj, colorChoose, (jint)value, 0,
                   (jint)DW_RED_VALUE(_value), (jint)DW_GREEN_VALUE(_value), (jint)DW_BLUE_VALUE(_value));
        if(_dw_jni_check_exception(env))
            return value;
        // Convert from Android Color to RGB back to Dynamic Windows
        b = ac & 0xff;
        g = (ac >> 8) & 0xff;
        r = (ac >> 16) & 0xff;
        newcolor = DW_RGB(r, g, b);
    }
    return newcolor;
}

/* 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.
 */
void API dw_draw_point(HWND handle, HPIXMAP pixmap, int x, int y)
{
    JNIEnv *env;

    if((handle || pixmap) && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        jlong fgcolor = _dw_get_color((unsigned long)pthread_getspecific(_dw_fgcolor_key));
        jlong bgcolor = _dw_get_color((unsigned long)pthread_getspecific(_dw_bgcolor_key));

        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID drawPoint = env->GetMethodID(clazz, "drawPoint",
                                               "(Lorg/dbsoft/dwindows/DWRender;Landroid/graphics/Bitmap;IIJJ)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, drawPoint, handle, pixmap ? pixmap->bitmap : nullptr, x, y, fgcolor, bgcolor);
        _dw_jni_check_exception(env);
    }
}

/* 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.
 */
void API dw_draw_line(HWND handle, HPIXMAP pixmap, int x1, int y1, int x2, int y2)
{
    JNIEnv *env;

    if((handle || pixmap) && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        jlong fgcolor = _dw_get_color((unsigned long)pthread_getspecific(_dw_fgcolor_key));
        jlong bgcolor = _dw_get_color((unsigned long)pthread_getspecific(_dw_bgcolor_key));

        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID drawLine = env->GetMethodID(clazz, "drawLine",
                                              "(Lorg/dbsoft/dwindows/DWRender;Landroid/graphics/Bitmap;IIIIJJ)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, drawLine, handle, pixmap ? pixmap->bitmap : nullptr, x1, y1, x2, y2, fgcolor, bgcolor);
        _dw_jni_check_exception(env);
    }
}

/* 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.
 */
void API dw_draw_text(HWND handle, HPIXMAP pixmap, int x, int y, const char *text)
{
    JNIEnv *env;

    if((handle || pixmap) && text && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        jlong fgcolor = _dw_get_color((unsigned long)pthread_getspecific(_dw_fgcolor_key));
        jlong bgcolor = _dw_get_color((unsigned long)pthread_getspecific(_dw_bgcolor_key));

        // Construct the string
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID drawLine = env->GetMethodID(clazz, "drawText",
                                              "(Lorg/dbsoft/dwindows/DWRender;Landroid/graphics/Bitmap;IILjava/lang/String;Landroid/graphics/Typeface;ILandroid/view/View;JJ)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, drawLine, handle, pixmap ? pixmap->bitmap : nullptr, x, y, jstr,
                            pixmap ? pixmap->typeface : nullptr, pixmap ? pixmap->fontsize : 0,
                            pixmap ? pixmap->handle : nullptr, fgcolor, bgcolor);
        _dw_jni_check_exception(env);
        env->DeleteLocalRef(jstr);
    }
}

/* 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)
{
    JNIEnv *env;

    if((handle || pixmap) && text && (width || height) && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct the string
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID fontTextExtentsGet = env->GetMethodID(clazz, "fontTextExtentsGet",
                                                        "(Lorg/dbsoft/dwindows/DWRender;Landroid/graphics/Bitmap;Ljava/lang/String;Landroid/graphics/Typeface;ILandroid/view/View;)J");
        // Call the method on the object
        jlong dimensions = env->CallLongMethod(_dw_obj, fontTextExtentsGet, handle, pixmap ? pixmap->bitmap : nullptr, jstr,
                            pixmap ? pixmap->typeface : nullptr, pixmap ? pixmap->fontsize : 0, pixmap ? pixmap->handle : nullptr);
        if(_dw_jni_check_exception(env))
            dimensions = 0;

        if(width)
            *width = dimensions & 0xFFFF;
        if(height)
            *height = (dimensions >> 32) & 0xFFFF;
        env->DeleteLocalRef(jstr);
    }
}

/* 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).
 *       npoints: Number of points passed in.
 *       x: Pointer to array of X coordinates.
 *       y: Pointer to array of Y coordinates.
 */
void API dw_draw_polygon(HWND handle, HPIXMAP pixmap, int flags, int npoints, int *x, int *y)
{
    JNIEnv *env;

    if((handle || pixmap) && npoints > 0 && x && y && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        jintArray jx = env->NewIntArray(npoints);
        jintArray jy = env->NewIntArray(npoints);

        if(jx && jy)
        {
            jlong fgcolor = _dw_get_color((unsigned long)pthread_getspecific(_dw_fgcolor_key));
            jlong bgcolor = _dw_get_color((unsigned long)pthread_getspecific(_dw_bgcolor_key));

            // Construct the integer arrays
            env->SetIntArrayRegion(jx, 0, npoints, x);
            env->SetIntArrayRegion(jy, 0, npoints, y);
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID drawPolygon = env->GetMethodID(clazz, "drawPolygon",
                                                     "(Lorg/dbsoft/dwindows/DWRender;Landroid/graphics/Bitmap;II[I[IJJ)V");
            // Call the method on the object
            env->CallVoidMethod(_dw_obj, drawPolygon, handle, pixmap ? pixmap->bitmap : nullptr, flags, npoints, jx, jy, fgcolor, bgcolor);
            _dw_jni_check_exception(env);
        }
    }
}

/* 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.
 */
void API dw_draw_rect(HWND handle, HPIXMAP pixmap, int flags, int x, int y, int width, int height)
{
    JNIEnv *env;

    if((handle || pixmap) && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        jlong fgcolor = _dw_get_color((unsigned long)pthread_getspecific(_dw_fgcolor_key));
        jlong bgcolor = _dw_get_color((unsigned long)pthread_getspecific(_dw_bgcolor_key));

        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID drawLine = env->GetMethodID(clazz, "drawRect",
                                              "(Lorg/dbsoft/dwindows/DWRender;Landroid/graphics/Bitmap;IIIIJJ)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, drawLine, handle, pixmap ? pixmap->bitmap : nullptr, x, y, width, height, fgcolor, bgcolor);
        _dw_jni_check_exception(env);
    }
}

/* 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.
 */
void API dw_draw_arc(HWND handle, HPIXMAP pixmap, int flags, int xorigin, int yorigin, int x1, int y1, int x2, int y2)
{
    JNIEnv *env;

    if((handle || pixmap) && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        jlong fgcolor = _dw_get_color((unsigned long)pthread_getspecific(_dw_fgcolor_key));
        jlong bgcolor = _dw_get_color((unsigned long)pthread_getspecific(_dw_bgcolor_key));

        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID drawLine = env->GetMethodID(clazz, "drawArc",
                                              "(Lorg/dbsoft/dwindows/DWRender;Landroid/graphics/Bitmap;IIIIIIIJJ)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, drawLine, handle, pixmap ? pixmap->bitmap : nullptr, flags, xorigin, yorigin, x1, y1, x2, y2, fgcolor, bgcolor);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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 nullptr on failure.
 */
HWND API dw_tree_new(ULONG cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID treeNew = env->GetMethodID(clazz, "treeNew",
                                             "(I)Lorg/dbsoft/dwindows/DWTree;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, treeNew, (int)cid), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * 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 nullptr on failure.
 */
HTREEITEM API dw_tree_insert_after(HWND handle, HTREEITEM item, const char *title, HICN icon, HTREEITEM parent, void *itemdata)
{
    JNIEnv *env;

    if(handle && title && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct the string
        jstring jstr = env->NewStringUTF(title);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID treeInsertAfter = env->GetMethodID(clazz, "treeInsertAfter",
                                                     "(Lorg/dbsoft/dwindows/DWTree;Lorg/dbsoft/dwindows/DWTreeItem;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Lorg/dbsoft/dwindows/DWTreeItem;J)Lorg/dbsoft/dwindows/DWTreeItem;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, treeInsertAfter, handle, item,
                                                                         jstr, icon, parent, (jlong)itemdata), _DW_REFERENCE_WEAK);
        env->DeleteLocalRef(jstr);
        return result;
    }
    return nullptr;
}

/*
 * 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 nullptr on failure.
 */
HTREEITEM API dw_tree_insert(HWND handle, const char *title, HICN icon, HTREEITEM parent, void *itemdata)
{
    return dw_tree_insert_after(handle, nullptr, 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 nullptr on error.
 */
char * API dw_tree_get_title(HWND handle, HTREEITEM item)
{
    JNIEnv *env;

    if(handle && item && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        const char *utf8 = nullptr;
        char *retval = nullptr;

        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID treeGetTitle = env->GetMethodID(clazz, "treeGetTitle",
                                                   "(Lorg/dbsoft/dwindows/DWTree;Lorg/dbsoft/dwindows/DWTreeItem;)Ljava/lang/String;");
        // Call the method on the object
        jstring result = (jstring)_dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, treeGetTitle, handle, item), _DW_REFERENCE_NONE);
        // Get the UTF8 string result
        if(result)
            utf8 = env->GetStringUTFChars(result, nullptr);
        if(utf8)
        {
            retval = strdup(utf8);
            env->ReleaseStringUTFChars(result, utf8);
        }
    }
    return nullptr;
}

/*
 * 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 nullptr on failure.
 */
HTREEITEM API dw_tree_get_parent(HWND handle, HTREEITEM item)
{
    JNIEnv *env;

    if(handle && item && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID treeGetParent = env->GetMethodID(clazz, "treeGetParent",
                                                  "(Lorg/dbsoft/dwindows/DWTree;Lorg/dbsoft/dwindows/DWTreeItem;)Lorg/dbsoft/dwindows/DWTreeItem;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, treeGetParent, handle, item), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * 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.
 */
void API dw_tree_item_change(HWND handle, HTREEITEM item, const char *title, HICN icon)
{
    JNIEnv *env;

    if(handle && item && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct the string
        jstring jstr = title ? env->NewStringUTF(title) : nullptr;
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID treeItemChange = env->GetMethodID(clazz, "treeItemChange",
                                                    "(Lorg/dbsoft/dwindows/DWTree;Lorg/dbsoft/dwindows/DWTreeItem;Ljava/lang/String;Landroid/graphics/drawable/Drawable;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, treeItemChange, handle, item, jstr, icon);
        _dw_jni_check_exception(env);
        env->DeleteLocalRef(jstr);
    }
}

/*
 * 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.
 */
void API dw_tree_item_set_data(HWND handle, HTREEITEM item, void *itemdata)
{
    JNIEnv *env;

    if(handle && item && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        jlong data = (jlong)itemdata;
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID treeItemSetData = env->GetMethodID(clazz, "treeItemSetData",
                                                     "(Lorg/dbsoft/dwindows/DWTree;Lorg/dbsoft/dwindows/DWTreeItem;J)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, treeItemSetData, handle, item, data);
    }
}

/*
 * 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 nullptr on failure.
 */
void * API dw_tree_item_get_data(HWND handle, HTREEITEM item)
{
    JNIEnv *env;
    void *retval = nullptr;

    if(handle && item && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID treeItemGetData = env->GetMethodID(clazz, "treeItemGetData",
                                                     "(Lorg/dbsoft/dwindows/DWTree;Lorg/dbsoft/dwindows/DWTreeItem;)J");
        // Call the method on the object
        retval = (void *)env->CallLongMethod(_dw_obj, treeItemGetData, handle, item);
    }
    return retval;
}

/*
 * 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.
 */
void API dw_tree_item_select(HWND handle, HTREEITEM item)
{
    JNIEnv *env;

    if(handle && item && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID treeItemSelect = env->GetMethodID(clazz, "treeItemSelect",
                                                    "(Lorg/dbsoft/dwindows/DWTree;Lorg/dbsoft/dwindows/DWTreeItem;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, treeItemSelect, handle, item);
    }
}

/*
 * Removes all nodes from a tree.
 * Parameters:
 *       handle: Handle to the window (widget) to be cleared.
 */
void API dw_tree_clear(HWND handle)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID treeClear = env->GetMethodID(clazz, "treeClear",
                                               "(Lorg/dbsoft/dwindows/DWTree;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, treeClear, handle);
    }
}

/* Internal function to set the expanded state */
void _dw_tree_item_set_expanded(HWND handle, HTREEITEM item, jint state)
{
    JNIEnv *env;

    if(handle && item && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID treeItemExpand = env->GetMethodID(clazz, "treeItemExpand",
                                                    "(Lorg/dbsoft/dwindows/DWTree;Lorg/dbsoft/dwindows/DWTreeItem;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, treeItemExpand, handle, item, state);
    }
}

/*
 * Expands a node on a tree.
 * Parameters:
 *       handle: Handle to the tree window (widget).
 *       item: Handle to node to be expanded.
 */
void API dw_tree_item_expand(HWND handle, HTREEITEM item)
{
    _dw_tree_item_set_expanded(handle, item, TRUE);
}

/*
 * Collapses a node on a tree.
 * Parameters:
 *       handle: Handle to the tree window (widget).
 *       item: Handle to node to be collapsed.
 */
void API dw_tree_item_collapse(HWND handle, HTREEITEM item)
{
    _dw_tree_item_set_expanded(handle, item, FALSE);
}

/*
 * Removes a node from a tree.
 * Parameters:
 *       handle: Handle to the window (widget) to be cleared.
 *       item: Handle to node to be deleted.
 */
void API dw_tree_item_delete(HWND handle, HTREEITEM item)
{
    JNIEnv *env;

    if(handle && item && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID treeItemDelete = env->GetMethodID(clazz, "treeItemDelete",
                                                    "(Lorg/dbsoft/dwindows/DWTree;Lorg/dbsoft/dwindows/DWTreeItem;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, treeItemDelete, handle, item);
    }
}

/*
 * Create a container object to be packed.
 * Parameters:
 *       id: An ID to be used for getting the resource from the
 *           resource file.
 * Returns:
 *       A handle to a container window or nullptr on failure.
 */
HWND API dw_container_new(ULONG cid, int multi)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerNew = env->GetMethodID(clazz, "containerNew",
                                                  "(II)Landroid/widget/ListView;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, containerNew, (int)cid, multi), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * 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)
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_container_setup(HWND handle, unsigned long *flags, char **titles, int count, int separator)
{
    int retval = DW_ERROR_GENERAL;

    if(handle && flags && titles && count > 0)
    {
        int z;

        for(z=0;z<count;z++)
        {
            JNIEnv *env;

            if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
            {
                retval = DW_ERROR_NONE;

                // Generate a string
                jstring jstr = env->NewStringUTF(titles[z]);
                // First get the class that contains the method you need to call
                jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
                // Get the method that you want to call
                jmethodID containerAddColumn = env->GetMethodID(clazz, "containerAddColumn",
                                                          "(Landroid/widget/ListView;Ljava/lang/String;I)V");
                // Call the method on the object
                env->CallVoidMethod(_dw_obj, containerAddColumn, handle, jstr, (int)flags[z]);
                if(_dw_jni_check_exception(env))
                    retval = DW_ERROR_GENERAL;
                env->DeleteLocalRef(jstr);
            }
        }
    }
    return retval;
}

/*
 * Configures the main filesystem column 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)
{
}

/*
 * 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).
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_filesystem_setup(HWND handle, unsigned long *flags, char **titles, int count)
{
    unsigned long fsflags[2] = { DW_CFA_BITMAPORICON, DW_CFA_STRING };
    char *fstitles[2] = { (char *)"Icon", (char *)"Filename" };
    int retval = dw_container_setup(handle, fsflags, fstitles, 2, 0);

    if(retval == DW_ERROR_NONE)
        retval = dw_container_setup(handle, flags, titles, count, 0);
    return retval;
}

/*
 * Allocates memory used to populate a container.
 * Parameters:
 *          handle: Handle to the container window (widget).
 *          rowcount: The number of items to be populated.
 * Returns:
 *       Handle to container items allocated or nullptr on error.
 */
void * API dw_container_alloc(HWND handle, int rowcount)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerAlloc = env->GetMethodID(clazz, "containerAlloc",
                                                    "(Landroid/widget/ListView;I)Landroid/widget/ListView;");
        // Call the method on the object
        return (void *)_dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, containerAlloc, handle, rowcount), _DW_REFERENCE_WEAK);
    }
    return nullptr;
}

/*
 * 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_container_set_item(HWND handle, void *pointer, int column, int row, void *data)
{
    int rowstart = DW_POINTER_TO_INT(dw_window_get_data(handle, "_dw_rowstart"));

    dw_container_change_item(handle, column, row + rowstart, data);
}

/* Internal version that doesn't update the list immediately */
void API _dw_container_change_item(HWND handle, int column, int row, void *data)
{
    JNIEnv *env;

    if(handle && data && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        int columntype = dw_container_get_column_type(handle, column);

        if((columntype & DW_CFA_BITMAPORICON))
        {
            jobject icon = *((jobject *)data);

            if(icon)
            {
                // First get the class that contains the method you need to call
                jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
                // Get the method that you want to call
                jmethodID containerChangeItem = env->GetMethodID(clazz, "containerChangeItemIcon",
                                                                 "(Landroid/widget/ListView;IILandroid/graphics/drawable/Drawable;)V");
                // Call the method on the object
                env->CallVoidMethod(_dw_obj, containerChangeItem, handle, column, row, icon);
                _dw_jni_check_exception(env);
            }
        }
        else if((columntype & DW_CFA_STRING))
        {
            const char *tmp = *((const char **)data);

            if(tmp)
            {
                jstring jstr = env->NewStringUTF(tmp);
                // First get the class that contains the method you need to call
                jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
                // Get the method that you want to call
                jmethodID containerChangeItem = env->GetMethodID(clazz, "containerChangeItemString",
                                                                 "(Landroid/widget/ListView;IILjava/lang/String;)V");
                // Call the method on the object
                env->CallVoidMethod(_dw_obj, containerChangeItem, handle, column, row, jstr);
                _dw_jni_check_exception(env);
                env->DeleteLocalRef(jstr);
            }
        }
        else if((columntype & DW_CFA_ULONG))
        {
            ULONG num = *((ULONG *)data);
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID containerChangeItem = env->GetMethodID(clazz, "containerChangeItemInt",
                                                             "(Landroid/widget/ListView;IIJ)V");
            // Call the method on the object
            env->CallVoidMethod(_dw_obj, containerChangeItem, handle, column, row, (jlong)num);
            _dw_jni_check_exception(env);
        }
        else if((columntype & DW_CFA_DATE))
        {
            CDATE cdate = *((CDATE *)data);
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID containerChangeItem = env->GetMethodID(clazz, "containerChangeItemDate",
                                                             "(Landroid/widget/ListView;IIIII)V");
            // Call the method on the object
            env->CallVoidMethod(_dw_obj, containerChangeItem, handle, column, row,
                                (jint)cdate.year, (jint)cdate.month, (jint)cdate.day);
            _dw_jni_check_exception(env);
        }
        else if((columntype & DW_CFA_TIME))
        {
            CTIME ctime = *((CTIME *)data);
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID containerChangeItem = env->GetMethodID(clazz, "containerChangeItemTime",
                                                             "(Landroid/widget/ListView;IIIII)V");
            // Call the method on the object
            env->CallVoidMethod(_dw_obj, containerChangeItem, handle, column, row,
                                (jint)ctime.hours, (jint)ctime.minutes, (jint)ctime.seconds);
            _dw_jni_check_exception(env);
        }
    }
}

/* Notify that the data changed, causing the container to refresh */
void _dw_container_refresh(HWND handle)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerRefresh = env->GetMethodID(clazz, "containerRefresh",
                                                      "(Landroid/widget/ListView;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, containerRefresh, handle);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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_change_item(handle, column, row, data);
    _dw_container_refresh(handle);
}

/*
 * 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 + 2, row, data);
    _dw_container_refresh(handle);
}

/*
 * 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_container_change_item(handle, 0, row, (void *)&icon);
    _dw_container_change_item(handle, 1, row, (void *)&filename);
    _dw_container_refresh(handle);
}

/*
 * 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_file(HWND handle, void *pointer, int row, const char *filename, HICN icon)
{
    dw_container_set_item(handle, pointer, 0, row, (void *)&icon);
    dw_container_set_item(handle, pointer, 1, row, (void *)&filename);
}

/*
 * 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 + 2, row, data);
}

/*
 * Sets the data 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.
 */
void API dw_container_set_row_data(void *pointer, int row, void *data)
{
    HWND handle = (HWND)pointer;

    if(handle)
    {
        int rowstart = DW_POINTER_TO_INT(dw_window_get_data(handle, "_dw_rowstart"));

        dw_container_change_row_data(handle, row + rowstart, data);
    }
}

/*
 * Changes the data of a row already inserted in the container.
 * Parameters:
 *          handle: Handle to the container window (widget).
 *          row: Zero based row of data being set.
 *          data: Data pointer.
 */
void API dw_container_change_row_data(HWND handle, int row, void *data)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerChangeRowData = env->GetMethodID(clazz, "containerChangeRowData",
                                                            "(Landroid/widget/ListView;IJ)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, containerChangeRowData, handle, row, (jlong)data);
        _dw_jni_check_exception(env);
    }
}

/*
 * Gets column type for a container column.
 * Parameters:
 *          handle: Handle to the container window (widget).
 *          column: Zero based column.
 * Returns:
 *       Constant identifying the the column type.
 */
int API dw_container_get_column_type(HWND handle, int column)
{
    JNIEnv *env;
    int retval = 0;
    int mask = (DW_CFA_BITMAPORICON | DW_CFA_STRING | DW_CFA_ULONG | DW_CFA_TIME | DW_CFA_DATE);

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerGetColumnType = env->GetMethodID(clazz, "containerGetColumnType",
                                                            "(Landroid/widget/ListView;I)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, containerGetColumnType, handle, column);
        if(_dw_jni_check_exception(env))
            retval = 0;
    }
    return (retval & mask);
}

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

/*
 * 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.
 */
void API dw_container_set_stripe(HWND handle, unsigned long oddcolor, unsigned long evencolor)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        jlong odd = _dw_get_color(oddcolor);
        jlong even = _dw_get_color(evencolor);

        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerSetStripe = env->GetMethodID(clazz, "containerSetStripe",
                                                        "(Landroid/widget/ListView;JJ)V");
        if(oddcolor == DW_RGB_TRANSPARENT)
            odd = -1;
        else if(oddcolor == DW_CLR_DEFAULT)
            odd = -2;
        if(evencolor == DW_RGB_TRANSPARENT)
            even = -1;
        else if(evencolor == DW_CLR_DEFAULT)
            even = -2;

        // Call the method on the object
        env->CallVoidMethod(_dw_obj, containerSetStripe, handle, odd, even);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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.
 */
void API dw_container_set_row_title(void *pointer, int row, const char *title)
{
    HWND handle = (HWND)pointer;

    if(handle)
    {
        int rowstart = DW_POINTER_TO_INT(dw_window_get_data(handle, "_dw_rowstart"));

        dw_container_change_row_title(handle, row + rowstart, title);
    }
}


/*
 * 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.
 */
void API dw_container_change_row_title(HWND handle, int row, const char *title)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Generate a string
        jstring jstr = title ? env->NewStringUTF(title) : nullptr;
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerChangeRowTitle = env->GetMethodID(clazz, "containerChangeRowTitle",
                                                             "(Landroid/widget/ListView;ILjava/lang/String;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, containerChangeRowTitle, handle, row, jstr);
        _dw_jni_check_exception(env);
        env->DeleteLocalRef(jstr);
    }
}

/*
 * 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.
 */
void API dw_container_insert(HWND handle, void *pointer, int rowcount)
{
    JNIEnv *env;

    if(pointer && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
        env->DeleteWeakGlobalRef((jobject)pointer);
    dw_window_set_data(handle, "_dw_rowstart", NULL);
    _dw_container_refresh(handle);
}

/*
 * 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.
 */
void API dw_container_clear(HWND handle, int redraw)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerClear = env->GetMethodID(clazz, "containerClear",
                                                    "(Landroid/widget/ListView;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, containerClear, handle);
        _dw_jni_check_exception(env);
        _dw_container_refresh(handle);
    }
}

/*
 * 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.
 */
void API dw_container_delete(HWND handle, int rowcount)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerDelete = env->GetMethodID(clazz, "containerDelete",
                                                     "(Landroid/widget/ListView;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, containerDelete, handle, rowcount);
        _dw_jni_check_exception(env);
        _dw_container_refresh(handle);
    }
}

/*
 * 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.
 */
void API dw_container_scroll(HWND handle, int direction, long rows)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerScroll = env->GetMethodID(clazz, "containerScroll",
                                                     "(Landroid/widget/ListView;II)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, containerScroll, handle, direction, (jint)rows);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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.
 * Returns:
 *       Pointer to data associated with first entry or nullptr on error.
 */
char * API dw_container_query_start(HWND handle, unsigned long flags)
{
    JNIEnv *env;
    char *retval = nullptr;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        if(flags & DW_CR_RETDATA) {
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID containerGetDataStart = env->GetMethodID(clazz, "containerGetDataStart",
                                                               "(Landroid/widget/ListView;I)J");
            // Call the method on the object
            jlong data = env->CallLongMethod(_dw_obj, containerGetDataStart, handle, (jint)flags);
            if(!_dw_jni_check_exception(env))
                retval = (char *)data;
        } else {
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID containerGetTitleStart = env->GetMethodID(clazz, "containerGetTitleStart",
                                                    "(Landroid/widget/ListView;I)Ljava/lang/String;");
            // Call the method on the object
            jstring jstr = (jstring)_dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, containerGetTitleStart, handle, (jint)flags), _DW_REFERENCE_NONE);
            if(jstr)
            {
                const char *str = env->GetStringUTFChars(jstr, nullptr);

                if(str)
                {
                    retval = strdup(str);
                    env->ReleaseStringUTFChars(jstr, str);
                }
            }
        }
    }
    return 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.
 * Returns:
 *       Pointer to data associated with next entry or nullptr on error or completion.
 */
char * API dw_container_query_next(HWND handle, unsigned long flags)
{
    JNIEnv *env;
    char *retval = nullptr;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        if(flags & DW_CR_RETDATA) {
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID containerGetDataStart = env->GetMethodID(clazz, "containerGetDataNext",
                                                               "(Landroid/widget/ListView;I)J");
            // Call the method on the object
            jlong data = env->CallLongMethod(_dw_obj, containerGetDataStart, handle, (jint)flags);
            if(!_dw_jni_check_exception(env))
                retval = (char *)data;
        } else {
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID containerGetTitleNext = env->GetMethodID(clazz, "containerGetTitleNext",
                                                               "(Landroid/widget/ListView;I)Ljava/lang/String;");
            // Call the method on the object
            jstring jstr = (jstring)_dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, containerGetTitleNext, handle, (jint)flags), _DW_REFERENCE_NONE);
            if(jstr)
            {
                const char *str = env->GetStringUTFChars(jstr, nullptr);

                if(str)
                {
                    retval = strdup(str);
                    env->ReleaseStringUTFChars(jstr, str);
                }
            }
        }
    }
    return 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().
 */
void API dw_container_cursor(HWND handle, const char *text)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Generate a string
        jstring jstr = text ? env->NewStringUTF(text) : nullptr;
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerCursor = env->GetMethodID(clazz, "containerCursor",
                                                               "(Landroid/widget/ListView;Ljava/lang/String;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, containerCursor, handle, jstr);
        _dw_jni_check_exception(env);
        if(jstr)
            env->DeleteLocalRef(jstr);
    }
}

/*
 * Cursors the item with the data speficied, and scrolls to that item.
 * Parameters:
 *       handle: Handle to the window (widget) to be queried.
 *       data:  Data usually returned by dw_container_query().
 */
void API dw_container_cursor_by_data(HWND handle, void *data)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerCursorByData = env->GetMethodID(clazz, "containerCursorByData",
                                                              "(Landroid/widget/ListView;J)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, containerCursorByData, handle, (jlong)data);
        _dw_jni_check_exception(env);
    }
}

/*
 * Deletes the item with the text speficied.
 * Parameters:
 *       handle: Handle to the window (widget).
 *       text:  Text usually returned by dw_container_query().
 */
void API dw_container_delete_row(HWND handle, const char *text)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Generate a string
        jstring jstr = text ? env->NewStringUTF(text) : nullptr;
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerRowDeleteByTitle = env->GetMethodID(clazz, "containerRowDeleteByTitle",
                                                               "(Landroid/widget/ListView;Ljava/lang/String;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, containerRowDeleteByTitle, handle, jstr);
        _dw_jni_check_exception(env);
        if(jstr)
            env->DeleteLocalRef(jstr);
    }
}

/*
 * Deletes the item with the data speficied.
 * Parameters:
 *       handle: Handle to the window (widget).
 *       data:  Data usually returned by dw_container_query().
 */
void API dw_container_delete_row_by_data(HWND handle, void *data)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID containerRowDeleteByData = env->GetMethodID(clazz, "containerRowDeleteByData",
                                                              "(Landroid/widget/ListView;J)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, containerRowDeleteByData, handle, (jlong)data);
        _dw_jni_check_exception(env);
    }
}

/*
 * Optimizes the column widths so that all data is visible.
 * Parameters:
 *       handle: Handle to the window (widget) to be optimized.
 */
void API dw_container_optimize(HWND handle)
{
    /* Not necessary with no columns */
}

/*
 * 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)
{
}

/*
 * 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)
{
}

HICN _dw_icon_load(const char *filename, const char *data, int len, int resid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a string
        jstring file = nullptr;
        if(filename)
            file = env->NewStringUTF(filename);
        // Construct a byte array
        jbyteArray bytearray = nullptr;
        if(data && len > 0)
        {
            bytearray = env->NewByteArray(len);
            env->SetByteArrayRegion(bytearray, 0, len, reinterpret_cast<const jbyte *>(data));
        }
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID iconNew = env->GetMethodID(clazz, "iconNew",
                                             "(Ljava/lang/String;[BII)Landroid/graphics/drawable/Drawable;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, iconNew,
                                              file, bytearray, len, resid), _DW_REFERENCE_STRONG);
        // TODO: Clean up after the array now that we are finished
        //if(bytearray)
        //env->ReleaseByteArrayElements(bytearray, (jbyte *) data, 0);
        if(file)
            env->DeleteLocalRef(file);
        return result;
    }
    return nullptr;
}

/*
 * 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.
 * Returns:
 *       Handle to the created icon or nullptr on error.
 */
HICN API dw_icon_load(unsigned long module, unsigned long resid)
{
    return _dw_icon_load(nullptr, nullptr, 0, resid);
}

/*
 * 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)
 * Returns:
 *       Handle to the created icon or nullptr on error.
 */
HICN API dw_icon_load_from_file(const char *filename)
{
    return _dw_icon_load(filename, nullptr, 0, 0);
}

/*
 * Obtains an icon from data.
 * Parameters:
 *       data: Data for the icon (ICO on OS/2 or Windows, XPM on Unix, PNG on Mac)
 *       len: Length of the passed in data.
 * Returns:
 *       Handle to the created icon or nullptr on error.
 */
HICN API dw_icon_load_from_data(const char *data, int len)
{
    return _dw_icon_load(nullptr, data, len, 0);
}

/*
 * Frees a loaded icon resource.
 * Parameters:
 *          handle: Handle to icon returned by dw_icon_load().
 */
void API dw_icon_free(HICN handle)
{
    if(handle)
    {
        JNIEnv *env;

        if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
        {
            env->DeleteGlobalRef(handle);
        }
    }
}

/*
 * Create a new MDI Frame to be packed.
 * Parameters:
 *       id: An ID to be used with dw_window_from_id or 0L.
 * Returns:
 *       Handle to the created MDI widget or nullptr on error.
 */
HWND API dw_mdi_new(unsigned long cid)
{
    return nullptr;
}

/*
 * 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 nullptr on failure.
 */
HWND API dw_splitbar_new(int type, HWND topleft, HWND bottomright, unsigned long cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID splitBarNew = env->GetMethodID(clazz, "splitBarNew",
                                                 "(ILandroid/view/View;Landroid/view/View;I)Landroidx/constraintlayout/widget/ConstraintLayout;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, splitBarNew, type, topleft, bottomright, (int)cid), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * 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.
 */
void API dw_splitbar_set(HWND handle, float percent)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        jfloat position = (jfloat)percent;
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID splitBarSet = env->GetMethodID(clazz, "splitBarSet",
                                                 "(Landroidx/constraintlayout/widget/ConstraintLayout;F)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, splitBarSet, handle, position);
        _dw_jni_check_exception(env);
    }
}

/*
 * Gets the position of a splitbar (pecentage).
 * Parameters:
 *       handle: The handle to the splitbar returned by dw_splitbar_new().
 * Returns:
 *       Position of the splitbar (percentage).
 */
float API dw_splitbar_get(HWND handle)
{
    JNIEnv *env;
    float retval = 0;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID splitBarGet = env->GetMethodID(clazz, "splitBarGet",
                                                 "(Landroidx/constraintlayout/widget/ConstraintLayout;)F");
        // Call the method on the object
        retval = (float)env->CallFloatMethod(_dw_obj, splitBarGet, handle);
        if(_dw_jni_check_exception(env))
            retval = 0;
    }
    return retval;
}

/*
 * Create a bitmap object to be packed.
 * Parameters:
 *       id: An ID to be used with dw_window_from_id() or 0L.
 * Returns:
 *       Handle to the created bitmap widget or nullptr on error.
 */
HWND API dw_bitmap_new(ULONG cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID mleNew = env->GetMethodID(clazz, "bitmapNew",
                                            "(I)Landroid/widget/ImageView;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, mleNew, (int)cid), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/* Unified internal function to create an Android internal Bitmap */
jobject _dw_jbitmap_new(unsigned long width, unsigned long height, const char *filename, const char *data, int len, int resid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a byte array
        jbyteArray bytearray = nullptr;
        if(data && len > 0)
        {
            bytearray = env->NewByteArray(len);
            env->SetByteArrayRegion(bytearray, 0, len, reinterpret_cast<const jbyte *>(data));
        }
        if(!_dw_jni_check_exception(env))
        {
            // Construct a string
            jstring file = nullptr;
            if(filename)
                file = env->NewStringUTF(filename);
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID pixmapNew = env->GetMethodID(clazz, "pixmapNew",
                                                   "(IILjava/lang/String;[BII)Landroid/graphics/Bitmap;");
            // Call the method on the object
            jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, pixmapNew,
                                                                             (jint) width,
                                                                             (jint) height,
                                                                             file, bytearray, len,
                                                                             resid),
                                                  _DW_REFERENCE_STRONG);
            // TODO: Clean up after the array now that we are finished
            //if(bytearray)
            //env->ReleaseByteArrayElements(bytearray, (jbyte *) data, 0);
            if(file)
                env->DeleteLocalRef(file);
            return result;
        }
    }
    return nullptr;
}

void _dw_pixmap_get_dimensions(HPIXMAP pixmap)
{
    JNIEnv *env;

    if(pixmap && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID pixmapGetDimensions = env->GetMethodID(clazz, "pixmapGetDimensions",
                                                         "(Landroid/graphics/Bitmap;)J");
        // Call the method on the object
        jlong dimensions = env->CallLongMethod(_dw_obj, pixmapGetDimensions, pixmap->bitmap);
        if(_dw_jni_check_exception(env))
            dimensions = 0;

        pixmap->width = dimensions & 0xFFFF;
        pixmap->height = (dimensions >> 32) & 0xFFFF;
    }
}

/*
 * 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 nullptr on failure.
 */
HPIXMAP API dw_pixmap_new(HWND handle, unsigned long width, unsigned long height, int depth)
{
    jobject jbitmap = _dw_jbitmap_new(width, height, nullptr, nullptr, 0, 0);

    if(jbitmap)
    {
        HPIXMAP pixmap = (HPIXMAP)calloc(1, sizeof(struct _hpixmap));

        if(pixmap)
        {
            pixmap->width = width;
            pixmap->height = height;
            pixmap->bitmap = jbitmap;
            pixmap->handle = handle;

            return pixmap;
        }
    }
    return nullptr;
}

/*
 * 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 nullptr on failure.
 */
HPIXMAP API dw_pixmap_new_from_file(HWND handle, const char *filename)
{
    jobject jbitmap = _dw_jbitmap_new(0, 0, filename, nullptr, 0, 0);

    if(jbitmap)
    {
        HPIXMAP pixmap = (HPIXMAP)calloc(1, sizeof(struct _hpixmap));

        if(pixmap)
        {
            pixmap->bitmap = jbitmap;
            pixmap->handle = handle;
            _dw_pixmap_get_dimensions(pixmap);

            return pixmap;
        }
    }
    return nullptr;
}

/*
 * Creates a pixmap from data in 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)
 *       len: Length of data
 * Returns:
 *       A handle to a pixmap or nullptr on failure.
 */
HPIXMAP API dw_pixmap_new_from_data(HWND handle, const char *data, int len)
{
    jobject jbitmap = _dw_jbitmap_new(0, 0, nullptr, data, len, 0);

    if(jbitmap)
    {
        HPIXMAP pixmap = (HPIXMAP)calloc(1, sizeof(struct _hpixmap));

        if(pixmap)
        {
            pixmap->bitmap = jbitmap;
            pixmap->handle = handle;
            _dw_pixmap_get_dimensions(pixmap);

            return pixmap;
        }
    }
    return nullptr;
}

/*
 * Sets the transparent color for a pixmap.
 * Parameters:
 *       pixmap: Handle to a pixmap returned by
 *               dw_pixmap_new..
 *       color:  Transparent RGB color
 * Note: This is only necessary on platforms that
 *       don't handle transparency automatically
 */
void API dw_pixmap_set_transparent_color( HPIXMAP pixmap, ULONG color )
{
    /* Not necessary on Android, all versions support transparency. */
}

/*
 * 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 nullptr on failure.
 */
HPIXMAP API dw_pixmap_grab(HWND handle, ULONG resid)
{
    jobject jbitmap = _dw_jbitmap_new(0, 0, nullptr, nullptr, 0, (int)resid);

    if(jbitmap)
    {
        HPIXMAP pixmap = (HPIXMAP)calloc(1, sizeof(struct _hpixmap));

        if(pixmap)
        {
            pixmap->bitmap = jbitmap;
            pixmap->handle = handle;
            _dw_pixmap_get_dimensions(pixmap);

            return pixmap;
        }
    }
    return nullptr;
}

/*
 * 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)
{
    JNIEnv *env;

    if(pixmap && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a string
        jstring jstr = env->NewStringUTF(fontname);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID typefaceFromFontName = env->GetMethodID(clazz, "typefaceFromFontName",
                                                          "(Ljava/lang/String;)Landroid/graphics/Typeface;");
        // Call the method on the object
        jobject typeface = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, typefaceFromFontName, jstr), _DW_REFERENCE_STRONG);
        if(typeface)
        {
            jobject oldtypeface = pixmap->typeface;
            pixmap->typeface = typeface;
            if(oldtypeface)
                env->DeleteGlobalRef(oldtypeface);
            pixmap->fontsize = atoi(fontname);
        }
        env->DeleteLocalRef(jstr);
        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)
    {
        JNIEnv *env;

        if(pixmap && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
        {
            env->DeleteGlobalRef(pixmap->bitmap);
            if(pixmap->typeface)
                env->DeleteGlobalRef(pixmap->typeface);
            free(pixmap);
        }
    }
}

/*
 * 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)
{
    JNIEnv *env;
    int retval = DW_ERROR_GENERAL;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID pixmapBitBlt = env->GetMethodID(clazz, "pixmapBitBlt",
                                                  "(Lorg/dbsoft/dwindows/DWRender;Landroid/graphics/Bitmap;IIIILorg/dbsoft/dwindows/DWRender;Landroid/graphics/Bitmap;IIII)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, pixmapBitBlt, dest, destp ? destp->bitmap : nullptr, xdest, ydest, width, height, src, srcp ? srcp->bitmap : nullptr, xsrc, ysrc, srcwidth, srcheight);
        if(_dw_jni_check_exception(env))
            retval = DW_ERROR_GENERAL;
    }
    return retval;
}

/*
 * Create a new calendar window (widget) to be packed.
 * Parameters:
 *       id: An ID to be used with dw_window_from_id() or 0L.
 * Returns:
 *       Handle to the created calendar or nullptr on error.
 */
HWND API dw_calendar_new(ULONG cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID calendarNew = env->GetMethodID(clazz, "calendarNew",
                                                 "(I)Landroid/widget/CalendarView;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, calendarNew, (int)cid), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * Sets the current date of a calendar.
 * Parameters:
 *       handle: The handle to the calendar returned by dw_calendar_new().
 *       year, month, day: To set the calendar to display.
 */
void API dw_calendar_set_date(HWND handle, unsigned int year, unsigned int month, unsigned int day)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        time_t date;
        struct tm ts = {0};

        // Convert to Unix time
        ts.tm_year = year - 1900;
        ts.tm_mon = month - 1;
        ts.tm_mday = day;
        date = mktime(&ts);

        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID calendarSetDate = env->GetMethodID(clazz, "calendarSetDate",
                                                     "(Landroid/widget/CalendarView;J)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, calendarSetDate, handle, (jlong)date);
        _dw_jni_check_exception(env);
    }
}

/*
 * Gets the year, month and day set in the calendar widget.
 * Parameters:
 *       handle: The handle to the calendar returned by dw_calendar_new().
 *       year: Variable to store the year or nullptr.
 *       month: Variable to store the month or nullptr.
 *       day: Variable to store the day or nullptr.
 */
void API dw_calendar_get_date(HWND handle, unsigned int *year, unsigned int *month, unsigned int *day)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        time_t date;
        struct tm ts;

        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID calendarGetDate = env->GetMethodID(clazz, "calendarGetDate",
                                                     "(Landroid/widget/CalendarView;)J");
        // Call the method on the object
        date = (time_t)env->CallLongMethod(_dw_obj, calendarGetDate, handle);
        if(_dw_jni_check_exception(env))
            date = 0;
        ts = *localtime(&date);
        if(year)
            *year = ts.tm_year + 1900;
        if(month)
            *month = ts.tm_mon + 1;
        if(day)
            *day = ts.tm_mday;
    }
}

/*
 * Causes the embedded HTML widget to take action.
 * Parameters:
 *       handle: Handle to the window.
 *       action: One of the DW_HTML_* constants.
 */
void API dw_html_action(HWND handle, int action)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID htmlAction = env->GetMethodID(clazz, "htmlAction",
                                                "(Landroid/webkit/WebView;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, htmlAction, handle, action);
        _dw_jni_check_exception(env);
    }
}

/*
 * Render raw HTML code in the embedded HTML widget..
 * Parameters:
 *       handle: Handle to the window.
 *       string: String buffer containing HTML code to
 *               be rendered.
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_html_raw(HWND handle, const char *string)
{
    JNIEnv *env;
    int retval = DW_ERROR_GENERAL;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(string);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID htmlRaw = env->GetMethodID(clazz, "htmlRaw",
                                                 "(Landroid/webkit/WebView;Ljava/lang/String;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, htmlRaw, handle, jstr);
        if(!_dw_jni_check_exception(env))
            retval = DW_ERROR_NONE;
        env->DeleteLocalRef(jstr);
    }
    return 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:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_html_url(HWND handle, const char *url)
{
    JNIEnv *env;
    int retval = DW_ERROR_GENERAL;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(url);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID htmlLoadURL = env->GetMethodID(clazz, "htmlLoadURL",
                                                 "(Landroid/webkit/WebView;Ljava/lang/String;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, htmlLoadURL, handle, jstr);
        if(!_dw_jni_check_exception(env))
            retval = DW_ERROR_NONE;
        env->DeleteLocalRef(jstr);
    }
    return 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.
 */
int API dw_html_javascript_run(HWND handle, const char *script, void *scriptdata)
{
    JNIEnv *env;
    int retval = DW_ERROR_GENERAL;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(script);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID htmlJavascriptRun = env->GetMethodID(clazz, "htmlJavascriptRun",
                                                       "(Landroid/webkit/WebView;Ljava/lang/String;J)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, htmlJavascriptRun, handle, jstr, (jlong)scriptdata);
        if(!_dw_jni_check_exception(env))
            retval = DW_ERROR_NONE;
        env->DeleteLocalRef(jstr);
    }
    return retval;
}

/*
 * Create a new HTML window (widget) to be packed.
 * Parameters:
 *       id: An ID to be used with dw_window_from_id() or 0L.
 * Returns:
 *       Handle to the created html widget or nullptr on error.
 */
HWND API dw_html_new(unsigned long cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID htmlNew = env->GetMethodID(clazz, "htmlNew",
                                             "(I)Landroid/webkit/WebView;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, htmlNew, (int)cid), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

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

/*
 * 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)
{
    /* No mouse pointer on Android */
}

/*
 * Create a menu object to be popped up.
 * Parameters:
 *       id: An ID to be used associated with this menu.
 * Returns:
 *       Handle to the created menu or nullptr on error.
 */
HMENUI API dw_menu_new(ULONG cid)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID menuNew = env->GetMethodID(clazz, "menuNew",
                                             "(I)Lorg/dbsoft/dwindows/DWMenu;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, menuNew, (int)cid), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * Create a menubar on a window.
 * Parameters:
 *       location: Handle of a window frame to be attached to.
 * Returns:
 *       Handle to the created menu bar or nullptr on error.
 */
HMENUI API dw_menubar_new(HWND location)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID menuBarNew = env->GetMethodID(clazz, "menuBarNew",
                                                "(Landroid/view/View;)Lorg/dbsoft/dwindows/DWMenu;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, menuBarNew, location), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * 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)
{
    JNIEnv *env;

    if(menu && *menu && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID menuDestroy = env->GetMethodID(clazz, "menuDestroy",
                                                 "(Lorg/dbsoft/dwindows/DWMenu;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, menuDestroy, *menu);
        _dw_jni_check_exception(env);
        *menu = nullptr;
    }
}

/*
 * 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 id)
{
    JNIEnv *env;

    if(menux && id > 0 && id < 30000 && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID menuDeleteItem = env->GetMethodID(clazz, "menuDeleteItem",
                                                    "(Lorg/dbsoft/dwindows/DWMenu;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, menuDeleteItem, menux, (int)id);
        if(!_dw_jni_check_exception(env))
            return DW_ERROR_NONE;
    }
    return DW_ERROR_UNKNOWN;
}

/*
 * 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)
{
    JNIEnv *env;

    if(menu && *menu && parent && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID menuPopup = env->GetMethodID(clazz, "menuPopup",
                                               "(Lorg/dbsoft/dwindows/DWMenu;Landroid/view/View;II)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, menuPopup, *menu, parent, x, y);
        _dw_jni_check_exception(env);
        *menu = nullptr;
    }
}

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.
 *       submenu: Handle to an existing menu to be a submenu or nullptr.
 * Returns:
 *       Handle to the created menu item or nullptr on error.
 */
HWND API dw_menu_append_item(HMENUI menux, const char *title, ULONG itemid, ULONG flags, int end, int check, HMENUI submenux)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        char *newtitle = (char *)alloca(strlen(title)+1);
        char accel = _dw_removetilde(newtitle, title);
        // Create a string
        jstring jstr = env->NewStringUTF(newtitle);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID menuAppendItem = env->GetMethodID(clazz, "menuAppendItem",
                                                    "(Lorg/dbsoft/dwindows/DWMenu;Ljava/lang/String;IIIILorg/dbsoft/dwindows/DWMenu;)Lorg/dbsoft/dwindows/DWMenuItem;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, menuAppendItem, menux, jstr, (int)itemid, (int)flags, end, check, submenux), _DW_REFERENCE_WEAK);
        env->DeleteLocalRef(jstr);
        return result;
    }
    return nullptr;
}

/*
 * 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)
{
    dw_menu_item_set_state(menux, itemid, check ? DW_MIS_CHECKED : DW_MIS_UNCHECKED);
}

/*
 * 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)
{
    JNIEnv *env;

    if(menux && itemid > 0 && itemid < 30000 && state && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID menuSetState = env->GetMethodID(clazz, "menuSetState",
                                                  "(Lorg/dbsoft/dwindows/DWMenu;II)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, menuSetState, menux, (int)itemid, (int)state);
        _dw_jni_check_exception(env);
    }
}

/*
 * Create a notebook object to be packed.
 * Parameters:
 *       id: An ID to be used for getting the resource from the
 *           resource file.
 * Returns:
 *       Handle to the created notebook or nullptr on error.
 */
HWND API dw_notebook_new(ULONG cid, int top)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID notebookNew = env->GetMethodID(clazz, "notebookNew",
                                                 "(II)Landroid/widget/RelativeLayout;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, notebookNew, (int)cid, top), _DW_REFERENCE_WEAK);
        return result;
    }
    return nullptr;
}

/*
 * 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.
 * Returns:
 *       ID of newly created notebook page.
 */
unsigned long API dw_notebook_page_new(HWND handle, ULONG flags, int front)
{
    JNIEnv *env;
    unsigned long result = 0;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID notebookPageNew = env->GetMethodID(clazz, "notebookPageNew",
                                                     "(Landroid/widget/RelativeLayout;JI)J");
        // Call the method on the object
        result = (unsigned long)env->CallLongMethod(_dw_obj, notebookPageNew, handle, (jlong)flags, front);
        if(_dw_jni_check_exception(env))
            result = 0;
    }
    return result;
}

/*
 * Remove a page from a notebook.
 * Parameters:
 *          handle: Handle to the notebook widget.
 *          pageid: ID of the page to be destroyed.
 */
void API dw_notebook_page_destroy(HWND handle, unsigned long pageid)
{
    JNIEnv *env;

    if(handle && pageid && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID notebookPageDestroy = env->GetMethodID(clazz, "notebookPageDestroy",
                                                         "(Landroid/widget/RelativeLayout;J)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, notebookPageDestroy, handle, (jlong)pageid);
        _dw_jni_check_exception(env);
    }
}

/*
 * Queries the currently visible page ID.
 * Parameters:
 *          handle: Handle to the notebook widget.
 * Returns:
 *       ID of visible notebook page.
 */
unsigned long API dw_notebook_page_get(HWND handle)
{
    JNIEnv *env;
    unsigned long result = 0;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key))) {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID notebookPageGet = env->GetMethodID(clazz, "notebookPageGet",
                                                     "(Landroid/widget/RelativeLayout;)J");
        // Call the method on the object
        result = (unsigned long)env->CallLongMethod(_dw_obj, notebookPageGet, handle);
        if(_dw_jni_check_exception(env))
            result = 0;
    }
    return result;
}

/*
 * Sets the currently visible page ID.
 * Parameters:
 *          handle: Handle to the notebook widget.
 *          pageid: ID of the page to be made visible.
 */
void API dw_notebook_page_set(HWND handle, unsigned long pageid)
{
    JNIEnv *env;

    if(handle && pageid && (env = (JNIEnv *)pthread_getspecific(_dw_env_key))) {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID notebookPageSet = env->GetMethodID(clazz, "notebookPageSet",
                                                     "(Landroid/widget/RelativeLayout;J)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, notebookPageSet, handle, (jlong)pageid);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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.
 */
void API dw_notebook_page_set_text(HWND handle, ULONG pageid, const char *text)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID notebookPageSetText = env->GetMethodID(clazz, "notebookPageSetText",
                                                         "(Landroid/widget/RelativeLayout;JLjava/lang/String;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, notebookPageSetText, handle, (jlong)pageid, jstr);
        _dw_jni_check_exception(env);
        env->DeleteLocalRef(jstr);
    }
}

/*
 * 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)
{
}

/*
 * 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.
 */
void API dw_notebook_pack(HWND handle, ULONG pageid, HWND page)
{
    JNIEnv *env;

    if(handle && pageid && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID notebookPagePack = env->GetMethodID(clazz, "notebookPagePack",
                                                      "(Landroid/widget/RelativeLayout;JLandroid/view/View;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, notebookPagePack, handle, (jlong)pageid, page);
        _dw_jni_check_exception(env);
    }
}

/*
 * Create a new Window Frame.
 * Parameters:
 *       owner: The Owner's window handle or HWND_DESKTOP.
 *       title: The Window title.
 *       flStyle: Style flags.
 * Returns:
 *       Handle to the created window or nullptr on error.
 */
HWND API dw_window_new(HWND hwndOwner, const char *title, ULONG flStyle)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(title);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowNew = env->GetMethodID(clazz, "windowNew",
                                               "(Ljava/lang/String;I)Landroid/widget/LinearLayout;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, windowNew, jstr, (int)flStyle), _DW_REFERENCE_WEAK);
        env->DeleteLocalRef(jstr);
        return result;
    }
    return nullptr;
}

/*
 * Call a function from the window (widget)'s context (typically the message loop thread).
 * 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 (* windowfunc)(void *);

    windowfunc = (void (*)(void *))function;

    if(windowfunc)
        windowfunc(data);
}


/*
 * 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)
{
    /* No mouse pointer on Android */
}

int _dw_window_hide_show(HWND handle, int state)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowHideShow = env->GetMethodID(clazz, "windowHideShow",
                                                    "(Landroid/view/View;I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, windowHideShow, handle, state);
        if(!_dw_jni_check_exception(env))
            return DW_ERROR_NONE;
    }
    return DW_ERROR_GENERAL;
}

/*
 * Makes the window visible.
 * Parameters:
 *           handle: The window handle to make visible.
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_window_show(HWND handle)
{
    return _dw_window_hide_show(handle, TRUE);
}

/*
 * Makes the window invisible.
 * Parameters:
 *           handle: The window handle to hide.
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_window_hide(HWND handle)
{
    return _dw_window_hide_show(handle, FALSE);
}

/*
 * 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.
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_window_set_color(HWND handle, ULONG fore, ULONG back)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        unsigned long _fore = _dw_get_color(fore);
        unsigned long _back = _dw_get_color(back);

        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowSetColor = env->GetMethodID(clazz, "windowSetColor",
                                                    "(Landroid/view/View;IIIIIIIIII)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, windowSetColor, handle,
                            (jint)fore, 0, (jint)DW_RED_VALUE(_fore), (jint)DW_GREEN_VALUE(_fore), (jint)DW_BLUE_VALUE(_fore),
                            (jint)back, 0, (jint)DW_RED_VALUE(_back), (jint)DW_GREEN_VALUE(_back), (jint)DW_BLUE_VALUE(_back));
        if(!_dw_jni_check_exception(env))
            return DW_ERROR_NONE;
    }
    return DW_ERROR_GENERAL;
}

/*
 * Sets the border size of a specified window (widget) handle.
 * Parameters:
 *          handle: The window (widget) handle.
 *          border: Size of the window border in pixels.
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_window_set_border(HWND handle, int border)
{
    /* No overlapping windows on Android, so no border */
    return DW_ERROR_GENERAL;
}

/*
 * Sets the style of a given window (widget).
 * Parameters:
 *          handle: Window (widget) handle.
 *          style: Style features enabled or disabled.
 *          mask: Corresponding bitmask of features to be changed.
 */
void API dw_window_set_style(HWND handle, ULONG style, ULONG mask)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowSetStyle = env->GetMethodID(clazz, "windowSetStyle",
                                                    "(Ljava/lang/Object;II)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, windowSetStyle, handle, (jint)style, (jint)mask);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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.
 */
void API dw_window_set_focus(HWND handle)
{
    JNIEnv *env;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowSetFocus = env->GetMethodID(clazz, "windowSetFocus",
                                                    "(Landroid/view/View;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, windowSetFocus, handle);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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.
 */
void API dw_window_default(HWND handle, HWND defaultitem)
{
    JNIEnv *env;

    if(handle && defaultitem && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowDefault = env->GetMethodID(clazz, "windowDefault",
                                                    "(Landroid/view/View;Landroid/view/View;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, windowDefault, handle, defaultitem);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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: See if this is possible on Android */
}

/*
 * Captures the mouse input to this window even if it is outside the bounds.
 * Parameters:
 *       handle: Handle to receive mouse input.
 */
void API dw_window_capture(HWND handle)
{
    /* No mouse pointer on Android */
}

/*
 * Releases previous mouse capture.
 */
void API dw_window_release(void)
{
    /* No mouse pointer on Android */
}

/*
 * 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: Do we need to do implement this? */
}

/*
 * 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"
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_window_set_font(HWND handle, const char *fontname)
{
    JNIEnv *env;
    int retval = DW_ERROR_GENERAL;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a string
        jstring jstr = env->NewStringUTF(fontname);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowSetFont = env->GetMethodID(clazz, "windowSetFont",
                                                    "(Landroid/view/View;Ljava/lang/String;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, windowSetFont, handle, jstr);
        if(!_dw_jni_check_exception(env))
            retval = DW_ERROR_NONE;
        env->DeleteLocalRef(jstr);
    }
    return retval;
}

/*
 * Returns the current font for the specified window.
 * Parameters:
 *           handle: The window handle from which to obtain the font.
 * Returns:
 *       A malloc()ed font name string to be dw_free()ed or nullptr on error.
 */
char * API dw_window_get_font(HWND handle)
{
    JNIEnv *env;
    char *fontname = nullptr;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowGetFont = env->GetMethodID(clazz, "windowGetFont",
                                                   "(Landroid/view/View;)Ljava/lang/String;");
        // Call the method on the object
        jstring jstr = (jstring)_dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, windowGetFont, handle), _DW_REFERENCE_NONE);

        if(jstr)
        {
            const char *str = env->GetStringUTFChars(jstr, nullptr);

            if(str)
            {
                fontname = strdup(str);
                env->ReleaseStringUTFChars(jstr, str);
            }
        }
    }
    return fontname;
}

/* 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 nullptr on error.
 */
char * API dw_font_choose(const char *currfont)
{
    return nullptr;
}

/*
 * 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)
{
    /* TODO: Implement this */
}

/*
 * Destroys a window and all of it's children.
 * Parameters:
 *           handle: The window handle to destroy.
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_window_destroy(HWND handle)
{
    JNIEnv *env;
    int retval = DW_ERROR_GENERAL;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowDestroy = env->GetMethodID(clazz, "windowDestroy",
                                                   "(Ljava/lang/Object;)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, windowDestroy, handle);
        _dw_jni_check_exception(env);
    }
    return retval;
}

/*
 * Gets the text used for a given window.
 * Parameters:
 *       handle: Handle to the window.
 * Returns:
 *       The text associsated with a given window or nullptr on error.
 */
char * API dw_window_get_text(HWND handle)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        const char *utf8 = nullptr;
        char *retval = nullptr;

        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowGetText = env->GetMethodID(clazz, "windowGetText",
                                                   "(Landroid/view/View;)Ljava/lang/String;");
        // Call the method on the object
        jstring result = (jstring)_dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, windowGetText, handle), _DW_REFERENCE_NONE);
        // Get the UTF8 string result
        if(result)
            utf8 = env->GetStringUTFChars(result, nullptr);
        if(utf8)
        {
            retval = strdup(utf8);
            env->ReleaseStringUTFChars(result, utf8);
        }
    }
    return nullptr;
}

/*
 * Sets the text used for a given window.
 * Parameters:
 *       handle: Handle to the window.
 *       text: The text associsated with a given window.
 */
void API dw_window_set_text(HWND handle, const char *text)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(text);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowSetText = env->GetMethodID(clazz, "windowSetText",
                                                   "(Landroid/view/View;Ljava/lang/String;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, windowSetText, handle, jstr);
        _dw_jni_check_exception(env);
        env->DeleteLocalRef(jstr);
    }
}

/*
 * 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)
{
    /* No mouse pointer on Android */
}

void _dw_window_set_enabled(HWND handle, jboolean state)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowSetEnabled = env->GetMethodID(clazz, "windowSetEnabled",
                                                      "(Landroid/view/View;Z)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, windowSetEnabled, handle, state);
        _dw_jni_check_exception(env);
    }
}

/*
 * Disables given window (widget).
 * Parameters:
 *       handle: Handle to the window.
 */
void API dw_window_disable(HWND handle)
{
    _dw_window_set_enabled(handle, JNI_FALSE);
}

/*
 * Enables given window (widget).
 * Parameters:
 *       handle: Handle to the window.
 */
void API dw_window_enable(HWND handle)
{
    _dw_window_set_enabled(handle, JNI_TRUE);
}

/*
 * 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)
 *       data: memory buffer containing image (Bitmap on OS/2 or
 *                 Windows and a pixmap on Unix, pass
 *                 nullptr if you use the id param)
 *       len: Length of data passed
 * 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)
{
    JNIEnv *env;
    int retval = DW_ERROR_UNKNOWN;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a byte array
        jbyteArray bytearray = nullptr;
        if(data && len > 0)
        {
            bytearray = env->NewByteArray(len);
            env->SetByteArrayRegion(bytearray, 0, len, reinterpret_cast<const jbyte *>(data));
        }
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowSetBitmapFromData = env->GetMethodID(clazz, "windowSetBitmapFromData",
                                                             "(Landroid/view/View;I[BI)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, windowSetBitmapFromData, handle, (int)cid, bytearray, len);
        _dw_jni_check_exception(env);
        // Clean up after the array now that we are finished
        //if(bytearray)
            //env->ReleaseByteArrayElements(bytearray, (jbyte *) data, 0);
    }
    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
 *                 nullptr 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)
{
    JNIEnv *env;
    int retval = DW_ERROR_UNKNOWN;

    if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a string
        jstring jstr = nullptr;
        if(filename) {
            jstr = env->NewStringUTF(filename);
        }
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowSetBitmapFromData = env->GetMethodID(clazz, "windowSetBitmap",
                                                             "(Landroid/view/View;ILjava/lang/String;)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, windowSetBitmapFromData, handle, (int)resid, jstr);
        _dw_jni_check_exception(env);
        if(jstr)
            env->DeleteLocalRef(jstr);
    }
    return retval;
}

/*
 * Sets the icon used for a given window.
 * Parameters:
 *       handle: Handle to the window.
 *       icon: Handle to icon to be used.
 */
void API dw_window_set_icon(HWND handle, HICN icon)
{
    /* TODO: See if this is possible, the icon is controlled by app resources */
}

/*
 * Gets the child window handle with specified ID.
 * Parameters:
 *       handle: Handle to the parent window.
 *       id: Integer ID of the child.
 * Returns:
 *       HWND of window with ID or nullptr on error.
 */
HWND API dw_window_from_id(HWND handle, int id)
{
    JNIEnv *env;
    HWND retval = nullptr;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowFromId = env->GetMethodID(clazz, "windowFromId",
                                                  "(Landroid/view/View;I)Landroid/view/View;");
        // Call the method on the object
        retval = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, windowFromId, handle, id), _DW_REFERENCE_NONE);
    }
    return retval;
}

/*
 * Minimizes or Iconifies a top-level window.
 * Parameters:
 *           handle: The window handle to minimize.
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_window_minimize(HWND handle)
{
    /* TODO: Not sure if we should do anything here */
    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)
{
    /* Shouldn't be necessary on Android */
}

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

/*
 * Makes the window bottommost.
 * Parameters:
 *           handle: The window handle to make bottommost.
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_window_lower(HWND handle)
{
    /* TODO: Not sure if we should do anyting here */
    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)
{
    /* Not possible on Android */
}

/*
 * Gets the size the system thinks the widget should be.
 * Parameters:
 *       handle: Window (widget) handle of the item to query.
 *       width: Width in pixels of the item or nullptr if not needed.
 *       height: Height in pixels of the item or nullptr if not needed.
 */
void API dw_window_get_preferred_size(HWND handle, int *width, int *height)
{
    if(width || height)
    {
        JNIEnv *env;

        if(handle && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
        {
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID windowGetPreferredSize = env->GetMethodID(clazz, "windowGetPreferredSize",
                                                                "(Landroid/view/View;)J");
            // Call the method on the object
            jlong dimensions = env->CallLongMethod(_dw_obj, windowGetPreferredSize, handle);
            if(_dw_jni_check_exception(env))
                dimensions = 0;
            if(width)
                *width = (int)(dimensions & 0xFFFF);
            if(height)
                *height = (int)((dimensions >> 32) & 0xFFFF);
        }
        else
        {
            if(width)
                *width = 0;
            if(height)
                *height = 0;
        }
    }
}

/*
 * 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)
{
    /* This is not possible on Android */
}

/*
 * 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)
{
    /* This is not possible on Android */
}

/*
 * 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)
{
    /* This is not possible on Android */
}

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

static jlong _dw_screen_dimensions = 0;

/*
 * Returns the width of the screen.
 */
int API dw_screen_width(void)
{
    if(!_dw_screen_dimensions)
    {
        JNIEnv *env;

        if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
        {
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID screenGetDimensions = env->GetMethodID(clazz, "screenGetDimensions",
                                                             "()J");
            // Call the method on the object
            _dw_screen_dimensions = env->CallLongMethod(_dw_obj, screenGetDimensions);
            if(_dw_jni_check_exception(env))
                _dw_screen_dimensions = 0;
        }
    }
    return _dw_screen_dimensions & 0xFFFF;
}

/*
 * Returns the height of the screen.
 */
int API dw_screen_height(void)
{
    if(!_dw_screen_dimensions)
    {
        JNIEnv *env;

        if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
        {
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID screenGetDimensions = env->GetMethodID(clazz, "screenGetDimensions",
                                                             "()J");
            // Call the method on the object
            _dw_screen_dimensions = env->CallLongMethod(_dw_obj, screenGetDimensions);
            if(_dw_jni_check_exception(env))
                _dw_screen_dimensions = 0;
        }
    }
    return (_dw_screen_dimensions >> 32) & 0xFFFF;
}

/* This should return the current color depth. */
unsigned long API dw_color_depth_get(void)
{
    return 32;
}

/*
 * Returns some information about the current operating environment.
 * Parameters:
 *       env: Pointer to a DWEnv struct.
 */
void API dw_environment_query(DWEnv *env)
{
    static char osName[_DW_ENV_STRING_SIZE+1] = { 0 };

    if(!osName[0])
    {
        JNIEnv *jenv;
        const char *release = nullptr;
        jstring jstr = nullptr;

        if((jenv = (JNIEnv *)pthread_getspecific(_dw_env_key)))
        {
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(jenv, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID androidGetRelease = jenv->GetMethodID(clazz, "androidGetRelease",
                                                           "()Ljava/lang/String;");
            // Call the method on the object
            jstr = (jstring)_dw_jni_check_result(jenv, jenv->CallObjectMethod(_dw_obj, androidGetRelease), _DW_REFERENCE_NONE);

            if(jstr)
                release = jenv->GetStringUTFChars(jstr, nullptr);
        }
        snprintf(osName, _DW_ENV_STRING_SIZE-1, "Android%s%s",
                 release ? " " : "", release ? release : "");
        if(release)
            jenv->ReleaseStringUTFChars(jstr, release);
    }
    memset(env, '\0', sizeof(DWEnv));

    strncpy(env->osName, osName, sizeof(env->osName)-1);
    strncpy(env->buildDate, __DATE__, sizeof(env->buildDate)-1);
    strncpy(env->buildTime, __TIME__, sizeof(env->buildTime)-1);
    strncpy(env->htmlEngine, "CHROME", sizeof(env->htmlEngine)-1);
    env->DWMajorVersion = DW_MAJOR_VERSION;
    env->DWMinorVersion = DW_MINOR_VERSION;
    env->DWSubVersion = DW_SUB_VERSION;

    /* Operating system major */
    env->MajorVersion = _dw_android_api;
    /* Operating system minor */
    env->MinorVersion = 0;
}

/*
 * Emits a beep.
 * Parameters:
 *       freq: Frequency.
 *       dur: Duration.
 */
void API dw_beep(int freq, int dur)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID doBeep = env->GetMethodID(clazz, "doBeep", "(I)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, doBeep, dur);
        _dw_jni_check_exception(env);
    }
}

/* Call this after drawing to the screen to make sure
 * anything you have drawn is visible.
 */
void API dw_flush(void)
{
}

/*
 * Add a named user data item to a window handle.
 * Parameters:
 *       window: Window handle to save data to.
 *       dataname: A string pointer identifying which data to be saved.
 *       data: User data to be saved to the window handle.
 */
void API dw_window_set_data(HWND window, const char *dataname, void *data)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(dataname);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowSetData = env->GetMethodID(clazz, "windowSetData",
                                                   "(Landroid/view/View;Ljava/lang/String;J)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, windowSetData, window, jstr, (jlong)data);
        _dw_jni_check_exception(env);
        env->DeleteLocalRef(jstr);
    }
}

/*
 * Gets a named user data item from a window handle.
 * Parameters:
 *       window: Window handle to get data from.
 *       dataname: A string pointer identifying which data to get.
 * Returns:
 *       Pointer to data or nullptr if no data is available.
 */
void * API dw_window_get_data(HWND window, const char *dataname)
{
    JNIEnv *env;
    void *retval = nullptr;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(dataname);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID windowGetData = env->GetMethodID(clazz, "windowGetData",
                                                   "(Landroid/view/View;Ljava/lang/String;)J");
        // Call the method on the object
        retval = (void *)env->CallLongMethod(_dw_obj, windowGetData, window, jstr);
        if(_dw_jni_check_exception(env))
            retval = nullptr;
        env->DeleteLocalRef(jstr);
    }
    return 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)
{
    JNIEnv *env;
    void *retval = nullptr;

    if(window1 && window2 && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        if(env->IsSameObject(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.
 */
HTIMER API dw_timer_connect(int interval, void *sigfunc, void *data)
{
    JNIEnv *env;
    HTIMER retval = 0;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Use a long paramater
        jlong longinterval = (jlong)interval;
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID timerConnect = env->GetMethodID(clazz, "timerConnect",
                                                  "(JJJ)Ljava/util/Timer;");
        // Call the method on the object
        retval = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj,
                                    timerConnect, longinterval, (jlong)sigfunc, (jlong)data), _DW_REFERENCE_STRONG);
    }
    return retval;
}

/*
 * Removes timer callback.
 * Parameters:
 *       id: Timer ID returned by dw_timer_connect().
 */
void API dw_timer_disconnect(HTIMER timerid)
{
    JNIEnv *env;

    if(timerid && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID timerDisconnect = env->GetMethodID(clazz, "timerDisconnect",
                                                     "(Ljava/util/Timer;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, timerDisconnect, timerid);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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, nullptr, 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;

    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 = nullptr, *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 *) = (void (*)(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 = nullptr, *tmp = DWRoot;

    while(tmp)
    {
        if(tmp->window == window)
        {
            void (*discfunc)(HWND, void *) = (void (*)(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 = nullptr, *tmp = DWRoot;

    while(tmp)
    {
        if(tmp->window == window && tmp->data == data)
        {
            void (*discfunc)(HWND, void *) = (void (*)(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_strlwr(char *buf)
{
    int z, len = 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 API dw_module_load(const char *name, HMOD *handle)
{
    int len;
    char *newname;
    char errorbuf[1025] = {0};


    if(!handle)
        return -1;

    if((len = strlen(name)) == 0)
        return   -1;

    /* Lenth + "lib" + ".so" + nullptr */
    newname = (char *)malloc(len + 7);

    if(!newname)
        return -1;

    sprintf(newname, "lib%s.so", name);
    _dw_strlwr(newname);

    *handle = dlopen(newname, RTLD_NOW);
    if(*handle == nullptr)
    {
        strncpy(errorbuf, dlerror(), 1024);
        printf("%s\n", errorbuf);
        sprintf(newname, "lib%s.so", name);
        *handle = dlopen(newname, RTLD_NOW);
    }

    free(newname);

    return (nullptr == *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 API 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   (nullptr == *func);
}

/* Frees the shared library previously opened.
 * Parameters:
 *         handle: Module handle returned by dw_module_load()
 */
int API dw_module_close(HMOD handle)
{
    if(handle)
        return dlclose(handle);
    return 0;
}

/*
 * Returns the handle to an unnamed mutex semaphore.
 */
HMTX API dw_mutex_new(void)
{
    HMTX mutex = (HMTX)malloc(sizeof(pthread_mutex_t));

    pthread_mutex_init(mutex, nullptr);
    return mutex;
}

/*
 * Closes a semaphore created by dw_mutex_new().
 * Parameters:
 *       mutex: The handle to the mutex returned by dw_mutex_new().
 */
void API dw_mutex_close(HMTX mutex)
{
    if(mutex)
    {
        pthread_mutex_destroy(mutex);
        free(mutex);
    }
}

/* Check if we are on the UI thread */
int _dw_is_ui_thread(void)
{
    JNIEnv *env;
    int retval = FALSE;
    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID isUIThread = env->GetMethodID(clazz, "isUIThread",
                                                "()Z");
        // Call the method on the object
        retval = env->CallBooleanMethod(_dw_obj, isUIThread);
        if(_dw_jni_check_exception(env))
            retval = FALSE;
    }
    return retval;
}

/*
 * 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 API 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(_dw_is_ui_thread())
    {
        while(pthread_mutex_trylock(mutex) != 0)
        {
            /* Process any pending events */
            dw_main_iteration();
            sched_yield();
        }
    }
    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 API dw_mutex_unlock(HMTX mutex)
{
    pthread_mutex_unlock(mutex);
}

/*
 * Returns the handle to an unnamed event semaphore.
 */
HEV API dw_event_new(void)
{
    HEV eve = (HEV)malloc(sizeof(struct _dw_unix_event));

    if(!eve)
        return nullptr;

    /* 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), nullptr);
    pthread_mutex_lock (&(eve->mutex));
    pthread_cond_init (&(eve->event), nullptr);

    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 API 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), nullptr);
    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 API dw_event_post (HEV eve)
{
    if(!eve)
        return DW_ERROR_NON_INIT;

    pthread_mutex_lock (&(eve->mutex));
    pthread_cond_broadcast (&(eve->event));
    eve->posted = 1;
    pthread_mutex_unlock (&(eve->mutex));
    return DW_ERROR_NONE;
}

/*
 * 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 API 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, nullptr);
        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 API 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 = nullptr;

    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, nullptr);

    /* 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);
        int DW_UNUSED(result);

        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, nullptr, nullptr, nullptr) == -1)
        {
            free(array);
            return;
        }

        if(FD_ISSET(listenfd, &rd))
        {
            struct _dw_seminfo *newarray;
            int newfd = accept(listenfd, nullptr, nullptr);

            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 = read(array[z].fd, &command, 1)) < 1)
                {
                    struct _dw_seminfo *newarray;

                    /* Remove this connection from the set */
                    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)
                                    result = 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)
                                result = 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 API dw_named_event_new(const char *name)
{
    struct sockaddr_un un = {0};
    int ev, *tmpsock = (int *)malloc(sizeof(int)*2);
    DWTID dwthread;

    if(!tmpsock)
        return nullptr;

    tmpsock[0] = socket(AF_UNIX, SOCK_STREAM, 0);
    ev = socket(AF_UNIX, SOCK_STREAM, 0);
    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], nullptr, nullptr);

    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);
        return nullptr;
    }

    /* Create a thread to handle this event semaphore */
    pthread_create(&dwthread, nullptr, (void *(*)(void *))_dw_handle_sem, (void *)tmpsock);
    return (HEV)DW_INT_TO_POINTER(ev);
}

/* 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 API dw_named_event_get(const char *name)
{
    struct sockaddr_un un;
    int ev = socket(AF_UNIX, SOCK_STREAM, 0);
    if(ev < 0)
        return nullptr;

    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));
    return (HEV)DW_INT_TO_POINTER(ev);
}

/* 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 API dw_named_event_reset(HEV eve)
{
    /* signal reset */
    char tmp = (char)0;

    if(DW_POINTER_TO_INT(eve) < 0)
        return DW_ERROR_NONE;

    if(write(DW_POINTER_TO_INT(eve), &tmp, 1) == 1)
        return DW_ERROR_NONE;
    return DW_ERROR_GENERAL;
}

/* 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 API dw_named_event_post(HEV eve)
{

    /* signal post */
    char tmp = (char)1;

    if(DW_POINTER_TO_INT(eve) < 0)
        return DW_ERROR_NONE;

    if(write(DW_POINTER_TO_INT(eve), &tmp, 1) == 1)
        return DW_ERROR_NONE;
    return DW_ERROR_GENERAL;
}

/* 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 API dw_named_event_wait(HEV eve, unsigned long timeout)
{
    fd_set rd;
    struct timeval tv, *useme = nullptr;
    int retval = 0;
    char tmp;

    if(DW_POINTER_TO_INT(eve) < 0)
        return DW_ERROR_NON_INIT;

    /* Set the timout or infinite */
    if(timeout != -1)
    {
        tv.tv_sec = timeout / 1000;
        tv.tv_usec = timeout % 1000;

        useme = &tv;
    }

    FD_ZERO(&rd);
    FD_SET(DW_POINTER_TO_INT(eve), &rd);

    /* Signal wait */
    tmp = (char)2;
    retval = write(DW_POINTER_TO_INT(eve), &tmp, 1);

    if(retval == 1)
        retval = select(DW_POINTER_TO_INT(eve)+1, &rd, nullptr, nullptr, useme);

    /* Signal done waiting. */
    tmp = (char)3;
    if(retval == 1)
        retval = write(DW_POINTER_TO_INT(eve), &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. :)
     */
    if(read(DW_POINTER_TO_INT(eve), &tmp, 1) == 1)
        return DW_ERROR_NONE;
    return DW_ERROR_GENERAL;
}

/* 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 API dw_named_event_close(HEV eve)
{
    /* Finally close the domain socket,
     * cleanup will continue in _dw_handle_sem.
     */
    close(DW_POINTER_TO_INT(eve));
    return DW_ERROR_NONE;
}

/*
 * 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)
{
    JNIEnv *env;

    _dw_jvm->AttachCurrentThread(&env, nullptr);
    pthread_setspecific(_dw_env_key, env);
    pthread_setspecific(_dw_fgcolor_key, nullptr);
    pthread_setspecific(_dw_bgcolor_key, (void *)DW_RGB_TRANSPARENT);
}

/*
 * 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)
{
    _dw_jvm->DetachCurrentThread();
}

/*
 * Setup thread independent color sets.
 */
void _dwthreadstart(void *data)
{
    void (*threadfunc)(void *) = nullptr;
    void **tmp = (void **)data;

    threadfunc = (void (*)(void *))tmp[0];

    /* Initialize colors */
    _dw_init_thread();

    threadfunc(tmp[1]);
    free(tmp);

    /* Free colors */
    _dw_deinit_thread();
}

/*
 * 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 API dw_named_memory_new(void **dest, int size, const char *name)
{
    char namebuf[1025];
    struct _dw_unix_shm *handle = (struct _dw_unix_shm *)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 nullptr;
    }

    if(ftruncate(handle->fd, size))
    {
        close(handle->fd);
        free(handle);
        return nullptr;
    }

    /* attach the shared memory segment to our process's address space. */
    *dest = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle->fd, 0);

    if(*dest == MAP_FAILED)
    {
        close(handle->fd);
        *dest = nullptr;
        free(handle);
        return nullptr;
    }

    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 API dw_named_memory_get(void **dest, int size, const char *name)
{
    char namebuf[1025];
    struct _dw_unix_shm *handle = (struct _dw_unix_shm *)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 nullptr;
    }

    /* attach the shared memory segment to our process's address space. */
    *dest = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle->fd, 0);

    if(*dest == MAP_FAILED)
    {
        close(handle->fd);
        *dest = nullptr;
        free(handle);
        return nullptr;
    }

    handle->size = size;
    handle->sid = -1;
    handle->path = nullptr;

    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 API dw_named_memory_free(HSHM handle, void *ptr)
{
    struct _dw_unix_shm *h = (struct _dw_unix_shm *)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 API dw_thread_new(void *func, void *data, int stack)
{
    DWTID dwthread;
    void **tmp = (void **)malloc(sizeof(void *) * 2);
    int rc;

    tmp[0] = func;
    tmp[1] = data;

    rc = pthread_create(&dwthread, nullptr, (void *(*)(void *))_dwthreadstart, (void *)tmp);
    if(rc == 0)
        return dwthread;
    return (DWTID)DW_ERROR_UNKNOWN;
}

/*
 * Ends execution of current thread immediately.
 */
void API dw_thread_end(void)
{
    pthread_exit(nullptr);
}

/*
 * Returns the current thread's ID.
 */
DWTID API dw_thread_id(void)
{
    return (DWTID)pthread_self();
}

/*
 * Initializes the Dynamic Windows engine.
 * Parameters:
 *           newthread: True if this is the only thread.
 *                      False if there is already a message loop running.
 *           argc: Passed in from main()
 *           argv: Passed in from main()
 * Returns:
 *       DW_ERROR_NONE (0) on success.
 */
int API dw_init(int newthread, int argc, char *argv[])
{
    JNIEnv *env;

    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());
    }
    if(!_dw_app_name[0])
    {
        const char *name = getprogname();

        if(name)
            strncpy(_dw_app_name, name, _DW_APP_ID_SIZE);
    }

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring appid = env->NewStringUTF(_dw_app_id);
        jstring appname = env->NewStringUTF(_dw_app_name);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID dwInit = env->GetMethodID(clazz, "dwInit",
                                            "(Ljava/lang/String;Ljava/lang/String;)I");
        // Call the method on the object
        _dw_android_api = env->CallIntMethod(_dw_obj, dwInit, appid, appname);
        if(_dw_jni_check_exception(env))
            _dw_android_api = 0;
        env->DeleteLocalRef(appid);
        env->DeleteLocalRef(appname);
    }
    return DW_ERROR_NONE;
}

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

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID dwindowsShutdown = env->GetMethodID(clazz, "dwindowsShutdown",
                                                      "()V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, dwindowsShutdown);
        _dw_jni_check_exception(env);
    }
}

/*
 * 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:
 *       Process ID on success or DW_ERROR_UNKNOWN (-1) on error.
 */
#if defined(__ANDROID__) && (__ANDROID_API__+0) < 28
int API dw_exec(const char *program, int type, char **params)
{
    int retval = DW_ERROR_UNKNOWN;

    /* Forking isn't recommended on Android... but it works...
     * and prior to API 28 it is the only way to launch processes.
     * Type is ignored, since this can only launch background processes.
     */
    if((retval = fork()) == 0)
    {
        int i;

        /* Close any forked file descriptors */
        for(i=3; i<256; i++)
            close(i);
        setsid();

        execvp(program, params);
    }
    return retval;
}
#else
#include <spawn.h>

int API dw_exec(const char *program, int type, char **params)
{
    int retval = DW_ERROR_UNKNOWN;
    pid_t pid;

    /* API 28 and later has posix_spawn*() so use that instead of
     * fork/exec, however launched processes are still background
     * processes so ignore the type parameter.
     */
    if(posix_spawnp(&pid, program, NULL, NULL, params, NULL) == 0)
    {
        if(pid > 0)
            retval = pid;
        else
            retval = DW_ERROR_NONE;
    }
    return retval;
}
#endif

/*
 * Loads a web browser pointed at the given URL.
 * Parameters:
 *       url: Uniform resource locator.
 * Returns:
 *       DW_ERROR_UNKNOWN (-1) on error; DW_ERROR_NONE (0) or a positive Process ID on success.
 */
int API dw_browse(const char *url)
{
    JNIEnv *env;
    int retval = DW_ERROR_UNKNOWN;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a string
        jstring jstr = env->NewStringUTF(url);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID browseURL = env->GetMethodID(clazz, "browseURL",
                                               "(Ljava/lang/String;)I");
        // Call the method on the object
        retval = env->CallIntMethod(_dw_obj, browseURL, jstr);
        if(_dw_jni_check_exception(env))
            retval = DW_ERROR_UNKNOWN;
        env->DeleteLocalRef(jstr);
    }
    return retval;
}

/*
 * 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 nullptr on failure.
 */
HPRINT API dw_print_new(const char *jobname, unsigned long flags, unsigned int pages, void *drawfunc, void *drawdata)
{
    DWPrint *print;

    if(!drawfunc || !(print = (DWPrint *)calloc(1, sizeof(DWPrint))))
        return nullptr;

    if(!jobname)
        jobname = "Dynamic Windows Print Job";

    print->drawfunc = (int (*)(HPRINT, HPIXMAP, int, void *))drawfunc;
    print->drawdata = drawdata;
    print->flags = flags;
    print->jobname = strdup(jobname);
    print->pages = (jint)pages;

    return print;
}

/*
 * 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 pr, unsigned long flags)
{
    JNIEnv *env;
    DWPrint *print = (DWPrint *)pr;
    int retval = DW_ERROR_UNKNOWN;

    if(print && print->drawfunc && (env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring jstr = env->NewStringUTF(print->jobname);
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID printRun = env->GetMethodID(clazz, "printRun",
                                              "(JILjava/lang/String;II)Landroid/print/PrintJob;");
        // Call the method on the object
        print->printjob = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, printRun, (jlong)pr, (jint)print->flags, jstr, (jint)print->pages, (jint)flags), _DW_REFERENCE_WEAK);
        if(print->printjob)
            retval = DW_ERROR_NONE;
        env->DeleteLocalRef(jstr);
    }
    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 pr)
{
    JNIEnv *env;
    DWPrint *print = (DWPrint *)pr;

    if(print)
    {
        if(print->printjob && (env = (JNIEnv *) pthread_getspecific(_dw_env_key)))
        {
            // First get the class that contains the method you need to call
            jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
            // Get the method that you want to call
            jmethodID printCancel = env->GetMethodID(clazz, "printCancel",
                                                     "(Landroid/print/PrintJob;)V");
            // Call the method on the object
            env->CallVoidMethod(_dw_obj, printCancel, print->printjob);
            _dw_jni_check_exception(env);
        }
        if(print->jobname)
            free(print->jobname);
        free(print);
    }
}

/*
 * Creates a new system notification if possible.
 * Parameters:
 *         title: The short title of the notification.
 *         imagepath: Path to an image to display or nullptr if none.
 *         description: A longer description of the notification,
 *                      or nullptr if none is necessary.
 * Returns:
 *         A handle to the notification which can be used to attach a "clicked" event if desired,
 *         or nullptr 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 API dw_notification_new(const char *title, const char *imagepath, const char *description, ...)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // Construct a String
        jstring appid = env->NewStringUTF(_dw_app_id);
        jstring ntitle = env->NewStringUTF(title);
        jstring ndesc = nullptr;
        jstring image = nullptr;

        if(description)
        {
            va_list args;
            char outbuf[1025] = {0};

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

            ndesc = env->NewStringUTF(outbuf);
        }
        if(imagepath)
            image = env->NewStringUTF(imagepath);

        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID notificationNew = env->GetMethodID(clazz, "notificationNew",
                                                     "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroidx/core/app/NotificationCompat$Builder;");
        // Call the method on the object
        jobject result = _dw_jni_check_result(env, env->CallObjectMethod(_dw_obj, notificationNew, ntitle, image, ndesc, appid), _DW_REFERENCE_WEAK);
        env->DeleteLocalRef(appid);
        env->DeleteLocalRef(ntitle);
        return result;
    }
    return nullptr;
}

/*
 * 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 API dw_notification_send(HWND notification)
{
    JNIEnv *env;

    if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
    {
        // First get the class that contains the method you need to call
        jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
        // Get the method that you want to call
        jmethodID notificationNew = env->GetMethodID(clazz, "notificationSend",
                                                     "(Landroidx/core/app/NotificationCompat$Builder;)V");
        // Call the method on the object
        env->CallVoidMethod(_dw_obj, notificationNew, notification);
        if(!_dw_jni_check_exception(env))
            return DW_ERROR_NONE;
    }
    return DW_ERROR_UNKNOWN;
}

/*
 * 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 nullptr on failure.
 */
wchar_t * API dw_utf8_to_wchar(const char *utf8string)
{
    size_t buflen = strlen(utf8string) + 1;
    wchar_t *temp = (wchar_t *)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 nullptr on failure.
 */
char * API dw_wchar_to_utf8(const wchar_t *wstring)
{
    size_t bufflen = 8 * wcslen(wstring) + 1;
    char *temp = (char *)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_HTML:                    /* Supports the HTML Widget */
        case DW_FEATURE_HTML_RESULT:             /* Supports the DW_SIGNAL_HTML_RESULT callback */
        case DW_FEATURE_NOTIFICATION:            /* Supports sending system notifications */
        case DW_FEATURE_UTF8_UNICODE:            /* Supports UTF8 encoded Unicode text */
        case DW_FEATURE_MLE_WORD_WRAP:           /* Supports word wrapping in Multi-line Edit boxes */
        case DW_FEATURE_CONTAINER_STRIPE:        /* Supports striped line display in container widgets */
        case DW_FEATURE_TREE:                    /* Supports the Tree Widget */
            return DW_FEATURE_ENABLED;
        case DW_FEATURE_CONTAINER_MODE:          /* Supports alternate container view modes */
            return _dw_container_mode;
        case DW_FEATURE_DARK_MODE:               /* Supports Dark Mode user interface */
        {
            /* Dark Mode on Android requires Android 10 (API 29) */
            if(_dw_android_api >= 29) {
                return _dw_dark_mode_detected();
            }
            return DW_FEATURE_UNSUPPORTED;
        }
        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_HTML:                    /* Supports the HTML Widget */
        case DW_FEATURE_HTML_RESULT:             /* Supports the DW_SIGNAL_HTML_RESULT callback */
        case DW_FEATURE_NOTIFICATION:            /* Supports sending system notifications */
        case DW_FEATURE_UTF8_UNICODE:            /* Supports UTF8 encoded Unicode text */
        case DW_FEATURE_MLE_WORD_WRAP:           /* Supports word wrapping in Multi-line Edit boxes */
        case DW_FEATURE_CONTAINER_STRIPE:        /* Supports striped line display in container widgets */
        case DW_FEATURE_TREE:                    /* Supports the Tree Widget */
            return DW_ERROR_GENERAL;
        /* These features are supported and configurable */
        case DW_FEATURE_CONTAINER_MODE:          /* Supports alternate container view modes */
        {
            if(state >= DW_CONTAINER_MODE_DEFAULT && state < DW_CONTAINER_MODE_MAX)
            {
                JNIEnv *env;

                if((env = (JNIEnv *)pthread_getspecific(_dw_env_key)))
                {
                    // First get the class that contains the method you need to call
                    jclass clazz = _dw_find_class(env, DW_CLASS_NAME);
                    // Get the method that you want to call
                    jmethodID setContainerMode = env->GetMethodID(clazz, "setContainerMode",
                                                                  "(I)V");
                    _dw_container_mode = state;
                    // Call the method on the object
                    env->CallVoidMethod(_dw_obj, setContainerMode, (jint)state);
                    _dw_jni_check_exception(env);
                }
                return DW_ERROR_NONE;
            }
            return DW_ERROR_GENERAL;
        }
        case DW_FEATURE_DARK_MODE:               /* Supports Dark Mode user interface */
        {
            /* Dark Mode on Android requires 10 (API 29) */
            if(_dw_android_api >= 29) {
                /* While technically configurable....
                 * for now just return DW_ERROR_GENERAL
                 */
                return DW_ERROR_GENERAL;
            }
            return DW_FEATURE_UNSUPPORTED;
        }
        default:
            return DW_FEATURE_UNSUPPORTED;
    }
}

#ifdef __cplusplus
}
#endif