char *cknwin = "Win32 GUI Support 8.0.029, 10 March 2004";
/* C K N W I N   --  Kermit GUI Windows support for Win32 systems */

/*
  Author: Jeffrey Altman (jaltman@secure-endpoints.com)
            Secure Endpoints Inc., New York City

  Copyright (C) 1985, 2004, Trustees of Columbia University in the City of New
  York.
*/

#include <windows.h>
#include <tapi.h>
#include <commctrl.h>
#include "ckcdeb.h"
#include "ckcker.h"
#include "ckcasc.h"
#include "cknwin.h"
#include "ckowin.h"
#include "ckntap.h"
#include "ckocon.h"
#include "ckuusr.h"
#include "ckokey.h"
#include "ckokvb.h"
#include "ckosyn.h"
#include "richedit.h"

/* Global variable */

HINSTANCE   hInst = 0 ;
extern HINSTANCE hInstance ;
HWND        hwndGUI = NULL ;
HWND        hwndConsole = NULL ;

extern HWND hwndDialer ;
extern LONG KermitDialerID ;
extern int  DeleteStartupFile;
extern char cmdfil[];

#ifdef KUI
#include "kui/ikcmd.h"
#include "kui/ikui.h"
extern struct _kui_init kui_init;
#endif /* KUI */

/* Function prototypes */

#ifdef CK_WIN
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
#endif

InitApplication(HINSTANCE);
InitInstance(HINSTANCE, int);
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

/* Application entry point */

#ifdef COMMENT
int WINAPI WinMain(hinstance, hPrevInstance, lpCmdLine, nCmdShow)
HINSTANCE hinstance;
HINSTANCE hPrevInstance;
LPSTR lpCmdLine;
int nCmdShow;
#endif


#ifndef CK_WIN
HANDLE WindowThread = INVALID_HANDLE_VALUE;
DWORD  WindowThreadId = -1 ;

