// MUSHview.cpp : implementation of the CMUSHView class
//

// ANSI test in PennMUSH: think [ansi(cR,Nick)][ansi(wB,Gammon)][ansi(u,Underscored)]

#include "stdafx.h"
#include "MUSHclient.h"

#include "doc.h"
#include "MUSHview.h"
#include "mainfrm.h"
#include "childfrm.h"
#include "sendvw.h"

#include "ActivityDoc.h"
#include "resource.h"
#include "genprint.h"
#include "TextAttributesDlg.h"
#include "GoToLineDlg.h"
#include "HighlightPhraseDlg.h"
#include "MultiLineTriggerDlg.h"
#include "errors.h"

#ifdef _DEBUG
//#define new DEBUG_NEW
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define REDRAW_DEBUG 0

#if REDRAW_DEBUG

#define TYPE_OF_RECT_FILL FillRect
// #define TYPE_OF_RECT_FILL FrameRect
#define TYPE_OF_RGN_FILL FillRgn
// #define TYPE_OF_RGN_FILL FrameRgn

void ShowInvalidatedRect (CMUSHView * pView, RECT & r, 
                          COLORREF colour = RGB (0, 255, 255))  // cyan?
  {
	CClientDC dc(pView);
  CBrush br;
  br.CreateSolidBrush (colour);    
  dc.TYPE_OF_RECT_FILL (&r, &br); 
  Sleep (500);    // pause so we can see it  
  }  // end of ShowInvalidatedRect

void ShowInvalidatedRgn (CMUSHView * pView, CRgn & r)
  {
	CClientDC dc(pView);
  CBrush br;
  br.CreateSolidBrush (RGB (0, 255, 255));    // cyan?
  dc.TYPE_OF_RGN_FILL (&r, &br); 
  Sleep (500);    // pause so we can see it  
  }  // end of ShowInvalidatedRgn

#endif
 

CString strMXP_menu_item [MXP_MENU_COUNT];

#define ACTION_ALIAS 1    // dummy action type for alias menus

// for conversion to Unicode
static WCHAR sUnicodeText [MAX_LINE_WIDTH];

int iAction = 0;

/////////////////////////////////////////////////////////////////////////////
// CMUSHView

IMPLEMENT_DYNCREATE(CMUSHView, CView)

BEGIN_MESSAGE_MAP(CMUSHView, CView)
	ON_WM_CONTEXTMENU()
	//{{AFX_MSG_MAP(CMUSHView)
	ON_COMMAND(ID_TEST_END, OnTestEnd)
	ON_COMMAND(ID_TEST_PAGEDOWN, OnTestPagedown)
	ON_COMMAND(ID_TEST_PAGEUP, OnTestPageup)
	ON_COMMAND(ID_TEST_START, OnTestStart)
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_SETCURSOR()
	ON_COMMAND(ID_TEST_LINEDOWN, OnTestLinedown)
	ON_COMMAND(ID_TEST_LINEUP, OnTestLineup)
	ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
	ON_WM_ERASEBKGND()
	ON_COMMAND(ID_KEYS_ACTIVATECOMMANDVIEW, OnKeysActivatecommandview)
	ON_WM_SIZE()
	ON_COMMAND(ID_DISPLAY_FREEZEOUTPUT, OnDisplayFreezeoutput)
	ON_UPDATE_COMMAND_UI(ID_DISPLAY_FREEZEOUTPUT, OnUpdateDisplayFreezeoutput)
	ON_UPDATE_COMMAND_UI(ID_STATUSLINE_FREEZE, OnUpdateStatuslineFreeze)
	ON_COMMAND(ID_WINDOW_MINIMIZE, OnWindowMinimize)
	ON_UPDATE_COMMAND_UI(ID_WINDOW_MINIMIZE, OnUpdateWindowMinimize)
	ON_WM_CHAR()
	ON_COMMAND(ID_DISPLAY_FIND, OnDisplayFind)
	ON_COMMAND(ID_DISPLAY_FINDAGAIN, OnDisplayFindagain)
	ON_COMMAND(ID_FILE_PRINT_WORLD, OnFilePrintWorld)
	ON_WM_VSCROLL()
	ON_COMMAND(ID_FILE_SAVESELECTION, OnFileSaveselection)
	ON_COMMAND(ID_FIND_AGAIN_BACKWARDS, OnFindAgainBackwards)
	ON_UPDATE_COMMAND_UI(ID_FIND_AGAIN_BACKWARDS, OnUpdateFindAgainBackwards)
	ON_COMMAND(ID_FIND_AGAIN_FORWARDS, OnFindAgainForwards)
	ON_UPDATE_COMMAND_UI(ID_FIND_AGAIN_FORWARDS, OnUpdateFindAgainForwards)
	ON_COMMAND(ID_DISPLAY_GOTOURL, OnDisplayGotourl)
	ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateNeedSelection)
	ON_COMMAND(ID_DISPLAY_SENDMAILTO, OnDisplaySendmailto)
	ON_COMMAND(ID_EDIT_SELECT_ALL, OnEditSelectAll)
	ON_WM_LBUTTONDBLCLK()
	ON_COMMAND(ID_DISPLAY_GOTOBOOKMARK, OnDisplayGotobookmark)
	ON_COMMAND(ID_DISPLAY_BOOKMARKSELECTION, OnDisplayBookmarkselection)
	ON_WM_MOUSEWHEEL()
	ON_COMMAND(ID_ALT_UPARROW, OnAltUparrow)
	ON_COMMAND(ID_ALT_DOWNARROW, OnAltDownarrow)
	ON_COMMAND(ID_DISPLAY_TEXTATTRIBUTES, OnDisplayTextattributes)
	ON_COMMAND(ID_EDIT_NOTESWORKAREA, OnEditNotesworkarea)
	ON_COMMAND(ID_DISPLAY_RECALLTEXT, OnDisplayRecalltext)
	ON_WM_KEYDOWN()
	ON_COMMAND(ID_DISPLAY_GOTOLINE, OnDisplayGotoline)
	ON_COMMAND(ID_GAME_COMMANDHISTORY, OnGameCommandhistory)
	ON_COMMAND(ID_FILE_CTRL_N, OnFileCtrlN)
	ON_COMMAND(ID_FILE_CTRL_P, OnFileCtrlP)
	ON_COMMAND(ID_EDIT_CTRL_Z, OnEditCtrlZ)
	ON_COMMAND(ID_EDIT_COPYASHTML, OnEditCopyashtml)
	ON_COMMAND(ID_DISPLAY_HIGHLIGHTPHRASE, OnDisplayHighlightphrase)
	ON_COMMAND(ID_DISPLAY_MULTILINETRIGGER, OnDisplayMultilinetrigger)
	ON_UPDATE_COMMAND_UI(ID_DISPLAY_GOTOURL, OnUpdateNeedSelection)
	ON_UPDATE_COMMAND_UI(ID_FILE_SAVESELECTION, OnUpdateNeedSelection)
	ON_UPDATE_COMMAND_UI(ID_DISPLAY_SENDMAILTO, OnUpdateNeedSelection)
	ON_COMMAND(ID_KEYS_TAB, OnKeysActivatecommandview)
	ON_COMMAND(ID_KEYS_ESCAPE, OnKeysActivatecommandview)
	ON_UPDATE_COMMAND_UI(ID_DISPLAY_TEXTATTRIBUTES, OnUpdateNeedSelection)
	ON_UPDATE_COMMAND_UI(ID_EDIT_COPYASHTML, OnUpdateNeedSelection)
	ON_COMMAND(ID_WINDOW_MAXIMIZE, OnWindowMaximize)
	ON_COMMAND(ID_WINDOW_RESTORE, OnWindowRestore)
	//}}AFX_MSG_MAP

  // macros
	ON_COMMAND_EX(ID_MACRO_CTRL_F10, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_CTRL_F11, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_CTRL_F12, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_CTRL_F2, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_CTRL_F3, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_CTRL_F5, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_CTRL_F7, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_CTRL_F8, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_CTRL_F9, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_F10, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_F11, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_F12, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_F2, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_F3, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_F4, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_F5, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_F7, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_F8, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_F9, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_SHIFT_F10, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_SHIFT_F11, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_SHIFT_F12, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_SHIFT_F2, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_SHIFT_F3, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_SHIFT_F4, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_SHIFT_F5, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_SHIFT_F7, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_SHIFT_F8, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_SHIFT_F9, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_A, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_B, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_J, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_K, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_L, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_M, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_N, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_O, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_P, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_Q, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_R, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_S, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_T, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_U, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_X, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_Y, OnMacroCommand)
	ON_COMMAND_EX(ID_ALT_Z, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_F1, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_CTRL_F1, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_SHIFT_F1, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_F6, OnMacroCommand)
	ON_COMMAND_EX(ID_MACRO_CTRL_F6, OnMacroCommand)

  // numeric keypad  
  ON_COMMAND_EX(ID_KEYPAD_0, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_1, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_2, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_3, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_4, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_5, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_6, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_7, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_8, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_9, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_DASH, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_DOT, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_PLUS, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_SLASH, OnKeypadCommand)
  ON_COMMAND_EX(ID_KEYPAD_STAR, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_0, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_1, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_2, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_3, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_4, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_5, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_6, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_7, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_8, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_9, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_DASH, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_DOT, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_PLUS, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_SLASH, OnKeypadCommand)
  ON_COMMAND_EX(ID_CTRL_KEYPAD_STAR, OnKeypadCommand)
	// Standard printing commands
	ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

  // MXP pop-up menu
  ON_COMMAND_RANGE(MXP_FIRST_MENU, 
                    MXP_FIRST_MENU + MXP_MENU_COUNT - 1, 
                    OnMXPMenu)

  // Accelerator commands
  ON_COMMAND_RANGE(ACCELERATOR_FIRST_COMMAND, 
                   ACCELERATOR_FIRST_COMMAND + ACCELERATOR_COUNT - 1, 
                   OnAcceleratorCommand)

END_MESSAGE_MAP()

//#define new DEBUG_NEW 

/////////////////////////////////////////////////////////////////////////////
// CMUSHView construction/destruction

CMUSHView::CMUSHView()
{

  m_selstart_line = 0;  
  m_selstart_col = 0;
  m_selend_line = 0;
  m_selend_col = 0;
  m_freeze = FALSE;
  m_bAtBufferEnd = false;
  m_last_line_drawn = 0;
  m_last_line = 0;
  m_nLastToolTipLine = 0;
  m_nLastToolTipColumn = 0;
  m_scroll_position = 0;

  m_iPauseStatus = ePauseUninitialised;

}

CMUSHView::~CMUSHView()
{
}

/////////////////////////////////////////////////////////////////////////////
// CMUSHView drawing

// for extracting parts of the style out - see copy in doc.cpp

#if 0
static inline short get_foreground (int style)
  {

  if (style & CUSTOM)
    {
    int iColour = (style >> 4) & 0x0FF;  // we have up to 256 custom colours
    if (iColour >= MAX_CUSTOM)
      iColour = 0;
    return iColour;   
    }
  else
    return (style >> 4) & 0x07;


  } // end of get_foreground

static inline short get_background (int style)
  {

  if (style & CUSTOM)
    return 0;   // custom colour backgrounds are defined as part of the colour
  else
    return (style >> 8) & 0x07;

  } // end of get_background

#endif

long CMUSHView::calculate_width (const int & line, 
                       const int len, 
                       CMUSHclientDoc* pDoc, 
                       CClientDC & dc)
  {
int thiscol = 0;
int cols_to_go = len;
int thislen;
CSize textsize;
long pixel = 0;
POSITION pos;
CLine * pLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (line));

  if (len <= 0)
    return 0;

  CStyle * pStyle;

  // do each batch of styles separately
  for (pos = pLine->styleList.GetHeadPosition(); pos && cols_to_go > 0; )
    {
    pStyle = pLine->styleList.GetNext (pos);

    if (pStyle->iLength <= 0)
      continue;   // ignore zero-length runs

    // don't overshoot requested column
    thislen = MIN (cols_to_go, pStyle->iLength);

// select appropriate font

    if (pDoc->m_font [pStyle->iFlags & 7])
      dc.SelectObject(pDoc->m_font [pStyle->iFlags & 7]);   


    if (pDoc->m_bUTF_8)
      {
      int iUnicodeCharacters = MultiByteToWideChar (CP_UTF8, 0,     // do not use flag MB_PRECOMPOSED - doesn't work!
                            &pLine->text [thiscol], thislen,  // input
                            sUnicodeText, MAX_LINE_WIDTH);         // output

      // now calculate width of Unicode pixels
      GetTextExtentPoint32W(
          dc.m_hDC,           // handle to device context
          sUnicodeText,  // pointer to text string
          iUnicodeCharacters,      // number of characters in string
          &textsize      // pointer to structure for string size
        );
 
      }
    else
      {


    // find exact width of this text (Ascii)

        textsize = dc.GetTextExtent (
                          &pLine->text [thiscol],
                          thislen);
      }

    thiscol += thislen;     // new column
    cols_to_go -= thislen;  // fewer in next batch
    pixel += textsize.cx;   // count pixels

    }   // end of this style


  return pixel;

  } // end of calculate_width


/*
   So that italic text doesn't get chopped off at the RH boundary we will draw each
   line twice - once for the background, and then redraw over it transparently for the
   text.

  */
void CMUSHView::display_text (CDC* pDC, 
                             const CMUSHclientDoc* pDoc, 
                             const long line,
                             const CLine * pLine, 
                             const int col, 
                             const int len, 
                             const BOOL selected,
                             long & pixel,
                             const bool bBackground)
  {
RECT r;

COLORREF colour1,
         colour2,
         clrBackground;


  if (len <= 0)
    return;

  pDC->SetBkMode (bBackground ? OPAQUE : TRANSPARENT);

int thiscol = col;
int cols_to_go = len;
int thislen;
CSize textsize;
CStyle * pStyle;
int iCol = 0;
POSITION foundpos;


  if (!pDoc->FindStyle (pLine, col, iCol, pStyle, foundpos))
    return;

  // get next style in sequence
  pLine->styleList.GetNext (foundpos);  // skip one we had

  // how much do we do of this style?
  thislen = iCol - col;

  while (thiscol < (col + len))
    {

    // don't overshoot
    thislen = MIN (cols_to_go, thislen);

    if (pDoc->m_font [pStyle->iFlags & 7])
      pDC->SelectObject(pDoc->m_font [pStyle->iFlags & 7]);

    pDoc->GetStyleRGB (pStyle, colour1, colour2);

    if (selected)
      {
      pDC->SetTextColor (pDoc->TranslateColour (colour2));  
      clrBackground = colour1;
      }
    else
      {
      pDC->SetTextColor (pDoc->TranslateColour (colour1));  
      clrBackground = colour2;
      }


int iUnicodeCharacters;

// unicode conversion from UTF-8

    if (pDoc->m_bUTF_8)
      {
      iUnicodeCharacters = MultiByteToWideChar (CP_UTF8, 0,     // do not use flag MB_PRECOMPOSED - doesn't work!
                            &pLine->text [thiscol], thislen,  // input
                            sUnicodeText, MAX_LINE_WIDTH);         // output

      // now calculate width of Unicode pixels
      GetTextExtentPoint32W(
          pDC->m_hDC,           // handle to device context
          sUnicodeText,  // pointer to text string
          iUnicodeCharacters,      // number of characters in string
          &textsize      // pointer to structure for string size
        );
 
      }
    else
      {


    // find exact width of this text (Ascii)

        textsize = pDC->GetTextExtent (
                          &pLine->text [thiscol],
                          thislen);
      }


    SetRect (&r, 
             pDoc->m_iPixelOffset + pixel,
             - pDoc->m_iPixelOffset + line * pDoc->m_FontHeight,
             pDoc->m_iPixelOffset + pixel + textsize.cx,
             - pDoc->m_iPixelOffset + (line + 1) * pDoc->m_FontHeight);
            
    OffsetRect (&r, -m_scroll_position.x, -m_scroll_position.y);

    if (bBackground)
      pDC->FillSolidRect (&r, pDoc->TranslateColour (clrBackground));
    else
      {
      if (pDoc->m_bUTF_8)     // Unicode output
        ExtTextOutW(          // W = wide
                  pDC->m_hDC,          
                  pDoc->m_iPixelOffset + pixel - m_scroll_position.x,  
                  - pDoc->m_iPixelOffset + line * pDoc->m_FontHeight - m_scroll_position.y,            
                  0,      // transparent
                  &r, 
                  sUnicodeText, 
                  iUnicodeCharacters,     
                  NULL);
      else                   // Ascii output
        pDC->ExtTextOut (
                  pDoc->m_iPixelOffset + pixel - m_scroll_position.x, 
                   - pDoc->m_iPixelOffset + line * pDoc->m_FontHeight - m_scroll_position.y,
                   0,   // transparent
                   &r,  
                   &pLine->text [thiscol], 
                   thislen,
                   NULL);

      }
    thiscol += thislen;
    cols_to_go -= thislen;  // this many fewer to go
    pixel += textsize.cx;

    if (!foundpos)
      break;  // no more styles

    pStyle = pLine->styleList.GetNext (foundpos);  // and get next one
    thislen = pStyle->iLength;                      

    } // end of this group of the same colour


  }  // end of CMUSHView::display_text 


