// 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 "LayoutSpace.h"

#include "WindowStream.h"
extern wstream msg;

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

static char defTranslations[] = 
" ~Shift <Btn3Motion>:		LayoutSpaceManager(MoveObject) \n\
 ~Shift <Btn3Up>:		LayoutSpaceManager(StopObject) \n\
 Shift <Btn3Motion>:		LayoutSpaceManager(ResizingObject) \n\
 Shift <Btn3Up>:		LayoutSpaceManager(NewSize) \n\
 <Btn2Up>:		LayoutSpaceManager(Ungrab) \n\
 <Btn1Down>:		LayoutSpaceManager(PlaceNewObject) \n\
 <Map>:              InitializeLayoutSpace()";

static char childTranslations[] = "#override \n\
  ~Shift <Btn3Down>:		LayoutChildManager(StartObject) \n\
  Shift <Btn3Down>:		LayoutChildManager(ResizeObject)";

void LayoutSpaceBindings(wstream& s)
{
   s << "Layout Space Bindings:\n\n";
   s << (char*)defTranslations << "\n";
   s << "\nChild Bindings:\n\n";
   s << (char*)childTranslations << "\n";
}


struct LayoutSpace_resources {
   Cursor      normal_cursor;    
   Cursor      placement_cursor;
   XtTranslations translations;
   XtTranslations child_translations;
   Boolean     rubberband;
};

#define XtNnormalCursor "normalCursor"
#define XtNplacementCursor "placementCursor"
#define XtNlayoutSpaceTranslations "layoutSpaceTranslations"
#define XtNlayoutChildTranslations "layoutChildTranslations"
#define XtNrubberbandObject "rubberbandObject"

typedef LayoutSpace_resources *resource_pointer;

static XtActionsRec actions[] =
{ 
   {"LayoutSpaceManager", Manager},
   {"LayoutChildManager", ChildManager},
   {"InitializeLayoutSpace", Initialize },
};

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

static XtResource subresources[] = {
   {XtNnormalCursor, XtCCursor, XtRCursor, sizeof(Cursor),
    offset(normal_cursor), XtRString, "cross"},
   {XtNplacementCursor, XtCCursor, XtRCursor, sizeof(Cursor),
    offset(placement_cursor), XtRString, "fleur"},
   {XtNlayoutSpaceTranslations, XtCTranslations, XtRTranslationTable,
         sizeof(XtTranslations),
    offset(translations), XtRString, (caddr_t)defTranslations},
   {XtNlayoutChildTranslations, XtCTranslations, XtRTranslationTable,
         sizeof(XtTranslations),
    offset(child_translations), XtRString, (caddr_t)childTranslations},
   {XtNrubberbandObject, XtCBoolean, XtRBoolean, sizeof(Boolean),
    offset(rubberband), XtRImmediate, (caddr_t)False}
};

#undef offset

static void Initialize(Core* c, XEvent* , String* , Cardinal* )
{
   LayoutSpace *ls = (LayoutSpace *)c;
   ls->InitializeWidget();
}

static void ChildManager(Core* c, XEvent* event, String* args, Cardinal*)
{
   LayoutSpace *ls = (LayoutSpace *)c->Parent();
   char action_type = *args[0];
//   cerr << "In ChildManager.\n";
   switch(action_type) {
    case 'S':			// change position of Object
//      cerr << "start moving: " << ls->Name() << "\n";
      ls->StartMovingObject(new WidgetObject(c,(String)c->Name()),ls->GetEventLocation(event));
      break;
    case 'R':
      ls->StartResizingObject(new WidgetObject(c,(String)c->Name()));
      break;
    default:
      cerr << "Child manager: unknown action.\n";
   }
}

