/*************************************************************************
**
**    OLE 2 Sample Code
**
**    outldata.c
**
**    This file contains LineList and NameTable functions
**    and related support functions.
**
**    (c) Copyright Microsoft Corp. 1992 - 1993 All Rights Reserved
**
*************************************************************************/


#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP g_lpApp;

OLECHAR ErrMsgListBox[] = OLESTR("Can't create ListBox!");

static int g_iMapMode;

/* LineList_Init
 * -------------
 *
 *      Create and Initialize the LineList (owner-drawn listbox)
 */
BOOL LineList_Init(LPLINELIST lpLL, LPOUTLINEDOC lpOutlineDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;

#if defined( INPLACE_CNTR )
   lpLL->m_hWndListBox = CreateWindow(
               "listbox",              /* Window class name           */
               NULL,                   /* Window's title              */

               /* OLE2NOTE: an in-place contanier MUST use
               **    WS_CLIPCHILDREN window style for the window
               **    that it uses as the parent for the server's
               **    in-place active window so that its
               **    painting does NOT interfere with the painting
               **    of the server's in-place active child window.
               */

               WS_CLIPCHILDREN |
               WS_CHILDWINDOW |
               WS_VISIBLE |
               WS_VSCROLL |
               WS_HSCROLL |
               LBS_EXTENDEDSEL |
               LBS_NOTIFY |
               LBS_OWNERDRAWVARIABLE |
               LBS_NOINTEGRALHEIGHT |
               LBS_USETABSTOPS,
               0, 0,                   /* Use default X, Y            */
               0, 0,                   /* Use default X, Y            */
               lpOutlineDoc->m_hWndDoc,/* Parent window's handle      */
               (HMENU)IDC_LINELIST,    /* Child Window ID             */
               lpOutlineApp->m_hInst,  /* Instance of window          */
               NULL);                  /* Create struct for WM_CREATE */
#else
   lpLL->m_hWndListBox = CreateWindow(
               "listbox",              /* Window class name           */
               NULL,                   /* Window's title              */
               WS_CHILDWINDOW |
               WS_VISIBLE |
               WS_VSCROLL |
               WS_HSCROLL |
               LBS_EXTENDEDSEL |
               LBS_NOTIFY |
               LBS_OWNERDRAWVARIABLE |
               LBS_NOINTEGRALHEIGHT |
               LBS_USETABSTOPS,
               0, 0,                   /* Use default X, Y            */
               0, 0,                   /* Use default X, Y            */
               lpOutlineDoc->m_hWndDoc,/* Parent window's handle      */
               (HMENU)IDC_LINELIST,    /* Child Window ID             */
               lpOutlineApp->m_hInst,  /* Instance of window          */
               NULL);                  /* Create struct for WM_CREATE */

#endif


   if(! lpLL->m_hWndListBox) {
      OutlineApp_ErrorMessage(g_lpApp, ErrMsgListBox);
      return FALSE;
   }

   lpOutlineApp->m_ListBoxWndProc =
         (FARPROC) GetWindowLong ( lpLL->m_hWndListBox, GWL_WNDPROC );
   SetWindowLong (lpLL->m_hWndListBox, GWL_WNDPROC, (LONG) LineListWndProc);

#if defined ( USE_DRAGDROP )
   /* m_iDragOverLine saves index of line that has drag/drop target
   **    feedback. we currently use our focus rectangle feedback for
   **    this. it would be better to have a different visual feedback
   **    for potential target of the pending drop.
   */
   lpLL->m_iDragOverLine = -1;
#endif

   lpLL->m_nNumLines = 0;
   lpLL->m_nMaxLineWidthInHimetric = 0;
   lpLL->m_lpDoc = lpOutlineDoc;
   _fmemset(&lpLL->m_lrSaveSel, 0, sizeof(LINERANGE));

   return TRUE;
}


/* LineList_Destroy
 * ----------------
 *
 *      Clear (delete) all Line objects from the list and free supporting
 *      memory (ListBox Window) used by the LineList object itself.
 */
void LineList_Destroy(LPLINELIST lpLL)
{
   int i;
   int linesTotal = lpLL->m_nNumLines;

   // Delete all Line objects
   for (i = 0; i < linesTotal; i++)
      LineList_DeleteLine(lpLL, 0);   // NOTE: always delete line 0

   // Remove all Lines from the ListBox
   SendMessage(lpLL->m_hWndListBox,LB_RESETCONTENT,0,0L);

   lpLL->m_nNumLines=0;
   DestroyWindow(lpLL->m_hWndListBox);
   lpLL->m_hWndListBox = NULL;
}


