/* ------------- listbox.c ------------ */

#include "dflat.h"

#ifdef INCLUDE_EXTENDEDSELECTIONS
static int ExtendSelections(DFWINDOW, int, int);
static void TestExtended(DFWINDOW, DF_PARAM);
static void ClearAllSelections(DFWINDOW);
static void SetSelection(DFWINDOW, int);
static void FlipSelection(DFWINDOW, int);
static void ClearSelection(DFWINDOW, int);
#else
#define TestExtended(w,p) /**/
#endif
static void ChangeSelection(DFWINDOW, int, int);
static void WriteSelection(DFWINDOW, int, int, DFRECT *);
static BOOL SelectionInWindow(DFWINDOW, int);

static int py = -1;    /* the previous y mouse coordinate */

#ifdef INCLUDE_EXTENDEDSELECTIONS
/* --------- DF_SHIFT_F8 Key ------------ */
static void AddModeKey(DFWINDOW wnd)
{
    if (DfIsMultiLine(wnd))    {
        wnd->AddMode ^= TRUE;
        DfSendMessage(DfGetParent(wnd), DFM_ADDSTATUS,
            wnd->AddMode ? ((DF_PARAM) "Add Mode") : 0, 0);
    }
}
#endif

/* --------- DF_UP (Up Arrow) Key ------------ */
static void UpKey(DFWINDOW wnd, DF_PARAM p2)
{
    if (wnd->selection > 0)    {
        if (wnd->selection == wnd->wtop)    {
            DfBaseWndProc(DF_LISTBOX, wnd, DFM_KEYBOARD, DF_UP, p2);
            DfPostMessage(wnd, DFM_LB_SELECTION, wnd->selection-1,
                DfIsMultiLine(wnd) ? p2 : FALSE);
        }
        else    {
            int newsel = wnd->selection-1;
            if (wnd->wlines == DfClientHeight(wnd))
                while (*DfTextLine(wnd, newsel) == DF_LINE)
                    --newsel;
            DfPostMessage(wnd, DFM_LB_SELECTION, newsel,
#ifdef INCLUDE_EXTENDEDSELECTIONS
                DfIsMultiLine(wnd) ? p2 :
#endif
                FALSE);
        }
    }
}

/* --------- DF_DN (Down Arrow) Key ------------ */
static void DnKey(DFWINDOW wnd, DF_PARAM p2)
{
    if (wnd->selection < wnd->wlines-1)    {
        if (wnd->selection == wnd->wtop+DfClientHeight(wnd)-1)  {
            DfBaseWndProc(DF_LISTBOX, wnd, DFM_KEYBOARD, DF_DN, p2);
            DfPostMessage(wnd, DFM_LB_SELECTION, wnd->selection+1,
                DfIsMultiLine(wnd) ? p2 : FALSE);
        }
        else    {
            int newsel = wnd->selection+1;
            if (wnd->wlines == DfClientHeight(wnd))
                while (*DfTextLine(wnd, newsel) == DF_LINE)
                    newsel++;
            DfPostMessage(wnd, DFM_LB_SELECTION, newsel,
#ifdef INCLUDE_EXTENDEDSELECTIONS
                DfIsMultiLine(wnd) ? p2 :
#endif
                FALSE);
        }
    }
}

/* --------- DF_HOME and DF_PGUP Keys ------------ */
static void HomePgUpKey(DFWINDOW wnd, DF_PARAM p1, DF_PARAM p2)
{
    DfBaseWndProc(DF_LISTBOX, wnd, DFM_KEYBOARD, p1, p2);
    DfPostMessage(wnd, DFM_LB_SELECTION, wnd->wtop,
#ifdef INCLUDE_EXTENDEDSELECTIONS
        DfIsMultiLine(wnd) ? p2 :
#endif
        FALSE);
}

/* --------- DF_END and DF_PGDN Keys ------------ */
static void EndPgDnKey(DFWINDOW wnd, DF_PARAM p1, DF_PARAM p2)
{
    int bot;
    DfBaseWndProc(DF_LISTBOX, wnd, DFM_KEYBOARD, p1, p2);
    bot = wnd->wtop+DfClientHeight(wnd)-1;
    if (bot > wnd->wlines-1)
        bot = wnd->wlines-1;
    DfPostMessage(wnd, DFM_LB_SELECTION, bot,
#ifdef INCLUDE_EXTENDEDSELECTIONS
        DfIsMultiLine(wnd) ? p2 :
#endif
        FALSE);
}