static void Manager(Core* c, XEvent* event, String* args, Cardinal*)
{
   LayoutSpace *ls = (LayoutSpace *)c;
   char action_type = *args[0];
   switch(action_type) {
    case 'U':
      ls->UngrabPointer(CurrentTime);			// remove grab
//      cerr << "Ungrab Pointer#\n";
      break;
    case 'M':			// moving Object
//      cerr << "moving: " << ls->Name() << "\n";
      ls->GetLastEvent(event);
      ls->RubberbandObject(ls->GetEventLocation(event));
      break;
    case 'S':
//      cerr << "Place Object: " << ls->Name() << "\n";
      ls->PlaceObject(ls->GetEventLocation(event));
      break;
    case 'P':			// place new Object
//      cerr << "Place New Object: " << ls->Name() << "\n";
//      msg << "Place New Object: " << ls->Name() << "\n" << flush;
      ls->PlaceNewObject(ls->GetEventLocation(event));
      break;
    case 'R':
      ls->GetLastEvent(event);
      ls->RubberbandSize(ls->GetEventLocation(event));
      break;
    case 'N':
      ls->ResizeObject(ls->GetEventLocation(event));
      break;
    default:
      ls->AbortCreatingObject();
      cerr << "LayoutSpaceManager: unknown action\n";
   }
}

Boolean LayoutSpace::class_inited = False;

LayoutSpace::LayoutSpace(Widget parent, ObjectViewBase* holder) 
: LayoutWidget("layoutspace", parent), object_holder(holder)
{
   InitializeClass();

   LayoutSpace_resources res;

   GetSubresources((caddr_t)&res, (String)Name(), "LayoutSpace", 
		   subresources, XtNumber(subresources));
   normal_cursor = res.normal_cursor;
   placement_cursor = res.placement_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 dosn't like it
   SetValues(XtNtranslations,(XtArgVal)res.translations);
   SetValues(XtNaccelerators,(XtArgVal)res.child_translations);

   creating_object = False;
   moving_object = 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);
}

void LayoutSpace::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(translations));i++;
      fix_resource(i,offset(child_translations));i++;
      fix_resource(i,offset(rubberband));i++;
#undef offset

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

XPoint LayoutSpace::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 << "LayoutSpace: Cannot handle this event.\n";
      xroot=-1000;
      yroot=-1000;
   }

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

   return point;
}

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

void LayoutSpace::PlaceNewObject(XPoint& point)
{
   if(CreatingObjectP()){
//      cerr << "Placing object.\n";
      UngrabPointer(CurrentTime);			// remove grab
//      cerr << "Releasing pointer.\n";
//      XSync(XtDisplay(), False); // DDD debugging 

      if(point.x > 0 && point.x < Width() && point.y > 0 && point.y < Height())
	CreateObject(point);
      else
	AbortCreatingObject();
   }
}

