/** \file mswtext.c
* Text entry field
*/

/*  XTrkCad - Model Railroad CAD
*  Copyright (C) 2005 Dave Bullis
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <windows.h>
#include <string.h>
#include <malloc.h>
#include <stdlib.h>
#include <commdlg.h>
#include <math.h>
#include <stdio.h>
#include "mswint.h"

/*
 *****************************************************************************
 *
 * Multi-line Text Boxes
 *
 *****************************************************************************
 */

static LOGFONT fixedFont = {
    /* Initial default values */
    -18, 0, /* H, W */
    0,		/* A */
    0,
    FW_REGULAR,
    0, 0, 0,/* I, U, SO */
    ANSI_CHARSET,
    0,		/* OP */
    0,		/* CP */
    0,		/* Q */
    FIXED_PITCH|FF_MODERN,	/* P&F */
    "Courier"
};
static HFONT fixedTextFont, prevTextFont;

struct wText_t {
    WOBJ_COMMON
    HANDLE hText;
};


void wTextClear(
    wText_p b)
{
    LRESULT rc;
    rc = SendMessage(b->hWnd, EM_SETREADONLY, (WPARAM)0, (LPARAM)0);
    rc = SendMessage(b->hWnd, EM_SETSEL, (WPARAM)0, (LPARAM)-1);
    rc = SendMessage(b->hWnd, WM_CLEAR, (WPARAM)0, (LPARAM)0);

    if (b->option&BO_READONLY) {
        rc = SendMessage(b->hWnd, EM_SETREADONLY, (WPARAM)1, (LPARAM)0);
    }
}

/**
 * Append text to a multiline text box:
 * For every \n a \r is added
 * the current text is retrieved from the control
 * the new text is appended and then set
 *
 * \param b IN text box handle
 * \param text IN text to add to text box
 * \return
 */

void wTextAppend(
    wText_p b,
    const char * text)
{
    char *cp;
    char *buffer;
    char *extText;
    int textSize;
    size_t len = strlen(text);

    if (!len) {
        return;
    }

    for (cp = (char *)text; *cp; cp++) {
        if (*cp == '\n') {
            len++;
        }
    }

    extText = malloc(len + 1 + 10);

    for (cp=extText; *text; cp++,text++) {
        if (*text == '\n') {
            *cp++ = '\r';
            *cp = '\n';
        } else {
            *cp = *text;
        }
    }

    *cp = '\0';
    textSize = GetWindowTextLength(b->hWnd);
    buffer = malloc((textSize + len + 1) * sizeof(char));

    if (buffer) {
        GetWindowText(b->hWnd, buffer, textSize + 1);
        strcat(buffer, extText);
        SetWindowText(b->hWnd, buffer);
        free(extText);
        free(buffer);
    } else {
        abort();
    }

    if (b->option&BO_READONLY) {
        SendMessage(b->hWnd, EM_SETREADONLY, (WPARAM)1, (LPARAM)0);
    }

	// scroll to bottom of text box
	SendMessage(b->hWnd, EM_LINESCROLL, (WPARAM)0, (LPARAM)10000);
}


BOOL_T wTextSave(
    wText_p b,
    const char * fileName)
{
    FILE * f;
    int lc, l, len;
    char line[255];
    f = wFileOpen(fileName, "w");

    if (f == NULL) {
        MessageBox(((wControl_p)(b->parent))->hWnd, "TextSave", "", MB_OK|MB_ICONHAND);
        return FALSE;
    }

    lc = (int)SendMessage(b->hWnd, EM_GETLINECOUNT, (WPARAM)0, (LPARAM)0);

    for (l=0; l<lc; l++) {
        *(WORD*)line = sizeof(line)-1;
        len = (int)SendMessage(b->hWnd, EM_GETLINE, (WPARAM)l, (LPARAM)line);
        line[len] = '\0';
        fprintf(f, "%s\n", line);
    }

    fclose(f);
    return TRUE;
}


