/* ==================================================== ======== ======= *
 *
 *  uuevent.cc
 *  Ubit Project [Elc][beta1][2001]
 *  Author: Eric Lecolinet
 *
 *  Part of the Ubit Toolkit: A Brick Construction Game Model for Creating GUIs
 *
 *  (C) 1999-2001 Eric Lecolinet @ ENST Paris
 *  WWW: http://www.enst.fr/~elc/ubit   Email: elc@enst.fr (subject: ubit)
 *
 * ***********************************************************************
 * COPYRIGHT NOTICE : 
 * THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY AND WITHOUT EVEN THE 
 * IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. 
 * YOU CAN REDISTRIBUTE IT AND/OR MODIFY IT UNDER THE TERMS OF THE GNU 
 * GENERAL PUBLIC LICENSE AS PUBLISHED BY THE FREE SOFTWARE FOUNDATION; 
 * EITHER VERSION 2 OF THE LICENSE, OR (AT YOUR OPTION) ANY LATER VERSION.
 * SEE FILES 'COPYRIGHT' AND 'COPYING' FOR MORE DETAILS.
 * ***********************************************************************
 *
 * ==================================================== [Elc:01] ======= *
 * ==================================================== ======== ======= */

//pragma ident	"@(#)uuevent.cc	   ubit:b1.11.0"
#include <udefs.hh>
#include <ubrick.hh>
#include <ubox.hh>
#include <ustr.hh>
#include <uevent.hh>
#include <ucontext.hh>
#include <uview.hh>
#include <uviewImpl.hh>
#include <ugraph.hh>
#include <X11/Xlib.h>

#define ANY_BUTTON (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask)

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
// effectue l'intersection de *this avec clip2
// *this est mis a jour (contient l'intersection), clip2 ne change pas
// renvoie:
// * false si pas d'intersection (region inchangee)
// * true sinon si region est incluse dans clip (ie. clipping inutile)

// OLD: renvoie:
// OLD: * 0 si pas d'intersection
// OLD: * 2 si region est incluse dans clip (ie. clipping inutile)
//  OLD:* 1 sinon (il il a une intersection ou clip inclus dans region)

u_bool URegion::setInter(const URegion &clip2) {
  //OLD int stat = 0;
  u_bool stat = false;

  // *** en x

  if (clip2.x < x) {
    if (clip2.x + clip2.width < x) {
      width = height = 0;	// securite !
      return false;// no inter
    }

    // fait: width = UMIN(x + width, clip2.x + clip2.width) - x;

    else if (x + width <= clip2.x + clip2.width) {
      //x = x;
      //width = x + width -x;
      //OLD!!! stat = 2; //region incluse horizontalement dans clip (clip inutile)
      stat = true;
    }
    else {
      //x = x;
      width = clip2.x + clip2.width -x;
      //OLD stat = 1;			// inter simple
      stat = true;
    }
  }

  else {    // (x <= clip2.x)
    if (x + width <= clip2.x) {
      width = height = 0;	// securite !
      //OLD return 0;// no inter
      return false;
    }

    // width = UMIN(x + width, clip2.x + clip2.width) - clip2.x;

    else if (x + width <= clip2.x + clip2.width) {
      width = x + width - clip2.x;
      x = clip2.x;		// !att a l'ordre!
      //OLDstat = 1;	
      stat = true;
    }
    else {
      width = clip2.width; 
      x = clip2.x;
      //OLD stat = 1;	  // clip inclus dans region (renvoie 1: clip necessaire)
      stat = true;
    }
  }

  // *** en y

  if (clip2.y < y) {
    if (clip2.y + clip2.height < y) {
      width = height = 0;	// securite !
      return 0;// no inter
    }

    // fait: height = UMIN(y + height, clip2.y + clip2.height) - y;

    else if (y + height <= clip2.y + clip2.height) {
      //y = y;
      //height = y + height -y;
      // region incluse horizontalement
      // *ET* verticalement dans clip (clip inutile)
      //OLD stat = (stat == 2) ? 2 : 1;
      stat = true;
    }
    else {
      //y = y;
      height = clip2.y + clip2.height -y;
      //OLD stat = 1;			// inter simple
      stat = true;
    }
  }

  else {    // (y <= clip2.y)
    if (y + height <= clip2.y) {
      width = height = 0;	// securite !
      return false;// no inter
    }

    // height = UMIN(y + height, clip2.y + clip2.height) - clip2.y;
    else if (y + height <= clip2.y + clip2.height) {
      height = y + height - clip2.y;
      y = clip2.y;		// !att a l'ordre!
      //OLD stat = 1;
      stat = true;
    }
    else {
      height = clip2.height; 
      y = clip2.y;
      //OLD stat = 1;	  // clip inclus dans region (renvoie 1: clip necessaire)
      stat = true;
    }
  }

  return stat;
}

