//===============================================================
// vtextcnv.cpp - TextCanvasPane, terminal like Text Drawing Canvas
//
// Copyright (C) 1995,1996,1997,1998  Bruce E. Wampler
//
// This file is part of the V C++ GUI Framework, and is covered
// under the terms of the GNU Library General Public License,
// Version 2. This library has NO WARRANTY. See the source file
// vapp.cxx for more complete information about license terms.
//===============================================================
#include <v/vos2.h>		// for OS/2 stuff
#include <v/vtextcnv.h>

// this is used to track what window, if any, has the cursor
// at any given time
static HWND caretWin = 0;

//================>>> vTextCanvasPane::vTextCanvasPane <<<====================
  vTextCanvasPane::vTextCanvasPane() : vCanvasPane(P_TextCanvas)
  {
    SysDebug(Constructor, "vTextCanvasPane::vTextCanvasPane() constructor \n")

    screen = new dChar[MAX_ROWS*MAX_COLS];
    rowsAllocated = MAX_ROWS;
    colsAllocated = MAX_COLS;

    for (int row = 0 ; row < MAX_ROWS ; ++row)
      for (int col = 0 ; col < MAX_COLS ; ++col)
      {
        ScrChar(row,col) = 0;
        ScrAttr(row,col) = 0;
      }

    cHeight = 0; cWidth = 0;
    curRow = 0; curCol = 0;

    maxRows = -1; maxCols = -1;		// We don't know this yet
    _cursorSet = 0;
  }

//================>>> vTextCanvasPane::initialize <<<=========================
  void vTextCanvasPane::initialize(vWindow* pWindow, HWND pWidget)
  {
    vCanvasPane::initialize(pWindow,pWidget);
    SysDebug1(Constructor,
      "vTextCanvasPane::initialize() (_drawCanvas=%u)\n", _drawCanvas)
    caretOn = 0;
  }

//================>>> vTextCanvasPane::vTextCanvasPane <<<====================
  vTextCanvasPane::vTextCanvasPane(const vTextCanvasPane& t)
  {
    vSysError("vTextCanvasPane - V semantics do not support copy constructors!");
  }

//================>>> vTextCanvasPane::~vTextCanvasPane <<<====================
  vTextCanvasPane::~vTextCanvasPane()
  {
    SysDebug(Destructor,"vTextCanvasPane::~vTextCanvasPane() desstructor\n")
    WinShowCursor (_drawCanvas, FALSE);
    WinDestroyCursor (_drawCanvas);
    caretOn = 0;
//    SysDebug1(OS2Dev,"                  DestroyCursor (hWnd=%u)\n", _drawCanvas)
    delete [] screen;
  }

//================>>> vTextCanvasPane::SetBackground <<<====================
  void vTextCanvasPane::SetBackground(vColor& color)
  {
    DrawTextCursor(-curRow, -curCol);  // turn the cursor off
    vCanvasPane::SetBackground(color);   // set the background color
    GotoRC(curRow, curCol);        // reset the cursor
  }

//======================>>> vTextCanvasPane::Clear <<<======================
  void vTextCanvasPane::Clear(void)
  {
    // Clear screen - including memory contents
    SysDebug(Text,"vTextCanvasPane::Clear\n")
    for (int row = 0 ; row < rowsAllocated ; ++row)
      for (int col = 0 ; col < colsAllocated ; ++col)
      {
        ScrChar(row,col) = 0 ; ScrAttr(row,col) = ChNormal;
      }
    DrawTextCursor(-1,-1);  // turn the cursor off
    vCanvasPane::Clear();   // wipe the canvas clean
    GotoRC(0,0);            // Go to home Row & Column
  }

//=================>>> vTextCanvasPane::ClearRow <<<======================
  void vTextCanvasPane::ClearRow(const int r, const int c)
  {
    // Clear screen row - including the text memory contents
    // leaves cursor wherevever it was and puts blanks
    // before cursor from the left margin
    SysDebug2(Text,"vTextCanvasPane::ClearRow(r:%d, c: %d)\n",r,c)

    GotoRC(r,c);            // update cursor for sure

    // Clearing implies that the previous part of the row should
    // have valid characters, so change them to blanks if now 0
    for (int cc = 0 ; cc < c ; ++cc)
    {
      if (ScrChar(r,cc) == 0)
      {
        ScrChar(r,cc) = ' '; ScrAttr(r,cc) = ChNormal;
      }
    }
    // Now clear the rest of the row
    for (int col = c ; col < maxCols ; ++col)
    {
      ScrChar(r,col) = 0 ; ScrAttr(r,col) = ChNormal;
    }

    ClearRowDisplay(r,c);  // wipe out char(r,c) on screen
    GotoRC(r,c);
  }

