/*-*-C-*-
 * Lassoo selections for the Windows CSE
 */

#include "resed.h"
#include "main.h"

#include "swicall.h"
#include "wimp.h"
#include "resformat.h"
#include "newmsgs.h"
#include "dragdrop.h"
#include "interactor.h"

#include "format.h"
#include "windowedit.h"
#include "lassoo.h"


typedef struct
{
    WindowObjPtr window;
    RectRec workbox;
    unsigned int buttons, modifiers;
} LassooClosureRec, *LassooClosurePtr;


/*
 * Select the gadgets that intersect with the specified work-area
 * bounding box.  If 'entirely' is set, then only select those
 * that are completely enclosed by the box.
 */

static error * select_bounded_gadgets (
    WindowObjPtr window,
    RectPtr inside,
    Bool toggle,
    Bool entirely
)
{
    Bool anyinvalid = FALSE;
    RectRec invalid;
    GadgetPtr gadget;

    invalid.minx = invalid.miny = BIG;
    invalid.maxx = invalid.maxy = -BIG;

    for (gadget = window->gadgets; gadget; gadget = gadget->next)
    {
        Bool changed = FALSE;
        RectPtr bbox = &gadget->hdr.bbox;

        if (entirely && wimp_rect_contained (bbox, inside) ||
            !entirely && wimp_rects_intersect (bbox, inside))
        {           
            if (gadget->selected)
            {
                if (toggle)
                {
                    /* Toggle off */
                    gadget->selected = FALSE;
                    window->numselected--;
                    changed = TRUE;
                }
            }
            else
            {
                /* Switch on */
                gadget->selected = TRUE;
                window->numselected++;
                changed = TRUE;
            }
        }

        if (changed)
        {
            wimp_merge_bboxes (&invalid, &invalid, bbox);
            anyinvalid = TRUE;
        }
    }

    if (anyinvalid)
    {
        windowedit_add_ears_to_bbox (&invalid, &invalid);
        ER ( wimp_invalidate (window->window, &invalid) );
    }

    return NULL;
}

       
/*
 * Interactor for the lassoo operation
 */

