/* 
 * tkWinClipboard.c --
 *
 *	This file contains functions for managing the clipboard.
 *
 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
 * Copyright (c) 1998-2000 by Scriptics Corporation.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) Id
 */

#include "tkWinInt.h"
#include "tkSelect.h"

static void	UpdateClipboard _ANSI_ARGS_((HWND hwnd));

/*
 *----------------------------------------------------------------------
 *
 * TkSelGetSelection --
 *
 *	Retrieve the specified selection from another process.  For
 *	now, only fetching XA_STRING from CLIPBOARD is supported.
 *	Eventually other types should be allowed.
 * 
 * Results:
 *	The return value is a standard Tcl return value.
 *	If an error occurs (such as no selection exists)
 *	then an error message is left in the interp's result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TkSelGetSelection(interp, tkwin, selection, target, proc, clientData)
    Tcl_Interp *interp;		/* Interpreter to use for reporting
				 * errors. */
    Tk_Window tkwin;		/* Window on whose behalf to retrieve
				 * the selection (determines display
				 * from which to retrieve). */
    Atom selection;		/* Selection to retrieve. */
    Atom target;		/* Desired form in which selection
				 * is to be returned. */
    Tk_GetSelProc *proc;	/* Procedure to call to process the
				 * selection, once it has been retrieved. */
    ClientData clientData;	/* Arbitrary value to pass to proc. */
{
    char *data, *destPtr;
    Tcl_DString ds;
    HGLOBAL handle;
    Tcl_Encoding encoding;
    int result, locale;

    if ((selection != Tk_InternAtom(tkwin, "CLIPBOARD"))
	    || (target != XA_STRING)
	    || !OpenClipboard(NULL)) {
	goto error;
    }

    /*
     * Attempt to get the data in Unicode form if available as this is
     * less work that CF_TEXT.
     */

    result = TCL_ERROR;
    if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
	handle = GetClipboardData(CF_UNICODETEXT);
	if (!handle) {
	    CloseClipboard();
	    goto error;
	}
	data = GlobalLock(handle);
	Tcl_DStringInit(&ds);
	Tcl_UniCharToUtfDString((Tcl_UniChar *)data,
		Tcl_UniCharLen((Tcl_UniChar *)data), &ds);
	GlobalUnlock(handle);
    } else if (IsClipboardFormatAvailable(CF_TEXT)) {
	/*
	 * Determine the encoding to use to convert this text.
	 */

	if (IsClipboardFormatAvailable(CF_LOCALE)) {
	    handle = GetClipboardData(CF_LOCALE);
	    if (!handle) {
		CloseClipboard();
		goto error;
	    }

	    /*
	     * Get the locale identifier, determine the proper code page
	     * to use, and find the corresponding encoding.
	     */

	    Tcl_DStringInit(&ds);
	    Tcl_DStringAppend(&ds, "cp######", -1);
	    data = GlobalLock(handle);
	    

	    /*
	     * Even though the documentation claims that GetLocaleInfo 
	     * expects an LCID, on Windows 9x it really seems to expect
	     * a LanguageID.
	     */

	    locale = LANGIDFROMLCID(*((int*)data));
	    GetLocaleInfo(locale, LOCALE_IDEFAULTANSICODEPAGE,
		    Tcl_DStringValue(&ds)+2, Tcl_DStringLength(&ds)-2);
	    GlobalUnlock(handle);

	    encoding = Tcl_GetEncoding(NULL, Tcl_DStringValue(&ds));
	    Tcl_DStringFree(&ds);
	} else {
	    encoding = NULL;
	}

	/*
	 * Fetch the text and convert it to UTF.
	 */

	handle = GetClipboardData(CF_TEXT);
	if (!handle) {
	    if (encoding) {
		Tcl_FreeEncoding(encoding);
	    }
	    CloseClipboard();
	    goto error;
	}
	data = GlobalLock(handle);
	Tcl_ExternalToUtfDString(encoding, data, -1, &ds);
	GlobalUnlock(handle);
	if (encoding) {
	    Tcl_FreeEncoding(encoding);
	}

    } else {
	CloseClipboard();
	goto error;
    }

    /*
     * Translate CR/LF to LF.
     */
	
    data = destPtr = Tcl_DStringValue(&ds);
    while (*data) {
	if (data[0] == '\r' && data[1] == '\n') {
	    data++;
	} else {
	    *destPtr++ = *data++;
	}
    }
    *destPtr = '\0';

    /*
     * Pass the data off to the selection procedure.
     */

    result = (*proc)(clientData, interp, Tcl_DStringValue(&ds));
    Tcl_DStringFree(&ds);
    CloseClipboard();
    return result;
     