/* LineList_AddLine
 * ----------------
 *
 *      Add one line to the list box. The line is added following the
 * line with index "nIndex". If nIndex is larger than the number of lines
 * in the ListBox, then the line is appended to the end. The selection
 * is set to the newly added line.
 */
void LineList_AddLine(LPLINELIST lpLL, LPLINE lpLine, int nIndex)
{
   int nAddIndex = (lpLL->m_nNumLines == 0 ?
         0 :
         (nIndex >= lpLL->m_nNumLines ? lpLL->m_nNumLines : nIndex+1));
   LINERANGE lrSel;

#if defined( USE_HEADING )
   int nHeight = Line_GetHeightInHimetric(lpLine);

   nHeight = XformHeightInHimetricToPixels(NULL, nHeight);

   // Add a dummy string to the row heading
   Heading_RH_SendMessage(OutlineDoc_GetHeading(lpLL->m_lpDoc),
         LB_INSERTSTRING, (WPARAM)nAddIndex, MAKELPARAM(nHeight, 0));
#endif


   lrSel.m_nStartLine = nAddIndex;
   lrSel.m_nEndLine =   nAddIndex;

   if (!lpLine) {
      OutlineApp_ErrorMessage(g_lpApp, OLESTR("Could not create line."));
      return;
   }

   SendMessage(lpLL->m_hWndListBox, LB_INSERTSTRING, (WPARAM)nAddIndex,
         (DWORD)lpLine);

   LineList_SetMaxLineWidthInHimetric(
         lpLL,
         Line_GetTotalWidthInHimetric(lpLine)
   );

   lpLL->m_nNumLines++;

   LineList_SetSel(lpLL, &lrSel);
}


/* LineList_DeleteLine
 * -------------------
 *
 *      Delete one line from listbox and memory
 */
void LineList_DeleteLine(LPLINELIST lpLL, int nIndex)
{
   LPLINE lpLine = LineList_GetLine(lpLL, nIndex);
   BOOL fResetSel;

   fResetSel = (BOOL)SendMessage(lpLL->m_hWndListBox, LB_GETSEL, (WPARAM)nIndex, 0L);

   if (lpLine)
      Line_Delete(lpLine);    // free memory of Line

   // Remove the Line from the ListBox
   SendMessage(lpLL->m_hWndListBox, LB_DELETESTRING, (WPARAM)nIndex, 0L);
   lpLL->m_nNumLines--;

   if (fResetSel) {
      if (nIndex > 0) {
#if defined( WIN32 )
         SendMessage(
               lpLL->m_hWndListBox,
               LB_SETSEL,
               (WPARAM)TRUE,
               (LPARAM)nIndex-1
         );
#else
         SendMessage(
               lpLL->m_hWndListBox,
               LB_SETSEL,
               (WPARAM)TRUE,
               MAKELPARAM(nIndex-1,0)
         );
#endif
      } else {
         if (lpLL->m_nNumLines > 0) {
#if defined( WIN32 )
            SendMessage(
                  lpLL->m_hWndListBox,
                  LB_SETSEL,
                  (WPARAM)TRUE,
                  (LPARAM)0
            );
#else
            SendMessage(
                  lpLL->m_hWndListBox,
                  LB_SETSEL,
                  (WPARAM)TRUE,
                  MAKELPARAM(0,0)
            );
#endif
         }
      }
   }

#if defined( USE_HEADING )
   // Remove the dummy string from the row heading
   Heading_RH_SendMessage(OutlineDoc_GetHeading(lpLL->m_lpDoc),
         LB_DELETESTRING, (WPARAM)nIndex, 0L);
#endif

}


/* LineList_ReplaceLine
 * --------------------
 *
 *      Replace the line at a given index in the list box with a new
 * line.
 */
void LineList_ReplaceLine(LPLINELIST lpLL, LPLINE lpLine, int nIndex)
{
   LPLINE lpOldLine = LineList_GetLine(lpLL, nIndex);

   if (lpOldLine)
      Line_Delete(lpOldLine);    // free memory of Line
   else
      return;     // if no previous line then invalid index

   SendMessage(
         lpLL->m_hWndListBox,
         LB_SETITEMDATA,
         (WPARAM)nIndex,
         (LPARAM)lpLine
   );
}


/* LineList_GetLineIndex
 * ---------------------
 *
 *      Return the index of the Line given a pointer to the line.
 *      Return -1 if the line is not found.
 */
