Initial import of the CDE 2.1.30 sources from the Open Group.
[oweals/cde.git] / cde / programs / dtmail / dtmail / XmTextEditor.C
1 /*
2  *+SNOTICE
3  *
4  *      $TOG: XmTextEditor.C /main/13 1998/02/03 10:29:51 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 #include <EUSCompat.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24 #include <sys/stat.h>
25
26 #if defined(NEED_MMAP_WRAPPER)
27 extern "C" {
28 #endif
29
30 #include <sys/types.h>
31 #include <sys/mman.h>
32
33 #if defined(NEED_MMAP_WRAPPER)
34 }
35 #endif
36
37 #include <Xm/Form.h>
38 #include <Xm/Text.h>
39 #include <assert.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <ctype.h>
43
44 #include "XmTextEditor.h"
45 #include <RoamApp.h>
46 #include <MailMsg.h>
47
48 XmTextEditor::XmTextEditor(
49     Widget parent,
50     DtMailEditor *owner_of_editor
51 )
52 {
53     my_parent   = parent;
54     my_owner    = owner_of_editor;
55     _w    = NULL;
56         my_text = NULL;
57
58     _buffer = NULL;
59     _buf_len = 0;
60     _modified_text = NULL;
61     _modified_text_buflen = 0;
62     begin_ins_bracket = NULL;
63     indent_str = NULL;
64     end_ins_bracket = NULL;
65     _auto_show_cursor = FALSE;
66     text_already_selected = FALSE;
67 }
68
69 XmTextEditor::~XmTextEditor() 
70 {
71     if (_buffer) {
72         delete _buffer;
73         _buffer = NULL;
74     }
75
76     if(_modified_text )  {
77         if(_modified_text->ptr)  {
78                 free(_modified_text->ptr);
79                 _modified_text->ptr = NULL;
80         }
81         free(_modified_text);
82         _modified_text = NULL;
83     }
84     if (NULL != indent_str)
85       free((void*) indent_str);
86 }
87
88 void
89 XmTextEditor::initialize()
90 {
91     Arg args[10];
92     int n = 0;
93
94 #if 0
95     short rows, cols;
96     DtMailEnv  error;
97     DtMail::Session * d_session = theRoamApp.session()->session();
98     DtMail::MailRc * mailrc = d_session->mailRc(error);
99
100     const char * value;
101     mailrc->getValue(error, "popuplines", &value);
102     if (error.isSet()) {
103         value = strdup("24");
104     }
105     rows = (short) strtol(value, NULL, 10);
106     if (NULL != value)
107       free((void*) value);
108
109     // If toolcols is set, overwrite the column width with "toolcols" value.
110     // Otherwise, default resource value will be used.
111     value = NULL;
112     mailrc->getValue(error, "toolcols", &value);
113     if (!error.isSet()){
114         cols = (short) strtol(value, NULL, 10);
115         XtSetArg(args[n], XmNcolumns, cols); n++;
116         if (NULL != value)
117           free((void*) value);
118     } else {
119         /*
120          * Default XmNcolumns
121          * MB_CUR_MAX == 1 : SingleByteLanguage
122          * MB_CUR_MAX >  1 : MultiByteLanguage
123          */
124         if ( MB_CUR_MAX == 1 )
125             value = "80";
126         else
127             value = "40";
128     }
129 #endif
130
131     XtSetArg(args[n], XmNeditable, FALSE); n++;
132     XtSetArg(args[n], XmNrows, 24); n++;
133     if ( MB_CUR_MAX == 1 ) {
134       XtSetArg(args[n], XmNcolumns, 80); n++;
135     } else {
136       XtSetArg(args[n], XmNcolumns, 40); n++;
137     }
138
139     _w = XmCreateScrolledText(my_parent, "Text", args, n );
140
141     update_display_from_props();
142     XtManageChild(_w);
143     XtAddEventHandler(XtParent(_w), ButtonPressMask,
144                         FALSE, MenuButtonHandler, 
145                         (XtPointer) this);
146 }
147
148 void
149 XmTextEditor::set_contents(
150                            const char *contents,
151                            const unsigned long len
152                            )
153 {
154     if (contents[len - 1] == 0) {
155         XmTextSetString(_w, (char *)contents);
156     }
157     else {
158         this->my_owner->needBuf(&_buffer, &_buf_len, len + 1);
159         this->my_owner->stripCRLF(&_buffer, contents, len);
160         XmTextSetString(_w, _buffer);
161     }
162 }
163
164 void
165 XmTextEditor::set_contents(const char * path)
166 {
167     loadFile(path, 0);
168 }
169
170 char*
171 XmTextEditor::get_contents()
172 {
173     return(XmTextGetString(_w));
174 }
175
176 void
177 XmTextEditor::append_to_contents(
178     const char *contents,
179     const unsigned long len
180 )
181 {
182
183     if (contents[len - 1] == 0) {
184         XmTextInsert( _w, XmTextGetLastPosition( _w ), 
185                       (char *)contents);
186     }
187     else {
188         // Not null terminated, and most likely has crlf instead of lf.
189         //
190         this->my_owner->needBuf(&_buffer, &_buf_len, len + 1);
191         this->my_owner->stripCRLF(&_buffer, contents, len);
192         XmTextInsert(_w, XmTextGetLastPosition(_w), _buffer);
193     }
194 }
195
196 void
197 XmTextEditor::append_to_contents(const char * path)
198 {
199     loadFile(path, (const int) XmTextGetLastPosition(_w));
200 }
201
202 void
203 XmTextEditor::append_at_cursor(
204                                  const char *contents,
205                                  const unsigned long len
206                                  )
207 {
208     if (contents[len - 1] == 0) {
209         XmTextInsert(
210                 _w, 
211                 XmTextGetInsertionPosition(_w), 
212                 (char *)contents
213                 );
214     }
215     else {
216         // Not null terminated, and most likely has crlf instead of lf.
217         //
218         this->my_owner->needBuf(&_buffer, &_buf_len, len + 1);
219         this->my_owner->stripCRLF(&_buffer, contents, len);
220         XmTextInsert(
221                 _w, 
222                 XmTextGetInsertionPosition(_w), 
223                 _buffer);
224     }
225 }
226
227 void
228 XmTextEditor::append_at_cursor(const char * path)
229 {
230     loadFile(path, (const int)XmTextGetInsertionPosition(_w));
231 }
232
233 void    
234 XmTextEditor::clear_contents()
235 {
236
237     XmTextSetString(_w, "");
238
239 }
240
241 Widget
242 XmTextEditor::get_text_widget()
243 {
244     return _w;
245 }
246
247 Pixel
248 XmTextEditor::get_text_foreground()
249 {
250     Pixel fg;
251     
252     XtVaGetValues(_w,
253         XmNforeground, &fg,
254         NULL);
255     return(fg);
256 }
257
258
259 Pixel
260 XmTextEditor::get_text_background()
261 {
262     Pixel bg;
263     
264     XtVaGetValues(_w,
265         XmNbackground, &bg,
266         NULL);
267     return(bg);
268 }
269
270 XmFontList
271 XmTextEditor::get_text_fontList()
272 {
273     XmFontList fl;
274     
275     XtVaGetValues(_w,
276         XmNfontList, &fl,
277         NULL);
278     return(fl);
279 }
280
281 Dimension
282 XmTextEditor::get_text_width()
283 {
284     Dimension wid;
285     
286     XtVaGetValues(_w,
287         XmNwidth, &wid,
288         NULL);
289     return (wid);
290 }
291
292 Widget
293 XmTextEditor::get_editor()
294 {
295     return XtParent(_w);
296 }
297
298 void
299 XmTextEditor::set_editable(
300         Boolean bval
301 )
302 {
303   XmTextSetEditable( _w, bval);
304
305   // If not editable, turn off the cursor?
306   XtVaSetValues(_w, XmNcursorPositionVisible, bval, NULL);
307 }
308
309
310 int
311 XmTextEditor::get_columns()
312 {
313   short ncolumns = 0;;
314   XtVaGetValues(_w, XmNcolumns, &ncolumns, NULL);
315   return ncolumns;
316 }
317
318
319 int
320 XmTextEditor::get_rows()
321 {
322   short nrows = 0;
323   XtVaGetValues(_w, XmNrows, &nrows, NULL);
324   return nrows;
325 }
326
327 void
328 XmTextEditor::set_columns(int ncolumns)
329 {
330   XtVaSetValues(_w, XmNcolumns, ncolumns, NULL);
331 }
332
333
334 void
335 XmTextEditor::set_rows(int nrows)
336 {
337   XtVaSetValues(_w, XmNrows, nrows, NULL);
338 }
339
340
341 // TOGO void
342 // TOGO XmTextEditor::set_container(
343 // TOGO    CMContainer
344 // TOGO )
345 // TOGO {
346 // TOGO    // Extract text and display?
347 // TOGO }
348
349 // TOGO CMContainer
350 // TOGO XmTextEditor::get_container()
351 // TOGO {
352 // TOGO    return(NULL);    
353 // TOGO }
354
355 void
356 XmTextEditor::auto_show_cursor_off()
357 {
358     // Get the original value of XmNautoShowCursorPosition
359     XtVaGetValues(_w,
360                   XmNautoShowCursorPosition, &_auto_show_cursor,
361                   NULL);
362
363     // Set it to false so we don't scroll with the cursor.
364     XtVaSetValues(_w,
365                   XmNautoShowCursorPosition, FALSE,
366                   NULL);
367 }
368
369 void
370 XmTextEditor::auto_show_cursor_restore()
371 {
372     // Restore the original value of XmNautoShowCursorPosition.
373     XtVaSetValues(_w,
374                   XmNautoShowCursorPosition, _auto_show_cursor,
375                   NULL);
376 }
377
378 void
379 XmTextEditor::set_to_top()
380 {
381
382     XmTextShowPosition(_w, 0);
383     XmTextSetInsertionPosition(_w, 0);
384
385 }
386
387 void
388 XmTextEditor::set_to_bottom()
389 {
390         XmTextSetInsertionPosition( _w, XmTextGetLastPosition(_w) );
391 }
392
393 #ifdef DEAD_WOOD
394 void
395 XmTextEditor::focus_callback(
396     Widget,
397     void *clientData,
398     void *
399 )
400 {
401  
402     XmTextEditor  *obj=(XmTextEditor *) clientData;
403
404     obj->obtained_focus();
405
406 }
407 #endif /* DEAD_WOOD */
408
409 void
410 XmTextEditor::obtained_focus()
411 {
412     // If there is text already selected, then the highlighted
413     // text is unhighlighted when the widget gets the focus
414     // next.  Need to disable the parent's menu items now.
415
416     if (XmTextGetSelection(_w) != NULL) {
417         text_already_selected = TRUE;
418     }
419
420     if (text_already_selected)
421         this->text_unselected();
422 }
423
424 void
425 XmTextEditor::text_selected_callback(
426     Widget,
427     void *clientData,
428     void *
429 )
430 {
431
432     XmTextEditor  *obj=(XmTextEditor *) clientData;
433
434     obj->text_selected();
435
436 }
437
438 void
439 XmTextEditor::text_unselected_callback(
440     Widget,
441     void *,
442     void *
443 )
444 {
445
446 //    XmTextEditor  *obj=(XmTextEditor *) clientData;
447
448 //    obj->text_unselected();
449
450 }
451
452 void
453 XmTextEditor::text_selected()
454 {
455     if (!text_already_selected) {
456         text_already_selected = TRUE;
457         my_owner->owner()->text_selected();
458     }
459 }
460
461 void
462 XmTextEditor::text_unselected()
463 {
464     text_already_selected = FALSE;
465     my_owner->owner()->text_unselected();
466 }
467
468 int
469 XmTextEditor::no_text()
470 {
471         char *text = get_contents();;
472         int text_len = strlen(text);
473         int result = 1;
474
475         for ( int k = 0;  k < text_len;  k++ ) {
476            if ( isgraph(text[k]) ) {
477                   result = 0;
478                   break;
479            }
480         }
481         XtFree(text);
482         return result;
483 }
484
485 void
486 XmTextEditor::undo_edit()
487 {
488         // This is to be consistent with DtEditor.
489     // Do nothing since Motif XmText does not support this.
490 }
491
492 void
493 XmTextEditor::set_word_wrap(Boolean)
494 {
495         // This is to be consistent with DtEditor.
496     // Do nothing since Motif XmText does not support this.
497 }
498
499 void
500 XmTextEditor::find_change()
501 {
502         // This is to be consistent with DtEditor.
503     // Do nothing since Motif XmText does not support this.
504 }
505
506 void
507 XmTextEditor::spell()
508 {
509         // This is to be consistent with DtEditor.
510     // Do nothing since Motif XmText does not support this.
511 }
512
513 void
514 XmTextEditor::format()
515 {
516         // This is to be consistent with DtEditor.
517     // Do nothing since Motif XmText does not support this.
518 }
519
520 void
521 XmTextEditor::cut_selection()
522 {
523         // Shouldn't really use CurrentTime
524         XmTextCut( _w, CurrentTime );
525 }
526
527 void
528 XmTextEditor::copy_selection()
529 {
530         // Shouldn't really use CurrentTime
531         XmTextCopy( _w, CurrentTime );
532 }
533
534 void
535 XmTextEditor::paste_from_clipboard()
536 {
537         XmTextPaste( _w );
538 }
539
540 void
541 XmTextEditor::paste_special_from_clipboard(Editor::InsertFormat format)
542 {
543         PSClientData cd;
544
545         cd.obj = this;
546         cd.insert_format = format;
547
548         XtAddCallback(_w, XmNmodifyVerifyCallback, 
549                         modify_verify_callback, (XtPointer)&cd);
550
551         XmTextPaste( _w );
552
553         XtRemoveCallback(_w, XmNmodifyVerifyCallback, 
554                         modify_verify_callback, (XtPointer)&cd);
555 }
556
557
558 // Removes the selection and leaves the resulting white space.
559 void
560 XmTextEditor::clear_selection()
561 {
562         // Shouldn't really use CurrentTime
563         // XmTextClearSelection( _w, CurrentTime );
564
565         // BUG in XmTextClearSelection.  Selection is not cleared.  Only the cursor
566         // is advanced to the last selected position.  Therefore need the following
567         // workaround.
568
569         XmTextPosition left, right;
570
571         if ( XmTextGetSelectionPosition( _w, &left, &right ) ) {
572            char *sel = XmTextGetSelection( _w );
573            // NOTE:  Modifying buffer returned by XmTextGetSelection.
574            // Future Motif implementation might return read only buffer.
575            if (sel != NULL) {
576                 memset( sel, ' ', strlen(sel) );
577                 // XmTextInsert( _w, left, sel );
578                 XmTextReplace( _w, left, right, sel );
579                 XtFree(sel);
580            }
581         }
582 }
583
584 // Removes the selection and compresses the resulting white space.
585 void
586 XmTextEditor::delete_selection()
587 {
588         XmTextRemove( _w );
589 }
590
591 void
592 XmTextEditor::select_all()
593 {
594         XmTextSetSelection( _w, 0, XmTextGetLastPosition(_w), CurrentTime );
595 }
596
597 void
598 XmTextEditor::disable_redisplay(void)
599 {
600     XmTextDisableRedisplay(_w);
601 }
602
603 void
604 XmTextEditor::enable_redisplay(void)
605 {
606     XmTextEnableRedisplay(_w);
607 }
608
609 void
610 XmTextEditor::loadFile(const char * path, const int pos)
611 {
612     int fd = open(path, O_RDONLY);
613     if (fd < 0) {
614         return;
615     }
616
617     struct stat info;
618     if (fstat(fd, &info) < 0) {
619         close(fd);
620         return;
621     }
622
623     int page_size = (int)sysconf(_SC_PAGESIZE);
624     size_t map_size = (size_t)(info.st_size + 
625                                 (page_size - (info.st_size % page_size)));
626     char * map;
627
628 #if defined(__osf__)
629     // Need the (char *) for compile to work in ALL cases ... for the
630     // POSIX "mmap" is (void *).  Also, this version of mmap does NOT
631     // allow requested length to be greater than the file size ...
632     // in contradiction to the documentation (don't round up).
633     map = (char *) mmap(0, info.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
634 #else
635     map = (char *) mmap(0, map_size, PROT_READ, MAP_PRIVATE, fd, 0);
636 #endif
637
638     if (map == (char *)-1) {
639         // We could not map it for some reason. Let's just read it into
640         // _buffer and pass it to XmText.
641         //
642         this->my_owner->needBuf(&_buffer, &_buf_len, info.st_size + 1);
643         if (read(fd, _buffer, (unsigned int) info.st_size) < 0) {
644             close(fd);
645             return;
646         }
647         _buffer[info.st_size] = 0;
648         XmTextInsert(_w, pos, _buffer);
649     }
650     else {
651         // We now have a mapped file. XmText wants a zero terminated
652         // buffer. We get luck with mmap because unless the file is
653         // an even page size, we will have some zero fill bytes that
654         // are legal to access.
655         //
656         // Of course in the case of an even page size file we must
657         // copy the buffer, terminate it and then give it to XmText.
658         //
659         if (info.st_size < map_size) {
660             XmTextInsert(_w, pos, map);
661         }
662         else {
663             this->my_owner->needBuf(&_buffer, &_buf_len, info.st_size + 1);
664             this->my_owner->stripCRLF(&_buffer, map, info.st_size);
665             XmTextInsert(_w, pos, _buffer);
666         }
667         munmap(map, map_size);
668     }
669
670     close(fd);
671 }
672
673 void 
674 XmTextEditor::modify_verify_callback(
675         Widget , XtPointer client, XtPointer call_data)
676 {
677         PSClientData *cd = (PSClientData *)client;
678         XmTextVerifyCallbackStruct *modify_info = 
679                         (XmTextVerifyCallbackStruct *)call_data;
680
681         // Make sure we are being called programmatically
682         if(modify_info->event != (XEvent *)NULL)
683                 return;
684
685         cd->obj->modifyPasteData(modify_info, cd->insert_format);
686 }
687
688 /*
689  * This fucntion modifies the pasted data
690  * with an indent prefix before each new line or brackets it.
691  */
692
693 void
694 XmTextEditor::modifyPasteData(
695         XmTextVerifyCallbackStruct *modify_info,
696         Editor::InsertFormat insert_format)
697 {
698         // The toolkit does not use this any more, this must be weird stuff.
699         if(modify_info->text->format == XmFMT_16_BIT)
700                 return;
701
702         if(_modified_text == NULL)
703                 _modified_text = (XmTextBlockRec *)calloc(1,sizeof(XmTextBlockRec));
704
705         char *sp = modify_info->text->ptr; // source pointer to the insert string 
706         int length = modify_info->text->length; // length does not include '\0' char
707         char *maxsp = sp  + length; // maxmimum source ptr
708
709         // Allocate memory rounded off to the nearest BUFINC
710         size_t size_req = (size_t)(((length/BUFINC)+1)*BUFINC);
711         if((_modified_text_buflen < size_req)   ||
712                   ((_modified_text_buflen > XmTextEditor::MAXBUFSZ) && 
713                         (size_req <  XmTextEditor::MAXBUFSZ))   )
714                 reallocPasteBuf(size_req);
715
716         if(_modified_text->ptr == NULL)
717                 return; // No memory available
718
719         switch( insert_format) {
720         case IF_INDENTED:       
721                 {
722                 DtMailEnv error;
723                 int ip = 0;
724
725                 // Get the indent prefix string
726                 DtMail::Session *m_session = 
727                                 theRoamApp.session()->session();
728                 m_session->mailRc(error)->getValue(error, 
729                                 "indentprefix", &indent_str);
730                 if ( error.isSet() || !indent_str) 
731                         indent_str = strdup("> ");
732
733                 size_t indlen = strlen(indent_str);
734
735                 // Copy the src buf into dest, inserting indent before '\n'
736                 while(sp < maxsp) {
737
738                         // Make sure there is enough space
739                         // for an indent prefix, one char and a terminating '\0'
740                         if(!((ip+indlen+2) < _modified_text_buflen) ) {
741                                 size_req = (size_t)((((_modified_text_buflen + 
742                                                 indlen+2)/BUFINC)+1)*BUFINC);
743                                 reallocPasteBuf(size_req);
744                                 if(_modified_text->ptr == NULL)
745                                         return; // No memory available
746                         }
747
748                         // Copy the indent string at the beginning 
749                         if(!ip) { 
750                                 memcpy(_modified_text->ptr, indent_str, indlen);
751                                 ip += indlen;
752                         }
753
754                         // Copy the next byte and check for new line
755                         _modified_text->ptr[ip++] = *sp++; 
756                         if(*(sp-1) == '\n') {
757                                 memcpy(&_modified_text->ptr[ip], indent_str, indlen);
758                                 ip += indlen;
759                         }
760
761                 }
762                 _modified_text->ptr[ip] = '\0'; // terminate with a null char
763                 _modified_text->length = ip; // Do not include '\0' char in len
764                 }
765                 break;
766         case IF_BRACKETED:
767                 {
768
769                 if( !begin_ins_bracket)
770                         begin_ins_bracket = GETMSG(DT_catd, 1, 199,
771                                 "\n------------- Begin Included Message -------------\n"); 
772                 if(!end_ins_bracket) 
773                         end_ins_bracket = GETMSG(DT_catd, 1, 200,
774                                 "\n------------- End Included Message -------------\n"); 
775                 
776                 size_t begin_len = strlen(begin_ins_bracket); 
777                 size_t end_len = strlen(end_ins_bracket); 
778
779                 // Make sure there is enough space
780                 if((size_req = length + begin_len + end_len + 1) > 
781                                         _modified_text_buflen) {
782                         size_req = (size_t) ((((size_req)/BUFINC)+1)*BUFINC);
783                         reallocPasteBuf(size_req);
784                 }
785
786                 if(_modified_text->ptr == NULL)
787                         return;
788
789                 strcpy(_modified_text->ptr, begin_ins_bracket);
790                 strncat(_modified_text->ptr,sp,length);
791                 strcat(_modified_text->ptr, end_ins_bracket); 
792                 _modified_text->length = end_len + begin_len + length;
793                 }
794                 break;
795         default:
796                 break;
797         }
798                         
799         _modified_text->format = modify_info->text->format;
800
801         // Stuff the modified text block ptr in the return call data
802         modify_info->text = _modified_text;
803 }
804
805 void
806 XmTextEditor::MenuButtonHandler(
807     Widget ,
808     XtPointer cd,
809     XEvent *event,
810     Boolean *)
811 {
812         XmTextEditor *obj = (XmTextEditor *)cd;
813
814         if(event->xany.type != ButtonPress)
815                 return;
816
817         XButtonEvent *be = (XButtonEvent *)event;
818
819         if(be->button == Button3)
820                 obj->my_owner->owner()->postTextPopup(event);
821 }
822