error:
    Tcl_AppendResult(interp, Tk_GetAtomName(tkwin, selection),
	    " selection doesn't exist or form \"",
	    Tk_GetAtomName(tkwin, target),
	    "\" not defined", (char *) NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * TkSetSelectionOwner --
 *
 *	This function claims ownership of the specified selection.
 *	If the selection is CLIPBOARD, then we empty the system
 *	clipboard.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Empties the system clipboard, and claims ownership.
 *
 *----------------------------------------------------------------------
 */

void
XSetSelectionOwner(display, selection, owner, time)
    Display* display;
    Atom selection;
    Window owner;
    Time time;
{
    HWND hwnd = owner ? TkWinGetHWND(owner) : NULL;
    Tk_Window tkwin;

    /*
     * This is a gross hack because the Tk_InternAtom interface is broken.
     * It expects a Tk_Window, even though it only needs a Tk_Display.
     */

    tkwin = (Tk_Window) TkGetMainInfoList()->winPtr;

    if (selection == Tk_InternAtom(tkwin, "CLIPBOARD")) {

	/*
	 * Only claim and empty the clipboard if we aren't already the
	 * owner of the clipboard.
	 */

	if (GetClipboardOwner() != hwnd) {
	    UpdateClipboard(hwnd);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkWinClipboardRender --
 *
 *	This function supplies the contents of the clipboard in
 *	response to a WM_RENDERFORMAT message.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Sets the contents of the clipboard.
 *
 *----------------------------------------------------------------------
 */

void
TkWinClipboardRender(dispPtr, format)
    TkDisplay *dispPtr;
    UINT format;
{
    TkClipboardTarget *targetPtr;
    TkClipboardBuffer *cbPtr;
    HGLOBAL handle;
    char *buffer, *p, *rawText, *endPtr;
    int length;
    Tcl_DString ds;

    for (targetPtr = dispPtr->clipTargetPtr; targetPtr != NULL;
	    targetPtr = targetPtr->nextPtr) {
	if (targetPtr->type == XA_STRING)
	    break;
    }

    /*
     * Count the number of newlines so we can add space for them in
     * the resulting string.
     */

    length = 0;
    if (targetPtr != NULL) {
	for (cbPtr = targetPtr->firstBufferPtr; cbPtr != NULL;
		cbPtr = cbPtr->nextPtr) {
	    length += cbPtr->length;
	    for (p = cbPtr->buffer, endPtr = p + cbPtr->length;
		    p < endPtr; p++) {
		if (*p == '\n') {
		    length++;
		}
	    }
	}
    }

    /*
     * Copy the data and change EOL characters.
     */

    buffer = rawText = ckalloc(length + 1);
    if (targetPtr != NULL) {
	for (cbPtr = targetPtr->firstBufferPtr; cbPtr != NULL;
		cbPtr = cbPtr->nextPtr) {
	    for (p = cbPtr->buffer, endPtr = p + cbPtr->length;
		    p < endPtr; p++) {
		if (*p == '\n') {
		    *buffer++ = '\r';
		}
		*buffer++ = *p;
	    }
	}
    }
    *buffer = '\0';

    /*
     * Depending on the platform, turn the data into Unicode or the
     * system encoding before placing it on the clipboard.
     */

    if (TkWinGetPlatformId() == VER_PLATFORM_WIN32_NT) {
	Tcl_DStringInit(&ds);
	Tcl_UtfToUniCharDString(rawText, -1, &ds);
	ckfree(rawText);
	handle = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE,
		Tcl_DStringLength(&ds)+2);
	if (!handle) {
	    Tcl_DStringFree(&ds);
	    return;
	}
	buffer = GlobalLock(handle);
	memcpy(buffer, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds) + 2);
	GlobalUnlock(handle);
	Tcl_DStringFree(&ds);
	SetClipboardData(CF_UNICODETEXT, handle);
    } else {
	Tcl_UtfToExternalDString(NULL, rawText, -1, &ds);
	ckfree(rawText);
	handle = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE,
		Tcl_DStringLength(&ds)+1);
	if (!handle) {
	    Tcl_DStringFree(&ds);
	    return;
	}
	buffer = GlobalLock(handle);
	memcpy(buffer, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds) + 1);
	GlobalUnlock(handle);
	Tcl_DStringFree(&ds);
	SetClipboardData(CF_TEXT, handle);
    }

    return;
}

/*
 *----------------------------------------------------------------------
 *
 * TkSelUpdateClipboard --
 *
 *	This function is called to force the clipboard to be updated
 *	after new data is added.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Clears the current contents of the clipboard.
 *
 *----------------------------------------------------------------------
 */

void
TkSelUpdateClipboard(winPtr, targetPtr)
    TkWindow *winPtr;
    TkClipboardTarget *targetPtr;
{
    HWND hwnd = TkWinGetHWND(winPtr->window);
    UpdateClipboard(hwnd);
}

/*
 *----------------------------------------------------------------------
 *
 * UpdateClipboard --
 *
 *	Take ownership of the clipboard, clear it, and indicate to the
 *	system the supported formats.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
UpdateClipboard(hwnd)
    HWND hwnd;
{
    TkWinUpdatingClipboard(TRUE);
    OpenClipboard(hwnd);
    EmptyClipboard();

    /*
     * CF_UNICODETEXT is only supported on NT, but it it is prefered
     * when possible.
     */

    if (TkWinGetPlatformId() == VER_PLATFORM_WIN32_NT) {
	SetClipboardData(CF_UNICODETEXT, NULL);
    } else {
	SetClipboardData(CF_TEXT, NULL);
    }
    CloseClipboard();
    TkWinUpdatingClipboard(FALSE);
}

/*
 *--------------------------------------------------------------
 *
 * TkSelEventProc --
 *
 *	This procedure is invoked whenever a selection-related
 *	event occurs. 
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Lots:  depends on the type of event.
 *
 *--------------------------------------------------------------
 */

void
TkSelEventProc(tkwin, eventPtr)
    Tk_Window tkwin;		/* Window for which event was
				 * targeted. */
    register XEvent *eventPtr;	/* X event:  either SelectionClear,
				 * SelectionRequest, or
				 * SelectionNotify. */
{
    if (eventPtr->type == SelectionClear) {
	TkSelClearSelection(tkwin, eventPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkSelPropProc --
 *
 *	This procedure is invoked when property-change events
 *	occur on windows not known to the toolkit.  This is a stub
 *	function under Windows.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TkSelPropProc(eventPtr)
    register XEvent *eventPtr;		/* X PropertyChange event. */
{
}