//=============>>> vTextCanvasPane::ClearRowDisplay <<<======================
  void vTextCanvasPane::ClearRowDisplay(const int r, const int c)
  {
    // this function clears the specified row on the
    // screen only, starting at column c to right edge
    // it does not alter the copy of text in memory
    int x, y;
    x = ColToX(c);
    y = RowToY(r);

    ClearRect(x, y-cHeight+1, _width - x, cHeight);
  }

//=================>>> vTextCanvasPane::ClearToEnd <<<======================
  void vTextCanvasPane::ClearToEnd(const int r, const int c)
  {
    // Clear text from r,c down to end of screen
    SysDebug2(Text,"vTextCanvasPane::ClearToEnd(r:%d, c: %d)\n",r,c)

    ClearRow(r,c);
    for (int row = r+1 ; row < maxRows ; ++row)
      ClearRow(row,0);

    GotoRC(r,c);
  }

//=================>>> vTextCanvasPane::DrawTextCursor <<<=================
// cursor management
  void vTextCanvasPane::DrawTextCursor(int r, int c)
  {
    // if window doesn't have focus then cursors don't work
    // and we just have to set the caretOn flag
    if (WinQueryFocus(HWND_DESKTOP) != _drawCanvas)
    {
      // window does not have focus so only update cursor status
      if (r < 0 )     // This is how to turn off the cursor
        caretOn = 0;
      else
        caretOn = 1;
    }
    else
    {
      // window has focus
      // check if it has been created, punt if it hasn't
      if ( caretWin != 0 )
      {
        // kill cursor if in another window
        if ( caretWin != _drawCanvas )
        {
          WinShowCursor (caretWin, FALSE);
          WinDestroyCursor(caretWin);
          caretWin = 0;
        }
      }

      // create cursor for pane if doesn't exist
      // but make sure pane has the focus
      if ( caretWin == 0 )
      {
        int tw = TextWidth("X");
#ifdef UNDERSCORE_CARET
        WinCreateCursor(_drawCanvas, 0, 0, tw, 3, CURSOR_FRAME | CURSOR_FLASH, NULL);
#else
        WinCreateCursor(_drawCanvas, 0, 0, tw, 3, CURSOR_SOLID | CURSOR_FLASH, NULL);
#endif
        caretWin = _drawCanvas;
        caretOn = 0;  // cursor is initially invisible
      }

      if (r < 0 )     // This is how to turn off the cursor
      {
        if (caretOn)	  // we will turn caret off, but only once
        {
          WinShowCursor (_drawCanvas, FALSE);
	  caretOn = 0;
        }
        return;
      }

      if (!caretOn)       // we will turn caret on, but only once
      {
        caretOn = 1;
        WinShowCursor (_drawCanvas, TRUE);
      }

      // we need to convert the target coords which are in V Space
      // to OS/2 Device Space
      POINTL XY = {ColToX(c), RowToY(r)};
/*
      HPS _hdc = _cpDC->handleDC();
      // (HPS, source, target, num points, array of points)
      GpiConvert(_hdc, CVTC_WORLD, CVTC_DEVICE, 1, &XY);
*/
      _cpDC->MapToOS2(&XY);

      WinCreateCursor (_drawCanvas, XY.x, XY.y,
        0, 0, CURSOR_SETPOS, NULL);
    }
  }

  //======================>>> vTextCanvasPane::DrawChar <<<======================
  void vTextCanvasPane::DrawChar(const char chr, const ChrAttr attr)
  {
    // Overwrite a char at current cursor, incr cursor
    char copy[2];	// for a copy
    copy[0] = chr; copy[1] = 0;
    SysDebug1(Text,"vTextCanvasPane::DrawChar(chr:%c)\n",chr)

    ScrChar(curRow,curCol) = chr ;
    ScrAttr(curRow,curCol) = attr;

    // for now, assume just reverse or normal on WHOLE row
    if (attr != ChNormal)		// normal or with attributes?
      vCanvasPane::DrawAttrText(ColToX(curCol), RowToY(curRow), copy, attr);
    else
      vCanvasPane::DrawText(ColToX(curCol), RowToY(curRow), copy);

    if (++curCol >= maxCols)
      curCol = maxCols - 1;
    GotoRC(curRow, curCol);
  }