void CMUSHView::OnDraw(CDC* pDC)
{
int startline,
    endline,
    line,
    lastline,
    startcol,
    endcol;

long pixel;

CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

  if (!pDoc->m_FontHeight)
    return;


/*

  // background image 
  // didn't really work - because text isn't transparent, for one thing

  if ((HBITMAP) pDoc->m_bitmap)
    {
     BITMAP  bi;
     pDoc->m_bitmap.GetBitmap(&bi);

    CClientDC dc(this);
    CDC bmDC;
    bmDC.CreateCompatibleDC(&dc);
    CBitmap *pOldbmp = bmDC.SelectObject(&pDoc->m_bitmap);

    pDC->BitBlt(0,0,bi.bmWidth,bi.bmHeight,&bmDC,0,0,SRCCOPY);
    bmDC.SelectObject(pOldbmp);
    }

*/

  CPoint pt = GetScrollPosition ();
   
RECT r;

  GetClientRect (&r);

  startline = (pt.y - pDoc->m_iPixelOffset) / pDoc->m_FontHeight;
  endline =  ((pt.y + r.bottom) / pDoc->m_FontHeight) + 2;

  if (startline < 0)
    startline = 0;

// include partial last line if necessary

  lastline = pDoc->GetLastLine ();

  if (endline > lastline)
    endline = lastline;

  if (startline > lastline)
    startline = lastline - 20;

POSITION pos = pDoc->GetLinePosition (startline);
unsigned short style = 0;    // background (bleed) style
COLORREF iForeColour = WHITE;
COLORREF iBackColour = BLACK;

  if (pDoc->m_bCustom16isDefaultColour)
    {
    iForeColour = 15;   // use custom colour 16
    iBackColour = 15;
    style = COLOUR_CUSTOM;
    }

  for (line = startline; line < endline && pos; line++)
    {

// retrieve actual pointer to this line

    CLine * pLine = pDoc->m_LineList.GetNext (pos);

    // on user input or note, assume default background bleed if nothing there
    if (pLine->flags & INPUT)
      {
      if (pDoc->m_bCustom16isDefaultColour)
        {
        iForeColour = 15;   // use custom colour 16
        iBackColour = 15;
        style = COLOUR_CUSTOM;
        }
      else
        {
        style = 0;
        iForeColour = WHITE;
        iBackColour = BLACK;
        }
      }

    startcol = MIN (m_selstart_col, pLine->len);
    endcol = MIN (m_selend_col, pLine->len);

    // Draw the background and then the text

    for (int iPass = 0; iPass < 2; iPass++)
      {
      bool bBackground = iPass == 0;

      pixel = 0;

  // show the selection in a different colour

      if (line == m_selstart_line && m_selstart_line == m_selend_line)
        {

  // selection starting and ending on the same line is a special case

          display_text (pDC, pDoc, line, pLine, 0, startcol, 
                        FALSE, pixel, bBackground);
          display_text (pDC, pDoc, line, pLine, startcol, endcol - startcol, 
                        TRUE, pixel, bBackground);
          display_text (pDC, pDoc, line, pLine, endcol, pLine->len - endcol, 
                        FALSE, pixel, bBackground);

        }
      else if (line == m_selstart_line)
          {

  // selection starts on this line

          display_text (pDC, pDoc, line, pLine, 0, startcol, 
                        FALSE, pixel, bBackground);
          display_text (pDC, pDoc, line, pLine, startcol, pLine->len - startcol, 
                        TRUE, pixel, bBackground);

          }
      else if (line == m_selend_line)
          {

  // selection ends on this line

          display_text (pDC, pDoc, line, pLine, 0, endcol, 
                        TRUE, pixel, bBackground);
          display_text (pDC, pDoc, line, pLine, endcol, pLine->len - endcol, 
                        FALSE, pixel, bBackground);

          }
      else
        {

        display_text (pDC, pDoc, line, pLine, 0, pLine->len, 
                      line >= m_selstart_line && line <= m_selend_line, pixel, bBackground);

        }  // end of selection not starting and ending on the same line

// bleed to edge if wanted
      
      if (bBackground && 
          App.m_bBleedBackground)
        {
        pDC->SetBkMode (OPAQUE);
        RECT r,
             cr;    // client rect
        COLORREF clrBackground;

        // -------- bleed to left --------

        // empty lines take background from previous line        
        if (pLine->len > 0)
          {
          CStyle * pStyle = pLine->styleList.GetHead ();
          style = pStyle->iFlags & STYLE_BITS;
          iForeColour = pStyle->iForeColour;
          iBackColour = pStyle->iBackColour;
          }

        // work out the colour
        if ((style & COLOURTYPE) == COLOUR_CUSTOM)
          {
          ASSERT (iForeColour >= 0 && iForeColour < MAX_CUSTOM);
          clrBackground = pDoc->m_customback [iForeColour];
          }
        else
        // RGB is just itself
        if ((style & COLOURTYPE) == COLOUR_RGB)
          if (style & INVERSE)    // inverse inverts foreground and background
            clrBackground = iForeColour;
          else
            clrBackground = iBackColour;
        else
          {
          ASSERT (iForeColour >= 0 && iForeColour < 8);
          ASSERT (iBackColour >= 0 && iBackColour < 8);
          if (style & INVERSE)    // inverse inverts foreground and background
            clrBackground = pDoc->m_normalcolour [iForeColour];
          else
            clrBackground = pDoc->m_normalcolour [iBackColour];
          }   // not custom

        SetRect (&r, 
                 0,
                 - pDoc->m_iPixelOffset + line * pDoc->m_FontHeight,
                 pDoc->m_iPixelOffset,
                 - pDoc->m_iPixelOffset + (line + 1) * pDoc->m_FontHeight);
            
        OffsetRect (&r, -m_scroll_position.x, -m_scroll_position.y);

        pDC->FillSolidRect (&r, pDoc->TranslateColour (clrBackground));

        // -------- bleed to right --------

        // empty lines take background from previous line        
        if (pLine->len > 0)
          {
          CStyle * pStyle = pLine->styleList.GetTail ();
          style = pStyle->iFlags & STYLE_BITS;
          iForeColour = pStyle->iForeColour;
          iBackColour = pStyle->iBackColour;
          }

        // work out the colour
        if ((style & COLOURTYPE) == COLOUR_CUSTOM)
          {
          ASSERT (iForeColour >= 0 && iForeColour < MAX_CUSTOM);
          clrBackground = pDoc->m_customback [iForeColour]; 
          }
        else
        if ((style & COLOURTYPE) == COLOUR_RGB)
          if (style & INVERSE)    // inverse inverts foreground and background
            clrBackground = iForeColour;
          else
            clrBackground = iBackColour;
        else
          {
          ASSERT (iForeColour >= 0 && iForeColour < 8);
          ASSERT (iBackColour >= 0 && iBackColour < 8);
          if (style & INVERSE)    // inverse inverts foreground and background
            clrBackground = pDoc->m_normalcolour [iForeColour];
          else
            clrBackground = pDoc->m_normalcolour [iBackColour];
          }   // not custom

        GetClientRect (&cr);


        SetRect (&r, 
                 pDoc->m_iPixelOffset + pixel,
                 - pDoc->m_iPixelOffset + line * pDoc->m_FontHeight,
                 cr.right,
                 - pDoc->m_iPixelOffset + (line + 1) * pDoc->m_FontHeight);
            
        OffsetRect (&r, -m_scroll_position.x, -m_scroll_position.y);

        pDC->FillSolidRect (&r, pDoc->TranslateColour (clrBackground));
        }   // end of bleed wanted

      // special drawing for horizontal rule
      if (pLine->flags & HORIZ_RULE)
        {
        pDC->SetBkMode (OPAQUE);
        RECT r,
             cr;    // client rect
        int iHalfFontHeight = pDoc->m_FontHeight / 2;
        if (iHalfFontHeight == 0)
          iHalfFontHeight = 1;

        GetClientRect (&cr);

        SetRect (&r, 
                 pDoc->m_iPixelOffset + 10,
                 - pDoc->m_iPixelOffset + line * pDoc->m_FontHeight + iHalfFontHeight,
                 cr.right - 10 - pDoc->m_iPixelOffset,
                 - pDoc->m_iPixelOffset + line * pDoc->m_FontHeight + iHalfFontHeight + 1);
            
        OffsetRect (&r, -m_scroll_position.x, -m_scroll_position.y);

        pDC->FillSolidRect (&r, pDoc->TranslateColour (RGB (132, 132, 132)));
        OffsetRect (&r, 0, 1);
        pDC->FillSolidRect (&r, pDoc->TranslateColour (RGB (198, 198, 198)));

        } // end of doing horizontal rule 

      }   // end of doing background and then text
    }   // end of doing each line

// make sure count (and title) is reset once we look at the view

  if (!GetParentFrame ()->IsIconic ())
    {
    m_last_line = pDoc->m_total_lines;
    FixupTitle ();
    }
  

}

void CMUSHView::OnInitialUpdate()
{
	CMUSHclientDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

	CView::OnInitialUpdate();

	CSize sizeTotal;

	sizeTotal.cx = sizeTotal.cy = 100;
	SetScrollSizes(sizeTotal, sizeTotal, sizeTotal);

  sizewindow ();

  FixupTitle ();

	if (m_ToolTip.Create(this, TTS_ALWAYSTIP) && m_ToolTip.AddTool(this))
	{
		m_ToolTip.SendMessage(TTM_SETMAXTIPWIDTH, 0, SHRT_MAX);
		m_ToolTip.SendMessage(TTM_SETDELAYTIME, TTDT_AUTOPOP, 5000);
		m_ToolTip.SendMessage(TTM_SETDELAYTIME, TTDT_INITIAL, 400);
		m_ToolTip.SendMessage(TTM_SETDELAYTIME, TTDT_RESHOW, 400);
	}
	else
	{
		TRACE("Error in creating ToolTip");
	}

}

/////////////////////////////////////////////////////////////////////////////
// CMUSHView printing

BOOL CMUSHView::OnPreparePrinting(CPrintInfo* pInfo)
{
	// default preparation
	return DoPreparePrinting(pInfo);
}

void CMUSHView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO: add extra initialization before printing
}

void CMUSHView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO: add cleanup after printing
}

/////////////////////////////////////////////////////////////////////////////
// CMUSHView diagnostics

#ifdef _DEBUG
void CMUSHView::AssertValid() const
{
	CView::AssertValid();
}

void CMUSHView::Dump(CDumpContext& dc) const
{
	CView::Dump(dc);
}

CMUSHclientDoc* CMUSHView::GetDocument() // non-debug version is inline
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMUSHclientDoc)));
	return (CMUSHclientDoc*)m_pDocument;
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CMUSHView message handlers

void CMUSHView::InvalidateLines (const int iLines)
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
RECT r;
CPoint pt;
int lastline;
long right;


  GetClientRect (&r);
  right = r.right;

  lastline = pDoc->GetLastLine ();

  pt.y =  (lastline * pDoc->m_FontHeight) - r.bottom;
  pt.x = 0;

  if (pt.y < 0)
    pt.y = 0;

  SetRect (&r, 
           0,  
            - pDoc->m_iPixelOffset + ((m_last_line_drawn - iLines) * pDoc->m_FontHeight) 
                - pt.y,
            pDoc->m_iPixelOffset + right,
             ((m_last_line_drawn + 1) * pDoc->m_FontHeight) 
                - pt.y);


  InvalidateRect (&r);

#if REDRAW_DEBUG
  ShowInvalidatedRect (this, r, RGB (255, 0, 0));
#endif

  } // end of  CMUSHView::InvalidateLines 


void CMUSHView::OnTestEnd() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
RECT r;
int lastline;
long right;

GetClientRect (&r);
right = r.right;

CPoint pt;

// include partial last line if necessary

lastline = pDoc->GetLastLine ();

pt.y =  (lastline * pDoc->m_FontHeight) - r.bottom;
pt.x = 0;

if (pt.y < 0)
  pt.y = 0;

ScrollToPosition (pt, App.m_bSmoothScrolling);


  InvalidateLines (1);
  
  m_last_line_drawn = lastline;

// scrolling to the end of the buffer will cancel any selection
// (so we can bookmark the last line easily)

  CRgn oldrgn;
  get_selection (oldrgn);
  InvalidateRgn (&oldrgn);

#if REDRAW_DEBUG
  ShowInvalidatedRgn (this, oldrgn);
#endif

  oldrgn.DeleteObject ();
  m_selstart_line = 0;  
  m_selstart_col = 0;
  m_selend_line = 0;
  m_selend_col = 0;
  m_bAtBufferEnd = true;  // note we are at end

  }

void CMUSHView::OnTestPagedown() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
RECT r;

CPoint pt = GetScrollPosition ();
int lastline;

GetClientRect (&r);

pt.y += r.bottom - pDoc->m_FontHeight;

// include partial last line if necessary

lastline = pDoc->GetLastLine ();

if (pt.y > ((lastline * pDoc->m_FontHeight) - r.bottom))
	pt.y =  (lastline * pDoc->m_FontHeight) - r.bottom;

ScrollToPosition (pt, false);

}

void CMUSHView::OnTestPageup() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

RECT r;

CPoint pt = GetScrollPosition ();

GetClientRect (&r);

pt.y -= r.bottom - pDoc->m_FontHeight;

if (pt.y < 0)
	pt.y = 0;

ScrollToPosition (pt, false);

}

void CMUSHView::OnTestStart() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

POINT pt = {0, 0};

ScrollToPosition (pt, false);

}



/* +++++++++++++++++++++++++++++++++++++++++++++++++++ */
/*                                                     */
/*       sizewindow                                      */
/*                                                     */
/* +++++++++++++++++++++++++++++++++++++++++++++++++++ */

// Size the window to hold our "screen"
// 

void CMUSHView::sizewindow (void)
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

  long top,
       left;
  CRect statusrect,
        toolbarrect;
  Frame.ReturnStatusRect (statusrect);
  Frame.ReturnToolbarRect (toolbarrect);

  RECT rect;

  GetOwner()->GetOwner()->GetWindowRect (&rect);

  top = 0;
  left = 0;
  
// calculate width

  m_DefaultWidth  =  (pDoc->m_nWrapColumn * pDoc->m_FontWidth) +   // Client width of screen is 640 pixels
                    (GetSystemMetrics (SM_CXFRAME) * 2) +  // allow for border
                     GetSystemMetrics (SM_CXVSCROLL) +  // allow for vertical scroll bar
                     + pDoc->m_iPixelOffset  +                  // plus text offset from side
                    4;      // allow for Windows 95 frame

// calculate height

  m_DefaultHeight =  (24 * pDoc->m_FontHeight) +      // Client height of screen is 400 pixels
                    statusrect.Height() +     // plus the status bar
                    toolbarrect.Height () +   // plus the tool bar
                    2 +                       // enough to see the WP cursor
                    pDoc->m_iPixelOffset +            // plus text offset from bottom
                    GetSystemMetrics (SM_CYMENU) + // plus the menu
                    (GetSystemMetrics (SM_CYFRAME) * 2) +
                    GetSystemMetrics (SM_CYCAPTION) +  // plus the title
                    20;    // allow for command input area

  RECT framerect;
  ::GetClientRect (((CMDIFrameWnd *)&Frame)->m_hWndMDIClient, &framerect);

  if (m_DefaultHeight > framerect.bottom)
    m_DefaultHeight = framerect.bottom;

  WINDOWPLACEMENT wp;
  wp.length = sizeof (wp);
  GetOwner()->GetOwner()->GetWindowPlacement (&wp);

  // don't resize if maximised
  if (wp.showCmd != SW_MAXIMIZE)
    GetOwner()->GetOwner()->MoveWindow (left, 
                      top, 
                      m_DefaultWidth,
                      m_DefaultHeight);

  // now recalulate scroll bar information

// find page size

RECT r;

  GetClientRect (&r);

// find last line

int lastline;

  lastline = pDoc->GetLastLine ();

CSize sizeTotal (pDoc->m_nWrapColumn * pDoc->m_FontWidth, lastline * pDoc->m_FontHeight),
      sizePage  (r.right - r.left - pDoc->m_iPixelOffset, r.bottom - r.top),
      sizeLine  (pDoc->m_FontWidth, pDoc->m_FontHeight);

 	SetScrollSizes (sizeTotal, sizePage, sizeLine);

  pDoc->SendWindowSizes (pDoc->m_nWrapColumn);

  } // end of sizewindow



void CMUSHView::addedstuff (void)
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

// find page size

RECT r;

  GetClientRect (&r);

// find last line

int lastline;

  lastline = pDoc->GetLastLine ();

CSize sizeTotal (pDoc->m_nWrapColumn * pDoc->m_FontWidth, lastline * pDoc->m_FontHeight),
      sizePage  (r.right - r.left - pDoc->m_iPixelOffset, r.bottom - r.top),
      sizeLine  (pDoc->m_FontWidth, pDoc->m_FontHeight);

 	SetScrollSizes (sizeTotal, sizePage, sizeLine);

// pretend they pressed "End" to force the view to update.

  if (!m_freeze)
    OnTestEnd ();   

// if we are still on first screen (no scroll bars) invalidate whole view

CPoint pt = GetScrollPosition ();

  if (pt.y == 0)
    {
    Invalidate ();
#if REDRAW_DEBUG
    RECT r;
    GetClientRect (&r);
    ShowInvalidatedRect (this, r);
#endif
    }

// if iconized, show number of lines since it wasn't iconised (in title)

  if (GetParentFrame ()->IsIconic ())
    {
    CString str;
    long diff = pDoc->m_total_lines - m_last_line;

    str.Format ("%s [%ld]", (const char *) pDoc->m_mush_name, diff);

    GetParentFrame ()->SetWindowText (str);
    }
  else
    {

// not iconised, remember last line count and restore document name in title

    m_last_line = pDoc->m_total_lines;

    CString strOldTitle;

    GetParentFrame ()->GetWindowText (strOldTitle);

    // amend title if necessary (avoid flicker)
    if (strOldTitle != pDoc->m_mush_name)
      GetParentFrame ()->SetWindowText (pDoc->m_mush_name);

    }

  }

// returns true if outside range
bool CMUSHView::calculate_line_and_column (const CPoint & point, CClientDC & dc,
                                           int & line, int & col,
                                           const bool bHalfWay)
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

int lastline;
long lastx = 0;
bool bOutside = true;
int oldstyle = 0;
long pixel;

  dc.SelectObject(pDoc->m_font [0]);

  line = (point.y + pDoc->m_iPixelOffset + m_scroll_position.y) / pDoc->m_FontHeight;

// include partial last line if necessary

  lastline = pDoc->GetLastLine ();

// make sure our line is inside the file

  if (line < 0)
    line = 0;
  else
    if (line > lastline)
      line = lastline;
    else
      bOutside = false;

  CLine * pLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (line));

// calculate which column we must be at

  for (col = pixel = 0; 
      pixel < point.x && col <= pLine->len; 
      col++)
        {
        lastx = pixel;
        pixel = calculate_width (line, col, pDoc, dc) + pDoc->m_iPixelOffset;
        }

  col--;    // columns are zero-relative

// if we are 50% through this character, take the next one, otherwise take the previous one

  if (bHalfWay)     // if wanted (not for menus)
    {
    lastx += (pixel - lastx) / 2;

    if (point.x < lastx)
      col--;
    }

// make sure column is reasonable

  if (col < 0)
    col = 0;

  return bOutside;

  } // end of CMUSHView::calculate_line_and_column 

void CMUSHView::extend_selection (const int line, const int col)
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

  if (line > m_pin_line || (line == m_pin_line && col > m_pin_col))
    {
    m_selstart_line = m_pin_line;
    m_selstart_col =  m_pin_col;
    m_selend_line = line;
    m_selend_col = col;
    }
  else
    {
    m_selstart_line = line;
    m_selstart_col = col;
    m_selend_line = m_pin_line;
    m_selend_col =  m_pin_col;
    }

  // auto-copy selection to clipboard
  if (pDoc->m_bCopySelectionToClipboard)
    if (pDoc->m_bAutoCopyInHTML)
      OnEditCopyashtml ();
    else
      OnEditCopy ();

  } // end of CMUSHView::extend_selection  

int CompareMenu (const void * elem1, const void * elem2)
  {
  CString string1 = (*((CString *) elem1));
  CString string2 = (*((CString *) elem2));

  // strip off plugin IDs
  CStringList strList;

  StringToList (string1, ":", strList);
  string1 = strList.GetHead ();
  
  StringToList (string2, ":", strList);
  string2 = strList.GetHead ();

  return string1.CompareNoCase (string2);

  }   // end of CompareMenu

void CMUSHView::AliasMenu (CPoint point)
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

