xclipboard.c revision 97e8a2e8
1/*
2 * $Xorg: xclipboard.c,v 1.4 2001/02/09 02:05:38 xorgcvs Exp $
3 *
4 *
5Copyright 1989, 1998  The Open Group
6
7Permission to use, copy, modify, distribute, and sell this software and its
8documentation for any purpose is hereby granted without fee, provided that
9the above copyright notice appear in all copies and that both that
10copyright notice and this permission notice appear in supporting
11documentation.
12
13The above copyright notice and this permission notice shall be included in
14all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
19OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
20AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
23Except as contained in this notice, the name of The Open Group shall not be
24used in advertising or otherwise to promote the sale, use or other dealings
25in this Software without prior written authorization from The Open Group.
26 * *
27 * Author:  Ralph Swick, DEC/Project Athena
28 * Updated for R4:  Chris D. Peterson,  MIT X Consortium.
29 * Reauthored by: Keith Packard, MIT X Consortium.
30 */
31/* $XFree86: xc/programs/xclipboard/xclipboard.c,v 1.8tsi Exp $ */
32
33#include <stdio.h>
34#include <X11/Intrinsic.h>
35#include <X11/StringDefs.h>
36#include <X11/Xatom.h>
37
38#include <X11/Xmu/Atoms.h>
39#include <X11/Xmu/StdSel.h>
40
41#include <X11/Shell.h>
42#include <X11/Xaw/Form.h>
43#include <X11/Xaw/Label.h>
44#include <X11/Xaw/Command.h>
45#include <X11/Xaw/AsciiText.h>
46#include <X11/Xaw/Dialog.h>
47#include <X11/Xaw/Cardinals.h>
48#include <X11/IntrinsicP.h>
49#include <X11/Xaw/TextP.h>
50#include <X11/Xfuncs.h>
51
52#ifdef XKB
53#include <X11/extensions/XKBbells.h>
54#endif
55
56#include <stdlib.h>
57
58#define Command commandWidgetClass
59#define Label	labelWidgetClass
60#define Text    asciiTextWidgetClass
61
62#define INFINITY 10000000	/* pretty big, huh? */
63
64typedef struct _Clip {
65    struct _Clip    *next, *prev;
66    char	    *clip;
67    char	    *filename;
68    int		    avail;
69} ClipRec, *ClipPtr;
70
71static Atom wm_delete_window;
72static Atom wm_protocols;
73
74static void EraseTextWidget ( void );
75static void NewCurrentClipContents ( char *data, int len );
76
77static long
78TextLength(Widget w)
79{
80    return XawTextSourceScan (XawTextGetSource (w),
81			      (XawTextPosition) 0,
82 			      XawstAll, XawsdRight, 1, TRUE);
83}
84
85static void
86SaveClip(Widget w, ClipPtr clip)
87{
88    Arg	    args[1];
89    char    *data;
90    int	    len;
91    Widget  source;
92
93    source = XawTextGetSource (w);
94    XtSetArg (args[0], XtNstring, &data);
95    XtGetValues (source, args, 1);
96    len = strlen (data);
97    if (len >= clip->avail)
98    {
99	if (clip->clip)
100	    free (clip->clip);
101	clip->clip = malloc (len + 1);
102	if (!clip->clip)
103	    clip->avail = 0;
104	else
105	    clip->avail = len + 1;
106    }
107    if (clip->avail)
108    {
109	strcpy (clip->clip, data);
110    }
111}
112
113static void
114RestoreClip(Widget w, ClipPtr clip)
115{
116    Arg	    args[1];
117    Widget  source;
118
119    source = XawTextGetSource (w);
120    XtSetArg (args[0], XtNstring, clip->clip);
121    XtSetValues (source, args, 1);
122}
123
124/*ARGSUSED*/
125static ClipPtr
126NewClip(Widget w, ClipPtr old)
127{
128    ClipPtr newClip;
129
130    newClip = (ClipPtr) malloc (sizeof (ClipRec));
131    if (!newClip)
132	return newClip;
133    newClip->clip = 0;
134    newClip->avail = 0;
135    newClip->prev = old;
136    newClip->next = NULL;
137    newClip->filename = NULL;
138    if (old)
139    {
140	newClip->next = old->next;
141	old->next = newClip;
142    }
143    return newClip;
144}
145
146/*ARGSUSED*/
147static void
148DeleteClip(Widget w, ClipPtr clip)
149{
150    if (clip->prev)
151	clip->prev->next = clip->next;
152    if (clip->next)
153	clip->next->prev = clip->prev;
154    if (clip->clip)
155	free (clip->clip);
156    free ((char *) clip);
157}
158
159static ClipPtr	currentClip;
160static Widget	top;
161static Widget	text, nextButton, prevButton, indexLabel;
162static Widget	fileDialog, fileDialogShell;
163static Widget	failDialog, failDialogShell;
164
165static int
166IndexCurrentClip (void)
167{
168    int	i = 0;
169    ClipPtr clip;
170
171    for (clip = currentClip; clip; clip = clip->prev)
172	i++;
173    return i;
174}
175
176static void
177set_button_state (void)
178{
179    Boolean prevvalid, nextvalid;
180    Arg arg;
181    char labelString[10];
182
183    prevvalid = currentClip->prev != NULL;
184    nextvalid = currentClip->next != NULL;
185    XtSetArg (arg, XtNsensitive, prevvalid);
186    XtSetValues (prevButton, &arg, ONE);
187    XtSetArg (arg, XtNsensitive, nextvalid);
188    XtSetValues (nextButton, &arg, ONE);
189    sprintf (labelString, "%d", IndexCurrentClip ());
190    XtSetArg (arg, XtNlabel, labelString);
191    XtSetValues (indexLabel, &arg, ONE);
192}
193
194/* ARGSUSED */
195static void
196NextCurrentClip(Widget w, XEvent *ev, String *parms, Cardinal *np)
197{
198    if (currentClip->next)
199    {
200	SaveClip (text, currentClip);
201	currentClip = currentClip->next;
202	RestoreClip (text, currentClip);
203	set_button_state ();
204    }
205}
206
207/* ARGSUSED */
208static void
209PrevCurrentClip(Widget w, XEvent *ev, String *parms, Cardinal *np)
210{
211    if (currentClip->prev)
212    {
213	SaveClip (text, currentClip);
214	currentClip = currentClip->prev;
215	RestoreClip (text, currentClip);
216	set_button_state ();
217    }
218}
219
220/* ARGSUSED */
221static void
222DeleteCurrentClip(Widget w, XEvent *ev, String *parms, Cardinal *np)
223{
224    ClipPtr newCurrent;
225
226    if (currentClip->prev)
227	newCurrent = currentClip->prev;
228    else
229	newCurrent = currentClip->next;
230    if (newCurrent)
231    {
232	DeleteClip (text, currentClip);
233	currentClip = newCurrent;
234	RestoreClip (text, currentClip);
235    }
236    else
237	EraseTextWidget ();
238    set_button_state ();
239}
240
241/* ARGSUSED */
242static void
243Quit(Widget w, XEvent *ev, String *parms, Cardinal *np)
244{
245    XtCloseDisplay  (XtDisplay (text));
246    exit (0);
247}
248
249static void
250CenterWidgetAtPoint(Widget w, int x, int y)
251{
252    Arg	args[2];
253    Dimension	width, height;
254
255    XtSetArg(args[0], XtNwidth, &width);
256    XtSetArg(args[1], XtNheight, &height);
257    XtGetValues (w, args, 2);
258    x = x - (int) width / 2;
259    y = y - (int) height / 2;
260    if (x < 0)
261	x = 0;
262    else {
263	int scr_width = WidthOfScreen (XtScreen(w));
264	if (x + (int)width > scr_width)
265	    x = scr_width - width;
266    }
267    if (y < 0)
268	y = 0;
269    else {
270	int scr_height = HeightOfScreen (XtScreen(w));
271	if (y + (int)height > scr_height)
272	    y = scr_height - height;
273    }
274    XtSetArg(args[0], XtNx, x);
275    XtSetArg(args[1], XtNy, y);
276    XtSetValues (w, args, 2);
277}
278
279static void
280CenterWidgetOnEvent(Widget w, XEvent *e)
281{
282    CenterWidgetAtPoint (w, e->xbutton.x_root, e->xbutton.y_root);
283}
284
285static void
286CenterWidgetOnWidget(Widget w, Widget wT)
287{
288    Position	rootX, rootY;
289    Dimension	width, height;
290    Arg		args[2];
291
292    XtSetArg (args[0], XtNwidth, &width);
293    XtSetArg (args[1], XtNheight, &height);
294    XtGetValues (wT, args, 2);
295    XtTranslateCoords (wT, (Position) width/2, (Position) height/2, &rootX, &rootY);
296    CenterWidgetAtPoint (w, (int) rootX, (int) rootY);
297}
298
299/*ARGSUSED*/
300static void
301SaveToFile(Widget w, XEvent *e, String *argv, Cardinal *argc)
302{
303    Arg	    args[1];
304    char    *filename;
305
306    filename = "clipboard";
307    if (currentClip->filename)
308	filename = currentClip->filename;
309    XtSetArg(args[0], XtNvalue, filename);
310    XtSetValues (fileDialog, args, 1);
311    CenterWidgetOnEvent (fileDialogShell, e);
312    XtPopup (fileDialogShell, XtGrabNone);
313}
314
315/*ARGSUSED*/
316static void
317AcceptSaveFile(Widget w, XEvent *e, String *argv, Cardinal *argc)
318{
319    char    *filename;
320    Boolean success;
321    Arg	    args[1];
322
323    filename = XawDialogGetValueString (fileDialog);
324    success = XawAsciiSaveAsFile (XawTextGetSource (text), filename);
325    XtPopdown (fileDialogShell);
326    if (!success)
327    {
328	char	failMessage[1024];
329
330	sprintf (failMessage, "Can't open file \"%s\"", filename);
331	XtSetArg (args[0], XtNlabel, failMessage);
332	XtSetValues (failDialog, args, 1);
333	CenterWidgetOnEvent (failDialogShell, e);
334	XtPopup (failDialogShell, XtGrabNone);
335    }
336    else
337    {
338	if (currentClip->filename)
339	    free (currentClip->filename);
340	currentClip->filename = malloc (strlen (filename) + 1);
341	if (currentClip->filename)
342	    strcpy (currentClip->filename, filename);
343    }
344}
345
346/* ARGSUSED */
347static void
348CancelSaveFile(Widget w, XEvent *ev, String *parms, Cardinal *np)
349{
350    XtPopdown (fileDialogShell);
351}
352
353/* ARGSUSED */
354static void
355FailContinue(Widget w, XEvent *ev, String *parms, Cardinal *np)
356{
357    XtPopdown (failDialogShell);
358}
359
360/*ARGSUSED*/
361static void
362WMProtocols(Widget w, XEvent *ev, String *params, Cardinal *n)
363{
364    if (ev->type == ClientMessage &&
365	ev->xclient.message_type == wm_protocols &&
366	ev->xclient.data.l[0] == (long) wm_delete_window) {
367	while (w && !XtIsShell(w))
368	    w = XtParent(w);
369	if (w == top)
370	    Quit(w, ev, params, n);
371	else if (w == fileDialogShell)
372	    CancelSaveFile(w, ev, params, n);
373	else if (w == failDialogShell)
374	    FailContinue(w, ev, params, n);
375    }
376}
377
378/* ARGUSED */
379static void
380NewCurrentClip(Widget w, XEvent *ev, String *parms, Cardinal *np)
381{
382    NewCurrentClipContents ("", 0);
383}
384
385static void
386NewCurrentClipContents(char *data, int len)
387{
388    XawTextBlock textBlock;
389
390    SaveClip (text, currentClip);
391
392    /* append new clips at the end */
393    while (currentClip && currentClip->next)
394	currentClip = currentClip->next;
395    /* any trailing clips with no text get overwritten */
396    if (strlen (currentClip->clip) != 0)
397	currentClip = NewClip (text, currentClip);
398
399    textBlock.ptr = data;
400    textBlock.firstPos = 0;
401    textBlock.length = len;
402    textBlock.format = FMT8BIT;
403    if (XawTextReplace(text, 0, TextLength (text), &textBlock)) {
404#ifdef XKB
405	XkbStdBell(XtDisplay(text), XtWindow(text), 0, XkbBI_Info);
406#else
407	XBell( XtDisplay(text), 0);
408#endif
409    }
410    set_button_state ();
411}
412
413static void
414EraseTextWidget(void)
415{
416    XawTextBlock block;
417
418    block.ptr = "";
419    block.length = 0;
420    block.firstPos = 0;
421    block.format = FMT8BIT;
422
423    XawTextReplace(text, 0, INFINITY, &block);
424    /* If this fails, too bad. */
425}
426
427
428XtActionsRec xclipboard_actions[] = {
429    { "NewClip", 	NewCurrentClip },
430    { "NextClip",	NextCurrentClip },
431    { "PrevClip",	PrevCurrentClip },
432    { "DeleteClip",	DeleteCurrentClip },
433    { "Save",		SaveToFile },
434    { "AcceptSave",	AcceptSaveFile },
435    { "CancelSave",	CancelSaveFile },
436    { "FailContinue",	FailContinue },
437    { "Quit",		Quit },
438    { "WMProtocols",	WMProtocols }
439};
440
441static XrmOptionDescRec table[] = {
442    {"-w",	    "wrap",		XrmoptionNoArg,  "on"},
443/*    {"-nw",	    "wrap",		XrmoptionNoArg,  "False"} */
444};
445
446static Boolean ConvertSelection ( Widget w, Atom *selection, Atom *target,
447				  Atom *type, XtPointer *value,
448				  unsigned long *length, int *format );
449static void LoseSelection ( Widget w, Atom *selection );
450
451static Atom	ManagerAtom, ClipboardAtom;
452
453/*ARGSUSED*/
454static void
455InsertClipboard(Widget w, XtPointer client_data, Atom *selection,
456		Atom *type, XtPointer value, unsigned long *length,
457		int *format)
458{
459    if (*type != XT_CONVERT_FAIL)
460	NewCurrentClipContents ((char *) value, *length);
461    else
462    {
463	Arg arg;
464	XtSetArg (arg, XtNlabel, "CLIPBOARD selection conversion failed");
465	XtSetValues (failDialog, &arg, 1);
466	CenterWidgetOnWidget (failDialogShell, text);
467	XtPopup (failDialogShell, XtGrabNone);
468#ifdef XKB
469	XkbStdBell( XtDisplay(w), XtWindow(w), 0, XkbBI_MinorError );
470#else
471	XBell( XtDisplay(w), 0 );
472#endif
473    }
474
475    XtOwnSelection(top, ClipboardAtom, CurrentTime,
476		   ConvertSelection, LoseSelection, NULL);
477    XFree(value);
478}
479
480static Boolean
481ConvertSelection(Widget w, Atom *selection, Atom *target,
482		 Atom *type, XtPointer *value, unsigned long *length,
483		 int *format)
484{
485    Display* d = XtDisplay(w);
486    XSelectionRequestEvent* req =
487	XtGetSelectionRequest(w, *selection, (XtRequestId)NULL);
488
489    if (*target == XA_TARGETS(d)) {
490	Atom* targetP;
491	Atom* std_targets;
492	unsigned long std_length;
493	XmuConvertStandardSelection(w, req->time, selection, target, type,
494				    (XPointer*)&std_targets, &std_length,
495				    format);
496	*value = XtMalloc(sizeof(Atom)*(std_length + 5));
497	targetP = *(Atom**)value;
498	*targetP++ = XA_STRING;
499	*targetP++ = XA_TEXT(d);
500	*targetP++ = XA_LENGTH(d);
501	*targetP++ = XA_LIST_LENGTH(d);
502	*targetP++ = XA_CHARACTER_POSITION(d);
503	*length = std_length + (targetP - (*(Atom **) value));
504	memmove( (char*)targetP, (char*)std_targets, sizeof(Atom)*std_length);
505	XtFree((char*)std_targets);
506	*type = XA_ATOM;
507	*format = 32;
508	return True;
509    }
510
511    if (*target == XA_LIST_LENGTH(d) ||
512	*target == XA_LENGTH(d))
513    {
514    	long * temp;
515
516    	temp = (long *) XtMalloc(sizeof(long));
517    	if (*target == XA_LIST_LENGTH(d))
518      	  *temp = 1L;
519    	else			/* *target == XA_LENGTH(d) */
520      	  *temp = (long) TextLength (text);
521
522    	*value = (XPointer) temp;
523    	*type = XA_INTEGER;
524    	*length = 1L;
525    	*format = 32;
526    	return True;
527    }
528
529    if (*target == XA_CHARACTER_POSITION(d))
530    {
531    	long * temp;
532
533    	temp = (long *) XtMalloc(2 * sizeof(long));
534    	temp[0] = (long) 0;
535    	temp[1] = TextLength (text);
536    	*value = (XPointer) temp;
537    	*type = XA_SPAN(d);
538    	*length = 2L;
539    	*format = 32;
540    	return True;
541    }
542
543    if (*target == XA_STRING ||
544      *target == XA_TEXT(d) ||
545      *target == XA_COMPOUND_TEXT(d))
546    {
547    	if (*target == XA_COMPOUND_TEXT(d))
548	    *type = *target;
549    	else
550	    *type = XA_STRING;
551	*length = TextLength (text);
552    	*value = _XawTextGetSTRING((TextWidget) text, 0, *length);
553    	*format = 8;
554    	return True;
555    }
556
557    if (XmuConvertStandardSelection(w, req->time, selection, target, type,
558				    (XPointer *) value, length, format))
559	return True;
560
561    return False;
562}
563
564static void
565LoseSelection(Widget w, Atom *selection)
566{
567    XtGetSelectionValue(w, *selection, XA_STRING, InsertClipboard,
568			NULL, CurrentTime);
569}
570
571/*ARGSUSED*/
572static Boolean
573RefuseSelection(Widget w, Atom *selection, Atom *target,
574		Atom *type, XtPointer *value, unsigned long *length,
575		int *format)
576{
577    return False;
578}
579
580/*ARGSUSED*/
581static void
582LoseManager(Widget w, Atom *selection)
583{
584    XtError("another clipboard has taken over control\n");
585}
586
587typedef struct {
588  Boolean wrap;
589} ResourceData, *ResourceDataPtr;
590
591static ResourceData userOptions;
592
593#define Offset(field) XtOffsetOf(ResourceData, field)
594
595XtResource resources[] = {
596  {"wrap", "Wrap", XtRBoolean, sizeof(Boolean),
597     Offset(wrap), XtRImmediate, (XtPointer)False}
598};
599
600#undef Offset
601
602int
603main(int argc, char *argv[])
604{
605    Arg args[4];
606    Cardinal n;
607    XtAppContext xtcontext;
608    Widget parent;
609
610    XtSetLanguageProc(NULL, NULL, NULL);
611
612    top = XtAppInitialize( &xtcontext, "XClipboard", table, XtNumber(table),
613			  &argc, argv, NULL, NULL, 0);
614
615    XtGetApplicationResources(top, (XtPointer)&userOptions, resources,
616			      XtNumber(resources), NULL, 0);
617
618    XtAppAddActions (xtcontext,
619		     xclipboard_actions, XtNumber (xclipboard_actions));
620    /* CLIPBOARD_MANAGER is a non-standard mechanism */
621    ManagerAtom = XInternAtom(XtDisplay(top), "CLIPBOARD_MANAGER", False);
622    ClipboardAtom = XA_CLIPBOARD(XtDisplay(top));
623    if (XGetSelectionOwner(XtDisplay(top), ManagerAtom))
624	XtError("another clipboard is already running\n");
625
626    parent = XtCreateManagedWidget("form", formWidgetClass, top, NULL, ZERO);
627    (void) XtCreateManagedWidget("quit", Command, parent, NULL, ZERO);
628    (void) XtCreateManagedWidget("delete", Command, parent, NULL, ZERO);
629    (void) XtCreateManagedWidget("new", Command, parent, NULL, ZERO);
630    (void) XtCreateManagedWidget("save", Command, parent, NULL, ZERO);
631    nextButton = XtCreateManagedWidget("next", Command, parent, NULL, ZERO);
632    prevButton = XtCreateManagedWidget("prev", Command, parent, NULL, ZERO);
633    indexLabel = XtCreateManagedWidget("index", Label, parent, NULL, ZERO);
634
635    n=0;
636    XtSetArg(args[n], XtNtype, XawAsciiString); n++;
637    XtSetArg(args[n], XtNeditType, XawtextEdit); n++;
638    if (userOptions.wrap) {
639	XtSetArg(args[n], XtNwrap, XawtextWrapWord); n++;
640	XtSetArg(args[n], XtNscrollHorizontal, False); n++;
641    }
642
643    text = XtCreateManagedWidget( "text", Text, parent, args, n);
644
645    currentClip = NewClip (text, (ClipPtr) 0);
646
647    set_button_state ();
648
649    fileDialogShell = XtCreatePopupShell("fileDialogShell",
650					 transientShellWidgetClass,
651					 top, NULL, ZERO);
652    fileDialog = XtCreateManagedWidget ("fileDialog", dialogWidgetClass,
653					fileDialogShell, NULL, ZERO);
654    XawDialogAddButton(fileDialog, "accept", NULL, NULL);
655    XawDialogAddButton(fileDialog, "cancel", NULL, NULL);
656
657    failDialogShell = XtCreatePopupShell("failDialogShell",
658					 transientShellWidgetClass,
659					 top, NULL, ZERO);
660    failDialog = XtCreateManagedWidget ("failDialog", dialogWidgetClass,
661					failDialogShell, NULL, ZERO);
662    XawDialogAddButton (failDialog, "continue", NULL, NULL);
663
664    XtRealizeWidget(top);
665    XtRealizeWidget(fileDialogShell);
666    XtRealizeWidget(failDialogShell);
667    XtOwnSelection(top, ManagerAtom, CurrentTime,
668		   RefuseSelection, LoseManager, NULL);
669    if (XGetSelectionOwner (XtDisplay(top), ClipboardAtom)) {
670	LoseSelection (top, &ClipboardAtom);
671    } else {
672    	XtOwnSelection(top, ClipboardAtom, CurrentTime,
673		       ConvertSelection, LoseSelection, NULL);
674    }
675    wm_delete_window = XInternAtom(XtDisplay(top), "WM_DELETE_WINDOW", False);
676    wm_protocols = XInternAtom(XtDisplay(top), "WM_PROTOCOLS", False);
677    (void) XSetWMProtocols(XtDisplay(top), XtWindow(top), &wm_delete_window,1);
678    (void) XSetWMProtocols(XtDisplay(top), XtWindow(fileDialogShell),
679			   &wm_delete_window,1);
680    (void) XSetWMProtocols(XtDisplay(top), XtWindow(failDialogShell),
681			   &wm_delete_window,1);
682    XtAppMainLoop(xtcontext);
683    exit(0);
684}
685