int LineList_GetLineIndex(LPLINELIST lpLL, LPLINE lpLine)
{
   LRESULT lRet;

   if (! lpLine) return -1;

   lRet = SendMessage(
         lpLL->m_hWndListBox,
         LB_FINDSTRING,
         (WPARAM)-1,
         (LPARAM)(LPCSTR)lpLine
      );

   return ((lRet == LB_ERR) ? -1 : (int)lRet);
}


/* LineList_GetLine
 * ----------------
 *
 *      Retrieve the pointer to the Line given its index in the LineList
 */
LPLINE LineList_GetLine(LPLINELIST lpLL, int nIndex)
{
   DWORD dWord;
   LRESULT lRet;

   if (lpLL->m_nNumLines == 0 || nIndex > lpLL->m_nNumLines || nIndex < 0)
      return NULL;

   lRet = SendMessage(
         lpLL->m_hWndListBox,LB_GETTEXT,nIndex,(LPARAM)(LPCSTR)&dWord);

   return ((lRet == LB_ERR || lRet == 0) ? NULL : (LPLINE)dWord);
}


/* LineList_SetFocusLine
 * ---------------------
 *
 */

void LineList_SetFocusLine ( LPLINELIST lpLL, WORD wIndex )
{

   SendMessage(lpLL->m_hWndListBox, LB_SETCARETINDEX, (WPARAM)wIndex, 0L );

}


/* LineList_GetLineRect
 * --------------------
 *
 * Retrieve the rectangle of a Line given its index in the LineList
 */
BOOL LineList_GetLineRect(LPLINELIST lpLL, int nIndex, LPRECT lpRect)
{
   DWORD iReturn = (DWORD)LB_ERR;

   if ( !(lpLL->m_nNumLines == 0 || nIndex > lpLL->m_nNumLines || nIndex < 0) )
      iReturn = SendMessage(lpLL->m_hWndListBox,LB_GETITEMRECT,nIndex,(LPARAM)lpRect);

   return (iReturn == LB_ERR ? FALSE : TRUE );
}


/* LineList_GetFocusLineIndex
 * --------------------------
 *
 * Get the index of the line that currently has focus (the active line).
 */
int LineList_GetFocusLineIndex(LPLINELIST lpLL)
{
   return (int)SendMessage(lpLL->m_hWndListBox,LB_GETCARETINDEX,0,0L);
}


/* LineList_GetCount
 * -----------------
 *
 *      Return number of line objects
 */
int LineList_GetCount(LPLINELIST lpLL)
{
   if (lpLL)
      return lpLL->m_nNumLines;
   else {
      OleDbgAssert(lpLL!=NULL);
      return 0;
   }
}


/* LineList_SetMaxLineWidthInHimetric
 * ----------------------------------
 *
 *  Adjust the maximum line width for the listbox. The max line width is
 *  used to determine if a horizontal scroll bar is needed.
 *
 *  Parameters:
 *      nWidthInHimetric - if +ve, width of an additional line
 *                       - if -ve, reset Max to be the value
 *
 *  Returns:
 *      TRUE is max line width of LineList changed
 *      FALSE if no change
 */
BOOL LineList_SetMaxLineWidthInHimetric(LPLINELIST lpLL, int nWidthInHimetric)
{
   int nWidthInPix;
   BOOL fSizeChanged = FALSE;
   LPSCALEFACTOR lpscale;

   if (!lpLL)
      return FALSE;

   lpscale = OutlineDoc_GetScaleFactor(lpLL->m_lpDoc);

   if (nWidthInHimetric < 0) {
      lpLL->m_nMaxLineWidthInHimetric = -1;
      nWidthInHimetric *= -1;
   }

   if (nWidthInHimetric > lpLL->m_nMaxLineWidthInHimetric) {
      lpLL->m_nMaxLineWidthInHimetric = nWidthInHimetric;
      nWidthInPix = XformWidthInHimetricToPixels(NULL, nWidthInHimetric +
            LOWORD(OutlineDoc_GetMargin(lpLL->m_lpDoc)) +
            HIWORD(OutlineDoc_GetMargin(lpLL->m_lpDoc)));

      nWidthInPix = (int)(nWidthInPix * lpscale->dwSxN / lpscale->dwSxD);
      SendMessage(
            lpLL->m_hWndListBox,
            LB_SETHORIZONTALEXTENT,
            nWidthInPix,
            0L
      );
      fSizeChanged = TRUE;

#if defined( USE_HEADING )
      Heading_CH_SetHorizontalExtent(
            OutlineDoc_GetHeading(lpLL->m_lpDoc), lpLL->m_hWndListBox);
#endif

   }
   return fSizeChanged;
}


/* LineList_GetMaxLineWidthInHimetric
 * ----------------------------------
 *
 *      Return the width of the widest line
 */