void
ckWindowThread( void * hInstance )
{
    HINSTANCE hinstance = (HINSTANCE) hInstance ;
    int nCmdShow = SW_SHOW ;
    MSG msg;
    extern int SysInited;

    setint();

    if (!InitApplication(hinstance))
    {
        debug( F100, "ckWindowThread InitApplication failed","",0 );
        WindowThreadId = 0;
        _endthread();
    }

    if (!InitInstance(hinstance, nCmdShow))
    {
        debug( F100, "ckWindowThread InitInstance failed","",0 );
        WindowThreadId = 0;
        _endthread();
    }

    debug( F100, "ckWindowThread Init successful","",0 );
    WindowThreadId = GetCurrentThreadId();

    while (GetMessage(&msg, (HWND) NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

#ifndef NOKVERBS
    if ( SysInited ) {
        debug(F110,"ckWindowThread","dokverb QUIT",0);
        dokverb( VTERM, K_QUIT );
    }
    else
#endif /* NOKVERBS */
        debug(F110,"ckWindowThread","quitting",0);
}

int
WindowThreadInit( void * hInstance )
{
    WindowThread = (HANDLE) _beginthread( ckWindowThread, 65535, hInstance ) ;
    do {
        msleep(50);
    } while ( WindowThreadId == -1 );

    if ( WindowThreadId == 0 )
        return FALSE;

#ifdef COMMENT
    if (!AttachThreadInput( GetCurrentThreadId(), WindowThreadId, TRUE ))
        printf("AttachThreadInput error = %d\n",GetLastError());
#endif /* COMMENT */
    return TRUE ;
}

void
WindowThreadClose( void )
{
    debug(F100,"Window Thread Close","",0);
    PostMessage(hwndGUI, WM_QUIT, 0, 0);
    msleep(50);
}
#else
HANDLE MainThread = NULL ;
#ifdef COMMENT
int
WindowThreadInit( void )
{
   return TRUE ;
}
#else /* COMMENT */
HANDLE WindowThread = INVALID_HANDLE_VALUE;
DWORD  WindowThreadId = -1 ;

void
ckWindowThread( void * hInstance )
{
    HINSTANCE hinstance = (HINSTANCE) hInstance ;
    int nCmdShow = SW_HIDE ;
    MSG msg;

    WindowThreadId = GetCurrentThreadId();

    if (!InitApplication(hinstance))
    {
       debug( F100, "ckWindowThread InitApplication failed","",0 );
       _endthread();
    }

    if (!InitInstance(hinstance, nCmdShow))
    {
       debug( F100, "ckWindowThread InitInstance failed","",0 );
       _endthread();
    }

    debug( F100, "ckWindowThread Init successful","",0 );

    while (GetMessage(&msg, (HWND) NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

int
WindowThreadInit( void * hInstance )
{
    WindowThread = (HANDLE) _beginthread( ckWindowThread, 65535, hInstance ) ;
    while ( WindowThreadId == -1 )
        msleep(50);
    if (!AttachThreadInput( GetCurrentThreadId(), WindowThreadId, TRUE ))
        printf("AttachThreadInput error = %d\n",GetLastError());
    return TRUE ;
}
#endif /* COMMENT */

extern void Main( int, char ** ) ;

void
ckMainThread( void * param )
{
    LPTSTR CmdLine = GetCommandLine() ;
    HANDLE hOut, hIn ;
    char ** argv;
    int    argc = 0 ;
    int i = 0, quote = 0 ;
    char * p = CmdLine ;

    debug(F111,"GetCommandLine()",CmdLine,strlen(CmdLine));
    hInstance = (HINSTANCE) param ;

    while( *p )
    {
       if ( *p == '"' && !quote ) {
           quote++;
       } else if ( quote && *p == '"' ) {
           quote = 0;
           while ( *(p+1) == ' ' )
               p++ ;
           if ( *(p+1) != '\0' )
               argc++;
       } else if ( !quote && *p == ' ' && *(p+1) != '\0' ) {
           argc++ ;
           while ( *(p+1) == ' ' )
               p++ ;
       }
       p++;
    }
    argc++ ;
    argv = malloc( (argc+1) * sizeof(char *) ) ;
    debug(F111,"ckMainThread","argc",argc);
    if ( argv == NULL ) {
		fprintf(stderr,"Out of memory\n");
        exit(1);
    } else {
        argv[argc] = "" ;

        p = CmdLine ;
        for ( i = 0; i < argc ; i++ )
        {
            if ( *p == '"' ) {
                p++;
                argv[i] = p ;
                while (*p && (*p != '"'))
                    p++;
                if (*p)
                    *p++ = '\0';
                while (*p && (*p == ' '))
                    p++;
            } else {
                argv[i] = p ;
                while ( *p && (*p != ' ') )
                    p++ ;
                if (*p)
                    *p++ = '\0';
                while (*p && (*p == ' '))
                    p++;
            }
            debug(F111,"ckMainThread argv",argv[i],i);
        }
    }

#ifndef KUI
    if ( AllocConsole() )
    {
        hOut = CreateFile( "CONOUT$", GENERIC_READ | GENERIC_WRITE,
                                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                                  NULL,
                                  OPEN_EXISTING,
                                  0,
                                  0) ;
        hIn = CreateFile( "CONIN$", GENERIC_READ | GENERIC_WRITE,
                                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                                  NULL,
                                  OPEN_EXISTING,
                                  0,
                                  0) ;

        SetStdHandle( STD_INPUT_HANDLE, hIn ) ;
        SetStdHandle( STD_OUTPUT_HANDLE, hOut ) ;
        SetStdHandle( STD_ERROR_HANDLE, hOut );
    }
#endif /* KUI */
    Main( argc, argv ) ;
}

HANDLE
MainThreadInit( HINSTANCE hInst )
{
    MainThread = (HANDLE) _beginthread( ckMainThread, 65535, hInst ) ;
    return(MainThread);
}

int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
#ifdef KUI
    extern int deblog ;

    kui_init.nCmdShow = nCmdShow;
    ckMainThread( hInstance ) ;
    return 0;
#else /* KUI */
    MSG msg;
    HINSTANCE hModule = GetModuleHandle(NULL);

    if (!InitApplication(hInstance))
    {
       debug( F100, "InitApplication failed","",0 );
       return 0;
    }

    if (!InitInstance(hInstance, nCmdShow))
    {
       debug( F100, "InitInstance failed","",0 );
       return 0;
    }

   MainThreadInit(hInstance) ;

    while (GetMessage(&msg, (HWND) NULL, 0, 0)) {
        printf("GetMessage\n");
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
   return msg.wParam ;
#endif /* KUI */
}
#endif

BOOL InitApplication(hinstance)
HINSTANCE hinstance;
{
    WNDCLASSEX wcx;

    /*
     * Fill in the window class structure with parameters
     * that describe the main window.
     */

    wcx.cbSize = sizeof(wcx);          /* size of structure      */
    wcx.style = CS_HREDRAW |
        CS_VREDRAW;                    /* redraw if size changes */
    wcx.lpfnWndProc = MainWndProc;     /* points to window proc. */
    wcx.cbClsExtra = 0;                /* no extra class memory  */
    wcx.cbWndExtra = 0;                /* no extra window memory */
    wcx.hInstance = hinstance;         /* handle of instance     */
    wcx.hIcon = LoadIcon(NULL,
        MAKEINTRESOURCE(1));           /* kermit 95 icon   */
    wcx.hCursor = LoadCursor(NULL,
        IDC_ARROW);                    /* predefined arrow       */
    wcx.hbrBackground = GetStockObject(
        WHITE_BRUSH);                  /* white background brush */
    wcx.lpszMenuName =  "CkMainMenu";    /* name of menu resource  */
    wcx.lpszClassName = "CkMainWClass";  /* name of window class   */
    wcx.hIconSm = LoadImage(hinstance, /* small class icon       */
        MAKEINTRESOURCE(5),
                             IMAGE_ICON,

        GetSystemMetrics(SM_CXSMICON),
        GetSystemMetrics(SM_CYSMICON),
        LR_DEFAULTCOLOR);

    /* Register the window class. */

    return RegisterClassEx(&wcx);
}

BOOL
InitInstance(hinstance, nCmdShow)
HINSTANCE hinstance;
int nCmdShow;
{
    /* Save the application-instance handle. */

    hInst = hinstance;

    /* Create the main window. */

#ifndef CK_WIN
   hwndGUI = CreateWindow(
        "CkMainWClass",      /* name of window class        */
        "Kermit 95",         /* title-bar string            */
        WS_CHILD, /* top-level window            */
        CW_USEDEFAULT,       /* default horizontal position */
        CW_USEDEFAULT,       /* default vertical position   */
        CW_USEDEFAULT,       /* default width               */
        CW_USEDEFAULT,       /* default height              */
        (HWND) hwndConsole,         /* console window             */
        (HMENU) 1,        /* use class menu              */
        hinstance,           /* handle of app. instance     */
        (LPVOID) NULL);      /* no window-creation data     */
#else
   hwndGUI = CreateWindow(
        "CkMainWClass",      /* name of window class        */
        "Kermit 95",         /* title-bar string            */
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,       /* default horizontal position */
        CW_USEDEFAULT,       /* default vertical position   */
        CW_USEDEFAULT,       /* default width               */
        CW_USEDEFAULT,       /* default height              */
        (HWND) NULL,
        (HMENU) NULL,        /* use class menu              */
        hinstance,           /* handle of app. instance     */
        (LPVOID) NULL);      /* no window-creation data     */
#endif

    if (!hwndGUI)
        return FALSE;

   ShowWindow( hwndGUI, SW_HIDE ) ;
   /* Let the dialer know */
   if ( StartedFromDialer )
      DialerSend( OPT_KERMIT_HWND, (LONG) hwndGUI ) ;

     /*
      * Show the window and send a WM_PAINT message to the window
      * procedure.
      */

    ShowWindow(hwndGUI, nCmdShow);
    UpdateWindow(hwndGUI);
    return TRUE;

}

LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    LRESULT result = 0 ;
    extern unsigned long startflags;

    switch ( msg ) {
    case WM_QUERYENDSESSION:
        debug(F111,"MainWndProc","WM_QUERYENDSESSION",msg);
        result = TRUE;
        if ( lparam & ENDSESSION_LOGOFF ) {
            debug(F100,"ENDSESSION_LOGOFF","",0);
            if ( startflags & 128 ) {
                debug(F100,"startflags & 128","",0);
                result = FALSE;
            }
        }
        break;

    case WM_ENDSESSION:
        debug(F111,"MainWndProc","WM_QUERYENDSESSION",msg);
        if ( wparam && (lparam & ENDSESSION_LOGOFF) ) {
            debug(F100,"ENDSESSION_LOGOFF","",0);
            if ( startflags & 128 ) {
                debug(F100,"startflags & 128","",0);
                result = TRUE;
            }
        }
        break;

#ifdef CK_TAPI
    case OPT_TAPI_INIT:
        debug(F110,"K95 WndProc","OPT_TAPI_INIT",0);
        wintapiinit();
        result = TRUE;
        break;

    case OPT_TAPI_SHUTDOWN:
        debug(F110,"K95 WndProc","OPT_TAPI_SHUTDOWN",0);
        wintapishutdown();
        result = TRUE;
        break;
#endif /* CK_TAPI */
    case OPT_KERMIT_HANGUP:
        debug(F110,"K95 WndProc","OPT_KERMIT_HANGUP",0);
#ifndef NOLOCAL
        if ( IsConnectMode() )
            SetConnectMode( FALSE, CSX_USERDISC ) ;
#endif /* NOLOCAL */
        putkeystr( VCMD, "hangup\rexit\ryes\r" ) ;
        DialerSend( OPT_KERMIT_HANGUP, 0 ) ;
        result = TRUE;
        break;

#ifndef NOLOCAL
    case OPT_DIALER_EXIT:
        debug(F110,"K95 WndProc","OPT_DIALER_EXIT",0);
        StartedFromDialer = 0;
        hwndDialer = 0;
        KermitDialerID = 0;
        result = TRUE;
        break;

    case OPT_DIALER_HWND:
        debug(F110,"K95 WndProc","OPT_DIALER_HWND",0);
        StartedFromDialer = 1;
        hwndDialer = (HWND)lparam;
        KermitDialerID = wparam;
        result = TRUE;
        break;

    case OPT_DIALER_CONNECT: {
        char buf[300], file[256];
        debug(F110,"K95 WndProc","OPT_DIALER_CONNECT",0);
        if ( GlobalGetAtomName((ATOM)lparam,file,sizeof(file)) ) {
            debug(F110,"Take File",file,0);
            ckmakmsg(buf,300,"take ",file,"\r",NULL);
            GlobalDeleteAtom((ATOM)lparam);
            putkeystr( VCMD, buf );
            if ( IsConnectMode() )
                SetConnectMode(0,CSX_ESCAPE);

            if ( DeleteStartupFile ) {
                zdelet(cmdfil);
                ckstrncpy(cmdfil,file,CKMAXPATH+1);
            } else {
                ckstrncpy(cmdfil,file,CKMAXPATH+1);
                DeleteStartupFile = 1;
            }
            DialerSend( OPT_KERMIT_HWND, (LONG) hwnd ) ;
        }
        result = TRUE;
        break;
    }
#endif /* NOLOCAL */

    default:
        debug(F111,"K95 WndProc","unknown message",msg);
        debug(F111,"K95 WndProc","wparam",wparam);
        debug(F111,"K95 WndProc","lparam",lparam);
        result = DefWindowProc( hwnd, msg, wparam, lparam );
   }
   return result ;
}

void
StartDialer(void)
{
    extern int cmdlvl;
#ifdef DCMDBUF
    extern struct cmdptr *cmdstk;
#else
    extern struct cmdptr cmdstk[];
#endif /* DCMDBUF */
    extern char exedir[CKMAXPATH];
    extern int nopush;
    HWND   _hwndDialer = 0;
    int    reuse;

    if ( nopush )
        return;

#ifndef NOSPL
    reuse = (cmdlvl == 0) && (ttchk() < 0);
#else
    reuse = (tlevel == -1) && (ttchk() < 0);
#endif /* NOSPL */

    if ( StartedFromDialer ) {
        if ( reuse ) {
            DialerSend(OPT_KERMIT_HWND2, (unsigned long)hwndGUI);
            DialerSend(OPT_KERMIT_PID,  GetCurrentProcessId());
        }
        ShowWindowAsync(hwndDialer,SW_SHOWNORMAL);
        SetForegroundWindow(hwndDialer);
    } else if (_hwndDialer = FindWindow(NULL, "Kermit-95 Dialer")) {
        StartedFromDialer = 1;
        hwndDialer = _hwndDialer;
        KermitDialerID = 0;
        DialerSend(OPT_KERMIT_HWND, (unsigned long)hwndGUI);
        if ( reuse ) {
            DialerSend(OPT_KERMIT_HWND2, (unsigned long)hwndGUI);
            DialerSend(OPT_KERMIT_PID,  GetCurrentProcessId());
        }
        ShowWindowAsync(hwndDialer,SW_SHOWNORMAL);
        SetForegroundWindow(hwndDialer);
        StartedFromDialer = 0;
    } else {
        static char buf[512] = "start ", *p, *q;

        for ( p = exedir, q = buf + 6; *p; p++, q++ ) {
            if ( *p == '/' ) {
                *q = '\\';
            } else
                *q = *p;
            if ( *q == '\\' ) {
                q++;
                *q = '\\';
            }
        }
        for ( p = "k95dial.exe"; *p ; p++, q++ )
            *q = *p;
        if ( reuse ) {
            for ( p = " -k "; *p ; p++, q++ )
                *q = *p;
            for ( p = ckultoa((LONG) hwndGUI); *p ; p++, q++ )
                *q = *p;
            *q++ = ' ';
            for ( p = ckultoa((LONG) GetCurrentProcessId()); *p ; p++, q++ )
                *q = *p;
        }
        *q = '\0';
        _zshcmd(buf,0);
    }
}

HWND
GetConsoleHwnd( void )
{
#ifdef KUI
    return getHwndKUI();
#else /* KUI */
    HWND hwndFound = NULL ;
    char NewWindowTitle[1024] ;
    char OldWindowTitle[1024] ;
    int i = 0 ;

    GetConsoleTitle( OldWindowTitle, 1024 ) ;

    for ( i=0 ; i < 20 ; i++ )
    {
      wsprintf( NewWindowTitle, "%d/%d",
                GetTickCount(),
                GetCurrentProcessId());
      SetConsoleTitle(NewWindowTitle);
      Sleep(50);
      hwndFound = FindWindow( NULL, NewWindowTitle ) ;
      if ( hwndFound )
         break;
    }
    debug(F111,"GetConsoleHwnd","Hwnd",hwndFound) ;
    SetConsoleTitle( OldWindowTitle ) ;
    return (hwndFound);
#endif /* KUI */
}

#ifdef KUI
static LPWORD 
lpwAlign( LPWORD lpIn )
{
    ULONG ul;

    ul = (ULONG) lpIn;
    ul += 3;
    ul >>=2;
    ul <<=2;
    return (LPWORD) ul;;
}

static char * sid_buf = NULL;
static int    sid_len = 0;
static int    sid_timeout = 0;
static UINT   sid_timer = 0;

#define ID_TEXT       150
#define ID_SID_TEXT   200
#define ID_SID_TIMER  (WM_USER+300)

static BOOL CALLBACK 
SingleInputDialogProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch ( message ) {
    case WM_INITDIALOG:
        if ( sid_timeout ) {
            sid_timer = SetTimer( hwndDlg, ID_SID_TIMER, sid_timeout * 1000, 
                                  NULL);
        }
        if ( GetDlgCtrlID((HWND) wParam) != ID_SID_TEXT )
        {
            SetFocus(GetDlgItem( hwndDlg, ID_SID_TEXT));
            return FALSE;
        }
        return TRUE;

    case WM_COMMAND:
        switch ( LOWORD(wParam) ) {
        case IDOK:
            if ( !GetDlgItemText(hwndDlg, ID_SID_TEXT, sid_buf, sid_len) )
                *sid_buf = '\0';
            /* fallthrough */
        case IDCANCEL:
            if ( sid_timeout )
                KillTimer(hwndDlg,sid_timer);
            EndDialog(hwndDlg, LOWORD(wParam));
            return TRUE;
        }
        break;
    case WM_TIMER:
        if ( sid_timeout )
            KillTimer(hwndDlg,sid_timer);
        EndDialog(hwndDlg, 0);
        return TRUE;
    }
    return FALSE;
}

/* 
 * dialog widths are measured in 1/4 character widths
 * dialog height are measured in 1/8 character heights
 */

static LRESULT
SingleInputDialog( HINSTANCE hinst, HWND hwndOwner, 
                   char * ptext[], int numlines, int width, char * prompt, 
                   int echo, char * dflt)
{
    HGLOBAL hgbl;
    LPDLGTEMPLATE lpdt;
    LPDLGITEMTEMPLATE lpdit;
    LPWORD lpw;
    LPWSTR lpwsz;
    LRESULT ret;
    int nchar, i;
    char * p;

    hgbl = GlobalAlloc(GMEM_ZEROINIT, 4096);
    if (!hgbl)
        return -1;
 
    lpdt = (LPDLGTEMPLATE)GlobalLock(hgbl);
 
    // Define a dialog box.
 
    lpdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU
                   | DS_MODALFRAME | WS_CAPTION | DS_CENTER 
                   | DS_SETFOREGROUND | DS_3DLOOK
                   | DS_SHELLFONT | DS_NOFAILCREATE;
    lpdt->cdit = numlines + 4;  // number of controls
    lpdt->x  = 10;  
    lpdt->y  = 10;
    lpdt->cx = 20 + width * 4; 
    lpdt->cy = 20 + (numlines + 4) * 14;

    lpw = (LPWORD) (lpdt + 1);
    *lpw++ = 0;   // no menu
    *lpw++ = 0;   // predefined dialog box class (by default)

    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, prompt, 
                                    -1, lpwsz, 128);
    lpw   += nchar;
    *lpw++ = 8;                        // font size (points)
    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "MS Shell Dlg", 
                                    -1, lpwsz, 128);
    lpw   += nchar;


    //-----------------------
    // Define an OK button.
    //-----------------------
    lpw = lpwAlign (lpw); // align DLGITEMTEMPLATE on DWORD boundary
    lpdit = (LPDLGITEMTEMPLATE) lpw;
    lpdit->style = WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON | WS_TABSTOP | WS_BORDER;
    lpdit->dwExtendedStyle = 0;
    lpdit->x  = (lpdt->cx - 14)/4 - 20; 
    lpdit->y  = 10 + (numlines + 3) * 14;
    lpdit->cx = 40; 
    lpdit->cy = 14;
    lpdit->id = IDOK;  // OK button identifier

    lpw = (LPWORD) (lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0080;    // button class

    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "OK", -1, lpwsz, 50);
    lpw   += nchar;
    *lpw++ = 0;           // no creation data

    //-----------------------
    // Define an Cancel button.
    //-----------------------
    lpw = lpwAlign (lpw); // align DLGITEMTEMPLATE on DWORD boundary
    lpdit = (LPDLGITEMTEMPLATE) lpw;
    lpdit->style = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP | WS_BORDER;
    lpdit->dwExtendedStyle = 0;
    lpdit->x  = (lpdt->cx - 14)*3/4 - 20; 
    lpdit->y  = 10 + (numlines + 3) * 14;
    lpdit->cx = 40; 
    lpdit->cy = 14;
    lpdit->id = IDCANCEL;  // CANCEL button identifier

    lpw = (LPWORD) (lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0080;    // button class

    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "Cancel", -1, lpwsz, 50);
    lpw   += nchar;
    *lpw++ = 0;           // no creation data

    /* Add controls for preface data */
    for ( i=0; i<numlines; i++) {
        /*-----------------------
         * Define a static text control.
         *-----------------------*/
        lpw = lpwAlign (lpw); /* align DLGITEMTEMPLATE on DWORD boundary */
        lpdit = (LPDLGITEMTEMPLATE) lpw;
        lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT;
        lpdit->dwExtendedStyle = 0;
        lpdit->x  = 10; 
        lpdit->y  = 10 + i * 14;
        lpdit->cx = strlen(ptext[i]) * 4 + 10; 
        lpdit->cy = 14;
        lpdit->id = ID_TEXT + i;  // text identifier

        lpw = (LPWORD) (lpdit + 1);
        *lpw++ = 0xFFFF;
        *lpw++ = 0x0082;                         // static class

        lpwsz = (LPWSTR) lpw;
        nchar = MultiByteToWideChar (CP_ACP, 0, ptext[i], 
                                         -1, lpwsz, 2*width);
        lpw   += nchar;
        *lpw++ = 0;           // no creation data
    }
    
    /* Prompt */
    /*-----------------------
    * Define a static text control.
    *-----------------------*/
    lpw = lpwAlign (lpw); /* align DLGITEMTEMPLATE on DWORD boundary */
    lpdit = (LPDLGITEMTEMPLATE) lpw;
    lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT;
    lpdit->dwExtendedStyle = 0;
    lpdit->x  = 10; 
    lpdit->y  = 10 + (numlines + 1) * 14;
    lpdit->cx = strlen(prompt) * 4; 
    lpdit->cy = 14;
    lpdit->id = ID_TEXT + (numlines);  // text identifier

    lpw = (LPWORD) (lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0082;                         // static class

    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, prompt, -1, lpwsz, 128);
    lpw   += nchar;
    *lpw++ = 0;           // no creation data


    /*-----------------------
    * Define an edit control.
    *-----------------------*/
    lpw = lpwAlign (lpw); /* align DLGITEMTEMPLATE on DWORD boundary */
    lpdit = (LPDLGITEMTEMPLATE) lpw;
    lpdit->style = WS_CHILD | WS_VISIBLE | ES_LEFT | WS_TABSTOP | WS_BORDER
        | (echo == 1 ? 0L : ES_PASSWORD);
    lpdit->dwExtendedStyle = 0;
    lpdit->x  = 10 + (strlen(prompt) + 1) * 4; 
    lpdit->y  = 10 + (numlines + 1) * 14;
    lpdit->cx = (width - (strlen(prompt) + 1)) * 4; 
    lpdit->cy = 14;
    lpdit->id = ID_SID_TEXT;                 // identifier

    lpw = (LPWORD) (lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0081;                         // edit class

    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, dflt ? dflt : "", -1, lpwsz, 128);
    lpw   += nchar;
    *lpw++ = 0;           // no creation data

    GlobalUnlock(hgbl); 
    ret = DialogBoxIndirect(hinst, (LPDLGTEMPLATE) hgbl, 
        hwndOwner, (DLGPROC) SingleInputDialogProc); 
    GlobalFree(hgbl); 

    switch ( ret ) {
    case 0:     /* Timeout */
        return -1;
    case IDOK:
        return 1;
    case IDCANCEL:
        return 0;
    default: {
        char buf[256];
        ckmakmsg(buf,256,"DialogBoxIndirect() failed ",ckitoa(GetLastError()), NULL, NULL);
        MessageBox(hwndOwner,
                    buf,
                    "GetLastError()",
                    MB_OK | MB_ICONINFORMATION | MB_TASKMODAL);
        return -1;
    }
    }
}

int
gui_txt_dialog(char * preface, char * prompt, int echo, 
               char * buf, int buflen, char * dflt, int timeout)
{
    int maxwidth = 0;
    int numlines = 0;
    char * plines[16], *p = preface ? preface : "";
    int i;

    if ( !buf || buflen <= 0 || buflen < (dflt ? strlen(dflt) : 0) )
        return(-1);

    sid_buf = buf;
    sid_len = buflen;
    sid_timeout = timeout;

    for ( i=0; i<16; i++ ) 
        plines[i] = NULL;

    while (*p && numlines < 16) {
        plines[numlines++] = p;
        for ( ;*p && *p != '\r' && *p != '\n'; p++ );
        if ( *p == '\r' && *(p+1) == '\n' ) {
            *p++ = '\0';
            p++;
        } else if ( *p == '\n' ) {
            *p++ = '\0';
        } 
        if ( strlen(plines[numlines-1]) > maxwidth )
            maxwidth = strlen(plines[numlines-1]);
    }

    i = strlen(prompt) + 1 + (buflen > 40 ? 40 : buflen);
    if ( maxwidth < i )
        maxwidth = i;

    return(SingleInputDialog(hInstance, hwndConsole, 
                              plines, numlines, maxwidth, prompt, echo, dflt));
}


static int             mid_cnt = 0;
static struct txtbox * mid_tb = NULL;

#define ID_MID_TEXT 300

static BOOL CALLBACK 
MultiInputDialogProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    int i;

    switch ( message ) {
    case WM_INITDIALOG:
        if ( GetDlgCtrlID((HWND) wParam) != ID_MID_TEXT )
        {
            SetFocus(GetDlgItem( hwndDlg, ID_MID_TEXT));
            return FALSE;
        }
        return TRUE;

    case WM_COMMAND:
        switch ( LOWORD(wParam) ) {
        case IDOK:
            for ( i=0; i < mid_cnt ; i++ ) {
                if ( !GetDlgItemText(hwndDlg, ID_MID_TEXT+i, mid_tb[i].t_buf, mid_tb[i].t_len) )
                    *mid_tb[i].t_buf = '\0';
            }
            /* fallthrough */
        case IDCANCEL:
            ShowWindowAsync(hwndConsole,SW_SHOWNORMAL);
            SetForegroundWindow(hwndConsole);
            EndDialog(hwndDlg, LOWORD(wParam));
            return TRUE;
        }
    }
    return FALSE;
}