CPoint menupoint = point;

  CMenu menu;
	VERIFY(menu.LoadMenu(IDR_MXP_MENU));

	CMenu* pPopup = menu.GetSubMenu(0);
	ASSERT(pPopup != NULL);
	CWnd* pWndPopupOwner = this;

  pPopup->DeleteMenu (0, MF_BYPOSITION);  // get rid of dummy item

  int i = 0;

  CAlias * pAlias;
  CString strAliasName;

  for (POSITION pos = pDoc->m_AliasMap.GetStartPosition(); 
       pos && i < MXP_MENU_COUNT; 
       )
     {
     pDoc->m_AliasMap.GetNextAssoc (pos, strAliasName, pAlias);
     if (pAlias->strLabel.IsEmpty () ||
        !pAlias->bMenu ||
        !pAlias->bEnabled)
        continue;

     // remember what to send if they click on it
     strMXP_menu_item [i] = pAlias->strLabel;
     i++;
    } // end of all aliases

  // do plugins
  for (POSITION plugin_pos = pDoc->m_PluginList.GetHeadPosition (); 
        plugin_pos && i < MXP_MENU_COUNT; )
    {
    pDoc->m_CurrentPlugin = pDoc->m_PluginList.GetNext (plugin_pos);

    if (pDoc->m_CurrentPlugin->m_bEnabled)
      for (POSITION pos = pDoc->GetAliasMap ().GetStartPosition (); 
            pos && i < MXP_MENU_COUNT; )
        {
        pDoc->GetAliasMap ().GetNextAssoc (pos, strAliasName, pAlias);

         if (pAlias->strLabel.IsEmpty () ||
            !pAlias->bMenu ||
            !pAlias->bEnabled)
            continue;

         // remember what to send if they click on it
         strMXP_menu_item [i] = pAlias->strLabel;
         strMXP_menu_item [i] += ":";
         strMXP_menu_item [i] += pDoc->m_CurrentPlugin->m_strID;  // need to know which plugin
         i++;

        }  // end of scanning plugin aliases
    } // end of doing plugins list
  pDoc->m_CurrentPlugin = NULL;

  if (i == 0)
    {
     pPopup->AppendMenu (MF_STRING | MF_GRAYED, MXP_FIRST_MENU, "(no alias menu items)");
     strMXP_menu_item [0].Empty ();
    }
  else
    {
        // sort the array - otherwise we'll be all over the map :)
    qsort (strMXP_menu_item, 
           i,
           sizeof (CString),
           CompareMenu);

    for (int j = 0; j < i; j++)
      {
      CString strMenu = Replace (strMXP_menu_item [j], "_", " ");

      // strip off plugin id
      CStringList strList;
      StringToList (strMenu, ":", strList);
      strMenu = strList.GetHead ();

      // add menu item
      pPopup->AppendMenu (MF_STRING | MF_ENABLED, MXP_FIRST_MENU + j, strMenu);     

      // alias map lookup must be lower case
      strMXP_menu_item [j].MakeLower ();
      } // end of building menu

    } // end of some menu items


	while (pWndPopupOwner->GetStyle() & WS_CHILD)
		pWndPopupOwner = pWndPopupOwner->GetParent();

  ClientToScreen(&point);

  // without this line the auto-enable always set "no items" to active
  Frame.m_bAutoMenuEnable  = FALSE;

  iAction = ACTION_ALIAS;
	pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, 
                        point.x, 
                        point.y,
			                  pWndPopupOwner);

  // put things back how they were
  Frame.m_bAutoMenuEnable  = TRUE;


  } // end of CMUSHView::AliasMenu

// quick check to see if a string is a plugin ID
static bool IsPluginID (const char * sID)
  {
  // return if id wrong length
  if (strlen (sID) != PLUGIN_UNIQUE_ID_LENGTH)
    return false;

  for (const char * p = sID; *p; p++)
    if (!isxdigit (*p))
      return false;

  return true;
  }  // end of IsPluginID

// quick check to see if a string is a subroutine name
static bool IsSubName (const char * sName)
  {
const char * p = sName;

// check for A-Z, 0-9 or underscore
  for (int i = 0; *p && *p != '('; p++, i++)
    if (!isalnum (*p) && *p != '_')
      return false;

// can't be zero length
  if (i == 0)
    return false;

  return true;
  }  // end of IsSubName

void CMUSHView::OnLButtonDown(UINT nFlags, CPoint point) 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

int line,
    col;

  if (!pDoc->m_FontHeight)
    return;

  if (GetKeyState (VK_CONTROL) < 0)  // ctrl+click
    {
    AliasMenu (point);
	  return;
    } // end of ctrl+click

	// CView changes the viewport origin and mapping mode.
	// It's necessary to convert the point from device coordinates
	// to logical coordinates, such as are stored in the document.
	CClientDC dc(this);
	OnPrepareDC(&dc);
	dc.DPtoLP(&point);
  dc.SelectObject(pDoc->m_font [0]);


  m_last_mouse_position = point;


// new, for MXP - see if they clicked on a hyperlink

//  if (pDoc->m_bMXP)
  if (true)   // NJG
    {
    // find which line and column the mouse position is at
              
    calculate_line_and_column (point, dc, line, col, false);

    // find line

    CLine * pLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (line));

    unsigned int iStyle;
    CStyle * pStyle;
    POSITION foundpos;
    int iCol;

    // don't do it *past* end of last word
    long pixel = calculate_width (line, pLine->len, pDoc, dc) + pDoc->m_iPixelOffset;

    if (point.x < pixel &&
       pDoc->FindStyle (pLine, col, iCol, pStyle, foundpos))
      {
      iStyle = pStyle->iFlags;
        
      if (pStyle->pAction &&
          !pStyle->pAction->m_strAction.IsEmpty () &&
          pStyle->pAction->m_strAction.Find ("&text;") == -1)
        if ((iStyle & ACTIONTYPE) == ACTION_SEND ||
            (iStyle & ACTIONTYPE) == ACTION_PROMPT)
          {

          CString strActions = pStyle->pAction->m_strAction;   // action
          CString strHints = pStyle->pAction->m_strHint;    // hints, if any

          CStringList actionsList;

          StringToList (strActions, POPUP_DELIMITER, actionsList);

          CString strAction = actionsList.GetHead ();

          // just send first action (default action)

          // unpause the output window if wanted
          if (pDoc->m_bUnpauseOnSend && m_freeze)
            {
            m_freeze = false;
            addedstuff ();   
            }

          if ((iStyle & ACTIONTYPE) == ACTION_SEND && 
              (pLine->flags & COMMENT) == COMMENT)
            {
            // action_send on a note line will be executed, not sent
            //  (for the world.Hyperlink function)
            // However an action in the form !!pluginID:script(arg)
            // eg. !!753ba7e011f3c8943a885f18:mysub(1234)   
            // will be passed the nominted sub in the nominated plugin

            CPlugin * pPlugin = NULL;

            // rather elaborate test ...

            // a. Must start with !!
            // b. Must end with )
            // c. Must have a "(" in it
            // d. After the !! there must be 24 hex characters
            // e. After that must be a colon (:)
            // f. After the colon must be a valid subroutine name
            if (strAction.GetLength () >= PLUGIN_UNIQUE_ID_LENGTH + 5 &&
                strAction.Left (2) == "!!" &&
                strAction.Right (1) == ")" &&
                strAction.Find ("(") != -1 &&
                strAction.Mid (PLUGIN_UNIQUE_ID_LENGTH + 2, 1) == ":" && 
                IsPluginID (strAction.Mid (2, PLUGIN_UNIQUE_ID_LENGTH)) &&
                IsSubName (strAction.Mid (PLUGIN_UNIQUE_ID_LENGTH + 3)))
              { // correct syntax for plugin call
              CString strPluginID = strAction.Mid (2, PLUGIN_UNIQUE_ID_LENGTH);
              strAction = strAction.Mid (PLUGIN_UNIQUE_ID_LENGTH + 3);
              int iBracket = strAction.Find ("(");
              if (iBracket != -1)
                {
                CString strScriptName = strAction.Left (iBracket);
                strScriptName.TrimRight ();
                CString strArg = strAction.Mid (iBracket + 1);
                strArg = strArg.Left (strArg.GetLength () - 1); // drop trailing )
                long iResult = pDoc->CallPlugin (strPluginID, strScriptName, strArg);
                CString strName = strPluginID;  // default to ID
                if (iResult != eOK)
                  {
                  CPlugin * pPlugin = pDoc->GetPlugin (strPluginID);
                  if (pPlugin)
                    strName = pPlugin->m_strName;
                  } // end of finding plugin's name
                switch (iResult)
                  {
                  case eNoSuchPlugin:
                    pDoc->ColourNote ("white", "red", 
                          CFormat ("Plugin \"%s\" is not installed",
                                  (LPCTSTR) strName));
                    break;
                  case eNoSuchRoutine:
                    pDoc->ColourNote ("white", "red", 
                          CFormat ("Script routine \"%s\" is not in plugin %s",
                                  (LPCTSTR) strScriptName,
                                  (LPCTSTR) strName));
                    break;

                  case eErrorCallingPluginRoutine:
                    pDoc->ColourNote ("white", "red", 
                          CFormat ("An error occurred calling plugin %s",
                                  (LPCTSTR) strName));
                    break;

                  } // end of switch on errors
                }  // end of ( found (this should occur, really)
              }   // end of plugin existing
            else
              {  // plugin does not exist - just execute it
              pDoc->m_iExecutionDepth = 0;
              pDoc->Execute (strAction);
              }
            } // end of note hyperlink
          else
            {   // not note line

            if (pDoc->CheckConnected ())
              return;

            // send to command window?
            if ( (iStyle & ACTIONTYPE) == ACTION_PROMPT)
              {
              if (m_bottomview->CheckTyping (pDoc, strAction))
                return;             
              m_bottomview->SetCommand (strAction);
              }
            else
              {
              // send it
              pDoc->SendMsg (strAction, 
                             pDoc->m_bEchoHyperlinkInOutputWindow,
                             false,           // don't queue
                             pDoc->LoggingInput ());
              if (pDoc->m_bHyperlinkAddsToCommandHistory)
                m_bottomview->AddToCommandHistory (strAction);
              }

            // put the focus back in the send window

            OnKeysActivatecommandview ();
            }   // end of not execute wanted
	        return;
          } // end of ACTION_SEND  or  ACTION_PROMPT
        else
        if ((iStyle & ACTIONTYPE) == ACTION_HYPERLINK)
          {
          CString strAction = pStyle->pAction->m_strAction;

          // don't let them slip in arbitrary OS commands
          if (strAction.Left (7).CompareNoCase ("http://") != 0 &&
              strAction.Left (8).CompareNoCase ("https://") != 0 &&
              strAction.Left (7).CompareNoCase ("mailto:") != 0)
            ::AfxMessageBox(CFormat ("Hyperlink action \"%s\" - permission denied.", 
                            (const char *) strAction), 
                            MB_ICONEXCLAMATION);
          else
            if ((long) ShellExecute (Frame, _T("open"), strAction, NULL, NULL, SW_SHOWNORMAL) <= 32)
              ::AfxMessageBox(CFormat ("Unable to open the hyperlink \"%s\"", 
                              (const char *) strAction), 
                              MB_ICONEXCLAMATION);
          return;
          }  // end of ACTION_HYPERLINK

      } // end of column found

    } // end of MXP enabled

  calculate_line_and_column (point, dc, line, col, true);


  CRgn oldrgn;
  CRgn newrgn;

// Get old selection region

  get_selection (oldrgn);

// if clicked *inside* old selection, leave selected  
  if (oldrgn.PtInRegion (point))
    {
    oldrgn.DeleteObject ();
	  return;
    }

// if shift key down, extend (or shrink) the current selection

  if (nFlags & MK_SHIFT)
    extend_selection (line, col);
  else
    {     // shift key not down, just start a new selection
    m_selend_line = m_pin_line = m_selstart_line = line;
    m_selend_col = m_pin_col = m_selstart_col = col;
    }     // end of shift key not down

// Get new selection region

  get_selection (newrgn);

  // Combine previously selected with new selection, excluding union
  oldrgn.CombineRgn (&oldrgn, &newrgn, RGN_XOR);

  // Invalidate new stuff
  InvalidateRgn (&oldrgn);

#if REDRAW_DEBUG
  ShowInvalidatedRgn (this, oldrgn);
#endif

  // Finished with regions
  newrgn.DeleteObject ();
  oldrgn.DeleteObject ();

	SetCapture();       // Capture the mouse until button up.

	return;
	
	// CView::OnLButtonDown(nFlags, point);
}

void CMUSHView::OnLButtonUp(UINT nFlags, CPoint point) 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

int line,
    col;

  if (!pDoc->m_FontHeight)
    return;

	if (GetCapture() != this)
		return; // If this window (view) didn't capture the mouse,
				// then the user isn't selecting in this window.

	CClientDC dc(this);

	// CView changes the viewport origin and mapping mode.
	// It's necessary to convert the point from device coordinates
	// to logical coordinates, such as are stored in the document.
	OnPrepareDC(&dc);  // set up mapping mode and viewport origin
	dc.DPtoLP(&point);
  dc.SelectObject(pDoc->m_font [0]);

  CRgn oldrgn;
  CRgn newrgn;

// Get old selection region

  get_selection (oldrgn);

// find which line and column the mouse position is at
              
  calculate_line_and_column (point, dc, line, col);

// extend (or shrink) the current selection

  extend_selection (line, col);

// Get new selection region

  if (!get_selection (newrgn) && pDoc->m_bAutoFreeze)
     m_freeze = true;   // freeze output so they can copy or print it


  // Combine previously selected with new selection, excluding union
  oldrgn.CombineRgn (&oldrgn, &newrgn, RGN_XOR);

  // Invalidate new stuff
  InvalidateRgn (&oldrgn);

#if REDRAW_DEBUG
  ShowInvalidatedRgn (this, oldrgn);
#endif

  // Finished with regions
  newrgn.DeleteObject ();
  oldrgn.DeleteObject ();

	ReleaseCapture();   // Release the mouse capture established at
    	    					// the beginning of the mouse drag.

	return;
	
//	CView::OnLButtonUp(nFlags, point);
}

void CMUSHView::OnMouseMove(UINT nFlags, CPoint point) 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

int line,
    col;

  if (!pDoc->m_FontHeight)
    return;

	CClientDC dc(this);
	// CView changes the viewport origin and mapping mode.
	// It's necessary to convert the point from device coordinates
	// to logical coordinates, such as are stored in the document.
	OnPrepareDC(&dc);

	dc.DPtoLP(&point);
  dc.SelectObject(pDoc->m_font [0]);

  // first work out the line/column the mouse is over

  calculate_line_and_column (point, dc, line, col, false);

  // tooltips stuff

	if (::IsWindow(m_ToolTip.m_hWnd))
	{

  // test for changed line or column here

		if (line != m_nLastToolTipLine || col != m_nLastToolTipColumn)
		{
			// Use Activate() to hide the tooltip.
			m_ToolTip.Activate(FALSE);		
		}

		m_ToolTip.Activate(TRUE);
    m_nLastToolTipLine = line;
    m_nLastToolTipColumn = col;
	}

  // first work out the line/column the mouse is over

  calculate_line_and_column (point, dc, line, col, true);

  // end of tooltips stuff

CLine * pLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (line));

  // only if the user is currently drawing a new stroke by dragging
	// the captured mouse.

	if (GetCapture() != this)
		return; // If this window (view) didn't capture the mouse,
				// then the user isn't drawing in this window.

  m_last_mouse_position = point;

// handle the case where the mouse moves outside the client area

RECT r;

// see if the mouse is outside the visible window

  GetClientRect (&r);

  if (point.y > r.bottom)
    doLinedown ();
  else
  if (point.y < 0)
    doLineup ();

  CRgn oldrgn;
  CRgn newrgn;

// Get old selection region

  get_selection (oldrgn);

// extend (or shrink) the current selection

  extend_selection (line, col);

// Get new selection region

  if (!get_selection (newrgn) && pDoc->m_bAutoFreeze)
     m_freeze = true;   // freeze output so they can copy or print it

  // Combine previously selected with new selection, excluding union
  oldrgn.CombineRgn (&oldrgn, &newrgn, RGN_XOR);

  // Invalidate new stuff
  InvalidateRgn (&oldrgn);

#if REDRAW_DEBUG
  ShowInvalidatedRgn (this, oldrgn);
#endif

  // Finished with regions
  newrgn.DeleteObject ();
  oldrgn.DeleteObject ();

	return;

}


int CMUSHView::mouse_still_down (void)
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

int line,
    col;

  if (!pDoc->m_FontHeight)
    return 0;

  // only if the user is currently drawing a new stroke by dragging
	// the captured mouse.

	if (GetCapture() != this)
		return 0; // If this window (view) didn't capture the mouse,
				// then the user isn't drawing in this window.

	CClientDC dc(this);
	// CView changes the viewport origin and mapping mode.
	// It's necessary to convert the point from device coordinates
	// to logical coordinates, such as are stored in the document.
	OnPrepareDC(&dc);
  dc.SelectObject(pDoc->m_font [0]);

// mouse will still be in the same spot, or we would have got a "mouse move"

CPoint point = m_last_mouse_position;

// handle the case where the mouse moves outside the client area

RECT r;

// see if the mouse is outside the visible window

  GetClientRect (&r);

  if (point.y > r.bottom)
    {
    doLinedown ();
    m_last_mouse_position.y += pDoc->m_FontHeight;
    }
  else 
    if (point.y < 0)
      {
      doLineup ();
      m_last_mouse_position.y -= pDoc->m_FontHeight;
      }
    else
      return 0;   // don't want any more idle calls

  CRgn oldrgn;
  CRgn newrgn;

// Get old selection region

  get_selection (oldrgn);

// find which line and column the mouse position is at
              
  calculate_line_and_column (point, dc, line, col);

// extend (or shrink) the current selection

  extend_selection (line, col);
  
// Get new selection region

  get_selection (newrgn);

  // Combine previously selected with new selection, excluding union
  oldrgn.CombineRgn (&oldrgn, &newrgn, RGN_XOR);

  // Invalidate new stuff
  InvalidateRgn (&oldrgn);

#if REDRAW_DEBUG
  ShowInvalidatedRgn (this, oldrgn);
#endif

  // Finished with regions
  newrgn.DeleteObject ();
  oldrgn.DeleteObject ();


  	return 1;   // want another idle loop

  } // end of CMUSHView::mouse_still_down

bool CMUSHView::get_selection (CRgn & rgn)
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);


RECT r;
long start_textsize,
      end_textsize;

long right;
int oldstyle = 0;

GetClientRect (&r);
right = r.right;

CPoint pt = GetScrollPosition ();

	CClientDC dc(this);
	OnPrepareDC(&dc);

  // if selection past end of data return empty region
  if (m_selstart_line > pDoc->GetLastLine ())
    {
    rgn.CreateRectRgn (0, 0, 0, 0);
    return true;
    }

  start_textsize = calculate_width (m_selstart_line, m_selstart_col, pDoc, dc);
  end_textsize = calculate_width (m_selend_line, m_selend_col, pDoc, dc);

  if (start_textsize < 0)
    start_textsize = 0;

// starting and ending on the same line is a special case

  if (m_selstart_line == m_selend_line)
    {

    SetRect (&r, 
              pDoc->m_iPixelOffset + start_textsize,
              - pDoc->m_iPixelOffset + (m_selstart_line * pDoc->m_FontHeight) - pt.y,
              pDoc->m_iPixelOffset + end_textsize,
              - pDoc->m_iPixelOffset + ((m_selstart_line + 1) * pDoc->m_FontHeight) - pt.y);

    // end before or equal start means empty region
    // if we cannot create the region, create an empty one
    if (m_selend_col <= m_selstart_col ||
        rgn.CreateRectRgn (r.left, r.top, r.right, r.bottom) == 0)
      {
      rgn.CreateRectRgn (0, 0, 0, 0);
      return true;
      }
    return false;
    }

