cdesktopenv/cde/programs/dtmail/dtmail/XmTextEditor.C

837 lines
17 KiB
C

/*
* CDE - Common Desktop Environment
*
* Copyright (c) 1993-2012, The Open Group. All rights reserved.
*
* These libraries and programs are free software; you can
* redistribute them and/or modify them under the terms of the GNU
* Lesser General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* These libraries and programs are distributed in the hope that
* they will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with these libraries and programs; if not, write
* to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301 USA
*/
/*
*+SNOTICE
*
* $TOG: XmTextEditor.C /main/13 1998/02/03 10:29:51 mgreess $
*
* RESTRICTED CONFIDENTIAL INFORMATION:
*
* The information in this document is subject to special
* restrictions in a confidential disclosure agreement between
* HP, IBM, Sun, USL, SCO and Univel. Do not distribute this
* document outside HP, IBM, Sun, USL, SCO, or Univel without
* Sun's specific written approval. This document and all copies
* and derivative works thereof must be returned or destroyed at
* Sun's request.
*
* Copyright 1993 Sun Microsystems, Inc. All rights reserved.
*
*+ENOTICE
*/
#include <EUSCompat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#if defined(NEED_MMAP_WRAPPER)
extern "C" {
#endif
#include <sys/types.h>
#include <sys/mman.h>
#if defined(NEED_MMAP_WRAPPER)
}
#endif
#include <Xm/Form.h>
#include <Xm/Text.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "XmTextEditor.h"
#include <RoamApp.h>
#include <MailMsg.h>
XmTextEditor::XmTextEditor(
Widget parent,
DtMailEditor *owner_of_editor
)
{
my_parent = parent;
my_owner = owner_of_editor;
_w = NULL;
my_text = NULL;
_buffer = NULL;
_buf_len = 0;
_modified_text = NULL;
_modified_text_buflen = 0;
begin_ins_bracket = NULL;
indent_str = NULL;
end_ins_bracket = NULL;
_auto_show_cursor = FALSE;
text_already_selected = FALSE;
}
XmTextEditor::~XmTextEditor()
{
if (_buffer) {
delete _buffer;
_buffer = NULL;
}
if(_modified_text ) {
if(_modified_text->ptr) {
free(_modified_text->ptr);
_modified_text->ptr = NULL;
}
free(_modified_text);
_modified_text = NULL;
}
if (NULL != indent_str)
free((void*) indent_str);
}
void
XmTextEditor::initialize()
{
Arg args[10];
int n = 0;
#if 0
short rows, cols;
DtMailEnv error;
DtMail::Session * d_session = theRoamApp.session()->session();
DtMail::MailRc * mailrc = d_session->mailRc(error);
const char * value;
mailrc->getValue(error, "popuplines", &value);
if (error.isSet()) {
value = strdup("24");
}
rows = (short) strtol(value, NULL, 10);
if (NULL != value)
free((void*) value);
// If toolcols is set, overwrite the column width with "toolcols" value.
// Otherwise, default resource value will be used.
value = NULL;
mailrc->getValue(error, "toolcols", &value);
if (!error.isSet()){
cols = (short) strtol(value, NULL, 10);
XtSetArg(args[n], XmNcolumns, cols); n++;
if (NULL != value)
free((void*) value);
} else {
/*
* Default XmNcolumns
* MB_CUR_MAX == 1 : SingleByteLanguage
* MB_CUR_MAX > 1 : MultiByteLanguage
*/
if ( MB_CUR_MAX == 1 )
value = "80";
else
value = "40";
}
#endif
XtSetArg(args[n], XmNeditable, FALSE); n++;
XtSetArg(args[n], XmNrows, 24); n++;
if ( MB_CUR_MAX == 1 ) {
XtSetArg(args[n], XmNcolumns, 80); n++;
} else {
XtSetArg(args[n], XmNcolumns, 40); n++;
}
_w = XmCreateScrolledText(my_parent, "Text", args, n );
update_display_from_props();
XtManageChild(_w);
XtAddEventHandler(XtParent(_w), ButtonPressMask,
FALSE, MenuButtonHandler,
(XtPointer) this);
}
void
XmTextEditor::set_contents(
const char *contents,
const unsigned long len
)
{
if (contents[len - 1] == 0) {
XmTextSetString(_w, (char *)contents);
}
else {
this->my_owner->needBuf(&_buffer, &_buf_len, len + 1);
this->my_owner->stripCRLF(&_buffer, contents, len);
XmTextSetString(_w, _buffer);
}
}
void
XmTextEditor::set_contents(const char * path)
{
loadFile(path, 0);
}
char*
XmTextEditor::get_contents()
{
return(XmTextGetString(_w));
}
void
XmTextEditor::append_to_contents(
const char *contents,
const unsigned long len
)
{
if (contents[len - 1] == 0) {
XmTextInsert( _w, XmTextGetLastPosition( _w ),
(char *)contents);
}
else {
// Not null terminated, and most likely has crlf instead of lf.
//
this->my_owner->needBuf(&_buffer, &_buf_len, len + 1);
this->my_owner->stripCRLF(&_buffer, contents, len);
XmTextInsert(_w, XmTextGetLastPosition(_w), _buffer);
}
}
void
XmTextEditor::append_to_contents(const char * path)
{
loadFile(path, (const int) XmTextGetLastPosition(_w));
}
void
XmTextEditor::append_at_cursor(
const char *contents,
const unsigned long len
)
{
if (contents[len - 1] == 0) {
XmTextInsert(
_w,
XmTextGetInsertionPosition(_w),
(char *)contents
);
}
else {
// Not null terminated, and most likely has crlf instead of lf.
//
this->my_owner->needBuf(&_buffer, &_buf_len, len + 1);
this->my_owner->stripCRLF(&_buffer, contents, len);
XmTextInsert(
_w,
XmTextGetInsertionPosition(_w),
_buffer);
}
}
void
XmTextEditor::append_at_cursor(const char * path)
{
loadFile(path, (const int)XmTextGetInsertionPosition(_w));
}
void
XmTextEditor::clear_contents()
{
XmTextSetString(_w, "");
}
Widget
XmTextEditor::get_text_widget()
{
return _w;
}
Pixel
XmTextEditor::get_text_foreground()
{
Pixel fg;
XtVaGetValues(_w,
XmNforeground, &fg,
NULL);
return(fg);
}
Pixel
XmTextEditor::get_text_background()
{
Pixel bg;
XtVaGetValues(_w,
XmNbackground, &bg,
NULL);
return(bg);
}
XmFontList
XmTextEditor::get_text_fontList()
{
XmFontList fl;
XtVaGetValues(_w,
XmNfontList, &fl,
NULL);
return(fl);
}
Dimension
XmTextEditor::get_text_width()
{
Dimension wid;
XtVaGetValues(_w,
XmNwidth, &wid,
NULL);
return (wid);
}
Widget
XmTextEditor::get_editor()
{
return XtParent(_w);
}
void
XmTextEditor::set_editable(
Boolean bval
)
{
XmTextSetEditable( _w, bval);
// If not editable, turn off the cursor?
XtVaSetValues(_w, XmNcursorPositionVisible, bval, NULL);
}
int
XmTextEditor::get_columns()
{
short ncolumns = 0;;
XtVaGetValues(_w, XmNcolumns, &ncolumns, NULL);
return ncolumns;
}
int
XmTextEditor::get_rows()
{
short nrows = 0;
XtVaGetValues(_w, XmNrows, &nrows, NULL);
return nrows;
}
void
XmTextEditor::set_columns(int ncolumns)
{
XtVaSetValues(_w, XmNcolumns, ncolumns, NULL);
}
void
XmTextEditor::set_rows(int nrows)
{
XtVaSetValues(_w, XmNrows, nrows, NULL);
}
// TOGO void
// TOGO XmTextEditor::set_container(
// TOGO CMContainer
// TOGO )
// TOGO {
// TOGO // Extract text and display?
// TOGO }
// TOGO CMContainer
// TOGO XmTextEditor::get_container()
// TOGO {
// TOGO return(NULL);
// TOGO }
void
XmTextEditor::auto_show_cursor_off()
{
// Get the original value of XmNautoShowCursorPosition
XtVaGetValues(_w,
XmNautoShowCursorPosition, &_auto_show_cursor,
NULL);
// Set it to false so we don't scroll with the cursor.
XtVaSetValues(_w,
XmNautoShowCursorPosition, FALSE,
NULL);
}
void
XmTextEditor::auto_show_cursor_restore()
{
// Restore the original value of XmNautoShowCursorPosition.
XtVaSetValues(_w,
XmNautoShowCursorPosition, _auto_show_cursor,
NULL);
}
void
XmTextEditor::set_to_top()
{
XmTextShowPosition(_w, 0);
XmTextSetInsertionPosition(_w, 0);
}
void
XmTextEditor::set_to_bottom()
{
XmTextSetInsertionPosition( _w, XmTextGetLastPosition(_w) );
}
#ifdef DEAD_WOOD
void
XmTextEditor::focus_callback(
Widget,
void *clientData,
void *
)
{
XmTextEditor *obj=(XmTextEditor *) clientData;
obj->obtained_focus();
}
#endif /* DEAD_WOOD */
void
XmTextEditor::obtained_focus()
{
// If there is text already selected, then the highlighted
// text is unhighlighted when the widget gets the focus
// next. Need to disable the parent's menu items now.
if (XmTextGetSelection(_w) != NULL) {
text_already_selected = TRUE;
}
if (text_already_selected)
this->text_unselected();
}
void
XmTextEditor::text_selected_callback(
Widget,
void *clientData,
void *
)
{
XmTextEditor *obj=(XmTextEditor *) clientData;
obj->text_selected();
}
void
XmTextEditor::text_unselected_callback(
Widget,
void *,
void *
)
{
// XmTextEditor *obj=(XmTextEditor *) clientData;
// obj->text_unselected();
}
void
XmTextEditor::text_selected()
{
if (!text_already_selected) {
text_already_selected = TRUE;
my_owner->owner()->text_selected();
}
}
void
XmTextEditor::text_unselected()
{
text_already_selected = FALSE;
my_owner->owner()->text_unselected();
}
int
XmTextEditor::no_text()
{
char *text = get_contents();;
int text_len = strlen(text);
int result = 1;
for ( int k = 0; k < text_len; k++ ) {
if ( isgraph(text[k]) ) {
result = 0;
break;
}
}
XtFree(text);
return result;
}
void
XmTextEditor::undo_edit()
{
// This is to be consistent with DtEditor.
// Do nothing since Motif XmText does not support this.
}
void
XmTextEditor::set_word_wrap(Boolean)
{
// This is to be consistent with DtEditor.
// Do nothing since Motif XmText does not support this.
}
void
XmTextEditor::find_change()
{
// This is to be consistent with DtEditor.
// Do nothing since Motif XmText does not support this.
}
void
XmTextEditor::spell()
{
// This is to be consistent with DtEditor.
// Do nothing since Motif XmText does not support this.
}
void
XmTextEditor::format()
{
// This is to be consistent with DtEditor.
// Do nothing since Motif XmText does not support this.
}
void
XmTextEditor::cut_selection()
{
// Shouldn't really use CurrentTime
XmTextCut( _w, CurrentTime );
}
void
XmTextEditor::copy_selection()
{
// Shouldn't really use CurrentTime
XmTextCopy( _w, CurrentTime );
}
void
XmTextEditor::paste_from_clipboard()
{
XmTextPaste( _w );
}
void
XmTextEditor::paste_special_from_clipboard(Editor::InsertFormat format)
{
PSClientData cd;
cd.obj = this;
cd.insert_format = format;
XtAddCallback(_w, XmNmodifyVerifyCallback,
modify_verify_callback, (XtPointer)&cd);
XmTextPaste( _w );
XtRemoveCallback(_w, XmNmodifyVerifyCallback,
modify_verify_callback, (XtPointer)&cd);
}
// Removes the selection and leaves the resulting white space.
void
XmTextEditor::clear_selection()
{
// Shouldn't really use CurrentTime
// XmTextClearSelection( _w, CurrentTime );
// BUG in XmTextClearSelection. Selection is not cleared. Only the cursor
// is advanced to the last selected position. Therefore need the following
// workaround.
XmTextPosition left, right;
if ( XmTextGetSelectionPosition( _w, &left, &right ) ) {
char *sel = XmTextGetSelection( _w );
// NOTE: Modifying buffer returned by XmTextGetSelection.
// Future Motif implementation might return read only buffer.
if (sel != NULL) {
memset( sel, ' ', strlen(sel) );
// XmTextInsert( _w, left, sel );
XmTextReplace( _w, left, right, sel );
XtFree(sel);
}
}
}
// Removes the selection and compresses the resulting white space.
void
XmTextEditor::delete_selection()
{
XmTextRemove( _w );
}
void
XmTextEditor::select_all()
{
XmTextSetSelection( _w, 0, XmTextGetLastPosition(_w), CurrentTime );
}
void
XmTextEditor::disable_redisplay(void)
{
XmTextDisableRedisplay(_w);
}
void
XmTextEditor::enable_redisplay(void)
{
XmTextEnableRedisplay(_w);
}
void
XmTextEditor::loadFile(const char * path, const int pos)
{
int fd = open(path, O_RDONLY);
if (fd < 0) {
return;
}
struct stat info;
if (fstat(fd, &info) < 0) {
close(fd);
return;
}
int page_size = (int)sysconf(_SC_PAGESIZE);
size_t map_size = (size_t)(info.st_size +
(page_size - (info.st_size % page_size)));
char * map;
map = (char *) mmap(0, map_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map == (char *)-1) {
// We could not map it for some reason. Let's just read it into
// _buffer and pass it to XmText.
//
this->my_owner->needBuf(&_buffer, &_buf_len, info.st_size + 1);
if (read(fd, _buffer, (unsigned int) info.st_size) < 0) {
close(fd);
return;
}
_buffer[info.st_size] = 0;
XmTextInsert(_w, pos, _buffer);
}
else {
// We now have a mapped file. XmText wants a zero terminated
// buffer. We get luck with mmap because unless the file is
// an even page size, we will have some zero fill bytes that
// are legal to access.
//
// Of course in the case of an even page size file we must
// copy the buffer, terminate it and then give it to XmText.
//
if (info.st_size < map_size) {
XmTextInsert(_w, pos, map);
}
else {
this->my_owner->needBuf(&_buffer, &_buf_len, info.st_size + 1);
this->my_owner->stripCRLF(&_buffer, map, info.st_size);
XmTextInsert(_w, pos, _buffer);
}
munmap(map, map_size);
}
close(fd);
}
void
XmTextEditor::modify_verify_callback(
Widget , XtPointer client, XtPointer call_data)
{
PSClientData *cd = (PSClientData *)client;
XmTextVerifyCallbackStruct *modify_info =
(XmTextVerifyCallbackStruct *)call_data;
// Make sure we are being called programmatically
if(modify_info->event != (XEvent *)NULL)
return;
cd->obj->modifyPasteData(modify_info, cd->insert_format);
}
/*
* This fucntion modifies the pasted data
* with an indent prefix before each new line or brackets it.
*/
void
XmTextEditor::modifyPasteData(
XmTextVerifyCallbackStruct *modify_info,
Editor::InsertFormat insert_format)
{
// The toolkit does not use this any more, this must be weird stuff.
if(modify_info->text->format == XmFMT_16_BIT)
return;
if(_modified_text == NULL)
_modified_text = (XmTextBlockRec *)calloc(1,sizeof(XmTextBlockRec));
char *sp = modify_info->text->ptr; // source pointer to the insert string
int length = modify_info->text->length; // length does not include '\0' char
char *maxsp = sp + length; // maxmimum source ptr
// Allocate memory rounded off to the nearest BUFINC
size_t size_req = (size_t)(((length/BUFINC)+1)*BUFINC);
if((_modified_text_buflen < size_req) ||
((_modified_text_buflen > XmTextEditor::MAXBUFSZ) &&
(size_req < XmTextEditor::MAXBUFSZ)) )
reallocPasteBuf(size_req);
if(_modified_text->ptr == NULL)
return; // No memory available
switch( insert_format) {
case IF_INDENTED:
{
DtMailEnv error;
int ip = 0;
// Get the indent prefix string
DtMail::Session *m_session =
theRoamApp.session()->session();
m_session->mailRc(error)->getValue(error,
"indentprefix", &indent_str);
if ( error.isSet() || !indent_str)
indent_str = strdup("> ");
size_t indlen = strlen(indent_str);
// Copy the src buf into dest, inserting indent before '\n'
while(sp < maxsp) {
// Make sure there is enough space
// for an indent prefix, one char and a terminating '\0'
if(!((ip+indlen+2) < _modified_text_buflen) ) {
size_req = (size_t)((((_modified_text_buflen +
indlen+2)/BUFINC)+1)*BUFINC);
reallocPasteBuf(size_req);
if(_modified_text->ptr == NULL)
return; // No memory available
}
// Copy the indent string at the beginning
if(!ip) {
memcpy(_modified_text->ptr, indent_str, indlen);
ip += indlen;
}
// Copy the next byte and check for new line
_modified_text->ptr[ip++] = *sp++;
if(*(sp-1) == '\n') {
memcpy(&_modified_text->ptr[ip], indent_str, indlen);
ip += indlen;
}
}
_modified_text->ptr[ip] = '\0'; // terminate with a null char
_modified_text->length = ip; // Do not include '\0' char in len
}
break;
case IF_BRACKETED:
{
if( !begin_ins_bracket)
begin_ins_bracket = GETMSG(DT_catd, 1, 199,
"\n------------- Begin Included Message -------------\n");
if(!end_ins_bracket)
end_ins_bracket = GETMSG(DT_catd, 1, 200,
"\n------------- End Included Message -------------\n");
size_t begin_len = strlen(begin_ins_bracket);
size_t end_len = strlen(end_ins_bracket);
// Make sure there is enough space
if((size_req = length + begin_len + end_len + 1) >
_modified_text_buflen) {
size_req = (size_t) ((((size_req)/BUFINC)+1)*BUFINC);
reallocPasteBuf(size_req);
}
if(_modified_text->ptr == NULL)
return;
strcpy(_modified_text->ptr, begin_ins_bracket);
strncat(_modified_text->ptr,sp,length);
strcat(_modified_text->ptr, end_ins_bracket);
_modified_text->length = end_len + begin_len + length;
}
break;
default:
break;
}
_modified_text->format = modify_info->text->format;
// Stuff the modified text block ptr in the return call data
modify_info->text = _modified_text;
}
void
XmTextEditor::MenuButtonHandler(
Widget ,
XtPointer cd,
XEvent *event,
Boolean *)
{
XmTextEditor *obj = (XmTextEditor *)cd;
if(event->xany.type != ButtonPress)
return;
XButtonEvent *be = (XButtonEvent *)event;
if(be->button == Button3)
obj->my_owner->owner()->postTextPopup(event);
}