/* 
 * dialog widths are measured in 1/4 character widths
 * dialog height are measured in 1/8 character heights
 */

static LRESULT
MultiInputDialog( HINSTANCE hinst, HWND hwndOwner, 
                  char * ptext[], int numlines, int width, 
                  int tb_cnt, struct txtbox * tb)
{
    HGLOBAL hgbl;
    LPDLGTEMPLATE lpdt;
    LPDLGITEMTEMPLATE lpdit;
    LPWORD lpw;
    LPWSTR lpwsz;
    LRESULT ret;
    int nchar, i, pwid;
    char * p;

    hgbl = GlobalAlloc(GMEM_ZEROINIT, 4096);
    if (!hgbl)
        return -1;
 
    mid_cnt = tb_cnt;
    mid_tb = tb;

    lpdt = (LPDLGTEMPLATE)GlobalLock(hgbl);
 
    // Define a dialog box.
 
    lpdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU
                   | DS_MODALFRAME | WS_CAPTION | DS_CENTER 
                   | DS_SETFOREGROUND | DS_3DLOOK
                   | DS_SHELLFONT | DS_NOFAILCREATE;
    lpdt->cdit = numlines + (2 * tb_cnt) + 2;  // number of controls
    lpdt->x  = 10;  
    lpdt->y  = 10;
    lpdt->cx = 20 + width * 4; 
    lpdt->cy = 20 + (numlines + tb_cnt + 4) * 14;

    lpw = (LPWORD) (lpdt + 1);
    *lpw++ = 0;   // no menu
    *lpw++ = 0;   // predefined dialog box class (by default)

    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "", -1, lpwsz, 128);
    lpw   += nchar;
    *lpw++ = 8;                        // font size (points)
    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "MS Shell Dlg", 
                                    -1, lpwsz, 128);
    lpw   += nchar;

    //-----------------------
    // Define an OK button.
    //-----------------------
    lpw = lpwAlign (lpw); // align DLGITEMTEMPLATE on DWORD boundary
    lpdit = (LPDLGITEMTEMPLATE) lpw;
    lpdit->style = WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON | WS_TABSTOP | WS_BORDER;
    lpdit->dwExtendedStyle = 0;
    lpdit->x  = (lpdt->cx - 14)/4 - 20; 
    lpdit->y  = 10 + (numlines + tb_cnt + 2) * 14;
    lpdit->cx = 40; 
    lpdit->cy = 14;
    lpdit->id = IDOK;  // OK button identifier

    lpw = (LPWORD) (lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0080;    // button class

    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "OK", -1, lpwsz, 50);
    lpw   += nchar;
    *lpw++ = 0;           // no creation data

    //-----------------------
    // Define an Cancel button.
    //-----------------------
    lpw = lpwAlign (lpw); // align DLGITEMTEMPLATE on DWORD boundary
    lpdit = (LPDLGITEMTEMPLATE) lpw;
    lpdit->style = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP | WS_BORDER;
    lpdit->dwExtendedStyle = 0;
    lpdit->x  = (lpdt->cx - 14)*3/4 - 20; 
    lpdit->y  = 10 + (numlines + tb_cnt + 2) * 14;
    lpdit->cx = 40; 
    lpdit->cy = 14;
    lpdit->id = IDCANCEL;  // CANCEL button identifier

    lpw = (LPWORD) (lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0080;    // button class

    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "Cancel", -1, lpwsz, 50);
    lpw   += nchar;
    *lpw++ = 0;           // no creation data

    /* Add controls for preface data */
    for ( i=0; i<numlines; i++) {
        /*-----------------------
         * Define a static text control.
         *-----------------------*/
        lpw = lpwAlign (lpw); /* align DLGITEMTEMPLATE on DWORD boundary */
        lpdit = (LPDLGITEMTEMPLATE) lpw;
        lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT;
        lpdit->dwExtendedStyle = 0;
        lpdit->x  = 10; 
        lpdit->y  = 10 + i * 14;
        lpdit->cx = strlen(ptext[i]) * 4 + 10; 
        lpdit->cy = 14;
        lpdit->id = ID_TEXT + i;  // text identifier

        lpw = (LPWORD) (lpdit + 1);
        *lpw++ = 0xFFFF;
        *lpw++ = 0x0082;                         // static class

        lpwsz = (LPWSTR) lpw;
        nchar = MultiByteToWideChar (CP_ACP, 0, ptext[i], 
                                         -1, lpwsz, 2*width);
        lpw   += nchar;
        *lpw++ = 0;           // no creation data
    }
    
    for ( i=0, pwid = 0; i<tb_cnt; i++) {
        if ( pwid < strlen(tb[i].t_lbl) )
            pwid = strlen(tb[i].t_lbl);
    }

    for ( i=0; i<tb_cnt; i++) {
        /* Prompt */
        /*-----------------------
         * Define a static text control.
         *-----------------------*/
        lpw = lpwAlign (lpw); /* align DLGITEMTEMPLATE on DWORD boundary */
        lpdit = (LPDLGITEMTEMPLATE) lpw;
        lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT;
        lpdit->dwExtendedStyle = 0;
        lpdit->x  = 10; 
        lpdit->y  = 10 + (numlines + i + 1) * 14;
        lpdit->cx = pwid * 4; 
        lpdit->cy = 14;
        lpdit->id = ID_TEXT + numlines + i;  // text identifier

        lpw = (LPWORD) (lpdit + 1);
        *lpw++ = 0xFFFF;
        *lpw++ = 0x0082;                         // static class

        lpwsz = (LPWSTR) lpw;
        nchar = MultiByteToWideChar (CP_ACP, 0, tb[i].t_lbl ? tb[i].t_lbl : "", 
                                     -1, lpwsz, 128);
        lpw   += nchar;
        *lpw++ = 0;           // no creation data

        /*-----------------------
         * Define an edit control.
         *-----------------------*/
        lpw = lpwAlign (lpw); /* align DLGITEMTEMPLATE on DWORD boundary */
        lpdit = (LPDLGITEMTEMPLATE) lpw;
        lpdit->style = WS_CHILD | WS_VISIBLE | ES_LEFT | WS_TABSTOP | WS_BORDER | (tb[i].t_echo == 1 ? 0L : ES_PASSWORD);
        lpdit->dwExtendedStyle = 0;
        lpdit->x  = 10 + (pwid + 1) * 4; 
        lpdit->y  = 10 + (numlines + i + 1) * 14;
        lpdit->cx = (width - (pwid + 1)) * 4; 
        lpdit->cy = 14;
        lpdit->id = ID_MID_TEXT + i;             // identifier

        lpw = (LPWORD) (lpdit + 1);
        *lpw++ = 0xFFFF;
        *lpw++ = 0x0081;                         // edit class

        lpwsz = (LPWSTR) lpw;
        nchar = MultiByteToWideChar (CP_ACP, 0, tb[i].t_dflt ? tb[i].t_dflt : "", 
                                     -1, lpwsz, 128);
        lpw   += nchar;
        *lpw++ = 0;           // no creation data
    }

    GlobalUnlock(hgbl); 
    ret = DialogBoxIndirect(hinst, (LPDLGTEMPLATE) hgbl, 
        hwndOwner, (DLGPROC) MultiInputDialogProc); 
    GlobalFree(hgbl); 

    switch ( ret ) {
    case 0:     /* Timeout */
        return -1;
    case IDOK:
        return 1;
    case IDCANCEL:
        return 0;
    default: {
        char buf[256];
        ckmakmsg(buf,256,"DialogBoxIndirect() failed ",ckitoa(GetLastError()), NULL, NULL);
        MessageBox(hwndOwner,
                    buf,
                    "GetLastError()",
                    MB_OK | MB_ICONINFORMATION | MB_TASKMODAL);
        return -1;
    }
    }
}

