static const char* version_id =
  "$Id: ConversationWindow.cc,v 1.23 1999/10/22 20:50:46 v2kea318 Exp $";

// $Log: ConversationWindow.cc,v $
// Revision 1.23  1999/10/22  20:50:46  v2kea318
// Use the OO Thread interface for thread things.
//
// Revision 1.22  1999/10/14  16:41:47  v2kea318
// Use the pseudo machine independant thread.h header to define all
// thread related things.
//
// Revision 1.21  1999/09/07  16:49:07  v2kea318
// Be sure to include Pthread.h to properly prototype the pthread_create
// function.
//
// Revision 1.20  1999/08/18  20:18:42  v2kea318
// Limit the mutex scope to just the text insertion in the update method.
//
// Revision 1.19  1999/08/18  20:13:59  v2kea318
// Changed the order of operations in the update method to build the
// header and text strings before grabbing the mutex.  There's no reason
// to block other users out while we're building a label string.
//
// Revision 1.18  1999/08/17  14:33:40  v2kea318
// *** empty log message ***
//
// Revision 1.17  1999/08/16  17:02:11  v2kea318
// Use the MutexRep object rather than Mutex to represent a mutex.  The
// get_mutex method now returns a copy of this object.  The rls_mutex method
// is no longer needed, so it is eliminated.
//
// Revision 1.16  1999/08/13  21:25:22  v2kea318
// Use a const_cast to pass the string to XmTextInsert.
//
// Revision 1.15  1999/08/09  15:11:28  v2kea318
// Remove the use of the Mutex::initialize() method.  This is now private
// in the Mutex class.
//
// Revision 1.14  1999/08/05  19:35:24  v2kea318
// Pass the time stamp from user rather than calculating it in the update
// method.
//
// Revision 1.13  1999/08/04  20:15:26  v2kea318
// *** empty log message ***
//
// Revision 1.12  1999/07/30  21:43:01  v2kea318
// Use the Mutex class for the mutex.
//
// Revision 1.11  1999/07/30  20:40:50  v2kea318
// Use Pthread.h instead of pthread.h to define threads.  This is a
// temporary kludge because of some extraneous spaces in the standard
// version.
//
// Revision 1.10  1999/07/30  20:24:09  v2kea318
// Finished converting to G++ compliant code.
//
// Revision 1.9  1999/07/30  17:55:48  v2kea318
// Reformat the header file names for Standard C++ compliance.
//
// Revision 1.8  1999/07/13  20:58:11  v2kea318
// Kill the event loop thread when we close the window.  There is no reason
// to have it running if there is no window.  Kill the thread with the SIGUSR1
// signal rather than SIGINT.  The previous was was interfering with the
// automatic cleanup algorithm in the main routine, which relies on catching
// SIGINT signals.  It now works correctly again.
//
// Revision 1.7  1999/07/13  20:50:45  v2kea318
// Fixed the sleeping in the event management loop.  Previously, there was no
// sleeping!  This is because sleep takes an int argument, so 0.25 becomes 0.
// Now, we sleep for 0.1 seconds (100000 microseconds) with the usleep
// function.
//
// Revision 1.6  1999/07/06  17:00:44  v2kea318
// Do not realize the window in the build_window method.  This will be done
// when a message is received.
//
// Revision 1.5  1999/07/01  21:47:04  v2kea318
// Got the close button to work!  The toplevel widget is now realized in
// a special routine.  The close button callback calls the destroy_force_rebuild
// method which unrealizes the window.  A subsequent update will
// re-realize the window, with the old text erased.
//
// Revision 1.4  1999/07/01  19:49:31  v2kea318
// Ring the bell when a new message is added to the display.  I want to
// find a way to bring the window to the top when this happens too.
//
// Revision 1.3  1999/07/01  19:03:59  v2kea318
// Divide the string being added to the window into a header and entry
// strings.  This is in anticipation of the day we can set the text color
// or background to something special for the header.  Made the text widget
// uneditable.
//
// Revision 1.2  1999/07/01  17:08:34  v2kea318
// Since the callback for the close button doesn't currently work correctly,
// do not add the callback.  The button will still appear, but not do anything.
//
// Revision 1.1  1999/07/01  17:00:16  v2kea318
// Initial revision
//
//***************************************************************************

#include "ConversationWindow.h"
#include <iostream>
#include <Xm/ScrolledW.h>
#include <Xm/Text.h>
#include <Xm/PushB.h>
#include <Xm/Form.h>
#include <unistd.h>
#include "get_userid.h"
#include <strstream>
#include <csignal>

//***************************************************************************

bool ConversationWindow::realized = false;
Widget ConversationWindow::text_widget;
MutexRep ConversationWindow::mut;
Pointer<Thread> ConversationWindow::loop_thread(NULL);

//***************************************************************************

static bool initialized = false;
static char* argv[] = { NULL, "-bg", "LightBlue" };
static int argc = (sizeof argv) / sizeof(char*);
static XtAppContext context;

// this routine is defined in handle_message.cc .
extern void destroy_conversation_window(Widget, XtPointer, XtPointer);

//***************************************************************************

