/*** winclip.c - windows clipboard editor extension
*
*   Copyright <C> 1988, Microsoft Corporation
*
* Purpose:
*  Contains the tglcase function.
*
* Revision History:
*
*   28-Jun-1988 LN  Created
*   12-Sep-1988 mz  Made WhenLoaded match declaration
*
*************************************************************************/

#include <windows.h>
#include <stdlib.h>			/* min macro definition 	*/
#include <string.h>                     /* prototypes for string fcns   */

#undef  pascal
#include "zext.h"

#define M_FALSE     ((flagType)0)
#define M_TRUE      ((flagType)(-1))

#define BUFLEN_MAX  (BUFLEN-1)

/*
** Internal function prototypes
*/
#ifdef DEBUG
    #define DPRINT(p) DoMessage(p)
#else
    #define DPRINT(p)
#endif

HWND ghwndClip;
HINSTANCE ghmod;
int gfmtArgType;

void DeleteArg( PFILE pFile, int argType, COL xStart, LINE yStart,
                COL xEnd, COL yEnd );

void InsertText( PFILE pFile, LPSTR pszText, DWORD dwInsMode,
                 COL xStart, LINE yStart );
flagType pascal EXTERNAL WinCutCopy (ARG *pArg, flagType fCut, flagType fClip);
LPSTR EndOfLine( LPSTR psz );
LPSTR EndOfBreak( LPSTR psz );
int ExtendLine( LPSTR psz, int cchSZ, char ch, int cchNew );

/*************************************************************************
**
** wincopy
** Toggle the case of alphabetics contaied within the selected argument:
**
**  NOARG	- Toggle case of entire current line
**  NULLARG	- Toggle case of current line, from cursor to end of line
**  LINEARG	- Toggle case of range of lines
**  BOXARG	- Toggle case of characters with the selected box
**  NUMARG	- Converted to LINEARG before extension is called.
**  MARKARG	- Converted to Appropriate ARG form above before extension is
**		  called.
**
**  STREAMARG	- Not Allowed. Treated as BOXARG
**  TEXTARG	- Not Allowed
**
*/
flagType
pascal
EXTERNAL
wincopy (
        unsigned int argData,       /* keystroke invoked with	*/
        ARG *pArg,                          /* argument data                */
        flagType fMeta              /* indicates preceded by meta	*/
        )
{

    return WinCutCopy( pArg, M_FALSE, M_FALSE );
}

flagType
pascal
EXTERNAL
wincut (
       unsigned int argData,       /* keystroke invoked with	*/
       ARG *pArg,                          /* argument data                */
       flagType fMeta              /* indicates preceded by meta	*/
       )
{

    return WinCutCopy( pArg, M_TRUE, fMeta );
}

