/*
 * Copyright (c) 2000, 2001, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#if MOTIF_VERSION!=1
    #error This file should only be compiled with motif 1.2
#endif

#include "awt_motif.h"
#include <Xm/VendorSEP.h>
#include <Xm/DragCP.h>
#include "debug_util.h"
#include "awt.h"

/*
 * awt_motif_getIMStatusHeight is a cut and paste of the ImGetGeo() function
 * found in CDE Motif's Xm/XmIm.c.  It returns the height of the Input Method
 * Status region attached to the given VendorShell.  This is needed in order
 * to calculate geometry for Frames and Dialogs that contain TextField or
 * TextArea widgets.
 *
 * BCB: Copying this function out of the Motif source is a horrible
 * hack. Unfortunately, Motif tries to hide the existence of the IM Status
 * region from us so it does not provide any public way to get this info.
 * Clearly a better long term solution needs to be found.
 */

typedef struct _XmICStruct {
    struct _XmICStruct *next;
    Widget icw;
    Window focus_window;
    XtArgVal foreground;
    XtArgVal background;
    XtArgVal background_pixmap;
    XtArgVal font_list;
    XtArgVal line_space;
    int32_t status_width;
    int32_t status_height;
    int32_t preedit_width;
    int32_t preedit_height;
    Boolean has_focus;
    Boolean need_reset;
}   XmICStruct;

typedef struct {
    Widget im_widget;
    XIMStyle input_style;
    XIC xic;
    int32_t status_width;
    int32_t status_height;
    int32_t preedit_width;
    int32_t preedit_height;
    XmICStruct *iclist;
    XmICStruct *current;
}   XmImInfo;

static XFontSet extract_fontset(XmFontList);
static XmICStruct *get_iclist(Widget);

#define MAXARGS 10
static Arg xic_vlist[MAXARGS];
static Arg status_vlist[MAXARGS];
static Arg preedit_vlist[MAXARGS];

#define NO_ARG_VAL -1
#define SEPARATOR_HEIGHT 2


#ifdef MOTIF_2_1_HACK
/* To shut up warning messages from "cc -v"
 *   Copied from Solaris 2.6 /usr/dt/include/Xm/BaseClassP.h and not
 *     there in Solaris 7.
 */
#if defined(__SunOS_5_7) || defined(__SunOS_5_8)
extern XmWidgetExtData _XmGetWidgetExtData(Widget, unsigned char);
#endif

#else

/*
   The following defines are to make the XmImGetXIC to compile on systems
   lower than SunOS 5.7, so therefore the following is a copy of the
   defines on SunOS 5.7/Motif2.1 header files.
*/
/*#if defined (__SunOS_5_5_1) || defined (__SunOS_5_6)*/
#define XmPER_SHELL 0

extern XIC XmImGetXIC(
                        Widget          w,
                        unsigned int    input_policy,
                        ArgList         args,
                        Cardinal        num_args) ;
#endif

static XmICStruct *
get_iclist(Widget w)
{
    Widget p;
    XmVendorShellExtObject ve;
    XmWidgetExtData extData;
    XmImInfo *im_info;

    p = w;
    while (!XtIsShell(p))
        p = XtParent(p);

    extData = (XmWidgetExtData)_XmGetWidgetExtData((Widget) p, XmSHELL_EXTENSION);
    if (extData == NULL)
        return NULL;

    ve = (XmVendorShellExtObject) extData->widget;
    if ((im_info = (XmImInfo *) ve->vendor.im_info) == NULL)
        return NULL;
    else
        return im_info->iclist;
}

