1/*
2 * tkWinClipboard.c --
3 *
4 *	This file contains functions for managing the clipboard.
5 *
6 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
7 * Copyright (c) 1998-2000 by Scriptics Corporation.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id$
13 */
14
15#include "tkWinInt.h"
16#include "tkSelect.h"
17
18static void		UpdateClipboard(HWND hwnd);
19
20/*
21 *----------------------------------------------------------------------
22 *
23 * TkSelGetSelection --
24 *
25 *	Retrieve the specified selection from another process. For now, only
26 *	fetching XA_STRING from CLIPBOARD is supported. Eventually other types
27 *	should be allowed.
28 *
29 * Results:
30 *	The return value is a standard Tcl return value. If an error occurs
31 *	(such as no selection exists) then an error message is left in the
32 *	interp's result.
33 *
34 * Side effects:
35 *	None.
36 *
37 *----------------------------------------------------------------------
38 */
39
40int
41TkSelGetSelection(
42    Tcl_Interp *interp,		/* Interpreter to use for reporting errors. */
43    Tk_Window tkwin,		/* Window on whose behalf to retrieve the
44				 * selection (determines display from which to
45				 * retrieve). */
46    Atom selection,		/* Selection to retrieve. */
47    Atom target,		/* Desired form in which selection is to be
48				 * returned. */
49    Tk_GetSelProc *proc,	/* Procedure to call to process the selection,
50				 * once it has been retrieved. */
51    ClientData clientData)	/* Arbitrary value to pass to proc. */
52{
53    char *data, *destPtr;
54    Tcl_DString ds;
55    HGLOBAL handle;
56    Tcl_Encoding encoding;
57    int result, locale;
58
59    if ((selection != Tk_InternAtom(tkwin, "CLIPBOARD"))
60	    || (target != XA_STRING)
61	    || !OpenClipboard(NULL)) {
62	goto error;
63    }
64
65    /*
66     * Attempt to get the data in Unicode form if available as this is less
67     * work that CF_TEXT.
68     */
69
70    result = TCL_ERROR;
71    if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
72	handle = GetClipboardData(CF_UNICODETEXT);
73	if (!handle) {
74	    CloseClipboard();
75	    goto error;
76	}
77	data = GlobalLock(handle);
78	Tcl_DStringInit(&ds);
79	Tcl_UniCharToUtfDString((Tcl_UniChar *)data,
80		Tcl_UniCharLen((Tcl_UniChar *)data), &ds);
81	GlobalUnlock(handle);
82    } else if (IsClipboardFormatAvailable(CF_TEXT)) {
83	/*
84	 * Determine the encoding to use to convert this text.
85	 */
86
87	if (IsClipboardFormatAvailable(CF_LOCALE)) {
88	    handle = GetClipboardData(CF_LOCALE);
89	    if (!handle) {
90		CloseClipboard();
91		goto error;
92	    }
93
94	    /*
95	     * Get the locale identifier, determine the proper code page to
96	     * use, and find the corresponding encoding.
97	     */
98
99	    Tcl_DStringInit(&ds);
100	    Tcl_DStringAppend(&ds, "cp######", -1);
101	    data = GlobalLock(handle);
102
103	    /*
104	     * Even though the documentation claims that GetLocaleInfo expects
105	     * an LCID, on Windows 9x it really seems to expect a LanguageID.
106	     */
107
108	    locale = LANGIDFROMLCID(*((int*)data));
109	    GetLocaleInfo(locale, LOCALE_IDEFAULTANSICODEPAGE,
110		    Tcl_DStringValue(&ds)+2, Tcl_DStringLength(&ds)-2);
111	    GlobalUnlock(handle);
112
113	    encoding = Tcl_GetEncoding(NULL, Tcl_DStringValue(&ds));
114	    Tcl_DStringFree(&ds);
115	} else {
116	    encoding = NULL;
117	}
118
119	/*
120	 * Fetch the text and convert it to UTF.
121	 */
122
123	handle = GetClipboardData(CF_TEXT);
124	if (!handle) {
125	    if (encoding) {
126		Tcl_FreeEncoding(encoding);
127	    }
128	    CloseClipboard();
129	    goto error;
130	}
131	data = GlobalLock(handle);
132	Tcl_ExternalToUtfDString(encoding, data, -1, &ds);
133	GlobalUnlock(handle);
134	if (encoding) {
135	    Tcl_FreeEncoding(encoding);
136	}
137
138    } else {
139	CloseClipboard();
140	goto error;
141    }
142
143    /*
144     * Translate CR/LF to LF.
145     */
146
147    data = destPtr = Tcl_DStringValue(&ds);
148    while (*data) {
149	if (data[0] == '\r' && data[1] == '\n') {
150	    data++;
151	} else {
152	    *destPtr++ = *data++;
153	}
154    }
155    *destPtr = '\0';
156
157    /*
158     * Pass the data off to the selection procedure.
159     */
160
161    result = (*proc)(clientData, interp, Tcl_DStringValue(&ds));
162    Tcl_DStringFree(&ds);
163    CloseClipboard();
164    return result;
165
166  error:
167    Tcl_AppendResult(interp, Tk_GetAtomName(tkwin, selection),
168	    " selection doesn't exist or form \"",
169	    Tk_GetAtomName(tkwin, target), "\" not defined", NULL);
170    return TCL_ERROR;
171}
172
173/*
174 *----------------------------------------------------------------------
175 *
176 * TkSetSelectionOwner --
177 *
178 *	This function claims ownership of the specified selection. If the
179 *	selection is CLIPBOARD, then we empty the system clipboard.
180 *
181 * Results:
182 *	None.
183 *
184 * Side effects:
185 *	Empties the system clipboard, and claims ownership.
186 *
187 *----------------------------------------------------------------------
188 */
189
190void
191XSetSelectionOwner(
192    Display *display,
193    Atom selection,
194    Window owner,
195    Time time)
196{
197    HWND hwnd = owner ? TkWinGetHWND(owner) : NULL;
198    Tk_Window tkwin;
199
200    /*
201     * This is a gross hack because the Tk_InternAtom interface is broken. It
202     * expects a Tk_Window, even though it only needs a Tk_Display.
203     */
204
205    tkwin = (Tk_Window) TkGetMainInfoList()->winPtr;
206
207    if (selection == Tk_InternAtom(tkwin, "CLIPBOARD")) {
208	/*
209	 * Only claim and empty the clipboard if we aren't already the owner
210	 * of the clipboard.
211	 */
212
213	if (GetClipboardOwner() != hwnd) {
214	    UpdateClipboard(hwnd);
215	}
216    }
217}
218
219/*
220 *----------------------------------------------------------------------
221 *
222 * TkWinClipboardRender --
223 *
224 *	This function supplies the contents of the clipboard in response to a
225 *	WM_RENDERFORMAT message.
226 *
227 * Results:
228 *	None.
229 *
230 * Side effects:
231 *	Sets the contents of the clipboard.
232 *
233 *----------------------------------------------------------------------
234 */
235
236void
237TkWinClipboardRender(
238    TkDisplay *dispPtr,
239    UINT format)
240{
241    TkClipboardTarget *targetPtr;
242    TkClipboardBuffer *cbPtr;
243    HGLOBAL handle;
244    char *buffer, *p, *rawText, *endPtr;
245    int length;
246    Tcl_DString ds;
247
248    for (targetPtr = dispPtr->clipTargetPtr; targetPtr != NULL;
249	    targetPtr = targetPtr->nextPtr) {
250	if (targetPtr->type == XA_STRING) {
251	    break;
252	}
253    }
254
255    /*
256     * Count the number of newlines so we can add space for them in the
257     * resulting string.
258     */
259
260    length = 0;
261    if (targetPtr != NULL) {
262	for (cbPtr = targetPtr->firstBufferPtr; cbPtr != NULL;
263		cbPtr = cbPtr->nextPtr) {
264	    length += cbPtr->length;
265	    for (p = cbPtr->buffer, endPtr = p + cbPtr->length;
266		    p < endPtr; p++) {
267		if (*p == '\n') {
268		    length++;
269		}
270	    }
271	}
272    }
273
274    /*
275     * Copy the data and change EOL characters.
276     */
277
278    buffer = rawText = ckalloc((unsigned)length + 1);
279    if (targetPtr != NULL) {
280	for (cbPtr = targetPtr->firstBufferPtr; cbPtr != NULL;
281		cbPtr = cbPtr->nextPtr) {
282	    for (p = cbPtr->buffer, endPtr = p + cbPtr->length;
283		    p < endPtr; p++) {
284		if (*p == '\n') {
285		    *buffer++ = '\r';
286		}
287		*buffer++ = *p;
288	    }
289	}
290    }
291    *buffer = '\0';
292
293    /*
294     * Depending on the platform, turn the data into Unicode or the system
295     * encoding before placing it on the clipboard.
296     */
297
298    if (TkWinGetPlatformId() == VER_PLATFORM_WIN32_NT) {
299	Tcl_DStringInit(&ds);
300	Tcl_UtfToUniCharDString(rawText, -1, &ds);
301	ckfree(rawText);
302	handle = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE,
303		(unsigned) Tcl_DStringLength(&ds) + 2);
304	if (!handle) {
305	    Tcl_DStringFree(&ds);
306	    return;
307	}
308	buffer = GlobalLock(handle);
309	memcpy(buffer, Tcl_DStringValue(&ds),
310		(unsigned) Tcl_DStringLength(&ds) + 2);
311	GlobalUnlock(handle);
312	Tcl_DStringFree(&ds);
313	SetClipboardData(CF_UNICODETEXT, handle);
314    } else {
315	Tcl_UtfToExternalDString(NULL, rawText, -1, &ds);
316	ckfree(rawText);
317	handle = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE,
318		(unsigned) Tcl_DStringLength(&ds) + 1);
319	if (!handle) {
320	    Tcl_DStringFree(&ds);
321	    return;
322	}
323	buffer = GlobalLock(handle);
324	memcpy(buffer, Tcl_DStringValue(&ds),
325		(unsigned) Tcl_DStringLength(&ds) + 1);
326	GlobalUnlock(handle);
327	Tcl_DStringFree(&ds);
328	SetClipboardData(CF_TEXT, handle);
329    }
330}
331
332/*
333 *----------------------------------------------------------------------
334 *
335 * TkSelUpdateClipboard --
336 *
337 *	This function is called to force the clipboard to be updated after new
338 *	data is added.
339 *
340 * Results:
341 *	None.
342 *
343 * Side effects:
344 *	Clears the current contents of the clipboard.
345 *
346 *----------------------------------------------------------------------
347 */
348
349void
350TkSelUpdateClipboard(
351    TkWindow *winPtr,
352    TkClipboardTarget *targetPtr)
353{
354    HWND hwnd = TkWinGetHWND(winPtr->window);
355    UpdateClipboard(hwnd);
356}
357
358/*
359 *----------------------------------------------------------------------
360 *
361 * UpdateClipboard --
362 *
363 *	Take ownership of the clipboard, clear it, and indicate to the system
364 *	the supported formats.
365 *
366 * Results:
367 *	None.
368 *
369 * Side effects:
370 *	None.
371 *
372 *----------------------------------------------------------------------
373 */
374
375static void
376UpdateClipboard(
377    HWND hwnd)
378{
379    TkWinUpdatingClipboard(TRUE);
380    OpenClipboard(hwnd);
381    EmptyClipboard();
382
383    /*
384     * CF_UNICODETEXT is only supported on NT, but it it is prefered when
385     * possible.
386     */
387
388    if (TkWinGetPlatformId() == VER_PLATFORM_WIN32_NT) {
389	SetClipboardData(CF_UNICODETEXT, NULL);
390    } else {
391	SetClipboardData(CF_TEXT, NULL);
392    }
393    CloseClipboard();
394    TkWinUpdatingClipboard(FALSE);
395}
396
397/*
398 *--------------------------------------------------------------
399 *
400 * TkSelEventProc --
401 *
402 *	This procedure is invoked whenever a selection-related event occurs.
403 *
404 * Results:
405 *	None.
406 *
407 * Side effects:
408 *	Lots: depends on the type of event.
409 *
410 *--------------------------------------------------------------
411 */
412
413void
414TkSelEventProc(
415    Tk_Window tkwin,		/* Window for which event was targeted. */
416    register XEvent *eventPtr)	/* X event: either SelectionClear,
417				 * SelectionRequest, or SelectionNotify. */
418{
419    if (eventPtr->type == SelectionClear) {
420	TkSelClearSelection(tkwin, eventPtr);
421    }
422}
423
424/*
425 *----------------------------------------------------------------------
426 *
427 * TkSelPropProc --
428 *
429 *	This procedure is invoked when property-change events occur on windows
430 *	not known to the toolkit. This is a stub function under Windows.
431 *
432 * Results:
433 *	None.
434 *
435 * Side effects:
436 *	None.
437 *
438 *----------------------------------------------------------------------
439 */
440
441void
442TkSelPropProc(
443    register XEvent *eventPtr)	/* X PropertyChange event. */
444{
445}
446
447/*
448 * Local Variables:
449 * mode: c
450 * c-basic-offset: 4
451 * fill-column: 78
452 * End:
453 */
454