/*****************************************************************************
 *
 ****************************************************************************/

#include "gemfintl.h"

#define SCL_DATA_SLL            0x0001
#define SCL_DATA_DLL            0x0002
#define SCL_DRAG_UPDATE         0x0100

static OBJECT scroll_tree[] = {
 { -1,  1,  6, G_IBOX,     NONE,       NORMAL,   (_Ob_spec_t)0x00FF1181L,     0x0000, 0x0000, 0x0000, 0x0000},

 {  6,  2,  5, G_IBOX,     NONE,       NORMAL,   (_Ob_spec_t)0x00001181L,     0x0000, 0x0000, 0x0000, 0x0000},
 {  3, -1, -1, G_BOXCHAR,  NONE,       NORMAL,   (_Ob_spec_t)0x01FF1181L,     0x0000, 0x0000, 0x0000, 0x0000},
 {  5,  4,  4, G_BOX,      NONE,       NORMAL,   (_Ob_spec_t)0x00FF1191L,     0x0000, 0x0000, 0x0000, 0x0000},
 {  3, -1, -1, G_BOX,      NONE,       NORMAL,   (_Ob_spec_t)0x00FF1181L,     0x0000, 0x0000, 0x0000, 0x0000},
 {  1, -1, -1, G_BOXCHAR,  NONE,       NORMAL,   (_Ob_spec_t)0x02FF1181L,     0x0000, 0x0000, 0x0000, 0x0000},

 {  0,  7, 22, G_IBOX,     NONE,       NORMAL,   (_Ob_spec_t)0x00001181L,     0x0000, 0x0000, 0x0000, 0x0000},
 {  8, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x0000, 0x0000, 0x0001},
 {  9, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x0001, 0x0000, 0x0001},
 { 10, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x0002, 0x0000, 0x0001},
 { 11, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x0003, 0x0000, 0x0001},
 { 12, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x0004, 0x0000, 0x0001},
 { 13, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x0005, 0x0000, 0x0001},
 { 14, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x0006, 0x0000, 0x0001},
 { 15, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x0007, 0x0000, 0x0001},
 { 16, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x0008, 0x0000, 0x0001},
 { 17, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x0009, 0x0000, 0x0001},
 { 18, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x000A, 0x0000, 0x0001},
 { 19, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x000B, 0x0000, 0x0001},
 { 20, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x000C, 0x0000, 0x0001},
 { 21, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x000D, 0x0000, 0x0001},
 { 22, -1, -1, G_RSTRING,  NONE,       NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x000E, 0x0000, 0x0001},
 {  6, -1, -1, G_RSTRING,  LASTOB,     NORMAL,   (_Ob_spec_t)NULL,            0x0000, 0x000F, 0x0000, 0x0001}
};

#define SCROLL_CONTROLS     1
#define SCROLL_UPARROW      2
#define SCROLL_PAGE         3
#define SCROLL_SLIDER       4
#define SCROLL_DOWNARROW    5
#define DATA_AREA           6
#define FIRST_DATA_LINE     7

#define MIN_DATA_CHARS      4
#define MIN_DATA_LINES      5
#define MAX_DATA_LINES     16

#define DATA_AREA_WGUTTER       (gl_w2char+gl_w4char)
#define DATA_AREA_HGUTTER       (gl_h2char)

#define SCROLL_CONTROL_WIDTH    ((2*gl_wchar) + gl_w2char)
#define SCROLL_ARROW_HEIGHT     (gl_hchar + gl_h4char)

#define MIN_SCROLLOBJ_WIDTH     (SCROLL_CONTROL_WIDTH+(MIN_DATA_CHARS*gl_wchar)+(2*DATA_AREA_WGUTTER))

#define MIN_SCROLLOBJ_HEIGHT    ((MIN_DATA_LINES*gl_hchar)+(2*DATA_AREA_HGUTTER))
#define MAX_SCROLLOBJ_HEIGHT    ((MAX_DATA_LINES*gl_hchar)+(2*DATA_AREA_HGUTTER))


/*----------------------------------------------------------------------------
 *
 *--------------------------------------------------------------------------*/

typedef struct tscroll_info {
    XUSERBLK xub;                   /* standard XUSERDEF header             */
    void     *datalist;             /* pointer to data to show in scroller  */
    short    numitems;              /* number of items in data list         */
    short    curitem;               /* index of currently selected item     */
    short    topitem;               /* index of item displayed on top line  */
    short    flags;                 /* processing flags                     */
    short    numlines;              /* number of lines in the scroller window*/
    short    statusobj;             /* obj in main tree that shows cur selection*/
    GRECT    dialog_rectangles[7];  /* rectangles describing scroller       */
} TSCROLLINFO;

