// wsmooth.c RHS 9/1/90

#include"windows.h"
#include"wsmooth.h"
#include"wsmooth2.h"
#include<stdio.h>
#include<string.h>
#include<malloc.h>
#include<stdarg.h>
#include<process.h>
#include<math.h>
#include"filebuf.h"

#define textrowsperpage()   ((PixRowsPerPage/CharLineHeight)+3)

#define SB_PIXLINEUP    (SB_LINEUP+0x0100)
#define SB_PIXLINEDOWN  (SB_LINEDOWN+0x0100)

long numlines;
HWND WinSmooth = 0;
char filename[128];
char classname[11] = "WinSmooth";
char windowname[128] = "WinSmooth";
BOOL direction = DOWN;

HANDLE hOrgBrush;
HCURSOR hNormalCursor, hHourGlassCursor;
BOOL sb_init = FALSE;

SETTINGS settings =
    {
    DEFAULTMSECS,
    DEFAULTPIXELROWS,
    DEFAULTBACK,
    DEFAULTFORE,
    FALSE
    };

HANDLE hinstance;

void ResetPixRows(void);
void ResetBackGround(HDC hDC);
void RetrieveSettings(void);
void PaintMainWindow(HWND hwnd);

   // beginning of WinSmooth
int PASCAL WinMain(HANDLE hInstance,HANDLE hPrevInstance,
    LPSTR lpszCmdLine, int cmdShow)
    {
    MSG msg;

    RetrieveSettings();                 // retrieve settings from WIN.INI
    lstrcpy((LPSTR)filename,lpszCmdLine);
    process_cmdline(filename);          // get (and override with) command-line settings

    hOrgBrush = CreateSolidBrush(settings.back);

    if(!hPrevInstance)
        {
        WNDCLASS cl;

        cl.lpszClassName = (LPSTR) classname;
        cl.hInstance = hInstance;
        cl.lpfnWndProc = MainWindowProc;
        cl.hCursor = LoadCursor(NULL, IDC_ARROW);
        cl.hIcon = LoadIcon(hInstance,(LPSTR)"WSMOOTH");
        cl.lpszMenuName = (LPSTR)"FILEMENU";
        cl.hbrBackground = hOrgBrush;
        cl.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
        cl.cbClsExtra = 0;
        cl.cbWndExtra = 0;

        if(!RegisterClass(&cl))
            return FALSE;
        hNormalCursor = cl.hCursor;
        }

    hHourGlassCursor = LoadCursor(NULL, IDC_WAIT);
    WinSmooth = CreateWindow((LPSTR)classname,
        (LPSTR)windowname,
        WS_OVERLAPPEDWINDOW | WS_VSCROLL,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        (LPSTR)NULL);

    if(!WinSmooth)
        return FALSE;

    ShowWindow(WinSmooth, cmdShow);
    UpdateWindow(WinSmooth);
    hinstance = hInstance;
    ChangeTitleBar(filename);           // in case it wasn't done when file was opened

    while(GetMessage(&msg, NULL, 0, 0))
        {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }
    exit(msg.wParam);
    }

   // WinSmooth termination and cleanup
BOOL FAR PASCAL CloseApplication(void)
    {
    filebuf_destruct();
    return TRUE;
    }

struct _keytable
    {
    WORD    virkey;
    int     msg;
    WORD    sbcode;
    } keyscroll[] =
    {
    VK_PRIOR,   WM_VSCROLL,     SB_PAGEUP,
    VK_NEXT,    WM_VSCROLL,     SB_PAGEDOWN,
    VK_HOME,    WM_VSCROLL,     SB_TOP,
    VK_END,     WM_VSCROLL,     SB_BOTTOM,
    VK_UP,      WM_VSCROLL,     SB_LINEUP,
    VK_DOWN,    WM_VSCROLL,     SB_LINEDOWN
    };
#define MAXKEYS (sizeof(keyscroll) / sizeof(keyscroll[0]))