u_bool URegion::setInter(const URegion *clip2) {
  return setInter(*clip2);
}
u_bool URegion::setInter(u_pos xx, u_pos yy, u_dim w, u_dim h) {
  URegion r(xx,yy,w,h);
  return setInter(r);
}

// les regions vides ne sont pas prises en compte !
void URegion::setClosure(const URegion &clip2) {
  if (width <= 0 || height <= 0) {
    *this = clip2;
  }
  else if (clip2.width <= 0 || clip2.height <= 0) {
    /*nop*/;
  }
  else {
    if (x > clip2.x) x = clip2.x;
    if (y > clip2.y) y = clip2.y;

    if (x + width < clip2.x + clip2.width) 
      width = clip2.x + clip2.width - x;
    if (y + height < clip2.y + clip2.height) 
      height = clip2.y + clip2.height - y;
  }
}

void URegion::setClosure(const URegion *clip2) {
  return setClosure(*clip2);
}
void URegion::setClosure(u_pos xx, u_pos yy, u_dim w, u_dim h) {
  URegion r(xx,yy,w,h);
  return setClosure(r);
}

/* ==================================================== ======== ======= */
/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
//!Note: 'win_view' must be a valid window view or 'null'

UEvent::UEvent(u_eid event_id, UView *win_view, UX_Event ev) {
  eid = event_id;
  xev = ev;
  cond = null;
  time = 0;
  winView    = win_view;
  sourceView = null;
  source     = null;
  parentContext = null;
  sourceContext = null;
  redrawStatus = false; 
  if (win_view) redrawClip.set(win_view); else redrawClip.set(0,0,0,0);
  xmouse = ymouse = 0;
  xdrag_ref = ydrag_ref = 0;
  detail = 0;
  through = null;
  propagate = false;
  flags = null;
  exts  = null;
  flagCount = extCount = 0;
  firstTranspView = null;
}

UEvent::~UEvent() {
  // flags and extensions vectors are shared by all stacked Contexts
  // where they are refererenced through pointers named respectively
  // pflags and pexts
  // thus, they must be freed by ~UEvent (if not null) and only there
  if (flags) free(flags); flags = null; flagCount = 0;
  if (exts) free(exts); exts = null; extCount = 0;

  if (parentContext) delete parentContext;
  parentContext = null;

  // note: les flags et exts des 2 contextes sont partages
  // mais comme c'est bien fait (parentContext est un UWinContext
  // qui detruit ces champs alors que sourceContext est un UContext
  // qui ne les detruits) il n'y a (normalement) pas de probleme
  if (sourceContext) delete sourceContext;
  sourceContext = null;
}