#ifdef INCLUDE_EXTENDEDSELECTIONS
/* --------- Space Bar Key ------------ */
static void SpacebarKey(DFWINDOW wnd, DF_PARAM p2)
{
    if (DfIsMultiLine(wnd))    {
        int sel = DfSendMessage(wnd, DFM_LB_CURRENTSELECTION, 0, 0);
        if (sel != -1)    {
            if (wnd->AddMode)
                FlipSelection(wnd, sel);
            if (DfItemSelected(wnd, sel))    {
                if (!((int) p2 & (DF_LEFTSHIFT | DF_RIGHTSHIFT)))
                    wnd->AnchorPoint = sel;
                ExtendSelections(wnd, sel, (int) p2);
            }
            else
                wnd->AnchorPoint = -1;
            DfSendMessage(wnd, DFM_PAINT, 0, 0);
        }
    }
}
#endif

/* --------- Enter ('\r') Key ------------ */
static void EnterKey(DFWINDOW wnd)
{
    if (wnd->selection != -1)    {
        DfSendMessage(wnd, DFM_LB_SELECTION, wnd->selection, TRUE);
        DfSendMessage(wnd, DFM_LB_CHOOSE, wnd->selection, 0);
    }
}

/* --------- All Other Key Presses ------------ */
static void KeyPress(DFWINDOW wnd, DF_PARAM p1, DF_PARAM p2)
{
    int sel = wnd->selection+1;
    while (sel < wnd->wlines)    {
        char *cp = DfTextLine(wnd, sel);
        if (cp == NULL)
            break;
#ifdef INCLUDE_EXTENDEDSELECTIONS
        if (DfIsMultiLine(wnd))
            cp++;
#endif
        /* --- special for directory list box --- */
        if (*cp == '[')
            cp++;
        if (tolower(*cp) == (int)p1)    {
            DfSendMessage(wnd, DFM_LB_SELECTION, sel,
                DfIsMultiLine(wnd) ? p2 : FALSE);
            if (!SelectionInWindow(wnd, sel))    {
                wnd->wtop = sel-DfClientHeight(wnd)+1;
                DfSendMessage(wnd, DFM_PAINT, 0, 0);
            }
            break;
        }
        sel++;
    }
}

/* --------- DFM_KEYBOARD Message ------------ */
static int KeyboardMsg(DFWINDOW wnd, DF_PARAM p1, DF_PARAM p2)
{
    switch ((int) p1)    {
#ifdef INCLUDE_EXTENDEDSELECTIONS
        case DF_SHIFT_F8:
            AddModeKey(wnd);
            return TRUE;
#endif
        case DF_UP:
            TestExtended(wnd, p2);
            UpKey(wnd, p2);
            return TRUE;
        case DF_DN:
            TestExtended(wnd, p2);
            DnKey(wnd, p2);
            return TRUE;
        case DF_PGUP:
        case DF_HOME:
            TestExtended(wnd, p2);
            HomePgUpKey(wnd, p1, p2);
            return TRUE;
        case DF_PGDN:
        case DF_END:
            TestExtended(wnd, p2);
            EndPgDnKey(wnd, p1, p2);
            return TRUE;
#ifdef INCLUDE_EXTENDEDSELECTIONS
        case ' ':
            SpacebarKey(wnd, p2);
            break;
#endif
        case '\r':
            EnterKey(wnd);
            return TRUE;
        default:
            KeyPress(wnd, p1, p2);
            break;
    }
    return FALSE;
}

/* ------- DFM_LEFT_BUTTON Message -------- */
static int LeftButtonMsg(DFWINDOW wnd, DF_PARAM p1, DF_PARAM p2)
{
    int my = (int) p2 - DfGetTop(wnd);
    if (my >= wnd->wlines-wnd->wtop)
        my = wnd->wlines - wnd->wtop;

    if (!DfInsideRect(p1, p2, DfClientRect(wnd)))
        return FALSE;
    if (wnd->wlines && my != py)    {
        int sel = wnd->wtop+my-1;
#ifdef INCLUDE_EXTENDEDSELECTIONS
        int sh = DfGetShift();
        if (!(sh & (DF_LEFTSHIFT | DF_RIGHTSHIFT)))    {
            if (!(sh & DF_CTRLKEY))
                ClearAllSelections(wnd);
            wnd->AnchorPoint = sel;
            DfSendMessage(wnd, DFM_PAINT, 0, 0);
        }
#endif
        DfSendMessage(wnd, DFM_LB_SELECTION, sel, TRUE);
        py = my;
    }
    return TRUE;
}