flagType
pascal
EXTERNAL
WinCutCopy (
           ARG *pArg,
           flagType fCut,
           flagType fNoClip
           )
{
    PFILE   pFile;                          /* file handle of current file  */
    COL xStart, xEnd;
    LINE yStart, yEnd;
    char achLine[BUFLEN];
    HANDLE hText;
    LPSTR pszText;
    int     iLine, cchLine;
    flagType fRet = M_TRUE;
    int     argSave, argType;

    pFile = FileNameToHandle ("", "");


    argSave = argType = pArg->argType;

    switch ( argType ) {
        case BOXARG:                        /* case switch box              */
            xStart = pArg->arg.boxarg.xLeft;
            xEnd   = pArg->arg.boxarg.xRight + 1;
            yStart = pArg->arg.boxarg.yTop;
            yEnd   = pArg->arg.boxarg.yBottom + 1;

            /* At this point...
             *  [xy]Start is Inclusive, [xy]End is EXCLUSIVE of the box arg
             */

#ifdef DEBUG
            wsprintf( achLine, " BoxDims : %d %d %d %d ", (int)xStart, (int)yStart, (int)xEnd, (int)yEnd);
            DoMessage( achLine );
#endif
            break;

        case NOARG:
            /* convert NOARG to a STREAMARG on whole current line */
            argType = STREAMARG;
            argSave = LINEARG;
            xStart = 0;
            yStart = pArg->arg.noarg.y;
            xEnd = 0;
            yEnd = yStart + 1;
            break;

        case TEXTARG:
            /*
             * Text args are only for real text.  NumArgs and MarkArgs are
             * converted to stream or box args by the editor since we say
             * we accept NUMARG and MARKARG during initialization.
             */
            argType = STREAMARG;
            argSave = STREAMARG;
            xStart = pArg->arg.textarg.x;
            xEnd = lstrlen(pArg->arg.textarg.pText) + xStart;
            yStart = yEnd = pArg->arg.textarg.y;
            break;

        case LINEARG:                       /* case switch line range       */
            /* convert LINEARG to a STREAMARG so we don't get lots of white space*/
            argType = STREAMARG;
            xStart = 0;
            xEnd = 0;
            yStart = pArg->arg.linearg.yStart;
            yEnd = pArg->arg.linearg.yEnd + 1;
#ifdef DEBUG
            wsprintf( achLine, " LineDims : %d %d %d %d ", (int)xStart, (int)yStart, (int)xEnd, (int)yEnd);
            DoMessage( achLine );
#endif

            /* At this point...
             *  [xy]Start is Inclusive, [xy]End is EXCLUSIVE of the line arg
             */

            break;

        case STREAMARG:
            /*
             * Set Start == first char pos in stream, End == first char pos
             * AFTER stream.
             */
            xStart = pArg->arg.streamarg.xStart;
            xEnd = pArg->arg.streamarg.xEnd;
            yStart = pArg->arg.streamarg.yStart;
            yEnd = pArg->arg.streamarg.yEnd;
#ifdef DEBUG
            wsprintf( achLine, " StreamDims : %d %d %d %d ", (int)xStart, (int)yStart, (int)xEnd, (int)yEnd);
            DoMessage( achLine );
#endif
            break;

        default:
#ifdef DEBUG
            wsprintf( achLine, " Unknown Arg: 0x%04x", argType );
            DoMessage( achLine );
            return M_TRUE;
#endif
            return M_FALSE;
    }

    if (!fNoClip) {
        if (argType == STREAMARG) {
            int cch = 0;
            int iChar;

            for ( iLine = yStart; iLine <= yEnd; iLine++ )
                cch += GetLine (iLine, achLine, pFile) + 3;

            hText = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cch);

            if (hText == NULL) {
                DoMessage( " winclip: Out of Memory" );
                return M_FALSE;
            }

            pszText = GlobalLock(hText);


            iChar = xStart;

            for ( iLine = yStart; iLine < yEnd; iLine++ ) {
                cchLine = GetLine (iLine, achLine, pFile);

                /* Incase we start after the end of the line */
                if (cchLine < iChar)
                    cch = 0;
                else
                    cch = cchLine - iChar;

                CopyMemory(pszText, &achLine[iChar], cch);
                pszText += cch;
                strcpy( pszText, "\r\n" );
                pszText += 2;
                iChar = 0;

            }

            /* Get partial last line */
            if (xEnd != 0) {
                cchLine = GetLine (iLine, achLine, pFile);

                /* if line is short, then pad it out */
                cchLine = ExtendLine( achLine, cchLine, ' ', xEnd );

                if (cchLine < iChar)
                    cchLine = 0;
                else
                    cchLine = xEnd - iChar;

                CopyMemory(pszText, &achLine[iChar], cchLine);
                pszText += cchLine;
            }

        } else {
            LINE iLine;
            int cchBox = xEnd - xStart;

            hText = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
                                (yEnd - yStart) * (cchBox + 3));

            if (hText == NULL) {
                DoMessage( " winclip: Out of Memory" );
                return M_FALSE;
            }

            pszText = GlobalLock(hText);

            for ( iLine = yStart; iLine < yEnd; iLine++ ) {
                cchLine = GetLine (iLine, achLine, pFile);

                if (argType == BOXARG)
                    cchLine = ExtendLine( achLine, cchLine, ' ', xEnd );

                if (cchLine < xStart )
                    cchLine = 0;
                else
                    cchLine -= xStart;

                cchLine = min(cchLine, cchBox);

                CopyMemory(pszText, &achLine[xStart], cchLine);
                pszText += cchLine;
                strcpy( pszText, "\r\n" );
                pszText += 2;

            }
        }

        *pszText = '\0';

        GlobalUnlock(hText);

        if (OpenClipboard(ghwndClip)) {
            EmptyClipboard();

            /*
             * Set the text into the clipboard
             */
            if (SetClipboardData(CF_TEXT, hText) == hText) {
                /*
                 * Remember the Arg type for pasting back
                 */
                if (gfmtArgType != 0) {
                    DWORD *pdw;
                    HANDLE hArgType = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
                                                  sizeof(DWORD));

                    if (hArgType != NULL && (pdw = GlobalLock(hArgType)) != NULL) {
                        *pdw = (DWORD)(argSave);

                        GlobalUnlock(hArgType);

                        SetClipboardData(gfmtArgType, hArgType);
                    }
                }
            } else {
                /* An error occured writing text to clipboard */

                wsprintf(achLine, " winclip: Error (%ld) setting data",
                         GetLastError());
                DoMessage( achLine );
                fRet = M_FALSE;
            }

            CloseClipboard();
        }
    }

    /*
     * No need to free the handle, USER32 will do it (yes it keeps
     * track of the client side handle) when we set the next clipboard
     * data.  (Love that Win3.1 compatibility!)
     */
    if (fRet && fCut)
        DeleteArg( pFile, argType, xStart, yStart, xEnd, yEnd );


    return fRet;
}