// do the first line, from startcol to the end

  SetRect (&r, 
            pDoc->m_iPixelOffset + start_textsize,
            - pDoc->m_iPixelOffset + (m_selstart_line * pDoc->m_FontHeight) - pt.y,
            pDoc->m_iPixelOffset + right,
            - pDoc->m_iPixelOffset + ((m_selstart_line + 1) * pDoc->m_FontHeight) - pt.y);

  CRgn firstrgn;
  CRgn middlergn;
  CRgn lastrgn;

  if (firstrgn.CreateRectRgn (r.left, r.top, r.right, r.bottom) == 0)
      firstrgn.CreateRectRgn (0, 0, 0, 0);

// do the middle lines, all columns

  if (m_selend_line > (m_selstart_line + 1))
    {
    SetRect (&r, 
              pDoc->m_iPixelOffset,
              - pDoc->m_iPixelOffset + ((m_selstart_line + 1) * pDoc->m_FontHeight) - pt.y,
              pDoc->m_iPixelOffset + right,
              - pDoc->m_iPixelOffset + ((m_selend_line) * pDoc->m_FontHeight) - pt.y);


    if (middlergn.CreateRectRgn (r.left, r.top, r.right, r.bottom) == 0)
      middlergn.CreateRectRgn (0, 0, 0, 0);

    }
  else
    middlergn.CreateRectRgn (0, 0, 0, 0);


// do the last line, from the start to endcol

  SetRect (&r, 
            pDoc->m_iPixelOffset,
            - pDoc->m_iPixelOffset + (m_selend_line * pDoc->m_FontHeight) - pt.y,
            pDoc->m_iPixelOffset + end_textsize,
            - pDoc->m_iPixelOffset + ((m_selend_line + 1) * pDoc->m_FontHeight) - pt.y);

  if (lastrgn.CreateRectRgn (r.left, r.top, r.right, r.bottom) == 0)
    lastrgn.CreateRectRgn (0, 0, 0, 0);


  // destination region must exist
  rgn.CreateRectRgn (0, 0, 0, 0);

  rgn.CombineRgn (&firstrgn, &middlergn, RGN_OR);
  bool bReturn = rgn.CombineRgn (&rgn, &lastrgn, RGN_OR) == NULLREGION;

  // delete old regions, not needed any more
  firstrgn.DeleteObject ();
  middlergn.DeleteObject ();
  lastrgn.DeleteObject ();

  return bReturn;   // true if empty region
  } // end of CMUSHView::get_selection

BOOL CMUSHView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
	
  if (nHitTest == HTCLIENT)
    {  

    // for MXP - turn cursor into hand over hyperlinks
//    if (pDoc->m_bMXP)        // NJG
    if (true)
      {

      // so where is the cursor
	    CPoint point;
	    ::GetCursorPos(&point);
	    ScreenToClient(&point);

	    CClientDC dc(this);
	    OnPrepareDC(&dc);
	    dc.DPtoLP(&point);
      dc.SelectObject(pDoc->m_font [0]);

      int line,
          col;

      // what line and column is that
      calculate_line_and_column (point, dc, line, col, false);

      // find the line data

      CLine * pLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (line));

      unsigned int iStyle;
      CStyle * pStyle;
      int iCol;
      POSITION foundpos;

      // don't show finger pointer *past* end of last word
      long pixel = calculate_width (line, pLine->len, pDoc, dc) + pDoc->m_iPixelOffset;

      if (pDoc->FindStyle (pLine, col, iCol, pStyle, foundpos))
        {
        iStyle = pStyle->iFlags;
        if ((iStyle & ACTIONTYPE) &&      // we have an action (send, hyperlink)
             pStyle->pAction &&
            !pStyle->pAction->m_strAction.IsEmpty () &&   // there is something to send
            pStyle->pAction->m_strAction.Find ("&text;") == -1 &&  // we know what &text; is
            CStaticLink::g_hCursorLink &&   // we have a finger cursor
            point.x < pixel)    // the mouse is not past the RH side of the line
          {
          ::SetCursor (CStaticLink::g_hCursorLink);
          return TRUE;
          } // we have an action!

        } // end of column found

      }   // end of MXP-enabled

    // not a hyperlink - just display the I-beam

    ::SetCursor (::LoadCursor (NULL, IDC_IBEAM));
    return TRUE;
    }
  else
    return CView::OnSetCursor(pWnd, nHitTest, message);

}


void CMUSHView::did_jump (void) 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

m_selstart_line -= JUMP_SIZE;
m_selend_line -= JUMP_SIZE;
m_pin_line -= JUMP_SIZE;

if (m_selstart_line < 0)
  {
  m_selstart_line = 0;
  m_selstart_col = 0;
  }

if (m_selend_line < 0)
  {
  m_selend_line = 0;
  m_selend_col = 0;
  }

if (m_pin_line < 0)
  {
  m_pin_line = 0;
  m_pin_col = 0;
  }

// if frozen, adjust scroll position so same piece of text stays visible

if (m_freeze)
  {

  CPoint orig_top = GetScrollPosition ();
  
	orig_top.y -= JUMP_SIZE * pDoc->m_FontHeight;

  if (orig_top.y < 0)
    {
    m_freeze = FALSE;
    orig_top.y = 0;
    }


  ScrollToPosition (orig_top, App.m_bSmoothScrolling);
  if (orig_top.y == 0)
    {
    Invalidate ();
#if REDRAW_DEBUG
    RECT r;
    GetClientRect (&r);
    ShowInvalidatedRect (this, r);
#endif
    }

  }  // end of being frozen

}   // end of did_jump


void CMUSHView::doStart (void)
  {
  OnTestStart ();
  }


void CMUSHView::doEnd (void)
  {
  OnTestEnd ();
  }


void CMUSHView::doPageup (void)
  {
  OnTestPageup ();
  }


void CMUSHView::doPagedown (void)
  {
  OnTestPagedown ();
  }

void CMUSHView::OnTestLinedown() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

RECT r;
int lastline;

GetClientRect (&r);

CPoint pt = GetScrollPosition ();

pt.y += pDoc->m_FontHeight;

// include partial last line if necessary

lastline = pDoc->GetLastLine ();

//if (pt.y > (pDoc->m_iPixelOffset + (lastline * pDoc->m_FontHeight) - r.bottom))
//	pt.y = pDoc->m_iPixelOffset + (lastline * pDoc->m_FontHeight) - r.bottom;

if (pt.y > ((lastline * pDoc->m_FontHeight) - r.bottom))
	pt.y =  (lastline * pDoc->m_FontHeight) - r.bottom;

ScrollToPosition (pt, App.m_bSmoothScrolling);

}

void CMUSHView::OnTestLineup() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

CPoint pt = GetScrollPosition ();

pt.y -= pDoc->m_FontHeight;

if (pt.y < 0)
	pt.y = 0;

ScrollToPosition (pt, App.m_bSmoothScrolling);

}


void CMUSHView::doLineup (void)
  {
  OnTestLineup ();
  }


void CMUSHView::doLinedown (void)
  {
  OnTestLinedown ();
  }

void CMUSHView::OnEditCopy() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

CString strSelection;

  if (GetSelection (strSelection))
    return;     // error in producing selection

  putontoclipboard (strSelection, pDoc->m_bUTF_8);

// put the focus back in the send window

  OnKeysActivatecommandview ();


}

void CMUSHView::OnUpdateNeedSelection(CCmdUI* pCmdUI) 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

long startcol,
     endcol;

  CLine * pStartLine,
        * pEndLine;

  pStartLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (m_selstart_line));
  pEndLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (m_selend_line));
  
  startcol  = MIN (m_selstart_col, pStartLine->len);
  endcol    = MIN (m_selend_col, pEndLine->len);

  pCmdUI->Enable (m_selend_line > m_selstart_line || 
                (m_selend_line == m_selstart_line && 
                 endcol > startcol));


}

BOOL CMUSHView::OnEraseBkgnd(CDC* pDC) 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

CBrush backbr;
CRect rect;

  if (pDoc->m_bCustom16isDefaultColour)
    backbr.CreateSolidBrush (pDoc->TranslateColour (pDoc->m_customback [15]));
  else
    backbr.CreateSolidBrush (pDoc->TranslateColour (pDoc->m_normalcolour [BLACK]));
  
  pDC->GetClipBox (&rect);

  pDC->SetBkMode (OPAQUE);
  pDC->FillRect (&rect, &backbr);

  return TRUE;
}

void CMUSHView::OnKeysActivatecommandview() 
{
	m_owner_frame->SetActiveView((CView*) m_bottomview);
}


void CMUSHView::OnSize(UINT nType, int cx, int cy) 
{
	CView::OnSize(nType, cx, cy);
	
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);


// find page size

RECT r;

  GetClientRect (&r);

// find last line

int lastline;

  lastline = pDoc->GetLastLine ();

CSize sizeTotal (pDoc->m_nWrapColumn * pDoc->m_FontWidth, lastline * pDoc->m_FontHeight),
      sizePage  (r.right - r.left - pDoc->m_iPixelOffset, r.bottom - r.top),
      sizeLine  (pDoc->m_FontWidth, pDoc->m_FontHeight);

	SetScrollSizes (sizeTotal, sizePage, sizeLine);

  if (m_bAtBufferEnd)
    OnTestEnd ();   

  Frame.FixUpTitleBar ();   // in case we need to add the mud name to the title bar

// this is for the guy that wants to fit the max text he can in his window,
// after resizing it

WINDOWPLACEMENT wp;
wp.length = sizeof (wp);
GetOwner()->GetOwner()->GetWindowPlacement (&wp);

// don't resize if not active, or minimized, or closed
if (wp.showCmd != SW_MINIMIZE &&
    pDoc->m_iConnectPhase == eConnectConnectedToMud &&
    (
    pDoc->m_pActiveOutputView == this  ||
    pDoc->m_pActiveCommandView == m_bottomview
    ))

  if (pDoc->m_bAutoWrapWindowWidth && 
      !pDoc->m_font_name.IsEmpty () &&
      pDoc->m_font_height > 0)
    {

    CDC dc;

    dc.CreateCompatibleDC (NULL);

    int lfHeight = -MulDiv(pDoc->m_font_height, dc.GetDeviceCaps(LOGPIXELSY), 72);
    CFont font;

     font.CreateFont(lfHeight, // int nHeight, 
				    0, // int nWidth, 
				    0, // int nEscapement, 
				    0, // int nOrientation, 
				    pDoc->m_font_weight, // int nWeight, 
				    0, // BYTE bItalic, 
				    0, // BYTE bUnderline, 
            0, // BYTE cStrikeOut, 
            pDoc->m_font_charset, // BYTE nCharSet, 
            0, // BYTE nOutPrecision, 
            0, // BYTE nClipPrecision, 
            0, // BYTE nQuality, 
            MUSHCLIENT_FONT_FAMILY, // BYTE nPitchAndFamily,    // was FF_DONTCARE
            pDoc->m_font_name);// LPCTSTR lpszFacename );

      // Get the metrics of the font.

      dc.SelectObject(font);

      TEXTMETRIC tm;
      dc.GetTextMetrics(&tm);


    int iWidth = (r.right - r.left - pDoc->m_iPixelOffset) / tm.tmAveCharWidth;

    // ensure in range that we allow
    if (iWidth < 20)
      iWidth = 20;
    if (iWidth > MAX_LINE_WIDTH)
      iWidth = MAX_LINE_WIDTH;

  // We must adjust the current line to allow for the new wrap size

    if (pDoc->m_pCurrentLine)     // a new world might not have a line yet
      {
      // save current line text
      CString strLine = CString (pDoc->m_pCurrentLine->text, pDoc->m_pCurrentLine->len);

  #ifdef USE_REALLOC
      pDoc->m_pCurrentLine->text  = (char *) realloc (pDoc->m_pCurrentLine->text, 
                                               MAX (pDoc->m_pCurrentLine->len, iWidth) 
                                               * sizeof (char));
  #else
      delete [] pDoc->m_pCurrentLine->text;
      pDoc->m_pCurrentLine->text = new char [MAX (pDoc->m_pCurrentLine->len, iWidth)];
  #endif

      // put text back
      memcpy (pDoc->m_pCurrentLine->text, (LPCTSTR) strLine, pDoc->m_pCurrentLine->len);
      ASSERT (pDoc->m_pCurrentLine->text);
    
      }   // end of having a current line

    pDoc->m_nWrapColumn = iWidth;

    pDoc->SendWindowSizes (iWidth);

    }   // end of auto-wrap wanted
}

void CMUSHView::OnDisplayFreezeoutput() 
{
  m_freeze = !m_freeze;

  if (!m_freeze)
    OnTestEnd ();   
	
}

void CMUSHView::OnUpdateDisplayFreezeoutput(CCmdUI* pCmdUI) 
{

  pCmdUI->SetCheck (m_freeze);
  pCmdUI->Enable (TRUE);

}

void CMUSHView::OnUpdateStatuslineFreeze_helper (CCmdUI* pCmdUI) 
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

int iNewPauseStatus = eNotPaused;

  m_bAtBufferEnd = GetScrollPos (SB_VERT) >= GetScrollLimit(SB_VERT);

// calculate pause status

   if (pDoc->m_iConnectPhase != eConnectConnectedToMud)
     iNewPauseStatus = eClosed;
   else
    if (m_freeze)
      {
       iNewPauseStatus = ePaused;
       if (!m_bAtBufferEnd)
         iNewPauseStatus = eMore;
      }

// same pause status? just exit

  if (m_iPauseStatus == iNewPauseStatus)
    return;

  switch (iNewPauseStatus)
    {
    default: return;
    case eNotPaused:  Frame.m_wndStatusBar.SendMessage 
                        (SB_SETTEXT, Frame.m_nPauseItem, (LPARAM) "");
                      break;
    case ePaused:     Frame.m_wndStatusBar.SendMessage 
                        (SB_SETTEXT, Frame.m_nPauseItem, (LPARAM) "PAUSE");
                      break;
    case eClosed:     Frame.m_wndStatusBar.SendMessage 
                        (SB_SETTEXT, Frame.m_nPauseItem, (LPARAM) "CLOSED");
                      break;
    case eMore:       Frame.m_wndStatusBar.SendMessage 
                        (SB_SETTEXT, SBT_OWNERDRAW | Frame.m_nPauseItem, 
                                  (LPARAM) "MORE");
                      break;      // draw MORE in inverse
    } // end of switch

// remember for next time

  m_iPauseStatus = iNewPauseStatus;


  }

void CMUSHView::OnUpdateStatuslineFreeze(CCmdUI* pCmdUI) 
{
OnUpdateStatuslineFreeze_helper (pCmdUI);
}

void CMUSHView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView) 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

  m_iPauseStatus = ePauseUninitialised;

	if (bActivate)
	  {

    pDoc->m_pActiveOutputView = this;
    Frame.FixUpTitleBar ();
    if (pDoc->m_new_lines && App.m_pActivityDoc)
      {
      pDoc->m_new_lines = 0;
      App.m_bUpdateActivity = TRUE;
      }
    else
      pDoc->m_new_lines = 0;	  

// make sure title is MUSH name

    FixupTitle ();

    // execute "get focus" script
    if (pDeactiveView != m_bottomview) // don't worry about swapping with bottom view
      {
      if (pDoc->m_ScriptEngine)
        {
        DISPPARAMS params = { NULL, NULL, 0, 0 };
        long nInvocationCount = 0;

        pDoc->ExecuteScript (pDoc->m_dispidWorldGetFocus, 
                     pDoc->m_strWorldGetFocus,
                     eWorldAction,
                     "world get focus", 
                     "getting focus",
                     params, 
                     nInvocationCount); 
        } // end of executing get focus script

      if (!pDoc->m_bWorldClosing)
        {

        // now do plugins "get focus"
        CPlugin * pSavedPlugin = pDoc->m_CurrentPlugin;
        pDoc->m_CurrentPlugin = NULL;

        // tell each plugin what we have received
        for (POSITION pluginpos = pDoc->m_PluginList.GetHeadPosition(); pluginpos; )
          {
          CPlugin * pPlugin = pDoc->m_PluginList.GetNext (pluginpos);

          if (!(pPlugin->m_bEnabled))   // ignore disabled plugins
            continue;

          // see what the plugin makes of this,
          pPlugin->ExecutePluginScript (ON_PLUGIN_GETFOCUS, pPlugin->m_dispid_plugin_get_focus);
          }   // end of doing each plugin

        pDoc->m_CurrentPlugin = pSavedPlugin;

        } // end of world not closing
      
      }   // end of executing "get focus" scripts
    // make sure status line is updated
    pDoc->ShowStatusLine ();

	  }
	else
    {
    pDoc->m_pActiveOutputView = NULL;
    Frame.FixUpTitleBar ();

    // execute "Lose focus" script
    if (pActivateView != m_bottomview) // don't worry about swapping with bottom view
      {
      if (pDoc->m_ScriptEngine)
        {
        DISPPARAMS params = { NULL, NULL, 0, 0 };
        long nInvocationCount = 0;

        pDoc->ExecuteScript (pDoc->m_dispidWorldLoseFocus,  
                     pDoc->m_strWorldLoseFocus,
                     eWorldAction,
                     "world lose focus", 
                     "losing focus",
                     params, 
                     nInvocationCount); 
        } // end of executing lose focus script

      if (!pDoc->m_bWorldClosing)
        {
        // now do plugins "lose focus"
        CPlugin * pSavedPlugin = pDoc->m_CurrentPlugin;
        pDoc->m_CurrentPlugin = NULL;

        // tell each plugin what we have received
        for (POSITION pluginpos = pDoc->m_PluginList.GetHeadPosition(); pluginpos; )
          {
          CPlugin * pPlugin = pDoc->m_PluginList.GetNext (pluginpos);

          if (!(pPlugin->m_bEnabled))   // ignore disabled plugins
            continue;

          // see what the plugin makes of this,
          pPlugin->ExecutePluginScript (ON_PLUGIN_LOSEFOCUS, pPlugin->m_dispid_plugin_lose_focus);
          }   // end of doing each plugin

        pDoc->m_CurrentPlugin = pSavedPlugin;
        } // end of world not closing
      }  // end of executing "Lose focus" scripts

    // make sure status line is updated
    Frame.SetStatusMessage (STATUS_NORMAL);
    }

  
	CView::OnActivateView(bActivate, pActivateView, pDeactiveView);

  // update which world has the tick
  if (App.m_pActivityView && App.m_pActivityDoc)
      App.m_pActivityDoc->UpdateAllViews (NULL);


}

void CMUSHView::OnWindowMinimize() 
{
	
	if (GetParentFrame ()->IsIconic ())
	  GetParentFrame ()->ShowWindow(SW_RESTORE);
  else
	  GetParentFrame ()->ShowWindow(SW_MINIMIZE);
		
}

void CMUSHView::OnUpdateWindowMinimize(CCmdUI* pCmdUI) 
{
	if (GetParentFrame ()->IsIconic ())
    pCmdUI->SetCheck (TRUE);
  else
    pCmdUI->SetCheck (FALSE);
}

void CMUSHView::FixupTitle (void)
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

CString strTitle = pDoc->m_mush_name;