static char nullstr[] = "";         /* empty string for unused data lines   */

/*----------------------------------------------------------------------------
 * internal functions...
 *--------------------------------------------------------------------------*/

#ifdef GEMFAST_PROTOS
  INTERNAL_VFUNC init_scroll_tree(void);
  INTERNAL_VFUNC update_tree_from_scrollinfo(TSCROLLINFO *pinfo);
  INTERNAL_VFUNC render_scroller(TSCROLLINFO *pinfo, GRECT *cliprect);
  INTERNAL_VFUNC continuous_scroll(TSCROLLINFO *pinfo, short scrollobj, short mx, short my);
  INTERNAL_IFUNC count_items(void *datalist);
  
  static void GCALLBACK udscroll(OBJECT *ptree, short obj, short slidepos, void *udata);
  static XUBT_STATUS GCALLBACK feel_scroller(XUSERBLK *xub, short mx, short my, short clicks);
#endif

INTERNAL_VFUNC init_scroll_tree()
/*****************************************************************************
 * one-time init, set y and height of all data lines in scroll tree.
 *  this is essentially in place of doing rsrc_obfix() calls on the tree.
 *  we calc all the xywh values on the fly except for the data lines which
 *  don't move in relation to their parent no matter what else is happening,
 *  so here we do the obfix-style fixup for the data lines.
 ****************************************************************************/
{
    short   hchar = gl_hchar;
    OBJECT *pobj = &scroll_tree[FIRST_DATA_LINE];

    do  {
        pobj->ob_y      *= hchar;
        pobj->ob_height *= hchar;
    } while (!((pobj++)->ob_flags & LASTOB));
}

INTERNAL_VFUNC update_tree_from_scrollinfo(pinfo)
    TSCROLLINFO *pinfo;
/*****************************************************************************
 * update the scroller dialog tree with values from the specified scroller.
 ****************************************************************************/
{
    short   i;
    short   item;
    short   linewidth;
    char    **strings;
    OBJECT  *pobj;
    short   slidesize;
    short   slidepos;
    long    pixel_range;

    /*------------------------------------------------------------------------
     * call lib routine to calc size & position of scroller.  returned
     * values are in the range 1-1000, or -1 for minimum-sized slider.
     *----------------------------------------------------------------------*/

    wc_scroll_calc(pinfo->topitem, pinfo->numitems, pinfo->numlines,
        &slidesize, &slidepos);

    /*------------------------------------------------------------------------
     * translate position and size into actual x/y/w/h values.
     *----------------------------------------------------------------------*/

    pixel_range = pinfo->dialog_rectangles[SCROLL_PAGE].g_h;

    slidesize = (short)((pixel_range * slidesize) / 1000L);

    if (slidesize < gl_hchar) {
        slidesize = gl_hchar;
    }

    slidepos = (short)(((pixel_range-slidesize) * slidepos) / 1000L);

    pinfo->dialog_rectangles[SCROLL_SLIDER].g_y = slidepos;
    pinfo->dialog_rectangles[SCROLL_SLIDER].g_h = slidesize;

    /*------------------------------------------------------------------------
     * set the dialog's controlling rectangles to scroller's values.
     *----------------------------------------------------------------------*/

    pobj = scroll_tree;

    for (i = 0; i < Array_els(pinfo->dialog_rectangles); ++i) {
        *(GRECT *)&pobj->ob_x = pinfo->dialog_rectangles[i];
        ++pobj;
    }

    /*------------------------------------------------------------------------
     * set string pointers in the visible area of the scroller dialog.
     *----------------------------------------------------------------------*/

    strings   = (char **)pinfo->datalist;
    item      = pinfo->topitem;
    linewidth = pinfo->dialog_rectangles[DATA_AREA].g_w;

    for (i = 0; i < MAX_DATA_LINES; ++i) {
        if (i >= pinfo->numlines) {         /* line isn't in visible area   */
            pobj->ob_flags |= HIDETREE;     /* of scroller, hide it.        */
        } else {
            pobj->ob_flags &= ~HIDETREE;    /* line is visible, unhide it.  */
            pobj->ob_width  = linewidth;    /* set line object width.       */
            if (item >= pinfo->numitems) {  /* if we're out of lines (short */
                pobj->ob_spec = (_Ob_spec_t)nullstr; /* data list), show an empty line.*/
            } else {
                pobj->ob_spec = (_Ob_spec_t)(strings[item]);
            }
            pobj->ob_state = (item == pinfo->curitem) ? SELECTED : NORMAL;
            ++item;
        }
        ++pobj;
    }
}