BOOL_T wTextPrint(
    wText_p b)
{
    int lc, l, len;
    char line[255];
    HDC hDc;
    int lineSpace;
    int linesPerPage;
    int currentLine;
    int IOStatus;
    TEXTMETRIC textMetric;
    DOCINFO docInfo;
    hDc = mswGetPrinterDC();
	HFONT hFont, hOldFont;

    if (hDc == (HDC)0) {
        MessageBox(((wControl_p)(b->parent))->hWnd, "Print", "Cannot print",
                   MB_OK|MB_ICONHAND);
        return FALSE;
    }

    docInfo.cbSize = sizeof(DOCINFO);
    docInfo.lpszDocName = "XTrkcad Log";
    docInfo.lpszOutput = (LPSTR)NULL;

	// Retrieve a handle to the monospaced stock font.  
	hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
	hOldFont = (HFONT)SelectObject(hDc, hFont);

    if (StartDoc(hDc, &docInfo) < 0) {
        MessageBox(((wControl_p)(b->parent))->hWnd, "Unable to start print job", NULL,
                   MB_OK|MB_ICONHAND);
        DeleteDC(hDc);
        return FALSE;
    }

    StartPage(hDc);

    GetTextMetrics(hDc, &textMetric);
    lineSpace = textMetric.tmHeight + textMetric.tmExternalLeading;
    linesPerPage = GetDeviceCaps(hDc, VERTRES) / lineSpace;
    currentLine = 1;
    lc = (int)SendMessage(b->hWnd, EM_GETLINECOUNT, (WPARAM)0, (LPARAM)0);
    IOStatus = 0;

    for (l=0; l<lc; l++) {
        *(WORD*)line = sizeof(line)-1;
        len = (int)SendMessage(b->hWnd, EM_GETLINE, (WPARAM)l, (LPARAM)line);
        TextOut(hDc, 0, currentLine*lineSpace, line, len);

        if (++currentLine > linesPerPage) {
            IOStatus = EndPage(hDc);
            if (IOStatus < 0 ) {
                break;
            }
            StartPage(hDc);
			currentLine = 1;
		}
    }

    if (IOStatus >= 0 ) {
        EndPage(hDc);
        EndDoc(hDc);
    }

	SelectObject(hDc, hOldFont);
    DeleteDC(hDc);
    return TRUE;
}


wBool_t wTextGetModified(
    wText_p b)
{
    int rc;
    rc = (int)SendMessage(b->hWnd, EM_GETMODIFY, (WPARAM)0, (LPARAM)0);
    return (wBool_t)rc;
}

/**
 * Get the size of the text in the text control including terminating '\0'. Note that
 * the text actually might be shorter if the text includes CRs.
 * 
 * \param b IN text control
 * \return required buffer size
 */
int wTextGetSize(
    wText_p b)
{
	int len;

	len = GetWindowTextLength(b->hWnd);

    return len + 1;
}

/** 
 * Get the text from a textentry. The buffer must be large enough for the text and
 * the terminating \0.
 * In case the string contains carriage returns these are removed. The returned string
 * will be shortened accordingly. 
 * To get the complete contents the buffer size must be equal or greater then the return
 * value of wTextGetSize()
 * 
 * \param b IN text entry
 * \param t IN/OUT buffer for text
 * \param s IN size of buffer 
 */
 
void wTextGetText(
    wText_p b,
    char * t,
    int s)
{
	char *buffer = malloc(s);
	char *ptr = buffer;
	GetWindowText(b->hWnd, buffer, s);

	// remove carriage returns
	while (*ptr) {
		if (*ptr != '\r') {
			*t = *ptr;
			t++;
		}
		ptr++;
	}
	free(buffer);
}


void wTextSetReadonly(
    wText_p b,
    wBool_t ro)
{
    if (ro) {
        b->option |= BO_READONLY;
    } else {
        b->option &= ~BO_READONLY;
    }

    SendMessage(b->hWnd, EM_SETREADONLY, (WPARAM)ro, (LPARAM)0);
}


void wTextSetSize(
    wText_p bt,
    wWinPix_t width,
    wWinPix_t height)
{
    bt->w = width;
    bt->h = height;

    if (!SetWindowPos(bt->hWnd, HWND_TOP, 0, 0,
                      bt->w, bt->h, SWP_NOMOVE|SWP_NOZORDER)) {
        mswFail("wTextSetSize: SetWindowPos");
    }
}