// don't bother showing "[open]" if connected - it is kind of annoying

  if (pDoc->m_iConnectPhase != eConnectConnectedToMud)
    {
    strTitle += " [";
    strTitle += GetConnectionStatus (pDoc->m_iConnectPhase);

    switch (pDoc->m_iConnectPhase)
      {
      case eConnectMudNameLookup:        
        strTitle += ": ";
        strTitle += pDoc->m_server; 
        break; 
      case eConnectProxyNameLookup:      
        strTitle += ": ";
        strTitle += pDoc->m_strProxyServerName; 
        break; 
      case eConnectConnectingToMud:      
        strTitle += ": ";
        strTitle += inet_ntoa (pDoc->m_sockAddr.sin_addr);
        strTitle += CFormat (", port %d]", pDoc->m_port);
        break; 
      case eConnectConnectingToProxy:    
        strTitle += ": ";
        strTitle += inet_ntoa (pDoc->m_ProxyAddr.sin_addr);
        strTitle += CFormat (", port %d]", pDoc->m_iProxyServerPort);
        break; 
      } // end of switch

    strTitle += "]";
    } // end of not connected

  CString strOldTitle;

  GetParentFrame ()->GetWindowText (strOldTitle);

  // only change title if necessary, to avoid flicker
  if (strTitle != strOldTitle)
    {
    GetParentFrame ()->SetWindowText (strTitle);
    if (Frame.m_wndMDITabs.InUse ())
      Frame.m_wndMDITabs.Update ();
    }

  } // end of FixupTitle

void CMUSHView::OnContextMenu(CWnd*, CPoint point)
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

		if (point.x == -1 && point.y == -1)
      {
			//keystroke invocation
			CRect rect;
			GetClientRect(rect);
			ClientToScreen(rect);

			point = rect.TopLeft();
			point.Offset(5, 5);
		}

// new, for MXP - see if they clicked on a hyperlink

  CPoint menupoint = point;
  ScreenToClient (&point);

  if (pDoc->m_bMXP && pDoc->m_FontHeight)
    {
    int line,
        col;


	  // CView changes the viewport origin and mapping mode.
	  // It's necessary to convert the point from device coordinates
	  // to logical coordinates, such as are stored in the document.
	  CClientDC dc(this);
	  OnPrepareDC(&dc);
	  dc.DPtoLP(&point);
    dc.SelectObject(pDoc->m_font [0]);

  // find which line and column the mouse position is at
            
    calculate_line_and_column (point, dc, line, col, false);

    // find line

    CLine * pLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (line));

    unsigned int iStyle;
    CStyle * pStyle;
    POSITION foundpos;
    int iCol;

    // don't do it *past* end of last word
    long pixel = calculate_width (line, pLine->len, pDoc, dc) + pDoc->m_iPixelOffset;

    if (point.x < pixel &&
        pDoc->FindStyle (pLine, col, iCol, pStyle, foundpos))
      {
      iStyle = pStyle->iFlags;
      if (pStyle->pAction &&
          !pStyle->pAction->m_strAction.IsEmpty () &&
          pStyle->pAction->m_strAction.Find ("&text;") == -1)
        if ((iStyle & ACTIONTYPE) == ACTION_SEND ||
            (iStyle & ACTIONTYPE) == ACTION_PROMPT)
          {

          CString strActions = pStyle->pAction->m_strAction;   // action
          CString strHints = pStyle->pAction->m_strHint;     // hints, if any

          CStringList actionsList,
                      hintsList;

          StringToList (strActions, POPUP_DELIMITER, actionsList);
          StringToList (strHints,   POPUP_DELIMITER, hintsList);

          // more hints than actions? first one must be the tooltip text

          if (hintsList.GetCount () > actionsList.GetCount ())
             hintsList.RemoveHead ();

          CMenu menu;
		      VERIFY(menu.LoadMenu(IDR_MXP_MENU));

		      CMenu* pPopup = menu.GetSubMenu(0);
		      ASSERT(pPopup != NULL);
		      CWnd* pWndPopupOwner = this;

          pPopup->DeleteMenu (0, MF_BYPOSITION);  // get rid of dummy item

          int iCount = MIN (actionsList.GetCount (), MXP_MENU_COUNT);

          // build up menu
          for (int i = 0; i < iCount; i++)
            {
            CString strAction = actionsList.RemoveHead ();
            CString strHint;

            // if we have a hint, take it
            if (!hintsList.IsEmpty ())
              strHint = hintsList.RemoveHead ();
            else
              strHint = strAction;    // otherwise use the action

            // ampersands won't come out right
            strHint.Replace ("&", "&&");

            // add menu item
            pPopup->AppendMenu (MF_STRING | MF_ENABLED, MXP_FIRST_MENU + i, strHint);

            if (i == 0)
              SetMenuDefaultItem(pPopup->m_hMenu, 0, MF_BYPOSITION);

            // remember what to send if they click on it
            strMXP_menu_item [i] = strAction;
            }

          iAction = iStyle & ACTIONTYPE;
		      while (pWndPopupOwner->GetStyle() & WS_CHILD)
			      pWndPopupOwner = pWndPopupOwner->GetParent();


		      pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, 
                                menupoint.x, 
                                menupoint.y,
			                          pWndPopupOwner);

	        return;
          } // we have ACTION_SEND or ACTION_PROMPT
        else
        if ((iStyle & ACTIONTYPE) == ACTION_HYPERLINK)
          {

          CMenu menu;
		      VERIFY(menu.LoadMenu(IDR_MXP_MENU));

		      CMenu* pPopup = menu.GetSubMenu(0);
		      ASSERT(pPopup != NULL);
		      CWnd* pWndPopupOwner = this;

          pPopup->DeleteMenu (0, MF_BYPOSITION);  // get rid of dummy item

          // add menu item
          pPopup->AppendMenu (MF_STRING | MF_ENABLED, MXP_FIRST_MENU, pStyle->pAction->m_strAction);

          SetMenuDefaultItem(pPopup->m_hMenu, 0, MF_BYPOSITION);

          // remember what to send if they click on it
          strMXP_menu_item [0] = pStyle->pAction->m_strAction;

          iAction = iStyle & ACTIONTYPE;
		      while (pWndPopupOwner->GetStyle() & WS_CHILD)
			      pWndPopupOwner = pWndPopupOwner->GetParent();

		      pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, 
                                menupoint.x, 
                                menupoint.y,
			                          pWndPopupOwner);

	        return;
          }  // end of ACTION_HYPERLINK

      } // end of column found

    }   // end of MXP active

		CMenu menu;
		VERIFY(menu.LoadMenu(CG_IDR_POPUP_MUSHVIEW));

		CMenu* pPopup = menu.GetSubMenu(0);
		ASSERT(pPopup != NULL);
		CWnd* pWndPopupOwner = this;
    CMenu mainmenu;

    // in full-screen mode, give access to all menu items
    if (Frame.IsFullScreen ())
      {
		  VERIFY(mainmenu.LoadMenu(IDR_MUSHCLTYPE));

      pPopup->AppendMenu (MF_SEPARATOR, 0, ""); 
      pPopup->AppendMenu (MF_POPUP | MF_ENABLED, (UINT ) mainmenu.m_hMenu, 
                          "Main Menus");     

      }

		while (pWndPopupOwner->GetStyle() & WS_CHILD &&
          pWndPopupOwner != pWndPopupOwner->GetParent())
			pWndPopupOwner = pWndPopupOwner->GetParent();

    pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, menupoint.x, menupoint.y,
			pWndPopupOwner);
}

void CMUSHView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{

// if typing is received in top view, activate bottom view and pass character on

  OnKeysActivatecommandview ();  
  m_bottomview->SendMessage(WM_CHAR, nChar, nRepCnt | (nFlags << 16));

  }

void CMUSHView::OnDisplayFind() 
{
m_bottomview->DoFind(false);
}

void CMUSHView::OnDisplayFindagain() 
{
m_bottomview->DoFind(true);
}


void CMUSHView::PrintWorld (void)
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

int pages = 0;
int line = 0;

POSITION pos;
CLine * pLine;

CLine * pStartLine,
      * pEndLine;

int thiscol,
    thislen,
    startcol,
    endcol;
short style;

  pStartLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (m_selstart_line));
  pEndLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (m_selend_line));

BOOL bHaveSelection = m_selend_line > m_selstart_line || 
                      (m_selend_line == m_selstart_line &&
                      m_selend_col > m_selstart_col);

// printer control block for printing

t_print_control_block pcb;

// count pages in print file - allow 4 lines for header and footer
  
  pages = (pDoc->m_LineList.GetCount () + (App.m_nPrinterLinesPerPage - 4) - 1) /  (App.m_nPrinterLinesPerPage - 4);
  
// attempt to open the printer

  if (print_start_document (pcb, 
                            pDoc->m_mush_name, 
                            1, 
                            pages,
                            App.m_nPrinterLeftMargin,
                            App.m_nPrinterTopMargin,
                            App.m_nPrinterLinesPerPage,
                            App.m_nPrinterFontSize,
                            App.m_nPrinterFontSize,   // line spacing
                            App.m_strPrinterFont,
                            bHaveSelection))
    return;

  Frame.SetStatusMessageNow ("Printing world...");

  unsigned int current_line = App.m_nPrinterLinesPerPage;   // force new page immediately

  if (pcb.pd->m_pd.Flags & PD_SELECTION)
    {
	  line = m_selstart_line;
    pos = pDoc->GetLinePosition (m_selstart_line);
    }
  else
    pos = pDoc->m_LineList.GetHeadPosition ();

  
  while (pos)
    {

// get the line data

    pLine = pDoc->m_LineList.GetNext (pos);

// do footer and header if required

    if (current_line > (App.m_nPrinterLinesPerPage - 2))
      {

// do footer if not first page

      if (pcb.current_page > 0)
        {
        if (print_printline (pcb, 1, ""))     // first do a blank line
          break;

        print_font (pcb, FONT_BOLD);

        if (print_printline (pcb, 1, "Page %ld", pcb.current_page))     // then print the page number
          break;

        print_font (pcb, FONT_NORMAL);

        print_end_page (pcb);

        }   // end of not first page

// now do page header

      print_start_page (pcb);

      print_font (pcb, FONT_BOLD | FONT_UNDERLINE);

      CString strTime;
      strTime = pLine->m_theTime.Format ("%A, %B %d, %Y, %#I:%M %p");

      if (print_printline (pcb, 2, "%s - %s", 
                            (LPCTSTR) pDoc->m_mush_name,
                            (LPCTSTR) strTime)) 
        break;

      print_font (pcb, FONT_NORMAL);

      current_line = 2;     // back to top of page
        
      }  // end of past end of page

 // finished header and footer, print the current line

// break line up into pieces which have the same style, and then print in that style

// start by calculating the start column - will be different for the first line

    startcol = 0;
    if ((pcb.pd->m_pd.Flags & PD_SELECTION) && 
      (line == m_selstart_line))
      startcol = m_selstart_col;

// and the end column - will be different for the last line

    endcol = pLine->len;
    if ((pcb.pd->m_pd.Flags & PD_SELECTION) && 
      (line == m_selend_line))
      endcol = m_selend_col;

    CStyle * pStyle;
    int i = 0;

    // find first style
    for (POSITION stylepos = pLine->styleList.GetHeadPosition(); stylepos; )
      {
      pStyle = pLine->styleList.GetNext (stylepos);
      i += pStyle->iLength;
      if (i >= startcol)
        break;    // found first style
      }

    // how much do we do of this style?
    thislen = i - startcol;
    int len = endcol - startcol;
    int cols_to_go = len;

    for (thiscol = startcol; thiscol < endcol; )
      {

      // don't overshoot
      thislen = MIN (cols_to_go, thislen);

      style = pStyle->iFlags & STYLE_BITS;

      if ((style & COLOURTYPE) == COLOUR_ANSI)
        {
        if (style & HILITE)
          print_font (pcb, pDoc->m_nBoldPrintStyle [pStyle->iForeColour]);
        else
          print_font (pcb, pDoc->m_nNormalPrintStyle [pStyle->iForeColour]);
        }
      else
        {
        if (style & HILITE)
          print_font (pcb, pDoc->m_nBoldPrintStyle [WHITE]);
        else
          print_font (pcb, pDoc->m_nNormalPrintStyle [WHITE]);
        }

      if (print_printline (pcb, 0, "%.*s", thislen, &pLine->text [thiscol]))
        break;
           
      thiscol += thislen;
      cols_to_go -= thislen;  // this many fewer to go
 
      if (!stylepos)
        break;  // no more styles

      // get next style in sequence
      pStyle = pLine->styleList.GetNext (stylepos);
      thislen = pStyle->iLength;

      } // end of this group of the same colour

    print_font (pcb, FONT_NORMAL);

    if (print_printline (pcb, 1, ""))   // move onto next line
      break;

    current_line++;
    line++;

// if printing selection, stop when past end of it

    if ((pcb.pd->m_pd.Flags & PD_SELECTION) && 
      ((line > m_selend_line && m_selend_col > 0) ||    // overshoot if we have stuff on last line
      (line >= m_selend_line && m_selend_col == 0))
      )
      break;

    }

// do final page footer

  if (!pcb.cancelled)
    {
    while (current_line <= (App.m_nPrinterLinesPerPage - 2))
      {
      if (print_printline (pcb, 1, ""))     // first do a blank line
        break;
      current_line++;
      }
      
    if (!pcb.cancelled)
      {
      print_font (pcb, FONT_BOLD);

      print_printline (pcb, 1, "Page %ld", pcb.current_page); 

      print_font (pcb, FONT_NORMAL);
      } 

    }   // end of doing page footer

// terminate the report by sending out the last page

  print_end_page (pcb);
  
  print_end_document (pcb);

  if (pcb.cancelled)
    ::AfxMessageBox ("Printing cancelled");

  pDoc->ShowStatusLine ();
  

  } // end of CMUSHView::PrintWorld

void CMUSHView::OnFilePrintWorld() 
{
PrintWorld ();
}

void CMUSHView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
  SCROLLINFO ScrollInfo;

  GetScrollInfo (SB_VERT, &ScrollInfo, SIF_ALL);

  RemoveToolTip ();

  switch (nSBCode)
    {
    case SB_THUMBTRACK:   // Drag scroll box to specified position. The current position is provided in nPos.
    case SB_THUMBPOSITION:// Scroll to the absolute position. The current position is provided in nPos.
          {
          ScrollToPosition (CPoint (0, ScrollInfo.nTrackPos), false);
          break;
          }
    case SB_BOTTOM:       //  Scroll to bottom.
          OnTestEnd ();
          break;
    case SB_ENDSCROLL:    //  End scroll.
          // what does this do? nothing?
          break;
    case SB_LINEDOWN:     //  Scroll one line down.
          OnTestLinedown ();
          break;
    case SB_LINEUP:       //  Scroll one line up.
          OnTestLineup ();
          break;
    case SB_PAGEDOWN:     //  Scroll one page down.
          OnTestPagedown();
          break;
    case SB_PAGEUP:       //  Scroll one page up.
          OnTestPageup();
          break;
    case SB_TOP:          //  Scroll to top.
          OnTestStart ();
          break;

    } // end of switch

}

void CMUSHView::OnFileSaveselection() 
{
	  CFileDialog	dlg(FALSE,						// FALSE for FileSave
					  "txt",						// default extension
					  "selection.txt",
					  OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT|OFN_PATHMUSTEXIST,
					  "Text files (*.txt)|*.txt||",
					  this);
	  dlg.m_ofn.lpstrTitle = "Saved selection";

	  if (dlg.DoModal() != IDOK)
		  return;

	  try
	    {
		  CFile	f (dlg.GetPathName(), CFile::modeCreate|CFile::modeWrite|CFile::shareExclusive);

      if (GetSelection (f))
        return;     // error in producing selection
    
      }
	  catch (CException* e)
	  {
		  e->ReportError();
		  e->Delete();
	  }
}    // end of CMUSHView::OnFileSaveselection

// gets the current selection and writes it to a file - returns true if failed

bool CMUSHView::GetSelection(CFile & f) 
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

long startcol,
     endcol;

int i;

// return if no valid selection

  CLine * pStartLine,
        * pEndLine;

  pStartLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (m_selstart_line));
  pEndLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (m_selend_line));
  
  startcol  = MIN (m_selstart_col, pStartLine->len);
  endcol    = MIN (m_selend_col, pEndLine->len);

  if (!(m_selend_line > m_selstart_line || 
              (m_selend_line == m_selstart_line && 
               endcol > startcol)))
    return true;

  try
    {

    CArchive ar(&f, CArchive::store);

  // starting and ending on the same line is a special case

    if (m_selend_line == m_selstart_line)
      ar.Write (&pStartLine->text [startcol], endcol - startcol);
    else
      {

  // do first line

      ar.Write (&pStartLine->text [startcol], pStartLine->len - startcol);

      if (pStartLine->hard_return)
        ar.WriteString (ENDLINE);

  // do middle lines

      POSITION pos = pDoc->GetLinePosition (m_selstart_line + 1);

      for (i = m_selstart_line + 1; i < m_selend_line && pos; i++)
        {
        CLine * pLine = pDoc->m_LineList.GetNext (pos);

        ar.Write (pLine->text, pLine->len);

        if (pLine->hard_return)
          ar.WriteString (ENDLINE);

        } // end of doing middle lines

  // do last line

      ar.Write (pEndLine->text, endcol);
    
      }

    ar.Close();
    }   // end of try block

  catch (CException * e)
    {
    e->ReportError ();
    e->Delete ();
    return true;    // bad return
    }

  return false;   // good return

  }  // end of CMUSHView::GetSelection

// get selection into a string
bool CMUSHView::GetSelection(CString & s) 
  {
char * p = NULL;

  try
    {
    CMemFile f;      // open memory file for writing

    if (GetSelection (f))
      return true;     // error in producing selection

    int nLength = f.GetLength ();
    p = (char *) f.Detach ();

    s = CString (p, nLength);

    free (p);   // remove memory allocated in CMemFile

    }   // end of try block

	catch (CException* e)
	  {
    if (p)
      free (p);   // remove memory allocated in CMemFile
		e->ReportError();
		e->Delete();
    return true;
	  }

  return false; // OK

  }

