Initial import of the CDE 2.1.30 sources from the Open Group.
[oweals/cde.git] / cde / programs / dtmail / dtmail / DtEditor.C
1 /*
2  *+SNOTICE
3  *
4  *      $TOG: DtEditor.C /main/11 1998/02/03 10:28:15 mgreess $
5  *
6  *      RESTRICTED CONFIDENTIAL INFORMATION:
7  *      
8  *      The information in this document is subject to special
9  *      restrictions in a confidential disclosure agreement between
10  *      HP, IBM, Sun, USL, SCO and Univel.  Do not distribute this
11  *      document outside HP, IBM, Sun, USL, SCO, or Univel without
12  *      Sun's specific written approval.  This document and all copies
13  *      and derivative works thereof must be returned or destroyed at
14  *      Sun's request.
15  *
16  *      Copyright 1993 Sun Microsystems, Inc.  All rights reserved.
17  *
18  *+ENOTICE
19  */
20
21 #ifndef I_HAVE_NO_IDENT
22 #else
23 #endif
24
25 #ifdef DTEDITOR
26
27 #include <Xm/Xm.h>
28 #include <Xm/Form.h>
29 #include <Xm/Text.h>
30 #include <assert.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <ctype.h>
34 #include <X11/IntrinsicP.h>
35 #include <Xm/Text.h>
36 #include <Xm/CutPaste.h>
37
38 #include "Help.hh"
39 #include "RoamApp.h"
40 #include "DtEditor.hh"
41 #include "MailMsg.h"               // DT_catd defined here
42
43 CDEM_DtWidgetEditor::CDEM_DtWidgetEditor(
44     Widget parent,
45     DtMailEditor *owner_of_editor
46 )
47 {
48     my_parent   = parent;
49     my_owner    = owner_of_editor;
50     my_text     = NULL;
51     my_text_core = NULL;
52     _modified_text = NULL;
53     _modified_text_buflen = 0;
54
55     begin_ins_bracket = NULL;
56     indent_str = NULL;
57     end_ins_bracket = NULL;
58     _auto_show_cursor = FALSE;
59
60     _buffer     = NULL;
61     _buf_len    = (unsigned long) 0;
62     text_already_selected = FALSE;
63 }
64
65 CDEM_DtWidgetEditor::~CDEM_DtWidgetEditor() 
66 {
67     if (my_text) {
68         // No DtEditor API equivalent
69         // Remove the callbacks first.
70
71         XtRemoveCallback(my_text, DtNtextSelectCallback,
72             &CDEM_DtWidgetEditor::text_selected_callback, this);
73         XtRemoveCallback(my_text, DtNtextDeselectCallback,
74             &CDEM_DtWidgetEditor::text_unselected_callback, this);
75         XtRemoveCallback( my_text, XmNhelpCallback, HelpTexteditCB, this ) ;
76
77         XtDestroyWidget(my_text);
78     }
79     if (_buffer) {
80         delete _buffer;
81         _buffer = NULL;
82     }
83
84     if(_modified_text )  {
85         if(_modified_text->ptr)  {
86                 free(_modified_text->ptr);
87                 _modified_text->ptr = NULL;
88         }
89         free(_modified_text);
90         _modified_text = NULL;
91     }
92     if (NULL != indent_str)
93       free((void*) indent_str);
94 }
95
96 void
97 CDEM_DtWidgetEditor::initialize()
98 {
99     
100     int i = 0;
101
102     Arg args[10];
103     int n = 0;
104
105 #if 0
106     short rows, cols;
107     DtMailEnv  error;
108     DtMail::Session * d_session = theRoamApp.session()->session();
109     DtMail::MailRc * mailrc = d_session->mailRc(error);
110
111     const char * value = NULL;
112     mailrc->getValue(error, "popuplines", &value);
113     if (error.isSet()) {
114         value = strdup("24");
115     }
116     rows = strtol(value, NULL, 10);
117     if (NULL != value)
118       free((void*) value);
119
120     // If toolcols is set, overwrite the column width with "toolcols" value.
121     // Otherwise, default resource value will be used.
122     value = NULL;
123     mailrc->getValue(error, "toolcols", &value);
124     if (!error.isSet()){
125         cols = strtol(value, NULL, 10);      
126         XtSetArg(args[i], DtNcolumns, cols); i++;
127         if (NULL != value)
128           free((void*) value);
129     } else {
130         /*
131          * Default XmNcolumns
132          * MB_CUR_MAX == 1 : SingleByteLanguage
133          * MB_CUR_MAX >  1 : MultiByteLanguage
134          */
135         if ( MB_CUR_MAX == 1 )
136             value = "80";
137         else
138             value = "40";
139
140         cols = strtol(value, NULL, 10);      
141         XtSetArg(args[i], DtNcolumns, cols); i++;
142     }
143 #endif
144
145     XtSetArg(args[i], DtNeditable, FALSE); i++;
146     XtSetArg(args[i], DtNrows, 24); i++;
147     if ( MB_CUR_MAX == 1 ) {
148       XtSetArg(args[i], DtNcolumns, 80); i++;
149     } else {
150       XtSetArg(args[i], DtNcolumns, 40); i++;
151     }
152     XtSetArg(args[i], DtNcursorPositionVisible, FALSE); i++;
153
154     my_text = DtCreateEditor(my_parent, "Text", args, i);
155
156     update_display_from_props();
157     XtAddCallback(my_text, DtNtextSelectCallback,
158            &CDEM_DtWidgetEditor::text_selected_callback, this);
159     XtAddCallback(my_text, DtNtextDeselectCallback,
160            &CDEM_DtWidgetEditor::text_unselected_callback, this);
161     XtAddCallback( my_text, XmNhelpCallback, HelpTexteditCB, this ) ;
162
163     XtAddEventHandler(my_text, ButtonPressMask,
164                         FALSE, MenuButtonHandler, 
165                         (XtPointer) this);
166
167     XtManageChild(my_text);
168
169 }
170
171
172 char*
173 CDEM_DtWidgetEditor::get_contents()
174 {
175     
176     DtEditorErrorCode status;
177     static DtEditorContentRec content;
178
179     content.type = DtEDITOR_TEXT;
180     
181     // Get the contents with hardCarriageReturns = TRUE and
182     // markContentsAsSaved = TRUE.
183
184     /*
185      * If hardCarriageReturns = TRUE, the performace of DtEditorGetContents()
186      * suffers since according to man 3 DtEditorGetContents,
187      *
188      *    The hardCarriageReturns argument, if set to True, indicates
189      *    that the DtEditor widget should replace any soft line feeds
190      *    (word wraps) with <newline>s when saving the data.  When
191      *    hardCarriageReturns is set to False, any line wrapped
192      *    because it reaches the right edge of the window is saved as
193      *    one complete line.
194      * 
195      * And current default value of DtNwordWrap is TRUE. See dtmail/Dtmail.
196      * My temporary and non-good solution is
197      *    - Change default to False.
198      *    - If DtNwordWarp == TRUE,
199      *          call DtEditorGetContents(my_text, &content, TRUE, TRUE)
200      *      if not,
201      *          call DtEditorGetContents(my_text, &content, False, TRUE)
202      * This value can be controllable by a resource file or Format menu.
203      *
204      * Goofy ? but...................;-(
205      */
206     Arg     args[1];
207     Boolean ww;
208
209     XtSetArg( args[0], DtNwordWrap, &ww );
210     XtGetValues( my_text, args, 1 );
211     status = DtEditorGetContents(my_text, &content, ww, TRUE);
212     
213     return(content.value.string);
214 }
215
216 void
217 CDEM_DtWidgetEditor::set_contents(
218     const char *contents,
219     const unsigned long len
220 )
221 {
222     DtEditorContentRec content;
223     DtEditorErrorCode status;
224
225     this->my_owner->needBuf(&_buffer, &_buf_len, len + 1);
226     this->my_owner->stripCRLF(&_buffer, contents, len);
227
228     content.type = DtEDITOR_TEXT;
229     content.value.string = _buffer;
230     status = DtEditorSetContents(my_text, &content);
231
232 }
233
234 void
235 CDEM_DtWidgetEditor::set_contents(
236     const char *path
237 )
238 {
239    DtEditorSetContentsFromFile(my_text, (char *)path);
240 }
241
242 void    
243 CDEM_DtWidgetEditor::clear_contents()
244 {
245
246 // Doesn't work yet.  Work around with setting an empty string...
247 //    DtEditorReset(my_text);
248
249     DtEditorContentRec content;
250     DtEditorErrorCode status;
251
252     content.type = DtEDITOR_TEXT;
253     content.value.string = NULL;
254     status = DtEditorSetContents(my_text, &content);
255
256 }
257
258 void
259 CDEM_DtWidgetEditor::append_to_contents(
260     const char *contents,
261     const unsigned long len
262 )
263 {
264     DtEditorContentRec rec;
265
266     rec.type = DtEDITOR_TEXT;
267
268     if ( contents[len - 1] == 0 ) {
269         rec.value.string = (char *)contents;
270     } else {
271         this->my_owner->needBuf(&_buffer, &_buf_len, len + 1);
272         this->my_owner->stripCRLF(&_buffer, contents, len);
273         rec.value.string = _buffer;
274     }
275
276     DtEditorInsert(my_text, &rec);
277 }
278
279 void
280 CDEM_DtWidgetEditor::append_to_contents(
281     const char *path
282 )
283 {
284
285     DtEditorAppendFromFile(my_text, (char *)path);
286     
287 }
288
289 void
290 CDEM_DtWidgetEditor::append_at_cursor(
291     const char *path
292 )
293 {
294     DtEditorInsertFromFile(my_text, (char *) path);
295 }
296
297 void
298 CDEM_DtWidgetEditor::append_at_cursor(
299     const char *contents,
300     const unsigned long len
301 )
302 {
303     DtEditorContentRec rec;
304
305     rec.type = DtEDITOR_TEXT;
306
307     if ( contents[len - 1] == 0 ) {
308         rec.value.string = (char *)contents;
309     } else {
310         this->my_owner->needBuf(&_buffer, &_buf_len, len + 1);
311         this->my_owner->stripCRLF(&_buffer, contents, len);
312         rec.value.string = _buffer;
313     }
314     //DtEditorAppend(my_text, &rec);
315     // Fix for the defect 179186 05-25-95
316     // The above API will insert "contents" to the end of the buffer
317     // (appending). It should change to DtEditorInsert which insert 
318     // string to the current position (the cursor's position)
319     DtEditorInsert(my_text, &rec);
320 }
321
322 Widget
323 CDEM_DtWidgetEditor::get_text_widget()
324 {
325     // We actually need to return the text widget contained
326     // within DtEditor.  For now, just return the DtEditor.
327
328     return(my_text);
329 }
330
331 Pixel
332 CDEM_DtWidgetEditor::get_text_foreground()
333 {
334     Pixel fg;
335     
336     XtVaGetValues(my_text,
337         DtNtextForeground, &fg,
338         NULL);
339
340     return(fg);
341 }
342
343
344 // DtEditor returns the bg color of the Form widget, not the 
345 // text widget that the Form contains.
346 // This explains why the attachment pane color is that of the scroll bar...
347 // DtEditor needs to return the color of its text widget.
348 // OBTW, DtNtextBackground and DtNtextForeground don't work.  They
349 // return uninitialized values
350
351 Pixel
352 CDEM_DtWidgetEditor::get_text_background()
353 {
354     Pixel bg;
355     
356     XtVaGetValues(my_text,
357         DtNtextBackground, &bg,
358         NULL);
359     
360     return(bg);
361 }
362
363 XmFontList
364 CDEM_DtWidgetEditor::get_text_fontList()
365 {
366     XmFontList fl;
367     
368     XtVaGetValues(my_text,
369         DtNtextFontList, &fl,
370         NULL);
371
372     return(fl);
373 }
374
375 Dimension
376 CDEM_DtWidgetEditor::get_text_width()
377 {
378     Dimension wid;
379     
380     XtVaGetValues(my_text,
381         XmNwidth, &wid,
382         NULL);
383     return (wid);
384 }
385
386
387 Widget
388 CDEM_DtWidgetEditor::get_editor()
389 {
390     return(my_text);
391 }
392
393 int
394 CDEM_DtWidgetEditor::get_columns()
395 {
396   short ncolumns;
397   XtVaGetValues(my_text, DtNcolumns, &ncolumns, NULL);
398   return (int) ncolumns;
399 }
400
401
402 int
403 CDEM_DtWidgetEditor::get_rows()
404 {
405   short nrows;
406   XtVaGetValues(my_text, DtNrows, &nrows, NULL);
407   return (int) nrows;
408 }
409
410 void
411 CDEM_DtWidgetEditor::set_editable(Boolean bval)
412 {
413    XtVaSetValues(my_text,
414                 DtNeditable, bval,
415                 DtNcursorPositionVisible, bval,
416                 NULL);
417 }
418
419
420 void
421 CDEM_DtWidgetEditor::set_columns(int ncolumns)
422 {
423   XtVaSetValues(my_text, DtNcolumns, ncolumns, NULL);
424 }
425
426
427 void
428 CDEM_DtWidgetEditor::set_rows(int nrows)
429 {
430   XtVaSetValues(my_text, DtNrows, nrows, NULL);
431 }
432
433 void
434 CDEM_DtWidgetEditor::undo_edit()
435 {
436
437     DtEditorUndoEdit(my_text);
438
439 }
440
441 void
442 CDEM_DtWidgetEditor::cut_selection()
443 {
444
445     DtEditorCutToClipboard(my_text);
446
447 }
448
449 void
450 CDEM_DtWidgetEditor::copy_selection()
451 {
452
453     DtEditorCopyToClipboard(my_text);
454
455 }
456
457 void
458 CDEM_DtWidgetEditor::paste_from_clipboard()
459 {
460
461     DtEditorPasteFromClipboard(my_text);
462
463 }
464
465 void
466 CDEM_DtWidgetEditor::paste_special_from_clipboard(
467     Editor::InsertFormat format
468 )
469 {
470     int status;
471     unsigned long length, recvd;
472     char *clipboard_data;
473     Display *dpy = XtDisplayOfObject(my_text);
474     Window window = XtWindowOfObject(my_text);
475
476     do {
477         status = XmClipboardInquireLength(dpy, window, "STRING", &length);
478     } while (status == ClipboardLocked);
479
480     if (length == 0) {
481         return;
482     }
483     
484     clipboard_data = XtMalloc((unsigned)length);
485     
486     do {
487         status = XmClipboardRetrieve(
488                         dpy, window, "STRING", clipboard_data, 
489                         length,  &recvd, NULL
490                         );
491     } while (status == ClipboardLocked);
492     
493     if (status != ClipboardSuccess || recvd != length) {
494         // Couldn't get all
495         
496         XtFree(clipboard_data);
497         return;
498     }
499
500     // Now modify the data such that the necessary formatting occurs
501     // within it.  Bracketting will cause a line at the beginning and
502     // end of the data.  Indenting will prepend a ">" before each line,
503     // realigning the lines if necessary.
504     // The results are stored in _modified_text so clipboard_data can 
505     // be freed immediately after this call.
506
507     this->modifyData(clipboard_data, (unsigned) length, format);
508     XtFree(clipboard_data);
509
510     // Now copy the modified data stripped of CRLFs to a buffer.
511     // Put that buffer into the structure appropriate for DtEditor.
512
513     DtEditorContentRec rec;
514
515     rec.type = DtEDITOR_TEXT;
516     
517     // Length needs to be reset since the text now contains
518     // new characters that do the necessary formatting.
519
520     length = _modified_text->length;
521
522     if ( _modified_text->ptr[(unsigned) length - 1] == 0 ) {
523         rec.value.string = (char *)_modified_text->ptr;
524     } else {
525         this->my_owner->needBuf(
526                         &_buffer, &_buf_len, 
527                         (unsigned) length + 1
528                         );
529         this->my_owner->stripCRLF(
530                         &_buffer, _modified_text->ptr, 
531                         (unsigned) length);
532         rec.value.string = _buffer;
533     }
534     
535     DtEditorInsert(my_text, &rec);
536 }
537
538
539 void
540 CDEM_DtWidgetEditor::clear_selection()
541 {
542
543     DtEditorClearSelection(my_text);
544
545 }
546
547 void
548 CDEM_DtWidgetEditor::delete_selection()
549 {
550     DtEditorDeleteSelection(my_text);
551 }
552
553 void
554 CDEM_DtWidgetEditor::set_word_wrap(
555     Boolean bval
556 )
557 {
558    XtVaSetValues(my_text, DtNwordWrap, bval, NULL);
559 }
560
561 void
562 CDEM_DtWidgetEditor::set_to_top()
563 {
564     XtVaSetValues(my_text, 
565         DtNtopCharacter, 0, 
566         DtNcursorPosition, 0,
567         NULL);
568 }
569
570
571 void
572 CDEM_DtWidgetEditor::text_selected_callback(
573     Widget,
574     void * clientData,
575     void *
576 )
577 {
578
579     CDEM_DtWidgetEditor  *obj=(CDEM_DtWidgetEditor *) clientData;
580
581     obj->text_selected();
582
583 }
584
585 void
586 CDEM_DtWidgetEditor::text_unselected_callback(
587     Widget,
588     void * clientData,
589     void *
590 )
591 {
592
593     CDEM_DtWidgetEditor  *obj=(CDEM_DtWidgetEditor *) clientData;
594
595     obj->text_unselected();
596
597 }
598
599 void
600 CDEM_DtWidgetEditor::text_selected()
601 {
602
603     if (!text_already_selected) {
604         text_already_selected = TRUE;
605         my_owner->owner()->text_selected();
606     }
607 }
608
609 void
610 CDEM_DtWidgetEditor::text_unselected()
611 {
612
613     my_owner->owner()->text_unselected();
614     text_already_selected = FALSE;
615
616 }
617
618 void
619 CDEM_DtWidgetEditor::find_change()
620 {
621    DtEditorInvokeFindChangeDialog(my_text);
622 }
623
624 void
625 CDEM_DtWidgetEditor::spell()
626 {
627    DtEditorInvokeSpellDialog(my_text);
628 }
629
630 void
631 CDEM_DtWidgetEditor::format()
632 {
633    DtEditorInvokeFormatDialog(my_text);
634 }
635
636 void
637 CDEM_DtWidgetEditor::auto_show_cursor_off()
638 {
639 }
640
641 void
642 CDEM_DtWidgetEditor::auto_show_cursor_restore()
643 {
644 }
645
646 void
647 CDEM_DtWidgetEditor::select_all()
648 {
649    DtEditorSelectAll(my_text);
650 }
651
652 void
653 CDEM_DtWidgetEditor::set_to_bottom()
654 {
655 }
656
657 int
658 CDEM_DtWidgetEditor::no_text()
659 {
660    char *text = get_contents();   
661    int text_len = strlen(text);
662    int result = 1;
663
664    for ( int k = 0;  k < text_len;  k++ ) {
665           if ( isgraph(text[k]) ) {
666                  result = 0;
667                  break;
668           }
669    }
670
671    XtFree(text);
672    return result;
673 }
674
675 void
676 CDEM_DtWidgetEditor::disable_redisplay()
677 {
678     DtEditorDisableRedisplay(my_text);
679 }
680
681 void
682 CDEM_DtWidgetEditor::enable_redisplay()
683 {
684
685     DtEditorEnableRedisplay(my_text);
686
687 }
688
689
690
691 /*
692  * This fucntion modifies the pasted data
693  * with an indent prefix before each new line or brackets it.
694  */
695
696 void
697 CDEM_DtWidgetEditor::modifyData(
698     char *sp,  // source pointer to the insert string 
699     int length, // length does not include '\0' char
700     Editor::InsertFormat insert_format
701 )
702 {
703     if(_modified_text == NULL)
704         _modified_text = (XmTextBlockRec *)calloc(1,sizeof(XmTextBlockRec));
705
706     char *maxsp = sp  + length; // maxmimum source ptr
707
708     // Allocate memory rounded off to the nearest BUFINC
709     size_t size_req = (size_t)(((length/BUFINC)+1)*BUFINC);
710     if((_modified_text_buflen < size_req)       ||
711        ((_modified_text_buflen > CDEM_DtWidgetEditor::MAXBUFSZ) && 
712         (size_req <  CDEM_DtWidgetEditor::MAXBUFSZ))    )
713         reallocPasteBuf(size_req);
714     
715     if(_modified_text->ptr == NULL)
716             return; // No memory available
717
718     switch( insert_format) {
719       case IF_INDENTED: 
720       {
721           DtMailEnv error;
722           int ip = 0;
723           
724                 // Get the indent prefix string
725           DtMail::Session *m_session = theRoamApp.session()->session();
726           m_session->mailRc(error)->getValue(error,"indentprefix", &indent_str);
727           if (error.isSet() || NULL == indent_str) 
728             indent_str = strdup("> ");
729
730           size_t indlen = strlen(indent_str);
731
732           // Copy the src buf into dest, inserting indent before '\n'
733           while(sp < maxsp) {
734
735               // Make sure there is enough space
736               // for an indent prefix, one char and a terminating '\0'
737               if(!((ip+indlen+2) < _modified_text_buflen) ) {
738                   size_req = (size_t)((((_modified_text_buflen + 
739                       indlen+2)/BUFINC)+1)*BUFINC);
740                   reallocPasteBuf(size_req);
741                   if(_modified_text->ptr == NULL)
742                       return; // No memory available
743               }
744
745               // Copy the indent string at the beginning 
746               if(!ip) { 
747                   memcpy(_modified_text->ptr, indent_str, indlen);
748                   ip += indlen;
749               }
750
751               // Copy the next byte and check for new line
752               _modified_text->ptr[ip++] = *sp++; 
753               if(*(sp-1) == '\n') {
754                   memcpy(&_modified_text->ptr[ip], indent_str, indlen);
755                   ip += indlen;
756               }
757               
758           }
759           _modified_text->ptr[ip] = '\0'; // terminate with a null char
760           _modified_text->length = ip; // Do not include '\0' char in len
761       }
762       break;
763       case IF_BRACKETED:
764     {
765         
766         if( !begin_ins_bracket)
767             begin_ins_bracket = GETMSG(DT_catd, 1, 201,
768                 "\n------------- Begin Included Message -------------\n"); 
769         if(!end_ins_bracket) 
770             end_ins_bracket = GETMSG(DT_catd, 1, 202,
771                 "\n------------- End Included Message -------------\n"); 
772         
773         size_t begin_len = strlen(begin_ins_bracket); 
774         size_t end_len = strlen(end_ins_bracket); 
775
776         // Make sure there is enough space
777         if((size_req = length + begin_len + end_len + 1) > 
778            _modified_text_buflen) {
779             size_req = (size_t) ((((size_req)/BUFINC)+1)*BUFINC);
780             reallocPasteBuf(size_req);
781         }
782
783         if(_modified_text->ptr == NULL)
784             return;
785
786         strcpy(_modified_text->ptr, begin_ins_bracket);
787         strncat(_modified_text->ptr,sp,length);
788         strcat(_modified_text->ptr, end_ins_bracket); 
789         _modified_text->length = end_len + begin_len + length;
790     }
791       break;
792     default:
793       break;
794   }
795 }
796
797 void
798 CDEM_DtWidgetEditor::MenuButtonHandler(
799     Widget ,
800     XtPointer cd,
801     XEvent *event,
802     Boolean *)
803 {
804     CDEM_DtWidgetEditor *obj = (CDEM_DtWidgetEditor *)cd;
805
806     if(event->xany.type != ButtonPress)
807         return;
808
809     XButtonEvent *be = (XButtonEvent *)event;
810
811     if(be->button == Button3)
812         obj->my_owner->owner()->postTextPopup(event);
813 }
814
815
816 #endif