int
gui_mtxt_dialog(char * preface, int n, struct txtbox tb[])
{
    int maxwidth = 0;
    int numlines = 0;
    int len;
    char * plines[16], *p = preface ? preface : "";
    int i;

    for ( i=0; i<16; i++ ) 
        plines[i] = NULL;

    while (*p && numlines < 16) {
        plines[numlines++] = p;
        for ( ;*p && *p != '\r' && *p != '\n'; p++ );
        if ( *p == '\r' && *(p+1) == '\n' ) {
            *p++ = '\0';
            p++;
        } else if ( *p == '\n' ) {
            *p++ = '\0';
        } 
        if ( strlen(plines[numlines-1]) > maxwidth )
            maxwidth = strlen(plines[numlines-1]);
    }

    for ( i=0;i<n;i++ ) {
        len = strlen(tb[i].t_lbl) + 1 + (tb[i].t_len > 40 ? 40 : tb[i].t_len);
        if ( maxwidth < len )
            maxwidth = len;
    }

    return(MultiInputDialog(hInstance, hwndConsole, 
                              plines, numlines, maxwidth, n, tb));
}


static BOOL CALLBACK 
VideoPopupDialogProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch ( message ) {
    case WM_INITDIALOG:
        if ( sid_timeout ) {
            sid_timer = SetTimer( hwndDlg, ID_SID_TIMER, sid_timeout * 1000, 
                                  NULL);
        }
        return TRUE;

    case WM_COMMAND:
        switch ( LOWORD(wParam) ) {
        case IDOK:
        case IDCANCEL:
            if ( sid_timeout )
                KillTimer(hwndDlg,sid_timer);
            EndDialog(hwndDlg, LOWORD(wParam));
            return TRUE;
        }
        break;
    case WM_TIMER:
        if ( sid_timeout )
            KillTimer(hwndDlg,sid_timer);
        EndDialog(hwndDlg, 0);
        return TRUE;
    }
    return FALSE;
}