/* ------------- DOUBLE_CLICK Message ------------ */
static int DoubleClickMsg(DFWINDOW wnd, DF_PARAM p1, DF_PARAM p2)
{
    if (DfWindowMoving || DfWindowSizing)
        return FALSE;
    if (wnd->wlines)    {
        DFRECT rc = DfClientRect(wnd);
        DfBaseWndProc(DF_LISTBOX, wnd, DOUBLE_CLICK, p1, p2);
        if (DfInsideRect(p1, p2, rc))
            DfSendMessage(wnd, DFM_LB_CHOOSE, wnd->selection, 0);
    }
    return TRUE;
}

/* ------------ DFM_ADDTEXT Message -------------- */
static int AddTextMsg(DFWINDOW wnd, DF_PARAM p1, DF_PARAM p2)
{
    int rtn = DfBaseWndProc(DF_LISTBOX, wnd, DFM_ADDTEXT, p1, p2);
    if (wnd->selection == -1)
        DfSendMessage(wnd, DFM_LB_SETSELECTION, 0, 0);
#ifdef INCLUDE_EXTENDEDSELECTIONS
    if (*(char *)p1 == DF_LISTSELECTOR)
        wnd->SelectCount++;
#endif
    return rtn;
}

/* --------- DFM_GETTEXT Message ------------ */
static void GetTextMsg(DFWINDOW wnd, DF_PARAM p1, DF_PARAM p2)
{
    if ((int)p2 != -1)    {
        char *cp1 = (char *)p1;
        char *cp2 = DfTextLine(wnd, (int)p2);
        while (cp2 && *cp2 && *cp2 != '\n')
            *cp1++ = *cp2++;
        *cp1 = '\0';
    }
}

/* --------- DF_LISTBOX Window Processing Module ------------ */
int DfListBoxProc(DFWINDOW wnd, DFMESSAGE msg, DF_PARAM p1, DF_PARAM p2)
{
    switch (msg)    {
        case DFM_CREATE_WINDOW:
            DfBaseWndProc(DF_LISTBOX, wnd, msg, p1, p2);
            wnd->selection = -1;
#ifdef INCLUDE_EXTENDEDSELECTIONS
            wnd->AnchorPoint = -1;
#endif
            return TRUE;
        case DFM_KEYBOARD:
            if (DfWindowMoving || DfWindowSizing)
                break;
            if (KeyboardMsg(wnd, p1, p2))
                return TRUE;
            break;
        case DFM_LEFT_BUTTON:
            if (LeftButtonMsg(wnd, p1, p2) == TRUE)
                return TRUE;
            break;
        case DOUBLE_CLICK:
            if (DoubleClickMsg(wnd, p1, p2))
                return TRUE;
            break;
        case DFM_BUTTON_RELEASED:
            if (DfWindowMoving || DfWindowSizing || DfVSliding)
                break;
            py = -1;
            return TRUE;
        case DFM_ADDTEXT:
            return AddTextMsg(wnd, p1, p2);
        case DFM_LB_GETTEXT:
            GetTextMsg(wnd, p1, p2);
            return TRUE;
        case DFM_CLEARTEXT:
            wnd->selection = -1;
#ifdef INCLUDE_EXTENDEDSELECTIONS
            wnd->AnchorPoint = -1;
#endif
            wnd->SelectCount = 0;
            break;
        case DFM_PAINT:
            DfBaseWndProc(DF_LISTBOX, wnd, msg, p1, p2);
            WriteSelection(wnd, wnd->selection, TRUE, (DFRECT *)p1);
            return TRUE;
        case DFM_SCROLL:
        case DFM_HORIZSCROLL:
        case DFM_SCROLLPAGE:
        case DFM_HORIZPAGE:
        case DFM_SCROLLDOC:
            DfBaseWndProc(DF_LISTBOX, wnd, msg, p1, p2);
            WriteSelection(wnd,wnd->selection,TRUE,NULL);
            return TRUE;
        case DFM_LB_CHOOSE:
            DfSendMessage(DfGetParent(wnd), DFM_LB_CHOOSE, p1, p2);
            return TRUE;
        case DFM_LB_SELECTION:
            ChangeSelection(wnd, (int) p1, (int) p2);
            DfSendMessage(DfGetParent(wnd), DFM_LB_SELECTION,
                wnd->selection, 0);
            return TRUE;
        case DFM_LB_CURRENTSELECTION:
            return wnd->selection;
        case DFM_LB_SETSELECTION:
            ChangeSelection(wnd, (int) p1, 0);
            return TRUE;
#ifdef INCLUDE_EXTENDEDSELECTIONS
        case DFM_CLOSE_WINDOW:
            if (DfIsMultiLine(wnd) && wnd->AddMode)    {
                wnd->AddMode = FALSE;
                DfSendMessage(DfGetParent(wnd), DFM_ADDSTATUS, 0, 0);
            }
            break;
#endif
        default:
            break;
    }
    return DfBaseWndProc(DF_LISTBOX, wnd, msg, p1, p2);
}