void wTextComputeSize(
    wText_p bt,
    wWinPix_t rows,
    wWinPix_t lines,
    wWinPix_t * w,
    wWinPix_t * h)
{
    static wWinPix_t scrollV_w = -1;
    static wWinPix_t scrollH_h = -1;
    HDC hDc;
    TEXTMETRIC metrics;

    if (scrollV_w < 0) {
        scrollV_w = GetSystemMetrics(SM_CXVSCROLL);
    }

    if (scrollH_h < 0) {
        scrollH_h = GetSystemMetrics(SM_CYHSCROLL);
    }

    hDc = GetDC(bt->hWnd);
    GetTextMetrics(hDc, &metrics);
    *w = rows * metrics.tmAveCharWidth + scrollV_w;
    *h = lines * (metrics.tmHeight + metrics.tmExternalLeading);
    ReleaseDC(bt->hWnd, hDc);

    if (bt->option&BT_HSCROLL) {
        *h += scrollH_h;
    }
}


void wTextSetPosition(
    wText_p bt,
    int pos)
{
    LRESULT rc;
    rc = SendMessage(bt->hWnd, EM_LINESCROLL, (WPARAM)0, (LPARAM)MAKELONG(-65535, 0));
}

static void textDoneProc(wControl_p b)
{
    wText_p t = (wText_p)b;
    HDC hDc;
    hDc = GetDC(t->hWnd);
    SelectObject(hDc, mswOldTextFont);
    ReleaseDC(t->hWnd, hDc);
}

static callBacks_t textCallBacks = {
    mswRepaintLabel,
    textDoneProc,
    NULL
};

wText_p wTextCreate(
    wWin_p	parent,
    wWinPix_t	x,
    wWinPix_t	y,
    const char	* helpStr,
    const char	* labelStr,
    long	option,
    wWinPix_t	width,
    wWinPix_t	height)
{
    wText_p b;
    DWORD style;
    RECT rect;
    int index;
    b = mswAlloc(parent, B_TEXT, labelStr, sizeof *b, NULL, &index);
    mswComputePos((wControl_p)b, x, y);
    b->option = option;
    style = ES_MULTILINE | ES_LEFT | ES_AUTOVSCROLL | ES_WANTRETURN |
            WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL;
#ifdef BT_HSCROLL

    if (option & BT_HSCROLL) {
        style |= WS_HSCROLL | ES_AUTOHSCROLL;
    }

#endif
    /*	  if (option & BO_READONLY)
    		style |= ES_READONLY;*/
    b->hWnd = CreateWindow("EDIT", NULL,
                           style, b->x, b->y,
                           width, height,
                           ((wControl_p)parent)->hWnd, (HMENU)(UINT_PTR)index, mswHInst, NULL);

    if (b->hWnd == NULL) {
        mswFail("CreateWindow(TEXT)");
        return b;
    }

    if (option & BT_FIXEDFONT) {
        if (fixedTextFont == (HFONT)0) {
            fixedTextFont =	 CreateFontIndirect(&fixedFont);
        }

        SendMessage(b->hWnd, WM_SETFONT, (WPARAM)fixedTextFont, (LPARAM)MAKELONG(1, 0));
    } else 	if (!mswThickFont) {
        SendMessage(b->hWnd, WM_SETFONT, (WPARAM)mswLabelFont, (LPARAM)0);
    }

    b->hText = (HANDLE)SendMessage(b->hWnd, EM_GETHANDLE, (WPARAM)0, (LPARAM)0);

    if (option & BT_CHARUNITS) {
        wWinPix_t w, h;
        wTextComputeSize(b, width, height, &w, &h);

        if (!SetWindowPos(b->hWnd, HWND_TOP, 0, 0,
                          w, h, SWP_NOMOVE|SWP_NOZORDER)) {
            mswFail("wTextCreate: SetWindowPos");
        }
    }

    GetWindowRect(b->hWnd, &rect);
    b->w = rect.right - rect.left;
    b->h = rect.bottom - rect.top;
    mswAddButton((wControl_p)b, FALSE, helpStr);
    mswCallBacks[B_TEXT] = &textCallBacks;
    return b;
}