/* 
 * dialog widths are measured in 1/4 character widths
 * dialog height are measured in 1/8 character heights
 */

static LRESULT
VideoPopupDialog( HINSTANCE hinst, HWND hwndOwner, videopopup * vp)
{
    HGLOBAL hgbl;
    LPDLGTEMPLATE lpdt;
    LPDLGITEMTEMPLATE lpdit;
    LPWORD lpw;
    LPWSTR lpwsz;
    LRESULT ret;
    int nchar, i, j;
    char * p;

    hgbl = GlobalAlloc(GMEM_ZEROINIT, 8192);
    if (!hgbl)
        return -1;
 
    lpdt = (LPDLGTEMPLATE)GlobalLock(hgbl);
 
    // Define a dialog box.
 
    lpdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU
                   | DS_MODALFRAME | WS_CAPTION | DS_CENTER 
                   | DS_SETFOREGROUND | DS_3DLOOK
                   | DS_SETFONT | DS_NOFAILCREATE;
    lpdt->cdit = vp->height + 2;  // number of controls
    lpdt->x  = 10;  
    lpdt->y  = 10;
    lpdt->cx = 20 + vp->width * 4; 
    lpdt->cy = 20 + (vp->height + 1) * 8 + 14;

    lpw = (LPWORD) (lpdt + 1);
    *lpw++ = 0;   // no menu
    *lpw++ = 0;   // predefined dialog box class (by default)

    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "", -1, lpwsz, 128);
    lpw   += nchar;
    *lpw++ = 8;                        // font size (points)
    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "Lucida Console", -1, lpwsz, 128);
    lpw   += nchar;


    //-----------------------
    // Define an OK button.
    //-----------------------
    lpw = lpwAlign (lpw); // align DLGITEMTEMPLATE on DWORD boundary
    lpdit = (LPDLGITEMTEMPLATE) lpw;
    lpdit->style = WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON | WS_TABSTOP | WS_BORDER;
    lpdit->dwExtendedStyle = 0;
    lpdit->x  = (lpdt->cx - 14)/4 - 20; 
    lpdit->y  = 10 + (vp->height + 1) * 8;
    lpdit->cx = 40; 
    lpdit->cy = 14;
    lpdit->id = IDOK;  // OK button identifier

    lpw = (LPWORD) (lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0080;    // button class

    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "OK", -1, lpwsz, 50);
    lpw   += nchar;
    *lpw++ = 0;           // no creation data

    //-----------------------
    // Define an Cancel button.
    //-----------------------
    lpw = lpwAlign (lpw); // align DLGITEMTEMPLATE on DWORD boundary
    lpdit = (LPDLGITEMTEMPLATE) lpw;
    lpdit->style = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP | WS_BORDER;
    lpdit->dwExtendedStyle = 0;
    lpdit->x  = (lpdt->cx - 14)*3/4 - 20; 
    lpdit->y  = 10 + (vp->height + 1) * 8;
    lpdit->cx = 40; 
    lpdit->cy = 14;
    lpdit->id = IDCANCEL;  // CANCEL button identifier

    lpw = (LPWORD) (lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0080;    // button class

    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "Cancel", -1, lpwsz, 50);
    lpw   += nchar;
    *lpw++ = 0;           // no creation data

    /* Add controls for preface data */
    for ( i=0; i<vp->height; i++) {
        /*-----------------------
         * Define a static text control.
         *-----------------------*/
        lpw = lpwAlign (lpw); /* align DLGITEMTEMPLATE on DWORD boundary */
        lpdit = (LPDLGITEMTEMPLATE) lpw;
        lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT;
        lpdit->dwExtendedStyle = 0;
        lpdit->x  = 10; 
        lpdit->y  = 10 + i * 8;
        lpdit->cx = vp->width * 4 + 10; 
        lpdit->cy = 8;
        lpdit->id = ID_TEXT + i;  // text identifier

        lpw = (LPWORD) (lpdit + 1);
        *lpw++ = 0xFFFF;
        *lpw++ = 0x0082;                         // static class

        lpwsz = (LPWSTR) lpw;
        for ( j=0;j<vp->width && vp->c[i][j];j++ )
            *lpwsz++ = vp->c[i][j];
        *lpwsz++ = 0;
        lpw   = lpwsz;

        *lpw++ = 0;           // no creation data
    }
    
    GlobalUnlock(hgbl); 
    ret = DialogBoxIndirect(hinst, (LPDLGTEMPLATE) hgbl, 
        hwndOwner, (DLGPROC) VideoPopupDialogProc); 
    GlobalFree(hgbl); 

    switch ( ret ) {
    case 0:     /* Timeout */
        return -1;
    case IDOK:
        return F_KVERB | K_HELP;
    case IDCANCEL:
        return 0;
    default: {
        char buf[256];
        ckmakmsg(buf,256,"DialogBoxIndirect() failed ",ckitoa(GetLastError()), NULL, NULL);
        MessageBox(hwndOwner,
                    buf,
                    "GetLastError()",
                    MB_OK | MB_ICONINFORMATION | MB_TASKMODAL);
        return -1;
    }
    }
}