int LineList_GetMaxLineWidthInHimetric(LPLINELIST lpLL)
{
   return lpLL->m_nMaxLineWidthInHimetric;
}


/* LineList_RecalcMaxLineWidthInHimetric
 * -------------------------------------
 *
 *  Recalculate the maximum line width in the entire list.
 *
 *  Parameters:
 *      nWidthInHimetric should be set to the width of line being removed.
 *      nWidthInHimetric == 0 forces list to recalculate in all cases.
 *      nWidthInHimetric == current max width => forces recalc.
 *
 *  Returns:
 *      TRUE is max line width of LineList changed
 *      FALSE if no change
 */
BOOL LineList_RecalcMaxLineWidthInHimetric(
      LPLINELIST          lpLL,
   int                 nWidthInHimetric
)
{
   int i;
   LPLINE lpLine;
   BOOL fSizeChanged = FALSE;
   int nOrgMaxLineWidthInHimetric = lpLL->m_nMaxLineWidthInHimetric;

   if (nWidthInHimetric == 0 ||
      nWidthInHimetric == lpLL->m_nMaxLineWidthInHimetric) {

      lpLL->m_nMaxLineWidthInHimetric = -1;

      LineList_SetMaxLineWidthInHimetric(lpLL, 0);

      for(i = 0; i < lpLL->m_nNumLines; i++) {
         lpLine=LineList_GetLine(lpLL, i);
         LineList_SetMaxLineWidthInHimetric(
               lpLL,
               Line_GetTotalWidthInHimetric(lpLine)
         );
      }
   }

   if (nOrgMaxLineWidthInHimetric != lpLL->m_nMaxLineWidthInHimetric)
      fSizeChanged = TRUE;

   return fSizeChanged;
}


/* LineList_CalcSelExtentInHimetric
 * --------------------------------
 *
 *      Calculate the extents (widht and height) of a selection of lines.
 *
 * if lplrSel == NULL, calculate extent of all lines.
 */
void LineList_CalcSelExtentInHimetric(
      LPLINELIST          lpLL,
      LPLINERANGE         lplrSel,
      LPSIZEL             lpsizel
)
{
   int i;
   int nEndLine;
   int nStartLine;
   LPLINE lpLine;
   long lWidth;

   if (lplrSel) {
      nEndLine = lplrSel->m_nEndLine;
      nStartLine = lplrSel->m_nStartLine;
   } else {
      nEndLine = LineList_GetCount(lpLL) - 1;
      nStartLine = 0;
   }

   lpsizel->cx = 0;
   lpsizel->cy = 0;

   for(i = nStartLine; i <= nEndLine; i++) {
      lpLine=LineList_GetLine(lpLL,i);
      if (lpLine) {
         lWidth = (long)Line_GetTotalWidthInHimetric(lpLine);
         lpsizel->cx = max(lpsizel->cx, lWidth);
         lpsizel->cy += lpLine->m_nHeightInHimetric;
      }
   }
}


/* LineList_GetWindow
 * ------------------
 *
 * Return handle of list box
 */
HWND LineList_GetWindow(LPLINELIST lpLL)
{
   return lpLL->m_hWndListBox;
}


/* LineList_GetDC
 * --------------
 *
 * Return DC handle of list box
 */
HDC LineList_GetDC(LPLINELIST lpLL)
{
   HFONT hfontOld;
   HDC hDC = GetDC(lpLL->m_hWndListBox);
   int     iXppli;     //* pixels per logical inch along width
   int     iYppli;     //* pixels per logical inch along height
   SIZE    size;

   // Setup a mapping mode for the DC which maps physical pixel
   // coordinates to HIMETRIC units. The standard MM_HIMETRIC mapping
   // mode does not work correctly because it does not take into
   // account that a logical inch on the display screen is drawn
   // physically larger than 1 inch. We will setup an anisotropic
   // mapping mode which will perform the transformation properly.

   g_iMapMode = SetMapMode(hDC, MM_ANISOTROPIC);
   iXppli = GetDeviceCaps (hDC, LOGPIXELSX);
   iYppli = GetDeviceCaps (hDC, LOGPIXELSY);
   SetViewportExtEx(hDC, iXppli, iYppli, &size);
   SetWindowExtEx(hDC, HIMETRIC_PER_INCH, HIMETRIC_PER_INCH, &size);

   // Set the default font size, and font face name
   hfontOld = SelectObject(hDC, OutlineApp_GetActiveFont(g_lpApp));

   return hDC;
}


/* LineList_ReleaseDC
 * ------------------
 *
 *      Release DC of list box returned from previous LineList_GetDC call.
 */