// adds a Flag to the Event (for transparent tools)
void UEvent::addFlag(const UFlag* flag) {
  if (flags)
    flags = (UFlag**) realloc(flags, (flagCount+1) * sizeof(UFlag*));
  else 
    flags = (UFlag**) malloc(sizeof(UFlag*));

  flags[flagCount] = (UFlag*) flag;
  flagCount++;
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */

void UEvent::setViewSource(UView *v) {
  sourceView = v;
  source = (v ? v->getBox() : null);
}

void UEvent::setGroupSource(UGroup *grp) {
  sourceView = null;
  source = grp;
}

void UEvent::setItemSource(UItem *it) {    //!!! A COMPLETER!!!
}
void UEvent::setPropSource(UProp *prop) {   //!!! A COMPLETER!!!
}

UGroup *UEvent::getSource() {
  return source;
}

UView* UEvent::getWinView() {
  return winView;
}

UWin* UEvent::getWin() {
  return (winView ? winView->getHardwin() : null);
}

u_time UEvent::getTime() {return time;} 

//NB: getClickCount et getKeyChar utilisent le meme champ!
//-- initialise dans disarmBehavior()
int UEvent::getClickCount() {return (int)detail;} 

//-- initialise dans typeBehavior()
int UEvent::getKeyChar() {return (int)detail;}

// return the location of the mouse relatively to the source VIEW
//NOTES: dans le cas des drags, xmouse, xmouse est relatif a la hardwin
//sur laquelle la souris se trouve actuellement et non celle qui a initie le Drag.
// ==> le calcul est fait en absolu sur les screen coords, *drag_ref etant les
//screen coords de la vue sur laquelle on a fait le Press 

u_pos UEvent::getX() {
  if (eid == mdrag) return xev->xbutton.x_root - xdrag_ref;
  return sourceView ? xmouse - sourceView->x : -1;
}
u_pos UEvent::getY() {
  if (eid == mdrag) return xev->xbutton.y_root - ydrag_ref;
  return sourceView ? ymouse - sourceView->y : -1;
}

// (available for mouse and key events)
// !!!ATT: normalement le nom du champ varie suivant type d'event
// (en pratique c'est tjrs le meme champ)

u_pos UEvent::getXscreen() {
  return xev->xbutton.x_root;
}
u_pos UEvent::getYscreen() {
  return xev->xbutton.y_root;
}

// returns a combination of key and mouse button modifiers
// --- can be used in all Key and Mouse Button callbacks
int UEvent::getMods() {
  return xev ? xev->xbutton.state : 0;
}

// returns a mask that identifies which mouse buttons are being pressed
// (and 0 if none)
int UEvent::getButtons() {
  return xev ? (xev->xbutton.state & ANY_BUTTON) : 0;
}

// returns the pressed button number when available
// --- can only be used in 'UOn::press' and 'UOn::release' callbacks
int UEvent::getButtonNumber() {
  if (xev && (xev->type == ButtonPress || xev->type == ButtonRelease))
    return xev->xbutton.button;
  else return 0;
}

// returns the X KeySym (but without taking modifiers into account)
// --- can be used in all Key callbacks
long UEvent::getKeySym() {
  if (xev && (xev->type == KeyPress || xev->type == KeyRelease))
    // inde=0 -> considere la table standard sans tenir compte des SHIFT
    return XKeycodeToKeysym(xev->xany.display, xev->xkey.keycode, 0);
  else return 0;
}

/***
// Button Number when the mouse is dragged
// Note: returns 0 if the mouse is moved without pressing any button
int UEvent::draggedButton() {
  uint stm = xev->xmotion.state & ANY_BUTTON;
  if (stm == 0) return 0;
  if (stm & Button1Mask) return 1;
  if (stm & Button2Mask) return 2;
  if (stm & Button3Mask) return 3;
  return 0;
}
void UEvent::queryMouse() {
  Display *display;
  Window w;
  Window *root_return, *child_return;
  int *root_x_return, *root_y_return;
  int *win_x_return, *win_y_return;
  unsigned int mask_return;
  // ca suffit pas: faut trouver la UWin
  Bool XQueryPointer(display, w, root_return, child_return,
		     root_x_return, root_y_return,
		     win_x_return, win_y_return,
  e(UEvent::QueryMouse, winview, x_in_win, y_in_win, null);
}
***/

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */

UContext* UEvent::getParentContext() {
  if (!parentContext)
    uerror("UEvent::getParentContext", "Undefined context!");
  return parentContext;
}

UContext* UEvent::getSourceContext(int pos) {
  if (!parentContext || !source || !sourceView) {
    uerror("UEvent::getSourceContext", "no source or undefined context!");
    return null;
  }

  if (!sourceContext) {
    sourceContext = new UContext(source, sourceView, *parentContext);
    int count = 0;

    for (ULink *ch = source->getChildLinks(); ch; ch = ch->next()) {
      if (count == pos) break;
      count++;
      if (ch->verifies(sourceContext, source)) {
	UBrick *b = ch->brick();
	UProp *prop;
	if ((prop = b->propCast())) prop->putProp(sourceContext, source);
      }
    }
  }
  return sourceContext;
}

// RECOPIE du context (pas un simple set!)
// att: usage special, l'EVENT REFERANT DOIT ETRE LE MEME!!! 
// fonction utilisee indirectement via locateSource

void UEvent::dumpParentContext(const UContext &ctx) {
  if (parentContext)  *((UContext*)parentContext) = ctx;
  //if (parentContext)  *(getParentContext()) = ctx;
  else uerror("UEvent::copyContext", "undefined context");
}

/* ==================================================== ======== ======= */
// RECOPIE de tout le contenu AINSI que celui des champs pointes

void UEvent::copy(const UEvent& e2) {
  int k;
  if (flags) free(flags); flags = null; flagCount = 0;
  if (exts) free(exts); exts = null; extCount = 0;

  if (parentContext) delete parentContext;
  parentContext = null;

  // recopie globale des champs
  *this = e2;

  // ALLOUER e recopier le CONTENU des champs pointes vu qu'ils seront
  // automatiquement detruits par le destructeur de UWinContext
  if (e2.parentContext) {
    parentContext = new UWinContext();
    // this method recopies the internal flags and exts arrays
    parentContext->copy(*e2.parentContext);
  }

  if (e2.flags) {
    flags = (UFlag**) malloc(flagCount * sizeof(UFlag*));
    for (k = 0; k < flagCount; k++) flags[k] = e2.flags[k];
  }
  if (e2.exts) {
    exts = (UBrick**) malloc(extCount * sizeof(UBrick*));
    for (k = 0; k < extCount; k++) exts[k] = e2.exts[k];
  }
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
// returns UView if found and null otherwise

UView *UEvent::locateSource(u_pos x_in_win, u_pos y_in_win) {
  if (!winView) {
    uerror("UEvent::locateSource", "NULL window view: can't localize");
    return null;
  }
  xmouse = x_in_win;
  ymouse = y_in_win;

  // e->firstTranspView = the first view that is transparent in the tree
  firstTranspView = null;

  parentContext = new UWinContext(winView); //!!
  //NB: parentContext detruit par ~UEvent

  sourceView = winView->locateSource(this, parentContext, redrawClip, null);
  ///sourceView = winView->locateSource(this, parentContext, redrawClip);
  source = (sourceView ? sourceView->getBox() : null); //!!

  if (through != null) {
    redrawStatus = 0;
    redrawClip.set(winView);
    firstTranspView = null;
    sourceView = winView->locateSource(this, parentContext, redrawClip, null);
    ///sourceView = winView->locateSource(this, parentContext, redrawClip);
    source = (sourceView ? sourceView->getBox() : null); //!!
  }

  return sourceView;
}

/* ==================================================== ======== ======= */

UView *UEvent::locateSource(UView * searched_view) {
  if (!winView) {
    uerror("UEvent::locateSource", "NULL window view: can't localize");
    return null;
  }
  // e->firstTranspView = the first view that is transparent in the tree
  firstTranspView = null;

  parentContext = new UWinContext(winView); //!!
  //NB: parentContext detruit par ~UEvent

  sourceView = 
    winView->locateSource(this, parentContext, redrawClip, searched_view);
  source = (sourceView ? sourceView->getBox() : null);

  if (through != null) {
    redrawStatus = 0;
    redrawClip.set(winView);
    firstTranspView = null;
    sourceView = 
      winView->locateSource(this, parentContext, redrawClip, searched_view);
    source = (sourceView ? sourceView->getBox() : null);
  }

  return sourceView;
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
// return the LOCAL coordinates of the origine of the Item
// (location is relative to the origin of the SOURCE View)

u_pos UItemData::getX(class UEvent *e) {
  return e->getView()->XwinToX(region.x);
}

u_pos UItemData::getY(class UEvent *e) {
  return e->getView()->YwinToY(region.y);
}

// return the width and the height of the Item
u_dim UItemData::getWidth()  {return region.width;}
u_dim UItemData::getHeight() {return region.height;}


UItemData::UItemData() {
  item     = null;
  itemLink = null;
  strpos   = -1;
  strpos2  = -1;       //!must be se to -1 if unused
  region.set(0,0,0,0); //!important pour set/union
  itemCtx  = null;     //!important pour ~UItemData
  exactMatch = false;
}

UItemData::~UItemData() {
  if (itemCtx) delete itemCtx;
}

// -- exact_match   = true  : Item is exactly located under the Mouse
//                    false : Item is the last Item before the Mouse position
// -- merge_regions = true  : region 'r' is merged with the previous 'region'
//                    false : region 'r' replaces the previous value of 'region'
//
void UItemData::set(UContext &ctx, UItem *_item, ULink *_link,
		    const URegion &r, int _strpos, u_bool exact_match) {
  item     = _item;
  itemLink = _link;
  strpos   = _strpos;
  //don't change strpos2!
  if (!itemCtx) itemCtx  = new UContext(ctx);
  exactMatch = exact_match;
  region = r;
}

void UItemData::merge(UContext &ctx, UItem *_item, ULink *_link, 
		      const URegion &r, u_bool exact_match) {
  item     = _item;
  itemLink = _link;
  //don't change strpos and strpos2!
  if (!itemCtx) itemCtx  = new UContext(ctx);
  exactMatch = exact_match;
  region.setClosure(r);
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
// Returns the UItem child that was clicked in this box (if any)
// Notes:
// - can return null
// - argument UItemData is filled with information that is related 
//   to the selected item

UItem* UEvent::getItem() {
  UItemData itd;
  return searchItem(itd, null, 0);
}

UItem* UEvent::getItem(UItemData &itd) {
  return searchItem(itd, null, 0);
}

// same as getItem() but discards UItems that are not Strings
UStr* UEvent::getStr(){
  UItemData itd;
  UItem *item = getItem(itd);
  return (item ? item->strCast() : null);
}

UStr* UEvent::getStr(UItemData &itd) {
  UItem *item = getItem(itd);
  return (item ? item->strCast() : null);
}

// NB: cette fct initialise entre-autres:
//     - itemLink : item clique
//     - region   : zone se trouve l'item
//     - props    : props associees a cet item

UItem* UEvent::searchItem(UItemData &itd, const UItem *searched_item,
			  int strpos1, int strpos2) {

  UViewUpdate vup(searched_item ? UViewUpdate::LOCATE_ITEM_PTR
		  : UViewUpdate::LOCATE_ITEM_POS);
  //if(searched_item)
  //  printf(">>searchItem(%d at strpos %d : %d) : e=%d, xmouse=%d, ymouse=%d\n",
  //	   searched_item, strpos1, strpos2, this, xmouse, ymouse);
  //else 
  //  printf(">>getItem: e=%d, xmouse=%d, ymouse=%d \n",this, xmouse, ymouse);

  vup.e = this;
  vup.itemData = &itd;
  itd.itemLink = null;
  itd.item     = (UItem*)searched_item;
  itd.strpos   = strpos1;
  itd.strpos2  = strpos2;

  URegion clip(sourceView);
  sourceView->doUpdate(*parentContext, clip, clip, vup);

  if (itd.itemLink) {
    //printf("found item %d at strpos %d: %d\n",itd.item,itd.strpos,itd.strpos2);
    return itd.item;
  }
  else {
    //printf("!NOT found item %d at strpos %d-%d\n",itd.item,itd.strpos,itd.strpos2);
    return null;
  }
}

/* ==================================================== [Elc:00] ======= */
/* ==================================================== ======== ======= */
// Cette fct recupere les props heritees via le graphe d'instanciation
// ATTENTION:
// -1- les props de cette UBox ne sont pas prises en compte
//     (ie. l'heritage s'arrete au PARENT de cette UBox)
// -2- cette fct n'a de sens et ne doit etre utilisee QUE SI l'objet 
//     est VISIBLE (car elle fait appel a getSource)
/*
UContext* UBox::getProps(UView *view) {

  if (!view->getWinView()) return null;
  UEvent e(UEvent::search, view->getWinView(), view);

  if (e.sourceView != view) return null;
  else if (!e.sourceProps) return null;
  else {
    UContext*p = new UContext();
    *p = *(e.sourceProps);
    return p;
  }
}
*/

/* ==================================================== [TheEnd] ======= */
/* ==================================================== [Elc:01] ======= */