int
gui_videopopup_dialog(videopopup * vp, int timeout)
{
    int i;

    sid_timeout = timeout;

    return(VideoPopupDialog(hInstance, hwndConsole, vp));
}

int
gui_position(int x, int y)
{
    KuiSetTerminalPosition(x,y);
    msleep(50);
    return(1);
}

int
gui_resize_pixels(int x, int y)
{
    KuiSetTerminalSize(x,y);
    msleep(50);
    return(1);
}

int
gui_resize_mode(int x)
{
    KuiSetTerminalResizeMode(x);
    msleep(50);
    return(1);
}


int
gui_win_run_mode(int x)
{
    KuiSetTerminalRunMode(x);
    return(1);
}

int
gui_saveas_dialog(char * preface, char * prompt, int fc,
                  char * def, char * result, int rlength)
{
    return KuiDownloadDialog(prompt,def,result,rlength);
}

int 
get_gui_window_pos_x(void)
{
    RECT r;

    if ( GetWindowRect(getHwndKUI(), &r) ) {
        return r.left;
    } else 
        return 0;
}

int 
get_gui_window_pos_y(void)
{
    RECT r;

    if ( GetWindowRect(getHwndKUI(), &r) ) {
        return r.top;
    } else 
        return 0;
}

int 
get_gui_resize_mode(void)
{
    return KuiGetTerminalResizeMode();
}