void LineList_ReleaseDC(LPLINELIST lpLL, HDC hDC)
{
   SetMapMode(hDC, g_iMapMode);
   ReleaseDC(lpLL->m_hWndListBox, hDC);
}


/* LineList_SetLineHeight
 * ----------------------
 *
 *      Set the height of a line in the LineList list box
 */
void LineList_SetLineHeight(LPLINELIST lpLL,int nIndex,int nHeightInHimetric)
{
   LPARAM          lParam;
   LPOUTLINEDOC    lpDoc;
   LPSCALEFACTOR   lpscale;
   UINT            uHeightInPix;
   LPHEADING       lphead;

   if (!lpLL)
      return;

   lpDoc = lpLL->m_lpDoc;
   lphead = OutlineDoc_GetHeading(lpDoc);
   lpscale = OutlineDoc_GetScaleFactor(lpDoc);

   uHeightInPix = XformHeightInHimetricToPixels(NULL, nHeightInHimetric);

   Heading_RH_SendMessage(lphead, LB_SETITEMDATA, (WPARAM)nIndex,
         MAKELPARAM(uHeightInPix, 0));

   uHeightInPix = (UINT)(uHeightInPix * lpscale->dwSyN / lpscale->dwSyD);

   if (uHeightInPix > LISTBOX_HEIGHT_LIMIT)
      uHeightInPix = LISTBOX_HEIGHT_LIMIT;


   lParam = MAKELPARAM(uHeightInPix, 0);
   SendMessage(lpLL->m_hWndListBox,LB_SETITEMHEIGHT,(WPARAM)nIndex, lParam);
   Heading_RH_SendMessage(lphead, LB_SETITEMHEIGHT, (WPARAM)nIndex, lParam);
   Heading_RH_ForceRedraw(lphead, TRUE);
}


/* LineList_ReScale
 * ----------------
 *
 *      Re-scale the LineList list box
 */
void LineList_ReScale(LPLINELIST lpLL, LPSCALEFACTOR lpscale)
{
   int nIndex;
   LPLINE lpLine;
   UINT uWidthInHim;

   if (!lpLL)
      return;

   for (nIndex = 0; nIndex < lpLL->m_nNumLines; nIndex++) {
      lpLine = LineList_GetLine(lpLL, nIndex);
      if (lpLine) {
         LineList_SetLineHeight(
               lpLL,
               nIndex,
               Line_GetHeightInHimetric(lpLine)
         );
      }
   }

   uWidthInHim = LineList_GetMaxLineWidthInHimetric(lpLL);
   LineList_SetMaxLineWidthInHimetric(lpLL, -(int)uWidthInHim);
}

/* LineList_SetSel
 * ---------------
 *
 *      Set the selection in list box
 */
void LineList_SetSel(LPLINELIST lpLL, LPLINERANGE lplrSel)
{
   DWORD dwSel;

   if (lpLL->m_nNumLines <= 0 || lplrSel->m_nStartLine < 0)
      return;     // no lines in list; can't set a selection

   dwSel = MAKELPARAM(lplrSel->m_nStartLine, lplrSel->m_nEndLine);

   lpLL->m_lrSaveSel = *lplrSel;

   /* remove previous selection */
#if defined( WIN32 )
   SendMessage(
         lpLL->m_hWndListBox,
         LB_SETSEL,
         (WPARAM)FALSE,
         (LPARAM)-1
   );
#else
   SendMessage(
         lpLL->m_hWndListBox,
         LB_SETSEL,
         (WPARAM)FALSE,
         MAKELPARAM(-1,0)
   );
#endif

   /* mark selection */
   SendMessage(lpLL->m_hWndListBox,LB_SELITEMRANGE, (WPARAM)TRUE, (LPARAM)dwSel);
   /* set focus line (caret) */
   LineList_SetFocusLine ( lpLL, (WORD)lplrSel->m_nStartLine );

}


/* LineList_GetSel
 * ---------------
 *
 * Get the selection in list box.
 *
 * Returns the count of items selected
 */
int LineList_GetSel(LPLINELIST lpLL, LPLINERANGE lplrSel)
{
   int nNumSel=(int)SendMessage(lpLL->m_hWndListBox,LB_GETSELCOUNT,0,0L);

   if (nNumSel) {
      SendMessage(lpLL->m_hWndListBox,LB_GETSELITEMS,
         (WPARAM)1,(LPARAM)(int FAR*)&(lplrSel->m_nStartLine));
      lplrSel->m_nEndLine = lplrSel->m_nStartLine + nNumSel - 1;
   } else {
      _fmemset(lplrSel, 0, sizeof(LINERANGE));
   }
   return nNumSel;
}


