Common Desktop Environment: ToolTalk Messaging Overview

Appendix B The CoEd Demonstration Program

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

The CoEd ptype file, shown in Example B-1 .


Example B-1 CoEd Ptype File

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

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.


Note -

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.



Example B-2 The CoEd.C File

/*
  * 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

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.


Note -

Ellipses (...) indicates code that has been omitted.



Example B-3 The CoEditor.C File

...  

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" ); 
}