INTERNAL_VFUNC render_scroller(pinfo, cliprect)
    TSCROLLINFO *pinfo;
    GRECT       *cliprect;
/*****************************************************************************
 * update the scroller visuals (controls, data area), then paint it.
 ****************************************************************************/
{
    update_tree_from_scrollinfo(pinfo);
    obj__draw(scroll_tree, ROOT, MAX_DEPTH, cliprect);
}

static long GCALLBACK see_scroller(pb)
    XPARMBLK *pb;
/*****************************************************************************
 * USERDEF drawing routine for scroller objects.
 ****************************************************************************/
{
    TSCROLLINFO *pinfo = (TSCROLLINFO *)pb->pub;

    pinfo->dialog_rectangles[0].g_x = pb->drawrect.g_x;
    pinfo->dialog_rectangles[0].g_y = pb->drawrect.g_y;

    render_scroller(pinfo, &pb->cliprect);

    return 0;
}

static void GCALLBACK udscroll(ptree, obj, slidepos, udata)
    OBJECT  *ptree;
    short   obj;
    short   slidepos;
    void    *udata;
/*****************************************************************************
 * the callback for grf_udslidebox() -- update window based on slider
 ****************************************************************************/
{
    TSCROLLINFO *pinfo = udata;

    if (slidepos > 2000) {
        slidepos -= 2000;
    } else if (slidepos > 1000) {
        slidepos -= 1000;
    }

    pinfo->topitem = (short)((slidepos * (pinfo->numitems - pinfo->numlines)) / 1000L);
    render_scroller(pinfo, &gl_rwdesk);

}

INTERNAL_VFUNC continuous_scroll(pinfo, scrollobj, mx, my)
    TSCROLLINFO *pinfo;
    short       scrollobj;
    short       mx;
    short       my;
/*****************************************************************************
 * continuously scroll the scroller object until mouse button released.
 ****************************************************************************/
{
    short mb;
    short slidey;
    short dmy;
    short scrollamt;
    short newtop;

    if (scrollobj == SCROLL_SLIDER) {
        grf_udslidebox(scroll_tree, SCROLL_SLIDER, TRUE, udscroll, pinfo);
        return;
    } else {
        switch (scrollobj) {
          case SCROLL_UPARROW:
            scrollamt = -1;
            break;
          case SCROLL_DOWNARROW:
            scrollamt = 1;
            break;
          case SCROLL_PAGE:
            objc_offset(scroll_tree, SCROLL_SLIDER, &dmy, &slidey);
            if (my < slidey) {
                scrollamt = -(pinfo->numlines);
            } else {
                scrollamt = pinfo->numlines;
            }
            break;
        }
    }

    do  {
        newtop = pinfo->topitem + scrollamt;
        if ((newtop + pinfo->numlines) > pinfo->numitems) {
            newtop = pinfo->numitems - pinfo->numlines;
        }
        if (newtop < 0) {
            newtop = 0;
        }
        if (pinfo->topitem != newtop || scrollamt == 0) {
            pinfo->topitem = newtop;
            render_scroller(pinfo, &gl_rwdesk);
        }
        graf_mkstate(&dmy, &dmy, &mb, &dmy);
    } while (mb);
}

static XUBT_STATUS GCALLBACK feel_scroller(xub, mx, my, clicks)
    XUSERBLK *xub;
    short    mx;
    short    my;
    short    clicks;