/* LineList_RemoveSel
 * ------------------
 *
 * Remove the selection in list box but save the selection state so that
 * it can be restored by calling LineList_RestoreSel
 * LineList_RemoveSel is called when the LineList window looses focus.
 */
void LineList_RemoveSel(LPLINELIST lpLL)
{
   LINERANGE lrSel;
   if (LineList_GetSel(lpLL, &lrSel) > 0) {
      lpLL->m_lrSaveSel = lrSel;
#if defined( WIN32 )
      SendMessage(
            lpLL->m_hWndListBox,
            LB_SETSEL,
            (WPARAM)FALSE,
            (LPARAM)-1
      );
#else
      SendMessage(
            lpLL->m_hWndListBox,
            LB_SETSEL,
            (WPARAM)FALSE,
            MAKELPARAM(-1,0)
      );
#endif
   }
}


/* LineList_RestoreSel
 * ------------------
 *
 * Restore the selection in list box that was previously saved by a call to
 * LineList_RemoveSel.
 * LineList_RestoreSel is called when the LineList window gains focus.
 */
void LineList_RestoreSel(LPLINELIST lpLL)
{
   LineList_SetSel(lpLL, &lpLL->m_lrSaveSel);
}


/* LineList_SetRedraw
 * ------------------
 *
 *      Enable/Disable the redraw of the linelist (listbox) on screen
 *
 *  fEnbaleDraw = TRUE      - enable redraw
 *                FALSE     - disable redraw
 */
void LineList_SetRedraw(LPLINELIST lpLL, BOOL fEnableDraw)
{
   SendMessage(lpLL->m_hWndListBox,WM_SETREDRAW,(WPARAM)fEnableDraw,0L);
}


/* LineList_ForceRedraw
 * --------------------
 *
 *      Force redraw of the linelist (listbox) on screen
 */
void LineList_ForceRedraw(LPLINELIST lpLL, BOOL fErase)
{
   InvalidateRect(lpLL->m_hWndListBox, NULL, fErase);
}


/* LineList_ForceLineRedraw
 * ------------------------
 *
 *      Force a particular line of the linelist (listbox) to redraw.
 */
void LineList_ForceLineRedraw(LPLINELIST lpLL, int nIndex, BOOL fErase)
{
   RECT   rect;

   LineList_GetLineRect( lpLL, nIndex, (LPRECT)&rect );
   InvalidateRect( lpLL->m_hWndListBox, (LPRECT)&rect, fErase );
}


/* LineList_ScrollLineIntoView
 * ---------------------------
 *  Make sure that the specified line is in view; if necessary scroll
 *      the listbox. if any portion of the line is visible, then no
 *      scrolling will occur.
 */
void LineList_ScrollLineIntoView(LPLINELIST lpLL, int nIndex)
{
   RECT rcWindow;
   RECT rcLine;
   RECT rcInt;

   if ( lpLL->m_nNumLines == 0 )
      return;

   if (! LineList_GetLineRect( lpLL, nIndex, (LPRECT)&rcLine ) )
      return;

   GetClientRect( lpLL->m_hWndListBox, (LPRECT) &rcWindow );

   if (! IntersectRect((LPRECT)&rcInt, (LPRECT)&rcWindow, (LPRECT)&rcLine))
      SendMessage(
            lpLL->m_hWndListBox,
            LB_SETTOPINDEX,
            (WPARAM)nIndex,
            (LPARAM)NULL
      );
}


/* LineList_CopySelToDoc
 * ---------------------
 *
 *      Copy the selection of the linelist to another document
 *
 *  RETURNS: number of lines copied.
 */
int LineList_CopySelToDoc(
      LPLINELIST              lpSrcLL,
      LPLINERANGE             lplrSel,
      LPOUTLINEDOC            lpDestDoc
)
{
   int             nEndLine;
   int             nStartLine;
   LPLINELIST      lpDestLL = &lpDestDoc->m_LineList;
   signed short    nDestIndex = LineList_GetFocusLineIndex(lpDestLL);
   LPLINE          lpSrcLine;
   int             nCopied = 0;
   int             i;

   if (lplrSel) {
      nEndLine = lplrSel->m_nEndLine;
      nStartLine = lplrSel->m_nStartLine;
   } else {
      nEndLine = LineList_GetCount(lpSrcLL) - 1;
      nStartLine = 0;
   }

   for(i = nStartLine; i <= nEndLine; i++) {
      lpSrcLine = LineList_GetLine(lpSrcLL, i);
      if (lpSrcLine && Line_CopyToDoc(lpSrcLine, lpDestDoc, nDestIndex)) {
         nDestIndex++;
         nCopied++;
      }
   }

   return nCopied;
}