//======================>>> vTextCanvasPane::DrawText <<<======================
  void vTextCanvasPane::DrawText(const char* text)
  {
    DoDrawText(text, ChNormal);	// Draw normal text
  }

//======================>>> vTextCanvasPane::DrawAttrText <<<======================
  void vTextCanvasPane::DrawAttrText(const char* text, const ChrAttr attr)
  {
    DoDrawText(text, attr);	// Draw reverse text
  }

//======================>>> vTextCanvasPane::DoDrawText <<<======================
  void vTextCanvasPane::DoDrawText(const char* text, const ChrAttr attr)
  {
    // Draw scrolling  text starting at current r,c
    if (!text || !*text)
      return;

    int startC = curCol;
    int needsFinalDraw = 0;
    SysDebug3(Text,"vTextCanvasPane::DrawText(%s)[r=%d,c=%d]\n",text,curRow,startC)

    for (int cc = 0 ; cc < curCol ; ++cc)  // change 0's to blanks if in middle of row
    {
      if (ScrChar(curRow,cc) == 0)
      {
        ScrChar(curRow,cc) = ' ';
        ScrAttr(curRow,cc) = ChNormal;
      }
    }

    for (const char* cp = text ; *cp != 0 ; ++cp)
    {
      if (*cp == '\n')		// new lines take special treatment
      {
        // Terminate current row with 0
        ScrChar(curRow,curCol) = 0;
        ScrAttr(curRow,curCol) = attr;
        // And draw row on the screen!
        DrawTextScr(curRow, startC, ScrAddr(curRow,startC));
        startC = curCol = 0;        // reset column to 0
        ++curRow;		    // move to the next row
        needsFinalDraw = 0;	    // Don't need final draw
        // Next row is empty
        ScrChar(curRow,curCol) = 0;
        ScrAttr(curRow,curCol) = ChNormal;
        if (curRow >= maxRows)	// see if off bottom
        {
          ScrollText(1);		// scroll up 1 row
	}
      }
      else if (curCol < maxCols)	// overwrite current line
      {
        ScrChar(curRow,curCol) = *cp;
        ScrAttr(curRow,curCol) = attr;
        ++curCol;
        needsFinalDraw = 1;		// need final draw
      }
    }

    if (needsFinalDraw)
      DrawTextScr(curRow,startC,ScrAddr(curRow,startC));

    GotoRC(curRow,curCol);
  }

//======================>>> vTextCanvasPane::DrawTextScr <<<======================
  void vTextCanvasPane::DrawTextScr(int r, int c, dChar* txt)
  {
    char copy[MAX_COLS];	// for a copy
    char* to = copy;
    ChrAttr curAttr = txt->attr;  // initial attribute
    int curCol = c;
    int startCol = c;

    for (dChar* dcp = txt ; dcp->chr != 0 ; ++dcp)
    {
      if (dcp->attr != curAttr)	// changing attr - need to show what
      {				// we've built so far
        *to = 0;			// terminate
        if (curAttr != ChNormal)	// normal or with attributes?
        {
          vCanvasPane::DrawAttrText(ColToX(startCol), RowToY(r),
	    copy, curAttr);
	}
	else
	{
	  vCanvasPane::DrawText(ColToX(startCol), RowToY(r), copy);
	}
	// Now resetup for next portion of the row
	to = copy;		    // restart buffer
	curAttr = dcp->attr;	    // new attribute
	startCol = curCol;	    // and new start column
	// fall through for rest of the row
      }
      *to++ = dcp->chr;
      curCol++;
    }
    *to = 0;
    // write rest of the row
    if (copy[0] != 0)			// don't bother with empty row
    {
      if (curAttr != ChNormal)	// normal or with attributes?
      {
        vCanvasPane::DrawAttrText(ColToX(startCol), RowToY(r),
          copy, curAttr);
      }
      else
      {
        vCanvasPane::DrawText(ColToX(startCol), RowToY(r), copy);
      }
    }
  }