#define MYTIMER         1
#define RBUTTONDCLICK   2

   // main window procedure for WinSmooth
long FAR PASCAL MainWindowProc(HWND hwnd, unsigned message, WORD wParam, LONG lParam)
    {
    static BOOL paused = TRUE, rbdclk = FALSE, app_activated = TRUE;
    static BOOL restart = TRUE;
    FARPROC lpProcAbout, lpOpenDialog, lpSettingsDialog;

    switch(message)
        {
                    //////////// Windows Messages
        case WM_CREATE:
            {
            if(!WSopenfile(filename))
                PostMessage(hwnd,WM_COMMAND,IDM_OPEN,0L);
            else
                PostMessage(hwnd,WM_WSM_STARTTIMER,0,0L);
            }
            break;

        case WM_PAINT:
            PaintMainWindow(hwnd);
            break;

        case WM_TIMER:                  // timer message
            switch(wParam)
                {
                case MYTIMER:
                    if(settings.PixelRows > 0)
                        PostMessage(hwnd,WM_VSCROLL,
                            (direction == DOWN) ? SB_PIXLINEDOWN : SB_PIXLINEUP,0L);
                    break;
                case RBUTTONDCLICK:
                    KillTimer(hwnd,RBUTTONDCLICK);
                    if(!rbdclk)
                        PostMessage(hwnd,WM_RBUTTONDOWN,MY_RBUTTON,0L);
                    else
                        rbdclk = FALSE;
                    break;
                }
            break;

        case WM_VSCROLL:                // vertical scrollbar message
            ProcessVscroll(hwnd, wParam, lParam);
            break;

        case WM_CLOSE:                  // close application
            DestroyWindow(hwnd);
            break;

        case WM_DESTROY:                // destroy application
            CloseApplication();
            KillTimer(hwnd,MYTIMER);
            DeleteObject(hOrgBrush);
            PostQuitMessage(0);
            break;

        case WM_INITMENU:               // stop timer when User uses menu
            restart = (paused ? FALSE : TRUE);
            SendMessage(hwnd,WM_WSM_STOPTIMER,0,0L);
            break;

        case WM_ACTIVATE:               // if activation message sent
            if(wParam == 0)
                app_activated = FALSE;
            break;

        case WM_COMMAND:                // menu messages
            {
            switch(wParam)
                {
                case IDM_OPEN:          // open a new file
                    {
                    int newfile,fileisopen = filebuf_fileisopen();

                    lpOpenDialog = MakeProcInstance(OpenDlg, hinstance);
                    // Open the file and get its handle
                    newfile = DialogBox(hinstance, "OPEN", hwnd, lpOpenDialog);
                    FreeProcInstance(lpOpenDialog);
                    if(newfile)
                        {
                        sb_init = FALSE;
                        SendMessage(hwnd,WM_VSCROLL,SB_TOP,0L);
                        InvalidateRect(hwnd, NULL, TRUE);
                        SendMessage(hwnd,WM_PAINT,0,0L);
                        }
                    else if(!fileisopen)
                        {
                        int i;

                        if((i = MessageBox(hwnd,"Terminate WinSmooth?","WinSmooth",
                                MB_OKCANCEL | MB_ICONQUESTION)) == IDOK)
                            PostMessage(hwnd,WM_CLOSE,0,0L);
                        else
                            PostMessage(hwnd,WM_COMMAND,IDM_OPEN,0L);
                        }
                    }
                    break;

                case IDM_ABOUT:         // display About box
                    lpProcAbout = MakeProcInstance(About, hinstance);
                    DialogBox(hinstance, "ABOUTBOX", hwnd, lpProcAbout);
                    FreeProcInstance(lpProcAbout);
                    break;

                case IDM_SETTINGS:
                    lpSettingsDialog = MakeProcInstance(Settings, hinstance);
                    DialogBox(hinstance, "SETTINGS", hwnd, lpSettingsDialog);
                    FreeProcInstance(lpSettingsDialog);
                    break;

                case IDM_EXIT:          // terminate program
                    PostMessage(hwnd,WM_CLOSE,0,0L);
                    break;

                default:
                    break;
                }
            if(restart)
                SendMessage(hwnd,WM_WSM_STARTTIMER,0,0L);
            }
            break;

            //////////////////// Mouse Message Handling
        case WM_LBUTTONDOWN:            // left mouse button == SPACE
            if(app_activated)
                PostMessage(hwnd,WM_CHAR,SPACE,0L);
            app_activated = TRUE;
            break;

        case WM_RBUTTONDOWN:            // right mouse button == '-'
            if(wParam == MY_RBUTTON)
                PostMessage(hwnd,WM_CHAR,'-',0L);
            else
                {
                rbdclk = FALSE;
                SetTimer(hwnd,RBUTTONDCLICK,GetDoubleClickTime(),NULL);
                }
            break;

        case WM_RBUTTONDBLCLK:          // right button Double click == '+'
            rbdclk = TRUE;
            PostMessage(hwnd,WM_WSM_INCPIXROWS,0,0L);
            break;

            //////////////////// Keyboard Message handling
        case WM_KEYDOWN:
            {
            int i;

            PostMessage(hwnd,WM_WSM_STARTTIMER,0,0L);
            for(i = 0; i < MAXKEYS; i++)
                if(wParam == keyscroll[i].virkey)
                    {
                    SendMessage(hwnd,keyscroll[i].msg,keyscroll[i].sbcode,0L);
                    break;
                    }
            }
            break;

        case WM_CHAR:
            PostMessage(hwnd,WM_WSM_STARTTIMER,0,0L);

            switch(wParam)
                {
                case ESC:               // quit the application
                    PostMessage(hwnd,WM_CLOSE,0,0L);
                    break;

                case SPACE:             // stop the timer
                    if(!paused)
                        PostMessage(hwnd,WM_WSM_STOPTIMER,0,0L);
                    break;

                case '+':               // increase # of rows scrolled
                    PostMessage(hwnd,WM_WSM_INCPIXROWS,0,0L);
                    break;

                case '-':               // decrease # of rows scrolled
                    PostMessage(hwnd,WM_WSM_DECPIXROWS,0,0L);
                    break;

                default:                // if 0-9, set # of rows scrolled
                    if(wParam >= '0' && wParam <= '9')
                        settings.PixelRows = (wParam-'0');
                    break;
                }
            break;

            //////////////////// Application Messages
        case WM_WSM_STARTTIMER:
            if(paused && settings.PixelRows > MINPIXELROWS)
                SetTimer(hwnd,MYTIMER,settings.Msecs,NULL);
            paused = FALSE;
            break;

        case WM_WSM_STOPTIMER:
            if(!paused)
                KillTimer(hwnd,MYTIMER);
            paused = TRUE;
            break;

        case WM_WSM_DECPIXROWS:
            if(settings.PixelRows > MINPIXELROWS)
                settings.PixelRows--;
            break;

        case WM_WSM_INCPIXROWS:
            if(settings.PixelRows < MAXPIXELROWS)
                settings.PixelRows++;
            break;

        case WM_WSM_HOURGLASS_CURSOR:
            SetCursor(hHourGlassCursor);
            break;

        case WM_WSM_NORMAL_CURSOR:
            SetCursor(hNormalCursor);
            break;

        case WM_SIZE:
            ResetPixRows();             // fall thru in case icon'd
        default:
            return (DefWindowProc(hwnd, message, wParam, lParam));
            break;
        }
    return (0L);
    }