/* LineList_SaveSelToStg
 * ---------------------
 *
 *      Save lines in selection into lpDestStg.
 *
 *      Return TRUE if ok, FALSE if error
 */
BOOL LineList_SaveSelToStg(
      LPLINELIST              lpLL,
      LPLINERANGE             lplrSel,
      UINT                    uFormat,
      LPSTORAGE               lpSrcStg,
      LPSTORAGE               lpDestStg,
      LPSTREAM                lpLLStm,
      BOOL                    fRemember
)
{
   int nEndLine;
   int nStartLine;
   int nNumLinesWritten = 0;
   HRESULT hrErr = NOERROR;
   ULONG nWritten;
   LPLINE lpLine;
   LINELISTHEADER_ONDISK llhRecord;
   int i;
   LARGE_INTEGER dlibSaveHeaderPos;
   LARGE_INTEGER dlibZeroOffset;
   LISet32( dlibZeroOffset, 0 );

   if (lplrSel) {
      nEndLine = lplrSel->m_nEndLine;
      nStartLine = lplrSel->m_nStartLine;
   } else {
      nEndLine = LineList_GetCount(lpLL) - 1;
      nStartLine = 0;
   }

   _fmemset(&llhRecord,0,sizeof(llhRecord));

   /* save seek position for LineList header record */
   hrErr = lpLLStm->lpVtbl->Seek(
         lpLLStm,
         dlibZeroOffset,
         STREAM_SEEK_CUR,
         (ULARGE_INTEGER FAR*)&dlibSaveHeaderPos
   );
   if (hrErr != NOERROR) goto error;

   /* write LineList header record */
   hrErr = lpLLStm->lpVtbl->Write(
         lpLLStm,
         (LPVOID)&llhRecord,
         sizeof(llhRecord),
         &nWritten
    );
   if (hrErr != NOERROR) {
      OleDbgOutHResult("Write LineList header returned", hrErr);
      goto error;
    }

   for(i = nStartLine; i <= nEndLine; i++) {
      lpLine = LineList_GetLine(lpLL, i);
      if(lpLine &&
         Line_SaveToStg(lpLine, uFormat, lpSrcStg, lpDestStg, lpLLStm,
                                                fRemember))
         llhRecord.m_nNumLines++;
   }

   /* retore seek position for LineList header record */
   hrErr = lpLLStm->lpVtbl->Seek(
         lpLLStm,
         dlibSaveHeaderPos,
         STREAM_SEEK_SET,
         NULL
   );
   if (hrErr != NOERROR) goto error;

   /* write LineList header record */
   hrErr = lpLLStm->lpVtbl->Write(
         lpLLStm,
         (LPVOID)&llhRecord,
         sizeof(llhRecord),
         &nWritten
   );
   if (hrErr != NOERROR) goto error;

   /* reset seek position to end of stream */
   hrErr = lpLLStm->lpVtbl->Seek(
         lpLLStm,
         dlibZeroOffset,
         STREAM_SEEK_END,
         NULL
   );
   if (hrErr != NOERROR) goto error;

   return TRUE;

error:
#if defined( _DEBUG )
   OleDbgAssertSz(
         hrErr == NOERROR,
         "Could not write LineList header to LineList stream"
   );
#endif
   return FALSE;
}


/* LineList_LoadFromStg
 * --------------------
 *
 *      Load lines into linelist from storage.
 *
 *      Return TRUE if ok, FALSE if error
 */
BOOL LineList_LoadFromStg(
      LPLINELIST              lpLL,
      LPSTORAGE               lpSrcStg,
      LPSTREAM                lpLLStm
)
{
   HRESULT hrErr;
   ULONG nRead;
   LPLINE lpLine;
   int i;
   int nNumLines;
   LINELISTHEADER_ONDISK llineRecord;

   /* write LineList header record */
   hrErr = lpLLStm->lpVtbl->Read(
         lpLLStm,
         (LPVOID)&llineRecord,
         sizeof(llineRecord),
         &nRead
   );

   if (hrErr != NOERROR) {
      OleDbgOutHResult("Read LineList header returned", hrErr);
      goto error;
    }

   nNumLines = (int) llineRecord.m_nNumLines;

   for(i = 0; i < nNumLines; i++) {
      lpLine = Line_LoadFromStg(lpSrcStg, lpLLStm, lpLL->m_lpDoc);
      if (! lpLine)
         goto error;

      // Directly add lines to LineList without trying to update a NameTbl
      LineList_AddLine(lpLL, lpLine, i-1);
   }

   return TRUE;

error:
   // Delete any Line objects that were created
   if (lpLL->m_nNumLines > 0) {
      int nNumLines = lpLL->m_nNumLines;
      for (i = 0; i < nNumLines; i++)
         LineList_DeleteLine(lpLL, i);
   }

   return FALSE;
}