/*************************************************************************
**
** winpaste
** Toggle the case of alphabetics contaied within the selected argument:
**
**  NOARG	- Toggle case of entire current line
**  NULLARG	- Toggle case of current line, from cursor to end of line
**  LINEARG	- Toggle case of range of lines
**  BOXARG	- Toggle case of characters with the selected box
**  NUMARG	- Converted to LINEARG before extension is called.
**  MARKARG	- Converted to Appropriate ARG form above before extension is
**		  called.
**
**  STREAMARG	- Not Allowed. Treated as BOXARG
**  TEXTARG	- Not Allowed
**
*/
flagType
pascal
EXTERNAL
winpaste (
         unsigned int argData,       /* keystroke invoked with	*/
         ARG *pArg,                          /* argument data                */
         flagType fMeta              /* indicates preceded by meta	*/
         )
{
    PFILE   pFile;                          /* file handle of current file  */
    COL xStart, xEnd;
    LINE yStart, yEnd;
    int argType;
    UINT fmtData = CF_TEXT;
    DWORD dwInsMode = STREAMARG;
    HANDLE hText;
    LPSTR pszText;

    /*
     * Get the clipboard text and insertion type
     */
    if (pArg->argType == TEXTARG) {
        int i, j;
        char achLine[3 + 1 + 3 + 1 + 1 + BUFLEN + 1 + 1 + 5 + 1];
        char *p;

        /*
         * Quick hack to make text arg pastes work like the do in Z
         */
        j = pArg->arg.textarg.cArg;
        if (j > 2)
            j = 2;

        achLine[0] = '\0';
        for ( i = 0; i < j; i++ )
            lstrcat(achLine, "arg ");

        p = achLine + lstrlen(achLine);
        wsprintf( p, "\"%s\" paste", pArg->arg.textarg.pText );
        return fExecute( achLine );
    }

    /* if no text then return FALSE */
    if (!IsClipboardFormatAvailable(fmtData)) {

        /* No text, try display text */
        fmtData = CF_DSPTEXT;

        if (!IsClipboardFormatAvailable(fmtData)) {
            /* bummer! no text at all, return FALSE */
            DoMessage( " winclip: invalid clipboard format" );
            return M_FALSE;
        }
    }

    if (!OpenClipboard(ghwndClip))
        return M_FALSE;

    hText = GetClipboardData(fmtData);
    if (hText == NULL || (pszText = GlobalLock(hText)) == NULL) {
        CloseClipboard();
        return M_FALSE;
    }


    /* Get insert mode */

    if (IsClipboardFormatAvailable(gfmtArgType)) {
        DWORD *pdw;
        HANDLE hInsMode;

        hInsMode = GetClipboardData(gfmtArgType);

        if (hInsMode != NULL && (pdw = GlobalLock(hInsMode)) != NULL) {
            dwInsMode = *pdw;

            GlobalUnlock(hInsMode);
        }
    }



    pFile = FileNameToHandle ("", "");

    argType = pArg->argType;

    switch ( argType ) {
        case BOXARG:                        /* case switch box              */
            /*
             * Set [xy]Start inclusive of box arg,
             *     [xy]End   exclusive of box arg.
             */
            xStart = pArg->arg.boxarg.xLeft;
            xEnd   = pArg->arg.boxarg.xRight + 1;
            yStart = pArg->arg.boxarg.yTop;
            yEnd   = pArg->arg.boxarg.yBottom + 1;
            break;

        case LINEARG:           /* case switch line range	*/
            /*
             * Set [xy]Start inclusive of line arg,
             *     [xy]End   exclusive of line arg.
             */
            xStart = 0;
            xEnd = BUFLEN + 1;
            yStart = pArg->arg.linearg.yStart;
            yEnd = pArg->arg.linearg.yEnd + 1;
            break;

        case STREAMARG:
            /*
             * Set [xy]Start inclusive of stream
             *     xEnd is EXCLUSIVE of stream
             *     yEnd is INCLUSIVE of stream
             */
            xStart = pArg->arg.streamarg.xStart;
            xEnd = pArg->arg.streamarg.xEnd;
            yStart = pArg->arg.streamarg.yStart;
            yEnd = pArg->arg.streamarg.yEnd;
            break;

        case NOARG:
            xStart = pArg->arg.noarg.x;
            xEnd = xStart + 1;
            yStart = pArg->arg.noarg.y;
            yEnd = yStart + 1;
            break;

        default:
            GlobalUnlock(hText);
            CloseClipboard();
            return M_FALSE;
    }


    /*
     * Delete any selection
     */
    DeleteArg( pFile, argType, xStart, yStart, xEnd, yEnd );

    /*
     * Insert new text with correct mode
     */
    InsertText( pFile, pszText, dwInsMode, xStart, yStart );

    GlobalUnlock(hText);
    CloseClipboard();

    return M_TRUE;
}