BOOL CMUSHView::PreTranslateMessage(MSG* pMsg) 
{
	if (::IsWindow(m_ToolTip.m_hWnd) && pMsg->hwnd == m_hWnd)
	{
		switch(pMsg->message)
		{
		case WM_LBUTTONDOWN:	
		case WM_MOUSEMOVE:
		case WM_LBUTTONUP:	
		case WM_RBUTTONDOWN:
		case WM_MBUTTONDOWN:	
		case WM_RBUTTONUP:
		case WM_MBUTTONUP:
			m_ToolTip.RelayEvent(pMsg);
			break;
		}
	}
	
  // F1 becomes a macro if wanted
 if ( pMsg->message == WM_KEYDOWN && App.m_bF1macro ) 
   { 
   map<long, WORD>::const_iterator it;
   CMUSHclientDoc* pDoc = GetDocument();
   ASSERT_VALID(pDoc);

   if ( pMsg->wParam == VK_F1 ) 
     { 
     if (GetKeyState (VK_SHIFT) < 0 &&  // shift
        GetKeyState (VK_CONTROL) >= 0)  // but not control
       {
       it = pDoc->m_AcceleratorToCommandMap.find (
         ((FVIRTKEY | FNOINVERT | FSHIFT) << 16) | VK_F1);

       if (it != pDoc->m_AcceleratorToCommandMap.end ())
         OnAcceleratorCommand (it->second);
       else
         OnMacroCommand (ID_MACRO_SHIFT_F1); 
       return TRUE; // No more processing 
       }
     if (GetKeyState (VK_CONTROL) < 0 &&  // control
        GetKeyState (VK_SHIFT) >= 0)  // but not shift
       {
       it = pDoc->m_AcceleratorToCommandMap.find (
         ((FVIRTKEY | FNOINVERT | FCONTROL) << 16) | VK_F1);

       if (it != pDoc->m_AcceleratorToCommandMap.end ())
         OnAcceleratorCommand (it->second);
       else
         OnMacroCommand (ID_MACRO_CTRL_F1); 
       return TRUE; // No more processing 
       }
     if (GetKeyState (VK_CONTROL) >= 0 &&  // not control
        GetKeyState (VK_SHIFT) >= 0)  // and not shift
       {
       it = pDoc->m_AcceleratorToCommandMap.find (
         ((FVIRTKEY | FNOINVERT) << 16) | VK_F1);
       if (it != pDoc->m_AcceleratorToCommandMap.end ())
         OnAcceleratorCommand (it->second);
       else
         OnMacroCommand (ID_MACRO_F1);
       return TRUE; // No more processing 
       }
     } 
   }    // end of WM_KEYDOWN &&  m_bF1macro

	return CView::PreTranslateMessage(pMsg);
}

BOOL CMUSHView::OnToolTipNeedText(UINT id, NMHDR * pNMHDR, LRESULT * pResult)
{
	BOOL bHandledNotify = FALSE;

	CPoint CursorPos;
	VERIFY(::GetCursorPos(&CursorPos));
	ScreenToClient(&CursorPos);

	CRect ClientRect;
	GetClientRect(ClientRect);

	// Make certain that the cursor is in the client rect, because the
	// mainframe also wants these messages to provide tooltips for the
	// toolbar.
	if (ClientRect.PtInRect(CursorPos))
	{
		TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;

    CMUSHclientDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);

    int line,
        col;

    if (!pDoc->m_FontHeight)
      return FALSE;

    // work out where the cursor is in the scroll view, and find the line it is on
	  CClientDC dc(this);
	  OnPrepareDC(&dc);
	  dc.DPtoLP(&CursorPos);
    dc.SelectObject(pDoc->m_font [0]);
    if (calculate_line_and_column (CursorPos, dc, line, col, false))
       pTTT->szText [0] = 0;    // cursor is outside a line
    else
      {
      CLine * pLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (line));
	    CString	strText;

      // see if it is a hyperlink, and if so display the text of what the link will do
      unsigned int iStyle;
      CStyle * pStyle;
      POSITION foundpos;
      int iCol = 0;

    // don't do it *past* end of last word
    long pixel = calculate_width (line, pLine->len, pDoc, dc) + pDoc->m_iPixelOffset;

    if (CursorPos.x < pixel && 
        pDoc->FindStyle (pLine, col, iCol, pStyle, foundpos))
        {
        iStyle = pStyle->iFlags;
        if (pStyle->pAction &&
            !pStyle->pAction->m_strAction.IsEmpty () &&
            pStyle->pAction->m_strAction.Find ("&text;") == -1)
          if ((iStyle & ACTIONTYPE) == ACTION_SEND ||
            (iStyle & ACTIONTYPE) == ACTION_PROMPT)

            {

            CString strActions = pStyle->pAction->m_strAction;   // action
            CString strHints = pStyle->pAction->m_strHint;   // hints, if any

            CStringList actionsList,
                        hintsList;

            StringToList (strActions, POPUP_DELIMITER, actionsList);
            StringToList (strHints,   POPUP_DELIMITER, hintsList);

            // hint defaults to the default action
            strText = actionsList.GetHead ();

            // if we have any hints, that can be the flyover text
            // it will either be the special hint (extra first item)
            // or the hint for the first action item

            if (!hintsList.IsEmpty ())
               strText = hintsList.GetHead ();

            } // end of ACTION_SEND  or ACTION_PROMPT
          else
          if ((iStyle & ACTIONTYPE) == ACTION_HYPERLINK)
            {
            if (pStyle->pAction->m_strHint.IsEmpty ())
              strText = pStyle->pAction->m_strAction;
            else
              strText = pStyle->pAction->m_strHint;

            } // end of ACTION_HYPERLINK

        } // end of column found

      // here for just the line information
      if (strText.IsEmpty ())
        {
        
        // if not wanted, don't do it
        if (!pDoc->m_bLineInformation)
          return FALSE;

        char * p = "";

		    // Adjust the text by filling in TOOLTIPTEXT
        CString strDate;
        strDate = pLine->m_theTime.Format ("%A, %B %d, %#I:%M:%S %p");

        if (pLine->flags & COMMENT)
          p = ", (note)";
        else
          if (pLine->flags & USER_INPUT)
            p = ", (input)";

        strText.Format ("Line %ld, %s%s", 
                        pLine->m_nLineNumber,
                        (LPCTSTR) strDate,
                        p);
        }   // end of not being a hyperlink

		  ::strcpy(pTTT->szText, strText.Left (sizeof(pTTT->szText) - 1));
      }   // end of line being in range (ie. cursor over actual line)

    bHandledNotify = TRUE;
	}
	return bHandledNotify;
}

void CMUSHView::RemoveToolTip (void)
  {
  // get rid of tooltip, it is incorrect if we scroll
	if (::IsWindow(m_ToolTip.m_hWnd))
	  {
	  m_ToolTip.Activate(FALSE);		
    m_nLastToolTipLine = 0;
    m_nLastToolTipColumn = 0;
	  }
  } // end of CMUSHView::RemoveToolTip

void CMUSHView::OnFindAgainBackwards() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

pDoc->m_DisplayFindInfo.m_bForwards = false;
m_bottomview->DoFind(true);
}

void CMUSHView::OnUpdateFindAgainBackwards(CCmdUI* pCmdUI) 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
	
pCmdUI->Enable (!pDoc->m_DisplayFindInfo.m_strFindStringList.IsEmpty ());
}

void CMUSHView::OnFindAgainForwards() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

pDoc->m_DisplayFindInfo.m_bForwards = true;
m_bottomview->DoFind(true);
	
}

void CMUSHView::OnUpdateFindAgainForwards(CCmdUI* pCmdUI) 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

pCmdUI->Enable (!pDoc->m_DisplayFindInfo.m_strFindStringList.IsEmpty ());
	
}

void CMUSHView::OnDisplayGotourl() 
{
CString strSelection;

  if (GetSelection (strSelection))
    return;     // error in producing selection

  CString strSelectionUpper;

  strSelection.TrimLeft ();
  strSelection.TrimRight ();

  strSelectionUpper = strSelection;
  strSelectionUpper.MakeUpper ();

  if (strSelection.IsEmpty ())
    ::AfxMessageBox ("No URL selected", MB_ICONEXCLAMATION);
  else if (strSelection.GetLength () > 512)
    ::AfxMessageBox ("URL too long", MB_ICONEXCLAMATION);
  else
    {
    if (strSelectionUpper.Left (15).Find ("://") == -1)
      strSelection = "http://" + strSelection;
    
    // fix problem with imbedded quotes
    strSelection.Replace ("\"", "\\\"");

    if ((long) ShellExecute (Frame, _T("open"), strSelection, NULL, NULL, SW_SHOWNORMAL) <= 32)
      ::AfxMessageBox(CFormat ("Unable to open the URL \"%s\"", (const char *) strSelection), 
                      MB_ICONEXCLAMATION);
    }   // end of non-empty selection


// put the focus back in the send window

  OnKeysActivatecommandview ();
	
}


void CMUSHView::OnDisplaySendmailto() 
{
CString strSelection;


  if (GetSelection (strSelection))
    return;     // error in producing selection

  CString strSelectionUpper;


  strSelection.TrimLeft ();
  strSelection.TrimRight ();

  strSelectionUpper = strSelection;
  strSelectionUpper.MakeUpper ();

  if (strSelection.IsEmpty ())
    ::AfxMessageBox ("No email address selected", MB_ICONEXCLAMATION);
  else if (strSelection.GetLength () > 128)
    ::AfxMessageBox ("Email address too long", MB_ICONEXCLAMATION);
  else if (strSelection.Find ('@') == -1)
    ::AfxMessageBox (CFormat ("Email address \"%s\" invalid - does not contain a \"@\"",
      (const char *) strSelection), MB_ICONEXCLAMATION);
  else
    {
    if (strSelectionUpper.Left (7) != "MAILTO:")
      strSelection = "mailto:" + strSelection;

    if ((long) ShellExecute (Frame, _T("open"), strSelection, NULL, NULL, SW_SHOWNORMAL) <= 32)
      ::AfxMessageBox(CFormat ("Unable to send mail to \"%s\"", (const char *) strSelection), 
                      MB_ICONEXCLAMATION);
    }   // end of non-empty selection


// put the focus back in the send window

  OnKeysActivatecommandview ();
	
}

void CMUSHView::OnEditSelectAll() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

  m_selstart_line = 0;
  m_selstart_col = 0;
  m_selend_line = pDoc->m_LineList.GetCount ();
  m_selend_col = pDoc->m_nWrapColumn;

  Invalidate ();

#if REDRAW_DEBUG
    RECT r;
    GetClientRect (&r);
    ShowInvalidatedRect (this, r);
#endif
}


BOOL CMUSHView::PreCreateWindow(CREATESTRUCT& cs) 
{

	cs.style &= ~FWS_ADDTOTITLE;
  cs.style	|= WS_VSCROLL;

	return CView::PreCreateWindow(cs);
}


void CMUSHView::ScrollToPosition ( POINT pt, const bool bSmooth)
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

SCROLLINFO ScrollInfo;
RECT r;
int iDeltaY = m_scroll_position.y - pt.y;

  RemoveToolTip ();
  GetClientRect (&r);

  // if we can do a smooth scroll, well let's do it!
  if (abs (iDeltaY) < r.bottom)
    {
// very smooth scrolling

    int iSmoothDelta = iDeltaY < 0 ? -1 : 1;

    if (App.m_bSmoothScrolling)
      {
      for (int i = 0; i < abs (iDeltaY); i++)
        {
        m_scroll_position.y -= iSmoothDelta;
        // update scroll bar
        GetScrollInfo (SB_VERT, &ScrollInfo, SIF_POS);
        ScrollInfo.nPos = m_scroll_position.y;
        SetScrollInfo (SB_VERT, &ScrollInfo, SIF_POS);
        ScrollWindow (0, iSmoothDelta);
        UpdateWindow ();
        }
      } // end of smooth scrolling
    else
      {
      ScrollWindow (0, iDeltaY);
      m_scroll_position = pt;
      if (App.m_bSmootherScrolling)
        UpdateWindow ();    // redraw immediately if wanted
      } // end of not smooth scrolling
    }
  else
    // more than a screenfull away? just redraw the whole lot
    {
    Invalidate ();
#if REDRAW_DEBUG
    RECT r;
    GetClientRect (&r);
    ShowInvalidatedRect (this, r);
#endif
    }

#if REDRAW_DEBUG
    CRgn rgn;
    GetUpdateRgn(&rgn);
    ShowInvalidatedRgn (this, rgn);
#endif

  m_scroll_position = pt;

  // update scroll bar
  GetScrollInfo (SB_VERT, &ScrollInfo, SIF_POS);
  ScrollInfo.nPos = pt.y;
  SetScrollInfo (SB_VERT, &ScrollInfo, SIF_POS);

//  GetScrollInfo (SB_HORZ, &ScrollInfo, SIF_POS);
//  ScrollInfo.nPos = pt.x;
//  SetScrollInfo (SB_HORZ, &ScrollInfo, SIF_POS);
  
  if (pDoc->m_bAutoFreeze)
    m_freeze = GetScrollPos (SB_VERT) < GetScrollLimit(SB_VERT);

  } // end of CMUSHView::ScrollToPosition

void CMUSHView::SetScrollSizes (SIZE sizeTotal, 
                     const SIZE& sizePage, 
                     const SIZE& sizeLine)
  {
  SCROLLINFO ScrollInfo;

  m_scroll_limit = sizeTotal;

  GetScrollInfo (SB_VERT, &ScrollInfo, SIF_ALL);
  ScrollInfo.nMin = 0;
  ScrollInfo.nMax = sizeTotal.cy - 1;
  ScrollInfo.nPage = sizePage.cy;
  SetScrollInfo (SB_VERT, &ScrollInfo, SIF_ALL);

  /*
  GetScrollInfo (SB_HORZ, &ScrollInfo, SIF_ALL);
  ScrollInfo.nMin = 0;
  ScrollInfo.nMax = sizeTotal.cx - 1;
  ScrollInfo.nPage = sizePage.cx;
  SetScrollInfo (SB_HORZ, &ScrollInfo, SIF_ALL);
*/

  } // end of CMUSHView::SetScrollSizes

void CMUSHView::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);


int line,
    col;

  if (!pDoc->m_FontHeight)
    return;

	// CView changes the viewport origin and mapping mode.
	// It's necessary to convert the point from device coordinates
	// to logical coordinates, such as are stored in the document.
	CClientDC dc(this);
	OnPrepareDC(&dc);
	dc.DPtoLP(&point);
  dc.SelectObject(pDoc->m_font [0]);

  m_last_mouse_position = point;

  CRgn oldrgn;
  CRgn newrgn;

// Get old selection region

  get_selection (oldrgn);
  
// find which line and column the mouse position is at
              
  calculate_line_and_column (point, dc, line, col, false);

  POSITION pos = pDoc->GetLinePosition (line);

  CString strCurrentLine;

  m_selstart_col = m_selend_col = col;
  m_selstart_line = m_selstart_line = line;

  // SHIFT-double-click to select a paragraph
  if (nFlags & MK_SHIFT)
    {
    POSITION prevpos = NULL;
    CLine * pLine;

     pLine = pDoc->m_LineList.GetPrev (pos);    // must go back at least one line
     while (pos)
       {
       prevpos = pos;   // remember line which did have a hard return
       pLine = pDoc->m_LineList.GetPrev (pos);
       if (pLine->hard_return)
         break;
       m_selstart_line--;
       }

     // if prevpos is non-null it is now the position of the last line with a hard return
     // so, get the next one, that is the one which starts *our* sequence

     if (prevpos)
        pDoc->m_LineList.GetNext (prevpos);
     else       // must be the only line in the buffer
       {
        prevpos = pDoc->m_LineList.GetHeadPosition ();
        m_selstart_line = 0;
       }

     m_selend_line = m_selstart_line;
     // prevpos now points to the first line from the previous batch of lines
     pos = prevpos;
     do
       {
       pLine = pDoc->m_LineList.GetNext (pos);
       strCurrentLine += CString (pLine->text, pLine->len);
       if (!pLine->hard_return)
         m_selend_line++;
       }  while (!pLine->hard_return && pos);

     m_selstart_col = 0;
     m_selend_col = pLine->len;

    }
  else
    {
    CLine * pLine = pDoc->m_LineList.GetAt (pos);
    while (m_selstart_col >= 0 && 
          !isspace ((unsigned char) pLine->text [m_selstart_col]) &&
          strchr (App.m_strWordDelimitersDblClick, pLine->text [m_selstart_col]) == NULL)
      m_selstart_col--;
    m_selstart_col++;   // now onto the start of that word

    m_pin_col = m_selstart_col;

    point.x = calculate_width (line, col, pDoc, dc);

    // a word will end on a space, or whatever
    while (m_selend_col < pLine->len && 
          !isspace ((unsigned char) pLine->text [m_selend_col]) &&
          strchr (App.m_strWordDelimitersDblClick, pLine->text [m_selend_col]) == NULL)
      m_selend_col++;

    if (m_selend_col > m_selstart_col)
      strCurrentLine = CString (&pLine->text [m_selstart_col], 
                                m_selend_col - m_selstart_col);
    }

// Get new selection region

  if (!get_selection (newrgn) && pDoc->m_bAutoFreeze)
     m_freeze = true;   // freeze output so they can copy or print it

  // Combine previously selected with new selection, excluding union
  oldrgn.CombineRgn (&oldrgn, &newrgn, RGN_XOR);

  // Invalidate new stuff
  InvalidateRgn (&oldrgn);

#if REDRAW_DEBUG
  ShowInvalidatedRgn (this, oldrgn);
#endif

  // Finished with regions
  newrgn.DeleteObject ();
  oldrgn.DeleteObject ();

  // if they want, send word to MUD
  if (!strCurrentLine.IsEmpty ())
    if (pDoc->m_bDoubleClickSends)
      {
      if (pDoc->CheckConnected ())
        return;

      m_bottomview->AddToCommandHistory (strCurrentLine);
      pDoc->SendMsg (strCurrentLine, pDoc->m_display_my_input, false, pDoc->LoggingInput ());
      }   // end of sending word to MUD
    else if (pDoc->m_bDoubleClickInserts)
      {
      m_bottomview->GetEditCtrl().ReplaceSel (strCurrentLine);
      }   // end of copying word into command buffer

  // make sure selection is meaningful
  if (m_selstart_line == m_selend_line && 
      m_selend_col <= m_selstart_col)
    {
    m_selstart_col = 0;
    m_selend_col = 0;
    }

  // auto-copy selection to clipboard
  if (pDoc->m_bCopySelectionToClipboard)
    if (pDoc->m_bAutoCopyInHTML)
      OnEditCopyashtml ();
    else
      OnEditCopy ();

}

void CMUSHView::OnDisplayGotobookmark() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

POSITION startpos, pos;
CLine * pLine,
      * pInitialLine;
int nNewLine = m_selstart_line,
    nInitialLine;

  // empty buffer? can't do it
  if (pDoc->m_LineList.IsEmpty ())
    return;

  // first find the position of the current selection, or start of buffer if none
  if (m_selstart_line <= 0 || m_selstart_line >= pDoc->m_LineList.GetCount ())
    {
    nNewLine = 0;
    pos = pDoc->m_LineList.GetHeadPosition ();
    }
  else
    pos = pDoc->GetLinePosition (m_selstart_line);

  startpos = pos;
  pInitialLine = pDoc->m_LineList.GetNext (pos);   // skip current position
  nInitialLine = nNewLine;

  while (pos)
    {
    nNewLine++;
    pLine = pDoc->m_LineList.GetNext (pos);
    if (pLine->flags & BOOKMARK)
      break;
    else
      pLine = NULL;

    // if we come to the end, loop back to the start
    if (!pos)
      {
      pos = pDoc->m_LineList.GetHeadPosition ();
      nNewLine = -1;
      }

    if (startpos == pos)
      break;  // reached start again? don't loop forever
    }   // end of loop

  // if we didn't find one *but* we were on one anyway, re-go to it

  if (!pLine && pInitialLine->flags & BOOKMARK)
    {
    pLine = pInitialLine;
    nNewLine = nInitialLine;
    }

  // if we found one, select it
  if (pLine)
    SelectLine (nNewLine);
	
} // end of MUSHView::OnDisplayGotobookmark