static HWND hwndRichEdit = INVALID_HANDLE_VALUE;
static HWND hwndTextDlg  = INVALID_HANDLE_VALUE;
static HANDLE hRichEditLib = INVALID_HANDLE_VALUE;

static BOOL CALLBACK 
TextDialogProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch ( message ) {
    case WM_INITDIALOG:
        if ( sid_timeout ) {
            sid_timer = SetTimer( hwndDlg, ID_SID_TIMER, sid_timeout * 1000, 
                                  NULL);
        }

        hwndTextDlg  = hwndDlg;
        hwndRichEdit = GetDlgItem( hwndDlg, ID_TEXT );
        PostRichEditInitSem();
        return TRUE;

    case WM_COMMAND:
        switch ( LOWORD(wParam) ) {
        case IDOK:
            if ( sid_timeout )
                KillTimer(hwndDlg,sid_timer);
            hwndTextDlg = hwndRichEdit = INVALID_HANDLE_VALUE;
            EndDialog(hwndDlg, LOWORD(wParam));
            PostRichEditCloseSem();
            return TRUE;
        }
        break;

    case WM_CLOSE:
    case WM_TIMER:
        if ( sid_timeout )
            KillTimer(hwndDlg,sid_timer);
        hwndTextDlg = hwndRichEdit = INVALID_HANDLE_VALUE;
        EndDialog(hwndDlg, 0);
        PostRichEditCloseSem();
        return TRUE;
    }
    return FALSE;
}

/* 
 * dialog widths are measured in 1/4 character widths
 * dialog height are measured in 1/8 character heights
 */