/*************************************************************************
**
** windel
**
**
*/
flagType
pascal
EXTERNAL
windel (
       unsigned int argData,               /* keystroke invoked with       */
       ARG *pArg,                          /* argument data                */
       flagType fMeta                      /* indicates preceded by meta   */
       )
{
    int argType = pArg->argType;

    if (argType == NOARG)
        return fExecute("delete");

    if (argType == NULLARG) {
        int c, x, y;
        c = pArg->arg.nullarg.cArg;
        x = pArg->arg.nullarg.x;
        y = pArg->arg.nullarg.y;

        pArg->argType = STREAMARG;
        pArg->arg.streamarg.xStart = x;
        pArg->arg.streamarg.xEnd = 0;
        pArg->arg.streamarg.yStart = y;
        pArg->arg.streamarg.yEnd = y + 1;
        pArg->arg.streamarg.cArg = c;
    }

    return WinCutCopy (pArg, M_TRUE, fMeta);
}

/*************************************************************************
**
** WhenLoaded
** Executed when extension gets loaded. Identify self & assign default
** keystroke.
**
** Entry:
**  none
*/
void
winclipWhenLoaded ()
{
#if 0
    WNDCLASS wc;

    ghmod = GetModuleHandle(NULL);

    wc.style = 0;
    wc.lpfnWndProc = (WNDPROC)DefWindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = ghmod;
    wc.hIcon = NULL;
    wc.hCursor =  NULL;
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  NULL;   /* Name of menu resource in .RC file. */
    wc.lpszClassName = "WinClipWClass"; /* Name used in call to CreateWindow. */

    if (RegisterClass(&wc) && (ghwndClip = CreateWindow( "WinClipWClass",
                                                         "ClipWindow", WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL,
                                                         ghmod, NULL)) == NULL ) {
        DoMessage( " winclip: Initialization failed!" );
    }
#else
    ghwndClip = NULL; //assign clipboard to this thread instead
#endif

    gfmtArgType = RegisterClipboardFormat( "Z Arg Type" );
}


void
DeleteArg(
         PFILE pFile,
         int argType,
         COL xStart,
         LINE yStart,
         COL xEnd,
         COL yEnd
         )
{

    switch ( argType ) {

        case STREAMARG:
            DelStream(pFile, xStart, yStart, xEnd, yEnd);
            break;

        case LINEARG:
            DelStream(pFile, 0, yStart, 0, yEnd);
            break;


        case BOXARG: {
                LINE iLine;

                for ( iLine = yStart; iLine < yEnd; iLine++ ) {
                    DelStream( pFile, xStart, iLine, xEnd, iLine );
                }

                break;
            }


        default:
            break;
    }
}