void CMUSHView::OnDisplayBookmarkselection() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

  // empty buffer? can't do it
  if (pDoc->m_LineList.IsEmpty ())
    return;

  CRgn oldrgn;
  CRgn newrgn;

// Get old selection region

  get_selection (oldrgn);

  // if no selection (or out of range) select last line in display
  if (m_selstart_line <= 0 || m_selstart_line >= pDoc->m_LineList.GetCount ())
    m_selstart_line = pDoc->GetLastLine () - 1;

  CLine * pLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (m_selstart_line));

  pLine->flags ^= BOOKMARK;   // toggle bookmark

  m_selstart_col = 0;
  m_selend_col = 0;
  m_selend_line = m_selstart_line;    

  if (pLine->flags & BOOKMARK)
    m_selend_col = pLine->len;


// Get new selection region

  get_selection (newrgn);

  // Combine previously selected with new selection, excluding union
  oldrgn.CombineRgn (&oldrgn, &newrgn, RGN_XOR);

  // Invalidate new stuff
  InvalidateRgn (&oldrgn);

#if REDRAW_DEBUG
  ShowInvalidatedRgn (this, oldrgn);
#endif

  // Finished with regions
  newrgn.DeleteObject ();
  oldrgn.DeleteObject ();

  
}   // end of CMUSHView::OnDisplayBookmarkselection


void CMUSHView::doBookmark (void)
  {
  OnDisplayBookmarkselection ();
  } // end of CMUSHView::doBookmark

void CMUSHView::doGotoBookmark (void)
  {
  OnDisplayGotobookmark ();
  } // end of CMUSHView::doGotoBookmark


void CMUSHView::EnsureSelectionVisible (void)
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

  if (!pDoc->m_FontHeight)
    return;

  CPoint pt = GetScrollPosition ();
   
RECT r;

GetClientRect (&r);

  int startline = (pt.y - pDoc->m_iPixelOffset) / pDoc->m_FontHeight + 1;
  int endline =  ((pt.y + r.bottom) / pDoc->m_FontHeight) - 1;

  if (startline < 0)
    startline = 0;

// include partial last line if necessary

  int lastline = pDoc->GetLastLine ();

  if (endline > lastline)
    endline = lastline;

  if (m_selend_line < startline || m_selstart_line > endline)
    {
    // scroll so bookmark is halfway down page
    CPoint pt (0, m_selstart_line * pDoc->m_FontHeight -
                  (r.bottom - r.top) / 2);

    if (pt.y < 0)
      pt.y = 0;

    CPoint lastline_pt;

    // include partial last line if necessary

    lastline_pt.y =  (lastline * pDoc->m_FontHeight) - r.bottom;
    lastline_pt.x = 0;

    if (lastline_pt.y < 0)
      lastline_pt.y = 0;

    // make sure we don't scroll so that the last line is half-way up the screen
    if (pt.y > lastline_pt.y)
      pt.y = lastline_pt.y;
    else
      m_freeze = true;


    ScrollToPosition (pt, App.m_bSmoothScrolling);
    } // end of selection not visible

  } // end of CMUSHView::EnsureSelectionVisible

BOOL CMUSHView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);


CRect rect;
	
  m_bottomview->GetClientRect (&rect);
  m_bottomview->ClientToScreen(&rect);

  // if mouse is over lower window (command window) then let it scroll instead
  if (rect.PtInRect (pt))
    return m_bottomview->OnMouseWheel (nFlags, zDelta, pt);


unsigned int iScrollLines;


    /*
     * Don't handle zoom and datazoom.
     */
    
  if (nFlags & MK_CONTROL || nFlags & MK_SHIFT)	
	  return CView::OnMouseWheel(nFlags, zDelta, pt);

  SystemParametersInfo (SPI_GETWHEELSCROLLLINES, 0, &iScrollLines, 0);

  if (iScrollLines == 0)
    return 0;   // scrolling not enabled

  if (iScrollLines == WHEEL_PAGESCROLL)
    {
    if (zDelta < 0)   // page down
      doPagedown ();
    else
      doPageup ();

    return 1;
    }

  // scroll lines not pages

bool bSmooth_saved = App.m_bSmoothScrolling;

  App.m_bSmoothScrolling = false;

  if (zDelta < 0) // line down
    {
    zDelta = 0 - zDelta;  // make positive
    zDelta /= WHEEL_DELTA;
    zDelta *= iScrollLines;

/*   old way
    for (int i = 0; i < zDelta; i++)
      doLinedown ();

*/

// -- new way

    RECT r;
    int lastline;


    CPoint ScrollPt = GetScrollPosition ();

    GetClientRect (&r);

    ScrollPt.y += zDelta * pDoc->m_FontHeight;

    // include partial last line if necessary

    lastline = pDoc->GetLastLine ();

    // limit to end of screen
    if (ScrollPt.y > ((lastline * pDoc->m_FontHeight) - r.bottom))
	    ScrollPt.y =  (lastline * pDoc->m_FontHeight) - r.bottom;

    ScrollToPosition (ScrollPt, false);

// -- end new way

    App.m_bSmoothScrolling = bSmooth_saved;
    return 1;
    }

  // line up

  zDelta /= WHEEL_DELTA;
  zDelta *= iScrollLines;


/*   old way

  for (int i = 0; i < zDelta; i++)
    doLineup ();

*/

// -- new way

  RECT r;

  CPoint ScrollPt = GetScrollPosition ();

  GetClientRect (&r);

  ScrollPt.y -= zDelta * pDoc->m_FontHeight;

  if (ScrollPt.y < 0)
	  ScrollPt.y = 0;

  ScrollToPosition (ScrollPt, false);

// -- end new way

  App.m_bSmoothScrolling = bSmooth_saved;

  return 1;
}

void CMUSHView::OnAltUparrow() 
{
// alt+up+arrow is received in the top view, activate bottom view and pass command on

  OnKeysActivatecommandview ();  
  m_bottomview->SendMessage(WM_COMMAND, ID_ALT_UPARROW, 0);
	
}

void CMUSHView::OnAltDownarrow() 
{
// alt+down+arrow is received in the top view, activate bottom view and pass command on

  OnKeysActivatecommandview ();  
  m_bottomview->SendMessage(WM_COMMAND, ID_ALT_DOWNARROW, 0);
	
}

void CMUSHView::OnDisplayTextattributes() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

CString str;

long startcol,
     endcol;

// return if no valid selection

  CLine * pStartLine,
        * pEndLine;

  pStartLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (m_selstart_line));
  pEndLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (m_selend_line));
  
  startcol  = MIN (m_selstart_col, pStartLine->len);
  endcol    = MIN (m_selend_col, pEndLine->len);

  if (!(m_selend_line > m_selstart_line || 
              (m_selend_line == m_selstart_line && 
               endcol > startcol)))
    return;

unsigned int iStyle;
CStyle * pStyle;
int iCol;
POSITION foundpos;

if (!pDoc->FindStyle (pStartLine, startcol, iCol, pStyle, foundpos))
  {
  ::AfxMessageBox ("Cannot find style of this character");
  return;
  }

iStyle = pStyle->iFlags;

char c = pStartLine->text [startcol];

char * sColours [8] = 
  {
  "Black",
  "Red",
  "Green",
  "Yellow",
  "Blue",
  "Magenta",
  "Cyan",
  "White"
   };

CTextAttributesDlg dlg;

  dlg.m_pDoc = pDoc;
  dlg.m_iLine = m_selstart_line + 1;    // make 1-relative
  dlg.m_pLine = pStartLine; 

  dlg.m_strLetter = c;
  if ((iStyle & COLOURTYPE) == COLOUR_CUSTOM)
    {
    ASSERT (pStyle->iForeColour >= 0 && pStyle->iForeColour < MAX_CUSTOM);
    dlg.m_strTextColour = "Custom";
    dlg.m_strBackColour = "Custom";
    dlg.m_strCustomColour = pDoc->m_strCustomColourName [pStyle->iForeColour];
    }
  else
  if ((iStyle & COLOURTYPE) == COLOUR_RGB)
    {
    dlg.m_strTextColour = CFormat ("R=%i, G=%i, B=%i", 
                                   GetRValue (pStyle->iForeColour),
                                   GetGValue (pStyle->iForeColour),
                                   GetBValue (pStyle->iForeColour));
    dlg.m_strBackColour = CFormat ("R=%i, G=%i, B=%i", 
                                   GetRValue (pStyle->iBackColour),
                                   GetGValue (pStyle->iBackColour),
                                   GetBValue (pStyle->iBackColour));                                  
    dlg.m_strCustomColour = "RGB";
    }
  else
    {
    ASSERT (pStyle->iForeColour >= 0 && pStyle->iForeColour < 8);
    ASSERT (pStyle->iBackColour >= 0 && pStyle->iBackColour < 8);
    dlg.m_strTextColour = sColours [pStyle->iForeColour & 7];
    dlg.m_strBackColour = sColours [pStyle->iBackColour & 7];
    dlg.m_strCustomColour = "n/a";
    }

  dlg.m_bBold    = (iStyle & HILITE) != 0;
  dlg.m_bItalic  = (iStyle & BLINK) != 0;
  dlg.m_bInverse = (iStyle & INVERSE) != 0;

  if ((iStyle & CHANGED) == 0)
    dlg.m_strModified.Empty ();
  else
    dlg.m_strModified = "The colour or style HAS been modified by a trigger.";

  COLORREF colour1, colour2;

  pDoc->GetStyleRGB (pStyle, colour1, colour2); // find what colour this style is

  dlg.m_strTextColourRGB = CFormat ("#%02X%02X%02X",
                            GetRValue (colour1),
                            GetGValue (colour1),
                            GetBValue (colour1)
                            );

  dlg.m_strBackgroundColourRGB = CFormat ("#%02X%02X%02X",
                            GetRValue (colour2),
                            GetGValue (colour2),
                            GetBValue (colour2)
                            );

  dlg.m_ctlTextSwatch.m_colour = colour1;
  dlg.m_ctlBackgroundSwatch.m_colour = colour2;

  dlg.DoModal ();

}

void CMUSHView::OnEditNotesworkarea() 
{
	CMUSHclientDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

  // see if they want an existing one
  if (pDoc->SwitchToNotepad ())
    return;

CString strSelection;

  GetSelection (strSelection);    // get selection, if any


  // edit current input window
  CreateTextWindow (strSelection,     // contents
                    CFormat ("Notepad: %s", (LPCTSTR) pDoc->m_mush_name),     // title
                    pDoc,   // document
                    pDoc->m_iUniqueDocumentNumber,      // document number
                    pDoc->m_input_font_name,
                    pDoc->m_input_font_height,
                    pDoc->m_input_font_weight,
                    pDoc->m_input_font_charset,
                    pDoc->m_input_text_colour,
                    pDoc->m_input_background_colour,
                    "",       // search string
                    "",       // line preamble
                    false,
                    false,
                    false,
                    false,  
                    false,
                    false,
                    eNotepadNormal
                    );
	
	
}

void CMUSHView::OnDisplayRecalltext() 
{
	CMUSHclientDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

CString strSelection;

  GetSelection (strSelection);

  // if no selection, or more than one paragraph, ask what to recall
  if (strSelection.IsEmpty () || strSelection.Find (ENDLINE) != -1)
    {
    pDoc->DoRecallText ();
    return;
    }


CString strMessage;

  strMessage = pDoc->RecallText (
                                strSelection,   // what to search for
                                false,    // match case
                                false,    // regexp
                                true,     // search commands
                                true,     // search output
                                true,     // search notes
                                0,         // all lines
                                pDoc->m_strRecallLinePreamble);  // default preamble    

  CreateTextWindow (strMessage, 
                    CFormat ("Recall: %s",
                       (LPCTSTR) strSelection),
                    pDoc,
                    pDoc->m_iUniqueDocumentNumber,
                    pDoc->m_font_name,
                    pDoc->m_font_height,
                    pDoc->m_font_weight,
                    pDoc->m_font_charset,
                    pDoc->m_normalcolour [WHITE],
                    pDoc->m_normalcolour [BLACK],
                    strSelection,
                    pDoc->m_strRecallLinePreamble,
                    false,    // match case
                    false,    // regexp
                    true,     // search commands
                    true,     // search output
                    true,     // search notes
                    0,       // all lines
                    eNotepadRecall);
  
}

void CMUSHView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{

  if (App.m_bAllTypingToCommandWindow)
    {
    // if typing is received in top view, activate bottom view and pass character on

    OnKeysActivatecommandview ();  
    m_bottomview->SendMessage(WM_KEYDOWN, nChar, nRepCnt | (nFlags << 16));

    }
  else
	  CView::OnKeyDown(nChar, nRepCnt, nFlags);


}


BOOL CMUSHView::OnKeypadCommand(UINT nIDC) 
  {
// if numeric keypad commands are received in the top view, activate bottom view and pass command on

  OnKeysActivatecommandview ();  
  m_bottomview->SendMessage(WM_COMMAND, nIDC, 0);
  return TRUE;

  }

BOOL CMUSHView::OnMacroCommand(UINT nIDC) 
  {
// if macro commands are received in the top view, activate bottom view and pass command on

  OnKeysActivatecommandview ();  
  m_bottomview->SendMessage(WM_COMMAND, nIDC, 0);
  return TRUE;

  }

void CMUSHView::OnAcceleratorCommand (UINT nID)
  {
// if accelerator commands are received in the top view, activate bottom view and pass command on

  OnKeysActivatecommandview ();  
  m_bottomview->SendMessage (nID);
  }


void CMUSHView::OnMXPMenu (UINT nID)
  {
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

  // send the appropriate menu item

  if (pDoc->CheckConnected ())
    return;

  switch (iAction)
    {
    case ACTION_SEND:
      {
      CString strAction = strMXP_menu_item [nID - MXP_FIRST_MENU];
      if (pDoc->m_bHyperlinkAddsToCommandHistory)
        m_bottomview->AddToCommandHistory (strAction);
      pDoc->m_tLastPlayerInput = CTime::GetCurrentTime();   // for <afk> tests
      pDoc->SendMsg (strAction, pDoc->m_bEchoHyperlinkInOutputWindow,
                    false, pDoc->LoggingInput ());
      }
      break;  // end of ACTION_SEND

    case ACTION_PROMPT:
      if (m_bottomview->CheckTyping (pDoc, strMXP_menu_item [nID - MXP_FIRST_MENU]))
        return;             
      m_bottomview->SetCommand (strMXP_menu_item [nID - MXP_FIRST_MENU]);
      break; // end of ACTION_PROMPT

    case ACTION_HYPERLINK:
      {
      CString strAction = strMXP_menu_item [nID - MXP_FIRST_MENU];

      // don't let them slip in arbitrary OS commands
      if (strAction.Left (7).CompareNoCase ("http://") != 0 &&
          strAction.Left (7).CompareNoCase ("mailto:") != 0)
        ::AfxMessageBox(CFormat ("Hyperlink action \"%s\" - permission denied.", 
                        (const char *) strAction), 
                        MB_ICONEXCLAMATION);
      else
        if ((long) ShellExecute (Frame, _T("open"), strAction, NULL, NULL, SW_SHOWNORMAL) <= 32)
          ::AfxMessageBox(CFormat ("Unable to open the hyperlink \"%s\"", 
                          (const char *) strAction), 
                          MB_ICONEXCLAMATION);


      }   
      break; // end of ACTION_HYPERLINK


    case ACTION_ALIAS:
      {
      CAlias * pAlias;
      CString strLabel;
      
      // strip off plugin id
      CStringList strList;
      StringToList (strMXP_menu_item [nID - MXP_FIRST_MENU], ":", strList);
      strLabel = strList.GetHead ();

      // alias might be in a plugin
      switch (strList.GetCount ())
        {
        case 1:   // main world
            if (!pDoc->m_AliasMap.Lookup (strLabel, pAlias))
              return;   // not there? strange
        break;

        case 2:   // plugin
          {
            CString strPluginID;
            strPluginID = strList.GetTail ();
            CPlugin * pPlugin = pDoc->GetPlugin (strPluginID);
            if (!pPlugin)
              return;   // plugin does not exist? strange
            if (!pPlugin->m_AliasMap.Lookup (strLabel, pAlias))
              return;   // not there? strange
          pDoc->m_CurrentPlugin = pPlugin;     // remember plugin so alias executes in correct space
          }

        break;
            default:   return;   // not 1 or 2 items? very strange
        } // end of switch


      CString strAction;

//---

        // copy contents to strAction area, substituting variables

        const char * p,
                   * start;
        long length;
        int subscript;

        CString strFixedContents = ::FixupEscapeSequences (pAlias->contents);
        p = start = strFixedContents;

        while (*p)
          {
          if (p [0] == '%' && isdigit (p [1]))
            {

        // copy up to the percent sign

            length = p - start;
    
            if (length > 0)
              strAction += CString (start, length);
                
            subscript = *++p - '0';

          // get ready for next batch from beyond the digit

            start = ++p;

            } // end of finding a percent sign, followed by a number
          else if (p [0] == '%' && toupper (p [1]) == 'C')
            {
            CString strClipboard;

            if (!GetClipboardContents (strClipboard, pDoc->m_bUTF_8))
              {
              pDoc->m_CurrentPlugin = NULL;
              return;
              }

        // copy up to the percent sign

            length = p - start;
          
            if (length > 0)
              strAction += CString (start, length);

            strAction += strClipboard;

  // get ready for next batch from beyond the 'c'

            p += 2;
            start = p;

            }   // end of finding %c
        else if (p [0] == '%' && toupper (p [1]) == '%')
          {
// copy up to the percent sign

         length = p - start;
      
          if (length > 0)
           strAction += CString (start, length);

          // get ready for next batch from beyond the %

          start = ++p;
          p++;    // don't reprocess the %

          }   // end of %%
         else if (pAlias->bExpandVariables && p [0] == '@')
           {
// copy up to the @ sign

         length = p - start;
      
          if (length > 0)
           strAction += CString (start, length);
            
          p++;    // skip the @

          // @@ becomes @
          if (*p == '@')
            {
            start = ++p;
            strAction += "@";
            continue;
            }

          const char * pName = p;

          // find end of variable name
          while (*p)
            if (*p == '_' || isalnum (*p))
              p++;
            else
              break;
          
          CString strName (pName, p - pName);

          if (strName.IsEmpty ())
            {
            ::AfxMessageBox("@ must be followed by a variable name");
            pDoc->m_CurrentPlugin = NULL;
            return;
            }

          CVariable * variable_item;

          strName.MakeLower ();
          if (!pDoc->m_VariableMap.Lookup (strName, variable_item))
            {
            ::AfxMessageBox(CFormat ("Variable '%s' is not defined.", (LPCTSTR) strName));
            pDoc->m_CurrentPlugin = NULL;
            return;
            }

          strAction += variable_item->strContents;

          // get ready for next batch from beyond the variable

          start = p;
           }
         else
            p++;
          } // end of processing each character in the replacement text

  // copy last group

        strAction += start;

//--- 


      if (pDoc->m_ScriptEngine)
        if (pAlias->dispid != DISPID_UNKNOWN)        // if we have a dispatch id
          {


          CString strType = "alias";
          CString strReason =  CFormat ("processing alias \"%s\"", 
                                        (LPCTSTR) pAlias->strLabel);

      if (pDoc->GetScriptEngine () && pDoc->GetScriptEngine ()->IsLua ())
            {
            list<double> nparams;
            list<string> sparams;
            sparams.push_back ((LPCTSTR) strMXP_menu_item [nID - MXP_FIRST_MENU]);
            sparams.push_back ((LPCTSTR) pAlias->name);
            pDoc->GetScriptEngine ()->ExecuteLua (pAlias->dispid, 
                                           pAlias->strProcedure, 
                                           eUserMenuAction,
                                           strType, 
                                           strReason, 
                                           nparams,
                                           sparams, 
                                           pAlias->nInvocationCount); 
            }   // end of Lua
          else
            {

            // execute any scripts associated with alias we found
            enum
              {
              eWildcards,
              eInputLine,
              eAliasName,
              eArgCount,     // this MUST be last
              };    

            COleSafeArray sa;   // for wildcard list
            COleVariant args [eArgCount];
            DISPPARAMS params = { args, NULL, eArgCount, 0 };

            args [eAliasName] = strMXP_menu_item [nID - MXP_FIRST_MENU];
            args [eInputLine] = pAlias->name;

            // --------------- set up wildcards array ---------------------------
            sa.Clear ();
            // nb - to be consistent with %1, %2 etc. we will make array 1-relative
            sa.CreateOneDim (VT_VARIANT, MAX_WILDCARDS, NULL, 1);
            for (long i = 1; i <= MAX_WILDCARDS; i++)
              {
              COleVariant v (CString ("")); // no wildcards are relevant
              sa.PutElement (&i, &v);
              }
            args [eWildcards] = sa;

            pDoc->ExecuteScript (pAlias->dispid,  
                                 pAlias->strProcedure,
                                 eUserMenuAction,
                                 strType, 
                                 strReason,
                                 params, 
                                 pAlias->nInvocationCount); 
            } // end of not Lua

          }     // end of having a dispatch ID

    // send the message

      bool bOmitFromLog = false;
      CString strExtraOutput;

      pDoc->m_iCurrentActionSource = eUserMenuAction;

      pDoc->SendTo (pAlias->iSendTo, 
              strAction, 
              pAlias->bOmitFromOutput,
              pAlias->bOmitFromLog,
              CFormat ("Alias: %s", (LPCTSTR) pAlias->strLabel),
              pAlias->strVariable,
              strExtraOutput
              );

      pDoc->m_iCurrentActionSource = eUnknownActionSource;

      if (!strExtraOutput.IsEmpty ())
         pDoc->DisplayMsg (strExtraOutput, strExtraOutput.GetLength (), COMMENT);

      pDoc->m_tLastPlayerInput = CTime::GetCurrentTime();   // for <afk> tests

      pAlias->nMatched++;   // count alias matches

      }
      pDoc->m_CurrentPlugin = NULL;     // no plugin active right now
      break;  // end of ACTION_ALIAS
    } // end of switch on iAction

  // unpause the output window if wanted
  if (pDoc->m_bUnpauseOnSend && m_freeze)
    {
    m_freeze = false;
    addedstuff ();   
    }

  // put the focus back in the send window

  OnKeysActivatecommandview ();

  } // end of CMUSHView::OnMXPMenu