int32_t
awt_motif_getIMStatusHeight(Widget vw, jobject tc)
{
    XmICStruct *icp;
    XmVendorShellExtObject ve;
    XmWidgetExtData extData;
    XmImInfo *im_info;
    int32_t width = 0;
    int32_t height = 0;
    XRectangle rect;
    XRectangle *rp;
    int32_t old_height;
    Arg args[1];
    int32_t base_height;
    XFontSet fs;
    XFontSet fss = NULL;
    XFontSet fsp = NULL;

    extData = (XmWidgetExtData)_XmGetWidgetExtData((Widget) vw, XmSHELL_EXTENSION);
    ve = (XmVendorShellExtObject) extData->widget;

    if ((icp = get_iclist(vw)) == NULL) {
        ve->vendor.im_height = 0;
        return 0;
    }
    im_info = (XmImInfo *) ve->vendor.im_info;
    if (im_info->xic == NULL) {
        ve->vendor.im_height = 0;
        return 0;
    }
    status_vlist[0].name = XNFontSet;
    status_vlist[1].name = NULL;
    preedit_vlist[0].name = XNFontSet;
    preedit_vlist[1].name = NULL;

    xic_vlist[0].name = XNAreaNeeded;
    xic_vlist[1].name = NULL;

    im_info->status_width = 0;
    im_info->status_height = 0;
    im_info->preedit_width = 0;
    im_info->preedit_height = 0;
    for (; icp != NULL; icp = icp->next) {
        if (im_info->input_style & XIMStatusArea) {
            if (icp->status_height == 0) {
                char *ret;

                if (icp->font_list == NO_ARG_VAL ||
                    (fss = extract_fontset((XmFontList) icp->font_list)) == NULL)
                    continue;

                status_vlist[0].value = (XtArgVal) fss;
                XSetICValues(im_info->xic,
                             XNStatusAttributes, &status_vlist[0],
                             NULL);

                xic_vlist[0].value = (XtArgVal) & rp;
                ret = XGetICValues(im_info->xic,
                                   XNStatusAttributes, &xic_vlist[0],
                                   NULL);

                if (ret) {
                    /* Cannot obtain XIC value. IM server may be gone. */
                    ve->vendor.im_height = 0;
                    return 0;
                } else {
                    icp->status_width = rp->width;
                    icp->status_height = rp->height;
                    XFree(rp);
                }
            }
            if (icp->status_width > im_info->status_width)
                im_info->status_width = icp->status_width;
            if (icp->status_height > im_info->status_height)
                im_info->status_height = icp->status_height;
        }
        if (im_info->input_style & XIMPreeditArea) {
            if (icp->preedit_height == 0) {
                if (icp->font_list == NO_ARG_VAL ||
                    (fsp = extract_fontset((XmFontList) icp->font_list)) == NULL)
                    continue;

                preedit_vlist[0].value = (XtArgVal) fsp;
                XSetICValues(im_info->xic,
                             XNPreeditAttributes, &preedit_vlist[0],
                             NULL);

                xic_vlist[0].value = (XtArgVal) & rp;
                XGetICValues(im_info->xic,
                             XNPreeditAttributes, &xic_vlist[0],
                             NULL);

                icp->preedit_width = rp->width;
                icp->preedit_height = rp->height;
                XFree(rp);
            }
            if (icp->preedit_width > im_info->preedit_width)
                im_info->preedit_width = icp->preedit_width;
            if (icp->preedit_height > im_info->preedit_height)
                im_info->preedit_height = icp->preedit_height;
        }
    }

    if (im_info->current != NULL && (fss != NULL || fsp != NULL)) {
        if (im_info->current->font_list != NO_ARG_VAL &&
            (fs = extract_fontset((XmFontList) im_info->current->font_list))
            != NULL) {
            if (fss != NULL)
                status_vlist[0].value = (XtArgVal) fs;
            else
                status_vlist[0].name = NULL;
            if (fsp != NULL)
                preedit_vlist[0].value = (XtArgVal) fs;
            else
                preedit_vlist[0].name = NULL;
            XSetICValues(im_info->xic,
                         XNStatusAttributes, &status_vlist[0],
                         XNPreeditAttributes, &preedit_vlist[0],
                         NULL);
        }
    }
    if (im_info->status_height > im_info->preedit_height)
        height = im_info->status_height;
    else
        height = im_info->preedit_height;
    old_height = ve->vendor.im_height;
    if (height)
        height += SEPARATOR_HEIGHT;

    ve->vendor.im_height = height;

    XtSetArg(args[0], XtNbaseHeight, &base_height);
    XtGetValues(vw, args, 1);
    if (base_height < 0)
        base_height = 0;
    XtSetArg(args[0], XtNbaseHeight, base_height);
    XtSetValues(vw, args, 1);
    return height;
}
static XRectangle geometryRect;
XVaNestedList awt_motif_getXICStatusAreaList(Widget w, jobject tc)
{
    Widget p;
    XmVendorShellExtObject ve;
    XmWidgetExtData extData;
    XmImInfo *im_info;
    XmICStruct *icp;

    XVaNestedList list = NULL;
    XRectangle  *ssgeometry = &geometryRect;
    Pixel  bg;
    Pixel  fg;
    Pixmap bpm;
    Dimension height,width;
    Position  x,y;

    p = w;
    while (!XtIsShell(p)){
        p = XtParent(p);
    }

    XtVaGetValues(p,
                  XmNx, &x,
                  XmNy, &y,
                  XmNwidth, &width,
                  XmNheight, &height,
                  NULL);

    extData = (XmWidgetExtData)_XmGetWidgetExtData((Widget) p, XmSHELL_EXTENSION);
    if (extData == NULL) {
        return NULL;
    }

    ve = (XmVendorShellExtObject) extData->widget;
    if ((im_info = (XmImInfo *) ve->vendor.im_info) == NULL) {
        return NULL;
    } else
        icp = im_info->iclist;


    if (icp) {
        /*
         * We hava at least a textfield/textarea in the frame, use the
         * first one.
         */
        ssgeometry->x = 0;
        ssgeometry->y = height - icp->status_height;
        ssgeometry->width = icp->status_width;
        ssgeometry->height = icp->status_height;

        /*
         * use motif TextComponent's resource
         */
        fg = icp->foreground;
        bg = icp->background;
        bpm = icp->background_pixmap;

        list = XVaCreateNestedList(0,
                        XNFontSet, extract_fontset((XmFontList)icp->font_list),
                        XNArea, ssgeometry,
                        XNBackground, bg,
                        XNForeground, fg,
                        XNBackgroundPixmap, bpm,
                        NULL);
   }
    return list ;
}