void
InsertText(
          PFILE pFile,
          LPSTR pszText,
          DWORD dwInsMode,
          COL xStart,
          LINE yStart
          )
{
    char ch;
    int  cchLine, cchText, cchCopy;
    LPSTR pszNL;
    char achLine[BUFLEN];
    char achEnd[BUFLEN];

    switch ( dwInsMode ) {
        case STREAMARG:
            /*
             * Split current line,
             * tack first line from buffer to end of new line
             * put the new lines in file
             * shove the last line to the beggining of the 2nd half of the line
             */
            DPRINT( "  Stream Paste" );
            if ( *pszText == '\0' )
                break;


            pszNL = EndOfLine(pszText);

            cchLine = GetLine( yStart, achLine, pFile );

            if (cchLine < xStart)
                cchLine = ExtendLine( achLine, cchLine, ' ', xStart );

            cchText = (int)(pszNL - pszText);
            if (xStart + cchText >= BUFLEN_MAX) {
                cchText = BUFLEN_MAX - xStart;
                pszNL = pszText + cchText;
            }

            strcpy( achEnd, &achLine[xStart] );
            cchLine -= xStart;

            CopyMemory( &achLine[xStart], pszText, cchText );
            cchText += xStart;
            achLine[cchText] = '\0';


            while ( *pszNL ) {
                PutLine( yStart++, achLine, pFile );
                CopyLine( NULL, pFile, 0, 0, yStart );

                pszText = EndOfBreak(pszNL);
                pszNL = EndOfLine(pszText);

                cchText = (int)(pszNL - pszText);

                CopyMemory( achLine, pszText, cchText );
                achLine[cchText] = '\0';
            }

            cchCopy = 0;
            if (cchLine + cchText > BUFLEN_MAX) {
                cchCopy = (cchLine + cchText) - BUFLEN_MAX;
                cchLine = cchLine - cchCopy;
            }

            CopyMemory( &achLine[cchText], achEnd, cchLine );
            achLine[cchLine+cchText] = '\0';
            PutLine( yStart++, achLine, pFile );

            if (cchCopy != 0) {
                CopyLine( NULL, pFile, 0, 0, yStart );
                CopyMemory( achLine, &achEnd[cchLine], cchCopy );
                achLine[cchCopy] = '\0';
                PutLine( yStart++, achLine, pFile);
            }
            break;

        case BOXARG:
            /*
             * Insert the text as a block into the middle of each line.
             * This could be tricky since we need to pad all short lines
             * out with spaces to match the lenght of the longest line
             * in the text.
             */

            DPRINT( "  Box Paste" );
            while ( *pszText ) {
                pszNL = EndOfLine(pszText);

                cchLine = GetLine( yStart, achLine, pFile );

                if (cchLine < xStart)
                    cchLine = ExtendLine( achLine, cchLine, ' ', xStart );

                cchText = (int)(pszNL - pszText);
                if (cchLine + cchText > BUFLEN_MAX)
                    cchText = BUFLEN_MAX - cchLine;

                /* insert text in middle of line */
                strcpy( achEnd, &achLine[xStart] );
                CopyMemory( &achLine[xStart], pszText, cchText );
                strcpy( &achLine[xStart + cchText], achEnd );

                /* put line in file */
                PutLine( yStart++, achLine, pFile );

                pszText = EndOfBreak(pszNL);
            }
            break;

        case LINEARG:
            /*
             * shove the lines in the buffer before the current line
             */
            DPRINT( "  Line Paste" );
            while ( *pszText ) {
                pszNL = EndOfLine(pszText);
                ch = *pszNL;
                *pszNL = '\0';
                CopyLine( NULL, pFile, 0, 0, yStart );
                PutLine( yStart++, pszText, pFile);
                *pszNL = ch;
                pszText = EndOfBreak(pszNL);
            }
            break;

        default:
            break;
    }

}


LPSTR
EndOfLine(
         LPSTR psz
         )
{
    int c;

    c = 0;
    while ( *psz && *psz != '\r' && *psz != '\n' && c++ < BUFLEN_MAX )
        psz++;

    return psz;
}

LPSTR
EndOfBreak(
          LPSTR psz
          )
{
    char chSkip;

    switch ( *psz ) {
        case '\r':
            chSkip = '\n';
            break;

        case '\n':
            chSkip = '\r';
            break;

        default:
            return psz;

    }

    if (*(++psz) == chSkip)
        psz++;

    return psz;
}


int
ExtendLine(
          LPSTR psz,
          int cchLine,
          char ch,
          int cchTotal
          )
{

    if ( cchLine >= cchTotal )
        return cchLine;

    if (cchTotal > BUFLEN_MAX)
        cchTotal = BUFLEN_MAX;

    psz = &psz[cchLine];

    while ( cchLine++ < cchTotal )
        *psz++ = ch;

    *psz = '\0';

    return cchLine;
}