long NewPixTopRow = 0L, CurPixTopRow = 0L, BottPixRow = 0L;
unsigned PixRowsPerPage = 0, CharLineHeight = 0, TextRowsPerPage;

   // vertical scroll processing for WinSmooth
void ProcessVscroll(HWND hwnd, WORD wParam, LONG lParam)
    {
    BOOL bscroll = FALSE;

    switch(wParam)
        {
        case SB_PIXLINEUP:      // move up a pixel row
            NewPixTopRow = CurPixTopRow - settings.PixelRows;
            direction = UP;
            break;
        case SB_PIXLINEDOWN:    // move down a pixel row
            NewPixTopRow = CurPixTopRow + settings.PixelRows;
            direction = DOWN;
            break;

        case SB_LINEUP:      // move up a character row
            NewPixTopRow = CurPixTopRow - CharLineHeight;
            direction = UP;
            break;
        case SB_LINEDOWN:    // move down a character row
            NewPixTopRow = CurPixTopRow + CharLineHeight;
            direction = DOWN;
            break;
        case SB_PAGEUP:        // move up a 'page' of rows
            NewPixTopRow = CurPixTopRow - (PixRowsPerPage - 1);
            break;
        case SB_PAGEDOWN:    // move down a 'page' of rows
            NewPixTopRow = CurPixTopRow + PixRowsPerPage - 1;
            break;
        case SB_TOP:        // go to top row
            NewPixTopRow = 0L;
            break;
        case SB_BOTTOM:       // go to bottom row
            if((NewPixTopRow = (BottPixRow-textrowsperpage())) < 0L)
                NewPixTopRow = 0L;
            break;
        case SB_THUMBPOSITION:    // reset to new position
        case SB_THUMBTRACK:    // reset to new position
            NewPixTopRow = (long)LOWORD(lParam);
            NewPixTopRow *= CharLineHeight;
            break;
        }

    switch(wParam)
        {
        case SB_PIXLINEUP:
        case SB_LINEUP:
        case SB_PIXLINEDOWN:
        case SB_LINEDOWN:
        case SB_PAGEUP:
        case SB_PAGEDOWN:
        case SB_TOP:
        case SB_BOTTOM:
        case SB_THUMBPOSITION:
            bscroll = TRUE;
            break;
        }
    NewPixTopRow = max(NewPixTopRow, 0L);
    NewPixTopRow = min(NewPixTopRow,BottPixRow);

    if(bscroll)
        {
        if((labs(NewPixTopRow - CurPixTopRow) < (long)PixRowsPerPage))
            {
            int temp1;
            long temp2;

            temp2 = CurPixTopRow - NewPixTopRow;
            temp1 = (int)temp2;
            ScrollWindow(hwnd, 0,temp1,NULL,NULL);
            }
        else
            InvalidateRect(hwnd, NULL, TRUE);
        UpdateWindow(hwnd);
        }
    }

