The Coeditor.C file, shown in Example B-3, shows the ToolTalk code that needs to be included in every editor application to pass a media callback and reply when a request has been completed. It also shows other optional ToolTalk functions that can be included in an editor application.
Ellipses (...) indicates code that has been omitted.
...
CoEditor::CoEditor(
Widget *parent
)
{
_init();
_init( parent );
}
CoEditor::CoEditor(
Widget *parent,
const char *file
)
{
_init();
_init( parent );
_load( file );
}
CoEditor::CoEditor(
Widget *parent,
Tt_messagemsg,
const char * /*docname*/,
Tt_status &status
)
{
_init();
status = _init( msg );
if (status != TT_OK) {
return;
}
_init( parent );
status = _acceptContract( msg );
}
CoEditor::CoEditor(
Widget *parent,
Tt_message msg,
int /*readOnly*/,
const char *file,
const char * /*docname*/,
Tt_status &status
)
{
_init();
status = _init( msg );
if (status != TT_OK) {
return;
}
_init( parent );
status = _load( file );
if (status != TT_OK) {
return;
}
status = _acceptContract( msg );
}
CoEditor::CoEditor(
Widget *parent,
Tt_messagemsg,
int /*readOnly*/,
unsigned char *contents,
int /*len*/,
const char * /*docname*/,
Tt_status &status
)
{
_init();
status = _init( msg );
if (status != TT_OK) {
return;
}
_init( parent );
XtVaSetValues( (Widget)_text,
XtNsourceType, (XtArgVal)OL_STRING_SOURCE,
XtNsource, (XtArgVal)contents,
NULL );
_textBuf = OlTextEditTextBuffer( _text );
RegisterTextBufferUpdate( _textBuf, CoEditor::_textUpdateCB_,
(caddr_t)this );
status = _acceptContract( msg );
}
CoEditor::~CoEditor()
{
//
// No need for a separate save if we are sending the document
// back in a reply.
//
if (_contract == 0) {
if (_modifiedByMe) {
// we revert before quitting if we don't want to save
_save();
}
} else {
int len;
char *contents = _contents( &len );
// Reply to media load callback with edited contents of text
ttmedia_load_reply( _contract, (unsigned char *)contents,
len, 1 );
if (contents != 0) {
XtFree( contents );
} _contract = 0;
}
numEditors--; // XXX assumes user destroys windows LIFO!
}
Tt_message
CoEditor::loadISOLatin1_(
Tt_message msg,
Tttk_op op,
Tt_status diagnosis,
unsigned char *contents,
int len,
char *file,
char *docname,
void *pWidget
)
{
static const char *here = "CoEditor::loadISOLatin1_()";
Tt_status status = TT_OK;
CoEditor *coEditor = 0;
if (diagnosis != TT_OK) {
// toolkit detected an error
if (tt_message_status( msg ) == TT_WRN_START_MESSAGE) {
//
// Error is in start message! We now have no
// reason to live, so tell main() to exit().
//
abortCode = 2;
}
// let toolkit handle the error
return msg;
}
if ((op == TTME_COMPOSE) & (file == 0)) {
oEditor = new CoEditor( (Widget *)pWidget, msg, docname,
status );
} else if (len > 0) {
coEditor = new CoEditor( (Widget *)pWidget, msg,
(op == TTME_DISPLAY),
contents, len, docname, status );
} else if (file != 0) {
coEditor = new CoEditor( (Widget *)pWidget, msg,
(op == TTME_DISPLAY),
file, docname, status );
} else {
// Fail a message
tttk_message_fail( msg, TT_DESKTOP_ENODATA, 0, 1 ); }
tt_free( (caddr_t)contents );
tt_free( file );
tt_free( docname );
return 0; }
void
CoEditor::_init()
{
_baseFrame = 0;
_controls = 0;
_fileBut = 0;
_editBut = 0;
_scrolledWin = 0;
_text = 0;
_textBuf = 0;
_modifiedByMe = FALSE;
_modifiedByOther = 0;
_contract = 0;
_contractPats = 0;
_filePats = 0;
_file = 0;
_x = INT_MAX;
_y = INT_MAX;
_w = INT_MAX;
_h = INT_MAX;
}
Tt_status
CoEditor::_init(
Tt_message msg
)
{
int width, height, xOffset, yOffset;
width = height = xOffset = yOffset = INT_MAX;
_contract = msg;
ttdt_sender_imprint_on( 0, msg, 0, &_w, &_h, &_x, &_y,
10 * timeOutFactor );
return TT_OK;
}
typedef enum {
Open,
Save,
SaveAs,
Revert
} FileOp;
static const char *fileButs[] = {
"Open...",
"Save",
"Save as...",
"Revert"
};
const int numFileButs = sizeof( fileButs ) / sizeof( const char * );
typedef enum {
Undo,
Cut,
Copy,
Paste,
Delete,
SelText,
SelAppt
} EditOp;
static const char *editButs[] = {
"Undo",
"Cut",
"Copy",
"Paste",
"Delete",
"Text as ISO_Latin_1",
"Text as Appointment"
};
const int numEditButs = sizeof( editButs ) / sizeof( const char * );
void
CoEditor::_init(
Widget *parent
)
{
if (*parent == 0) {
if (_contract != 0) {
//
// Re-open display, since $DISPLAY may have changed by
// ttdt_sender_imprint_on().
//
XtCloseDisplay( myDpy );
myDpy = XtOpenDisplay( myContext, 0, 0, "CoEd", 0, 0,
pArgc, globalArgv );
}
*parent = XtAppCreateShell( 0, "CoEd",
applicationShellWidgetClass, myDpy, 0, 0 );
XtVaSetValues( *parent,
XtNmappedWhenManaged, False,
XtNheight, 1,
XtNwidth, 1,
0 );
XtRealizeWidget( *parent );
}
shell = XtCreatePopupShell( "CoEd",
applicationShellWidgetClass, *parent, 0, 0 );
XtVaSetValues( shell, XtNuserData, this, 0 );
// Pop up next to our parent
if ((_x != INT_MAX) && (_y != INT_MAX) && (_w != INT_MAX)) {
// XXX Be smarter about picking a geometry
Dimension x = _x + _w;
Dimension y = _y;
XtVaSetValues( shell, XtNx, x, XtNy, y, 0 );
}
XtAddCallback( shell, XtNdestroyCallback, CoEditor::_destroyCB_,
this );
OlAddCallback( shell, XtNwmProtocol, CoEditor::_wmProtocolCB_, this );
_baseFrame = XtVaCreateManagedWidget(
"baseFrame", rubberTileWidgetClass, shell, 0 );
_controls = XtVaCreateManagedWidget( "controls",
controlAreaWidgetClass, _baseFrame,
XtNweight, (XtArgVal)0,
0 );
_fileBut = XtVaCreateManagedWidget( "File",
menuButtonWidgetClass, _controls, 0 );
Widget menuPane;
XtVaGetValues( _fileBut, XtNmenuPane, &menuPane, 0 );
for (int i = 0; i < numFileButs; i++) {
Widget but = XtVaCreateManagedWidget( fileButs[i],
oblongButtonWidgetClass, menuPane,
XtNuserData, i, 0 );
XtAddCallback( but, XtNselect, CoEditor::_fileButsCB_, this );
} _editBut = XtVaCreateManagedWidget( "Edit",
menuButtonWidgetClass, _controls, 0 );
XtVaGetValues( _editBut, XtNmenuPane, &menuPane, 0 );
for (i = 0; i < numEditButs; i++) {
Widget but = XtVaCreateManagedWidget( editButs[i],
oblongButtonWidgetClass, menuPane,
XtNuserData, i, 0 );
XtAddCallback( but, XtNselect, CoEditor::_editButsCB_, this );
} _scrolledWin = XtVaCreateManagedWidget(
"scrolledWin", scrolledWindowWidgetClass,
_baseFrame,
XtNforceVerticalSB,(XtArgVal)True,
0 );
_text = (TextEditWidget)XtVaCreateManagedWidget(
"text", textEditWidgetClass, _scrolledWin,
0 );
XtVaSetValues( (Widget)_text, XtNuserData, this, 0 );
XtRealizeWidget( shell );
XtPopup( shell, XtGrabNone );
if (numEditors < MaxEditors) {
editors[ numEditors ] = this;
numEditors++;
}
if (numEditors >= maxBuffers) {
tt_ptype_undeclare( "DT_CoEd" );
}
}
Tt_status
CoEditor::_unload()
{
Tt_status status = TT_OK;
if (_filePats != 0) {
// Unregister interest in ToolTalk events and destroy patterns
status = ttdt_file_quit( _filePats, 1 );
_filePats = 0;
}
if (_file != 0) {
free( _file );
_file = 0;
}
return status;
}
Tt_status
CoEditor::_load(
const char *file
)
{
int reloading = 1;
if (file != 0) {
if ((_file != 0) && (strcmp( file, _file ) != 0)) {
reloading = 0;
_unload();
} else {
_file = strdup( file );
}
}
// Join a file Can be called recursively, below
if (_filePats == 0) {
_filePats = ttdt_file_join( _file, TT_SCOPE_NONE, 1,
CoEditor::_fileCB_, this );
}
XtVaSetValues( (Widget)_text,
XtNsourceType, (XtArgVal)OL_DISK_SOURCE,
XtNsource, (XtArgVal)_file,
NULL ); _textBuf = OlTextEditTextBuffer( _text );
RegisterTextBufferUpdate( _textBuf, CoEditor::_textUpdateCB_,
(caddr_t)this );
if (_modifiedByMe && reloading) {
ttdt_file_event( _contract, TTDT_REVERTED, _filePats, 1 );
} _modifiedByMe = 0;
// Does the file have any changes pending?
_modifiedByOther = ttdt_Get_Modified( _contract, _file, TT_BOTH,
10 * timeOutFactor );
if (_modifiedByOther) {
int choice = userChoice( myContext, _baseFrame,
"Another tool has modifications pending for "
"this file.\nDo you want to ask it to save "
"or revert the file?", 3, "Save", "Revert",
"Ignore" );
Tt_status status = TT_OK;
switch (choice) {
case 0:
// Save pending changes
status = ttdt_Save( _contract, _file, TT_BOTH,
10 * timeOutFactor );
break;
case 1:
// Revert file to last version
status = ttdt_Revert( _contract, _file, TT_BOTH,
10 * timeOutFactor );
break;
}
if (status != TT_OK) {
char *s = tt_status_message( status );
userChoice( myContext, _baseFrame, s, 1, "Okay" );
tt_free( s );
} else if (choice == 0) {
// file was saved, so reload<
return _load( 0 );
} else if (choice == 1) {
// file was reverted
_modifiedByOther = 0;
}
}
return TT_OK;
}
Tt_status
CoEditor::_load(
unsigned char *contents,
int //len
)
{
_unload();
XtVaSetValues( (Widget)_text,
XtNsourceType, (XtArgVal)OL_DISK_SOURCE,
XtNsource, (XtArgVal)contents,
NULL );
_textBuf = OlTextEditTextBuffer( _text );
RegisterTextBufferUpdate( _textBuf, CoEditor::_textUpdateCB_,
(caddr_t)this ); _modifiedByMe = 0;
_modifiedByOther = 0; return TT_OK;
}
//
// Caller responsible for reporting any errors to user
//
Tt_status
CoEditor::_save()
{
Tt_status status;
if (_file != 0) {
if (SaveTextBuffer( _textBuf, _file ) != SAVE_SUCCESS) {
return TT_DESKTOP_EIO;
}
_modifiedByMe = 0;
_modifiedByOther = 0;
// File has been saved
ttdt_file_event( _contract, TTDT_SAVED, _filePats, 1 );
}
if (_contract != 0) {
int len = 0;
char *contents = 0;
if (_file == 0) {
// If you worry that the buffer might be big,
// you could instead try a a temp file to
// transfer the data "out of band".
contents = _contents( &len );
}
status = ttmedia_Deposit( _contract, 0, "ISO_Latin_1",
(unsigned char *)contents,
len, _file, 10 * timeOutFactor );
if (status != TT_OK) {
return status;
}
_modifiedByMe = 0;
_modifiedByOther = 0;
if (contents != 0) {
XtFree( contents );
}
}
return status;
}
Tt_status
CoEditor::_revert() // XXX how about we always just send Revert? :-)
{
if (! _modifiedByMe) {
return TT_OK;
} return _load( 0 ); // XXX what if it's not a file? keep last deposit
}
void
CoEditor::_destroyCB_(
Widget w,
XtPointer coEditor,
XtPointer call_data
)
{
((CoEditor *)coEditor)->_destroyCB( w, call_data );
}
void
CoEditor::_destroyCB(
Widget ,
XtPointer //call_data
)
{
delete this;
}
void
CoEditor::_wmProtocolCB_(
Widget w,
XtPointer coEditor,
XtPointer wmMsg
)
{
((CoEditor *)coEditor)->_wmProtocolCB( w, (OlWMProtocolVerify*)wmMsg );
}
void
CoEditor::_wmProtocolCB(
Widget w,
OlWMProtocolVerify *wmMsg
)
{
switch (wmMsg->msgtype) {
case OL_WM_DELETE_WINDOW:
if (_modifiedByMe) {
int choice =
userChoice( myContext, _baseFrame,
"The text has unsaved changes.",
3, "Save, then Quit",
"Discard, then Quit",
"Cancel" );
switch (choice) {
case 0:
break;
case 1:
_revert();
break;
case 2:
return;
}
}
if umEditors > 1) {
XtDestroyWidget( shell );
} else {
// XXX OlWmProtocolAction() doesn't call destructor?!
delete this;
OlWMProtocolAction( w, wmMsg, OL_DEFAULTACTION );
}
break;
default:
OlWMProtocolAction( w, wmMsg, OL_DEFAULTACTION );
break;
}
}
void
CoEditor::_fileButsCB_(
Widget button,
XtPointer coEditor,
XtPointer call_data
)
{
((CoEditor *)coEditor)->_fileButsCB( button, call_data );
}
void
CoEditor::_fileButsCB(
Widget button,
XtPointer //call_data
)
{
FileOp op; XtVaGetValues( button, XtNuserData, &op, 0 );
Tt_status status = TT_OK;
switch (op) {
case Open:
break;
case Revert:
status =_revert();
break;
case Save:
status =_save();
break;
case SaveAs:
break;
}
if (status != TT_OK) {
_adviseUser( status );
}
}
void
CoEditor::_editButsCB_(
Widget button,
XtPointer coEditor,
XtPointer call_data
)
{
((CoEditor *)coEditor)->_editButsCB( button, call_data );
}
void
CoEditor::_editButsCB(
Widget button,
XtPointer //call_data
)
{
EditOp op;
XtVaGetValues( button, XtNuserData, &op, 0 );
Tt_status status = TT_OK;
switch (op) {
int len;
char *contents;
const char *mediaType;
Tt_messagemsg;
Tt_pattern *pats;
case SelText:
case SelAppt:
if (op == SelText) {
mediaType = "ISO_Latin_1";
} else {
mediaType = "DT_CM_Appointment";
}
//contents = _selection( &len );
contents = _contents( &len );
if (len <= 0) {
return;
}
// Media load callback
msg = ttmedia_load( _contract, CoEditor::_mediaLoadMsgCB_,
this, TTME_EDIT, mediaType,
(unsigned char *)contents, len, 0, 0, 1 );
if (contents != 0) {
XtFree( contents );
}
status = tt_ptr_error( msg );
if (status != TT_OK) {
break;
}
pats = ttdt_subcontract_manage( msg, 0, shell, this );
status = tt_ptr_error( pats );
if (status != TT_OK) {
break;
}
break;
}
if (status != TT_OK) {
char *s = tt_status_message( status );
char buf[ 1024 ];
sprintf( buf, "%d: %s", status, s );
tt_free( s );
userChoice( myContext, _baseFrame, buf, 1, "Okay" );
}
}
char *
CoEditor::_contents(
int *len ) {
_textBuf = OlTextEditTextBuffer( _text );
TextLocation start = { 0, 0, 0 };
TextLocation end = LastTextBufferLocation( _textBuf );
char *contents = GetTextBufferBlock( _textBuf, start, end );
*len = 0;
if (contents != 0) {
*len = strlen( contents );
}
return contents;
}
Tt_status
CoEditor::_acceptContract(
Tt_message msg ) {
static const char *here = "CoEditor::_acceptContract()";
_contract = msg;
if (tt_message_status( msg ) == TT_WRN_START_MESSAGE) {
//
// Join session before accepting start message,
// to prevent unnecessary starts of our ptype
//
Widget session_shell = shell;
if (maxBuffers > 1) {
//
// If we are in multi-window mode, just use
// our unmapped toplevel shell as our session
// shell, since we do not know if any particular
// window will exist the whole time we are in
// the session.
//
session_shell = XtParent(shell );
}
// Join the session and register patterns and callbacks
sessPats = ttdt_session_join( 0, 0, session_shell, this, 1 );
}
// Accept responsibility to handle a request
_contractPats = ttdt_message_accept(
msg, CoEditor::_contractCB_, shell, this,
1, 1 );
Tt_status status = tt_ptr_error( _contractPats );
if (status != TT_OK) {
return status;
} return status;
}
Tt_message
CoEditor::_contractCB_(
Tt_message, //msg,
Tttk_op, //op,
Widget, //shell,
void*, //coEditor,
Tt_message //Contract
)
{
return 0; }
void
CoEditor::_editButCB_(
Widget w,
XtPointer coEditor,
XtPointer call_data
)
{
((CoEditor *)coEditor)->_editButCB( w, call_data );
}
void
CoEditor::_editButCB(
Widget ,
XtPointer //call_data
)
{
int len;
char *contents = _contents( &len );
// Media Load Callback
Tt_message msg = ttmedia_load( _contract, CoEditor::_mediaLoadMsgCB_,
this, TTME_EDIT, "ISO_Latin_1",
(unsigned char *)contents,
len, 0, 0, 1 );
if (contents != 0) {
XtFree( contents );
}
Tt_pattern *pats = ttdt_subcontract_manage( msg, 0, shell, this );
}
Tt_message
CoEditor::_mediaLoadMsgCB_(
Tt_message msg,
Tttk_op op,
unsigned char *contents,
int len,
char *file,
void *clientData
)
{
return ((CoEditor *)clientData)->_mediaLoadMsgCB( msg, op,
contents, len, file ); }
Tt_message
CoEditor::_mediaLoadMsgCB(
Tt_message msg,
Tttk_op,
unsigned char *contents,
int len,
char *file
)
{
if (len > 0) {
XtVaSetValues( (Widget)_text,
XtNsourceType, (XtArgVal)OL_STRING_SOURCE,
XtNsource, (XtArgVal)contents,
NULL );
_textBuf = OlTextEditTextBuffer( _text );
RegisterTextBufferUpdate( _textBuf, CoEditor::_textUpdateCB_,
(caddr_t)this );
// ReplaceBlockInTextBuffer
} else if (file != 0) {
}
tt_message_destroy( msg );
return 0;
}
void
CoEditor::_textUpdateCB_(
XtPointer coEditor,
XtPointer pTextBuffer,
EditResult status ) {
if (coEditor == 0) {
return;
}
((CoEditor *)coEditor)->_textUpdateCB( (TextBuffer *)pTextBuffer,
status );
}
void
CoEditor::_textUpdateCB(
TextBuffer *textBuf,
EditResult //editStatus ) {
//Tt_status status;
if (_textBuf != textBuf) {
fprintf( stderr, "_textBuf != textBuf" );
}
if ((! _modifiedByMe) && TextBufferModified( _textBuf )) {
_modifiedByMe = TRUE;
// File has changes pending
ttdt_file_event( _contract, TTDT_MODIFIED, _filePats, 1 );
}
}
Tt_message
CoEditor::_fileCB_(
Tt_message msg,
Tttk_op op,
char *pathname,
void *coEditor,
int trust,
int me
)
{
tt_free( pathname );
if (coEditor == 0) {
return msg;
}
return ((CoEditor *)coEditor)->_fileCB( msg, op, pathname,
trust, me );
}
Tt_message
CoEditor::_fileCB(
Tt_message msg,
Tttk_op op,
char *pathname,
int, //trust
int //me
)
{
tt_free( pathname );
Tt_status status = TT_OK;
switch (op) {
case TTDT_MODIFIED:
if (_modifiedByMe) {
// Hmm, the other editor either doesn't know or
// doesn't care that we are already modifying the
// file, so the last saver will win.
// XXX Or: a race condition has arisen!
} else {
// Interrogate user if she ever modifies the buffer
_modifiedByOther = 1;
XtAddCallback( (Widget)_text, XtNmodifyVerification,
(XtCallbackProc)CoEditor::_textModifyCB_, 0 );
}
break;
case TTDT_GET_MODIFIED:
tt_message_arg_ival_set( msg, 1, _modifiedByMe );
tt_message_reply( msg );
break;
case TTDT_SAVE:
status = _save();
if (status == TT_OK) {
tt_message_reply( msg );
} else {
// Fail message
tttk_message_fail( msg, status, 0, 0 );
}
break;
case TTDT_REVERT:
status = _revert();
if (status == TT_OK) {
tt_message_reply( msg );
} else {
// Fail message
tttk_message_fail( msg, status, 0, 0 );
}
break;
case TTDT_REVERTED:
case TTDT_SAVED:
case TTDT_MOVED:
case TTDT_DELETED:
printf( "CoEditor::_fileCB(): %s\n", tttk_op_string( op ));
break;
}
tt_message_destroy( msg );
return 0;
}
void CoEditor::_textModifyCB_(
TextEditWidget text,
XtPointer ,
OlTextModifyCallData *mod
)
{
CoEditor *coEditor = 0;
XtVaGetValues( (Widget)text, XtNuserData, &coEditor, 0 );
if (coEditor == 0) {
return;
}
coEditor->_textModifyCB( mod );
}
void
CoEditor::_textModifyCB(
OlTextModifyCallData *mod
)
{
if (_modifiedByOther != 1) {
return;
}
int cancel = userChoice( myContext, _baseFrame,
"Another tool has modifications pending for this file.\n"
"Are you sure you want to start modifying the file?",
2, "Modify", "Cancel" );
if (cancel) {
mod->ok = FALSE;
}
_modifiedByOther = 2;
}
void
CoEditor::_adviseUser(
Tt_status status
)
{
char *s = tt_status_message( status );
char buf[ 1024 ];
sprintf( buf, "%d: %s", status, s );
tt_free( s );
userChoice( myContext, _baseFrame, buf, 1, "Okay" );
}