static XFontSet
extract_fontset(XmFontList fl)
{
    XmFontContext context;
    XmFontListEntry next_entry;
    XmFontType type_return;
    XtPointer tmp_font;
    XFontSet first_fs = NULL;
    char *font_tag;

    if (!XmFontListInitFontContext(&context, fl))
        return NULL;

    do {
        next_entry = XmFontListNextEntry(context);
        if (next_entry) {
            tmp_font = XmFontListEntryGetFont(next_entry, &type_return);
            if (type_return == XmFONT_IS_FONTSET) {
                font_tag = XmFontListEntryGetTag(next_entry);
                if (!strcmp(font_tag, XmFONTLIST_DEFAULT_TAG)) {
                    XmFontListFreeFontContext(context);
                    XtFree(font_tag);
                    return (XFontSet) tmp_font;
                }
                XtFree(font_tag);
                if (first_fs == NULL)
                    first_fs = (XFontSet) tmp_font;
            }
        }
    } while (next_entry);

    XmFontListFreeFontContext(context);
    return first_fs;
}

/*
 * Motif 1.2 requires that an X event passed to XmDragStart is of
 * ButtonPress type. In Motif 2.1 the restriction is relaxed to allow
 * ButtonPress, ButtonRelease, KeyRelease, KeyPress, MotionNotify events
 * as drag initiators. Actually the code in Motif 1.2 works okay for these
 * events as well, since it uses only the fields that have the same values
 * in all five event types. To bypass the initial sanity check in
 * XmDragStart we forcibly change event type to ButtonPress.
 *
 * This function causes an UnsatisfiedLinkError on Linux.
 * Since Linux only links against Motif 2.1, we can safely remove
 * this function altogether from the Linux build.
 * bchristi 1/22/2001
 */

#ifdef __solaris__
void
awt_motif_adjustDragTriggerEvent(XEvent* xevent) {
    xevent->type = ButtonPress;
}
#endif /* __solaris__ */

static XmDragStartProc do_drag_start = NULL;
static Widget drag_initiator = NULL;

static void
CheckedDragStart(XmDragContext dc, Widget src, XEvent *event) {
    DASSERT(do_drag_start != NULL);
    DASSERT(drag_initiator != NULL);
    /*
     * Fix for BugTraq ID 4407057.
     * Enable the drag operation only if it is registered on the specific widget.
     * We use this check to disable Motif default drag support.
     */
    if (src == drag_initiator) {
        do_drag_start(dc, src, event);
    } else {
        /*
         * This is the last chance to destroy the XmDragContext widget.
         * NOTE: We rely on the fact that Motif 1.2 never uses the pointer
         * to XmDragContext object returned from XmDragStart.
         */
        XtDestroyWidget(dc);
    }
}

void
awt_motif_enableSingleDragInitiator(Widget w) {
    DASSERT(drag_initiator == NULL && do_drag_start == NULL && w != NULL);
    drag_initiator = w;
    do_drag_start = xmDragContextClassRec.drag_class.start;
    DASSERT(do_drag_start != NULL);
    xmDragContextClassRec.drag_class.start = (XmDragStartProc)CheckedDragStart;
}