/* Provide a routine that will handle XEvents for the display.  This routine
   will run in its own thread.  We can't simply use XtAppMainLoop as that
   will monopolize the window and we won't be able to get other threads
   to work.  What we will do is check for a pending event.  If there is one,
   dispatch it.  Otherwise, sleep for a short period of time and then try
   again.  This design will allow the window updating routine to do what
   it needs to do to update the window. */
static bool event_loop_running;
static void kill_event_loop(int s)
{
  event_loop_running = false;
}
static void* event_loop(void* ptr)
{
  event_loop_running = true;

  // if this thread gets a SIGINT signal, set the event_loop_running flag off.
  signal(SIGUSR1, kill_event_loop);

  /* Loop while we have pending events or the event_loop_running flag
     is on.  This way, if we take a SIGINT signal and turn the flag off,
     we will finish off the pending events for the window. */
  XtInputMask mask;
  while ((mask = XtAppPending(context)) > 0 || event_loop_running) {
    if (mask) 
      // there is an event, so deal with it.
      XtAppProcessEvent(context, mask);
    else 
      // no events, so sleep
      usleep(100000); // 1/10 second
  }
  return NULL;
}

//***************************************************************************

static Widget* toplevel = NULL;

//***************************************************************************

void ConversationWindow::realize_window()
{
  if (!realized) {
    // realize the widget.
    XtRealizeWidget(*toplevel);
    realized = true;

    // Reset the text in the window to an empty string.
    int n = 0; Arg args[10];
    XtSetArg(args[n], XmNvalue, ""); n++;
    XtSetValues(text_widget, args, n);

    // start the window event monitoring thread.
    if (!loop_thread)
      (loop_thread = Thread::create())->start(event_loop, NULL);
  }
}

//***************************************************************************

void ConversationWindow::build_window()
{
  if (!initialized) {
    initialized = true;
    int n; Arg args[50];

    // context and toplevel.
    if (!toplevel) {
      toplevel = new Widget;
      string win_label = "msg for ";
      win_label += string(get_userid());
      argv[0] = new char [win_label.size() + 1];
      ostrstream label_buff(argv[0], win_label.size() + 1);
      label_buff << win_label.c_str() << ends;
      *toplevel = XtAppInitialize(&context, argv[0],
				  NULL, 0, &argc, argv,
				  NULL, NULL, 0);
    }
    
    // build the window.
    n = 0;
    XtSetArg(args[n], XmNresizable, True); n++;
    XtSetArg(args[n], XmNheight, 250); n++;
    XtSetArg(args[n], XmNwidth, 500); n++;
    Widget form = XmCreateForm(*toplevel, "form", args, n);
    XtManageChild(form);
    
    // create the scrolled text widget.
    n = 0;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNbottomPosition, 90); n++;
    XtSetArg(args[n], XmNscrollVertical, True); n++;
    XtSetArg(args[n], XmNscrollHorizontal, False); n++;
    XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
    XtSetArg(args[n], XmNwordWrap, True); n++;
    XtSetArg(args[n], XmNeditable, False); n++;
    text_widget = XmCreateScrolledText(form, "text", args, n);
    XtManageChild(text_widget);
    
    // add a quit button.
    n = 0;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNleftPosition, 45); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNrightPosition, 55); n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
    Widget quit_button = XmCreatePushButton(form, "close", args, n);
    XtManageChild(quit_button);
    
    // add the callback.
    XtAddCallback(quit_button, XmNactivateCallback, 
		  destroy_conversation_window, NULL);
  }
}

//***************************************************************************

ConversationWindow::ConversationWindow()
{
  MutexRep m = get_mutex();
  build_window();
}

//***************************************************************************

void ConversationWindow::update(msg_action_code type, const string& text,
				const string& user, 
				const string& timestamp)
{
  // Build a header for the new entry.
  string update_header = (type == COPY) ? "To: " : "From: ";
  update_header += user + string(" (") + timestamp
                        + string(")\n");

  // Build the text of the entry.
  string update_text = text + string("\n\n");
    
  { // synchronize the threads over the insertion code.      
      // grab hold of the mutex.
      MutexRep m = get_mutex();

      // re-realize the window, if necessary.
      realize_window();

      // Add the string to the display.
      XmTextInsert(text_widget, XmTextGetLastPosition(text_widget), 
		   const_cast<char *>(update_header.c_str()));
      XmTextInsert(text_widget, XmTextGetLastPosition(text_widget),
		   const_cast<char *>(update_text.c_str()));
  } // the threads are again unsynchronized.
  
  // get some useful parameters.
  Display* display = XtDisplay(text_widget);
  Window window = XtWindow(text_widget);

  // ring the bell.
  XBell(display, -25);

  // Raise the window.  Why doesn't this work?
  XMapRaised(display, window);
}

//***************************************************************************

MutexRep ConversationWindow::get_mutex()
{
  return mut;  
}

//***************************************************************************

void ConversationWindow::destroy_force_rebuild()
{
  // unrealize the window.
  XtUnrealizeWidget(*toplevel);
  realized = false;

  /* Kill the event management thread.  If the window is closed, there's
     no reason to keep it running. */
  loop_thread->kill(SIGUSR1);
  loop_thread = Pointer<Thread>(NULL);
}