void  LayoutSpace::AddObject(ObjectViewBase* object)
{
   object_holder=object;
   creating_object = True;
   
//   object->GetSize(current_width, current_height);
 
   int retVal;

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

void LayoutSpace::CreateObject(XPoint& point)
{
//   cerr << "LayoutSpace: Creating...\n";
   msg << "Object: " << object_holder->name;
   msg << " created.\n" << flush;

//   cerr << "xpos= " << xpos << " ypos= " << ypos << "\n";
   object_holder->Place(point);
   object_holder->MakeEditable(this);
   delete object_holder;
   creating_object = False;
}

void LayoutSpace::AbortCreatingObject()
{ 
   delete object_holder;
   creating_object = False; 
   XBell(XtDisplay(),50);
   msg << "** Aborted **\n" << flush;
}

void LayoutSpace::StartMovingObject(ObjectViewBase* object, XPoint& point)
{
   int retVal;

   object_holder = object;
   moving_object = True;
   object->GetSize(current_width, current_height);
   current_position.x = -1;
   current_position.y = -1;
   object->GetPosition(original_position.x, original_position.y);
   // store the original relative position of the cursor
   original_position.x = point.x - original_position.x;
   original_position.y = point.y - original_position.y;

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

void LayoutSpace::RubberbandObject(XPoint& point)
{
   if(moving_object){
      XPoint new_position;
      new_position.x = point.x - original_position.x;
      new_position.y = point.y - original_position.y;

      if(rubberband){
	 if(PointerInsideP(current_position))EraseRubberbandOfObject();
	 current_position = new_position;
	 if(PointerInsideP(point))DrawRubberbandOfObject();
      }
      else if(PointerInsideP(point)){
	 object_holder->Move(new_position); 
      }
   }
}

void LayoutSpace::PlaceObject(XPoint& point)
{
   if(moving_object){
//      cerr << "PlaceObject.\n";
      UngrabPointer(CurrentTime);			// remove grab
      if(PointerInsideP(current_position)){
	 if(rubberband)
	   EraseRubberbandOfObject();
	 if(PointerInsideP(point)){
	    object_holder->Move(current_position);
	 }
      }
      delete object_holder;
      object_holder = NULL;
      moving_object = False;
   }
}

void LayoutSpace::StartResizingObject(ObjectViewBase* object)
{
   int retVal;

   object_holder = object;
   resizing_object = True;
   object->GetPosition(original_position.x, original_position.y);
   current_position = original_position;
   current_width=current_height=0;
   object->GetSize(original_width, original_height);

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

void LayoutSpace::RubberbandSize(XPoint& point)
{
   if(resizing_object){
      EraseRubberbandOfObject();
      if(point.x > original_position.x && 
	 point.y > original_position.y){
	 current_position = original_position;
	 current_position.x += 1;
	 current_position.y += 1;
	 current_width = point.x - current_position.x;
	 current_height = point.y - current_position.y;
//	 cerr << "ox= " << original_position.x << " oy= " << original_position.y << "\n";
//	 cerr << "cx= " << current_position.x << " cy= " << current_position.y << "\n";
//	 cerr << "px= " << point.x << " py= " << point.y << "\n";
//	 cerr << "cw= " << current_width << " ch= " << current_height << "\n";
      }
      else if(point.x > original_position.x + original_width){
	 current_position.x = original_position.x + 1;
	 current_position.y = point.y;
	 current_width = point.x - original_position.x;
	 current_height = original_position.y + original_height - point.y;
      }
      else if(point.y > original_position.y + original_height){
	 current_position.y = original_position.y + 1;
	 current_position.x = point.x;
	 current_height = point.y - original_position.y;
	 current_width = original_position.x + original_width - point.x;
      }
      else {
	 current_position = point;
	 current_width = original_position.x - point.x + original_width;
	 current_height = original_position.y - point.y + original_height;
//	 cerr << "cx= " << current_position.x << " cy= " << current_position.y << "\n";
//	 cerr << "cw= " << current_width << " ch= " << current_height << "\n";
      }

      DrawRubberbandOfObject();
   }
}

void LayoutSpace::ResizeObject(XPoint& point)
{
   if(resizing_object){
//      cerr << "Resize Object.\n";
      UngrabPointer(CurrentTime);			// remove grab
      EraseRubberbandOfObject();

      if(point.x > original_position.x && 
	 point.y > original_position.y)
	current_position = original_position;

      // correct for rubberbanding ?

      object_holder->Resize(current_position, current_width, current_height);

      delete object_holder;
      object_holder = NULL;
      resizing_object = False;
   }
}

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

void LayoutSpace::DrawRubberbandOfObject()
{
   XDrawRectangle(XtDisplay(), XtWindow(), flip_gc,
		  current_position.x, current_position.y,
		  current_width, current_height);
}

// default object WidgetObject

void WidgetObject::Move(XPoint& p)
{
   widget->SetValues(XtNx, p.x, XtNy, p.y);
}

void WidgetObject::Resize(XPoint& p, Dimension w, Dimension h)
{
   widget->SetValues(XtNx, p.x, XtNy, p.y,
		     XtNwidth, w, XtNheight, h);
}

void WidgetObject::Place(XPoint& p)
{
//   cerr << "WidgetObject::Place\n";
   Move(p);
   widget->Manage();
}

void WidgetObject::GetSize(Dimension& w, Dimension& h)
{
   w = widget->Width(); h = widget->Height();
}

void WidgetObject::GetPosition(Position& xpos, Position& ypos)
{
   xpos = widget->X(); ypos = widget->Y();
}

void WidgetObject::MakeEditable(LayoutSpace* lay)
{
//   cerr << "WidgetObject::MakeEditable.\n";
//   cerr << "widget= " << hex << widget << "\n";
//   widget->InstallAccelerators(lay);

// Fake accelerators
// Hope that child's translations don't clash with the editing translations
// We have both Edit mode and Play mode active
// Eventually there will be separate Edit and Play modes

   ArgArray args;
   XtTranslations tr;
   args(XtNaccelerators)=XtArgVal(&tr);
   lay->GetValues(args);
   widget->OverrideTranslations(tr);
//   widget->AugmentTranslations(tr);
}
