共通デスクトップ環境 ToolTalk メッセージの概要

付録 B CoEd デモンストレーション・プログラム

この付録には、CoEd と呼ばれる ToolTalk デモンストレーション・プログラムのための ToolTalk 関連コードを示すファイルとソースコードの一覧があります。CoEd デモ・プログラムは、ToolTalk デスクトップ・サービス・メッセージ・セットを使用します。エディタの複数のインスタンスが同時に同じファイルを編集しているときに、エディタが ToolTalk サービスを使用して、ユーザが加えたすべての変更の同期を取る方法について説明します。

CoEd ptype ファイル

CoEd ptype ファイルについて、例 B-1 に示します。


例 B-1 CoEd ptype ファイル

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

CoEd.C ファイル

例 B-2 に示す CoEd.C ファイルは、各アプリケーションに組み込む必要がある ToolTalk コードを示します。これにより、ツールキットの初期化、ToolTalk セッションへの参加、パターンの登録、ToolTalk サービスのイベント・ループへの追加を実行できます。


注 -

このファイルには、エディタ・アプリケーションとして機能する場合の CoEd に固有の ToolTalk コードも入っています。このコードには、ptype の宣言と起動メッセージの処理も含まれています。



例 B-2 CoEd.C ファイル

/*
 * 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 );    
}

Coeditor.C ファイル

例 B-3 に示す Coeditor.C ファイルは、各エディタ・アプリケーションに組み込む必要がある ToolTalk コードを示します。これにより、メディア・コールバックを渡し、要求の完了時に応答できます。エディタ・アプリケーションに組み込むことができるその他のオプションの ToolTalk コードも示します。


注 -

省略符号 (...) は、省略されたコードを示します。



例 B-3 CoEditor.C ファイル

...     

CoEditor::CoEditor(
    Widget *parent 
) 
{
    _init();
    _init( parent ); 
} 

CoEditor::CoEditor(
    Widget          *parent,
    const char      *file 
) 
{
    _init();
    _init( parent );
    _load( file ); 
}    

CoEditor::CoEditor(
    Widget          *parent,
    Tt_message      msg,
    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_message      msg,
    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" ); 
}