void CMUSHView::OnDisplayGotoline() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

CGoToLineDlg dlg;

  dlg.m_iMaxLine = pDoc->m_LineList.GetCount ();
  dlg.m_iLineNumber = pDoc->m_lastGoTo;
  
  if (dlg.DoModal () != IDOK)
    return;

  SelectLine (dlg.m_iLineNumber - 1); // line is zero-relative

  pDoc->m_lastGoTo = dlg.m_iLineNumber; // in case we want to go there again

}


void CMUSHView::doGotoLine (void)
  {
  OnDisplayGotoline ();
  } // end of CMUSHView::doGotoLine


void CMUSHView::SelectLine (const int iLine)
  {

    CRgn oldrgn;
    CRgn newrgn;

  // Invalidate old selection rectangle

  // Get old selection region

    get_selection (oldrgn);

  // select the found text, so it is highlighted

    m_selstart_line = iLine;
    m_selend_line = iLine + 1;
    m_selstart_col =  0;
    m_selend_col = 0;

  // Invalidate new selection rectangle

  // Get new selection region

    get_selection (newrgn);

    // Combine previously selected with new selection, excluding union
    oldrgn.CombineRgn (&oldrgn, &newrgn, RGN_XOR);

    // Invalidate new stuff
    InvalidateRgn (&oldrgn);

#if REDRAW_DEBUG
  ShowInvalidatedRgn (this, oldrgn);
#endif

    // Finished with regions
    newrgn.DeleteObject ();
    oldrgn.DeleteObject ();

  // freeze output, so they can study the found text at leisure

    EnsureSelectionVisible ();

  } // end of CMUSHView::SelectLine 


void CMUSHView::OnGameCommandhistory() 
{
m_bottomview->DoCommandHistory();
}

void CMUSHView::OnFileCtrlN() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

  if (pDoc->m_bCtrlNGoesToNextCommand)
    m_bottomview->DoNextCommand ();
  else
    App.OnFileNew ();	
	
}

void CMUSHView::OnFileCtrlP() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

  if (pDoc->m_bCtrlPGoesToPreviousCommand)
    m_bottomview->DoPreviousCommand ();
  else
    PrintWorld ();	
}

void CMUSHView::OnEditCtrlZ() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

  if (pDoc->m_bCtrlZGoesToEndOfBuffer)
     OnTestEnd ();
	
}

void OutputOneLine (CMUSHclientDoc* pDoc,
                    CArchive & ar, 
                    CLine * pLine, 
                    const int iStartCol, 
                    const int iEndCol,
                    const COLORREF defaultbackcolour,
                    COLORREF & lastforecolour,
                    COLORREF & lastbackcolour,
                    bool & bInSpan)
  {
  int iCol = 0;

  CString strText = CString (pLine->text, pLine->len);

  // can't do if no styles
  if (pLine->styleList.IsEmpty ())
     return;

  if (pLine->len <= 0)
     return;

  for (POSITION pos = pLine->styleList.GetHeadPosition(); pos; )
    {
    COLORREF colour1,
             colour2;

    CStyle * pStyle = pLine->styleList.GetNext (pos);

    int iLength = pStyle->iLength;

    // ignore zero length styles
    if (iLength <= 0)
       continue;

    // skip if before starting column
    if ((iCol + iLength) >= iStartCol)
      {

      // if it starts in this style run - adjust length down and column up
      if (iStartCol > iCol)
        {
        iLength -= iStartCol - iCol;
        iCol += iStartCol - iCol;
        }

      // if it ends in this style run - adjust length down

      if ((iCol + iLength) > iEndCol)
        iLength = iEndCol - iCol;

      // ignore zero length styles
      if (iLength <= 0)
         continue;

      pDoc->GetStyleRGB (pStyle, colour1, colour2); // find what colour this style is

      if (colour1 != lastforecolour || colour2 != lastbackcolour)
        {
        // cancel earlier span
        if (bInSpan)
          {
           ar.WriteString ("</span>");
           bInSpan = false;
          }

        ar.WriteString (CFormat ("</font><font color=\"#%02X%02X%02X\">",
                            GetRValue (colour1),
                            GetGValue (colour1),
                            GetBValue (colour1)
                            ));

        // for efficiency we will only use <span> if we need to change the
        // background colour
        if (colour2 != defaultbackcolour)
          {
          ar.WriteString (CFormat ("<span style=\"color: #%02X%02X%02X; "
                                   "background: #%02X%02X%02X\">",
                                      GetRValue (colour1),
                                      GetGValue (colour1),
                                      GetBValue (colour1),
                                      GetRValue (colour2),
                                      GetGValue (colour2),
                                      GetBValue (colour2)
                                      ));
          bInSpan = true;
          }
        lastforecolour = colour1;
        lastbackcolour = colour2;
        }

      if (pStyle->iFlags & UNDERLINE)
        ar.WriteString ("<u>");

  // bold actually looks a bit silly :)
  //    if (pStyle->iFlags & HILITE)
  //      ar.WriteString ("<b>");

      ar.WriteString (FixHTMLString (strText.Mid (iCol, iLength)));

  //    if (pStyle->iFlags & HILITE)
  //      ar.WriteString ("</b>");
      if (pStyle->iFlags & UNDERLINE)
        ar.WriteString ("</u>");
      }   // end of being before starting column

    iCol += iLength; // new column

    // past end column - stop

    if (iCol >= iEndCol)
      break;

    }   // end of doing each style

  } // end of OutputOneLine

void CMUSHView::OnEditCopyashtml() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

char * p = NULL;

  try
    {
    CMemFile f;      // open memory file for writing

    long startcol,
         endcol;

    int i;

  // return if no valid selection

    CLine * pStartLine,
          * pEndLine;

    pStartLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (m_selstart_line));
    pEndLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (m_selend_line));

    startcol  = MIN (m_selstart_col, pStartLine->len);
    endcol    = MIN (m_selend_col, pEndLine->len);

    if (!(m_selend_line > m_selstart_line || 
                (m_selend_line == m_selstart_line && 
                 endcol > startcol)))
      return;

    CArchive ar(&f, CArchive::store);


    COLORREF colour1 = 0,
             colour2 = 0,
             lastforecolour = 0,
             lastbackcolour = 0,
             defaultbackcolour;
    bool bInSpan = false;

    if (!pStartLine->styleList.IsEmpty ())
      {
      CStyle * pStartStyle = pStartLine->styleList.GetHead ();
      pDoc->GetStyleRGB (pStartStyle, colour1, colour2); 
      }

    defaultbackcolour = colour2;

    ar.WriteString (CFormat ("<!-- Produced by MUSHclient v %s - www.mushclient.com -->",
                             MUSHCLIENT_VERSION));
    ar.WriteString (ENDLINE);
    ar.WriteString (CFormat ("<table border=0 cellpadding=5 bgcolor=\"#%02X%02X%02X\">",
                        GetRValue (colour2),
                        GetGValue (colour2),
                        GetBValue (colour2)));
    ar.WriteString (ENDLINE);

    ar.WriteString ("<tr><td>");
    ar.WriteString (ENDLINE);

    CString strFont = "<pre><code>"
                    "<font size=2 face=\"$FONT$FixedSys, Lucida Console, Courier New, Courier\">"
                    "<font color=\"#0\">";

    if (pDoc->m_font_name != "FixedSys")
      {
      CString s = pDoc->m_font_name;
      s += ", ";
      strFont = ::Replace (strFont, "$FONT$", s);
      }
    else
       strFont = ::Replace (strFont, "$FONT$", "");  // font already there

    ar.WriteString (strFont);
    ar.WriteString (ENDLINE);


  // starting and ending on the same line is a special case

    if (m_selend_line == m_selstart_line)
      OutputOneLine (pDoc, ar, pStartLine, startcol, endcol, 
                     defaultbackcolour, lastforecolour, lastbackcolour, bInSpan);
    else
      {

  // do first line

      OutputOneLine (pDoc, ar, pStartLine, startcol, pStartLine->len, 
                    defaultbackcolour, lastforecolour, lastbackcolour, bInSpan);
      ar.WriteString (ENDLINE);

  // do middle lines

      POSITION pos = pDoc->GetLinePosition (m_selstart_line + 1);

      for (i = m_selstart_line + 1; i < m_selend_line && pos; i++)
        {
        CLine * pLine = pDoc->m_LineList.GetNext (pos);

        OutputOneLine (pDoc, ar, pLine, 0, pLine->len, 
                       defaultbackcolour, lastforecolour, lastbackcolour, bInSpan);
        ar.WriteString (ENDLINE);

        } // end of doing middle lines

  // do last line

      OutputOneLine (pDoc, ar, pEndLine, 0, endcol, 
                      defaultbackcolour, lastforecolour, lastbackcolour, bInSpan);
      ar.WriteString (ENDLINE);
    
      }

    if (bInSpan)
       ar.WriteString ("</span>");
    ar.WriteString ("</font></font></code></pre>");
    ar.WriteString (ENDLINE);
    ar.WriteString ("</td></tr></table>");
    ar.WriteString (ENDLINE);

    ar.Close();

    int nLength = f.GetLength ();
    p = (char *) f.Detach ();

    CString strSelection (p, nLength);

    free (p);   // remove memory allocated in CMemFile
    p = NULL;
    putontoclipboard (strSelection, pDoc->m_bUTF_8);

    }   // end of try block

	catch (CException* e)
	  {
    if (p)
      free (p);   // remove memory allocated in CMemFile
		e->ReportError();
		e->Delete();
	  }

// put the focus back in the send window

  OnKeysActivatecommandview ();

	
}


void CMUSHView::OnDisplayHighlightphrase() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

CHighlightPhraseDlg dlg;

  dlg.m_iOtherForeground = pDoc->m_normalcolour [WHITE]; // their normal white
  dlg.m_iOtherBackground = pDoc->m_normalcolour [BLACK]; // their normal black

CString str;

long startcol,
     endcol;

// return if no valid selection

  CLine * pStartLine,
        * pEndLine;

  pStartLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (m_selstart_line));
  pEndLine = pDoc->m_LineList.GetAt (pDoc->GetLinePosition (m_selend_line));
  
  startcol  = MIN (m_selstart_col, pStartLine->len);
  endcol    = MIN (m_selend_col, pEndLine->len);

  if ((m_selend_line > m_selstart_line || 
              (m_selend_line == m_selstart_line && 
               endcol > startcol)))
    {
    CStyle * pStyle;
    int iCol;
    POSITION foundpos;

    if (pDoc->FindStyle (pStartLine, startcol, iCol, pStyle, foundpos))
      pDoc->GetStyleRGB (pStyle, dlg.m_iOtherForeground, dlg.m_iOtherBackground); // find what colour this style is

    CString strSelection;
    int i;

    GetSelection (strSelection);

    // stop at first linefeed
    i = strSelection.Find ("\n");

    if (i != -1)
      strSelection = strSelection.Left (i - 1);

    if (strSelection.GetLength () > 80)
      strSelection = strSelection.Left (80);

    dlg.m_strText = strSelection;

    } // end of selected text

  memcpy (dlg.m_customtext, pDoc->m_customtext, sizeof (dlg.m_customtext));
  memcpy (dlg.m_customback, pDoc->m_customback, sizeof (dlg.m_customback));
  for (int i = 0; i < MAX_CUSTOM; i++)
     dlg.m_strCustomName [i] = pDoc->m_strCustomColourName [i];
	
  dlg.m_iColour = OTHER_CUSTOM + 1;    // other custom colour

  // assume they want to match whole words
  dlg.m_bWord = true;

  if (dlg.DoModal () != IDOK)
    return;

  CString strText;
 
  if (dlg.m_bWord)
     strText += "\\b";    // word boundary

  strText += ConvertToRegularExpression (dlg.m_strText, false, false);

  if (dlg.m_bWord)
     strText += "\\b";    // word boundary

  t_regexp * regexp = NULL;

  // compile regular expression
  try 
    {
    regexp = regcomp (strText, dlg.m_bMatchCase ?  0 : PCRE_CASELESS );
    }   // end of try
  catch(CException* e)
    {
    e->Delete ();
    ::AfxMessageBox ("Cannot compile regular expression");
    return;
    } // end of catch

  CString strTriggerName;

  // invent name for it
  strTriggerName.Format ("*trigger%s", (LPCTSTR) App.GetUniqueString ());

  CTrigger * trigger_item;

  // create new trigger item and insert in trigger map
  pDoc->GetTriggerMap ().SetAt (strTriggerName, trigger_item = new CTrigger);

  trigger_item->nUpdateNumber    = App.GetUniqueNumber ();   // for concurrency checks

  trigger_item->trigger          = strText;
  trigger_item->colour           = dlg.m_iColour - 1;
  trigger_item->bKeepEvaluating  = true;      // keep evaluating wanted
  trigger_item->ignore_case      = !dlg.m_bMatchCase;
  
  trigger_item->bRegexp          = true;      // yes, it's a regexp
  trigger_item->bRepeat          = true;      // repeat on same line
  trigger_item->regexp           = regexp;
  trigger_item->iSendTo          = eSendToWorld;
  trigger_item->iSequence        = 90;
  trigger_item->strGroup         = "Highlighted Words";

  trigger_item->iOtherForeground = dlg.m_iOtherForeground;
  trigger_item->iOtherBackground = dlg.m_iOtherBackground;

  pDoc->SortTriggers ();

  // we need to know to save it
  pDoc->SetModifiedFlag (TRUE);
	
} // end of CMUSHView::OnDisplayHighlightphrase

void CMUSHView::OnDisplayMultilinetrigger() 
{
CMUSHclientDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

CMultiLineTriggerDlg dlg;

CString strSelection;

  GetSelection (strSelection);

  strSelection = strSelection.Left (10000); // just make sure not really big
  strSelection.Replace ("\r", "");  // don't want \r in the string

  dlg.m_strTriggerText = ConvertToRegularExpression (strSelection, false, false);
  dlg.m_strTriggerText.Replace ("\\n", "\r\n"); // put linebreaks back
 
  dlg.m_bMatchCase = true;

  if (dlg.DoModal () != IDOK)
    return;

  CString strText =  dlg.m_strTriggerText;

  int iCount = 1;          // the first line is always there
  // count lines in trigger
  const char * p;

  for (p = strText; *p; p++)
    if (*p == '\n')
      iCount++;

  strText.Replace (ENDLINE, "\\n"); // replace linebreaks with \n
  strText += "\\Z";    // subject boundary

  t_regexp * regexp = NULL;

  // compile regular expression
  try 
    {
    regexp = regcomp (strText, dlg.m_bMatchCase ?  0 : PCRE_CASELESS );
    }   // end of try
  catch(CException* e)
    {
    e->Delete ();
    ::AfxMessageBox ("Cannot compile regular expression");
    return;
    } // end of catch

  CString strTriggerName;

  // invent name for it
  strTriggerName.Format ("*trigger%s", (LPCTSTR) App.GetUniqueString ());

  CTrigger * trigger_item;

  // create new trigger item and insert in trigger map
  pDoc->GetTriggerMap ().SetAt (strTriggerName, trigger_item = new CTrigger);

  trigger_item->nUpdateNumber    = App.GetUniqueNumber ();   // for concurrency checks

  trigger_item->trigger          = strText;
  trigger_item->colour           = SAMECOLOUR;
  trigger_item->ignore_case      = !dlg.m_bMatchCase;
  
  trigger_item->contents         = "%0";      // display all text
  trigger_item->bRegexp          = true;      // yes, it's a regexp
  trigger_item->bMultiLine        = true;      // multiline
  trigger_item->bKeepEvaluating  = true;      // keep evaluating wanted
  trigger_item->regexp           = regexp;
  trigger_item->iSendTo          = eSendToOutput;
  trigger_item->strGroup         = "Multi Line";

  trigger_item->iLinesToMatch = iCount;

  pDoc->SortTriggers ();

  // we need to know to save it
  pDoc->SetModifiedFlag (TRUE);

} // end of CMUSHView::OnDisplayMultilinetrigger

void CMUSHView::OnWindowMaximize() 
{
	  GetParentFrame ()->ShowWindow(SW_SHOWMAXIMIZED);
	
}

void CMUSHView::OnWindowRestore() 
{
	  GetParentFrame ()->ShowWindow(SW_RESTORE);
	
}