#if defined( USE_DRAGDROP )


/* LineList_SetFocusLineFromPointl
 * -------------------------------
 *
 */

void LineList_SetFocusLineFromPointl( LPLINELIST lpLL, POINTL pointl )
{
   int i = LineList_GetLineIndexFromPointl( lpLL, pointl );

   if ( i == (int)-1)
      return ;
   else
      LineList_SetFocusLine( lpLL, (WORD)i );
}


/* LineList_SetDragOverLineFromPointl
 * ----------------------------------
 *
 */

void LineList_SetDragOverLineFromPointl ( LPLINELIST lpLL, POINTL pointl )
{
   int    nIndex = LineList_GetLineIndexFromPointl( lpLL, pointl );
   LPLINE lpline = LineList_GetLine( lpLL, nIndex );

   if (!lpline)
      return;

   if (! lpline->m_fDragOverLine) {
      /* user has dragged over a new line. force new drop target line
      **    to repaint so that drop feedback will be drawn.
      */
      lpline->m_fDragOverLine = TRUE;
      LineList_ForceLineRedraw( lpLL, nIndex, TRUE /*fErase*/);

      if (lpLL->m_iDragOverLine!= -1 && lpLL->m_iDragOverLine!=nIndex) {

         /* force previous drop target line to repaint so that drop
         **    feedback will be undrawn
         */
         lpline = LineList_GetLine( lpLL, lpLL->m_iDragOverLine );
         if (lpline)
            lpline->m_fDragOverLine = FALSE;

         LineList_ForceLineRedraw(
               lpLL,lpLL->m_iDragOverLine,TRUE /*fErase*/);
      }

      lpLL->m_iDragOverLine = nIndex;

      // Force repaint immediately
      UpdateWindow(lpLL->m_hWndListBox);
   }
}


/* LineList_Scroll
 * ---------------
 *
 * Scroll the LineList list box in the desired direction by one line.
 *
 *      this function is called during a drag operation.
 */

void LineList_Scroll(LPLINELIST lpLL, DWORD dwScrollDir)
{
   switch (dwScrollDir) {
      case SCROLLDIR_UP:
         SendMessage( lpLL->m_hWndListBox, WM_VSCROLL, SB_LINEUP, 0L );
         break;

      case SCROLLDIR_DOWN:
         SendMessage( lpLL->m_hWndListBox, WM_VSCROLL, SB_LINEDOWN, 0L );
         break;
   }
}


/* LineList_GetLineIndexFromPointl
 * -------------------------------
 *   do hit test to get index of line corresponding to pointl
 */
int LineList_GetLineIndexFromPointl(LPLINELIST lpLL, POINTL pointl)
{
   RECT  rect;
   POINT point;
   DWORD i;

   point.x = (int)pointl.x;
   point.y = (int)pointl.y;

   ScreenToClient( lpLL->m_hWndListBox, &point);

   if ( lpLL->m_nNumLines == 0 )
      return -1;

   GetClientRect( lpLL->m_hWndListBox, (LPRECT) &rect );

   i = SendMessage( lpLL->m_hWndListBox, LB_GETTOPINDEX, (WPARAM)NULL, (LPARAM)NULL );

   for ( ;; i++){

      RECT rectItem;

      if (!LineList_GetLineRect( lpLL, (int)i, (LPRECT)&rectItem ) )
         return -1;

      if ( rectItem.top > rect.bottom )
         return -1;

      if ( rectItem.top <= point.y && point.y <= rectItem.bottom)
         return (int)i;

   }

}


/* LineList_RestoreDragFeedback
 * ----------------------------
 *
 * Retore the index of the line that currently has focus (the active line).
 */
void LineList_RestoreDragFeedback(LPLINELIST lpLL)
{
   LPLINE lpLine;

   if (lpLL->m_iDragOverLine < 0 )
      return;

   lpLine = LineList_GetLine( lpLL, lpLL->m_iDragOverLine);

   if (lpLine) {

      lpLine->m_fDragOverLine = FALSE;
      LineList_ForceLineRedraw( lpLL,lpLL->m_iDragOverLine,TRUE /*fErase*/);

      // Force repaint immediately
      UpdateWindow(lpLL->m_hWndListBox);
   }

   lpLL->m_iDragOverLine = -1;

}

#endif
