// This may look like C code, but it is really -*- C++ -*-

/***********************************************************
Copyright 1991 by the Massachusetts Institute of Technology

Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation, and that the name of M.I.T. not be used in advertising or
publicity pertaining to distribution of the software without specific,
written prior permission.  M.I.T. makes no representations about the
suitability of this software for any purpose.  It is provided "as is"
without express or implied warranty.


Author: Roman J. Budzianowski - Project Athena MIT

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

#include "ObjectEditor.h"
#include "stream.h"

static void Manager(Core* , XEvent*, String* , Cardinal* );
static void Initialize(Core*  , XEvent*, String* , Cardinal* );
static void Refresh(Core*  , XEvent*, String* , Cardinal* );

static char defTranslations[] = 
"<Btn2Motion>:		ObjectEditorManager(Connect) \n\
 <Btn2Up>:		ObjectEditorManager(AbortConnection) \n\
 <Btn3Motion>:		ObjectEditorManager(MoveObject) \n\
 <Btn3Up>:		ObjectEditorManager(StopObject) \n\
 <Btn1Up>:		ObjectEditorManager(PlaceNewObject) \n\
 <Expose>:		Refresh() \n\
 <Map>:              Initialize()";

struct ObjectEditor_resources {
   Cursor      normal_cursor;    
   Cursor      placement_cursor;
   Cursor      connection_cursor;
   XtTranslations translations;
   Boolean     rubberband;
};

#define XtNplacementCursor "placementCursor"
#define XtNconnectionCursor "connectionCursor"
#define XtNobjectEditorTranslations "objectEditorTranslations"
#define XtNrubberbandObject "rubberbandObject"

typedef ObjectEditor_resources *resource_pointer;

static XtActionsRec actions[] =
{ 
   {"ObjectEditorManager", Manager},
   {"Initialize", Initialize },
   {"Refresh", Refresh }
};

#ifdef cfront_not_broken
#define offset(field) XtOffset(resource_pointer, field)
#else
#define offset(field) 0
#endif

static XtResource subresources[] = {
   {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
    offset(normal_cursor), XtRString, "cross"},
   {XtNplacementCursor, XtCCursor, XtRCursor, sizeof(Cursor),
    offset(placement_cursor), XtRString, "target"},
   {XtNconnectionCursor, XtCCursor, XtRCursor, sizeof(Cursor),
    offset(connection_cursor), XtRString, "dot"},
   {XtNobjectEditorTranslations, XtCTranslations, XtRTranslationTable,
         sizeof(XtTranslations),
    offset(translations), XtRString, (caddr_t)defTranslations},
   {XtNrubberbandObject, XtCBoolean, XtRBoolean, sizeof(Boolean),
    offset(rubberband), XtRImmediate, (caddr_t)False}
};

#undef offset

static void Initialize(Core* c, XEvent* , String* , Cardinal* )
{
   ObjectEditor *oe = (ObjectEditor *)c;
   cerr << "Created: " << oe->Name() << "\n";
   oe->Initialize();
}

static void Refresh(Core* c, XEvent* event , String* , Cardinal* )
{
   ObjectEditor *oe = (ObjectEditor *)c;

   oe->GetLastEvent(event);
   if(event->xexpose.count == 0){
//      cerr << "Expose: " << oe->Name() << "\n";
      oe->RefreshConnections();
   }
}

static void Manager(Core* c, XEvent* event, String* args, Cardinal*)
{
   ObjectEditor *oe = (ObjectEditor *)c;
   char action_type = *args[0];
   switch(action_type) {
    case 'C':			// connecting
//      cerr << "connecting: " << oe->Name() << "\n";
      oe->RubberbandConnection(oe->GetEventLocation(event));
      break;
    case 'A':			// abort connection
      oe->AbortConnection();
      break;
    case 'M':			// moving object
//      cerr << "moving: " << oe->Name() << "\n";
      oe->GetLastEvent(event);
      oe->RubberbandObject(oe->GetEventLocation(event));
      break;
    case 'S':
      cerr << "Place Object: " << oe->Name() << "\n";
      oe->PlaceObject(oe->GetEventLocation(event));
      break;
    case 'P':			// place new object
      cerr << "Place New Object: " << oe->Name() << "\n";
      oe->PlaceNewObject(oe->GetEventLocation(event));
      break;
    default:
      oe->AbortCreatingObject();
      cerr << "ObjectEditorManager: unknown action\n";
   }
}

Boolean ObjectEditor::class_inited = False;

ObjectEditor::ObjectEditor(Widget parent) 
: LayoutWidget("editspace", parent), connection_in_progress(NULL), connection_list(NULL),
  moving_object(NULL)
{
   InitializeClass();

   ObjectEditor_resources res;

   GetSubresources((caddr_t)&res, (String)Name(), "ObjectEditor", 
		   subresources, XtNumber(subresources));
   normal_cursor = res.normal_cursor;
   placement_cursor = res.placement_cursor;
   connection_cursor = res.connection_cursor;
   rubberband        = res.rubberband;
   // XXX
   // this is temporary: eventually both default_translations and user specified
   // translations will be loaded in sequence.
   //   tm.translations = res.translations; // ??? cfront doesn't like it
   SetValues(XtNtranslations,(XtArgVal)res.translations);

   new_object.creating = False;

   grab_mask = ButtonPressMask|ButtonReleaseMask|PointerMotionMask;

   XtGCMask valuemask;
   XGCValues values;

   values.function = GXinvert;
   values.plane_mask = border_pixel ^ background_pixel;
   values.subwindow_mode = IncludeInferiors; 
   valuemask = GCPlaneMask | GCFunction | GCSubwindowMode;
   flip_gc = GetGC(valuemask, &values);

   values.foreground = border_pixel;
   valuemask = GCForeground;
   normal_gc = GetGC(valuemask, &values);

   values.foreground = background_pixel;
   valuemask = GCForeground;
   invert_gc = GetGC(valuemask, &values);

}

void ObjectEditor::InitializeClass()
{
   if(class_inited == False){

#ifndef cfront_not_broken

#include "fixres.h"
      int i=0;
#define offset(field) XtOffset(resource_pointer, field)
      fix_resource(i,offset(normal_cursor));i++;
      fix_resource(i,offset(placement_cursor));i++;
      fix_resource(i,offset(connection_cursor));i++;
      fix_resource(i,offset(translations));i++;
      fix_resource(i,offset(rubberband));i++;
#undef offset

#endif
      GetAppContext()->AddActions(actions, XtNumber(actions));
      
      class_inited = True;
   }
}

XPoint ObjectEditor::GetEventLocation(XEvent* event)
{
   XPoint point;
   Position xroot,yroot,x,y;

   switch (event->xany.type) {
    case ButtonPress:
    case ButtonRelease: 
      xroot = event->xbutton.x_root;
      yroot = event->xbutton.y_root;
      break;
    case KeyPress:
    case KeyRelease:    
      xroot = event->xkey.x_root;
      yroot = event->xkey.y_root;
      break;
    case MotionNotify:  
      xroot = event->xmotion.x_root;
      yroot = event->xmotion.y_root;
      break;
    default:	    
      cerr << "Cannot handle this event.\n";
   }

   TranslateCoords(0,0,x,y);
   point.x = xroot - x;
   point.y = yroot - y;

   return point;
}

void ObjectEditor::GetLastEvent(XEvent* event)
{
   while( XCheckTypedWindowEvent(XtDisplay(),XtWindow(),event->type,event) )
//     cerr << ">>     Event skipped.\n";
     ;
}

void ObjectEditor::RefreshConnections()
{
   // for now we just refresh everything
   if(!moving_object){
      Connection* c = connection_list;
      cerr << "In RefreshConnections()\n";
      while(c){
	 cerr << "Refreshing\n";
	 DrawConnectionLine(c,normal_gc);
	 c = c->next;
      }
   }
}

void ObjectEditor::InitializeConnection(Position xpos=0, Position ypos=0)
{
   XPoint point;
   point.x = xpos; point.y = ypos;
   connection_in_progress->start = point;
   connection_in_progress->end = point;
}

void ObjectEditor::AbortConnection()
{
   if(connection_in_progress){
      cerr << "ObjectEditor::AbortConnection\n";

      UngrabPointer(CurrentTime);			// remove grab
      DrawConnectionLine(flip_gc);
      ClearConnectionWindows(connection_in_progress);
      delete connection_in_progress;
      connection_in_progress=NULL;
   }
}

void ObjectEditor::ClearConnectionWindows(Connection* c)
{
   //  output anchor is always set
   XClearWindow(XtDisplay(),c->output_anchor->XtWindow());
   // connection may be aborted before input anchor is set
   if(c->input_anchor)
      XClearWindow(XtDisplay(),c->input_anchor->XtWindow());

}

void ObjectEditor::StartConnection(ObjectView* output, Anchor* output_anchor)
{
   connection_in_progress = new Connection(output, output_anchor);
   InitializeConnection(output_anchor->X()+output_anchor->Width()/2,
			output_anchor->Y()+output_anchor->Height()/2);
   cerr << "starting connection\n";
   
   int retVal;
   retVal = GrabPointer(True,grab_mask,GrabModeAsync,GrabModeSync,None,
	       connection_cursor,CurrentTime);
   if(retVal != GrabSuccess)
     cerr << "StartConnection: cannot grab pointer\n";
}

void ObjectEditor::CommitConnection(ObjectView* input, Anchor* input_anchor)
{
   if(connection_in_progress){
      cerr << "Commit Connection\n";

      if(connection_in_progress->Connect(input,input_anchor)){
	 UngrabPointer(CurrentTime);			// remove grab

	 ClearConnectionWindows(connection_in_progress);
	 connection_list = connection_in_progress->Push(connection_list);
      }
      else
	AbortConnection();
      connection_in_progress = NULL;
   }
}

void ObjectEditor::RubberbandConnection(XPoint& point)
{
   if(connection_in_progress){
      DrawConnectionLine(flip_gc);
      connection_in_progress->end = point;
      DrawConnectionLine(flip_gc);
   }
}
 
void ObjectEditor::DrawConnectionLine(Connection* c, GC gc)
{
   XDrawLine(XtDisplay(),XtWindow(),gc,
	     c->start.x,c->start.y,
	     c->end.x,c->end.y);
}

void ObjectEditor::DrawConnectionLine(GC gc)
{
   DrawConnectionLine(connection_in_progress, gc);
}

void ObjectEditor::PlaceNewObject(XPoint& point)
{
   if(CreatingObjectP()){
      cerr << "Placing object.\n";
      UngrabPointer(CurrentTime);			// remove grab
      cerr << "Releasing pointer.\n";
      if(point.x > 0 && point.x < Width() && point.y > 0 && point.y < Height())
	CreateObjectView(point.x,point.y);
      else
	AbortCreatingObject();
   }
}

void  ObjectEditor::AddObjectView(String name, 
				     Cardinal input_count, Cardinal output_count)
{
   new_object.name = name;
   new_object.object = NULL;
   new_object.input_count = input_count;
   new_object.output_count = output_count;
   new_object.creating = True;

   int retVal;

   cerr << "**Grabbing Pointer!!!\n";
   retVal = GrabPointer(False,grab_mask,GrabModeAsync,GrabModeSync,None,
	       placement_cursor,CurrentTime);
   if(retVal != GrabSuccess)
     cerr << "AddObjectView: cannot grab pointer\n";
}

ObjectView* ObjectEditor::CreateObjectView(Position xpos, Position ypos) 
{
   ObjectView* object = new ObjectView(this, new_object.name);
   object->Manage();

   cerr << "xpos= " << xpos << " ypos= " << ypos << "\n";
   object->SetValues(XtNx,xpos,XtNy,ypos);

   object->AddInput(new_object.input_count);

   object->AddOutput(new_object.output_count);

   new_object.object = object;
   new_object.creating = False;
   return object;
}

void ObjectEditor::StartMovingObject(ObjectView* object)
{
   int retVal;

   moving_object = object;
   current_position.x = -1;
   current_position.y = -1;

   cerr << "**Grabbing Pointer!!!\n";
   retVal = GrabPointer(False,grab_mask,GrabModeAsync,GrabModeSync,None,
	       placement_cursor,CurrentTime);
   if(retVal != GrabSuccess)
     cerr << "AddObjectView: cannot grab pointer\n";
}

void ObjectEditor::RubberbandObject(XPoint& point)
{
   if(moving_object){
      if(rubberband){
	 if(PointerInsideP(current_position))EraseRubberbandOfObject();
	 current_position = point;
	 if(PointerInsideP(point))DrawRubberbandOfObject();
      }
      else if(PointerInsideP(point)){
	 moving_object->Move(point);
	 UpdateObjectConnections();
      }
   }
}

void ObjectEditor::PlaceObject(XPoint& point)
{
   if(moving_object){
      cerr << "PlaceObject.\n";
      UngrabPointer(CurrentTime);			// remove grab
      if(PointerInsideP(current_position)){
	 EraseRubberbandOfObject();
	 if(PointerInsideP(point)){
	    moving_object->Move(point);
	    UpdateObjectConnections();
	 }
      }
      moving_object = NULL;
      if(!rubberband)		// XXX ?
	RefreshConnections();
   }
}

void ObjectEditor::UpdateObjectConnections()
{
   Connection* c;
   
   cerr << "Updating connections of: " << moving_object->Name() << "\n";
   while(c=moving_object->GetInputConnections()){
      cerr << "Input...";
      DrawConnectionLine(c,invert_gc);
      c->end = RecalculateConnection(c->end, c->input_anchor);
      DrawConnectionLine(c,normal_gc);
   }

   while(c=moving_object->GetOutputConnections()){
      cerr << "Ouput...";
      DrawConnectionLine(c,invert_gc);
      c->start = RecalculateConnection(c->start, c->output_anchor);
      DrawConnectionLine(c,normal_gc);
   }
   cerr << "Done.\n";
}

XPoint& ObjectEditor::RecalculateConnection(XPoint& point, Anchor* anchor)
{
   point.x = anchor->X() + anchor->Width()/2;
   point.y = anchor->Y() + anchor->Height()/2;
   return point;
}
	

Boolean ObjectEditor::PointerInsideP(XPoint& point)
{
   return (point.x >= 0 && point.x < Width() && point.y >= 0 && point.y <= Height());
}

void ObjectEditor::DrawRubberbandOfObject()
{
   XDrawRectangle(XtDisplay(), XtWindow(), flip_gc,
		  current_position.x, current_position.y,
		  moving_object->Width(), moving_object->Height());
}

//
// class Connection
//

Connection* Connection::Connect(ObjectView* i, Anchor* i_anchor)
{ 
   cerr << "Connect:\n";
   input = i; 
   input_anchor = i_anchor;
   output_anchor->Connect(this);
   input_anchor->Connect(this);
   cerr << "Verifying connection.\n";
   if(input != output)
     return this;
   else
     return (Connection*)NULL;
}

