//  CStrWnd.cpp

//  See CStrWnd.h for a definition of what the
//  class CStrWnd does

#include    "stdhdr.h"
#include    "test_r.h"
#include    "cstrwnd.h"
#include    "strdetab.h"

CStrWnd::CStrWnd (const char * window_name)
{
VERIFY (LoadAccelTable ("CStrWndAccelTable"));
VERIFY (Create (NULL, window_name,
                WS_CHILD | WS_VSCROLL, rectDefault,
                NULL));
SetFocus ();
current_first_row = 0; current_first_col = 0;
h_scroll_flag = 0;
length_of_longest_line = 0;
}

BEGIN_MESSAGE_MAP (CStrWnd, CMDIChildWnd)
    ON_WM_PAINT ()
    ON_WM_VSCROLL ()
    ON_WM_HSCROLL ()
    ON_WM_KEYDOWN ()

    ON_COMMAND (CSW_DOC_START, OnDocStart)
    ON_COMMAND (CSW_DOC_END, OnDocEnd)
    ON_COMMAND (CSW_LINE_RIGHT, OnLineRight)
    ON_COMMAND (CSW_LINE_LEFT, OnLineLeft)
END_MESSAGE_MAP ()

afx_msg void CStrWnd::OnPaint ()
{
CPaintDC dc (this);
//  Use a fixed-pitch font for better presentation
VERIFY (dc.SelectStockObject(OEM_FIXED_FONT) != NULL);

//  First find out number of lines in the window
TEXTMETRIC tm;
VERIFY (dc.GetTextMetrics (&tm));
const int char_height = tm.tmHeight
                        + tm.tmExternalLeading;
const int char_width = tm.tmAveCharWidth;
CRect rect;
GetClientRect (rect);
no_of_lines = rect.Height () / char_height;
no_of_chars_in_line = rect.Width () / char_width;

//  Set up the tab stop positions
const int tab_interval = 4;
const int no_of_tabs = 120 / tab_interval;
int char_tab_stops [no_of_tabs];
for (int i = 0; i < no_of_tabs; i++)
    char_tab_stops [i] = (i + 1) * tab_interval;

//  Write a window_full of text

for (int lc = 0;
     lc < no_of_lines
     && current_first_row + lc
        < text_buffer.GetSize ();
     lc++)
    {
    const int max_line_length = 256;
    char out_buf [max_line_length + 1];
    int line_length;
    //  Convert tabs to spaces
    strdetab (out_buf, max_line_length,
              text_buffer [current_first_row + lc],
              no_of_tabs, char_tab_stops,
              &line_length);
    //  Get pointer to first char in line for output
    const char * first_char = out_buf
                              + current_first_col;
    //  Update record of longest line length
    if (length_of_longest_line < line_length)
        length_of_longest_line = line_length;
    //  Call up horizontal scroll bar if needed
    if (line_length > no_of_chars_in_line)
        h_scroll_flag = 1;
    dc.TextOut (0, lc * char_height, first_char,
                line_length - current_first_col);
    }
if (h_scroll_flag) ShowScrollBar (SB_HORZ, TRUE);
}

afx_msg void CStrWnd::OnVScroll (UINT nSBCode,
                                 UINT npos,
                                 CScrollBar * pBar)
{
int min_scroll_pos, max_scroll_pos,
    max_index = text_buffer.GetSize () - 1,
    scrolled = 1;
if (max_index < 0) max_index = 0;
    //  Cater for no lines in text_buffer
switch (nSBCode)
    {
    case SB_LINEDOWN:
        current_first_row++; break;
    case SB_PAGEDOWN:
        current_first_row += no_of_lines - 1; break;
    case SB_LINEUP:
        current_first_row--; break;
    case SB_PAGEUP:
        current_first_row -= no_of_lines - 1; break;
    case SB_THUMBPOSITION:
        GetScrollRange (SB_VERT, &min_scroll_pos,
                        &max_scroll_pos);
        current_first_row =
            (max_index * (npos - min_scroll_pos))
             / (max_scroll_pos - min_scroll_pos);
        break;
    default:
    //  We did nothing, so do not update the window!
        scrolled = 0; break;
    }

if (scrolled)
    {
    //  Now make sure that the current row is in range
    if (current_first_row < 0) current_first_row = 0;
    if (current_first_row > max_index)
        current_first_row = max_index;
    Invalidate (TRUE);
    SetVerticalScroll ();
    UpdateWindow ();
    }
}