static BOOL SelectionInWindow(DFWINDOW wnd, int sel)
{
    return (wnd->wlines && sel >= wnd->wtop &&
            sel < wnd->wtop+DfClientHeight(wnd));
}

static void WriteSelection(DFWINDOW wnd, int sel,
                                    int reverse, DFRECT *rc)
{
    if (DfIsVisible(wnd))
        if (SelectionInWindow(wnd, sel))
            DfWriteTextLine(wnd, rc, sel, reverse);
}

#ifdef INCLUDE_EXTENDEDSELECTIONS
/* ----- Test for extended selections in the listbox ----- */
static void TestExtended(DFWINDOW wnd, DF_PARAM p2)
{
    if (DfIsMultiLine(wnd) && !wnd->AddMode &&
            !((int) p2 & (DF_LEFTSHIFT | DF_RIGHTSHIFT)))    {
        if (wnd->SelectCount > 1)    {
            ClearAllSelections(wnd);
            DfSendMessage(wnd, DFM_PAINT, 0, 0);
        }
    }
}

/* ----- Clear selections in the listbox ----- */
static void ClearAllSelections(DFWINDOW wnd)
{
    if (DfIsMultiLine(wnd) && wnd->SelectCount > 0)    {
        int sel;
        for (sel = 0; sel < wnd->wlines; sel++)
            ClearSelection(wnd, sel);
    }
}

/* ----- Invert a selection in the listbox ----- */
static void FlipSelection(DFWINDOW wnd, int sel)
{
    if (DfIsMultiLine(wnd))    {
        if (DfItemSelected(wnd, sel))
            ClearSelection(wnd, sel);
        else
            SetSelection(wnd, sel);
    }
}

static int ExtendSelections(DFWINDOW wnd, int sel, int shift)
{
    if (shift & (DF_LEFTSHIFT | DF_RIGHTSHIFT) &&
                        wnd->AnchorPoint != -1)    {
        int i = sel;
        int j = wnd->AnchorPoint;
        int rtn;
        if (j > i)
            swap(i,j);
        rtn = i - j;
        while (j <= i)
            SetSelection(wnd, j++);
        return rtn;
    }
    return 0;
}

static void SetSelection(DFWINDOW wnd, int sel)
{
    if (DfIsMultiLine(wnd) && !DfItemSelected(wnd, sel))    {
        char *lp = DfTextLine(wnd, sel);
        *lp = DF_LISTSELECTOR;
        wnd->SelectCount++;
    }
}

static void ClearSelection(DFWINDOW wnd, int sel)
{
    if (DfIsMultiLine(wnd) && DfItemSelected(wnd, sel))    {
        char *lp = DfTextLine(wnd, sel);
        *lp = ' ';
        --wnd->SelectCount;
    }
}

BOOL DfItemSelected(DFWINDOW wnd, int sel)
{
	if (sel != -1 && DfIsMultiLine(wnd) && sel < wnd->wlines)    {
        char *cp = DfTextLine(wnd, sel);
        return (int)((*cp) & 255) == DF_LISTSELECTOR;
    }
    return FALSE;
}
#endif

static void ChangeSelection(DFWINDOW wnd,int sel,int shift)
{
    if (sel != wnd->selection)    {
#ifdef INCLUDE_EXTENDEDSELECTIONS
        if (DfIsMultiLine(wnd))        {
            int sels;
            if (!wnd->AddMode)
                ClearAllSelections(wnd);
            sels = ExtendSelections(wnd, sel, shift);
            if (sels > 1)
                DfSendMessage(wnd, DFM_PAINT, 0, 0);
            if (sels == 0 && !wnd->AddMode)    {
                ClearSelection(wnd, wnd->selection);
                SetSelection(wnd, sel);
                wnd->AnchorPoint = sel;
            }
        }
#endif
        WriteSelection(wnd, wnd->selection, FALSE, NULL);
        wnd->selection = sel;
        WriteSelection(wnd, sel, TRUE, NULL);
     }
}

/* EOF */