/*****************************************************************************
 * routine to handle a click on a scroller object.
 ****************************************************************************/
{
    short        clickobj;
    TSCROLLINFO *pinfo = (TSCROLLINFO *)xub;
    XUBT_STATUS  status;

    update_tree_from_scrollinfo(pinfo);
    clickobj = objc_find(scroll_tree, ROOT, MAX_DEPTH, mx, my);

    if (clickobj <= ROOT || clickobj == DATA_AREA) {
        status = XUBT_NONE;
    } else if (DATA_AREA == obj_parent(scroll_tree, clickobj)) {
        pinfo->curitem = pinfo->topitem + (clickobj - FIRST_DATA_LINE);
        pinfo->xub.ob_spec = scroll_tree[clickobj].ob_spec;
        update_tree_from_scrollinfo(pinfo);
        obj__draw(scroll_tree, DATA_AREA, MAX_DEPTH, &gl_rwdesk);
        if (pinfo->statusobj != NO_OBJECT) {
            rsc_sstrings(xub->parent_tree, pinfo->statusobj, (char *)pinfo->xub.ob_spec, -1);
            obj__draw(xub->parent_tree, pinfo->statusobj, MAX_DEPTH, &gl_rwdesk);
        }
        status = XUBT_VALUE;
        if (clicks > 1) {
            status |= XUBT_DCEXIT;
        }
        evn_wbutton(EVN_BUTTONUP);
    } else {
        continuous_scroll(pinfo, clickobj, mx, my);
        status = XUBT_VISUAL;
    }

    return status;
}

INTERNAL_IFUNC count_items(datalist)
    void *datalist;
/*****************************************************************************
 *
 ****************************************************************************/
{
    char **strings;
    short numitems = 0;

    for (strings = (char **)datalist; *strings != NULL; ++strings) {
        ++numitems;
    }

    return numitems;
}

/*----------------------------------------------------------------------------
 * public functions...
 *--------------------------------------------------------------------------*/

short obj_make_tscroll(ptree, obj, statusobj)
    OBJECT *ptree;
    short   obj;
    short   statusobj;
/*****************************************************************************
 *
 ****************************************************************************/
{
    register OBJECT      *pobj = &ptree[obj];
    register TSCROLLINFO *pinfo;

    if (scroll_tree[FIRST_DATA_LINE].ob_height == 1) {
        init_scroll_tree(); /* do one-time init if not done already         */
    }

    /*------------------------------------------------------------------------
     * validate parms.
     *----------------------------------------------------------------------*/

    if (0 == apl_vshared()) {
        return gfErr_vdi_handle;
    }

    if (pobj->ob_width < MIN_SCROLLOBJ_WIDTH ||
        pobj->ob_height < MIN_SCROLLOBJ_HEIGHT) {
        return gfErr_object_too_small;
    }

    if (pobj->ob_height > MAX_SCROLLOBJ_HEIGHT) {
        return gfErr_object_too_big;
    }

    /*------------------------------------------------------------------------
     * If the object has already been made into a G_TSCROLL extended object,
     * just get its pointer, else make it into such an object.
     *----------------------------------------------------------------------*/

    if ((pobj->ob_type & 0x00FF) == G_USERDEF) {
        pinfo = (TSCROLLINFO *)pobj->_Ob_spec;
        if (pinfo->xub.ob_type != G_TSCROLL) {
            return gfErr_wrong_type;
        }
    } else {
        if (NULL == (pinfo = apl_malloc((long)sizeof(*pinfo)))) {
            return gfErr_no_memory;
        }
        obj_mxuserdef(ptree, obj, &pinfo->xub, see_scroller, feel_scroller, (long)sizeof(*pinfo));
        pinfo->xub.ob_type = G_TSCROLL;
        pinfo->xub.ob_spec = (_Ob_spec_t)NULL;
    }

    pinfo->datalist  = NULL;
    pinfo->numitems  = 0;
    pinfo->curitem   = NO_OBJECT;
    pinfo->topitem   = 0;
    pinfo->flags     = 0;
    pinfo->statusobj = statusobj;
    pinfo->numlines  = (pobj->ob_height - (2 * DATA_AREA_HGUTTER)) / gl_hchar;

    if (statusobj != NO_OBJECT) {
        rsc_sstrings(ptree, statusobj, nullstr, -1);
    }

    /*------------------------------------------------------------------------
     * build the rectangles describing the on-screen scroller.
     *----------------------------------------------------------------------*/

#define SetRect(idx, x, y, w, h)            \
    pinfo->dialog_rectangles[idx].g_x = x;  \
    pinfo->dialog_rectangles[idx].g_y = y;  \
    pinfo->dialog_rectangles[idx].g_w = w;  \
    pinfo->dialog_rectangles[idx].g_h = h;

    SetRect(ROOT,
        pobj->ob_x,
        pobj->ob_y,
        pobj->ob_width,
        pobj->ob_height);

    SetRect(SCROLL_CONTROLS,
        (pobj->ob_width - SCROLL_CONTROL_WIDTH),
        0,
        SCROLL_CONTROL_WIDTH,
        pobj->ob_height);

    SetRect(SCROLL_UPARROW,
        0,
        0,
        SCROLL_CONTROL_WIDTH,
        SCROLL_ARROW_HEIGHT);

    SetRect(SCROLL_PAGE,
        0,
        SCROLL_ARROW_HEIGHT,
        SCROLL_CONTROL_WIDTH,
        (pobj->ob_height - 2 * SCROLL_ARROW_HEIGHT));

    SetRect(SCROLL_SLIDER,
        0,
        0,
        SCROLL_CONTROL_WIDTH,
        1);

    SetRect(SCROLL_DOWNARROW,
        0,
        (SCROLL_ARROW_HEIGHT + pinfo->dialog_rectangles[SCROLL_PAGE].g_h),
        SCROLL_CONTROL_WIDTH,
        SCROLL_ARROW_HEIGHT);

    SetRect(DATA_AREA,
        DATA_AREA_WGUTTER,
        DATA_AREA_HGUTTER,
        (pobj->ob_width - SCROLL_CONTROL_WIDTH - (2 * DATA_AREA_WGUTTER)),
        (pobj->ob_height - (2 * DATA_AREA_HGUTTER)));

#undef SetRect

    return 0;

}