static LRESULT
TextPopupDialog( HINSTANCE hinst, HWND hwndOwner, char * title, int h, int w)
{
    HGLOBAL hgbl;
    LPDLGTEMPLATE lpdt;
    LPDLGITEMTEMPLATE lpdit;
    LPWORD lpw;
    LPWSTR lpwsz;
    LRESULT ret;
    int nchar, i, j, font = 12;
    char * p;

    if ( !w ) w = 80;
    if ( !h ) h = 25;

    hgbl = GlobalAlloc(GMEM_ZEROINIT, 8192);
    if (!hgbl)
        return -1;
 
    lpdt = (LPDLGTEMPLATE)GlobalLock(hgbl);
 
    // Define a dialog box.
 
    lpdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU
                   | DS_MODALFRAME | WS_CAPTION | DS_CENTER 
                   | DS_SETFOREGROUND | DS_3DLOOK
                   | DS_SETFONT | DS_NOFAILCREATE;
    lpdt->cdit = 2;  // number of controls
    lpdt->x  = 0;  
    lpdt->y  = 0;
    lpdt->cx = 14 + w * 4; 
    lpdt->cy = 32 + h * font;

    lpw = (LPWORD) (lpdt + 1);
    *lpw++ = 0;   // no menu
    *lpw++ = 0;   // predefined dialog box class (by default)

    lpwsz = (LPWSTR) lpw;   /* caption */
    nchar = MultiByteToWideChar (CP_ACP, 0, title, -1, lpwsz, 128);
    lpw   += nchar;
    *lpw++ = font;                        // font size (points)
    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "Lucida Console", -1, lpwsz, 128);
    lpw   += nchar;


    /*-----------------------
     * Define a Rich Edit 2.0 control.
     *-----------------------*/
    lpw = lpwAlign (lpw); /* align DLGITEMTEMPLATE on DWORD boundary */
    lpdit = (LPDLGITEMTEMPLATE) lpw;
    lpdit->style = ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | 
                   ES_NUMBER | WS_BORDER | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP |
                   WS_VISIBLE;
    lpdit->dwExtendedStyle = 0; // WS_EX_CLIENTEDGE;
    lpdit->x  = 7; 
    lpdit->y  = 7;
    lpdit->cx = w * 4; 
    lpdit->cy = h * font;
    lpdit->id = ID_TEXT;

    lpw = (LPWORD) (lpdit + 1);
    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "RichEdit20W", -1, lpwsz, 50);
    lpw   += nchar;

    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "", -1, lpwsz, 50);
    lpw   += nchar;

    *lpw++ = 0;           // no creation data
    
    //-----------------------
    // Define an OK button.
    //-----------------------
    lpw = lpwAlign (lpw); // align DLGITEMTEMPLATE on DWORD boundary
    lpdit = (LPDLGITEMTEMPLATE) lpw;
    lpdit->style = WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON | WS_TABSTOP | WS_BORDER;
    lpdit->dwExtendedStyle = 0;
    lpdit->x  = 7 + w * 2 - 25; 
    lpdit->y  = 14 + h * font;
    lpdit->cx = 50; 
    lpdit->cy = 14;
    lpdit->id = IDOK;  // OK button identifier

    lpw = (LPWORD) (lpdit + 1);
    *lpw++ = 0xFFFF;
    *lpw++ = 0x0080;    // button class

    lpwsz = (LPWSTR) lpw;
    nchar = MultiByteToWideChar (CP_ACP, 0, "OK", -1, lpwsz, 50);
    lpw   += nchar;
    *lpw++ = 0;           // no creation data

    GlobalUnlock(hgbl); 
    ret = DialogBoxIndirect(hinst, (LPDLGTEMPLATE) hgbl, 
                            hwndOwner, (DLGPROC) TextDialogProc); 
    GlobalFree(hgbl); 

    switch ( ret ) {
    case 0:     /* Timeout */
        return -1;
    case IDOK:
        return 1;
    default: {
        char buf[256];
        ckmakmsg(buf,256,"DialogBoxIndirect() failed ",ckitoa(GetLastError()), NULL, NULL);
        MessageBox(hwndOwner,
                    buf,
                    "GetLastError()",
                    MB_OK | MB_ICONINFORMATION | MB_TASKMODAL);
        return -1;
    }
    }
}


static struct {
    char title[256];
    int  h;
    int  w;
} gui_text_params;

void 
gui_text_popup_thread(void * dummy)
{
    TextPopupDialog( hInstance, hwndConsole, 
                     gui_text_params.title, gui_text_params.h, gui_text_params.w );
}

static USHORT * EditStreamBuffer = NULL;
static int EditStreamMaxLen = 0;
static int EditStreamLen = 0;
static int stream_complete = 0;

static DWORD CALLBACK
EditStreamCallback(DWORD_PTR dwCookie,
                          LPBYTE pbBuff,
                          LONG cb,
                          LONG *pcb
                          )
{
  start:
    RequestRichEditMutex(INFINITE);
    if ( stream_complete && EditStreamLen == 0 ) {
        *pcb = 0;
    } else {
        if ( EditStreamLen == 0 ) {
            ReleaseRichEditMutex();
            Sleep(100);
            goto start;
        }

        if ( cb % 2 == 1 )
            cb--;

        if ( cb >= EditStreamLen * 2 ) {
            memcpy(pbBuff, EditStreamBuffer, EditStreamLen * 2);
            *pcb = EditStreamLen * 2;
            EditStreamLen = 0;
        } else {
            int i, j;
            memcpy(pbBuff, EditStreamBuffer, cb);
            *pcb = cb;
            for (i = 0, j = cb / 2; j <= EditStreamLen; i++, j++) {
                EditStreamBuffer[i] = EditStreamBuffer[j];
            }
            EditStreamLen = i;
        }
    }
    ReleaseRichEditMutex();
    return 0;
}

static EDITSTREAM EditStream = { 0, 0, EditStreamCallback };

int
gui_text_popup_create(char * title, int h, int w)
{
    HANDLE thread;

    if ( hRichEditLib == INVALID_HANDLE_VALUE ) {
        hRichEditLib = LoadLibrary("riched20.dll");
        if ( hRichEditLib == INVALID_HANDLE_VALUE ) {
            MessageBox(hwndConsole,
                        "The library RICHED20.DLL required for GUI Text Edit controls is not present on this system",
                        "Error",
                        MB_OK | MB_ICONINFORMATION | MB_TASKMODAL);
            return -1;
        }
    }

    CreateRichEditInitSem( FALSE );
    CreateRichEditCloseSem( FALSE );
    CreateRichEditMutex(FALSE);
    stream_complete = 0;

    gui_text_params.h = h;
    gui_text_params.w = w;
    ckstrncpy(gui_text_params.title,title,256);

    thread = (HANDLE) _beginthread( gui_text_popup_thread, 65535, NULL ) ;
    if ( thread != INVALID_HANDLE_VALUE )
        WaitRichEditInitSem( INFINITE );
    CloseRichEditInitSem();

    if ( hwndRichEdit != INVALID_HANDLE_VALUE ) {
        PostMessage(hwndRichEdit, EM_AUTOURLDETECT, (WPARAM) TRUE, (LPARAM) 0);
        PostMessage(hwndRichEdit, EM_STREAMIN, (WPARAM)SF_TEXT | SF_UNICODE, (LPARAM)&EditStream);
    }
    return 0;
}

int 
gui_text_popup_append(unsigned short uch)
{
  start:
    RequestRichEditMutex(INFINITE);
    if ( EditStreamMaxLen == 0 ) {
        EditStreamBuffer = malloc(4096 * sizeof(USHORT));
        EditStreamMaxLen = 4096;
        EditStreamLen = 0;
    }

    if ( EditStreamLen < EditStreamMaxLen ) {
        static int found_cr = 0;
        if ( uch == CR ) {
            found_cr = 1;
            EditStreamBuffer[EditStreamLen++] = 0x2028;
        } else if ( uch == LF ) {
            if ( !found_cr )
                EditStreamBuffer[EditStreamLen++] = 0x2028;
            found_cr = 0;
        } else {
            EditStreamBuffer[EditStreamLen++] = uch;
            found_cr = 0;
        }
    } else {
        ReleaseRichEditMutex();
        Sleep(100);
        goto start;
    }
    ReleaseRichEditMutex();
    return 0;
}

int
gui_text_popup_close(void)
{
    if ( hwndTextDlg != INVALID_HANDLE_VALUE )
        PostMessage(hwndTextDlg, WM_CLOSE, 0, 0);
    CloseRichEditCloseSem();
    CloseRichEditMutex();
    free(EditStreamBuffer);
    EditStreamBuffer = NULL;
    EditStreamLen = EditStreamMaxLen = 0;
    return 0;
}

int
gui_text_popup_wait(int seconds)
{
    RequestRichEditMutex(INFINITE);
    stream_complete = 1;
    ReleaseRichEditMutex();

    if ( hwndRichEdit != INVALID_HANDLE_VALUE ) {
        WaitRichEditCloseSem(seconds == -1 ? INFINITE : seconds * 1000);
        return 0;
    }
    return -1;
}
#endif /* KUI */