//=====================>>> vTextCanvasPane::EnterEV <<<==========================
  void vTextCanvasPane::EnterEV()
  {
    SysDebug2(CmdEvents,"vTextCanvasPane::EnterEV RC(%u,%u))\n", curRow,curCol)
    // when gaining focus, we need to show the cursor if it was previously
    // set since the cursor was destroyed when the window lost focus
    _hasFocus = 1;
    if (caretOn)
    {
      DrawTextCursor(curRow,curCol);
    }
    EnterFocus();		// call the virtual function
  }

//=====================>>> vCanvasPane::LeaveEV <<<==========================
  void vTextCanvasPane::LeaveEV(void)
  {
    SysDebug2(CmdEvents,"vTextCanvasPane::LeaveEV RC(%u,%u))\n", curRow,curCol)

    if (!_hasFocus)       // don't double leave focus
      return;

    _hasFocus = 0;

    if (caretWin == _drawCanvas)   // We have a created caret
    {
      WinShowCursor (_drawCanvas, FALSE);
      WinDestroyCursor (_drawCanvas);
      caretWin = 0;
    }

    LeaveFocus();   // call the virtual function
  }

//======================>>> vTextCanvasPane::FontChanged <<<======================
  void vTextCanvasPane::FontChanged(VCONST vFont& newfont)
  {
    // The font has changed, so we must recalculate everything

//1.18?    Clear();			// 1.18 - match X version
    ResizeText();
  }

//======================>>> vTextCanvasPane::GotoRC <<<======================
// Move cursor to row, col
  void vTextCanvasPane::GotoRC(const int r, const int c)
  {
    // First, a sanity check
    int row = (r < 0) ? 0 : r;
    int col = (c < 0) ? 0 : c;

    if (row >= maxRows)
      row = maxRows - 1;
    if (col >= maxCols)
      col = maxCols - 1;

    curRow = row;
    curCol = col;

    // have to be sure column is blank filled if moved to new column
    for (int cc = 0 ; cc < col ; ++cc)
    {
      if (ScrChar(row,cc) == 0)
      {
        ScrChar(row,cc) = ' ';
        ScrAttr(row,cc) = ChNormal;
      }
    }
    DrawTextCursor(curRow, curCol);		// draw the cursor to here
  }

//======================>>> vTextCanvasPane::MouseDown <<<======================
  void vTextCanvasPane::MouseDown(int x, int y, int button)
  {
    // Map xy to text positions, call alternate mouse down
    TextMouseDown(YToRow(y), XToCol(x), button);
  }

//========================>>> vTextCanvasPane::MouseUp <<<======================
  void vTextCanvasPane::MouseUp(int x, int y, int button)
  {
    // Map xy to text positions, call alternate mouse up
    TextMouseUp(YToRow(y), XToCol(x), button);
  }

//======================>>> vTextCanvasPane::MouseMove <<<======================
  void vTextCanvasPane::MouseMove(int x, int y, int button)
  {
    // Map xy to text positions, call alternate mouse Move
    TextMouseMove(YToRow(y), XToCol(x), button);
  }

//=========================>>> vTextCanvasPane::Redraw <<<======================
  void vTextCanvasPane::Redraw(int x, int y, int width, int height)
  {
    if (maxRows < 0)		// This is the FIRST redraw
      {
	ResizeText();		// resize the text!
	return;			// don't redraw twice!
      }
#ifdef vDEBUG		// Don't have a SysDebug4 macro, so do it by hand
    if (DebugState.WindowEvents && DebugState.System)
	printf("vTextCanvasPane::Redraw(x=%d, y=%d, w=%d, h=%d)\n",
          x,y,width,height);
#endif
    if (!_cursorSet)
      {
	SetCursor(VC_IBar);		// Change here
	_cursorSet = 1;
      }
    // OK, do a REALLY stupid redraw
    for (int r = 0 ; r < maxRows ; ++r)
	DrawTextScr(r,0,ScrAddr(r,0));
  }

//===================>>> vTextCanvasPane::ResizeText <<<======================
  void vTextCanvasPane::ResizeText(const int rows, const int cols)
  {
    // This one is overridable so user can pick up new size
    SysDebug2(WindowEvents,"vTextCanvasPane::ResizeText(%d,%d)\n",rows,cols)
  }

  //===================>>> vTextCanvasPane::SetTextRowsCols <<<======================
  void vTextCanvasPane::SetTextRowsCols(int rows, int cols)
  {
    int newheight = (rows * cHeight) + (2*MARGIN);
    int newwidth = (cols * cWidth) + (2*MARGIN);
    SetWidthHeight(newwidth, newheight);
  }