void obj_set_tscroll(ptree, obj, datalist, numitems, curitem, topitem)
    OBJECT *ptree;
    short   obj;
    void    *datalist;
    short   numitems;
    short   curitem;
    short   topitem;
/*****************************************************************************
 *
 ****************************************************************************/
{
    TSCROLLINFO *pinfo;

    if (ptree[obj].ob_flags & INDIRECT) {
        pinfo = *(TSCROLLINFO **)ptree[obj].ob_spec;
    } else {
        pinfo = (TSCROLLINFO *)ptree[obj].ob_spec;
    }

    if ((ptree[obj].ob_type & 0x00FF) != G_USERDEF) {
        return;
    }

    if (pinfo->xub.ob_type != G_TSCROLL) {
        return;
    }

    /*------------------------------------------------------------------------
     * fill in the details in the scrollinfo structure...
     *----------------------------------------------------------------------*/

    if (datalist != NULL) {
        pinfo->datalist = datalist;
        pinfo->curitem  = NO_OBJECT;
        pinfo->topitem  = 0;
        if (numitems < 0) {
            numitems = count_items(datalist);
        }
    }

    if (numitems >= 0) {
        pinfo->numitems = numitems;
    }

    if (curitem >= NO_OBJECT) {
        if (curitem < pinfo->numitems) {
            pinfo->curitem = curitem;
        } else {
            pinfo->curitem = NO_OBJECT;
        }
    }

    if (topitem >= 0) {
        if (topitem < pinfo->numitems) {
            pinfo->topitem = topitem;
        } else {
            pinfo->topitem = 0;
        }
    }

    if (pinfo->datalist == NULL || pinfo->curitem == NO_OBJECT) {
        pinfo->xub.ob_spec = (_Ob_spec_t)NULL;
    } else {
        void **items = (void **)pinfo->datalist;
        pinfo->xub.ob_spec = (_Ob_spec_t)items[pinfo->curitem];
    }

    if (pinfo->statusobj != NO_OBJECT) {
        char *curstr;
        if (pinfo->curitem == NO_OBJECT) {
            curstr = nullstr;
        } else {
            curstr = (char *)pinfo->xub.ob_spec;
        }
        rsc_sstrings(ptree, pinfo->statusobj, curstr, -1);
    }

}

short obj_get_tscroll(ptree, obj, curstring, curitem, topitem)
    OBJECT *ptree;
    short   obj;
    char    **curstring;
    short   *curitem;
    short   *topitem;
/*****************************************************************************
 *
 ****************************************************************************/
{
    TSCROLLINFO  *pinfo;

    if (ptree[obj].ob_flags & INDIRECT) {
        pinfo = *(TSCROLLINFO **)ptree[obj].ob_spec;
    } else {
        pinfo = (TSCROLLINFO *)ptree[obj].ob_spec;
    }

    if ((ptree[obj].ob_type & 0x00FF) != G_USERDEF) {
        return NO_OBJECT;
    }

    if (pinfo->xub.ob_type != G_TSCROLL) {
        return NO_OBJECT;
    }

    if (topitem != NULL) {
        *topitem = pinfo->topitem;
    }

    if (curstring != NULL) {
        *curstring = (char*)pinfo->xub.ob_spec;
    }

    if (curitem != NULL) {
        *curitem = pinfo->curitem;
    }

    return pinfo->curitem;
}