int Y, lineno;

   // WinSmooth paint procedure
void PaintMainWindow(HWND hwnd)
    {
    PAINTSTRUCT ps;
    HDC hdc;

    int TextTopRow, recttop, rectbott;
    static int X, CharHeight;
    int bottchar;

    BeginPaint(hwnd, &ps);
    recttop = ps.rcPaint.top;
    rectbott = ps.rcPaint.bottom;
    hdc = ps.hdc;

    if(!sb_init)
        {
        TEXTMETRIC tmFontInfo;

        numlines = (long)filebuf_numlines();
        GetTextMetrics(hdc, (LPTEXTMETRIC) &tmFontInfo);
        CharLineHeight = tmFontInfo.tmExternalLeading + tmFontInfo.tmHeight;
        CharHeight = tmFontInfo.tmHeight;
        X = tmFontInfo.tmAveCharWidth;

        BottPixRow = (numlines*CharLineHeight);
        SetScrollRange(hwnd,SB_VERT,0,(unsigned)numlines,TRUE);

        ResetBackGround(hdc);
        ResetPixRows();
        }

        // for writing the text...
    SetBkMode(hdc,TRANSPARENT);         // has to be for back ground dithering
    SetTextColor(hdc,settings.fore);
    SetBkColor(hdc,settings.back);

    Y = (int)(NewPixTopRow%CharLineHeight)*-1;
    if(sb_init)
        Y--;
    else
        sb_init = TRUE;

    UnrealizeObject(hOrgBrush);
    SetBrushOrg(hdc,0, (Y%8) ? (Y%8)+8 : 0);
    SelectObject(hdc,hOrgBrush);

    if(Rectangle(hdc,ps.rcPaint.left,recttop,ps.rcPaint.right,ps.rcPaint.bottom))
        FillRect(hdc,&ps.rcPaint,hOrgBrush);
    SelectObject(hdc,hOrgBrush);

    TextTopRow = max(0u,(unsigned)(NewPixTopRow/CharLineHeight));

    for( lineno = TextTopRow; lineno < min((int)numlines, TextTopRow+TextRowsPerPage-1); lineno++)
        {
        bottchar = Y+CharHeight;

        if( ((rectbott-recttop+1 < (int)CharLineHeight) &&
                ((recttop >= Y && recttop <= bottchar) ||
                 (rectbott >= Y && rectbott <= bottchar)))
            ||

            (Y >= recttop && Y <= rectbott) ||
            (bottchar >= recttop && bottchar <= rectbott))
            {
            LPSTR p;
            unsigned len;

            filebuf_seekline(lineno);
            p = filebuf_nextline(&len);
            TextOut(hdc, X, Y, p, len);
            }
        Y += CharLineHeight;
        }

    CurPixTopRow = NewPixTopRow;
    SetScrollPos(hwnd,SB_VERT,(unsigned)(NewPixTopRow/CharLineHeight),TRUE);
    ValidateRect(hwnd, (LPRECT)NULL);
    EndPaint(hwnd, (LPPAINTSTRUCT) &ps);

    if((direction == DOWN) && (CurPixTopRow > (BottPixRow-textrowsperpage())))
        direction = UP;
    else if(CurPixTopRow == 0L && direction == UP)
        direction = DOWN;
    }

   // sets up background color for main window