afx_msg void CStrWnd::OnHScroll (UINT nSBCode,
                                 UINT npos,
                                 CScrollBar * pBar)
{
int min_scroll_pos, max_scroll_pos,
    scrolled = 1;
GetScrollRange (SB_VERT, &min_scroll_pos,
                &max_scroll_pos);
switch (nSBCode)
    {
    case SB_LINERIGHT:
        current_first_col++; break;
    case SB_PAGERIGHT:
        current_first_col += 10; break;
    case SB_LINELEFT:
        current_first_col--; break;
    case SB_PAGELEFT:
        current_first_col -= 10; break;
    case SB_LEFT:
        current_first_col = 0; break;
    case SB_RIGHT:
        current_first_col = length_of_longest_line - 1;
        break;
    case SB_THUMBPOSITION:
        current_first_col =
            ((length_of_longest_line - 1)
             * (npos - min_scroll_pos))
             / (max_scroll_pos - min_scroll_pos);
        break;
    default:
        //  We did nothing, so do not update  window!
        scrolled = 0; break;
    }

if (scrolled)
    {
    //  Now ensure that we are within limits
    if (current_first_col < 0) current_first_col = 0;
    if (current_first_col >= length_of_longest_line)
        current_first_col = length_of_longest_line - 1;
    Invalidate (TRUE);
    if (h_scroll_flag)
        SetScrollPos (SB_HORZ,
                  ((max_scroll_pos - min_scroll_pos)
                  * current_first_col)
                  / (length_of_longest_line -1)
                  + min_scroll_pos,
                  TRUE);
    UpdateWindow ();
    }
}

afx_msg void CStrWnd::OnKeyDown (UINT nChar,
                                 UINT nRepCnt,
                                 UINT nFlags)
{
switch (nChar)
    {
    case VK_DOWN:
        OnVScroll (SB_LINEDOWN, 0, NULL); break;
    case VK_NEXT:                   // Page down
        OnVScroll (SB_PAGEDOWN, 0, NULL); break;
    case VK_UP:
        OnVScroll (SB_LINEUP, 0, NULL); break;
    case VK_PRIOR:                  // Page up
        OnVScroll (SB_PAGEUP, 0, NULL); break;
    case VK_LEFT:
        OnHScroll (SB_LINELEFT, 0, NULL); break;
    case VK_HOME:
        OnHScroll (SB_LEFT, 0, NULL); break;
    case VK_RIGHT:
        OnHScroll (SB_LINERIGHT, 0, NULL); break;
    case VK_END:
        OnHScroll (SB_RIGHT, 0, NULL); break;
    }
}

afx_msg void CStrWnd::OnDocStart ()
{
int min_scroll_pos, max_scroll_pos;
GetScrollRange (SB_VERT, &min_scroll_pos,
                &max_scroll_pos);
Invalidate (TRUE);
current_first_row = 0;
SetScrollPos (SB_VERT, min_scroll_pos, TRUE);
UpdateWindow ();
}

afx_msg void CStrWnd::OnDocEnd ()
{
int min_scroll_pos, max_scroll_pos;
GetScrollRange (SB_VERT, &min_scroll_pos,
                &max_scroll_pos);
Invalidate (TRUE);
current_first_row = text_buffer.GetUpperBound () - 4;
//  Leave 4 lines on the screen
if (current_first_row < 0) current_first_row = 0;
SetScrollPos (SB_VERT, max_scroll_pos, TRUE);
UpdateWindow ();
}

afx_msg void CStrWnd::OnLineRight ()
{OnHScroll (SB_PAGERIGHT, 0, NULL);}

afx_msg void CStrWnd::OnLineLeft ()
{OnHScroll (SB_PAGELEFT, 0, NULL);}

void CStrWnd::UpdateScreen ()
{
current_first_row = text_buffer.GetUpperBound ()
                    - no_of_lines;
if (current_first_row < 0) current_first_row = 0;
SetVerticalScroll ();
Invalidate (TRUE);
UpdateWindow ();
}

void CStrWnd::SetVerticalScroll ()
{
int min_scroll_pos, max_scroll_pos,
    max_index = text_buffer.GetUpperBound ();
GetScrollRange (SB_VERT, &min_scroll_pos,
                &max_scroll_pos);
SetScrollPos (SB_VERT,
              ((max_scroll_pos - min_scroll_pos )
                * current_first_row)
              / ((max_index > 0) ? max_index : 1)
              + min_scroll_pos,
              TRUE);
}