static error * lassoo_interactor (
    unsigned int event,
    int *buf,
    void *closure,
    Bool *consumed
)
{
    LassooClosurePtr lassoo = (LassooClosurePtr) closure;
    static Bool donepointer = FALSE;
    Bool removeptr;

    if (buf == NULL)            /* we are being asked to cancel */
    {
        /* reset pointer to normal if necessary */
        if (donepointer)
        {
            dragdrop_normal_pointer ();
            donepointer = FALSE;
        }

        /* remove drag box from window */
        wimp_update_eor_box (lassoo->window->window, &lassoo->workbox);

        /* reset auto-scroll functions */
        (void) dragdrop_scroll (NULL, NULL, NULL);

        /* cancel wimp dragbox operation */
        return swi(Wimp_DragBox,  R1, 0,  END);
    }
    
    switch (event)
    {
    case EV_NULL_REASON_CODE:
        {
            PointerInfoRec pointer;
            PointRec work;
            PointRec oldscroll, newscroll;
            Bool abouttoscroll;

            (void) swi (Wimp_GetPointerInfo,  R1, &pointer,  END);
            
            /*
             * Look to see if scrolling is about to happen.
             * If so, then we wish only to remove the lassoo: it will be
             *  redrawn by the REDRAW_WINDOW_REQUEST code.
             * The auto-scrolling routines actually update the scroll offset,
             *  so we must preserve the original and reinstate afterwards.
             */

            oldscroll = lassoo->window->window->scrolloffset;
            abouttoscroll = dragdrop_scroll (lassoo->window->window,
                                             &pointer.position, &removeptr);
            if (abouttoscroll)
            {
                /* take note of new scroll offset before restoring */
                newscroll = lassoo->window->window->scrolloffset;
                lassoo->window->window->scrolloffset = oldscroll;
            }
            
            wimp_convert_point (ScreenToWork, lassoo->window->window,
                                                 &pointer.position, &work);

            if (work.x != lassoo->workbox.maxx ||
                work.y != lassoo->workbox.miny || abouttoscroll)
            {
                wimp_update_eor_box (lassoo->window->window,
                                                 &lassoo->workbox);
                lassoo->workbox.maxx = work.x;
                lassoo->workbox.miny = work.y;

                /* don't plot new lassoo if about to scroll */
                if (!abouttoscroll)
                    wimp_update_eor_box (lassoo->window->window,
                                                     &lassoo->workbox);
            }
            else
            {
                wimp_start_rotate_box ();
                wimp_update_eor_box (lassoo->window->window,
                                                 &lassoo->workbox);
                wimp_end_rotate_box ();
            }

            if (abouttoscroll)
            {
                /* restore new scroll offset ready for redraw code */
                lassoo->window->window->scrolloffset = newscroll;

                if (donepointer == FALSE)
                {
                    dragdrop_scroll_pointer ();
                    donepointer = TRUE;
                }
            }
            if (donepointer && removeptr)
            {
                dragdrop_normal_pointer ();
                donepointer = FALSE;
            }
        }
        break;

    case EV_REDRAW_WINDOW_REQUEST:
        {
            WindowRedrawPtr redraw = (WindowRedrawPtr) buf;
            
            if (redraw->handle == lassoo->window->window->handle)
            {
                PointerInfoRec pointer;
                PointRec work;

                *consumed = TRUE;

                /* redraw exposed part of window */
                windowedit_redraw_window (redraw, lassoo->window);

                /* determine revised position of lassoo and draw it */
                (void) swi (Wimp_GetPointerInfo,  R1, &pointer,  END);
                wimp_convert_point (ScreenToWork, lassoo->window->window,
                                                  &pointer.position, &work);
                lassoo->workbox.maxx = work.x;
                lassoo->workbox.miny = work.y;
                wimp_update_eor_box (lassoo->window->window,
                                                     &lassoo->workbox);
            }
        }
        break;

    case EV_KEY_PRESSED:
        {
            KeyPressPtr key = (KeyPressPtr) buf;

            dragdrop_nudge (key->code, 4);

            /* lassoo has priority over keyboard shortcuts */
            *consumed = (key->code != 0x1b);
        }
        break;

    case EV_USER_DRAG_BOX:
        {
            interactor_cancel();
            *consumed = TRUE;
            wimp_regularise_rect (&lassoo->workbox);
            return select_bounded_gadgets (
                lassoo->window,
                &lassoo->workbox,
                lassoo->buttons == MB_DRAG(MB_ADJUST),
                lassoo->modifiers & MODIFIER_SHIFT);
        }
        break;
    }
    return NULL;
}


error * lassoo_start (
    WindowObjPtr window,
    MouseClickPtr mouse,
    unsigned int modifiers
)
{
    /* Lassoo icons */
    DragBoxRec drag;
    static LassooClosureRec closure;
    PointRec work;

    interactor_cancel();
    wimp_convert_point (ScreenToWork, window->window,
                                              &mouse->position, &work);
    closure.window = window;
    closure.buttons = mouse->buttons;
    closure.modifiers = modifiers;
    closure.workbox.minx = work.x;
    closure.workbox.miny = work.y;
    closure.workbox.maxx = work.x + scalex;
    closure.workbox.maxy = work.y + scaley;

    wimp_update_eor_box (window->window, &closure.workbox);

    drag.windowhandle = window->window->handle;
    drag.type = 7;
    drag.constrain = window->window->visarea;

    ER ( swi (Wimp_DragBox,  R1, &drag,  END) );

    interactor_install (lassoo_interactor, (void *) &closure);
    interactor_enable_events (BIT(EV_NULL_REASON_CODE));
    interactor_set_timeout (2);

    return NULL;
}