void ResetBackGround(HDC hDC)
    {
    HANDLE hOldBrush;

    hOrgBrush = CreateSolidBrush(settings.back);
    hOldBrush = SelectObject(hDC,hOrgBrush);
    DeleteObject(hOldBrush);
    SetClassWord(WinSmooth,GCW_HBRBACKGROUND,hOrgBrush);
    }

   // sets pixel row variable based on window size
void ResetPixRows(void)
    {
    RECT    rect;

    if(WinSmooth)
        {
        GetClientRect(WinSmooth, &rect);
        PixRowsPerPage = rect.bottom;
        if(CharLineHeight)
            TextRowsPerPage = textrowsperpage();
        }
    }

#define INIBUFSIZE 20
   // retrieves WIN.INI settings for WinSmooth
void RetrieveSettings(void)
    {
    char buf[INIBUFSIZE+1],defbuf[INIBUFSIZE+1];

    settings.Msecs = GetProfileInt((LPSTR)"WinSmooth",(LPSTR)"Msecs",settings.Msecs);
    settings.PixelRows = GetProfileInt((LPSTR)"WinSmooth",(LPSTR)"PixelRows",settings.PixelRows);
    
    sprintf(defbuf,"%lu",settings.back);
    GetProfileString((LPSTR)"WinSmooth",(LPSTR)"BackGround",(LPSTR)defbuf,(LPSTR)buf,INIBUFSIZE);
    sscanf(buf,"%lu",&settings.back);

    sprintf(defbuf,"%lu",settings.fore);
    GetProfileString((LPSTR)"WinSmooth",(LPSTR)"ForeGround",(LPSTR)defbuf,(LPSTR)buf,INIBUFSIZE);
    sscanf(buf,"%lu",&settings.fore);

    settings.stripbits = GetProfileInt((LPSTR)"WinSmooth",(LPSTR)"StripBits",settings.stripbits);
    }

// end of wsmooth.c