//===================>>> vTextCanvasPane::ResizeText <<<======================
  void vTextCanvasPane::ResizeText(void)
  {
    // determine how many rows and columns we have
    cHeight = TextHeight(cAscent, cDescent);
    if (cHeight == 0)
      cHeight = 1;
    cWidth  = TextWidth("m");	                 // Use "c" as average char

    maxRows = (_height-(2*MARGIN)) / cHeight;	 // how many rows
    if (maxRows < 1)
      maxRows = 1;

    if (maxRows > rowsAllocated)
      maxRows = rowsAllocated;

    maxCols = (_width-(2*MARGIN)) / cWidth;
    if (maxCols < 1)
      maxCols = 1;

    if (maxCols >= (colsAllocated - 1))
      maxCols = colsAllocated - 2;		// leave room for 0 terminator

    if (curRow > maxRows)			// sanity check
      curRow = 0;
    if (curCol > maxCols)
      curCol = 0;

// 1.18? Clear();                                 // 1.18 Match X version.
    ResizeText(maxRows,maxCols);
    Redraw(0,0,_width,_height);			// force a redraw
  }

//======================>>> vTextCanvasPane::Resize <<<======================
  void vTextCanvasPane::Resize(int w, int h)
  {
    vCanvasPane::Resize(w,h);
    ResizeText();
  }

//======================>>> vTextCanvasPane::ScrollText <<<======================
  void vTextCanvasPane::ScrollText(const int lineCount)
  {
    // scroll text on screen up or down by lineCount
    int count = (lineCount < 0) ? -lineCount : lineCount;    // abs val
    if (count >= maxRows)	// see if doing the whole screen
    {
      Clear();
      if (lineCount < 0)
        GotoRC(maxRows-1,0);
    }

    if (lineCount > 0)		// Scroll up -- blank lines at bottom
    {
      int r,c, to;
      // Do the scroll: move all the data up count rows
      for (r = count ; r < maxRows ; ++r)
      {
        to = r - count;	// ScrChar is a macro, so need this
        // copy one row up
        for (c = 0 ; c < maxCols ; ++c)
        {
          ScrChar(to,c) = ScrChar(r,c);
	  ScrAttr(to,c) = ScrAttr(r,c);
	}
      }
      curRow = maxRows - count;	// set to bottom used row of screen
      curCol = 0;
      // Now, draw the screen with new rows
      for (r = 0 ; r < curRow ; ++r)
      {
        ClearRowDisplay(r,0);
        DrawTextScr(r,0,ScrAddr(r,0));
      }
      // And clear the rest of the screen
      for (r = curRow ; r < maxRows ; ++r)
      {
        ClearRow(r,0);
      }
    }
    else			// scroll down
    {
      int r,c, to;
      int lastRow = maxRows -1;	// the last row on the screen
      // Do the scroll: move all the data down count rows
      for (r = lastRow - count ; r >= 0 ; --r)
      {
        to = r + count;	// ScrChar is a macro, so need this
        // copy one row down
        for (c = 0 ; c < maxCols ; ++c)
        {
          ScrChar(to,c) = ScrChar(r,c);
	  ScrAttr(to,c) = ScrAttr(r,c);
	}
      }
      curRow = count;    // set to topmost used row of screen
      curCol = 0;

      // Now, draw the screen with new rows
      for (r = curRow ; r < maxRows ; ++r)
      {
        ClearRowDisplay(r,0);
        DrawTextScr(r,0,ScrAddr(r,0));
      }
      // And clear the top part of the screen
      for (r = 0 ; r < curRow ; ++r)
      {
        ClearRow(r,0);
      }
    }
  }
  
//======================>>> vTextCanvasPane::YToRow <<<======================
// assumes origin is at top left corner
  int vTextCanvasPane::YToRow(int y)
  {
    if (cHeight == 0)
	cHeight = 1;
    int row = (y - MARGIN) / cHeight;

    if (row >= maxRows)
	return maxRows - 1;		// adjust for margins
    else if (row < 0)
	return 0;
    else
	return row;
  }

//======================>>> vTextCanvasPane::XToCol <<<======================
  int vTextCanvasPane::XToCol(int x)
  {
    if (cWidth == 0)
	cWidth = 1;
    int col = (x - MARGIN) / cWidth;
    if (col >= maxCols)
	return maxCols - 1;		// adjust for margins
    else if (col < 0)
	return 0;
    else
	return col;
  }
