Add GNU LGPL headers to all .c .C and .h files
[oweals/cde.git] / cde / lib / DtWidget / EditCalls.c
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /* $XConsortium: EditCalls.c /main/4 1996/03/26 19:53:27 drk $
24  **********************************<+>*************************************
25  ***************************************************************************
26  **
27  **  File:        EditCalls.c
28  **
29  **  Project:     DtEditor widget interface for text edit services.
30  **
31  **  Description: Contains the public functions related to undo,
32  **               cut, copy, paste, and the internal Modify/Verify callback.
33  **  -----------
34  **
35  *******************************************************************
36  * (c) Copyright 1996 Digital Equipment Corporation.
37  * (c) Copyright 1993, 1994, 1996 Hewlett-Packard Company.
38  * (c) Copyright 1993, 1994, 1996 International Business Machines Corp.
39  * (c) Copyright 1993, 1994, 1996 Sun Microsystems, Inc.
40  * (c) Copyright 1996 Novell, Inc. 
41  * (c) Copyright 1996 FUJITSU LIMITED.
42  * (c) Copyright 1996 Hitachi.
43  * (c) Copyright 1993, 1994 Unix System Labs, Inc., a subsidiary of Novell, Inc.
44  ********************************************************************
45  **
46  **
47  **************************************************************************
48  **********************************<+>*************************************/
49
50 #include "EditorP.h"
51 #include "DtWidgetI.h"
52
53 void
54 _DtEditorResetUndo(
55         DtEditorWidget editor)
56 {
57
58   /*
59    * Reset deletion & insertion contexts.
60    */
61
62   if( (M_deletedText(editor) != (char *)NULL) &&
63       (strlen(M_deletedText(editor)) != 0) )
64   {
65     XtFree(M_deletedText(editor));
66     M_deletedText(editor) = (char *) NULL;
67   } 
68     
69   M_deletionStart(editor) = NO_DELETION_IN_PROGRESS;
70   M_insertStart(editor) = 0;
71   M_insertionLength(editor) = 0;
72
73 } /* end _DtEditorResetUndo */
74
75
76 Boolean
77 DtEditorUndoEdit(
78         Widget widget)
79 {
80     DtEditorWidget pPriv = (DtEditorWidget) widget;
81     _DtWidgetToAppContext(widget);
82     _DtAppLock(app);
83
84     /*
85      * Remove any insertion, and then put back any previous deletion.  
86      * The tricky part is that removing the insertion looks like a deletion 
87      * and the modifyVerify callback will save in the undo buffer, wiping
88      * out any previous deletion (before we can reinsert it).  So we have to
89      * save the previous deletion before undoing the insertion so we can
90      * reinsert it.  Make sense?
91      */
92     if(M_insertionLength(pPriv) != 0)
93     {
94         /*
95          * There is an insertion so remove everything we just inserted
96          */
97         if( (M_deletedText(pPriv) != (char *)NULL) && 
98             (strlen(M_deletedText(pPriv)) != 0) )
99         {
100             /*
101              * If there is a current deletion, save it before removing the
102              * insertion so we can put it back (i.e. undo the deletion).
103              */
104             int oldDeleteStart = M_deletionStart(pPriv);
105             char *oldDeletion = M_deletedText(pPriv);
106             M_deletedText(pPriv) = (char *)NULL;
107
108             /*
109              * 1. Delete the last insertion.
110              */
111             XmTextSetSelection(M_text(pPriv), M_insertStart(pPriv), 
112                                M_insertStart(pPriv) + M_insertionLength(pPriv),
113                                CurrentTime);
114
115             M_insertionLength(pPriv)=0;
116             XmTextRemove(M_text(pPriv));
117
118             /*
119              * 2. Put back the previous deletion.
120              */
121             XmTextInsert(M_text(pPriv), oldDeleteStart, oldDeletion);
122
123             XtFree(oldDeletion);
124         }
125         else
126         {
127             /* 
128              * There is no deletion, so we just have to remove the 
129              * insertion.
130              */
131             XmTextSetSelection(M_text(pPriv), M_insertStart(pPriv), 
132                                M_insertStart(pPriv) + M_insertionLength(pPriv),
133                                CurrentTime);
134             M_insertionLength(pPriv) = 0;
135             XmTextRemove(M_text(pPriv));
136         }
137     }
138     else if(M_deletedText(pPriv) != (char *)NULL)
139     {
140         /*
141          * Nothing has been inserted so just undo the previous deletion.
142          */
143
144         char *oldDeletion = M_deletedText(pPriv);
145         M_deletedText(pPriv) = (char *)NULL;
146
147         XmTextInsert(M_text(pPriv), M_deletionStart(pPriv), oldDeletion);
148
149         XtFree(oldDeletion);
150     }
151     else
152     {
153       /* 
154        * There is no insertion to remove or deletion to put back in
155        * (i.e. nothing to undo) so return False.
156        */
157       _DtAppUnlock(app);
158       return(False);
159     }
160
161   _DtAppUnlock(app);
162   return(True);
163
164 } /* end DtEditorUndoEdit */
165
166 Boolean
167 DtEditorCutToClipboard(
168         Widget widget)
169 {
170
171     DtEditorWidget editor = (DtEditorWidget) widget;
172     XEvent *event;
173     _DtWidgetToAppContext(widget);
174     _DtAppLock(app);
175
176     /*
177      * Create an event with a correct timestamp
178      */
179     event = (XEvent *) XtMalloc( sizeof(XEvent) );
180     event->xkey.time = XtLastTimestampProcessed( M_display(editor) );
181
182     /*
183      * Call routine to cut selection to clipboard
184      */
185     XtCallActionProc(M_text(editor), "cut-clipboard", event, NULL, 0);
186
187     XtFree( (char *) event );
188
189     _DtAppUnlock(app);
190     return(True);
191
192 }
193
194 Boolean
195 DtEditorCopyToClipboard(
196         Widget widget)
197 {
198     DtEditorWidget editor = (DtEditorWidget) widget;
199     XEvent *event;
200     _DtWidgetToAppContext(widget);
201     _DtAppLock(app);
202
203     /*
204      * Create an event with a correct timestamp
205      */
206     event = (XEvent *) XtMalloc( sizeof(XEvent) );
207     event->xkey.time = XtLastTimestampProcessed( M_display(editor) );
208
209     /*
210      * Call routine to copy selection to clipboard
211      */
212     XtCallActionProc(M_text(editor), "copy-clipboard", event, NULL, 0);
213
214     XtFree( (char *) event );
215
216     _DtAppUnlock(app);
217     return(True);
218 }
219
220 Boolean
221 DtEditorPasteFromClipboard(
222         Widget widget)
223 {
224     DtEditorWidget editor = (DtEditorWidget) widget;
225     XEvent *event;
226     _DtWidgetToAppContext(widget);
227     _DtAppLock(app);
228
229     /*
230      * Create an event with a correct timestamp
231      */
232     event = (XEvent *) XtMalloc( sizeof(XEvent) );
233     event->xkey.time = XtLastTimestampProcessed( M_display(editor) );
234
235     /*
236      * Call routine to paste contents of clipboard at insertion cursor
237      */
238     XtCallActionProc(M_text(editor), "paste-clipboard", event, NULL, 0);
239
240     XtFree( (char *) event );
241
242     _DtAppUnlock(app);
243     return(True);
244
245 }
246
247
248 /*
249  * SetUndoDeletionState maintains the contents of editStuff.undo related
250  * to deletion (the deleted text & its original starting position).  
251  *
252  * SetUndoDeletionState can also reset/invalidate the undo contents related to
253  * insertion if it detects a "new" deletion.  The idea is to treat consecutive
254  * deletions as atomic from the viewpoint of undo.  A delete is 
255  * non-consecutive if it's start or end position is not coincidental with
256  * the last deletion.  One set of consecutive insertions is allowed following
257  * deletions, and will be undone by the DtEditorUndo().  Non-consecutive
258  * insertions will reset/invalidate the deletion undo buffer.
259  */
260 static void 
261 SetUndoDeletionState(
262         DtEditorWidget pPriv,
263         XmTextVerifyCallbackStruct *cb)
264 {
265     char *pDeletedText;
266
267     /* 
268      * Get the text which will be deleted from the text widget.
269      */
270     pDeletedText = (char *)_XmStringSourceGetString( 
271                                                 (XmTextWidget) M_text(pPriv), 
272                                                 cb->startPos, 
273                                                 cb->endPos, 
274                                                 False);
275
276     if( M_deletedText(pPriv) != (char *)NULL    && 
277         M_insertionLength(pPriv) == 0           &&
278         (cb->startPos == M_deletionStart(pPriv) || 
279          cb->endPos == M_deletionStart(pPriv))      
280       )
281     {
282         /*
283          * Continuation of the current deletion.  For a continuation, there
284          * must have been no intervening insertions, and we must be deleting
285          * from the same point, either forward or backward.
286          */
287         char *oldUndo = M_deletedText(pPriv);
288         M_deletedText(pPriv) = XtMalloc( strlen(M_deletedText(pPriv)) +
289                                          strlen(pDeletedText) + 1 );
290     
291         if(cb->startPos == M_deletionStart(pPriv)) {
292             /*
293              * deleting forward - deletionStart remains the same.
294              */
295             strcpy(M_deletedText(pPriv), oldUndo);
296             strcat(M_deletedText(pPriv), pDeletedText);
297         }
298         else {
299             /*
300              * deleting backward (e.g. Backspace)
301              */
302             strcpy(M_deletedText(pPriv), pDeletedText);
303             strcat(M_deletedText(pPriv), oldUndo);
304             M_deletionStart(pPriv) = cb->startPos;
305         }
306         if(oldUndo != (char *)NULL)
307             XtFree(oldUndo);
308     }
309     else 
310     {
311         /*
312          * Starting a new deletion context.  Replace the old deletion 
313          * context, and remove the insertion context.
314          */
315         _DtEditorResetUndo( pPriv );
316
317         M_deletedText(pPriv) = XtMalloc(strlen(pDeletedText) + 1);
318     
319         strcpy(M_deletedText(pPriv), pDeletedText);
320         M_deletionStart(pPriv) = cb->startPos;
321         M_insertStart(pPriv) = cb->startPos;
322         M_insertionLength(pPriv) = 0;
323     }
324
325     if( pDeletedText != (char *)NULL )
326       XtFree( pDeletedText );
327 } /* SetUndoDeletionState */
328
329 /*
330  * SetUndoInsertionState maintains the contents of editStuff.undo related
331  * to insertions (the number of characters inserted & the position of the
332  * first one).
333  */
334 static void 
335 SetUndoInsertionState(
336         DtEditorWidget pPriv,
337         XmTextVerifyCallbackStruct *cb)
338 {
339     if(M_insertionLength(pPriv) == 0) {
340         /*
341          * We've started a new deletion context, so reset the insertion
342          * context.
343          */
344         M_insertStart(pPriv) = cb->startPos;
345         M_insertionLength(pPriv) = _DtEditor_CountCharacters(cb->text->ptr,
346                                                     cb->text->length);
347     }
348     else
349     {
350         /*
351          * Determine if we're continuing the current insertion context
352          * or beginning a new one.
353          */
354         if(cb->startPos == (M_insertStart(pPriv) + M_insertionLength(pPriv)))
355             M_insertionLength(pPriv) += _DtEditor_CountCharacters(
356                                             cb->text->ptr, cb->text->length);
357         else
358         {
359             /*
360              * We're starting a new insertion context, so invalidate any
361              * existing deletion context, and reset the insertion context.
362              */
363             _DtEditorResetUndo( pPriv );
364             M_insertStart(pPriv) = cb->startPos;
365             M_insertionLength(pPriv) = _DtEditor_CountCharacters(
366                                         cb->text->ptr, cb->text->length);
367         }
368     }
369 } /* SetUndoInsertionState */
370
371
372 /************************************************************************
373  *
374  *  _DtEditorModifyVerifyCB - The modify/verify callback
375  *
376  *      The modify verify callback handles incoming data and the data which
377  *      will be replaced.  The replaced data is saved (for later undos).
378  *
379  *  Parameters: 
380  *      widget      - the text widget
381  *      client_data - the edit area widget
382  *      call_data   - callback structure
383  *
384  ************************************************************************/
385
386 /* ARGSUSED */
387 void 
388 _DtEditorModifyVerifyCB(
389         Widget w,
390         caddr_t client_data,
391         caddr_t call_data )
392 {
393   register XmTextVerifyCallbackStruct * cb = 
394                                 (XmTextVerifyCallbackStruct *) call_data;
395   DtEditorWidget editor = (DtEditorWidget) client_data;
396
397   /*
398    * Loading all new data so no need to set up the data for later undos
399    */
400   if (M_loadingAllNewData(editor) == True)
401   {
402     _DtEditorResetUndo( editor );
403     M_unreadChanges(editor) = False;
404     M_loadingAllNewData(editor) = False;
405   }
406   else
407   { 
408
409     /* 
410      * Adding additional data, rather than replacing all of the contents.
411      *
412      * Mark that the contents have been modified since the last time the
413      * application requested a copy.
414      */
415     M_unreadChanges(editor) = True;
416
417     /*
418      * First, account for any data which will be removed by the new data.
419      * If text is being deleted, then grab a copy for later undo's.
420      */
421     if(cb->endPos > cb->startPos)
422       SetUndoDeletionState(editor, cb);
423
424     /*
425      * If text is being inserted, then change the undo insertion state.
426      */
427     if(cb->text->length > 0)
428         SetUndoInsertionState(editor, cb);
429
430   } 
431
432 } /* end ModifyVerifyCB */
433