This appendix contains the files and source code listing showing the ToolTalk related code for a ToolTalk demonstration program called CoEd. The CoEd demo program uses the ToolTalk Desktop Services message set. It illustrates how an editor can use the ToolTalk service to keep all changes made by the user in sync if multiple instances of the editor are editing the same file at the same time.
The CoEd ptype file, shown in Example B-1 .
ptype DT_CoEd { /* Process type identifier */ start "CoEd; /* Start string */ handle: /* Receiving process */ /* * Display ISO_Latin_1 */ session Display( in ISO_Latin_1 contents) => start opnum = 1; /* Signature */ session Display( in ISO_Latin_1 contents, in messageID counterfoil ) => start opnum = 2; session Display( in ISO_Latin_1 contents, in title docName ) => start opnum = 3; session Display( in ISO_Latin_1 contents, in messageID counterfoil, in title docName ) => start opnum = 4; /* * Edit ISO_Latin_1 */ session Edit( inout ISO_Latin_1 contents) => start opnum = 101; session Edit( inout ISO_Latin_1 contents, in messageID counterfoil) => start opnum = 102; session Edit( inout ISO_Latin_1 contents, in title docName) => start opnum = 103; session Edit( inout ISO_Latin_1 contents, in messageID counterfoil, in title docName) => start opnum = 104; /* * Compose ISO_Latin_1 */ session Edit( out ISO_Latin_1 contents) => start opnum = 201; session Edit( out ISO_Latin_1 contents, in messageID counterfoil) => start opnum = 202; session Edit( out ISO_Latin_1 contents, in title docName) => start opnum = 203; session Edit( out ISO_Latin_1 contents, in messageID counterfoil, in title docName) => start opnum = 204; /* * Open an ISO_Latin_1 buffer */ session Open( in ISO_Latin_1 contents, out bufferID docBuf, in boolean readOnly ) => start opnum = 400; session Open( in ISO_Latin_1 contents, out bufferID docBuf, in boolean readOnly, in boolean mapped ) => start opnum = 401; session Open( in ISO_Latin_1 contents, out bufferID docBuf, in boolean readOnly, in boolean mapped, in integer shareLevel ) => start opnum = 402; session Open( in ISO_Latin_1 contents, out bufferID docBuf, in boolean readOnly, in boolean mapped, in integer shareLevel, in locator initialPos ) => start opnum = 403; };
The CoEd.C file, shown in Example B-2 , shows the ToolTalk code that needs to be included in every application to initialize the toolkit, join a ToolTalk session and registering patterns, and add the ToolTalk service to its event loop.
This file also contains ToolTalk code that is specific to CoEd in its role as an editor application. This code includes declaring a ptype and processing the start message.
/* * CoEd.cc * * Copyright (c) 1991,1993 by Sun Microsystems. */ #include <stdlib.h> #include <desktop/tttk.h> // Include the ToolTalk messaging toolkit #include <CoEd.h> #include "CoEditor.h" #include "CoEdTextBuffer.h" XtAppContext myContext; Widget myTopWidget = 0; Display *myDpy; int abortCode = 0; Tt_pattern *sessPats = 0; // Patterns returned when session joined int timeOutFactor = 1000; int maxBuffers = 1000; int *pArgc; char **globalArgv; const char *ToolName = "CoEd"; const char *usage = "Usage: CoEd [-p01] [-w n] [-t n] [file]\n" " -p print ToolTalk procid\n" " -0 do not open an initial composition window\n" " -1 be a single-buffer editor\n" " -w sleep for n seconds before coming up\n" " -t use n as timeout factor, in milliseconds (default: 1000)\n" ; void main( int argc, char **argv ) { static const char *here = "main()"; int delay = 0; int printid = 0; int compose = 1; char *file = 0; OlToolkitInitialize( 0 ); XtToolkitInitialize(); myContext = XtCreateApplicationContext(); // // This display may get closed, and another opened, inside // CoEditor::_init(), if e.g. our parent is on a different screen // pArgc = &argc; globalArgv = argv; myDpy = XtOpenDisplay( myContext, 0, 0, "CoEd", 0, 0, &argc, argv ); int c; while ((c = getopt( argc, argv, "p01w:t:" )) != -1) { switch (c) { case `p': printid = 1; break; case `0': compose = 0; break; case `1': maxBuffers = 1; break; case `w': delay = atoi( optarg ); break; case `t': timeOutFactor = atoi( optarg ); break; default: fputs( usage, stderr ); exit( 1 ); } } if (optind < argc) { file = argv[ optind ]; } while (delay > 0) { sleep( 1 ); delay--; } int myTtFd; // Obtain process identifier // Initialize toolkit and create a ToolTalk communication endpoint char *myProcID = ttdt_open( &myTtFd, ToolName, "SunSoft", "%I", 1 ); // Declare ptype ttmedia_ptype_declare( "DT_CoEd", 0, CoEditor::loadISOLatin1_, (void *)&myTopWidget, 1 ); // Process the message that started us, if any tttk_Xt_input_handler( 0, 0, 0 ); if (abortCode != 0) { // Error in message that caused us to start. exit( abortCode ); } if (CoEditor::numEditors == 0) { // started by hand, not by ToolTalk if (file == 0) { if (compose) { new CoEditor( &myTopWidget ); } } else { new CoEditor( &myTopWidget, file ); } } // // If sessPats is unset, then we have not joined the desktop // session yet. So join it. // if (sessPats == 0) { Widget session_shell = CoEditor::editors[0]->shell; if (maxBuffers > 1) { // // In multi-window mode, no single window is the // distinguished window. // session_shell = myTopWidget; } sessPats = ttdt_session_join( 0, 0, session_shell, 0, 1 ); } XtAppAddInput( myContext, myTtFd,(XtPointer)XtInputReadMask, tttk_Xt_input_handler, myProcID ); XtAppMainLoop( myContext ); }
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" ); }